@crvouga/atlas 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +178 -604
- package/dist/bun/src/driver.d.ts +41 -7
- package/dist/bun/src/driver.js +47 -6
- package/dist/bun/src/index.d.ts +8 -0
- package/dist/bun/src/index.js +8 -0
- package/dist/core/src/cli.d.ts +14 -1
- package/dist/core/src/cli.js +30 -1
- package/dist/core/src/config.d.ts +70 -4
- package/dist/core/src/config.js +62 -3
- package/dist/core/src/events/cloudevents.d.ts +54 -9
- package/dist/core/src/events/cloudevents.js +60 -9
- package/dist/core/src/graph.d.ts +21 -0
- package/dist/core/src/graph.js +26 -0
- package/dist/core/src/index.d.ts +11 -0
- package/dist/core/src/index.js +11 -0
- package/dist/core/src/lint.d.ts +30 -0
- package/dist/core/src/lint.js +13 -0
- package/dist/core/src/plan.d.ts +39 -1
- package/dist/core/src/plan.js +28 -1
- package/dist/core/src/privacy.d.ts +29 -2
- package/dist/core/src/privacy.js +19 -2
- package/dist/core/src/report/formats.d.ts +34 -4
- package/dist/core/src/report/formats.js +24 -4
- package/dist/core/src/report/markdown.d.ts +15 -0
- package/dist/core/src/report/markdown.js +8 -0
- package/dist/core/src/run/clients.d.ts +44 -5
- package/dist/core/src/run/clients.js +46 -2
- package/dist/core/src/run/execute.d.ts +69 -16
- package/dist/core/src/run/execute.js +67 -16
- package/dist/core/src/run/frames.d.ts +13 -4
- package/dist/core/src/run/frames.js +19 -4
- package/dist/core/src/run/http.d.ts +34 -1
- package/dist/core/src/run/http.js +14 -1
- package/dist/core/src/run/types.d.ts +76 -3
- package/dist/core/src/run/types.js +19 -3
- package/dist/core/src/signals.d.ts +49 -5
- package/dist/core/src/signals.js +22 -5
- package/dist/core/src/spec/compose.d.ts +23 -1
- package/dist/core/src/spec/compose.js +10 -1
- package/dist/core/src/spec/index.d.ts +9 -1
- package/dist/core/src/spec/index.js +9 -1
- package/dist/core/src/spec/load.d.ts +22 -1
- package/dist/core/src/spec/load.js +31 -1
- package/dist/core/src/spec/parse.d.ts +16 -2
- package/dist/core/src/spec/parse.js +25 -2
- package/dist/core/src/spec/scxml.d.ts +25 -4
- package/dist/core/src/spec/scxml.js +36 -4
- package/dist/core/src/spec/types.d.ts +52 -1
- package/dist/core/src/spec/types.js +24 -0
- package/dist/detox/src/index.d.ts +54 -11
- package/dist/detox/src/index.js +45 -7
- package/dist/playwright/src/driver.d.ts +39 -3
- package/dist/playwright/src/driver.js +27 -3
- package/dist/playwright/src/index.d.ts +10 -0
- package/dist/playwright/src/index.js +10 -0
- package/dist/playwright/src/media.d.ts +50 -4
- package/dist/playwright/src/media.js +55 -4
- package/dist/playwright/src/signals.d.ts +44 -5
- package/dist/playwright/src/signals.js +29 -5
- package/dist/reports/src/attachment-files.d.ts +44 -0
- package/dist/reports/src/attachment-files.js +121 -0
- package/dist/reports/src/attachment-response.d.ts +31 -0
- package/dist/reports/src/attachment-response.js +55 -0
- package/dist/reports/src/aws-credentials.d.ts +48 -0
- package/dist/reports/src/aws-credentials.js +221 -0
- package/dist/reports/src/browser-navigation.d.ts +14 -0
- package/dist/reports/src/browser-navigation.js +7 -0
- package/dist/reports/src/bundle.d.ts +34 -2
- package/dist/reports/src/bundle.js +38 -2
- package/dist/reports/src/catalog.d.ts +34 -3
- package/dist/reports/src/catalog.js +17 -3
- package/dist/reports/src/cli-stores.d.ts +36 -0
- package/dist/reports/src/cli-stores.js +73 -0
- package/dist/reports/src/clock.d.ts +7 -2
- package/dist/reports/src/clock.js +5 -2
- package/dist/reports/src/completion-order.d.ts +18 -5
- package/dist/reports/src/completion-order.js +18 -5
- package/dist/reports/src/compose.d.ts +80 -0
- package/dist/reports/src/compose.js +117 -0
- package/dist/reports/src/copy-store.d.ts +29 -0
- package/dist/reports/src/copy-store.js +52 -0
- package/dist/reports/src/directory-outline.d.ts +38 -0
- package/dist/reports/src/directory-outline.js +103 -0
- package/dist/reports/src/directory-store.d.ts +20 -0
- package/dist/reports/src/directory-store.js +166 -0
- package/dist/reports/src/doctor.d.ts +29 -0
- package/dist/reports/src/doctor.js +83 -0
- package/dist/reports/src/effective-delta.d.ts +34 -0
- package/dist/reports/src/effective-delta.js +25 -0
- package/dist/reports/src/evidence-detail.d.ts +64 -0
- package/dist/reports/src/evidence-detail.js +92 -0
- package/dist/reports/src/evidence-live.d.ts +40 -4
- package/dist/reports/src/evidence-live.js +52 -4
- package/dist/reports/src/evidence-types.d.ts +70 -1
- package/dist/reports/src/evidence-types.js +5 -1
- package/dist/reports/src/evidence-view.d.ts +58 -6
- package/dist/reports/src/evidence-view.js +44 -5
- package/dist/reports/src/explorer-types.d.ts +40 -3
- package/dist/reports/src/explorer-types.js +8 -0
- package/dist/reports/src/explorer-versions.d.ts +9 -1
- package/dist/reports/src/explorer-versions.js +18 -1
- package/dist/reports/src/export-evidence.d.ts +16 -1
- package/dist/reports/src/export-evidence.js +52 -1
- package/dist/reports/src/fetcher.d.ts +27 -3
- package/dist/reports/src/fetcher.js +11 -3
- package/dist/reports/src/format-date.d.ts +6 -0
- package/dist/reports/src/format-date.js +6 -0
- package/dist/reports/src/format.d.ts +16 -0
- package/dist/reports/src/format.js +10 -0
- package/dist/reports/src/frames.d.ts +36 -3
- package/dist/reports/src/frames.js +22 -3
- package/dist/reports/src/hash.d.ts +18 -0
- package/dist/reports/src/hash.js +13 -0
- package/dist/reports/src/host.d.ts +29 -0
- package/dist/reports/src/host.js +64 -0
- package/dist/reports/src/html-frame.d.ts +5 -6
- package/dist/reports/src/html-frame.js +12 -1
- package/dist/reports/src/http-store.d.ts +52 -0
- package/dist/reports/src/http-store.js +145 -0
- package/dist/reports/src/http.d.ts +7 -0
- package/dist/reports/src/http.js +6 -0
- package/dist/reports/src/ingest.d.ts +54 -0
- package/dist/reports/src/ingest.js +210 -0
- package/dist/reports/src/init.d.ts +40 -0
- package/dist/reports/src/init.js +364 -0
- package/dist/reports/src/junit.d.ts +33 -6
- package/dist/reports/src/junit.js +87 -21
- package/dist/reports/src/keyboard.d.ts +20 -0
- package/dist/reports/src/keyboard.js +11 -0
- package/dist/reports/src/keys.d.ts +21 -3
- package/dist/reports/src/keys.js +21 -3
- package/dist/reports/src/layer-axes.d.ts +48 -0
- package/dist/reports/src/layer-axes.js +71 -0
- package/dist/reports/src/layer-catalog.d.ts +28 -1
- package/dist/reports/src/layer-catalog.js +19 -2
- package/dist/reports/src/layer-merge.d.ts +98 -9
- package/dist/reports/src/layer-merge.js +154 -33
- package/dist/reports/src/layer-source.d.ts +18 -0
- package/dist/reports/src/layer-source.js +14 -0
- package/dist/reports/src/layer-store.d.ts +42 -0
- package/dist/reports/src/layer-store.js +63 -5
- package/dist/reports/src/layer-types.d.ts +34 -1
- package/dist/reports/src/layers-route.d.ts +47 -4
- package/dist/reports/src/layers-route.js +88 -12
- package/dist/reports/src/media-types.d.ts +11 -0
- package/dist/reports/src/media-types.js +22 -0
- package/dist/reports/src/namespace-guard.d.ts +10 -1
- package/dist/reports/src/namespace-guard.js +10 -1
- package/dist/reports/src/navigation.d.ts +12 -0
- package/dist/reports/src/navigation.js +7 -0
- package/dist/reports/src/object-io.d.ts +11 -4
- package/dist/reports/src/object-io.js +4 -4
- package/dist/reports/src/object-store-conformance.d.ts +27 -0
- package/dist/reports/src/object-store-conformance.js +87 -0
- package/dist/reports/src/object-store.d.ts +33 -24
- package/dist/reports/src/object-store.js +17 -24
- package/dist/reports/src/parse-with.d.ts +16 -0
- package/dist/reports/src/parse-with.js +14 -0
- package/dist/reports/src/performance-part.d.ts +25 -1
- package/dist/reports/src/performance-part.js +15 -1
- package/dist/reports/src/performance.d.ts +22 -2
- package/dist/reports/src/performance.js +12 -2
- package/dist/reports/src/persist.d.ts +12 -3
- package/dist/reports/src/persist.js +12 -3
- package/dist/reports/src/phone.d.ts +18 -1
- package/dist/reports/src/phone.js +18 -1
- package/dist/reports/src/protocol.d.ts +30 -4
- package/dist/reports/src/protocol.js +15 -4
- package/dist/reports/src/publication.d.ts +21 -1
- package/dist/reports/src/publication.js +21 -1
- package/dist/reports/src/publish-checks.d.ts +35 -0
- package/dist/reports/src/publish-checks.js +122 -0
- package/dist/reports/src/publish.d.ts +44 -9
- package/dist/reports/src/publish.js +40 -7
- package/dist/reports/src/read.d.ts +46 -16
- package/dist/reports/src/read.js +38 -14
- package/dist/reports/src/recording-bundle.d.ts +13 -2
- package/dist/reports/src/recording-bundle.js +4 -2
- package/dist/reports/src/recording-keys.d.ts +15 -1
- package/dist/reports/src/recording-keys.js +15 -1
- package/dist/reports/src/remote-report.d.ts +28 -7
- package/dist/reports/src/remote-report.js +15 -7
- package/dist/reports/src/report-cli.d.ts +51 -0
- package/dist/reports/src/report-cli.js +485 -0
- package/dist/reports/src/report-index.d.ts +99 -0
- package/dist/reports/src/report-index.js +337 -0
- package/dist/reports/src/report-loader.d.ts +10 -0
- package/dist/reports/src/report-loader.js +6 -0
- package/dist/reports/src/report-parts.d.ts +29 -3
- package/dist/reports/src/report-parts.js +40 -3
- package/dist/reports/src/report-status.d.ts +48 -0
- package/dist/reports/src/report-status.js +156 -0
- package/dist/reports/src/report-types.d.ts +73 -0
- package/dist/reports/src/report-types.js +3 -0
- package/dist/reports/src/reporter.d.ts +67 -0
- package/dist/reports/src/reporter.js +128 -0
- package/dist/reports/src/reports-explorer-route.d.ts +541 -0
- package/dist/reports/src/reports-explorer-route.js +11 -0
- package/dist/reports/src/reports-list-route.d.ts +9 -0
- package/dist/reports/src/reports-list-route.js +9 -0
- package/dist/reports/src/reports-route.d.ts +561 -22
- package/dist/reports/src/reports-route.js +11 -3
- package/dist/reports/src/reports-store.d.ts +19 -2
- package/dist/reports/src/reports-store.js +10 -2
- package/dist/reports/src/resilient-cache.d.ts +14 -1
- package/dist/reports/src/resilient-cache.js +8 -1
- package/dist/reports/src/retention.d.ts +17 -1
- package/dist/reports/src/retention.js +5 -1
- package/dist/reports/src/run-bundle.d.ts +63 -0
- package/dist/reports/src/run-bundle.js +159 -0
- package/dist/reports/src/s3-store.d.ts +79 -0
- package/dist/reports/src/s3-store.js +295 -0
- package/dist/reports/src/scenario.d.ts +14 -4
- package/dist/reports/src/scenario.js +5 -4
- package/dist/reports/src/sigv4.d.ts +72 -0
- package/dist/reports/src/sigv4.js +96 -0
- package/dist/reports/src/snapshot-bundle.d.ts +24 -1
- package/dist/reports/src/snapshot-bundle.js +27 -2
- package/dist/reports/src/snapshot-surface.d.ts +20 -9
- package/dist/reports/src/snapshot-surface.js +32 -19
- package/dist/reports/src/source-content.d.ts +23 -0
- package/dist/reports/src/source-content.js +30 -1
- package/dist/reports/src/source-entries.d.ts +13 -0
- package/dist/reports/src/source-entries.js +6 -0
- package/dist/reports/src/source-part.d.ts +15 -0
- package/dist/reports/src/source-part.js +8 -0
- package/dist/reports/src/source-reader.d.ts +11 -0
- package/dist/reports/src/source-reader.js +5 -0
- package/dist/reports/src/spec-evidence.d.ts +12 -2
- package/dist/reports/src/spec-evidence.js +6 -2
- package/dist/reports/src/spec-inventory.d.ts +15 -1
- package/dist/reports/src/spec-inventory.js +22 -1
- package/dist/reports/src/spec-link.d.ts +28 -0
- package/dist/reports/src/spec-link.js +70 -0
- package/dist/reports/src/spec-set.d.ts +63 -0
- package/dist/reports/src/spec-set.js +177 -0
- package/dist/reports/src/status.d.ts +10 -0
- package/dist/reports/src/status.js +5 -0
- package/dist/reports/src/store-prefix.d.ts +28 -0
- package/dist/reports/src/store-prefix.js +22 -0
- package/dist/reports/src/store-url.d.ts +89 -0
- package/dist/reports/src/store-url.js +160 -0
- package/dist/reports/src/test-catalog-entity.d.ts +11 -0
- package/dist/reports/src/test-catalog-entity.js +6 -0
- package/dist/reports/src/types.d.ts +6 -1
- package/dist/reports/src/use-report-nav.d.ts +35 -0
- package/dist/reports/src/use-report-nav.js +12 -0
- package/dist/schema/src/api.d.ts +799 -0
- package/dist/schema/src/api.js +201 -0
- package/dist/schema/src/cli-results.d.ts +210 -0
- package/dist/schema/src/cli-results.js +124 -0
- package/dist/schema/src/common.d.ts +53 -0
- package/dist/schema/src/common.js +42 -0
- package/dist/schema/src/index.d.ts +14 -0
- package/dist/schema/src/index.js +14 -0
- package/dist/schema/src/layer.d.ts +2273 -0
- package/dist/schema/src/layer.js +462 -0
- package/dist/schema/src/manifest.d.ts +95 -0
- package/dist/schema/src/manifest.js +84 -0
- package/dist/schema/src/report-sources.d.ts +28 -0
- package/dist/schema/src/report-sources.js +25 -0
- package/dist/schema/src/runs-index.d.ts +16 -0
- package/dist/schema/src/runs-index.js +15 -0
- package/dist/schema/src/server-config.d.ts +93 -0
- package/dist/schema/src/server-config.js +73 -0
- package/dist/schema/src/spec.d.ts +69 -0
- package/dist/schema/src/spec.js +58 -0
- package/dist/server/src/app.d.ts +62 -0
- package/dist/server/src/app.js +419 -0
- package/dist/server/src/auth.d.ts +41 -0
- package/dist/server/src/auth.js +59 -0
- package/dist/server/src/config.d.ts +83 -0
- package/dist/server/src/config.js +187 -0
- package/dist/server/src/dev.d.ts +12 -0
- package/dist/server/src/dev.js +80 -0
- package/dist/server/src/events.d.ts +29 -0
- package/dist/server/src/events.js +101 -0
- package/dist/server/src/host.d.ts +30 -0
- package/dist/server/src/host.js +115 -0
- package/dist/server/src/index.d.ts +16 -0
- package/dist/server/src/index.js +16 -0
- package/dist/server/src/llms.d.ts +19 -0
- package/dist/server/src/llms.js +63 -0
- package/dist/server/src/main.d.ts +30 -0
- package/dist/server/src/main.js +112 -0
- package/dist/server/src/openapi.d.ts +59 -0
- package/dist/server/src/openapi.js +374 -0
- package/dist/server/src/problem.d.ts +8 -0
- package/dist/server/src/problem.js +39 -0
- package/dist/server/src/site.d.ts +13 -0
- package/dist/server/src/site.js +43 -0
- package/dist/server/src/store-api.d.ts +28 -0
- package/dist/server/src/store-api.js +159 -0
- package/dist/server/src/validate.d.ts +71 -0
- package/dist/server/src/validate.js +197 -0
- package/dist/vitest/src/index.d.ts +27 -5
- package/dist/vitest/src/index.js +18 -5
- package/dist/webdriver/src/driver.d.ts +63 -11
- package/dist/webdriver/src/driver.js +57 -6
- package/dist/webdriver/src/index.d.ts +8 -0
- package/dist/webdriver/src/index.js +1 -0
- package/dist/webdriver/src/protocol.d.ts +75 -6
- package/dist/webdriver/src/protocol.js +79 -6
- package/docs/agents.md +137 -0
- package/docs/deploying.md +345 -0
- package/docs/dogfooding.md +175 -0
- package/docs/extending.md +242 -0
- package/docs/integrating.md +485 -0
- package/docs/reference/bun.md +141 -0
- package/docs/reference/cli.md +146 -0
- package/docs/reference/core.md +2426 -0
- package/docs/reference/detox.md +150 -0
- package/docs/reference/http-api.md +303 -0
- package/docs/reference/playwright.md +303 -0
- package/docs/reference/reports.md +7098 -0
- package/docs/reference/schema.md +1651 -0
- package/docs/reference/server.md +685 -0
- package/docs/reference/vitest.md +45 -0
- package/docs/reference/webdriver.md +330 -0
- package/docs/report-backends.md +144 -0
- package/docs/report-contract.md +400 -0
- package/docs/reporting-issues.md +160 -0
- package/docs/server.md +285 -0
- package/docs/statecharts.md +641 -0
- package/llms-full.txt +3506 -0
- package/llms.txt +47 -0
- package/package.json +33 -5
- package/packages/core/README.md +2 -1
- package/packages/reports/README.md +69 -2
- package/packages/reports/STORAGE.md +5 -1
- package/packages/schema/README.md +14 -0
- package/packages/schema/schemas/check-detail.schema.json +1032 -0
- package/packages/schema/schemas/checks-result.schema.json +26 -0
- package/packages/schema/schemas/command-error.schema.json +35 -0
- package/packages/schema/schemas/doctor-result.schema.json +60 -0
- package/packages/schema/schemas/effective-delta.schema.json +490 -0
- package/packages/schema/schemas/effective-evidence.schema.json +471 -0
- package/packages/schema/schemas/evidence-page.schema.json +271 -0
- package/packages/schema/schemas/hello-event.schema.json +22 -0
- package/packages/schema/schemas/init-plan.schema.json +347 -0
- package/packages/schema/schemas/layer-rows.schema.json +199 -0
- package/packages/schema/schemas/layer.schema.json +2888 -0
- package/packages/schema/schemas/merge-result.schema.json +40 -0
- package/packages/schema/schemas/outline.schema.json +62 -0
- package/packages/schema/schemas/problem.schema.json +37 -0
- package/packages/schema/schemas/publish-checks.schema.json +1413 -0
- package/packages/schema/schemas/publish-result.schema.json +168 -0
- package/packages/schema/schemas/ready.schema.json +68 -0
- package/packages/schema/schemas/report-event.schema.json +100 -0
- package/packages/schema/schemas/report-status.schema.json +370 -0
- package/packages/schema/schemas/server-config.schema.json +194 -0
- package/packages/schema/schemas/server-info.schema.json +114 -0
- package/packages/schema/schemas/spec-set.schema.json +103 -0
- package/packages/schema/schemas/store-listing.schema.json +29 -0
- package/packages/schema/schemas/store-write-result.schema.json +28 -0
- package/packages/schema/schemas/stream-result.schema.json +59 -0
- package/packages/server/README.md +43 -0
- package/packages/server/openapi.json +6699 -0
- package/packages/visualizer/README.md +27 -5
- package/packages/visualizer/example/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/example/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/example/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/example/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/example/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/example/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/failures/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/full/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/partial/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/removed/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/cart.qnt +7 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/checkout.openslo.yaml +10 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/drink-price.dmn +24 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/loyalty.feature +7 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/ordering.feature +36 -0
- package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/payment.als +4 -0
- package/packages/visualizer/scripts/make-fixtures.ts +2 -0
- package/packages/visualizer/scripts/publish-data.ts +1 -2
- package/packages/visualizer/src/app/AppShell.tsx +9 -2
- package/packages/visualizer/src/app/report-sources.tsx +6 -4
- package/packages/visualizer/src/app/router.tsx +3 -3
- package/packages/visualizer/src/components/Header.module.css +35 -0
- package/packages/visualizer/src/components/Header.tsx +41 -4
- package/packages/visualizer/src/data/adapters/adapter.ts +5 -17
- package/packages/visualizer/src/data/adapters/index.ts +10 -1
- package/packages/visualizer/src/evidence/api.ts +14 -2
- package/packages/visualizer/src/evidence/artifacts.module.css +213 -0
- package/packages/visualizer/src/evidence/artifacts.tsx +272 -0
- package/packages/visualizer/src/evidence/check-detail.tsx +38 -4
- package/packages/visualizer/src/evidence/evidence-context.tsx +74 -12
- package/packages/visualizer/src/evidence/evidence.module.css +20 -0
- package/packages/visualizer/src/evidence/marks.tsx +2 -0
- package/packages/visualizer/src/evidence/overview.tsx +3 -3
- package/packages/visualizer/src/evidence/sources-panel.tsx +88 -34
- package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
- package/packages/visualizer/src/find/Finder.tsx +1 -7
- package/packages/visualizer/src/find/open.ts +4 -0
- package/packages/visualizer/src/live/live-stream.ts +204 -0
- package/packages/visualizer/src/runtime.ts +19 -0
- package/packages/visualizer/src/state/ui-store.ts +6 -2
- package/packages/visualizer/vite/atlas-server-proxy.ts +36 -0
- package/packages/visualizer/vite/example-reports.ts +475 -0
- package/packages/visualizer/vite.config.ts +18 -2
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module OpenAPI document of the Atlas server
|
|
3
|
+
*
|
|
4
|
+
* The server's HTTP API described as an OpenAPI 3.1 document (JSON Schema 2020-12 inside), generated from the
|
|
5
|
+
* operation table below and the Zod schemas of `packages/schema/src/api.ts`. It is served at `GET /api/openapi.json`,
|
|
6
|
+
* shipped as `@crvouga/atlas/openapi.json`, and committed as `packages/server/openapi.json` (`bun run docs` rewrites
|
|
7
|
+
* it; a test fails when it differs). Any OpenAPI tool can generate a client, a mock or a test suite from it.
|
|
8
|
+
*
|
|
9
|
+
* The table is held to the code by a test: every route the server registers must have an operation here and every
|
|
10
|
+
* operation here must be a route the server registers. Adding a route without describing it fails the build.
|
|
11
|
+
*
|
|
12
|
+
* Stability: this API only grows within `API_VERSION`: optional parameters, optional response fields, new
|
|
13
|
+
* operations and new events. See AGENTS.md ("The server is a living application").
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod/v4';
|
|
16
|
+
import { EffectiveDeltaSchema, EffectiveEvidenceSchema, EvidencePageSchema, HealthSchema, LayerRowsSchema, CheckDetailSchema, OutlineSchema, ProblemSchema, ReadySchema, ServerInfoSchema, StoreListingSchema, StoreWriteResultSchema, ChecksResultSchema, HelloEventSchema, ReportEventSchema, PingEventSchema } from '../../schema/src/api.js';
|
|
17
|
+
import { AtlasChangeSchema, PublishChecksSchema, ReportSourcesSchema, ReportStatusSchema, RunsIndexSchema, SpecIndexSchema } from '../../schema/src/index.js';
|
|
18
|
+
/** The version of the API described, `major.minor.patch`: the minor grows when an operation or field is added, the major never within `/api`. */
|
|
19
|
+
export const API_VERSION = '1.0.0';
|
|
20
|
+
/** The JSON Schema of a Zod schema, as OpenAPI 3.1 embeds it. `output` for what the server sends, `input` for what it accepts. */
|
|
21
|
+
function schemaOf(schema, io = 'output') {
|
|
22
|
+
const { $schema: _dialect, ...json } = z.toJSONSchema(schema, { target: 'draft-2020-12', unrepresentable: 'any', io });
|
|
23
|
+
return json;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The named schemas of the document (`components.schemas`), each written once and referred to by `$ref`. `output` is
|
|
27
|
+
* the shape the server sends, `input` the shape it accepts.
|
|
28
|
+
*/
|
|
29
|
+
const COMPONENTS = {
|
|
30
|
+
Problem: [ProblemSchema, 'output'], Health: [HealthSchema, 'output'], Ready: [ReadySchema, 'output'], ServerInfo: [ServerInfoSchema, 'output'],
|
|
31
|
+
EffectiveEvidence: [EffectiveEvidenceSchema, 'output'], EffectiveDelta: [EffectiveDeltaSchema, 'output'], ReportStatus: [ReportStatusSchema, 'output'],
|
|
32
|
+
PublishChecks: [PublishChecksSchema.partial({ recordedAtMs: true }), 'input'], ChecksResult: [ChecksResultSchema, 'output'],
|
|
33
|
+
StoreListing: [StoreListingSchema, 'output'], StoreWriteResult: [StoreWriteResultSchema, 'output'],
|
|
34
|
+
Outline: [OutlineSchema, 'output'], EvidencePage: [EvidencePageSchema, 'output'], LayerRows: [LayerRowsSchema, 'output'], CheckDetail: [CheckDetailSchema, 'output'],
|
|
35
|
+
SpecIndex: [SpecIndexSchema, 'output'], ReportSources: [ReportSourcesSchema, 'output'], RunsIndex: [RunsIndexSchema, 'output'],
|
|
36
|
+
HelloEvent: [HelloEventSchema, 'output'], ReportEvent: [ReportEventSchema, 'output'], AtlasChangeEvent: [AtlasChangeSchema, 'output'], PingEvent: [PingEventSchema, 'output']
|
|
37
|
+
};
|
|
38
|
+
const ref = (name) => ({ $ref: `#/components/schemas/${name}` });
|
|
39
|
+
const str = { type: 'string' };
|
|
40
|
+
const problem = (description) => ({ description, contentType: 'application/problem+json', schema: ref('Problem') });
|
|
41
|
+
const json = (description, name) => ({ description, contentType: 'application/json', schema: ref(name) });
|
|
42
|
+
const NOT_READY = problem('The server is still reading its backends. Retry after the number of seconds in `Retry-After`.');
|
|
43
|
+
const UNAUTHORIZED = problem('No token, or an unknown one. Send `Authorization: Bearer <token>`.');
|
|
44
|
+
const FORBIDDEN = problem('The token is known but its role may not do this.');
|
|
45
|
+
const KEY = { name: 'key', in: 'path', required: true, description: 'An object key under `layers/`, such as `layers/manifests/<id>.json`. It contains slashes, which are not percent-encoded; every other character is.', schema: str, example: 'layers/index/9007199254740985-0f3a….json' };
|
|
46
|
+
const BACKEND = { name: 'backend', in: 'path', required: true, description: 'A backend id from `/api/server`.', schema: str };
|
|
47
|
+
const LAYER_ID = { name: 'id', in: 'path', required: true, description: 'A layer id: 64 lowercase hex digits.', schema: { type: 'string', pattern: '^[0-9a-f]{64}$' } };
|
|
48
|
+
/**
|
|
49
|
+
* Every operation of the API. The order is the order of the document. The descriptions are the documentation an
|
|
50
|
+
* agent reads, so they say what to send, what comes back, and what to do when it does not work.
|
|
51
|
+
*/
|
|
52
|
+
export const OPERATIONS = [
|
|
53
|
+
{
|
|
54
|
+
route: 'GET /healthz', path: '/healthz', operationId: 'getHealth', tag: 'Operations', role: 'none',
|
|
55
|
+
summary: 'Is the process up',
|
|
56
|
+
description: 'Answers `200` as long as the process runs, whatever its backends are doing. Use it as a liveness probe. It needs no token and is not logged.',
|
|
57
|
+
responses: { '200': json('The process is up.', 'Health') }
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
route: 'GET /readyz', path: '/readyz', operationId: 'getReady', tag: 'Operations', role: 'none',
|
|
61
|
+
summary: 'Has the server read its backends',
|
|
62
|
+
description: 'Answers `200` once the first pass over the backends is done and at least one backend answers, `503` before that. Route traffic on it. The body counts what the report holds. No token needed.',
|
|
63
|
+
responses: { '200': json('Ready.', 'Ready'), '503': json('Not ready yet; the body is the same shape with `ready: false`.', 'Ready') }
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
route: 'GET /api/openapi.json', path: '/api/openapi.json', operationId: 'getOpenApi', tag: 'Operations', role: 'none',
|
|
67
|
+
summary: 'This document',
|
|
68
|
+
description: 'The OpenAPI 3.1 description of this API. No token needed, so a client that has only the server\'s address can learn how to use it.',
|
|
69
|
+
responses: { '200': { description: 'The OpenAPI document.', contentType: 'application/json', schema: { type: 'object' } } }
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
route: 'GET /llms.txt', path: '/llms.txt', operationId: 'getLlmsTxt', tag: 'Operations', role: 'none',
|
|
73
|
+
summary: 'How to use this server, for an agent',
|
|
74
|
+
description: 'A plain-text guide ([llms.txt](https://llmstxt.org)) written for an AI agent that was handed only this server\'s address: what it is, how to authenticate, the five commands that integrate a project, and where the machine-readable description is. No token needed.',
|
|
75
|
+
responses: { '200': { description: 'The guide, Markdown.', contentType: 'text/plain', schema: str } }
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
route: 'GET /api/server', path: '/api/server', operationId: 'getServer', tag: 'Operations', role: 'read',
|
|
79
|
+
summary: 'What this server is and who is asking',
|
|
80
|
+
description: 'The role of the token of this request, which backend takes uploads, whether each backend is reachable, counts, and `capabilities`: check a capability by name before using the feature it names. `atlas report doctor` and the viewer use it.',
|
|
81
|
+
responses: { '200': json('The server.', 'ServerInfo'), '401': UNAUTHORIZED }
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
route: 'GET /api/events', path: '/api/events', operationId: 'streamEvents', tag: 'Live', role: 'read',
|
|
85
|
+
summary: 'Server-sent events: results arrived, specs or runs changed',
|
|
86
|
+
description: [
|
|
87
|
+
'A `text/event-stream` that stays open. Events say *that* something changed; the state is always fetched, so a client that missed events (a dropped connection) loses nothing: read again on reconnect.',
|
|
88
|
+
'',
|
|
89
|
+
'| event | data |',
|
|
90
|
+
'| --- | --- |',
|
|
91
|
+
'| `hello` | `{ version, ready, time }`, sent first |',
|
|
92
|
+
'| `report` | `{ version, totalLayers, added: [{ id, producer, environment, branch, sha, recordedAtMs, counts }], addedTotal }`: layers arrived |',
|
|
93
|
+
'| `atlas:change` | `{ scope: "specs" \\| "runs", files, runIds }`: new specs or runs |',
|
|
94
|
+
'| `ping` | `{}` every `heartbeatSeconds`, so a quiet server is told apart from a dead connection |',
|
|
95
|
+
'',
|
|
96
|
+
'Past `limits.maxStreams` viewers the answer is `503` with `Retry-After`: poll `/api/reports/evidence/effective` instead. A browser needs no code beyond `new EventSource("/api/events")`; other clients send the token as `Authorization: Bearer`.'
|
|
97
|
+
].join('\n'),
|
|
98
|
+
responses: {
|
|
99
|
+
'200': { description: 'The stream.', contentType: 'text/event-stream', schema: str },
|
|
100
|
+
'401': UNAUTHORIZED,
|
|
101
|
+
'503': problem('Too many viewers are connected; poll instead.')
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
route: 'GET /api/reports/evidence/effective', path: '/api/reports/evidence/effective', operationId: 'getReport', tag: 'Report', role: 'read',
|
|
106
|
+
summary: 'The whole report, or only what changed since a version',
|
|
107
|
+
description: [
|
|
108
|
+
'The report as it stands: only the layers that win at least one check, each with only the rows that win. Whatever the history behind it, this is one request.',
|
|
109
|
+
'',
|
|
110
|
+
'`version` changes exactly when the report does, so it works as an ETag: send `If-None-Match` and get `304`. Better, send `?since=<version>` of the report you hold and get **only what changed** (`delta: true`): replace the layers of the same id, drop `removed`, keep the rest. If the server does not remember that version it answers with the whole report (no `delta` key), so a client always handles both.',
|
|
111
|
+
'',
|
|
112
|
+
'To combine layers yourself, a check\'s *slot* is `(producer, kind, specPath, check id, environment, parameters)`; the newest completed result wins it. See `docs/report-contract.md`.'
|
|
113
|
+
].join('\n'),
|
|
114
|
+
parameters: [{ name: 'since', in: 'query', description: 'The `version` of the report you hold.', schema: str }, { name: 'if-none-match', in: 'header', description: 'The ETag (the quoted version) of the report you hold.', schema: str }],
|
|
115
|
+
responses: {
|
|
116
|
+
'200': { description: 'The whole report, or a delta when `since` was given and remembered.', contentType: 'application/json', schema: { oneOf: [ref('EffectiveEvidence'), ref('EffectiveDelta')] }, headers: { ETag: { description: 'The quoted `version`.', schema: str } } },
|
|
117
|
+
'304': { description: 'You already hold this version.' },
|
|
118
|
+
'401': UNAUTHORIZED,
|
|
119
|
+
'503': NOT_READY
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
route: 'GET /api/status', path: '/api/status', operationId: 'getStatus', tag: 'Report', role: 'read',
|
|
124
|
+
summary: 'The gate: does the report pass',
|
|
125
|
+
description: [
|
|
126
|
+
'The `atlas report status --format json` document, worked out by the server from the layers it holds and remembered for each version of the report. It passes (`ok: true`) only if some check completed, none failed, none is still running, every layer and backend could be read, and every required environment has results. **Unknown is never a pass.**',
|
|
127
|
+
'',
|
|
128
|
+
'The header `X-Atlas-Ok: true|false` carries the verdict, so a shell can test it without parsing. Narrow it by choice, per request: `branch` (repeatable) and `ignoreModified` read only matching layers.'
|
|
129
|
+
].join('\n'),
|
|
130
|
+
parameters: [
|
|
131
|
+
{ name: 'branch', in: 'query', description: 'Only layers recorded on this branch. Repeatable.', schema: str },
|
|
132
|
+
{ name: 'ignoreModified', in: 'query', description: '`1`: leave out layers from modified checkouts and unknown origins.', schema: { type: 'string', enum: ['1'] } },
|
|
133
|
+
{ name: 'requireEnvironments', in: 'query', description: 'Comma-separated environments that must have results.', schema: str, example: 'desktop-web,ios' },
|
|
134
|
+
{ name: 'requireAllSpecs', in: 'query', description: '`1`: fail while any spec of the outline has no results.', schema: { type: 'string', enum: ['1'] } }
|
|
135
|
+
],
|
|
136
|
+
responses: { '200': { ...json('The verdict, whether or not it passes.', 'ReportStatus'), headers: { 'X-Atlas-Ok': { description: '`true` or `false`.', schema: { type: 'string', enum: ['true', 'false'] } } } }, '401': UNAUTHORIZED, '503': NOT_READY }
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
route: 'POST /api/checks', path: '/api/checks', operationId: 'postChecks', tag: 'Upload', role: 'publish',
|
|
140
|
+
summary: 'Upload one layer of checks as JSON',
|
|
141
|
+
description: [
|
|
142
|
+
'The simplest way in: anything that can make an HTTP request can report. The body is the checks file (`packages/schema/schemas/publish-checks.schema.json`); `recordedAtMs` defaults to now. Binary evidence goes in as `base64`. The server writes the layer and it is in every viewer\'s report before this answers.',
|
|
143
|
+
'',
|
|
144
|
+
'Send the token as `Authorization: Bearer <token>` (a `publish` or `admin` token). A browser\'s sign-in box is refused for uploads on purpose. A check id appearing twice in one body is `422`.'
|
|
145
|
+
].join('\n'),
|
|
146
|
+
body: { description: 'The checks of one producer.', contentType: 'application/json', schema: ref('PublishChecks'), required: true },
|
|
147
|
+
responses: { '201': json('The layer was written.', 'ChecksResult'), '401': UNAUTHORIZED, '403': FORBIDDEN, '413': problem('The body is over `limits.maxJsonMiB`.'), '422': problem('The body is not valid: the detail says which field.') }
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
route: 'GET /api/store', path: '/api/store', operationId: 'listStore', tag: 'Store protocol', role: 'read',
|
|
151
|
+
summary: 'List keys under layers/',
|
|
152
|
+
description: 'One page of the keys of `layers/…` (never anything else), in code-unit order. Page with `after` = the `next` of the previous page. This is the `listPage` of the `ObjectStore` interface; `atlas report merge` and `httpStore` use it.',
|
|
153
|
+
parameters: [
|
|
154
|
+
{ name: 'prefix', in: 'query', description: 'Only keys starting with this. Default `layers/`.', schema: str },
|
|
155
|
+
{ name: 'after', in: 'query', description: 'Only keys after this one (exclusive).', schema: str },
|
|
156
|
+
{ name: 'limit', in: 'query', description: 'Page size, 1 to 1000. Default 1000.', schema: { type: 'integer', minimum: 1, maximum: 1000 } }
|
|
157
|
+
],
|
|
158
|
+
responses: { '200': json('A page.', 'StoreListing'), '401': UNAUTHORIZED, '422': problem('The prefix is not one Atlas holds.') }
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
route: 'GET /api/store/*', path: '/api/store/{key}', operationId: 'getObject', tag: 'Store protocol', role: 'read',
|
|
162
|
+
summary: 'Read an object',
|
|
163
|
+
description: 'The bytes of one object, with the content type it was stored with. Everything under `layers/` is immutable, so the answer is cacheable for good. The bytes came from a test run and are untrusted: they are served with `nosniff` and a sandboxing Content-Security-Policy, whatever type their uploader gave them.',
|
|
164
|
+
parameters: [KEY],
|
|
165
|
+
responses: { '200': { description: 'The object.', contentType: 'application/octet-stream', schema: { type: 'string', format: 'binary' } }, '401': UNAUTHORIZED, '404': problem('No such object.'), '422': problem('Not a key Atlas holds.') }
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
route: 'HEAD /api/store/*', path: '/api/store/{key}', operationId: 'headObject', tag: 'Store protocol', role: 'read',
|
|
169
|
+
summary: 'Does an object exist',
|
|
170
|
+
description: '`200` when the object exists, `404` when not. No body.',
|
|
171
|
+
parameters: [KEY],
|
|
172
|
+
responses: { '200': { description: 'It exists.' }, '401': UNAUTHORIZED, '404': problem('No such object.') }
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
route: 'PUT /api/store/*', path: '/api/store/{key}', operationId: 'putObject', tag: 'Store protocol', role: 'publish',
|
|
176
|
+
summary: 'Upload an object',
|
|
177
|
+
description: [
|
|
178
|
+
'The write half of the `ObjectStore` interface. The server checks what it can from the bytes in hand and says no to what would corrupt the report for everyone:',
|
|
179
|
+
'',
|
|
180
|
+
'- a **layer manifest** (`layers/manifests/<id>.json`) must be a valid layer whose content hashes to `<id>`, and its blobs must already be there (`409` otherwise);',
|
|
181
|
+
'- a **blob** (`layers/blobs/<sha256>.gz`) must be gzip of content whose SHA-256 is its name; a **spec file** likewise;',
|
|
182
|
+
'- a **catalog entry** (`…/index/…`) must name something already uploaded (`409` otherwise): it is what makes a layer visible, so it is written last;',
|
|
183
|
+
'- a **run manifest** must have passed its privacy scan;',
|
|
184
|
+
'- keys are immutable: the same bytes again are `200` (a retry), other bytes under the same key are `409`.',
|
|
185
|
+
'',
|
|
186
|
+
'Upload in dependency order: blobs, then the manifest, then the catalog entry. `atlas report publish`, `atlas report stream` and `httpStore` do. Needs a `Bearer` token of role `publish` or `admin`; a browser\'s sign-in box is refused.'
|
|
187
|
+
].join('\n'),
|
|
188
|
+
parameters: [KEY, { name: 'content-type', in: 'header', description: 'Stored with the object and returned when it is read.', schema: str }],
|
|
189
|
+
body: { description: 'The object\'s bytes.', contentType: 'application/octet-stream', schema: { type: 'string', format: 'binary' }, required: true },
|
|
190
|
+
responses: {
|
|
191
|
+
'200': json('Already there with the same bytes.', 'StoreWriteResult'), '201': json('Stored.', 'StoreWriteResult'),
|
|
192
|
+
'401': UNAUTHORIZED, '403': FORBIDDEN, '409': problem('A dependency has not been uploaded yet, or other bytes are already stored under this key.'),
|
|
193
|
+
'413': problem('Over `limits.maxObjectMiB`.'), '422': problem('Not a key Atlas writes, or the content is not what its name says.')
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
route: 'DELETE /api/store/*', path: '/api/store/{key}', operationId: 'deleteObject', tag: 'Store protocol', role: 'publish',
|
|
198
|
+
summary: 'Remove a scratch object',
|
|
199
|
+
description: 'Only the scratch objects `atlas report doctor` writes (`layers/doctor-…`) can be removed. Layers, specs and runs are never removed through the API.',
|
|
200
|
+
parameters: [KEY],
|
|
201
|
+
responses: { '204': { description: 'Removed (or it was not there).' }, '401': UNAUTHORIZED, '403': problem('Not a scratch object, or the token may not.') }
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
route: 'GET /api/reports/outline', path: '/api/reports/outline', operationId: 'getOutline', tag: 'Report', role: 'read',
|
|
205
|
+
summary: 'The specs a report is laid over',
|
|
206
|
+
description: 'The specs of the newest committed spec set (of `main`, else the newest), so the report needs no checkout. A spec here with no result shows as having none.',
|
|
207
|
+
responses: { '200': json('The outline.', 'Outline'), '401': UNAUTHORIZED }
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
route: 'GET /api/reports/outline/spec', path: '/api/reports/outline/spec', operationId: 'getOutlineSpec', tag: 'Report', role: 'read',
|
|
211
|
+
summary: 'The text of one spec',
|
|
212
|
+
description: 'The text of a spec file of the outline, by its path.',
|
|
213
|
+
parameters: [{ name: 'path', in: 'query', required: true, description: 'The spec\'s path, `specs/<path>`.', schema: str }],
|
|
214
|
+
responses: { '200': { description: 'The spec text.', contentType: 'text/plain', schema: str }, '401': UNAUTHORIZED, '404': problem('No such spec in the outline.') }
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
route: 'GET /api/reports/evidence', path: '/api/reports/evidence', operationId: 'listLayers', tag: 'Report', role: 'read',
|
|
218
|
+
summary: 'Page through every layer header',
|
|
219
|
+
description: 'The history: layer headers newest first, merged from every backend. Prefer `/api/reports/evidence/effective`, which is one request and holds only what wins. Use this to audit what was recorded.',
|
|
220
|
+
parameters: [{ name: 'cursor', in: 'query', description: 'The `cursor` of the previous page.', schema: str }],
|
|
221
|
+
responses: { '200': json('A page of headers.', 'EvidencePage'), '401': UNAUTHORIZED, '503': NOT_READY }
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
route: 'GET /api/reports/evidence/rows/:backend/:id', path: '/api/reports/evidence/rows/{backend}/{id}', operationId: 'getLayerRows', tag: 'Report', role: 'read',
|
|
225
|
+
summary: 'The checks of one layer',
|
|
226
|
+
description: 'Every check of one layer (not only those that win), with the spec fingerprints it recorded.',
|
|
227
|
+
parameters: [BACKEND, LAYER_ID],
|
|
228
|
+
responses: { '200': json('The rows.', 'LayerRows'), '401': UNAUTHORIZED, '404': problem('No such layer in that backend.') }
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
route: 'GET /api/reports/evidence/detail/:backend/:id', path: '/api/reports/evidence/detail/{backend}/{id}', operationId: 'getCheckDetail', tag: 'Report', role: 'read',
|
|
232
|
+
summary: 'Everything one result carries',
|
|
233
|
+
description: 'The timeline, annotations, retries, attachments and extensions of one check, and what its run carries as a whole. Without `check`, only the run\'s. Attachment bytes are fetched from `/api/reports/layers/{id}/blobs/{bundleKey}`.',
|
|
234
|
+
parameters: [BACKEND, LAYER_ID, { name: 'check', in: 'query', description: 'The check id.', schema: str }, { name: 'kind', in: 'query', description: 'The kind of the part, when the same id is in several.', schema: str }],
|
|
235
|
+
responses: { '200': json('The detail.', 'CheckDetail'), '401': UNAUTHORIZED, '404': problem('No such layer or check.') }
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
route: 'GET /api/reports/layers/:id/blobs/*', path: '/api/reports/layers/{id}/blobs/{bundleKey}', operationId: 'getAttachment', tag: 'Report', role: 'read',
|
|
239
|
+
summary: 'A file a layer carries',
|
|
240
|
+
description: 'A screenshot, video, trace, log or any attachment of a layer, by the `bundleKey` its attachment names (`attachments/<sha256>`). Served as untrusted bytes: never sniffed, never run, sandboxed; immutable, cacheable for good; videos can be sought with `Range`.',
|
|
241
|
+
parameters: [LAYER_ID, { name: 'bundleKey', in: 'path', required: true, description: 'The attachment\'s `bundleKey`, which contains a slash.', schema: str }, { name: 'range', in: 'header', description: 'A byte range, `bytes=0-1023`.', schema: str }],
|
|
242
|
+
responses: { '200': { description: 'The bytes, with the type the producer gave (or `application/octet-stream`).', contentType: 'application/octet-stream', schema: { type: 'string', format: 'binary' } }, '206': { description: 'The requested range.' }, '304': { description: 'Not modified.' }, '401': UNAUTHORIZED, '404': problem('No such layer or file.') }
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
route: 'GET /api/reports/layers/:id/*', path: '/api/reports/layers/{id}/{path}', operationId: 'getRecordedFile', tag: 'Report', role: 'read',
|
|
246
|
+
summary: 'A recorded test of a statechart run',
|
|
247
|
+
description: 'The `report.json`, captured files and frame stylesheet of a recorded statechart test of a layer (`tests/<testId>/report.json`, `tests/<testId>/files/…`, `frame.css`). Untrusted bytes, served like attachments.',
|
|
248
|
+
parameters: [LAYER_ID, { name: 'path', in: 'path', required: true, description: 'The path under the layer.', schema: str }],
|
|
249
|
+
responses: { '200': { description: 'The file.', contentType: 'application/octet-stream', schema: { type: 'string', format: 'binary' } }, '401': UNAUTHORIZED, '404': problem('No such file.') }
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
route: 'GET /api/reports/live/:source/frame.css', path: '/api/reports/live/{source}/frame.css', operationId: 'getLiveFrameCss', tag: 'Report', role: 'read',
|
|
253
|
+
summary: 'Frame stylesheet of a live spec run (development only)',
|
|
254
|
+
description: 'For spec runs writing on the same machine as a development host (`atlas-visualizer dev`). A server has none: it answers `404`.',
|
|
255
|
+
parameters: [{ name: 'source', in: 'path', required: true, description: 'The live source id.', schema: str }],
|
|
256
|
+
responses: { '200': { description: 'The stylesheet.', contentType: 'text/css', schema: str }, '404': problem('Not a live source.') }
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
route: 'GET /api/reports/live/:source/tests/:testId/*', path: '/api/reports/live/{source}/tests/{testId}/{path}', operationId: 'getLiveTestFile', tag: 'Report', role: 'read',
|
|
260
|
+
summary: 'A file of a test in a live spec run (development only)',
|
|
261
|
+
description: 'For spec runs writing on the same machine as a development host. A server has none: it answers `404`.',
|
|
262
|
+
parameters: [{ name: 'source', in: 'path', required: true, description: 'The live source id.', schema: str }, { name: 'testId', in: 'path', required: true, description: 'The test id.', schema: str }, { name: 'path', in: 'path', required: true, description: 'The path under the test.', schema: str }],
|
|
263
|
+
responses: { '200': { description: 'The file.', contentType: 'application/octet-stream', schema: { type: 'string', format: 'binary' } }, '404': problem('Not a live source.') }
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
route: 'GET /api/data/sources.json', path: '/api/data/sources.json', operationId: 'getSources', tag: 'Viewer data', role: 'read',
|
|
267
|
+
summary: 'The sources the viewer reads',
|
|
268
|
+
description: 'The viewer\'s address book of data sources, in the shape a static host serves. A server has one source, itself.',
|
|
269
|
+
responses: { '200': json('The sources file.', 'ReportSources'), '401': UNAUTHORIZED }
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
route: 'GET /api/data/specs/index.json', path: '/api/data/specs/index.json', operationId: 'getSpecIndex', tag: 'Viewer data', role: 'read',
|
|
273
|
+
summary: 'The spec files',
|
|
274
|
+
description: 'The files of the newest committed spec set, relative to `specs/`.',
|
|
275
|
+
responses: { '200': json('The index.', 'SpecIndex'), '401': UNAUTHORIZED }
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
route: 'GET /api/data/specs/*', path: '/api/data/specs/{file}', operationId: 'getSpecFile', tag: 'Viewer data', role: 'read',
|
|
279
|
+
summary: 'One spec file',
|
|
280
|
+
description: 'The text of a spec file, with the digest of its content as `ETag`.',
|
|
281
|
+
parameters: [{ name: 'file', in: 'path', required: true, description: 'The file\'s path relative to `specs/`.', schema: str }],
|
|
282
|
+
responses: { '200': { description: 'The text.', contentType: 'text/plain', schema: str }, '304': { description: 'Not modified.' }, '401': UNAUTHORIZED, '404': problem('No such spec.') }
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
route: 'GET /api/data/runs/index.json', path: '/api/data/runs/index.json', operationId: 'getRunsIndex', tag: 'Viewer data', role: 'read',
|
|
286
|
+
summary: 'The statechart runs',
|
|
287
|
+
description: 'The newest uploaded statechart runs (`atlas report publish --manifest … --upload-run`), newest first.',
|
|
288
|
+
responses: { '200': json('The index.', 'RunsIndex'), '401': UNAUTHORIZED }
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
route: 'GET /api/data/runs/:id/*', path: '/api/data/runs/{id}/{path}', operationId: 'getRunFile', tag: 'Viewer data', role: 'read',
|
|
292
|
+
summary: 'A file of an uploaded run',
|
|
293
|
+
description: 'A file of an uploaded run: its manifest, timelines, screenshots, clips. Untrusted bytes, served sandboxed; immutable.',
|
|
294
|
+
parameters: [{ name: 'id', in: 'path', required: true, description: 'The run id.', schema: str }, { name: 'path', in: 'path', required: true, description: 'The path inside the run.', schema: str }],
|
|
295
|
+
responses: { '200': { description: 'The file.', contentType: 'application/octet-stream', schema: { type: 'string', format: 'binary' } }, '401': UNAUTHORIZED, '404': problem('No such run or file.') }
|
|
296
|
+
}
|
|
297
|
+
];
|
|
298
|
+
/**
|
|
299
|
+
* Routes the server registers that are not operations of this API, each with the reason. The coverage test skips
|
|
300
|
+
* these and only these.
|
|
301
|
+
*/
|
|
302
|
+
export const NOT_OPERATIONS = new Map([
|
|
303
|
+
['ALL /*', 'Middleware: authentication and request logging for every route.'],
|
|
304
|
+
['POST /api/*', 'Every other path under /api answers 405 to a write, never the page.'],
|
|
305
|
+
['PUT /api/*', 'Every other path under /api answers 405 to a write, never the page.'],
|
|
306
|
+
['PATCH /api/*', 'Every other path under /api answers 405 to a write, never the page.'],
|
|
307
|
+
['DELETE /api/*', 'Every other path under /api answers 405 to a write, never the page.'],
|
|
308
|
+
['GET /*', 'The viewer (a single-page app); an unknown /api path is a 404 problem document, never the page.']
|
|
309
|
+
]);
|
|
310
|
+
const SECURITY = {
|
|
311
|
+
none: [],
|
|
312
|
+
read: [{ bearer: [] }, { basic: [] }],
|
|
313
|
+
publish: [{ bearer: [] }],
|
|
314
|
+
admin: [{ bearer: [] }]
|
|
315
|
+
};
|
|
316
|
+
/** The OpenAPI 3.1 document of the API. `serverUrl` (such as `https://atlas.example.com`) is written into `servers` when given. */
|
|
317
|
+
export function openApiDocument(options = {}) {
|
|
318
|
+
const paths = {};
|
|
319
|
+
for (const op of OPERATIONS) {
|
|
320
|
+
const method = op.route.split(' ')[0].toLowerCase();
|
|
321
|
+
(paths[op.path] ??= {})[method] = {
|
|
322
|
+
operationId: op.operationId,
|
|
323
|
+
tags: [op.tag],
|
|
324
|
+
summary: op.summary,
|
|
325
|
+
description: `${op.description}${op.role === 'none' ? '' : `\n\nRole needed: \`${op.role}\`.`}`,
|
|
326
|
+
'x-atlas-role': op.role,
|
|
327
|
+
security: SECURITY[op.role],
|
|
328
|
+
...(op.parameters?.length ? { parameters: op.parameters.map(({ schema, ...rest }) => ({ required: rest.in === 'path' ? true : false, ...rest, schema: schema ?? str })) } : {}),
|
|
329
|
+
...(op.body ? { requestBody: { required: op.body.required ?? false, description: op.body.description, content: { [op.body.contentType]: { schema: op.body.schema } } } } : {}),
|
|
330
|
+
responses: Object.fromEntries(Object.entries(op.responses).map(([status, response]) => [status, {
|
|
331
|
+
description: response.description,
|
|
332
|
+
...(response.headers ? { headers: Object.fromEntries(Object.entries(response.headers).map(([name, header]) => [name, header])) } : {}),
|
|
333
|
+
...(response.contentType ? { content: { [response.contentType]: { schema: response.schema ?? {} } } } : {})
|
|
334
|
+
}]))
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
openapi: '3.1.0',
|
|
339
|
+
info: {
|
|
340
|
+
title: 'Atlas server API',
|
|
341
|
+
version: API_VERSION,
|
|
342
|
+
summary: 'Take in test results from every environment, keep them, and show everyone the combined report, live.',
|
|
343
|
+
description: [
|
|
344
|
+
'One Atlas server per project. Clients upload layers of results (`POST /api/checks`, or the store protocol under `/api/store`); viewers read the combined report (`/api/reports/evidence/effective`) and are told when it changes (`/api/events`). A report is layered, never partitioned: the newest completed result of every check wins its slot, whoever sent it.',
|
|
345
|
+
'',
|
|
346
|
+
'**Authentication.** With tokens configured, send `Authorization: Bearer <token>`. Roles: `read` < `publish` < `admin`. Uploading needs a Bearer token; a browser\'s HTTP Basic sign-in (any user name, the token as the password) is accepted for reading only. A server with no tokens is open and listens on this machine only unless told otherwise.',
|
|
347
|
+
'',
|
|
348
|
+
'**Errors** are [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem documents (`application/problem+json`) with a `detail` that says what to do. `401` send a token, `403` that token may not, `409` upload what this depends on first or you are changing something immutable, `413` too large, `422` not valid, `503` still starting (retry after `Retry-After`).',
|
|
349
|
+
'',
|
|
350
|
+
'**Compatibility.** Within `/api` the API only grows: optional parameters, optional response fields, new events. Ignore what you do not know.',
|
|
351
|
+
'',
|
|
352
|
+
'Guides: `/llms.txt`. Source: https://github.com/crvouga/atlas.'
|
|
353
|
+
].join('\n'),
|
|
354
|
+
license: { name: 'MIT', identifier: 'MIT' }
|
|
355
|
+
},
|
|
356
|
+
servers: [{ url: options.serverUrl ?? '/' }],
|
|
357
|
+
tags: [
|
|
358
|
+
{ name: 'Operations', description: 'Health, readiness, identity, and this description.' },
|
|
359
|
+
{ name: 'Report', description: 'The combined report, and the gate.' },
|
|
360
|
+
{ name: 'Live', description: 'Server-sent events.' },
|
|
361
|
+
{ name: 'Upload', description: 'The simplest way in: one JSON request.' },
|
|
362
|
+
{ name: 'Store protocol', description: 'The `ObjectStore` interface over HTTP: what `atlas report publish`, `stream`, `merge` and `httpStore` use.' },
|
|
363
|
+
{ name: 'Viewer data', description: 'Specs and runs, laid out the way the viewer reads a static host.' }
|
|
364
|
+
],
|
|
365
|
+
paths,
|
|
366
|
+
components: {
|
|
367
|
+
securitySchemes: {
|
|
368
|
+
bearer: { type: 'http', scheme: 'bearer', description: 'A token of the server: `Authorization: Bearer <token>`. Needed to upload.' },
|
|
369
|
+
basic: { type: 'http', scheme: 'basic', description: 'Any user name, the token as the password. For reading only: the browser\'s sign-in box.' }
|
|
370
|
+
},
|
|
371
|
+
schemas: Object.fromEntries(Object.entries(COMPONENTS).map(([name, [schema, io]]) => [name, schemaOf(schema, io)]))
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** A response for a problem, built directly (used where no handler is involved, such as authentication). */
|
|
2
|
+
export declare function problemResponse(status: number, detail: string, headers?: Record<string, string>): Response;
|
|
3
|
+
/**
|
|
4
|
+
* The response made into a problem document when it is an error (status 400 or more) with a JSON body naming an
|
|
5
|
+
* `error`. Headers (`WWW-Authenticate`, `Retry-After`, `Allow`) and any other body fields are kept. Anything else,
|
|
6
|
+
* including every success, is returned as it is.
|
|
7
|
+
*/
|
|
8
|
+
export declare function asProblem(response: Response): Promise<Response>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Problem documents
|
|
3
|
+
*
|
|
4
|
+
* Every error the server answers is an [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem document
|
|
5
|
+
* (`application/problem+json`): `type`, `title`, `status`, `detail`, and `error`, a copy of `detail` for clients
|
|
6
|
+
* written before the standard was adopted. Handlers answer however is natural (`{ error }`); one place,
|
|
7
|
+
* {@link asProblem}, makes every error response a problem document, so none can differ.
|
|
8
|
+
*/
|
|
9
|
+
import { STATUS_CODES } from 'node:http';
|
|
10
|
+
/** A response for a problem, built directly (used where no handler is involved, such as authentication). */
|
|
11
|
+
export function problemResponse(status, detail, headers = {}) {
|
|
12
|
+
return new Response(JSON.stringify({ type: 'about:blank', title: STATUS_CODES[status] ?? 'Error', status, detail, error: detail }), {
|
|
13
|
+
status,
|
|
14
|
+
headers: { 'content-type': 'application/problem+json', ...headers }
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The response made into a problem document when it is an error (status 400 or more) with a JSON body naming an
|
|
19
|
+
* `error`. Headers (`WWW-Authenticate`, `Retry-After`, `Allow`) and any other body fields are kept. Anything else,
|
|
20
|
+
* including every success, is returned as it is.
|
|
21
|
+
*/
|
|
22
|
+
export async function asProblem(response) {
|
|
23
|
+
if (response.status < 400 || !/\bjson\b/.test(response.headers.get('content-type') ?? '') || /problem\+json/.test(response.headers.get('content-type') ?? ''))
|
|
24
|
+
return response;
|
|
25
|
+
let body;
|
|
26
|
+
try {
|
|
27
|
+
body = await response.clone().json();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return response;
|
|
31
|
+
}
|
|
32
|
+
if (typeof body !== 'object' || body === null || typeof body.error !== 'string')
|
|
33
|
+
return response;
|
|
34
|
+
const { error, ...rest } = body;
|
|
35
|
+
const headers = new Headers(response.headers);
|
|
36
|
+
headers.set('content-type', 'application/problem+json');
|
|
37
|
+
headers.delete('content-length');
|
|
38
|
+
return new Response(JSON.stringify({ type: 'about:blank', title: STATUS_CODES[response.status] ?? 'Error', status: response.status, detail: error, error, ...rest }), { status: response.status, headers });
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Finding the viewer
|
|
3
|
+
*
|
|
4
|
+
* Locates the built viewer a server serves: the folder named by `--site`, `./atlas-site`, or one built once into the user's
|
|
5
|
+
* cache from the viewer source shipped in the package (`atlas-visualizer build --no-data`). The build is the same for every
|
|
6
|
+
* project because the server writes its own address book into the page, so it is built at most once per installed version.
|
|
7
|
+
*/
|
|
8
|
+
/** Where the viewer's source is, in this repository and in an installed package. */
|
|
9
|
+
export declare function visualizerRoot(): string | null;
|
|
10
|
+
/** Build the viewer once into a folder, for a server that was not given a prebuilt one. */
|
|
11
|
+
export declare function buildSite(out: string, log: (text: string) => void): Promise<boolean>;
|
|
12
|
+
/** The viewer to serve: the one named, a build beside the project, or one built into the user's cache. */
|
|
13
|
+
export declare function findSite(named: string | undefined, cwd: string, log: (text: string) => void): Promise<string | null>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Finding the viewer
|
|
3
|
+
*
|
|
4
|
+
* Locates the built viewer a server serves: the folder named by `--site`, `./atlas-site`, or one built once into the user's
|
|
5
|
+
* cache from the viewer source shipped in the package (`atlas-visualizer build --no-data`). The build is the same for every
|
|
6
|
+
* project because the server writes its own address book into the page, so it is built at most once per installed version.
|
|
7
|
+
*/
|
|
8
|
+
import { spawn } from 'node:child_process';
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
/** Where the viewer's source is, in this repository and in an installed package. */
|
|
14
|
+
export function visualizerRoot() {
|
|
15
|
+
for (const relative of ['../../visualizer', '../../../packages/visualizer']) {
|
|
16
|
+
const dir = fileURLToPath(new URL(relative, import.meta.url));
|
|
17
|
+
if (existsSync(path.join(dir, 'bin', 'atlas-visualizer.mjs')))
|
|
18
|
+
return dir;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
/** Build the viewer once into a folder, for a server that was not given a prebuilt one. */
|
|
23
|
+
export async function buildSite(out, log) {
|
|
24
|
+
const root = visualizerRoot();
|
|
25
|
+
if (!root)
|
|
26
|
+
return false;
|
|
27
|
+
log(`Building the viewer into ${out} (once; pass --site to use a prebuilt one)\n`);
|
|
28
|
+
return new Promise(resolve => {
|
|
29
|
+
const child = spawn(process.execPath, [path.join(root, 'bin', 'atlas-visualizer.mjs'), 'build', '--no-data', '--out', out], { stdio: ['ignore', 'ignore', 'inherit'], env: { ...process.env, INIT_CWD: process.cwd() } });
|
|
30
|
+
child.on('error', () => resolve(false));
|
|
31
|
+
child.on('close', code => resolve(code === 0 && existsSync(path.join(out, 'index.html'))));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/** The viewer to serve: the one named, a build beside the project, or one built into the user's cache. */
|
|
35
|
+
export async function findSite(named, cwd, log) {
|
|
36
|
+
if (named)
|
|
37
|
+
return existsSync(path.join(named, 'index.html')) ? named : null;
|
|
38
|
+
const cache = path.join(os.homedir(), '.cache', 'atlas', 'site');
|
|
39
|
+
for (const candidate of [path.resolve(cwd, 'atlas-site'), cache])
|
|
40
|
+
if (existsSync(path.join(candidate, 'index.html')))
|
|
41
|
+
return candidate;
|
|
42
|
+
return (await buildSite(cache, log)) ? cache : null;
|
|
43
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Store protocol routes
|
|
3
|
+
*
|
|
4
|
+
* The `ObjectStore` interface over HTTP at `/api/store`: list keys under `layers/`, read and probe an object, upload one
|
|
5
|
+
* (checked by `validateUpload`, immutable once stored) and remove a scratch object. This is what `httpStore` talks to. An
|
|
6
|
+
* upload is told to the report only after it is stored, and a layer's manifest, parsed while it was checked, is handed on so
|
|
7
|
+
* it is not read again. Everything read is served as untrusted bytes (never sniffed, never run).
|
|
8
|
+
*
|
|
9
|
+
* @see docs/server.md#the-store-protocol
|
|
10
|
+
*/
|
|
11
|
+
import { Hono } from 'hono';
|
|
12
|
+
import { type ObjectStore } from '../../reports/src/object-store.js';
|
|
13
|
+
import { readBytes } from '../../reports/src/object-io.js';
|
|
14
|
+
import type { ReportLayer } from '../../reports/src/layer-types.js';
|
|
15
|
+
import type { Auth } from './auth.js';
|
|
16
|
+
/** What the store routes need: the `store` they read and write, the `auth` that decides who may, the largest object accepted, and `onStored`, told after each upload with its key, its kind and (for a layer catalog entry) the layer already parsed. */
|
|
17
|
+
export type StoreApiOptions = {
|
|
18
|
+
readonly store: ObjectStore;
|
|
19
|
+
readonly auth: Auth;
|
|
20
|
+
readonly maxObjectBytes: number;
|
|
21
|
+
/** Told after something was uploaded: its key, and what kind of thing it is. */
|
|
22
|
+
readonly onStored: (key: string, kind: string, layer?: ReportLayer) => void | Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
/** Read a request body, refusing as soon as it is over the limit rather than after holding it all. */
|
|
25
|
+
export declare function readLimited(request: Request, limit: number): Promise<Uint8Array>;
|
|
26
|
+
/** The Hono app of the store protocol, to be mounted at `/api/store`. Reads need role `read`; writes need `publish` through a Bearer token. */
|
|
27
|
+
export declare function storeApi(options: StoreApiOptions): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
28
|
+
export { readBytes };
|