@crvouga/atlas 0.4.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 +7 -1
- package/README.md +178 -617
- 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 +35 -5
- package/dist/reports/src/evidence-live.js +18 -2
- package/dist/reports/src/evidence-types.d.ts +68 -1
- package/dist/reports/src/evidence-types.js +5 -1
- package/dist/reports/src/evidence-view.d.ts +43 -4
- package/dist/reports/src/evidence-view.js +13 -2
- 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 +35 -5
- package/dist/reports/src/layer-axes.js +25 -5
- package/dist/reports/src/layer-catalog.d.ts +28 -1
- package/dist/reports/src/layer-catalog.js +18 -1
- package/dist/reports/src/layer-merge.d.ts +98 -9
- package/dist/reports/src/layer-merge.js +147 -31
- package/dist/reports/src/layer-source.d.ts +18 -0
- package/dist/reports/src/layer-source.js +11 -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 +32 -1
- package/dist/reports/src/layers-route.d.ts +47 -4
- package/dist/reports/src/layers-route.js +86 -7
- 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 +26 -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 +42 -9
- package/dist/reports/src/publish.js +36 -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 +37 -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 +32 -4
- package/packages/core/README.md +2 -1
- package/packages/reports/README.md +65 -0
- package/packages/reports/STORAGE.md +4 -0
- 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 +21 -0
- 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 +40 -3
- 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 +3 -1
- package/packages/visualizer/src/evidence/evidence-context.tsx +74 -12
- 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/vite/atlas-server-proxy.ts +36 -0
- package/packages/visualizer/vite.config.ts +11 -3
|
@@ -1,11 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Choosing between the shared and the local reports store
|
|
3
|
+
*
|
|
4
|
+
* Decides which store answers for a commit or a source when a project has both a shared store and this machine's own,
|
|
5
|
+
* and merges their source records. The local store wins for what it holds, so a remote outage cannot hide a local run.
|
|
6
|
+
*/
|
|
1
7
|
import type { SourceRecord } from './protocol.js';
|
|
2
8
|
import type { ObjectStore } from './object-store.js';
|
|
9
|
+
/** The stores a reports host reads. */
|
|
3
10
|
export type ReportsStores = {
|
|
11
|
+
/** The shared reports store, or `null` when this deployment has no credential for one. */
|
|
4
12
|
readonly store: ObjectStore | null;
|
|
13
|
+
/** This machine's own store, where collected local runs are written. */
|
|
5
14
|
readonly localStore?: ObjectStore;
|
|
6
15
|
};
|
|
7
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* The store to read a commit from: the local store when it has that commit's record, else the shared store (`null`
|
|
18
|
+
* when there is none). Collected local runs own their timelines; a remote outage cannot hide them.
|
|
19
|
+
*/
|
|
8
20
|
export declare function reportsStoreForCommit(deps: ReportsStores, sha: string): Promise<ObjectStore | null>;
|
|
21
|
+
/** The store to read one source of a commit from: the local store when it holds that source, else the shared store (`null` when there is none). */
|
|
9
22
|
export declare function reportsStoreForSource(deps: ReportsStores, sha: string, source: string): Promise<ObjectStore | null>;
|
|
10
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Lists the source records of a commit from the shared and the local store, one per source name. A local source
|
|
25
|
+
* replaces the shared one of the same name only; others from both are kept. A store that fails is skipped, and the
|
|
26
|
+
* error is thrown only when no source was found at all and a store failed.
|
|
27
|
+
*/
|
|
11
28
|
export declare function reportsSources(deps: ReportsStores, sha: string): Promise<readonly SourceRecord[]>;
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import { listSourceRecords, readCommit, } from './read.js';
|
|
2
2
|
import { readStoredSource } from './source-content.js';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* The store to read a commit from: the local store when it has that commit's record, else the shared store (`null`
|
|
5
|
+
* when there is none). Collected local runs own their timelines; a remote outage cannot hide them.
|
|
6
|
+
*/
|
|
4
7
|
export async function reportsStoreForCommit(deps, sha) {
|
|
5
8
|
if (deps.localStore && (await readCommit(deps.localStore, sha)) !== null)
|
|
6
9
|
return deps.localStore;
|
|
7
10
|
return deps.store;
|
|
8
11
|
}
|
|
12
|
+
/** The store to read one source of a commit from: the local store when it holds that source, else the shared store (`null` when there is none). */
|
|
9
13
|
export async function reportsStoreForSource(deps, sha, source) {
|
|
10
14
|
if (deps.localStore &&
|
|
11
15
|
(await readStoredSource(deps.localStore, sha, source)) !== null)
|
|
12
16
|
return deps.localStore;
|
|
13
17
|
return deps.store;
|
|
14
18
|
}
|
|
15
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Lists the source records of a commit from the shared and the local store, one per source name. A local source
|
|
21
|
+
* replaces the shared one of the same name only; others from both are kept. A store that fails is skipped, and the
|
|
22
|
+
* error is thrown only when no source was found at all and a store failed.
|
|
23
|
+
*/
|
|
16
24
|
export async function reportsSources(deps, sha) {
|
|
17
25
|
const stores = [deps.store, deps.localStore].filter((store) => store != null);
|
|
18
26
|
const results = await Promise.allSettled(stores.map((store) => listSourceRecords(store, sha)));
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Resilient report cache
|
|
3
|
+
*
|
|
4
|
+
* Wraps a persistent `ReportCache` (IndexedDB, which a browser may deny or corrupt) so a failure of the cache never
|
|
5
|
+
* blocks reports: everything is also kept in memory and every storage error is swallowed.
|
|
6
|
+
*/
|
|
1
7
|
import type { ReportCache } from './fetcher.js';
|
|
2
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Returns a cache that reads and writes `cache` and also keeps every value in memory. Storage is an optimization. A
|
|
10
|
+
* denied or corrupt persistent cache cannot block reports.
|
|
11
|
+
*
|
|
12
|
+
* `get` answers from `cache`, falling back to memory when it has no value or throws. `set` and `remove` always apply
|
|
13
|
+
* to memory first, then try `cache` and ignore its errors, so a value written while storage is failing is still
|
|
14
|
+
* readable for the life of this wrapper (but not after a reload).
|
|
15
|
+
*/
|
|
3
16
|
export declare function resilientReportCache(cache: ReportCache): ReportCache;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { memoryReportCache } from './fetcher.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Returns a cache that reads and writes `cache` and also keeps every value in memory. Storage is an optimization. A
|
|
4
|
+
* denied or corrupt persistent cache cannot block reports.
|
|
5
|
+
*
|
|
6
|
+
* `get` answers from `cache`, falling back to memory when it has no value or throws. `set` and `remove` always apply
|
|
7
|
+
* to memory first, then try `cache` and ignore its errors, so a value written while storage is failing is still
|
|
8
|
+
* readable for the life of this wrapper (but not after a reload).
|
|
9
|
+
*/
|
|
3
10
|
export function resilientReportCache(cache) {
|
|
4
11
|
const fallback = memoryReportCache();
|
|
5
12
|
return {
|
|
@@ -1,9 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Report retention
|
|
3
|
+
*
|
|
4
|
+
* The plan for what cleanup may remove from a store. There is none: Atlas never deletes a layer or a commit's evidence
|
|
5
|
+
* by itself (the contract is deletion-free), so the plan always retains everything and lists no key to delete.
|
|
6
|
+
* Retention is the operator's choice, made outside Atlas.
|
|
7
|
+
*
|
|
8
|
+
* @see docs/report-contract.md#guarantees
|
|
9
|
+
*/
|
|
1
10
|
import type { ObjectStore } from './object-store.js';
|
|
2
11
|
import type { CommitRecord } from './protocol.js';
|
|
12
|
+
/** What a retention pass would keep and delete. */
|
|
3
13
|
export type ReportRetentionPlan = {
|
|
14
|
+
/** The commit records found, all of them. */
|
|
4
15
|
readonly retained: readonly CommitRecord[];
|
|
16
|
+
/** Keys to delete. Always empty: automatic cleanup never discards evidence. */
|
|
5
17
|
readonly deleteKeys: readonly string[];
|
|
6
18
|
};
|
|
7
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Lists the commits recorded in `index/` with their records, and no key to delete. Report evidence outlives branch
|
|
21
|
+
* heads: automatic cleanup never discards a layer. A listed commit with no readable record is left out of `retained`.
|
|
22
|
+
*/
|
|
8
23
|
export declare function planReportRetention(store: ObjectStore): Promise<ReportRetentionPlan>;
|
|
24
|
+
/** Same as `planReportRetention`. Despite the name it deletes nothing, and `deleteKeys` is always empty. */
|
|
9
25
|
export declare function pruneReports(store: ObjectStore): Promise<ReportRetentionPlan>;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { readCommit } from './read.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Lists the commits recorded in `index/` with their records, and no key to delete. Report evidence outlives branch
|
|
4
|
+
* heads: automatic cleanup never discards a layer. A listed commit with no readable record is left out of `retained`.
|
|
5
|
+
*/
|
|
3
6
|
export async function planReportRetention(store) {
|
|
4
7
|
const keys = await store.listKeys('index/');
|
|
5
8
|
const shas = new Set(keys.flatMap(key => /([0-9a-f]{40})\.json$/.exec(key)?.[1] ?? []));
|
|
@@ -11,6 +14,7 @@ export async function planReportRetention(store) {
|
|
|
11
14
|
}
|
|
12
15
|
return { retained, deleteKeys: [] };
|
|
13
16
|
}
|
|
17
|
+
/** Same as `planReportRetention`. Despite the name it deletes nothing, and `deleteKeys` is always empty. */
|
|
14
18
|
export async function pruneReports(store) {
|
|
15
19
|
return planReportRetention(store);
|
|
16
20
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type ObjectStore, type StoredObject } from './object-store.js';
|
|
2
|
+
type Count = {
|
|
3
|
+
total: number;
|
|
4
|
+
passed: number;
|
|
5
|
+
failed: number;
|
|
6
|
+
flaky: number;
|
|
7
|
+
notReached: number;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Builds the row the viewer lists for a finished run (a `RunSummary`) from its `manifest.json`, which may be any JSON:
|
|
11
|
+
* every field is read defensively and a missing one takes a default.
|
|
12
|
+
*
|
|
13
|
+
* `startedAt` is `run.startedAt`, else the time in the run id (`2026-10-09T12-00-00-000Z-...`), else the epoch;
|
|
14
|
+
* `mode` is `run.mode`, else `showcase` for an id ending in `showcase`, else `fast`. `journeys` counts the manifest's
|
|
15
|
+
* `journeys` (or the `paths` of kind `journey`) by status `passed`, `failed`, `flaky`, with any other status counted
|
|
16
|
+
* as `notReached`. `progress` is always `complete`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function summarizeManifest(id: string, manifest: unknown): {
|
|
19
|
+
journeys: Count;
|
|
20
|
+
coverage?: {
|
|
21
|
+
transitions?: Count | undefined;
|
|
22
|
+
states?: Count | undefined;
|
|
23
|
+
} | undefined;
|
|
24
|
+
progress: "complete";
|
|
25
|
+
hasManifest: boolean;
|
|
26
|
+
specVersion?: string | undefined;
|
|
27
|
+
durationMs?: number | undefined;
|
|
28
|
+
id: string;
|
|
29
|
+
startedAt: string;
|
|
30
|
+
mode: string;
|
|
31
|
+
commit: string | null;
|
|
32
|
+
branch: string | null;
|
|
33
|
+
};
|
|
34
|
+
/** Thrown by `publishRunBundle` when a run's privacy scan did not pass (or never ran), so the run is not uploaded. The same rule the static viewer applies. */
|
|
35
|
+
export declare class RunNotShareableError extends Error {
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Uploads a finished run directory so the viewer can browse it from the store. Files go first and
|
|
39
|
+
* `manifest.json` after them, then the catalog entry, so a half-uploaded run is never listed. Running it
|
|
40
|
+
* again after a failure finishes the job; a run that is already complete is left alone.
|
|
41
|
+
*
|
|
42
|
+
* The run id is the directory's name (letters, digits, `_`, `.`, `-`; not starting with a dot). Files are written to
|
|
43
|
+
* `layers/runs/<id>/<relative path>` with a media type from their extension; dot-files, dot-folders and symbolic links
|
|
44
|
+
* are skipped. Resolves to the file and byte counts uploaded (both 0 with `alreadyPublished: true` when the manifest
|
|
45
|
+
* was already in the store; a missing catalog entry is then written). Throws `RunNotShareableError` when
|
|
46
|
+
* `manifest.privacy.passed` is not `true`, and an `Error` for a badly named directory or a missing or unreadable
|
|
47
|
+
* `manifest.json`.
|
|
48
|
+
*/
|
|
49
|
+
export declare function publishRunBundle(store: ObjectStore, directory: string): Promise<{
|
|
50
|
+
readonly id: string;
|
|
51
|
+
readonly files: number;
|
|
52
|
+
readonly bytes: number;
|
|
53
|
+
readonly alreadyPublished: boolean;
|
|
54
|
+
}>;
|
|
55
|
+
/**
|
|
56
|
+
* Lists the newest `limit` (default 100) runs in the store, newest first by `startedAt` (ties by id). The catalog is
|
|
57
|
+
* newest first, so one page of it is enough; entries never change, so `cache` (catalog key to summary) lets a
|
|
58
|
+
* long-lived reader open each only once. A catalog entry that is empty or not JSON is skipped.
|
|
59
|
+
*/
|
|
60
|
+
export declare function listRunSummaries(store: ObjectStore, limit?: number, cache?: Map<string, ReturnType<typeof summarizeManifest>>): Promise<ReturnType<typeof summarizeManifest>[]>;
|
|
61
|
+
/** Reads one file of a run, or `null` when it is missing. Also `null`, without reading, for an `id` that is not a plain run name or a `relative` path with an empty, `.`, `..` or backslash segment, so a path can never leave the run. */
|
|
62
|
+
export declare function readRunFile(store: ObjectStore, id: string, relative: string): Promise<StoredObject | null>;
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Run bundles
|
|
3
|
+
*
|
|
4
|
+
* Uploads a finished statechart run directory (manifest, screenshots, clips) to a store under `layers/runs/<run>/` and
|
|
5
|
+
* lists and reads them back for the viewer. A run is shared only once its privacy scan passed. The manifest is written
|
|
6
|
+
* after its files and the catalog entry (`layers/runs/index/<inverted-time>-<run>.json`) after the manifest, so a
|
|
7
|
+
* half-uploaded run is never listed, and uploading again finishes the job. Uses Node's `fs`, so it is not for
|
|
8
|
+
* browsers.
|
|
9
|
+
*
|
|
10
|
+
* @see docs/report-contract.md#where-bytes-go
|
|
11
|
+
*/
|
|
12
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { eachLimited } from './completion-order.js';
|
|
15
|
+
import { contentTypeOf } from './media-types.js';
|
|
16
|
+
import { readUtf8, writeUtf8 } from './object-io.js';
|
|
17
|
+
import { isPaged } from './object-store.js';
|
|
18
|
+
const RUN_PREFIX = 'layers/runs/';
|
|
19
|
+
const INDEX_PREFIX = 'layers/runs/index/';
|
|
20
|
+
const object = (value) => (value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {});
|
|
21
|
+
const count = (value) => {
|
|
22
|
+
const found = object(value);
|
|
23
|
+
if (!Object.keys(found).length)
|
|
24
|
+
return undefined;
|
|
25
|
+
const n = (key) => (typeof found[key] === 'number' ? found[key] : 0);
|
|
26
|
+
return { total: n('total'), passed: n('passed'), failed: n('failed'), flaky: n('flaky'), notReached: n('notReached') };
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Builds the row the viewer lists for a finished run (a `RunSummary`) from its `manifest.json`, which may be any JSON:
|
|
30
|
+
* every field is read defensively and a missing one takes a default.
|
|
31
|
+
*
|
|
32
|
+
* `startedAt` is `run.startedAt`, else the time in the run id (`2026-10-09T12-00-00-000Z-...`), else the epoch;
|
|
33
|
+
* `mode` is `run.mode`, else `showcase` for an id ending in `showcase`, else `fast`. `journeys` counts the manifest's
|
|
34
|
+
* `journeys` (or the `paths` of kind `journey`) by status `passed`, `failed`, `flaky`, with any other status counted
|
|
35
|
+
* as `notReached`. `progress` is always `complete`.
|
|
36
|
+
*/
|
|
37
|
+
export function summarizeManifest(id, manifest) {
|
|
38
|
+
const root = object(manifest);
|
|
39
|
+
const run = object(root.run);
|
|
40
|
+
const coverage = object(object(root.coverage).overall);
|
|
41
|
+
const paths = (Array.isArray(root.journeys) ? root.journeys : Array.isArray(root.paths) ? root.paths.filter(item => object(item).kind === 'journey') : []).map(object);
|
|
42
|
+
const journeys = { total: paths.length, passed: 0, failed: 0, flaky: 0, notReached: 0 };
|
|
43
|
+
for (const item of paths) {
|
|
44
|
+
if (item.status === 'passed')
|
|
45
|
+
journeys.passed++;
|
|
46
|
+
else if (item.status === 'failed')
|
|
47
|
+
journeys.failed++;
|
|
48
|
+
else if (item.status === 'flaky')
|
|
49
|
+
journeys.flaky++;
|
|
50
|
+
else
|
|
51
|
+
journeys.notReached++;
|
|
52
|
+
}
|
|
53
|
+
const fromId = id.match(/^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z/);
|
|
54
|
+
return {
|
|
55
|
+
id,
|
|
56
|
+
startedAt: typeof run.startedAt === 'string' ? run.startedAt : fromId ? `${fromId[1]}T${fromId[2]}:${fromId[3]}:${fromId[4]}.${fromId[5]}Z` : new Date(0).toISOString(),
|
|
57
|
+
mode: typeof run.mode === 'string' ? run.mode : id.endsWith('showcase') ? 'showcase' : 'fast',
|
|
58
|
+
commit: typeof run.commit === 'string' ? run.commit : null,
|
|
59
|
+
branch: typeof run.branch === 'string' ? run.branch : null,
|
|
60
|
+
...(typeof run.durationMs === 'number' ? { durationMs: run.durationMs } : {}),
|
|
61
|
+
...(typeof run.specVersion === 'string' ? { specVersion: run.specVersion } : {}),
|
|
62
|
+
progress: 'complete',
|
|
63
|
+
hasManifest: true,
|
|
64
|
+
...(Object.keys(coverage).length ? { coverage: { ...(count(coverage.states) ? { states: count(coverage.states) } : {}), ...(count(coverage.transitions) ? { transitions: count(coverage.transitions) } : {}) } } : {}),
|
|
65
|
+
journeys
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function walk(dir, root = dir) {
|
|
69
|
+
const nested = await Promise.all((await readdir(dir, { withFileTypes: true })).map(async (entry) => {
|
|
70
|
+
if (entry.name.startsWith('.'))
|
|
71
|
+
return [];
|
|
72
|
+
const full = path.join(dir, entry.name);
|
|
73
|
+
return entry.isDirectory() ? walk(full, root) : entry.isFile() ? [path.relative(root, full).split(path.sep).join('/')] : [];
|
|
74
|
+
}));
|
|
75
|
+
return nested.flat();
|
|
76
|
+
}
|
|
77
|
+
/** Thrown by `publishRunBundle` when a run's privacy scan did not pass (or never ran), so the run is not uploaded. The same rule the static viewer applies. */
|
|
78
|
+
export class RunNotShareableError extends Error {
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Uploads a finished run directory so the viewer can browse it from the store. Files go first and
|
|
82
|
+
* `manifest.json` after them, then the catalog entry, so a half-uploaded run is never listed. Running it
|
|
83
|
+
* again after a failure finishes the job; a run that is already complete is left alone.
|
|
84
|
+
*
|
|
85
|
+
* The run id is the directory's name (letters, digits, `_`, `.`, `-`; not starting with a dot). Files are written to
|
|
86
|
+
* `layers/runs/<id>/<relative path>` with a media type from their extension; dot-files, dot-folders and symbolic links
|
|
87
|
+
* are skipped. Resolves to the file and byte counts uploaded (both 0 with `alreadyPublished: true` when the manifest
|
|
88
|
+
* was already in the store; a missing catalog entry is then written). Throws `RunNotShareableError` when
|
|
89
|
+
* `manifest.privacy.passed` is not `true`, and an `Error` for a badly named directory or a missing or unreadable
|
|
90
|
+
* `manifest.json`.
|
|
91
|
+
*/
|
|
92
|
+
export async function publishRunBundle(store, directory) {
|
|
93
|
+
const id = path.basename(path.resolve(directory));
|
|
94
|
+
if (!/^[\w.-]+$/.test(id) || id.startsWith('.'))
|
|
95
|
+
throw new Error(`A run directory is named like 2026-10-09T12-00-00-000Z-fast; "${id}" is not.`);
|
|
96
|
+
let manifest;
|
|
97
|
+
try {
|
|
98
|
+
manifest = JSON.parse(await readFile(path.join(directory, 'manifest.json'), 'utf8'));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
throw new Error(`${directory} has no readable manifest.json: is the run finished?`);
|
|
102
|
+
}
|
|
103
|
+
if (object(object(manifest).privacy).passed !== true)
|
|
104
|
+
throw new RunNotShareableError(`Run ${id} is not shared: its privacy scan did not pass (or never ran). Fix what it found and run it again.`);
|
|
105
|
+
const summary = summarizeManifest(id, manifest);
|
|
106
|
+
const inverse = String(Number.MAX_SAFE_INTEGER - Math.max(0, Date.parse(summary.startedAt) || 0)).padStart(16, '0');
|
|
107
|
+
const catalog = `${INDEX_PREFIX}${inverse}-${id}.json`;
|
|
108
|
+
if (await store.head(`${RUN_PREFIX}${id}/manifest.json`)) {
|
|
109
|
+
// The manifest is written before the catalog entry: an upload that died in between is finished here.
|
|
110
|
+
if (!(await store.head(catalog)))
|
|
111
|
+
await writeUtf8(store, catalog, JSON.stringify(summary), 'application/json');
|
|
112
|
+
return { id, files: 0, bytes: 0, alreadyPublished: true };
|
|
113
|
+
}
|
|
114
|
+
const files = await walk(directory);
|
|
115
|
+
let bytes = 0;
|
|
116
|
+
await eachLimited(files.filter(file => file !== 'manifest.json'), 16, async (file) => {
|
|
117
|
+
const body = new Uint8Array(await readFile(path.join(directory, file)));
|
|
118
|
+
bytes += body.length;
|
|
119
|
+
await store.put(`${RUN_PREFIX}${id}/${file}`, body, contentTypeOf(file));
|
|
120
|
+
});
|
|
121
|
+
const text = await readFile(path.join(directory, 'manifest.json'), 'utf8');
|
|
122
|
+
bytes += text.length;
|
|
123
|
+
await writeUtf8(store, `${RUN_PREFIX}${id}/manifest.json`, text, 'application/json');
|
|
124
|
+
await writeUtf8(store, catalog, JSON.stringify(summary), 'application/json');
|
|
125
|
+
return { id, files: files.length, bytes, alreadyPublished: false };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Lists the newest `limit` (default 100) runs in the store, newest first by `startedAt` (ties by id). The catalog is
|
|
129
|
+
* newest first, so one page of it is enough; entries never change, so `cache` (catalog key to summary) lets a
|
|
130
|
+
* long-lived reader open each only once. A catalog entry that is empty or not JSON is skipped.
|
|
131
|
+
*/
|
|
132
|
+
export async function listRunSummaries(store, limit = 100, cache = new Map()) {
|
|
133
|
+
const keys = isPaged(store) ? (await store.listPage(INDEX_PREFIX, { limit })).keys : (await store.listKeys(INDEX_PREFIX)).slice(0, limit);
|
|
134
|
+
const rows = [];
|
|
135
|
+
await eachLimited(keys, 16, async (key) => {
|
|
136
|
+
const known = cache.get(key);
|
|
137
|
+
if (known) {
|
|
138
|
+
rows.push(known);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const raw = await readUtf8(store, key);
|
|
142
|
+
if (!raw)
|
|
143
|
+
return;
|
|
144
|
+
try {
|
|
145
|
+
const row = JSON.parse(raw);
|
|
146
|
+
cache.set(key, row);
|
|
147
|
+
rows.push(row);
|
|
148
|
+
}
|
|
149
|
+
catch { /* an unreadable marker is skipped */ }
|
|
150
|
+
});
|
|
151
|
+
return rows.sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt) || (a.id < b.id ? -1 : 1));
|
|
152
|
+
}
|
|
153
|
+
/** Reads one file of a run, or `null` when it is missing. Also `null`, without reading, for an `id` that is not a plain run name or a `relative` path with an empty, `.`, `..` or backslash segment, so a path can never leave the run. */
|
|
154
|
+
export async function readRunFile(store, id, relative) {
|
|
155
|
+
const parts = relative.split('/');
|
|
156
|
+
if (!/^[\w.-]+$/.test(id) || id.startsWith('.') || parts.some(part => !part || part === '.' || part === '..' || part.includes('\\')))
|
|
157
|
+
return null;
|
|
158
|
+
return store.get(`${RUN_PREFIX}${id}/${relative}`);
|
|
159
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type CredentialProvider } from './aws-credentials.js';
|
|
2
|
+
import type { ObjectStore, PagedListing } from './object-store.js';
|
|
3
|
+
import { type Credentials } from './sigv4.js';
|
|
4
|
+
/** How to reach a bucket. Only `bucket` is required. */
|
|
5
|
+
export type S3StoreOptions = {
|
|
6
|
+
/** The bucket name. A name containing `.` is addressed path-style. */
|
|
7
|
+
readonly bucket: string;
|
|
8
|
+
/** A folder inside the bucket that holds everything: `atlas/my-project`. */
|
|
9
|
+
readonly prefix?: string;
|
|
10
|
+
/** Default `AWS_REGION`, then `us-east-1`. Cloudflare R2 uses `auto`. */
|
|
11
|
+
readonly region?: string;
|
|
12
|
+
/** For anything that is not AWS: `https://<account>.r2.cloudflarestorage.com`, `http://localhost:9000`. */
|
|
13
|
+
readonly endpoint?: string;
|
|
14
|
+
/** `endpoint/bucket/key` instead of `bucket.endpoint/key`. Default: yes for a custom endpoint, no for AWS. */
|
|
15
|
+
readonly forcePathStyle?: boolean;
|
|
16
|
+
/** Keys, or a provider. Default: the same lookup an AWS SDK does ({@link credentialChain}). */
|
|
17
|
+
readonly credentials?: Credentials | CredentialProvider;
|
|
18
|
+
/** The environment variables to read (`AWS_REGION`, `AWS_ENDPOINT_URL_S3`, credentials, ...). Default: `process.env`. */
|
|
19
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
20
|
+
/** The `fetch` to send requests with. Default: the global one; inject a fake in tests. */
|
|
21
|
+
readonly fetch?: typeof fetch;
|
|
22
|
+
/** Attempts after the first for a request that failed in a way worth retrying. Default 4. */
|
|
23
|
+
readonly retries?: number;
|
|
24
|
+
/** For the service to start answering, per attempt, in milliseconds. A large body may take longer. Default 30 000. */
|
|
25
|
+
readonly timeoutMs?: number;
|
|
26
|
+
/** Requests in flight at once. Default 64: S3 answers in tens of milliseconds, so parallelism is what makes a cold start fast. */
|
|
27
|
+
readonly concurrency?: number;
|
|
28
|
+
/** The clock used to sign requests. Default: the system clock; inject in tests. */
|
|
29
|
+
readonly now?: () => Date;
|
|
30
|
+
/** The wait between retries, in milliseconds. Default: a real timer; inject in tests. */
|
|
31
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* An error from the store. The message names the bucket and what to do about it (for example the missing IAM
|
|
35
|
+
* permission); `status` and `code` are what the service answered, `null` when it was never reached.
|
|
36
|
+
* `retryable` says whether trying again can help; the store already retried before throwing.
|
|
37
|
+
*/
|
|
38
|
+
export declare class S3Error extends Error {
|
|
39
|
+
readonly status: number | null;
|
|
40
|
+
readonly code: string | null;
|
|
41
|
+
readonly retryable: boolean;
|
|
42
|
+
constructor(message: string, status: number | null, code: string | null, retryable?: boolean);
|
|
43
|
+
}
|
|
44
|
+
/** An `ObjectStore` on S3 with paged listing, a secret-free label, and diagnostics. */
|
|
45
|
+
export type S3Store = ObjectStore & PagedListing & {
|
|
46
|
+
/** A description with no secrets: `s3://bucket/prefix (us-east-1)`. */
|
|
47
|
+
readonly label: string;
|
|
48
|
+
/** Where the credentials came from, once a request has been made. */
|
|
49
|
+
credentialSource(): string | null;
|
|
50
|
+
/** One cheap round trip: can this store be listed? Used by readiness checks and `atlas report doctor`. */
|
|
51
|
+
ping(): Promise<{
|
|
52
|
+
readonly ok: boolean;
|
|
53
|
+
readonly ms: number;
|
|
54
|
+
readonly error?: string;
|
|
55
|
+
}>;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* An {@link ObjectStore} on S3, or anything that speaks its protocol: Cloudflare R2, MinIO, Google
|
|
59
|
+
* Cloud Storage (interoperability), Backblaze B2, Wasabi, SeaweedFS. No SDK: requests are signed with
|
|
60
|
+
* Signature Version 4 and sent with `fetch`, so it runs on Bun and Node and adds nothing
|
|
61
|
+
* to the install.
|
|
62
|
+
*
|
|
63
|
+
* Made for many writers and a long-lived reader: connections are reused, requests in flight are
|
|
64
|
+
* capped, throttling and server errors are retried with jittered backoff, a bucket in another region
|
|
65
|
+
* is followed, and expired credentials are refreshed once.
|
|
66
|
+
*
|
|
67
|
+
* Behaviour to know: a missing key reads as `null`/`false` only when S3 answers 404. For an account without
|
|
68
|
+
* `s3:ListBucket`, S3 answers 403 for a missing key, which throws `S3Error` with a hint: the publishing credentials
|
|
69
|
+
* need `s3:GetObject`, `s3:PutObject` and `s3:ListBucket` (`atlas report policy` prints a least-privilege policy).
|
|
70
|
+
* `getUri` returns a presigned URL valid for one hour. `deleteMany` sends one request per key, 16 at a time.
|
|
71
|
+
* `listKeys` pages through the whole listing and sorts it; use `listPage` to read part of a large catalog. Nothing is
|
|
72
|
+
* checked or created at construction: the first request is the first contact.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* const store = s3Store({ bucket: 'my-evidence', prefix: 'atlas/my-project', region: 'eu-west-1' });
|
|
76
|
+
* await store.put('layers/manifests/x.json', bytes, 'application/json');
|
|
77
|
+
* const { ok, error } = await store.ping();
|
|
78
|
+
*/
|
|
79
|
+
export declare function s3Store(options: S3StoreOptions): S3Store;
|