From 5f91fbed376b5165d76f7c2e0a43d17d025ab79a Mon Sep 17 00:00:00 2001 From: Matthew Hanson Date: Sat, 20 Jun 2026 16:35:02 -0400 Subject: [PATCH 1/2] fix(docs): add working `ingest:example` script and correct quickstart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README and quickstart referenced `npm run ingest:example` (which did not exist) and stated that `docker compose up` starts the API — it only starts OpenSearch and LocalStack, so `curl localhost:3000` failed (#1084). - Add `examples/sample-collection.json` + `examples/sample-item.json` and a `bin/ingest-example.mjs` script (wired to `npm run ingest:example`) that posts them to the local API via the Transaction extension. It gives a clear hint if the API isn't running and retries the item create while the new collection becomes searchable. - Correct the README quick start and quickstart Step 1 to `npm install` + `npm run serve` (the API), and clarify what `docker compose` actually starts. Closes #1084. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 12 +++--- bin/ingest-example.mjs | 63 ++++++++++++++++++++++++++++++ docs/getting-started/quickstart.md | 16 ++++++-- examples/sample-collection.json | 16 ++++++++ examples/sample-item.json | 23 +++++++++++ package.json | 3 +- 6 files changed, 124 insertions(+), 9 deletions(-) create mode 100755 bin/ingest-example.mjs create mode 100644 examples/sample-collection.json create mode 100644 examples/sample-item.json diff --git a/README.md b/README.md index ae1816c3..63f2983f 100644 --- a/README.md +++ b/README.md @@ -48,17 +48,19 @@ Stac-server is a production-ready implementation of the [STAC API specification] Get started with Docker Compose for local development: ```bash -# Clone the repository +# Clone and install git clone https://github.com/stac-utils/stac-server.git cd stac-server +npm install -# Start services +# Start OpenSearch and LocalStack docker compose up -d -# Ingest sample data -npm run ingest:example +# Start the STAC API on http://localhost:3000 (leave this running) +npm run serve -# Test the API +# In a separate terminal: ingest sample data, then query the API +npm run ingest:example curl http://localhost:3000/ ``` diff --git a/bin/ingest-example.mjs b/bin/ingest-example.mjs new file mode 100755 index 00000000..358600c0 --- /dev/null +++ b/bin/ingest-example.mjs @@ -0,0 +1,63 @@ +#!/usr/bin/env node +// Ingest the example collection and item into a locally running stac-server +// via the Transaction extension. Requires the API to be running (npm run serve) +// with ENABLE_TRANSACTIONS_EXTENSION=true (which `npm run serve` sets). + +import { readFile } from 'node:fs/promises' +import { fileURLToPath } from 'node:url' +import path from 'node:path' + +const API = (process.env.STAC_API_URL || 'http://localhost:3000').replace(/\/$/, '') +const examplesDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'examples') + +const loadJson = async (file) => + JSON.parse(await readFile(path.join(examplesDir, file), 'utf8')) + +const post = async (url, body, label, { tolerate404 = false } = {}) => { + let res + try { + res = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }) + } catch { + console.error(`\nCould not reach the STAC API at ${API}.`) + console.error('Make sure the services and API are running:') + console.error(' docker compose up -d # OpenSearch + LocalStack') + console.error(' npm run serve # STAC API on :3000 (in a separate terminal)\n') + process.exit(1) + } + // 409 = already exists, fine for a re-runnable example. 404 may be transient + // while a newly created collection becomes searchable; the caller retries. + if (res.status === 404 && tolerate404) return res.status + if (!res.ok && res.status !== 409) { + console.error(`Failed to ${label}: ${res.status} ${res.statusText}`) + console.error(await res.text()) + process.exit(1) + } + const note = res.status === 409 ? ' (already exists)' : '' + console.log(` ${label}: ${res.status}${note}`) + return res.status +} + +const sleep = (ms) => new Promise((resolve) => { setTimeout(resolve, ms) }) + +const collection = await loadJson('sample-collection.json') +const item = await loadJson('sample-item.json') + +console.log(`Ingesting example data into ${API}`) +await post(`${API}/collections`, collection, `create collection '${collection.id}'`) + +// A newly created collection isn't immediately searchable (OpenSearch refreshes +// ~1s after a write), and the item endpoint validates the collection exists, so +// retry a few times on 404 before giving up. +const itemsUrl = `${API}/collections/${collection.id}/items` +for (let attempt = 1; ; attempt += 1) { + const status = await post(itemsUrl, item, `create item '${item.id}'`, { tolerate404: attempt < 10 }) + if (status !== 404) break + await sleep(1000) +} + +console.log('\nDone. Try:') +console.log(` curl '${API}/search?collections=${collection.id}'`) diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 5e516bd2..a92a750a 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -11,18 +11,28 @@ Get STAC Server running with sample data in 5 minutes. ## Step 1: Start the Services ```bash -# Clone the repository +# Clone and install git clone https://github.com/stac-utils/stac-server.git cd stac-server +npm install -# Start services +# Start OpenSearch and LocalStack docker compose up -d # Wait for services to be ready (about 30 seconds) sleep 30 + +# Start the STAC API (leave this running in its own terminal) +npm run serve ``` -This starts OpenSearch and the STAC API on `http://localhost:3000`. +`docker compose` starts OpenSearch and LocalStack; `npm run serve` runs the STAC API +on `http://localhost:3000` with the Transaction extension enabled (so the steps below +can create collections and items). + +> **Tip:** Once the API is running, you can load a ready-made sample collection and item +> in one step with `npm run ingest:example`, then skip to [Step 5](#step-5-search-for-items). +> The steps below walk through doing it manually. ## Step 2: Verify the API diff --git a/examples/sample-collection.json b/examples/sample-collection.json new file mode 100644 index 00000000..6b85335f --- /dev/null +++ b/examples/sample-collection.json @@ -0,0 +1,16 @@ +{ + "id": "sample-collection", + "type": "Collection", + "stac_version": "1.0.0", + "description": "A sample collection", + "license": "proprietary", + "extent": { + "spatial": { + "bbox": [[-180, -90, 180, 90]] + }, + "temporal": { + "interval": [["2020-01-01T00:00:00Z", null]] + } + }, + "links": [] +} diff --git a/examples/sample-item.json b/examples/sample-item.json new file mode 100644 index 00000000..c94290da --- /dev/null +++ b/examples/sample-item.json @@ -0,0 +1,23 @@ +{ + "type": "Feature", + "stac_version": "1.0.0", + "id": "sample-item", + "collection": "sample-collection", + "geometry": { + "type": "Point", + "coordinates": [-122.4, 37.8] + }, + "bbox": [-122.4, 37.8, -122.4, 37.8], + "properties": { + "datetime": "2024-01-15T10:30:00Z", + "title": "Sample Item" + }, + "assets": { + "thumbnail": { + "href": "https://example.com/thumbnail.jpg", + "type": "image/jpeg", + "title": "Thumbnail" + } + }, + "links": [] +} diff --git a/package.json b/package.json index 569bd186..8fb277bf 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "package": "sls package", "serve": "REQUEST_LOGGING_FORMAT=dev LOG_LEVEL=debug STAC_API_URL=http://localhost:3000 ENABLE_TRANSACTIONS_EXTENSION=true nodemon --exec node --import=tsx ./src/lambdas/api/local.ts", "build-api-docs": "npx @redocly/cli build-docs src/lambdas/api/openapi.yaml -o ./docs/api-spec.html", - "prepare": "husky" + "prepare": "husky", + "ingest:example": "node bin/ingest-example.mjs" }, "ava": { "verbose": true, From c1a55fceeb6b066cbf7553b10537803caded8738 Mon Sep 17 00:00:00 2001 From: Matthew Hanson Date: Sat, 20 Jun 2026 17:21:32 -0400 Subject: [PATCH 2/2] chore: lint ingest-example script and fix transactions troubleshooting Per review: - Rename bin/ingest-example.mjs -> .js so it's covered by `npm run lint` (the repo is ESM, and the eslint script only globs .js/.ts). - Correct the quickstart "Transaction extension errors" section, which still toggled transactions via `docker compose` even though the API now runs via `npm run serve`. Co-Authored-By: Claude Opus 4.8 (1M context) --- bin/{ingest-example.mjs => ingest-example.js} | 5 ++++- docs/getting-started/quickstart.md | 9 ++++----- package.json | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) rename bin/{ingest-example.mjs => ingest-example.js} (91%) diff --git a/bin/ingest-example.mjs b/bin/ingest-example.js similarity index 91% rename from bin/ingest-example.mjs rename to bin/ingest-example.js index 358600c0..f451fd81 100755 --- a/bin/ingest-example.mjs +++ b/bin/ingest-example.js @@ -53,11 +53,14 @@ await post(`${API}/collections`, collection, `create collection '${collection.id // ~1s after a write), and the item endpoint validates the collection exists, so // retry a few times on 404 before giving up. const itemsUrl = `${API}/collections/${collection.id}/items` +const label = `create item '${item.id}'` +/* eslint-disable no-await-in-loop -- sequential retry is intentional */ for (let attempt = 1; ; attempt += 1) { - const status = await post(itemsUrl, item, `create item '${item.id}'`, { tolerate404: attempt < 10 }) + const status = await post(itemsUrl, item, label, { tolerate404: attempt < 10 }) if (status !== 404) break await sleep(1000) } +/* eslint-enable no-await-in-loop */ console.log('\nDone. Try:') console.log(` curl '${API}/search?collections=${collection.id}'`) diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index a92a750a..eea5d1d4 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -187,11 +187,10 @@ Wait for "Node started" message. ### Transaction extension errors -The Transaction extension is enabled by default in Docker Compose. To disable: - -```bash -ENABLE_TRANSACTIONS_EXTENSION=false docker compose up -``` +The Transaction extension (required for the `POST` steps and `npm run ingest:example` +above) is enabled by `npm run serve`. If you run the API another way and those +requests return 404, make sure `ENABLE_TRANSACTIONS_EXTENSION=true` is set for the +server process. ## Next Steps diff --git a/package.json b/package.json index 8fb277bf..ec5ca5e0 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "serve": "REQUEST_LOGGING_FORMAT=dev LOG_LEVEL=debug STAC_API_URL=http://localhost:3000 ENABLE_TRANSACTIONS_EXTENSION=true nodemon --exec node --import=tsx ./src/lambdas/api/local.ts", "build-api-docs": "npx @redocly/cli build-docs src/lambdas/api/openapi.yaml -o ./docs/api-spec.html", "prepare": "husky", - "ingest:example": "node bin/ingest-example.mjs" + "ingest:example": "node bin/ingest-example.js" }, "ava": { "verbose": true,