From 517fa5798d8c7fc83d89d257536f8d199d642714 Mon Sep 17 00:00:00 2001 From: Chris Whited Date: Fri, 25 Sep 2026 11:14:29 -1000 Subject: [PATCH 1/4] chore(deps): bump to latest as of 2026-09-25. align with latest amp admin api --- .oxfmtrc.json | 30 + .oxlintrc.json | 8 +- .rulesync/commands/review-pr.md | 2 +- .rulesync/rules/effect.md | 44 +- .rulesync/rules/overview.md | 6 +- .vscode/extensions.json | 5 +- .vscode/settings.json | 3 +- dprint.json | 29 - package.json | 22 +- packages/amp/package.json | 20 +- packages/amp/src/admin/api.ts | 289 +- packages/amp/src/admin/domain.ts | 193 +- packages/amp/src/admin/error.ts | 715 ++--- packages/amp/src/admin/service.ts | 448 ++- packages/amp/src/arrow-flight/errors.ts | 65 +- packages/amp/src/arrow-flight/node.ts | 2 +- packages/amp/src/arrow-flight/service.ts | 72 +- packages/amp/src/arrow-flight/transport.ts | 40 +- packages/amp/src/arrow-flight/types.ts | 12 +- packages/amp/src/auth/error.ts | 149 +- packages/amp/src/auth/service.ts | 647 +++-- packages/amp/src/cdc-stream/batch-store.ts | 12 +- packages/amp/src/cdc-stream/errors.ts | 19 +- .../amp/src/cdc-stream/memory-batch-store.ts | 7 +- packages/amp/src/cdc-stream/stream.ts | 35 +- packages/amp/src/core/domain.ts | 566 ++-- .../src/internal/arrow-flight-ipc/Decoder.ts | 22 +- .../src/internal/arrow-flight-ipc/Errors.ts | 30 +- .../amp/src/internal/arrow-flight-ipc/Json.ts | 16 +- .../src/internal/arrow-flight-ipc/Readers.ts | 46 +- .../internal/arrow-flight-ipc/RecordBatch.ts | 32 +- .../src/internal/arrow-flight-ipc/Schema.ts | 172 +- packages/amp/src/internal/pkce.ts | 13 +- packages/amp/src/protocol-stream/errors.ts | 41 +- packages/amp/src/protocol-stream/messages.ts | 5 +- packages/amp/src/protocol-stream/service.ts | 87 +- .../amp/src/protocol-stream/validation.ts | 234 +- packages/amp/src/registry/api.ts | 327 +-- packages/amp/src/registry/domain.ts | 44 +- packages/amp/src/registry/error.ts | 140 +- .../src/transactional-stream/algorithms.ts | 4 +- .../amp/src/transactional-stream/errors.ts | 19 +- .../src/transactional-stream/memory-store.ts | 16 +- .../src/transactional-stream/state-actor.ts | 230 +- .../src/transactional-stream/state-store.ts | 7 +- .../amp/src/transactional-stream/stream.ts | 66 +- .../amp/src/transactional-stream/types.ts | 8 +- packages/amp/test/admin/service.test.ts | 339 +++ .../test/arrow-flight-ipc/roundtrip.test.ts | 346 +-- .../amp/test/arrow-flight/service.test.ts | 41 +- .../test/arrow-test-harness/BufferUtils.ts | 25 +- .../arrow-test-harness/FlightDataGenerator.ts | 25 +- .../test/arrow-test-harness/RandomUtils.ts | 30 +- .../test/arrow-test-harness/SchemaBuilder.ts | 9 +- .../arrow-test-harness/ValueComparison.ts | 42 +- .../arrow-test-harness/generators/decimal.ts | 2 +- .../arrow-test-harness/generators/nested.ts | 24 +- .../generators/primitives.ts | 28 +- .../arrow-test-harness/generators/strings.ts | 10 +- .../arrow-test-harness/generators/temporal.ts | 10 +- .../cdc-stream/memory-batch-store.test.ts | 40 +- packages/amp/test/cdc-stream/stream.test.ts | 288 +- .../amp/test/internal/explain/parser.test.ts | 11 +- .../amp/test/protocol-stream/reorg.test.ts | 143 +- .../test/protocol-stream/validation.test.ts | 150 +- .../transactional-stream/algorithms.test.ts | 27 +- .../transactional-stream/memory-store.test.ts | 148 +- .../transactional-stream/state-actor.test.ts | 300 +- packages/cli/package.json | 4 +- packages/cli/src/cli.ts | 19 +- packages/cli/src/commands/auth/login.ts | 48 +- packages/cli/src/commands/auth/logout.ts | 4 +- packages/cli/src/commands/auth/token.ts | 126 +- packages/cli/src/commands/query.ts | 86 +- packages/tools/oxc/oxlintrc.json | 52 +- .../rules/no-import-from-barrel-package.ts | 7 +- packages/tools/oxc/src/oxlint/types.ts | 5 +- .../no-import-from-barrel-package.test.ts | 54 +- .../test/no-opaque-instance-fields.test.ts | 113 +- packages/tools/oxc/test/utils.ts | 7 +- pnpm-lock.yaml | 2560 ++++++++++------- pnpm-workspace.yaml | 29 +- scripts/circular.mjs | 13 +- specs/cdc-stream.md | 81 +- specs/transactional-stream.md | 41 +- tsconfig.base.json | 16 +- tsconfig.json | 17 +- tsconfig.packages.json | 6 +- vitest.config.ts | 5 +- vitest.shared.ts | 3 - 90 files changed, 5291 insertions(+), 5042 deletions(-) create mode 100644 .oxfmtrc.json delete mode 100644 dprint.json create mode 100644 packages/amp/test/admin/service.test.ts diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..9972ebe --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,30 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "printWidth": 120, + "tabWidth": 2, + "useTabs": false, + "endOfLine": "lf", + "semi": false, + "singleQuote": false, + "jsxSingleQuote": false, + "quoteProps": "preserve", + "trailingComma": "none", + "arrowParens": "always", + "experimentalOperatorPosition": "start", + "proseWrap": "preserve", + "sortPackageJson": false, + "ignorePatterns": [ + "**/dist", + "**/build", + "**/docs", + "**/coverage", + "contracts/**", + "packages/amp/src/protobuf/**", + "*.yml", + "*.yaml", + "*.toml", + "*.css", + "*.html", + "*.jsonc" + ] +} diff --git a/.oxlintrc.json b/.oxlintrc.json index 4b2172f..941fc52 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -12,14 +12,10 @@ // See related issue: https://github.com/oxc-project/oxc/issues/13204 "packages/amp/src/protobuf/**/*" ], - "jsPlugins": [ - "@amp/oxc/oxlint" - ], + "jsPlugins": ["@amp/oxc/oxlint"], "overrides": [ { - "files": [ - "**/{test,dtslint,examples,benchmark,bundle,scripts,scratchpad}/**" - ], + "files": ["**/{test,dtslint,examples,benchmark,bundle,scripts,scratchpad}/**"], "rules": { "eslint/no-console": "off", "effect/no-import-from-barrel-package": "off" diff --git a/.rulesync/commands/review-pr.md b/.rulesync/commands/review-pr.md index a4950cf..ae70d1e 100644 --- a/.rulesync/commands/review-pr.md +++ b/.rulesync/commands/review-pr.md @@ -1,5 +1,5 @@ --- -description: 'Review a pull request' +description: "Review a pull request" targets: ["*"] --- diff --git a/.rulesync/rules/effect.md b/.rulesync/rules/effect.md index 6fe4996..45025e7 100644 --- a/.rulesync/rules/effect.md +++ b/.rulesync/rules/effect.md @@ -28,15 +28,18 @@ import { Effect, Schema, Stream } from "effect" Define services with `Context.Tag`: ```typescript -class MyService extends Context.Tag("Amp/MyService") Effect.Effect -}>() {} +class MyService extends Context.Tag("Amp/MyService")< + MyService, + { + readonly doSomething: (input: string) => Effect.Effect + } +>() {} ``` Access services in generators with `yield*`: ```typescript -Effect.gen(function*() { +Effect.gen(function* () { const svc = yield* MyService return yield* svc.doSomething("input") }) @@ -63,13 +66,15 @@ Multiple `Effect.provide` calls each wrap the effect in another layer of indirec ```typescript // Bad — repeated provide calls -Effect.runPromise(program.pipe( - Effect.provide(TransactionalStream.layer), - Effect.provide(InMemoryStateStore.layer), - Effect.provide(ProtocolStream.layer), - Effect.provide(ArrowFlight.layer), - Effect.provide(Transport.layer) -)) +Effect.runPromise( + program.pipe( + Effect.provide(TransactionalStream.layer), + Effect.provide(InMemoryStateStore.layer), + Effect.provide(ProtocolStream.layer), + Effect.provide(ArrowFlight.layer), + Effect.provide(Transport.layer) + ) +) // Good — compose first, provide once const AppLayer = TransactionalStream.layer.pipe( @@ -89,22 +94,19 @@ Wrapping `Effect.gen` in an arrow function allocates a new generator on every ca ```typescript // Bad — allocates a generator per invocation const query = (sql: string) => - Effect.gen(function*() { + Effect.gen(function* () { const svc = yield* ArrowFlight return yield* svc.query(sql) }) // Good -const query = Effect.fn("query")(function*(sql: string) { +const query = Effect.fn("query")(function* (sql: string) { const svc = yield* ArrowFlight return yield* svc.query(sql) }) // With explicit return type annotation -const query = Effect.fn("query")(function*(sql: string): Effect.fn.Return< - QueryResult, - ArrowFlightError -> { +const query = Effect.fn("query")(function* (sql: string): Effect.fn.Return { const svc = yield* ArrowFlight return yield* svc.query(sql) }) @@ -115,9 +117,7 @@ const query = Effect.fn("query")(function*(sql: string): Effect.fn.Return< Use `Schema.brand` for domain primitives: ```typescript -export const Network = Schema.Lowercase.pipe( - Schema.brand("Amp/Models/Network") -).annotations({ identifier: "Network" }) +export const Network = Schema.Lowercase.pipe(Schema.brand("Amp/Models/Network")).annotations({ identifier: "Network" }) export type Network = typeof Network.Type ``` @@ -126,9 +126,7 @@ export type Network = typeof Network.Type Use `Schema.TaggedError` for typed, serializable errors: ```typescript -export class MyError extends Schema.TaggedError( - "Amp/MyError" -)("MyError", { +export class MyError extends Schema.TaggedError("Amp/MyError")("MyError", { cause: Schema.Defect }) {} ``` diff --git a/.rulesync/rules/overview.md b/.rulesync/rules/overview.md index 3adf144..2684390 100644 --- a/.rulesync/rules/overview.md +++ b/.rulesync/rules/overview.md @@ -40,8 +40,8 @@ Each public module has a **root-level barrel `.ts` file** in `src/` that re-expo ```bash pnpm check # Type check root project pnpm check:recursive # Type check all packages -pnpm lint # Check with oxlint + dprint -pnpm lint:fix # Auto-fix with oxlint + dprint +pnpm lint # Check with oxlint + oxfmt +pnpm lint:fix # Auto-fix with oxlint + oxfmt pnpm test # Run all tests (vitest) pnpm vitest run # Single test file pnpm build # Full build (tsc + babel) @@ -49,7 +49,7 @@ pnpm build # Full build (tsc + babel) ## Code Style -- **Formatter**: dprint — no semicolons (ASI), double quotes, no trailing commas, 120 char line width +- **Formatter**: oxfmt — no semicolons (ASI), double quotes, no trailing commas, 120 char line width - **Linter**: oxlint with custom `@amp/oxc` plugin - **Array syntax**: `Array` and `ReadonlyArray`, never `T[]` (enforced by `typescript/array-type`) - **Effect imports**: Always namespace imports from subpaths — `import * as Effect from "effect/Effect"`, never `from "effect"` diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 1a118e9..99e2f7d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,3 @@ { - "recommendations": [ - "oxc.oxc-vscode", - "dprint.dprint" - ] + "recommendations": ["oxc.oxc-vscode"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index fc41fe8..b77c3bf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,9 @@ { - "dprint.path": "node_modules/.bin/dprint", "typescript.tsdk": "node_modules/typescript/lib", "typescript.preferences.importModuleSpecifier": "relative", "typescript.enablePromptUseWorkspaceTsdk": true, "editor.formatOnSave": true, - "editor.defaultFormatter": "dprint.dprint", + "editor.defaultFormatter": "oxc.oxc-vscode", "editor.formatOnSaveMode": "file", "editor.codeActionsOnSave": { "source.fixAll.oxc": "explicit" diff --git a/dprint.json b/dprint.json deleted file mode 100644 index 2d6d495..0000000 --- a/dprint.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$schema": "https://dprint.dev/schemas/v0.json", - "includes": ["**/*.{ts,tsx,js,jsx,json,md}"], - "indentWidth": 2, - "lineWidth": 120, - "newLineKind": "lf", - "typescript": { - "semiColons": "asi", - "quoteStyle": "alwaysDouble", - "trailingCommas": "never", - "operatorPosition": "maintain", - "arrowFunction.useParentheses": "force" - }, - "excludes": [ - "**/dist", - "**/build", - "**/docs", - "**/coverage", - "contracts/**", - "!scratchpad/**/*", - // TODO: move to nested configuration if possible - "packages/amp/src/Protobuf/**" - ], - "plugins": [ - "https://plugins.dprint.dev/typescript-0.93.4.wasm", - "https://plugins.dprint.dev/markdown-0.20.0.wasm", - "https://plugins.dprint.dev/json-0.21.1.wasm" - ] -} diff --git a/package.json b/package.json index f815026..dca80ed 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "type": "module", - "packageManager": "pnpm@11.8.0+sha512.c1f5e7c4cb241c8f174b743851d82f42b802324afc8b0f116b96adb15aa06664948dde36960a3ba1079ba5b4b29dd0140135b94b5b5f5263592249d68e555f26", + "packageManager": "pnpm@12.6.0+sha512.3ef68f951cb111ac204b4a5a16f0b2ddf0da56a96e0413e81d855d9f0b55ef926714709028e1cd00c405c2c5fb7b9e8ec4dc46777c805d0373c2f2ff00fd20ec", "scripts": { "prepare": "effect-language-service patch", "build": "tspc -b tsconfig.packages.json && pnpm --recursive --parallel --filter \"./packages/**/*\" run build", @@ -11,31 +11,31 @@ "check:recursive": "pnpm --recursive --filter \"./packages/**/*\" exec tspc -b tsconfig.json", "circular": "node scripts/circular.mjs", "clean": "node scripts/clean.mjs", - "lint": "oxlint && dprint check", - "lint:fix": "oxlint --fix && dprint fmt", + "lint": "oxlint && oxfmt --check", + "lint:fix": "oxlint --fix && oxfmt", "test": "vitest" }, "devDependencies": { "@amp/oxc": "workspace:^", - "@babel/cli": "^8.0.1", - "@babel/core": "^8.0.1", + "@babel/cli": "^8.0.6", + "@babel/core": "^8.0.6", "@babel/plugin-transform-export-namespace-from": "^8.0.1", "@babel/plugin-transform-modules-commonjs": "^8.0.1", "@effect/language-service": "catalog:", "@effect/vitest": "catalog:", "@types/node": "catalog:", - "@typescript/native-preview": "7.0.0-dev.20260130.1", + "@typescript/native-preview": "7.0.0-dev.20260707.2", "@vitest/coverage-v8": "catalog:", "@vitest/ui": "catalog:", "babel-plugin-annotate-pure-calls": "^0.5.0", - "dprint": "^0.54.0", "glob": "^13.0.6", - "globals": "^17.7.0", + "globals": "^17.12.0", "madge": "^8.0.0", - "oxlint": "^1.71.0", - "rulesync": "^8.31.0", + "oxfmt": "^0.70.0", + "oxlint": "^1.85.0", + "rulesync": "^19.0.0", "ts-patch": "^4.0.1", - "typescript": "^6.0.3", + "typescript": "^7.0.2", "vite-tsconfig-paths": "^6.1.1", "vitest": "catalog:", "vitest-mock-express": "^2.2.0" diff --git a/packages/amp/package.json b/packages/amp/package.json index df3bdcf..0c7bf81 100644 --- a/packages/amp/package.json +++ b/packages/amp/package.json @@ -1,6 +1,6 @@ { "name": "@edgeandnode/amp", - "version": "0.0.51", + "version": "0.0.52", "type": "module", "license": "BUSL-1.1", "description": "Build and manage blockchain datasets.", @@ -42,22 +42,22 @@ "coverage": "vitest --coverage" }, "peerDependencies": { - "@bufbuild/protobuf": "^2.12.1", - "@connectrpc/connect": "^2.1.2", - "@connectrpc/connect-node": "^2.1.2", + "@bufbuild/protobuf": "^2.15.0", + "@connectrpc/connect": "^2.2.0", + "@connectrpc/connect-node": "^2.2.0", "effect": "catalog:" }, "devDependencies": { - "@bufbuild/buf": "^1.71.0", - "@bufbuild/protobuf": "^2.12.1", - "@bufbuild/protoc-gen-es": "^2.12.1", - "@connectrpc/connect": "^2.1.2", - "@connectrpc/connect-node": "^2.1.2", + "@bufbuild/buf": "^1.73.0", + "@bufbuild/protobuf": "^2.15.0", + "@bufbuild/protoc-gen-es": "^2.15.0", + "@connectrpc/connect": "^2.2.0", + "@connectrpc/connect-node": "^2.2.0", "effect": "catalog:" }, "dependencies": { "jiti": "^2.7.0", - "jose": "^6.2.3", + "jose": "^6.2.12", "viem": "catalog:" } } diff --git a/packages/amp/src/admin/api.ts b/packages/amp/src/admin/api.ts index fa0c5ce..a64fe07 100644 --- a/packages/amp/src/admin/api.ts +++ b/packages/amp/src/admin/api.ts @@ -2,7 +2,7 @@ * This module contains the HttpApi definitions for the Amp Admin API. * * The Admin API provides operations for managing: - * - Datasets (registration, versioning, deployment) + * - Datasets (registration, versioning, manifests) * - Jobs (listing, stopping, deletion) * - Workers (listing) * - Providers (listing) @@ -62,16 +62,17 @@ const JobsQueryParams = Schema.Struct({ // GET /datasets - List all datasets const getDatasets = HttpApiEndpoint.get("getDatasets", "/datasets", { error: [ - Error.DatasetStoreError, - Error.MetadataDbError, Error.ListAllDatasetsError, + Error.InvalidStoredDatasetNamespaceError, + Error.InvalidStoredDatasetNameError, + Error.InvalidStoredDatasetVersionError, HttpApiError.Forbidden, HttpApiError.Unauthorized ], success: Domain.GetDatasetsResponse }) -export type GetDatasetsError = typeof getDatasets["~Error"]["Type"] +export type GetDatasetsError = (typeof getDatasets)["~Error"]["Type"] // POST /datasets - Register a dataset const registerDataset = HttpApiEndpoint.post("registerDataset", "/datasets", { @@ -79,94 +80,66 @@ const registerDataset = HttpApiEndpoint.post("registerDataset", "/datasets", { error: [ Error.InvalidPayloadFormatError, Error.InvalidManifestError, + Error.InvalidSortedByConfigError, + Error.ManifestSerializationError, + Error.ManifestValidationError, + Error.SortedTableStatementNotFoundError, + Error.SortedManifestTableNotFoundError, + Error.ManifestRegistrationError, Error.ManifestLinkingError, Error.ManifestNotFoundError, - Error.ManifestRegistrationError, - Error.ManifestValidationError, - Error.StoreError, - Error.UnsupportedDatasetKindError, - Error.VersionTaggingError + Error.NamespaceNotFoundError, + Error.VersionTaggingError, + Error.GetDatasetError, + Error.PhaserProviderNotFoundError, + Error.PhaserProviderParseFailedError, + Error.PhaserProviderNetworkMismatchError, + Error.PhaserConnectionFailedError, + Error.PhaserDiscoveryFailedError, + Error.PhaserInvalidDiscoveryError, + Error.PhaserNoTablesDiscoveredError ], - success: Schema.Void + success: Domain.RegisterDatasetResponse.pipe(HttpApiSchema.status(201)) }) -export type RegisterDatasetError = typeof registerDataset["~Error"]["Type"] +export type RegisterDatasetError = (typeof registerDataset)["~Error"]["Type"] // GET /datasets/{namespace}/{name}/versions - List versions -const getDatasetVersions = HttpApiEndpoint.get( - "getDatasetVersions", - "/datasets/:namespace/:name/versions", - { - params: { - namespace: DatasetNamespaceParam, - name: DatasetNameParam - }, - error: [ - Error.DatasetStoreError, - Error.InvalidRequestError, - Error.MetadataDbError, - Error.ListVersionTagsError - ], - success: Domain.GetDatasetVersionsResponse - } -) +const getDatasetVersions = HttpApiEndpoint.get("getDatasetVersions", "/datasets/:namespace/:name/versions", { + params: { + namespace: DatasetNamespaceParam, + name: DatasetNameParam + }, + error: [ + Error.InvalidPathError, + Error.NamespaceNotFoundError, + Error.ListVersionTagsError, + Error.ResolveRevisionError, + Error.InvalidStoredDatasetVersionError + ], + success: Domain.GetDatasetVersionsResponse +}) -export type GetDatasetVersionsError = typeof getDatasetVersions["~Error"]["Type"] +export type GetDatasetVersionsError = (typeof getDatasetVersions)["~Error"]["Type"] // GET /datasets/{namespace}/{name}/versions/{revision} - Get dataset version -const getDatasetVersion = HttpApiEndpoint.get( - "getDatasetVersion", - "/datasets/:namespace/:name/versions/:revision", - { - params: { - namespace: DatasetNamespaceParam, - name: DatasetNameParam, - revision: DatasetRevisionParam - }, - error: [ - Error.DatasetNotFoundError, - Error.DatasetStoreError, - Error.InvalidPathError, - Error.MetadataDbError, - Error.ResolveRevisionError, - Error.GetManifestPathError, - Error.ReadManifestError, - Error.ParseManifestError - ], - success: Domain.GetDatasetVersionResponse - } -) - -export type GetDatasetVersionError = typeof getDatasetVersion["~Error"]["Type"] - -// POST /datasets/{namespace}/{name}/versions/{revision}/deploy - Deploy dataset -const deployDataset = HttpApiEndpoint.post( - "deployDataset", - "/datasets/:namespace/:name/versions/:revision/deploy", - { - params: { - namespace: DatasetNamespaceParam, - name: DatasetNameParam, - revision: DatasetRevisionParam - }, - payload: Domain.DeployDatasetPayload, - error: [ - Error.DatasetNotFoundError, - Error.DatasetStoreError, - Error.InvalidPathError, - Error.InvalidBodyError, - Error.MetadataDbError, - Error.SchedulerError, - Error.ResolveRevisionError, - Error.GetDatasetError, - Error.ListVersionTagsError, - Error.WorkerNotAvailableError - ], - success: Domain.DeployDatasetResponse.pipe(HttpApiSchema.status(202)) - } -) +const getDatasetVersion = HttpApiEndpoint.get("getDatasetVersion", "/datasets/:namespace/:name/versions/:revision", { + params: { + namespace: DatasetNamespaceParam, + name: DatasetNameParam, + revision: DatasetRevisionParam + }, + error: [ + Error.InvalidPathError, + Error.DatasetNotFoundError, + Error.ResolveRevisionError, + Error.GetDatasetError, + Error.ListDatasetTagsError + ], + success: Domain.GetDatasetVersionResponse +}) -export type DeployDatasetError = typeof deployDataset["~Error"]["Type"] +export type GetDatasetVersionError = (typeof getDatasetVersion)["~Error"]["Type"] // GET /datasets/{namespace}/{name}/versions/{revision}/manifest - Get manifest const getDatasetManifest = HttpApiEndpoint.get( @@ -179,10 +152,10 @@ const getDatasetManifest = HttpApiEndpoint.get( revision: DatasetRevisionParam }, error: [ - Error.DatasetNotFoundError, - Error.DatasetStoreError, Error.InvalidPathError, - Error.MetadataDbError, + Error.DatasetNotFoundError, + Error.ManifestNotFoundError, + Error.ResolveRevisionError, Error.GetManifestPathError, Error.ReadManifestError, Error.ParseManifestError @@ -191,31 +164,7 @@ const getDatasetManifest = HttpApiEndpoint.get( } ) -export type GetDatasetManifestError = typeof getDatasetManifest["~Error"]["Type"] - -// GET /datasets/{namespace}/{name}/versions/{revision}/sync-progress - Get sync progress -const getDatasetSyncProgress = HttpApiEndpoint.get( - "getDatasetSyncProgress", - "/datasets/:namespace/:name/versions/:revision/sync-progress", - { - params: { - namespace: DatasetNamespaceParam, - name: DatasetNameParam, - revision: DatasetRevisionParam - }, - error: [ - Error.DatasetNotFoundError, - Error.GetDatasetError, - Error.GetSyncProgressError, - Error.InvalidPathParamsError, - Error.ResolveRevisionError, - Error.PhysicalTableError - ], - success: Domain.GetDatasetSyncProgressResponse - } -) - -export type GetDatasetSyncProgressError = typeof getDatasetSyncProgress["~Error"]["Type"] +export type GetDatasetManifestError = (typeof getDatasetManifest)["~Error"]["Type"] // ============================================================================= // Job Endpoints @@ -228,12 +177,14 @@ const getJobs = HttpApiEndpoint.get("getJobs", "/jobs", { Error.InvalidQueryParametersError, Error.LimitTooLargeError, Error.LimitInvalidError, - Error.ListJobsError + Error.ListJobsError, + Error.ListJobDescriptorsError, + Error.DeserializeJobDescriptorError ], success: Domain.GetJobsResponse }) -export type GetJobsError = typeof getJobs["~Error"]["Type"] +export type GetJobsError = (typeof getJobs)["~Error"]["Type"] // GET /jobs/{jobId} - Get job by ID const getJobById = HttpApiEndpoint.get("getJobById", "/jobs/:id", { @@ -243,44 +194,36 @@ const getJobById = HttpApiEndpoint.get("getJobById", "/jobs/:id", { error: [ Error.InvalidJobIdError, Error.JobNotFoundError, - Error.GetJobError + Error.GetJobError, + Error.GetDescriptorError, + Error.DeserializeJobDescriptorError ], success: Models.JobInfo }) -export type GetJobByIdError = typeof getJobById["~Error"]["Type"] +export type GetJobByIdError = (typeof getJobById)["~Error"]["Type"] // PUT /jobs/{jobId}/stop - Stop job const stopJob = HttpApiEndpoint.put("stopJob", "/jobs/:id/stop", { params: { id: JobIdParam }, - error: [ - Error.InvalidJobIdError, - Error.JobNotFoundError, - Error.StopJobError, - Error.UnexpectedStateConflictError - ], + error: [Error.InvalidJobIdError, Error.JobNotFoundError, Error.StopJobError, Error.UnexpectedStateConflictError], success: Schema.Void.pipe(HttpApiSchema.status(200)) }) -export type StopJobError = typeof stopJob["~Error"]["Type"] +export type StopJobError = (typeof stopJob)["~Error"]["Type"] // DELETE /jobs/{jobId} - Delete job const deleteJob = HttpApiEndpoint.delete("deleteJob", "/jobs/:id", { params: { id: JobIdParam }, - error: [ - Error.InvalidJobIdError, - Error.JobConflictError, - Error.GetJobError, - Error.DeleteJobError - ], + error: [Error.InvalidJobIdError, Error.JobConflictError, Error.GetJobError, Error.DeleteJobError], success: Schema.Void.pipe(HttpApiSchema.status(204)) }) -export type DeleteJobError = typeof deleteJob["~Error"]["Type"] +export type DeleteJobError = (typeof deleteJob)["~Error"]["Type"] // ============================================================================= // Worker Endpoints @@ -292,7 +235,7 @@ const getWorkers = HttpApiEndpoint.get("getWorkers", "/workers", { success: Domain.GetWorkersResponse }) -export type GetWorkersError = typeof getWorkers["~Error"]["Type"] +export type GetWorkersError = (typeof getWorkers)["~Error"]["Type"] // ============================================================================= // Schema Endpoints @@ -302,33 +245,38 @@ export type GetWorkersError = typeof getWorkers["~Error"]["Type"] const getOutputSchema = HttpApiEndpoint.post("getOutputSchema", "/schema", { payload: Domain.GetOutputSchemaPayload, error: [ - Error.CatalogQualifiedTableError, - Error.CatalogQualifiedFunctionError, - Error.DatasetNotFoundError, - Error.DependencyAliasNotFoundError, - Error.DependencyNotFoundError, - Error.DependencyResolutionError, - Error.EmptyTablesAndFunctionsError, - Error.EthCallNotAvailableError, - Error.EthCallUdfCreationError, - Error.FunctionNotFoundInDatasetError, - Error.FunctionReferenceResolutionError, - Error.GetDatasetError, Error.InvalidPayloadFormatError, - Error.InvalidTableNameError, + Error.EmptyTablesAndFunctionsError, Error.InvalidTableSqlError, - Error.InvalidDependencyAliasForTableRefError, - Error.InvalidDependencyAliasForFunctionRefError, Error.NonIncrementalQueryError, + Error.StaticSourceNotMaterializableError, + Error.SelfReferencingTableError, + Error.SelfRefTableNotFoundError, + Error.CyclicDependencyError, + Error.SortedTableStatementNotFoundError, + Error.CatalogQualifiedTableError, + Error.CatalogQualifiedTableInNetworkResolutionError, + Error.InvalidTableNameError, + Error.TableReferenceResolutionError, + Error.DependencyNotFoundError, + Error.DependencyManifestLinkCheckError, + Error.DependencyVersionResolutionError, + Error.NoTableReferencesError, + Error.SessionConfigError, + Error.InvalidPlanError, Error.SchemaInferenceError, + Error.MissingBlockNumError, + Error.MissingTsError, + Error.DependencyAliasNotFoundError, + Error.GetDatasetError, Error.TableNotFoundInDatasetError, - Error.TableReferenceResolutionError, - Error.UnqualifiedTableError + Error.SelfRefNetworksNotResolvedError, + Error.TableReferencesNotResolvedError ], success: Domain.GetOutputSchemaResponse }) -export type GetOutputSchemaError = typeof getOutputSchema["~Error"]["Type"] +export type GetOutputSchemaError = (typeof getOutputSchema)["~Error"]["Type"] // ============================================================================= // Manifest Endpoints @@ -340,15 +288,27 @@ const registerManifest = HttpApiEndpoint.post("registerManifest", "/manifests", error: [ Error.InvalidPayloadFormatError, Error.InvalidManifestError, + Error.InvalidSortedByConfigError, + Error.ManifestSerializationError, Error.ManifestValidationError, + Error.SortedTableStatementNotFoundError, + Error.SortedManifestTableNotFoundError, Error.ManifestStorageError, + Error.ManifestTransactionBeginError, Error.ManifestRegistrationError, - Error.UnsupportedDatasetKindError + Error.ManifestTransactionCommitError, + Error.PhaserProviderNotFoundError, + Error.PhaserProviderParseFailedError, + Error.PhaserProviderNetworkMismatchError, + Error.PhaserConnectionFailedError, + Error.PhaserDiscoveryFailedError, + Error.PhaserInvalidDiscoveryError, + Error.PhaserNoTablesDiscoveredError ], success: Domain.RegisterManifestResponse.pipe(HttpApiSchema.status(201)) }) -export type RegisterManifestError = typeof registerManifest["~Error"]["Type"] +export type RegisterManifestError = (typeof registerManifest)["~Error"]["Type"] // ============================================================================= // Provider Endpoints @@ -359,7 +319,7 @@ const getProviders = HttpApiEndpoint.get("getProviders", "/providers", { success: Domain.GetProvidersResponse }) -export type GetProvidersError = typeof getProviders["~Error"]["Type"] +export type GetProvidersError = (typeof getProviders)["~Error"]["Type"] // ============================================================================= // Admin API Groups @@ -373,48 +333,32 @@ export class DatasetGroup extends HttpApiGroup.make("dataset") .add(getDatasets) .add(getDatasetVersions) .add(getDatasetVersion) - .add(deployDataset) - .add(getDatasetManifest) - .add(getDatasetSyncProgress) -{} + .add(getDatasetManifest) {} /** * The api group for the job endpoints. */ -export class JobGroup extends HttpApiGroup.make("job") - .add(getJobs) - .add(getJobById) - .add(stopJob) - .add(deleteJob) -{} +export class JobGroup extends HttpApiGroup.make("job").add(getJobs).add(getJobById).add(stopJob).add(deleteJob) {} /** * The api group for the worker endpoints. */ -export class WorkerGroup extends HttpApiGroup.make("worker") - .add(getWorkers) -{} +export class WorkerGroup extends HttpApiGroup.make("worker").add(getWorkers) {} /** * The api group for the schema endpoints. */ -export class SchemaGroup extends HttpApiGroup.make("schema") - .add(getOutputSchema) -{} +export class SchemaGroup extends HttpApiGroup.make("schema").add(getOutputSchema) {} /** * The api group for the manifest endpoints. */ -export class ManifestGroup extends HttpApiGroup.make("manifest") - .add(registerManifest) -{} +export class ManifestGroup extends HttpApiGroup.make("manifest").add(registerManifest) {} /** * The api group for the provider endpoints. */ -export class ProviderGroup extends HttpApiGroup.make("provider") - .add(getProviders) -{} +export class ProviderGroup extends HttpApiGroup.make("provider").add(getProviders) {} // ============================================================================= // Admin API @@ -429,5 +373,4 @@ export class Api extends HttpApi.make("AmpAdminApi") .add(WorkerGroup) .add(SchemaGroup) .add(ManifestGroup) - .add(ProviderGroup) -{} + .add(ProviderGroup) {} diff --git a/packages/amp/src/admin/domain.ts b/packages/amp/src/admin/domain.ts index f1704f5..3163d88 100644 --- a/packages/amp/src/admin/domain.ts +++ b/packages/amp/src/admin/domain.ts @@ -27,16 +27,43 @@ export type GetDatasetsResponse = typeof GetDatasetsResponse.Type /** * Request payload for registering a dataset. + * + * The manifest can either be the full manifest content, or the hash of a + * manifest which was previously registered. */ export const RegisterDatasetPayload = Schema.Struct({ namespace: Schema.String, name: Schema.String, version: Schema.optional(Schema.String), - manifest: Models.DatasetManifest + manifest: Schema.Union([Models.DatasetHash, Models.DatasetManifest]) }).annotate({ identifier: "RegisterDatasetPayload" }) export type RegisterDatasetPayload = typeof RegisterDatasetPayload.Type +/** + * Response schema for registering a dataset. + */ +export const RegisterDatasetResponse = Schema.Struct({ + namespace: Models.DatasetNamespace, + name: Models.DatasetName, + version: Schema.optional(Models.DatasetVersion), + manifestHash: Models.DatasetHash, + kind: Models.DatasetKind, + startBlock: Models.NonNegativeInt, + finalizedBlocksOnly: Schema.Boolean, + tables: Schema.Array(Schema.String) +}) + .pipe( + Schema.encodeKeys({ + manifestHash: "manifest_hash", + startBlock: "start_block", + finalizedBlocksOnly: "finalized_blocks_only" + }) + ) + .annotate({ identifier: "RegisterDatasetResponse" }) + +export type RegisterDatasetResponse = typeof RegisterDatasetResponse.Type + /** * Response schema for getting a dataset version. */ @@ -45,82 +72,76 @@ export const GetDatasetVersionResponse = Schema.Struct({ namespace: Models.DatasetNamespace, name: Models.DatasetName, revision: Models.DatasetRevision, - manifestHash: Models.DatasetHash -}).pipe(Schema.encodeKeys({ manifestHash: "manifest_hash" })).annotate({ identifier: "GetDatasetVersionResponse" }) + manifestHash: Models.DatasetHash, + startBlock: Models.NonNegativeInt, + finalizedBlocksOnly: Schema.Boolean, + tables: Schema.Array(Schema.String), + /** + * Tags pointing at this manifest, ordered versions first, then `"latest"`, + * then `"dev"`. + */ + tags: Schema.Array(Schema.String) +}) + .pipe( + Schema.encodeKeys({ + manifestHash: "manifest_hash", + startBlock: "start_block", + finalizedBlocksOnly: "finalized_blocks_only" + }) + ) + .annotate({ identifier: "GetDatasetVersionResponse" }) export type GetDatasetVersionResponse = typeof GetDatasetVersionResponse.Type /** - * Response schema for listing dataset versions. + * Information about a single version of a dataset. */ -export const GetDatasetVersionsResponse = Schema.Struct({ - versions: Schema.Array(Models.DatasetVersion) -}).annotate({ identifier: "GetDatasetVersionsResponse" }) - -export type GetDatasetVersionsResponse = typeof GetDatasetVersionsResponse.Type - -/** - * Request payload for deploying a dataset. - */ -export const DeployDatasetPayload = Schema.Struct({ - endBlock: Schema.optional(Schema.NullOr(Schema.String)), - parallelism: Schema.optional(Schema.Number), - workerId: Schema.optional(Schema.String) -}).pipe(Schema.encodeKeys({ - endBlock: "end_block", - workerId: "worker_id" -})).annotate({ identifier: "DeployDatasetPayload" }) - -export type DeployDatasetPayload = typeof DeployDatasetPayload.Type - -/** - * Response schema for deploying a dataset. - */ -export const DeployDatasetResponse = Schema.Struct({ - jobId: Models.JobId -}).pipe(Schema.encodeKeys({ jobId: "job_id" })).annotate({ identifier: "DeployDatasetResponse" }) +export const DatasetVersionInfo = Schema.Struct({ + version: Models.DatasetVersion, + manifestHash: Models.DatasetHash, + createdAt: Schema.DateTimeUtc, + updatedAt: Schema.DateTimeUtc +}) + .pipe( + Schema.encodeKeys({ + manifestHash: "manifest_hash", + createdAt: "created_at", + updatedAt: "updated_at" + }) + ) + .annotate({ identifier: "DatasetVersionInfo" }) -export type DeployDatasetResponse = typeof DeployDatasetResponse.Type +export type DatasetVersionInfo = typeof DatasetVersionInfo.Type /** - * Table sync progress information. + * The special `latest` and `dev` tags of a dataset. */ -export const TableSyncProgress = Schema.Struct({ - tableName: Schema.String, - currentBlock: Schema.optional(Schema.Int), - startBlock: Schema.optional(Schema.Int), - jobId: Schema.optional(Models.JobId), - jobStatus: Schema.optional(Models.JobStatus), - filesCount: Schema.Int, - totalSizeBytes: Schema.Int -}).pipe(Schema.encodeKeys({ - tableName: "table_name", - currentBlock: "current_block", - startBlock: "start_block", - jobId: "job_id", - jobStatus: "job_status", - filesCount: "files_count", - totalSizeBytes: "total_size_bytes" -})).annotate({ identifier: "TableSyncProgress" }) +export const DatasetSpecialTags = Schema.Struct({ + /** + * The latest semantic version, if any. + */ + latest: Schema.optional(Models.DatasetVersion), + /** + * The manifest hash the dev tag points to, if any. + */ + dev: Schema.optional(Models.DatasetHash) +}).annotate({ identifier: "DatasetSpecialTags" }) -export type TableSyncProgress = typeof TableSyncProgress.Type +export type DatasetSpecialTags = typeof DatasetSpecialTags.Type /** - * Response schema for getting dataset sync progress. + * Response schema for listing dataset versions. */ -export const GetDatasetSyncProgressResponse = Schema.Struct({ +export const GetDatasetVersionsResponse = Schema.Struct({ namespace: Models.DatasetNamespace, name: Models.DatasetName, - revision: Models.DatasetRevision, - manifestHash: Models.DatasetHash, - tables: Schema.Array(TableSyncProgress) -}).pipe(Schema.encodeKeys({ - namespace: "dataset_namespace", - name: "dataset_name", - manifestHash: "manifest_hash" -})).annotate({ identifier: "GetDatasetSyncProgressResponse" }) + versions: Schema.Array(DatasetVersionInfo), + specialTags: DatasetSpecialTags +}) + .pipe(Schema.encodeKeys({ specialTags: "special_tags" })) + .annotate({ identifier: "GetDatasetVersionsResponse" }) -export type GetDatasetSyncProgressResponse = typeof GetDatasetSyncProgressResponse.Type +export type GetDatasetVersionsResponse = typeof GetDatasetVersionsResponse.Type // ============================================================================= // Job Request/Response Schemas @@ -132,7 +153,9 @@ export type GetDatasetSyncProgressResponse = typeof GetDatasetSyncProgressRespon export const GetJobsResponse = Schema.Struct({ jobs: Schema.Array(Models.JobInfo), nextCursor: Schema.optional(Models.JobId) -}).pipe(Schema.encodeKeys({ nextCursor: "next_cursor" })).annotate({ identifier: "GetJobsResponse" }) +}) + .pipe(Schema.encodeKeys({ nextCursor: "next_cursor" })) + .annotate({ identifier: "GetJobsResponse" }) export type GetJobsResponse = typeof GetJobsResponse.Type @@ -144,15 +167,9 @@ export type GetJobsResponse = typeof GetJobsResponse.Type * Request payload for schema analysis. */ export const GetOutputSchemaPayload = Schema.Struct({ - tables: Schema.Record(Schema.String, Schema.String), - dependencies: Schema.optional(Schema.Record( - Schema.String, - Models.DatasetReferenceFromString - )), - functions: Schema.optional(Schema.Record( - Schema.String, - Models.FunctionDefinition - )) + tables: Schema.optional(Schema.Record(Schema.String, Schema.String)), + dependencies: Schema.optional(Schema.Record(Schema.String, Models.DatasetReferenceFromString)), + functions: Schema.optional(Schema.Record(Schema.String, Models.FunctionDefinition)) }).annotate({ identifier: "GetOutputSchemaPayload" }) export type GetOutputSchemaPayload = typeof GetOutputSchemaPayload.Type @@ -161,10 +178,7 @@ export type GetOutputSchemaPayload = typeof GetOutputSchemaPayload.Type * Response schema for schema analysis. */ export const GetOutputSchemaResponse = Schema.Struct({ - schemas: Schema.Record( - Schema.String, - Models.TableSchemaWithNetworks - ) + schemas: Schema.Record(Schema.String, Models.TableSchema) }).annotate({ identifier: "GetOutputSchemaResponse" }) export type GetOutputSchemaResponse = typeof GetOutputSchemaResponse.Type @@ -179,10 +193,14 @@ export type GetOutputSchemaResponse = typeof GetOutputSchemaResponse.Type export const WorkerInfo = Schema.Struct({ nodeId: Schema.String, heartbeatAt: Schema.String -}).pipe(Schema.encodeKeys({ - nodeId: "node_id", - heartbeatAt: "heartbeat_at" -})).annotate({ identifier: "WorkerInfo" }) +}) + .pipe( + Schema.encodeKeys({ + nodeId: "node_id", + heartbeatAt: "heartbeat_at" + }) + ) + .annotate({ identifier: "WorkerInfo" }) export type WorkerInfo = typeof WorkerInfo.Type @@ -214,12 +232,17 @@ export type RegisterManifestResponse = typeof RegisterManifestResponse.Type /** * Provider information returned by the API. - */ -export const ProviderInfo = Schema.Struct({ - name: Schema.String, - network: Models.Network, - config: Schema.Any -}).annotate({ identifier: "ProviderInfo" }) + * + * Contains the provider name and kind, along with any additional + * provider-specific configuration fields. + */ +export const ProviderInfo = Schema.StructWithRest( + Schema.Struct({ + name: Schema.String, + kind: Schema.String + }), + [Schema.Record(Schema.String, Schema.Unknown)] +).annotate({ identifier: "ProviderInfo" }) export type ProviderInfo = typeof ProviderInfo.Type /** diff --git a/packages/amp/src/admin/error.ts b/packages/amp/src/admin/error.ts index 05f4477..0a697b0 100644 --- a/packages/amp/src/admin/error.ts +++ b/packages/amp/src/admin/error.ts @@ -24,7 +24,11 @@ export const makeError = < const Code extends string, const Tag extends string, const Fields extends Schema.Struct.Fields = {} ->(code: Code, tag: Tag, fields?: Fields): Schema.encodeKeys< +>( + code: Code, + tag: Tag, + fields?: Fields +): Schema.encodeKeys< Schema.Struct< Fields & { readonly _tag: Schema.withDecodingDefaultKey> @@ -65,10 +69,12 @@ export const makeError = < * over time. Use `error_code` for programmatic decisions. */ message: Schema.String - }).pipe(Schema.encodeKeys({ - code: "error_code", - message: "error_message" - })) as any + }).pipe( + Schema.encodeKeys({ + code: "error_code", + message: "error_message" + }) + ) as any // ============================================================================= // Dataset Errors @@ -81,27 +87,12 @@ export const makeError = < * - SQL query contains a catalog-qualified table reference (catalog.schema.table) * - Only dataset-qualified tables are supported (dataset.table) */ -export const CatalogQualifiedTableError = makeError( - "CATALOG_QUALIFIED_TABLE", - "CatalogQualifiedTableError" -).annotate({ httpApiStatus: 400 }) +export const CatalogQualifiedTableError = makeError("CATALOG_QUALIFIED_TABLE", "CatalogQualifiedTableError").annotate({ + httpApiStatus: 400 +}) export type CatalogQualifiedTableError = typeof CatalogQualifiedTableError.Type -/** - * CatalogQualifiedFunction - Function reference includes a catalog qualifier. - * - * Causes: - * - SQL query contains a catalog-qualified function reference (catalog.schema.function) - * - Only dataset-qualified functions are supported (dataset.function) - */ -export const CatalogQualifiedFunctionError = makeError( - "CATALOG_QUALIFIED_FUNCTION", - "CatalogQualifiedFunctionError" -).annotate({ httpApiStatus: 400 }) - -export type CatalogQualifiedFunctionError = typeof CatalogQualifiedFunctionError.Type - /** * DatasetNotFound - The requested dataset does not exist. * @@ -110,29 +101,12 @@ export type CatalogQualifiedFunctionError = typeof CatalogQualifiedFunctionError * - Dataset has been deleted * - Dataset not yet registered */ -export const DatasetNotFoundError = makeError( - "DATASET_NOT_FOUND", - "DatasetNotFoundError" -).annotate({ httpApiStatus: 404 }) +export const DatasetNotFoundError = makeError("DATASET_NOT_FOUND", "DatasetNotFoundError").annotate({ + httpApiStatus: 404 +}) export type DatasetNotFoundError = typeof DatasetNotFoundError.Type -/** - * DatasetStoreError - Failure in dataset storage operations. - * - * Causes: - * - File/object store retrieval failures - * - Manifest parsing errors (TOML/JSON) - * - Unsupported dataset kind - * - Dataset name validation failures - */ -export const DatasetStoreError = makeError( - "DATASET_STORE_ERROR", - "DatasetStoreError" -).annotate({ httpApiStatus: 500 }) - -export type DatasetStoreError = typeof DatasetStoreError.Type - /** * DependencyAliasNotFound - Dependency alias not found in dependencies map. * @@ -154,26 +128,12 @@ export type DependencyAliasNotFoundError = typeof DependencyAliasNotFoundError.T * - Referenced dependency does not exist in dataset store * - Specified version or hash cannot be found */ -export const DependencyNotFoundError = makeError( - "DEPENDENCY_NOT_FOUND", - "DependencyNotFoundError" -).annotate({ httpApiStatus: 404 }) +export const DependencyNotFoundError = makeError("DEPENDENCY_NOT_FOUND", "DependencyNotFoundError").annotate({ + httpApiStatus: 404 +}) export type DependencyNotFoundError = typeof DependencyNotFoundError.Type -/** - * DependencyResolution - Failed to resolve dependency. - * - * Causes: - * - Database query fails during resolution - */ -export const DependencyResolutionError = makeError( - "DEPENDENCY_RESOLUTION", - "DependencyResolutionError" -).annotate({ httpApiStatus: 500 }) - -export type DependencyResolutionError = typeof DependencyResolutionError.Type - /** * EmptyTablesAndFunctions - No tables or functions provided. * @@ -188,97 +148,73 @@ export const EmptyTablesAndFunctionsError = makeError( export type EmptyTablesAndFunctionsError = typeof EmptyTablesAndFunctionsError.Type /** - * EthCallNotAvailable - eth_call function not available for dataset. + * GetDatasetError - Failed to retrieve dataset from store. * * Causes: - * - eth_call function is referenced in SQL but dataset doesn't support it - * - Dataset is not an EVM RPC dataset + * - Dataset manifest is invalid or corrupted + * - Unsupported dataset kind + * - Storage backend errors when reading dataset */ -export const EthCallNotAvailableError = makeError( - "ETH_CALL_NOT_AVAILABLE", - "EthCallNotAvailableError" -).annotate({ httpApiStatus: 404 }) +export const GetDatasetError = makeError("GET_DATASET_ERROR", "GetDatasetError").annotate({ httpApiStatus: 500 }) -export type EthCallNotAvailableError = typeof EthCallNotAvailableError.Type +export type GetDatasetError = typeof GetDatasetError.Type /** - * EthCallUdfCreationError - Failed to create ETH call UDF. - * - * Causes: - * - Invalid provider configuration for dataset - * - Provider connection issues + * GetManifestPathError - Failed to query manifest path from metadata database. */ -export const EthCallUdfCreationError = makeError( - "ETH_CALL_UDF_CREATION_ERROR", - "EthCallUdfCreationError" -).annotate({ httpApiStatus: 500 }) +export const GetManifestPathError = makeError("GET_MANIFEST_PATH_ERROR", "GetManifestPathError").annotate({ + httpApiStatus: 500 +}) -export type EthCallUdfCreationError = typeof EthCallUdfCreationError.Type +export type GetManifestPathError = typeof GetManifestPathError.Type /** - * FunctionNotFoundInDataset - Function not found in referenced dataset. - * - * Causes: - * - SQL query references a function that doesn't exist in the dataset - * - Function name is misspelled + * InvalidStoredDatasetNamespace - A stored dataset namespace is malformed. */ -export const FunctionNotFoundInDatasetError = makeError( - "FUNCTION_NOT_FOUND_IN_DATASET", - "FunctionNotFoundInDatasetError" -).annotate({ httpApiStatus: 404 }) +export const InvalidStoredDatasetNamespaceError = makeError( + "INVALID_STORED_DATASET_NAMESPACE", + "InvalidStoredDatasetNamespaceError" +).annotate({ httpApiStatus: 500 }) -export type FunctionNotFoundInDatasetError = typeof FunctionNotFoundInDatasetError.Type +export type InvalidStoredDatasetNamespaceError = typeof InvalidStoredDatasetNamespaceError.Type /** - * FunctionReferenceResolution - Failed to resolve function references from SQL. - * - * Causes: - * - Unsupported DML statements encountered + * InvalidStoredDatasetName - A stored dataset name is malformed. */ -export const FunctionReferenceResolutionError = makeError( - "FUNCTION_REFERENCE_RESOLUTION", - "FunctionReferenceResolutionError" +export const InvalidStoredDatasetNameError = makeError( + "INVALID_STORED_DATASET_NAME", + "InvalidStoredDatasetNameError" ).annotate({ httpApiStatus: 500 }) -export type FunctionReferenceResolutionError = typeof FunctionReferenceResolutionError.Type +export type InvalidStoredDatasetNameError = typeof InvalidStoredDatasetNameError.Type /** - * GetDatasetError - Failed to retrieve dataset from store. - * - * Causes: - * - Dataset manifest is invalid or corrupted - * - Unsupported dataset kind - * - Storage backend errors when reading dataset + * InvalidStoredDatasetVersion - A stored dataset version is malformed. */ -export const GetDatasetError = makeError( - "GET_DATASET_ERROR", - "GetDatasetError" +export const InvalidStoredDatasetVersionError = makeError( + "INVALID_STORED_DATASET_VERSION", + "InvalidStoredDatasetVersionError" ).annotate({ httpApiStatus: 500 }) -export type GetDatasetError = typeof GetDatasetError.Type +export type InvalidStoredDatasetVersionError = typeof InvalidStoredDatasetVersionError.Type /** - * GetManifestPathError - Failed to query manifest path from metadata database. + * ListDatasetTagsError - Failed to list the tags pointing at a dataset manifest. */ -export const GetManifestPathError = makeError( - "GET_MANIFEST_PATH_ERROR", - "GetManifestPathError" -).annotate({ httpApiStatus: 500 }) +export const ListDatasetTagsError = makeError("LIST_DATASET_TAGS_ERROR", "ListDatasetTagsError").annotate({ + httpApiStatus: 500 +}) -export type GetManifestPathError = typeof GetManifestPathError.Type +export type ListDatasetTagsError = typeof ListDatasetTagsError.Type /** - * GetSyncProgressError - Failed to retrieve the dataset sync progress - * - * Causes: - * - Unable to resolve the dataset synchronization progress server side + * NamespaceNotFound - The namespace does not exist or is unavailable to the caller. */ -export const GetSyncProgressError = makeError( - "GET_SYNC_PROGRESS_ERROR", - "GetSyncProgressError" -).annotate({ httpApiStatus: 500 }) +export const NamespaceNotFoundError = makeError("NAMESPACE_NOT_FOUND", "NamespaceNotFoundError").annotate({ + httpApiStatus: 404 +}) -export type GetSyncProgressError = typeof GetSyncProgressError.Type +export type NamespaceNotFoundError = typeof NamespaceNotFoundError.Type // ============================================================================= // Job Errors @@ -291,10 +227,7 @@ export type GetSyncProgressError = typeof GetSyncProgressError.Type * - Job ID contains invalid characters * - Job ID format does not match expected pattern */ -export const InvalidJobIdError = makeError( - "INVALID_JOB_ID", - "InvalidJobIdError" -).annotate({ httpApiStatus: 400 }) +export const InvalidJobIdError = makeError("INVALID_JOB_ID", "InvalidJobIdError").annotate({ httpApiStatus: 400 }) export type InvalidJobIdError = typeof InvalidJobIdError.Type @@ -305,60 +238,42 @@ export type InvalidJobIdError = typeof InvalidJobIdError.Type * - Job ID does not exist in the system * - Job has been deleted */ -export const JobNotFoundError = makeError( - "JOB_NOT_FOUND", - "JobNotFoundError" -).annotate({ httpApiStatus: 404 }) +export const JobNotFoundError = makeError("JOB_NOT_FOUND", "JobNotFoundError").annotate({ httpApiStatus: 404 }) export type JobNotFoundError = typeof JobNotFoundError.Type /** * JobConflict - Job exists but cannot be deleted (not in terminal state). */ -export const JobConflictError = makeError( - "JOB_CONFLICT", - "JobConflictError" -).annotate({ httpApiStatus: 409 }) +export const JobConflictError = makeError("JOB_CONFLICT", "JobConflictError").annotate({ httpApiStatus: 409 }) export type JobConflictError = typeof JobConflictError.Type /** * GetJobError - Failed to retrieve job from scheduler. */ -export const GetJobError = makeError( - "GET_JOB_ERROR", - "GetJobError" -).annotate({ httpApiStatus: 500 }) +export const GetJobError = makeError("GET_JOB_ERROR", "GetJobError").annotate({ httpApiStatus: 500 }) export type GetJobError = typeof GetJobError.Type /** * DeleteJobError - Failed to delete job from scheduler. */ -export const DeleteJobError = makeError( - "DELETE_JOB_ERROR", - "DeleteJobError" -).annotate({ httpApiStatus: 500 }) +export const DeleteJobError = makeError("DELETE_JOB_ERROR", "DeleteJobError").annotate({ httpApiStatus: 500 }) export type DeleteJobError = typeof DeleteJobError.Type /** * StopJobError - Database error during stop operation. */ -export const StopJobError = makeError( - "STOP_JOB_ERROR", - "StopJobError" -).annotate({ httpApiStatus: 500 }) +export const StopJobError = makeError("STOP_JOB_ERROR", "StopJobError").annotate({ httpApiStatus: 500 }) export type StopJobError = typeof StopJobError.Type /** * ListJobsError - Failed to list jobs from scheduler. */ -export const ListJobsError = makeError( - "LIST_JOBS_ERROR", - "ListJobsError" -).annotate({ httpApiStatus: 500 }) +export const ListJobsError = makeError("LIST_JOBS_ERROR", "ListJobsError").annotate({ httpApiStatus: 500 }) export type ListJobsError = typeof ListJobsError.Type @@ -372,6 +287,34 @@ export const UnexpectedStateConflictError = makeError( export type UnexpectedStateConflictError = typeof UnexpectedStateConflictError.Type +/** + * ListJobDescriptorsError - Failed to list the descriptors of the listed jobs. + */ +export const ListJobDescriptorsError = makeError("LIST_JOB_DESCRIPTORS_ERROR", "ListJobDescriptorsError").annotate({ + httpApiStatus: 500 +}) + +export type ListJobDescriptorsError = typeof ListJobDescriptorsError.Type + +/** + * GetDescriptorError - Failed to retrieve the descriptor of a job. + */ +export const GetDescriptorError = makeError("GET_DESCRIPTOR_ERROR", "GetDescriptorError").annotate({ + httpApiStatus: 500 +}) + +export type GetDescriptorError = typeof GetDescriptorError.Type + +/** + * DeserializeJobDescriptorError - Failed to deserialize a job descriptor. + */ +export const DeserializeJobDescriptorError = makeError( + "DESERIALIZE_JOB_DESCRIPTOR_ERROR", + "DeserializeJobDescriptorError" +).annotate({ httpApiStatus: 500 }) + +export type DeserializeJobDescriptorError = typeof DeserializeJobDescriptorError.Type + // ============================================================================= // Manifest Errors // ============================================================================= @@ -384,40 +327,36 @@ export type UnexpectedStateConflictError = typeof UnexpectedStateConflictError.T * - Circular dependencies between datasets * - Schema validation failures */ -export const InvalidManifestError = makeError( - "INVALID_MANIFEST", - "InvalidManifestError" -).annotate({ httpApiStatus: 400 }) +export const InvalidManifestError = makeError("INVALID_MANIFEST", "InvalidManifestError").annotate({ + httpApiStatus: 400 +}) export type InvalidManifestError = typeof InvalidManifestError.Type /** * ManifestLinkingError - Failed to link manifest to dataset. */ -export const ManifestLinkingError = makeError( - "MANIFEST_LINKING_ERROR", - "ManifestLinkingError" -).annotate({ httpApiStatus: 500 }) +export const ManifestLinkingError = makeError("MANIFEST_LINKING_ERROR", "ManifestLinkingError").annotate({ + httpApiStatus: 500 +}) export type ManifestLinkingError = typeof ManifestLinkingError.Type /** * ManifestNotFound - Manifest with the provided hash not found. */ -export const ManifestNotFoundError = makeError( - "MANIFEST_NOT_FOUND", - "ManifestNotFoundError" -).annotate({ httpApiStatus: 404 }) +export const ManifestNotFoundError = makeError("MANIFEST_NOT_FOUND", "ManifestNotFoundError").annotate({ + httpApiStatus: 404 +}) export type ManifestNotFoundError = typeof ManifestNotFoundError.Type /** * ManifestRegistrationError - Failed to register manifest in the system. */ -export const ManifestRegistrationError = makeError( - "MANIFEST_REGISTRATION_ERROR", - "ManifestRegistrationError" -).annotate({ httpApiStatus: 500 }) +export const ManifestRegistrationError = makeError("MANIFEST_REGISTRATION_ERROR", "ManifestRegistrationError").annotate( + { httpApiStatus: 500 } +) export type ManifestRegistrationError = typeof ManifestRegistrationError.Type @@ -429,208 +368,237 @@ export type ManifestRegistrationError = typeof ManifestRegistrationError.Type * - Invalid table references in SQL * - Type inference errors */ -export const ManifestValidationError = makeError( - "MANIFEST_VALIDATION_ERROR", - "ManifestValidationError" -).annotate({ httpApiStatus: 400 }) +export const ManifestValidationError = makeError("MANIFEST_VALIDATION_ERROR", "ManifestValidationError").annotate({ + httpApiStatus: 400 +}) export type ManifestValidationError = typeof ManifestValidationError.Type /** * ManifestStorageError - Failed to write manifest to object store. */ -export const ManifestStorageError = makeError( - "MANIFEST_STORAGE_ERROR", - "ManifestStorageError" -).annotate({ httpApiStatus: 500 }) +export const ManifestStorageError = makeError("MANIFEST_STORAGE_ERROR", "ManifestStorageError").annotate({ + httpApiStatus: 500 +}) export type ManifestStorageError = typeof ManifestStorageError.Type /** * ParseManifestError - Failed to parse manifest JSON. */ -export const ParseManifestError = makeError( - "PARSE_MANIFEST_ERROR", - "ParseManifestError" -).annotate({ httpApiStatus: 500 }) +export const ParseManifestError = makeError("PARSE_MANIFEST_ERROR", "ParseManifestError").annotate({ + httpApiStatus: 500 +}) export type ParseManifestError = typeof ParseManifestError.Type /** * ReadManifestError - Failed to read manifest from object store. */ -export const ReadManifestError = makeError( - "READ_MANIFEST_ERROR", - "ReadManifestError" -).annotate({ httpApiStatus: 500 }) +export const ReadManifestError = makeError("READ_MANIFEST_ERROR", "ReadManifestError").annotate({ httpApiStatus: 500 }) export type ReadManifestError = typeof ReadManifestError.Type -// ============================================================================= -// Request Validation Errors -// ============================================================================= - /** - * InvalidPath - Invalid path parameters. + * InvalidSortedByConfig - The manifest `sorted_by` configuration is invalid. */ -export const InvalidPathError = makeError( - "INVALID_PATH", - "InvalidPathError" -).annotate({ httpApiStatus: 400 }) +export const InvalidSortedByConfigError = makeError("INVALID_SORTED_BY_CONFIG", "InvalidSortedByConfigError").annotate({ + httpApiStatus: 400 +}) -export type InvalidPathError = typeof InvalidPathError.Type +export type InvalidSortedByConfigError = typeof InvalidSortedByConfigError.Type /** - * InvalidBody - Invalid request body. + * ManifestSerializationError - Failed to serialize the validated manifest. */ -export const InvalidBodyError = makeError( - "INVALID_BODY", - "InvalidBodyError" -).annotate({ httpApiStatus: 400 }) +export const ManifestSerializationError = makeError( + "MANIFEST_SERIALIZATION_ERROR", + "ManifestSerializationError" +).annotate({ httpApiStatus: 500 }) -export type InvalidBodyError = typeof InvalidBodyError.Type +export type ManifestSerializationError = typeof ManifestSerializationError.Type /** - * InvalidPathParams - Invalid request path parameters. + * ManifestTransactionBeginError - Failed to begin the manifest registration transaction. */ -export const InvalidPathParamsError = makeError( - "INVALID_PATH_PARAMS", - "InvalidPathParamsError" -).annotate({ httpApiStatus: 400 }) +export const ManifestTransactionBeginError = makeError( + "MANIFEST_TRANSACTION_BEGIN_ERROR", + "ManifestTransactionBeginError" +).annotate({ httpApiStatus: 500 }) -export type InvalidPathParamsError = typeof InvalidPathParamsError.Type +export type ManifestTransactionBeginError = typeof ManifestTransactionBeginError.Type /** - * InvalidPayloadFormat - Invalid request payload format. + * ManifestTransactionCommitError - Failed to commit the manifest registration transaction. */ -export const InvalidPayloadFormatError = makeError( - "INVALID_PAYLOAD_FORMAT", - "InvalidPayloadFormatError" -).annotate({ httpApiStatus: 400 }) +export const ManifestTransactionCommitError = makeError( + "MANIFEST_TRANSACTION_COMMIT_ERROR", + "ManifestTransactionCommitError" +).annotate({ httpApiStatus: 500 }) -export type InvalidPayloadFormatError = typeof InvalidPayloadFormatError.Type +export type ManifestTransactionCommitError = typeof ManifestTransactionCommitError.Type /** - * InvalidQueryParameters - Invalid query parameters. + * SortedManifestTableNotFound - Internal manifest consistency failure. */ -export const InvalidQueryParametersError = makeError( - "INVALID_QUERY_PARAMETERS", - "InvalidQueryParametersError" -).annotate({ httpApiStatus: 400 }) +export const SortedManifestTableNotFoundError = makeError( + "SORTED_MANIFEST_TABLE_NOT_FOUND", + "SortedManifestTableNotFoundError" +).annotate({ httpApiStatus: 500 }) -export type InvalidQueryParametersError = typeof InvalidQueryParametersError.Type +export type SortedManifestTableNotFoundError = typeof SortedManifestTableNotFoundError.Type /** - * InvalidRequest - The request is malformed or contains invalid parameters. + * SortedTableStatementNotFound - Internal manifest consistency failure. */ -export const InvalidRequestError = makeError( - "INVALID_REQUEST", - "InvalidRequestError" -).annotate({ httpApiStatus: 400 }) +export const SortedTableStatementNotFoundError = makeError( + "SORTED_TABLE_STATEMENT_NOT_FOUND", + "SortedTableStatementNotFoundError" +).annotate({ httpApiStatus: 500 }) -export type InvalidRequestError = typeof InvalidRequestError.Type +export type SortedTableStatementNotFoundError = typeof SortedTableStatementNotFoundError.Type /** - * InvalidSelector - The provided dataset selector is malformed or invalid. + * PhaserProviderNotFound - No Phaser provider is configured for the manifest network. */ -export const InvalidSelectorError = makeError( - "INVALID_SELECTOR", - "InvalidSelectorError" +export const PhaserProviderNotFoundError = makeError( + "PHASER_PROVIDER_NOT_FOUND", + "PhaserProviderNotFoundError" ).annotate({ httpApiStatus: 400 }) -export type InvalidSelectorError = typeof InvalidSelectorError.Type +export type PhaserProviderNotFoundError = typeof PhaserProviderNotFoundError.Type /** - * InvalidTableName - Table name does not conform to SQL identifier rules. + * PhaserProviderParseFailed - The Phaser provider configuration is invalid. */ -export const InvalidTableNameError = makeError( - "INVALID_TABLE_NAME", - "InvalidTableNameError" +export const PhaserProviderParseFailedError = makeError( + "PHASER_PROVIDER_PARSE_FAILED", + "PhaserProviderParseFailedError" ).annotate({ httpApiStatus: 400 }) -export type InvalidTableNameError = typeof InvalidTableNameError.Type +export type PhaserProviderParseFailedError = typeof PhaserProviderParseFailedError.Type /** - * InvalidTableSql - SQL syntax error in table definition. + * PhaserProviderNetworkMismatch - The Phaser provider network does not match the manifest. */ -export const InvalidTableSqlError = makeError( - "INVALID_TABLE_SQL", - "InvalidTableSqlError" +export const PhaserProviderNetworkMismatchError = makeError( + "PHASER_PROVIDER_NETWORK_MISMATCH", + "PhaserProviderNetworkMismatchError" ).annotate({ httpApiStatus: 400 }) -export type InvalidTableSqlError = typeof InvalidTableSqlError.Type +export type PhaserProviderNetworkMismatchError = typeof PhaserProviderNetworkMismatchError.Type /** - * InvalidDependencyAliasForTableRef - Invalid dependency alias in table reference. + * PhaserConnectionFailed - Failed to connect to the Phaser bridge. */ -export const InvalidDependencyAliasForTableRefError = makeError( - "INVALID_DEPENDENCY_ALIAS_FOR_TABLE_REF", - "InvalidDependencyAliasForTableRefError" -).annotate({ httpApiStatus: 400 }) +export const PhaserConnectionFailedError = makeError( + "PHASER_CONNECTION_FAILED", + "PhaserConnectionFailedError" +).annotate({ httpApiStatus: 502 }) -export type InvalidDependencyAliasForTableRefError = typeof InvalidDependencyAliasForTableRefError.Type +export type PhaserConnectionFailedError = typeof PhaserConnectionFailedError.Type /** - * InvalidDependencyAliasForFunctionRef - Invalid dependency alias in function reference. + * PhaserDiscoveryFailed - Phaser bridge table discovery failed. */ -export const InvalidDependencyAliasForFunctionRefError = makeError( - "INVALID_DEPENDENCY_ALIAS_FOR_FUNCTION_REF", - "InvalidDependencyAliasForFunctionRefError" -).annotate({ httpApiStatus: 400 }) +export const PhaserDiscoveryFailedError = makeError("PHASER_DISCOVERY_FAILED", "PhaserDiscoveryFailedError").annotate({ + httpApiStatus: 502 +}) -export type InvalidDependencyAliasForFunctionRefError = typeof InvalidDependencyAliasForFunctionRefError.Type +export type PhaserDiscoveryFailedError = typeof PhaserDiscoveryFailedError.Type /** - * LimitTooLarge - The requested limit exceeds the maximum allowed value. + * PhaserInvalidDiscovery - Phaser bridge returned invalid table metadata. */ -export const LimitTooLargeError = makeError( - "LIMIT_TOO_LARGE", - "LimitTooLargeError" -).annotate({ httpApiStatus: 400 }) +export const PhaserInvalidDiscoveryError = makeError( + "PHASER_INVALID_DISCOVERY", + "PhaserInvalidDiscoveryError" +).annotate({ httpApiStatus: 502 }) -export type LimitTooLargeError = typeof LimitTooLargeError.Type +export type PhaserInvalidDiscoveryError = typeof PhaserInvalidDiscoveryError.Type /** - * LimitInvalid - The requested limit is invalid (zero). + * PhaserNoTablesDiscovered - Phaser bridge returned no usable tables. */ -export const LimitInvalidError = makeError( - "LIMIT_INVALID", - "LimitInvalidError" -).annotate({ httpApiStatus: 400 }) +export const PhaserNoTablesDiscoveredError = makeError( + "PHASER_NO_TABLES_DISCOVERED", + "PhaserNoTablesDiscoveredError" +).annotate({ httpApiStatus: 502 }) -export type LimitInvalidError = typeof LimitInvalidError.Type +export type PhaserNoTablesDiscoveredError = typeof PhaserNoTablesDiscoveredError.Type // ============================================================================= -// Database Errors +// Request Validation Errors // ============================================================================= /** - * MetadataDbError - Database operation failure in the metadata PostgreSQL database. + * InvalidPath - Invalid path parameters. + */ +export const InvalidPathError = makeError("INVALID_PATH", "InvalidPathError").annotate({ httpApiStatus: 400 }) + +export type InvalidPathError = typeof InvalidPathError.Type + +/** + * InvalidPayloadFormat - Invalid request payload format. */ -export const MetadataDbError = makeError( - "METADATA_DB_ERROR", - "MetadataDbError" -).annotate({ httpApiStatus: 500 }) +export const InvalidPayloadFormatError = makeError("INVALID_PAYLOAD_FORMAT", "InvalidPayloadFormatError").annotate({ + httpApiStatus: 400 +}) -export type MetadataDbError = typeof MetadataDbError.Type +export type InvalidPayloadFormatError = typeof InvalidPayloadFormatError.Type /** - * PhysicalTableError - Failed to access the physical table metadata. + * InvalidQueryParameters - Invalid query parameters. */ -export const PhysicalTableError = makeError( - "PHYSICAL_TABLE_ERROR", - "PhysicalTableError" -).annotate({ httpApiStatus: 500 }) +export const InvalidQueryParametersError = makeError( + "INVALID_QUERY_PARAMETERS", + "InvalidQueryParametersError" +).annotate({ httpApiStatus: 400 }) + +export type InvalidQueryParametersError = typeof InvalidQueryParametersError.Type + +/** + * InvalidTableName - Table name does not conform to SQL identifier rules. + */ +export const InvalidTableNameError = makeError("INVALID_TABLE_NAME", "InvalidTableNameError").annotate({ + httpApiStatus: 400 +}) + +export type InvalidTableNameError = typeof InvalidTableNameError.Type + +/** + * InvalidTableSql - SQL syntax error in table definition. + */ +export const InvalidTableSqlError = makeError("INVALID_TABLE_SQL", "InvalidTableSqlError").annotate({ + httpApiStatus: 400 +}) + +export type InvalidTableSqlError = typeof InvalidTableSqlError.Type + +/** + * LimitTooLarge - The requested limit exceeds the maximum allowed value. + */ +export const LimitTooLargeError = makeError("LIMIT_TOO_LARGE", "LimitTooLargeError").annotate({ httpApiStatus: 400 }) -export type PhysicalTableError = typeof PhysicalTableError.Type +export type LimitTooLargeError = typeof LimitTooLargeError.Type + +/** + * LimitInvalid - The requested limit is invalid (zero). + */ +export const LimitInvalidError = makeError("LIMIT_INVALID", "LimitInvalidError").annotate({ httpApiStatus: 400 }) + +export type LimitInvalidError = typeof LimitInvalidError.Type + +// ============================================================================= +// Database Errors +// ============================================================================= /** * ResolveRevisionError - Failed to resolve the dataset revision. */ -export const ResolveRevisionError = makeError( - "RESOLVE_REVISION_ERROR", - "ResolveRevisionError" -).annotate({ httpApiStatus: 500 }) +export const ResolveRevisionError = makeError("RESOLVE_REVISION_ERROR", "ResolveRevisionError").annotate({ + httpApiStatus: 500 +}) export type ResolveRevisionError = typeof ResolveRevisionError.Type @@ -645,20 +613,18 @@ export type ResolveRevisionError = typeof ResolveRevisionError.Type * - SQL contains LIMIT, ORDER BY, GROUP BY, DISTINCT, window functions * - SQL uses outer joins */ -export const NonIncrementalQueryError = makeError( - "NON_INCREMENTAL_QUERY", - "NonIncrementalQueryError" -).annotate({ httpApiStatus: 400 }) +export const NonIncrementalQueryError = makeError("NON_INCREMENTAL_QUERY", "NonIncrementalQueryError").annotate({ + httpApiStatus: 400 +}) export type NonIncrementalQueryError = typeof NonIncrementalQueryError.Type /** * SchemaInference - Failed to infer output schema from query. */ -export const SchemaInferenceError = makeError( - "SCHEMA_INFERENCE", - "SchemaInferenceError" -).annotate({ httpApiStatus: 500 }) +export const SchemaInferenceError = makeError("SCHEMA_INFERENCE", "SchemaInferenceError").annotate({ + httpApiStatus: 500 +}) export type SchemaInferenceError = typeof SchemaInferenceError.Type @@ -668,7 +634,7 @@ export type SchemaInferenceError = typeof SchemaInferenceError.Type export const TableNotFoundInDatasetError = makeError( "TABLE_NOT_FOUND_IN_DATASET", "TableNotFoundInDatasetError" -).annotate({ httpApiStatus: 404 }) +).annotate({ httpApiStatus: 400 }) export type TableNotFoundInDatasetError = typeof TableNotFoundInDatasetError.Type @@ -683,99 +649,174 @@ export const TableReferenceResolutionError = makeError( export type TableReferenceResolutionError = typeof TableReferenceResolutionError.Type /** - * UnqualifiedTable - Table reference is not qualified with a dataset. + * CatalogQualifiedTableInNetworkResolution - A catalog-qualified table was encountered while resolving table networks. */ -export const UnqualifiedTableError = makeError( - "UNQUALIFIED_TABLE", - "UnqualifiedTableError" -).annotate({ httpApiStatus: 400 }) +export const CatalogQualifiedTableInNetworkResolutionError = makeError( + "CATALOG_QUALIFIED_TABLE_IN_NETWORK_RESOLUTION", + "CatalogQualifiedTableInNetworkResolutionError" +).annotate({ httpApiStatus: 500 }) -export type UnqualifiedTableError = typeof UnqualifiedTableError.Type +export type CatalogQualifiedTableInNetworkResolutionError = typeof CatalogQualifiedTableInNetworkResolutionError.Type -// ============================================================================= -// Scheduler/Worker Errors -// ============================================================================= +/** + * CyclicDependency - Tables in the request reference each other cyclically. + */ +export const CyclicDependencyError = makeError("CYCLIC_DEPENDENCY", "CyclicDependencyError").annotate({ + httpApiStatus: 400 +}) + +export type CyclicDependencyError = typeof CyclicDependencyError.Type /** - * SchedulerError - Indicates a failure in the job scheduling system. + * DependencyManifestLinkCheck - Failed to verify that a dependency manifest is linked to its dataset. */ -export const SchedulerError = makeError( - "SCHEDULER_ERROR", - "SchedulerError" +export const DependencyManifestLinkCheckError = makeError( + "DEPENDENCY_MANIFEST_LINK_CHECK", + "DependencyManifestLinkCheckError" ).annotate({ httpApiStatus: 500 }) -export type SchedulerError = typeof SchedulerError.Type +export type DependencyManifestLinkCheckError = typeof DependencyManifestLinkCheckError.Type /** - * WorkerNotAvailable - Specified worker not found or inactive. + * DependencyVersionResolution - Failed to resolve the version of a dependency. */ -export const WorkerNotAvailableError = makeError( - "WORKER_NOT_AVAILABLE", - "WorkerNotAvailableError" -).annotate({ httpApiStatus: 400 }) +export const DependencyVersionResolutionError = makeError( + "DEPENDENCY_VERSION_RESOLUTION", + "DependencyVersionResolutionError" +).annotate({ httpApiStatus: 500 }) -export type WorkerNotAvailableError = typeof WorkerNotAvailableError.Type +export type DependencyVersionResolutionError = typeof DependencyVersionResolutionError.Type /** - * SchedulerListWorkersError - Failed to list workers from the scheduler. + * InvalidPlan - The query plan could not be built from the user-provided SQL. */ -export const SchedulerListWorkersError = makeError( - "SCHEDULER_LIST_WORKERS_ERROR", - "SchedulerListWorkersError" -).annotate({ httpApiStatus: 500 }) +export const InvalidPlanError = makeError("INVALID_PLAN", "InvalidPlanError").annotate({ httpApiStatus: 400 }) -export type SchedulerListWorkersError = typeof SchedulerListWorkersError.Type +export type InvalidPlanError = typeof InvalidPlanError.Type -// ============================================================================= -// Store Errors -// ============================================================================= +/** + * MissingBlockNum - The table output is missing the required block number column. + */ +export const MissingBlockNumError = makeError("MISSING_BLOCK_NUM", "MissingBlockNumError").annotate({ + httpApiStatus: 400 +}) + +export type MissingBlockNumError = typeof MissingBlockNumError.Type + +/** + * MissingTs - The table output is missing the required timestamp column. + */ +export const MissingTsError = makeError("MISSING_TS", "MissingTsError").annotate({ httpApiStatus: 400 }) + +export type MissingTsError = typeof MissingTsError.Type /** - * StoreError - Dataset store operation error. + * NoTableReferences - A table query does not reference any table. */ -export const StoreError = makeError( - "STORE_ERROR", - "StoreError" +export const NoTableReferencesError = makeError("NO_TABLE_REFERENCES", "NoTableReferencesError").annotate({ + httpApiStatus: 400 +}) + +export type NoTableReferencesError = typeof NoTableReferencesError.Type + +/** + * SelfReferencingTable - A table query references itself. + */ +export const SelfReferencingTableError = makeError("SELF_REFERENCING_TABLE", "SelfReferencingTableError").annotate({ + httpApiStatus: 400 +}) + +export type SelfReferencingTableError = typeof SelfReferencingTableError.Type + +/** + * SelfRefNetworksNotResolved - The networks of a self-referenced table could not be resolved. + */ +export const SelfRefNetworksNotResolvedError = makeError( + "SELF_REF_NETWORKS_NOT_RESOLVED", + "SelfRefNetworksNotResolvedError" ).annotate({ httpApiStatus: 500 }) -export type StoreError = typeof StoreError.Type +export type SelfRefNetworksNotResolvedError = typeof SelfRefNetworksNotResolvedError.Type /** - * UnsupportedDatasetKind - Dataset kind is not supported. + * SelfRefTableNotFound - A self-referenced table does not exist in the request. */ -export const UnsupportedDatasetKindError = makeError( - "UNSUPPORTED_DATASET_KIND", - "UnsupportedDatasetKindError" +export const SelfRefTableNotFoundError = makeError("SELF_REF_TABLE_NOT_FOUND", "SelfRefTableNotFoundError").annotate({ + httpApiStatus: 400 +}) + +export type SelfRefTableNotFoundError = typeof SelfRefTableNotFoundError.Type + +/** + * SessionConfigError - Failed to configure the query session. + */ +export const SessionConfigError = makeError("SESSION_CONFIG_ERROR", "SessionConfigError").annotate({ + httpApiStatus: 500 +}) + +export type SessionConfigError = typeof SessionConfigError.Type + +/** + * StaticSourceNotMaterializable - A static dataset source cannot be materialized by a derived table. + */ +export const StaticSourceNotMaterializableError = makeError( + "STATIC_SOURCE_NOT_MATERIALIZABLE", + "StaticSourceNotMaterializableError" ).annotate({ httpApiStatus: 400 }) -export type UnsupportedDatasetKindError = typeof UnsupportedDatasetKindError.Type +export type StaticSourceNotMaterializableError = typeof StaticSourceNotMaterializableError.Type /** - * VersionTaggingError - Failed to tag version for the dataset. + * TableReferencesNotResolved - The table references of a query could not be resolved. + */ +export const TableReferencesNotResolvedError = makeError( + "TABLE_REFERENCES_NOT_RESOLVED", + "TableReferencesNotResolvedError" +).annotate({ httpApiStatus: 500 }) + +export type TableReferencesNotResolvedError = typeof TableReferencesNotResolvedError.Type + +// ============================================================================= +// Scheduler/Worker Errors +// ============================================================================= + +/** + * SchedulerListWorkersError - Failed to list workers from the scheduler. */ -export const VersionTaggingError = makeError( - "VERSION_TAGGING_ERROR", - "VersionTaggingError" +export const SchedulerListWorkersError = makeError( + "SCHEDULER_LIST_WORKERS_ERROR", + "SchedulerListWorkersError" ).annotate({ httpApiStatus: 500 }) +export type SchedulerListWorkersError = typeof SchedulerListWorkersError.Type + +// ============================================================================= +// Store Errors +// ============================================================================= + +/** + * VersionTaggingError - Failed to tag version for the dataset. + */ +export const VersionTaggingError = makeError("VERSION_TAGGING_ERROR", "VersionTaggingError").annotate({ + httpApiStatus: 500 +}) + export type VersionTaggingError = typeof VersionTaggingError.Type /** * ListAllDatasetsError - Failed to list all datasets from dataset store. */ -export const ListAllDatasetsError = makeError( - "LIST_ALL_DATASETS_ERROR", - "ListAllDatasetsError" -).annotate({ httpApiStatus: 500 }) +export const ListAllDatasetsError = makeError("LIST_ALL_DATASETS_ERROR", "ListAllDatasetsError").annotate({ + httpApiStatus: 500 +}) export type ListAllDatasetsError = typeof ListAllDatasetsError.Type /** * ListVersionTagsError - Failed to list version tags from dataset store. */ -export const ListVersionTagsError = makeError( - "LIST_VERSION_TAGS_ERROR", - "ListVersionTagsError" -).annotate({ httpApiStatus: 500 }) +export const ListVersionTagsError = makeError("LIST_VERSION_TAGS_ERROR", "ListVersionTagsError").annotate({ + httpApiStatus: 500 +}) export type ListVersionTagsError = typeof ListVersionTagsError.Type diff --git a/packages/amp/src/admin/service.ts b/packages/amp/src/admin/service.ts index 0630be2..9b029f5 100644 --- a/packages/amp/src/admin/service.ts +++ b/packages/amp/src/admin/service.ts @@ -2,7 +2,7 @@ * This module provides the AdminApi service for interacting with the Amp Admin API. * * The Admin API allows managing: - * - Datasets (registration, versioning, deployment, sync progress) + * - Datasets (registration, versioning, manifests) * - Jobs (listing, stopping, deletion) * - Workers (listing) * - Providers (listing) @@ -24,7 +24,7 @@ import * as Auth from "../auth/service.ts" import type * as Models from "../core/domain.ts" import * as Api from "./api.ts" import type * as Domain from "./domain.ts" -import type { DatasetStoreError, ListAllDatasetsError, MetadataDbError, SchedulerListWorkersError } from "./error.ts" +import type { SchedulerListWorkersError } from "./error.ts" // ============================================================================= // Admin API Service Types @@ -33,10 +33,7 @@ import type { DatasetStoreError, ListAllDatasetsError, MetadataDbError, Schedule /** * Represents possible errors that can occur when performing HTTP requests. */ -export type HttpError = - | HttpApiError.Forbidden - | HttpApiError.Unauthorized - | HttpClientError.HttpClientError +export type HttpError = HttpApiError.Forbidden | HttpApiError.Unauthorized | HttpClientError.HttpClientError // ============================================================================= // Admin API Service @@ -45,220 +42,152 @@ export type HttpError = /** * A service which can be used to execute operations against the Amp admin API. */ -export class AdminApi extends Context.Service Effect.Effect - - /** - * Get all datasets. - * - * @return The list of all datasets. - */ - readonly getDatasets: Effect.Effect< - Domain.GetDatasetsResponse, - | HttpError - | Api.GetDatasetsError - | DatasetStoreError - | ListAllDatasetsError - | MetadataDbError - > - - /** - * Get all versions of a specific dataset. - * - * @param namespace The namespace of the dataset. - * @param name The name of the dataset. - * @return The list of all dataset versions. - */ - readonly getDatasetVersions: ( - namespace: Models.DatasetNamespace, - name: Models.DatasetName - ) => Effect.Effect< - Domain.GetDatasetVersionsResponse, - HttpError | Api.GetDatasetVersionsError - > - - /** - * Get a specific dataset version. - * - * @param namespace The namespace of the dataset. - * @param name The name of the dataset. - * @param revision The version/revision of the dataset. - * @return The dataset version information. - */ - readonly getDatasetVersion: ( - namespace: Models.DatasetNamespace, - name: Models.DatasetName, - revision: Models.DatasetRevision - ) => Effect.Effect< - Domain.GetDatasetVersionResponse, - HttpError | Api.GetDatasetVersionError - > - - /** - * Deploy a dataset version. - * - * @param namespace The namespace of the dataset. - * @param name The name of the dataset to deploy. - * @param revision The version/revision to deploy. - * @param options The deployment options. - * @return The deployment response with job ID. - */ - readonly deployDataset: ( - namespace: Models.DatasetNamespace, - name: Models.DatasetName, - revision: Models.DatasetRevision, - options?: { - endBlock?: string | null | undefined - parallelism?: number | undefined - workerId?: string | undefined - } | undefined - ) => Effect.Effect< - Domain.DeployDatasetResponse, - HttpError | Api.DeployDatasetError - > - - /** - * Get the manifest for a dataset version. - * - * @param namespace The namespace of the dataset. - * @param name The name of the dataset. - * @param revision The version/revision of the dataset. - * @return The dataset manifest. - */ - readonly getDatasetManifest: ( - namespace: Models.DatasetNamespace, - name: Models.DatasetName, - revision: Models.DatasetRevision - ) => Effect.Effect - - /** - * Retrieves sync progress information for a specific dataset revision, - * including per-table current block numbers, job status, and file statistics. - * - * @param namespace The namespace of the dataset. - * @param name The name of the dataset. - * @param revision The version/revision of the dataset. - * @return The dataset sync progress. - */ - readonly getDatasetSyncProgress: ( - namespace: Models.DatasetNamespace, - name: Models.DatasetName, - revision: Models.DatasetRevision - ) => Effect.Effect< - Domain.GetDatasetSyncProgressResponse, - HttpError | Api.GetDatasetSyncProgressError - > - - /** - * Get all jobs with optional pagination and filtering. - * - * @param options Pagination and filtering options. - * @return The list of jobs with pagination cursor. - */ - readonly getJobs: (options?: { - limit?: number | undefined - lastJobId?: number | undefined - status?: string | undefined - }) => Effect.Effect< - Domain.GetJobsResponse, - HttpError | Api.GetJobsError - > - - /** - * Get a job by ID. - * - * @param jobId The ID of the job to get. - * @return The job information. - */ - readonly getJobById: ( - jobId: number - ) => Effect.Effect - - /** - * Stop a job by ID. - * - * @param jobId The ID of the job to stop. - * @return Void on success. - */ - readonly stopJob: ( - jobId: number - ) => Effect.Effect - - /** - * Delete a job by ID. - * - * @param jobId The ID of the job to delete. - * @return Void on success. - */ - readonly deleteJob: ( - jobId: number - ) => Effect.Effect - - /** - * Get all workers. - * - * @return The list of workers. - */ - readonly getWorkers: Effect.Effect< - Domain.GetWorkersResponse, - HttpError | Api.GetWorkersError | SchedulerListWorkersError - > - - /** - * Get all providers. - * - * @return The list of providers. - */ - readonly getProviders: Effect.Effect - - /** - * Register a manifest. - * - * @param manifest The manifest to register. - * @return The registered manifest response with hash. - */ - readonly registerManifest: ( - manifest: unknown - ) => Effect.Effect< - Domain.RegisterManifestResponse, - HttpError | Api.RegisterManifestError - > - - /** - * Gets the schema of a dataset. - * - * @param request - The schema request with tables and dependencies. - * @returns An effect that resolves to the schema response. - */ - readonly getOutputSchema: ( - request: Domain.GetOutputSchemaPayload - ) => Effect.Effect< - Domain.GetOutputSchemaResponse, - HttpError | Api.GetOutputSchemaError - > -}>()( - "Amp/AdminApi" -) {} +export class AdminApi extends Context.Service< + AdminApi, + { + /** + * Register a dataset manifest. + * + * @param namespace The namespace of the dataset to register. + * @param name The name of the dataset to register. + * @param manifest The dataset manifest to register, or the hash of a previously registered manifest. + * @param version Optional version of the dataset to register. If omitted, only the "dev" tag is updated. + * @return The registered dataset information. + */ + readonly registerDataset: ( + namespace: Models.DatasetNamespace, + name: Models.DatasetName, + manifest: Models.DatasetManifest | Models.DatasetHash, + version?: Models.DatasetRevision | undefined + ) => Effect.Effect + + /** + * Get all datasets. + * + * @return The list of all datasets. + */ + readonly getDatasets: Effect.Effect + + /** + * Get all versions of a specific dataset. + * + * @param namespace The namespace of the dataset. + * @param name The name of the dataset. + * @return The list of all dataset versions. + */ + readonly getDatasetVersions: ( + namespace: Models.DatasetNamespace, + name: Models.DatasetName + ) => Effect.Effect + + /** + * Get a specific dataset version. + * + * @param namespace The namespace of the dataset. + * @param name The name of the dataset. + * @param revision The version/revision of the dataset. + * @return The dataset version information. + */ + readonly getDatasetVersion: ( + namespace: Models.DatasetNamespace, + name: Models.DatasetName, + revision: Models.DatasetRevision + ) => Effect.Effect + + /** + * Get the manifest for a dataset version. + * + * @param namespace The namespace of the dataset. + * @param name The name of the dataset. + * @param revision The version/revision of the dataset. + * @return The dataset manifest. + */ + readonly getDatasetManifest: ( + namespace: Models.DatasetNamespace, + name: Models.DatasetName, + revision: Models.DatasetRevision + ) => Effect.Effect + + /** + * Get all jobs with optional pagination and filtering. + * + * @param options Pagination and filtering options. + * @return The list of jobs with pagination cursor. + */ + readonly getJobs: (options?: { + limit?: number | undefined + lastJobId?: number | undefined + status?: string | undefined + }) => Effect.Effect + + /** + * Get a job by ID. + * + * @param jobId The ID of the job to get. + * @return The job information. + */ + readonly getJobById: (jobId: number) => Effect.Effect + + /** + * Stop a job by ID. + * + * @param jobId The ID of the job to stop. + * @return Void on success. + */ + readonly stopJob: (jobId: number) => Effect.Effect + + /** + * Delete a job by ID. + * + * @param jobId The ID of the job to delete. + * @return Void on success. + */ + readonly deleteJob: (jobId: number) => Effect.Effect + + /** + * Get all workers. + * + * @return The list of workers. + */ + readonly getWorkers: Effect.Effect< + Domain.GetWorkersResponse, + HttpError | Api.GetWorkersError | SchedulerListWorkersError + > + + /** + * Get all providers. + * + * @return The list of providers. + */ + readonly getProviders: Effect.Effect + + /** + * Register a manifest. + * + * @param manifest The manifest to register. + * @return The registered manifest response with hash. + */ + readonly registerManifest: ( + manifest: unknown + ) => Effect.Effect + + /** + * Gets the schema of a dataset. + * + * @param request - The schema request with tables and dependencies. + * @returns An effect that resolves to the schema response. + */ + readonly getOutputSchema: ( + request: Domain.GetOutputSchemaPayload + ) => Effect.Effect + } +>()("Amp/AdminApi") {} export interface MakeOptions { readonly url: string | URL } -const make = Effect.fnUntraced(function*(options: MakeOptions) { +const make = Effect.fnUntraced(function* (options: MakeOptions) { type Service = typeof AdminApi.Service const auth = yield* Effect.serviceOption(Auth.Auth) @@ -269,7 +198,7 @@ const make = Effect.fnUntraced(function*(options: MakeOptions) { onNone: constUndefined, onSome: (authService) => HttpClient.mapRequestEffect( - Effect.fnUntraced(function*(request) { + Effect.fnUntraced(function* (request) { const authInfo = yield* authService.getCachedAuthInfo.pipe( // Treat cache errors as "no auth available" Effect.catch(() => Effect.succeed(Option.none())) @@ -284,22 +213,8 @@ const make = Effect.fnUntraced(function*(options: MakeOptions) { // Dataset Operations - const deployDataset: Service["deployDataset"] = Effect.fn("AdminApi.deployDataset")( - function*(namespace, name, revision, deployOptions) { - const params = { namespace, name, revision } - const payload = { - endBlock: deployOptions?.endBlock, - parallelism: deployOptions?.parallelism, - workerId: deployOptions?.workerId - } - yield* Effect.annotateCurrentSpan({ params, payload }) - return yield* client.dataset.deployDataset({ params, payload }) - }, - Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die) - ) - const getDatasetManifest: Service["getDatasetManifest"] = Effect.fn("AdminApi.getDatasetManifest")( - function*(namespace, name, revision) { + function* (namespace, name, revision) { const params = { namespace, name, revision } yield* Effect.annotateCurrentSpan({ params }) return yield* client.dataset.getDatasetManifest({ params }) @@ -307,13 +222,12 @@ const make = Effect.fnUntraced(function*(options: MakeOptions) { Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die) ) - const getDatasets: Service["getDatasets"] = client.dataset.getDatasets({}).pipe( - Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die), - Effect.withSpan("AdminApi.getDatasets") - ) + const getDatasets: Service["getDatasets"] = client.dataset + .getDatasets({}) + .pipe(Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die), Effect.withSpan("AdminApi.getDatasets")) const getDatasetVersion: Service["getDatasetVersion"] = Effect.fn("AdminApi.getDatasetVersion")( - function*(namespace, name, revision) { + function* (namespace, name, revision) { const params = { namespace, name, revision } yield* Effect.annotateCurrentSpan({ params }) return yield* client.dataset.getDatasetVersion({ params }) @@ -322,7 +236,7 @@ const make = Effect.fnUntraced(function*(options: MakeOptions) { ) const getDatasetVersions: Service["getDatasetVersions"] = Effect.fn("AdminApi.getDatasetVersions")( - function*(namespace, name) { + function* (namespace, name) { const params = { namespace, name } yield* Effect.annotateCurrentSpan({ params }) return yield* client.dataset.getDatasetVersions({ params }) @@ -331,7 +245,7 @@ const make = Effect.fnUntraced(function*(options: MakeOptions) { ) const registerDataset: Service["registerDataset"] = Effect.fn("AdminApi.registerDataset")( - function*(namespace, name, manifest, version) { + function* (namespace, name, manifest, version) { const payload = { namespace, name, version, manifest } yield* Effect.annotateCurrentSpan({ payload }) return yield* client.dataset.registerDataset({ payload }) @@ -339,21 +253,10 @@ const make = Effect.fnUntraced(function*(options: MakeOptions) { Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die) ) - const getDatasetSyncProgress: Service["getDatasetSyncProgress"] = Effect.fn( - "AdminApi.getDatasetSyncProgress" - )( - function*(namespace, name, revision) { - const params = { namespace, name, revision } - yield* Effect.annotateCurrentSpan({ params }) - return yield* client.dataset.getDatasetSyncProgress({ params }) - }, - Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die) - ) - // Job Operations const getJobs: Service["getJobs"] = Effect.fn("AdminApi.getJobs")( - function*(jobsOptions) { + function* (jobsOptions) { const query = { limit: jobsOptions?.limit, lastJobId: jobsOptions?.lastJobId, @@ -366,7 +269,7 @@ const make = Effect.fnUntraced(function*(options: MakeOptions) { ) const getJobById: Service["getJobById"] = Effect.fn("AdminApi.getJobById")( - function*(jobId) { + function* (jobId) { const params = { id: jobId } yield* Effect.annotateCurrentSpan({ params }) return yield* client.job.getJobById({ params }) @@ -375,7 +278,7 @@ const make = Effect.fnUntraced(function*(options: MakeOptions) { ) const stopJob: Service["stopJob"] = Effect.fn("AdminApi.stopJob")( - function*(jobId) { + function* (jobId) { const params = { id: jobId } yield* Effect.annotateCurrentSpan({ params }) return yield* client.job.stopJob({ params }) @@ -384,7 +287,7 @@ const make = Effect.fnUntraced(function*(options: MakeOptions) { ) const deleteJob: Service["deleteJob"] = Effect.fn("AdminApi.deleteJob")( - function*(jobId) { + function* (jobId) { const params = { id: jobId } yield* Effect.annotateCurrentSpan({ params }) return yield* client.job.deleteJob({ params }) @@ -394,22 +297,20 @@ const make = Effect.fnUntraced(function*(options: MakeOptions) { // Worker Operations - const getWorkers: Service["getWorkers"] = client.worker.getWorkers({}).pipe( - Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die), - Effect.withSpan("AdminApi.getWorkers") - ) + const getWorkers: Service["getWorkers"] = client.worker + .getWorkers({}) + .pipe(Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die), Effect.withSpan("AdminApi.getWorkers")) // Provider Operations - const getProviders: Service["getProviders"] = client.provider.getProviders({}).pipe( - Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die), - Effect.withSpan("AdminApi.getProviders") - ) + const getProviders: Service["getProviders"] = client.provider + .getProviders({}) + .pipe(Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die), Effect.withSpan("AdminApi.getProviders")) // Manifest Operations const registerManifest: Service["registerManifest"] = Effect.fn("AdminApi.registerManifest")( - function*(manifest) { + function* (manifest) { return yield* client.manifest.registerManifest({ payload: manifest }) }, Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die) @@ -418,19 +319,17 @@ const make = Effect.fnUntraced(function*(options: MakeOptions) { // Schema Operations const getOutputSchema: Service["getOutputSchema"] = Effect.fn("AdminApi.getOutputSchema")( - function*(payload) { + function* (payload) { return yield* client.schema.getOutputSchema({ payload }) }, Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die) ) return AdminApi.of({ - deployDataset, getDatasetManifest, getDatasets, getDatasetVersion, getDatasetVersions, - getDatasetSyncProgress, registerDataset, getJobs, getJobById, @@ -446,21 +345,14 @@ const make = Effect.fnUntraced(function*(options: MakeOptions) { /** * Creates a layer for the Admin API service. */ -export const layer = (options: MakeOptions): Layer.Layer< - AdminApi, - never, - HttpClient.HttpClient -> => Layer.effect(AdminApi, make(options)) +export const layer = (options: MakeOptions): Layer.Layer => + Layer.effect(AdminApi, make(options)) /** * Creates a layer for the Admin API service with authentication provided by * default. */ -export const layerAuth = (options: MakeOptions): Layer.Layer< - AdminApi, - never, - HttpClient.HttpClient | KeyValueStore.KeyValueStore -> => - Layer.effect(AdminApi, make(options)).pipe( - Layer.provide(Auth.layer) - ) +export const layerAuth = ( + options: MakeOptions +): Layer.Layer => + Layer.effect(AdminApi, make(options)).pipe(Layer.provide(Auth.layer)) diff --git a/packages/amp/src/arrow-flight/errors.ts b/packages/amp/src/arrow-flight/errors.ts index ea60998..60c1c4b 100644 --- a/packages/amp/src/arrow-flight/errors.ts +++ b/packages/amp/src/arrow-flight/errors.ts @@ -21,9 +21,7 @@ export type ArrowFlightError = /** * Represents an Arrow Flight RPC request that failed. */ -export class RpcError extends Schema.TaggedErrorClass( - "Amp/RpcError" -)("RpcError", { +export class RpcError extends Schema.TaggedError("Amp/RpcError")("RpcError", { method: Schema.String, /** * The underlying reason for the failed RPC request. @@ -35,9 +33,7 @@ export class RpcError extends Schema.TaggedErrorClass( * Represents an error that occurred as a result of a `FlightInfo` request * returning an empty list of endpoints from which data can be acquired. */ -export class NoEndpointsError extends Schema.TaggedErrorClass( - "Amp/NoEndpointsError" -)("NoEndpointsError", { +export class NoEndpointsError extends Schema.TaggedError("Amp/NoEndpointsError")("NoEndpointsError", { /** * The SQL query that was requested. */ @@ -52,46 +48,49 @@ export class NoEndpointsError extends Schema.TaggedErrorClass( * For Amp queries, there should only ever be **one** authoritative source * of data. */ -export class MultipleEndpointsError extends Schema.TaggedErrorClass( - "Amp/MultipleEndpointsError" -)("MultipleEndpointsError", { - /** - * The SQL query that was requested. - */ - query: Schema.String -}) {} +export class MultipleEndpointsError extends Schema.TaggedError("Amp/MultipleEndpointsError")( + "MultipleEndpointsError", + { + /** + * The SQL query that was requested. + */ + query: Schema.String + } +) {} /** * Represents an error that occurred as a result of a `FlightInfo` request * whose endpoint did not have a ticket. */ -export class TicketNotFoundError extends Schema.TaggedErrorClass( - "Amp/TicketNotFoundError" -)("TicketNotFoundError", { - /** - * The SQL query that was requested. - */ - query: Schema.String -}) {} +export class TicketNotFoundError extends Schema.TaggedError("Amp/TicketNotFoundError")( + "TicketNotFoundError", + { + /** + * The SQL query that was requested. + */ + query: Schema.String + } +) {} /** * Represents an error that occurred as a result of failing to parse an Apache * Arrow RecordBatch. */ -export class ParseRecordBatchError extends Schema.TaggedErrorClass( - "Amp/ParseRecordBatchError" -)("ParseRecordBatchError", { - /** - * The underlying reason for the failure to parse a record batch. - */ - cause: Schema.Defect() -}) {} +export class ParseRecordBatchError extends Schema.TaggedError("Amp/ParseRecordBatchError")( + "ParseRecordBatchError", + { + /** + * The underlying reason for the failure to parse a record batch. + */ + cause: Schema.Defect() + } +) {} /** * Represents an error that occurred as a result of failing to parse an Apache * Arrow DictionaryBatch. */ -export class ParseDictionaryBatchError extends Schema.TaggedErrorClass( +export class ParseDictionaryBatchError extends Schema.TaggedError( "Amp/ParseDictionaryBatchError" )("ParseDictionaryBatchError", { /** @@ -104,9 +103,7 @@ export class ParseDictionaryBatchError extends Schema.TaggedErrorClass( - "Amp/ParseSchemaError" -)("ParseSchemaError", { +export class ParseSchemaError extends Schema.TaggedError("Amp/ParseSchemaError")("ParseSchemaError", { /** * The underlying reason for the failure to parse a schema. */ diff --git a/packages/amp/src/arrow-flight/node.ts b/packages/amp/src/arrow-flight/node.ts index ce6e18a..af07a55 100644 --- a/packages/amp/src/arrow-flight/node.ts +++ b/packages/amp/src/arrow-flight/node.ts @@ -9,7 +9,7 @@ import { Interceptors, Transport } from "./transport.ts" export const layerTransportGrpc = (options: GrpcTransportOptions): Layer.Layer => Layer.effect( Transport, - Effect.gen(function*() { + Effect.gen(function* () { const interceptors = yield* Interceptors return createGrpcTransport({ ...options, diff --git a/packages/amp/src/arrow-flight/service.ts b/packages/amp/src/arrow-flight/service.ts index fc74766..f13e225 100644 --- a/packages/amp/src/arrow-flight/service.ts +++ b/packages/amp/src/arrow-flight/service.ts @@ -60,10 +60,7 @@ export class ArrowFlight extends Context.Service< readonly query: ( sql: string, options?: Options - ) => Effect.Effect< - ReadonlyArray>, - ArrowFlightError - > + ) => Effect.Effect>, ArrowFlightError> /** * Executes an Arrow Flight SQL query and returns a stream of results. @@ -87,14 +84,12 @@ export class ArrowFlight extends Context.Service< } >()("Amp/ArrowFlight") {} -const make = Effect.gen(function*() { +const make = Effect.gen(function* () { const auth = yield* Effect.serviceOption(Auth) const transport = yield* Transport const client = createClient(FlightService, transport) - const decodeRecordBatchMetadata = Schema.decodeEffect( - RecordBatchMetadataFromUint8Array - ) + const decodeRecordBatchMetadata = Schema.decodeEffect(RecordBatchMetadataFromUint8Array) /** * Execute a SQL query and return a stream of rows. @@ -103,15 +98,13 @@ const make = Effect.gen(function*() { query: string, options?: Options ): Stream.Stream, ArrowFlightError> => - Effect.gen(function*() { + Effect.gen(function* () { const contextValues = createContextValues() // A cache failure ("can't read the saved token") is not an Arrow Flight // error and shouldn't surface as one — fall back to "no cached auth" // and let the server's auth response (if any) be the source of truth. const authInfo = Option.isSome(auth) - ? yield* auth.value.getCachedAuthInfo.pipe( - Effect.orElseSucceed(() => Option.none()) - ) + ? yield* auth.value.getCachedAuthInfo.pipe(Effect.orElseSucceed(() => Option.none())) : Option.none() // Setup the query context with authentication information, if available @@ -132,10 +125,7 @@ const make = Effect.gen(function*() { headers.set("amp-stream", "true") } if (Predicate.isNotUndefined(options?.resumeWatermark)) { - headers.set( - "amp-resume", - blockRangesToResumeWatermark(options.resumeWatermark) - ) + headers.set("amp-resume", blockRangesToResumeWatermark(options.resumeWatermark)) } const flightInfo = yield* Effect.tryPromise({ @@ -156,7 +146,7 @@ const make = Effect.gen(function*() { } const flightDataStream = Stream.unwrap( - Effect.gen(function*() { + Effect.gen(function* () { const controller = yield* Effect.acquireRelease( Effect.sync(() => new AbortController()), (abort) => Effect.sync(() => abort.abort()) @@ -170,9 +160,7 @@ const make = Effect.gen(function*() { let schema: ArrowSchema | undefined const dictionaryRegistry = new DictionaryRegistry() - const dataSchema = Schema.Array( - options?.schema ?? Schema.Record(Schema.String, Schema.Unknown) - ) + const dataSchema = Schema.Array(options?.schema ?? Schema.Record(Schema.String, Schema.Unknown)) const decodeRecordBatchData = Schema.decodeEffect(dataSchema) @@ -180,10 +168,7 @@ const make = Effect.gen(function*() { // Convert FlightData stream to a stream of rows return flightDataStream.pipe( Stream.mapEffect( - Effect.fnUntraced(function*(flightData): Effect.fn.Return< - Option.Option, - ArrowFlightError - > { + Effect.fnUntraced(function* (flightData): Effect.fn.Return, ArrowFlightError> { const messageType = yield* Effect.orDie(getMessageType(flightData)) switch (messageType) { @@ -202,23 +187,13 @@ const make = Effect.gen(function*() { return Option.none() } case MessageHeaderType.RECORD_BATCH: { - const metadata = yield* decodeRecordBatchMetadata( - flightData.appMetadata - ).pipe( - Effect.mapError( - (cause) => new ParseRecordBatchError({ cause }) - ) + const metadata = yield* decodeRecordBatchMetadata(flightData.appMetadata).pipe( + Effect.mapError((cause) => new ParseRecordBatchError({ cause })) ) const recordBatch = yield* parseRecordBatch(flightData).pipe( - Effect.mapError( - (cause) => new ParseRecordBatchError({ cause }) - ) - ) - const decodedRecordBatch = decodeRecordBatch( - recordBatch, - flightData.dataBody, - schema! + Effect.mapError((cause) => new ParseRecordBatchError({ cause })) ) + const decodedRecordBatch = decodeRecordBatch(recordBatch, flightData.dataBody, schema!) const jsonOptions: RecordBatchToJsonOptions = { dictionaryRegistry } @@ -237,19 +212,13 @@ const make = Effect.gen(function*() { const json = recordBatchToJson(decodedRecordBatch, jsonOptions) const data = yield* decodeRecordBatchData(json).pipe( - Effect.mapError( - (cause) => new ParseRecordBatchError({ cause }) - ) + Effect.mapError((cause) => new ParseRecordBatchError({ cause })) ) return Option.some({ data, metadata } as Result) } } - return yield* Effect.die( - new Cause.IllegalArgumentError( - `Invalid message type received: ${messageType}` - ) - ) + return yield* Effect.die(new Cause.IllegalArgumentError(`Invalid message type received: ${messageType}`)) }) ), Stream.filterMap(Filter.fromPredicateOption(Fn.identity)) @@ -259,16 +228,13 @@ const make = Effect.gen(function*() { const query = ( sql: string, options?: Options - ): Effect.Effect< - ReadonlyArray>, - ArrowFlightError - > => + ): Effect.Effect>, ArrowFlightError> => Stream.runCollect(streamQuery(sql, options)).pipe( Effect.map((chunk) => Array.from(chunk)), Effect.withSpan("ArrowFlight.query") ) - const explain = Effect.fn("ArrowFlight.explain")(function*( + const explain = Effect.fn("ArrowFlight.explain")(function* ( sql: string, options?: { readonly analyze?: boolean | undefined } ) { @@ -308,9 +274,7 @@ export const layer: Layer.Layer = Laye * @param ranges - The block ranges to convert. * @returns A resume watermark string. */ -const blockRangesToResumeWatermark = ( - ranges: ReadonlyArray -): string => { +const blockRangesToResumeWatermark = (ranges: ReadonlyArray): string => { const watermarks: Record = {} for (const range of ranges) { watermarks[range.network] = { diff --git a/packages/amp/src/arrow-flight/transport.ts b/packages/amp/src/arrow-flight/transport.ts index 9d790a2..87733b1 100644 --- a/packages/amp/src/arrow-flight/transport.ts +++ b/packages/amp/src/arrow-flight/transport.ts @@ -16,9 +16,7 @@ import type { AuthInfo } from "../core/domain.ts" * A transport implements a protocol, such as Connect or gRPC-web, and allows * for the concrete clients to be independent of the protocol. */ -export class Transport extends Context.Service()( - "@edgeandnode/amp/Transport" -) {} +export class Transport extends Context.Service()("@edgeandnode/amp/Transport") {} /** * A service which abstracts the set of interceptors that are passed to a given @@ -29,34 +27,34 @@ export class Transport extends Context.Service()( * mutate the request and response, catch errors and retry/recover, emit * logs, or do nearly everything else. */ -export class Interceptors extends Context.Reference( - "Amp/ArrowFlight/ConnectRPC/Interceptors", - { defaultValue: () => Arr.empty() } -) {} +export class Interceptors extends Context.Reference("Amp/ArrowFlight/ConnectRPC/Interceptors", { + defaultValue: () => Arr.empty() +}) {} -export const AuthInfoContextKey = createContextKey( - undefined, - { description: "Authentication information obtained from the Amp auth server" } -) +export const AuthInfoContextKey = createContextKey(undefined, { + description: "Authentication information obtained from the Amp auth server" +}) /** * A layer which will add an interceptor to the configured set of `Interceptors` * which uses the specified `token` for bearer token authentication. */ export const layerInterceptorToken = (token: Redacted.Redacted) => - Layer.effectContext(Effect.gen(function*() { - const interceptors = yield* Interceptors + Layer.effectContext( + Effect.gen(function* () { + const interceptors = yield* Interceptors - const interceptor: Interceptor = (next) => (request) => { - const accessToken = Redacted.value(token) + const interceptor: Interceptor = (next) => (request) => { + const accessToken = Redacted.value(token) - request.header.append("Authorization", `Bearer ${accessToken}`) + request.header.append("Authorization", `Bearer ${accessToken}`) - return next(request) - } + return next(request) + } - return Context.make(Interceptors, Arr.append(interceptors, interceptor)) - })) + return Context.make(Interceptors, Arr.append(interceptors, interceptor)) + }) + ) /** * A layer which will add an interceptor to the configured set of `Interceptors` @@ -68,7 +66,7 @@ export const layerInterceptorToken = (token: Redacted.Redacted) => * value of the authentication information access token. */ export const layerInterceptorBearerAuth = Layer.effectContext( - Effect.gen(function*() { + Effect.gen(function* () { const interceptors = yield* Interceptors const interceptor: Interceptor = (next) => (request) => { diff --git a/packages/amp/src/arrow-flight/types.ts b/packages/amp/src/arrow-flight/types.ts index 788e913..3f38d2e 100644 --- a/packages/amp/src/arrow-flight/types.ts +++ b/packages/amp/src/arrow-flight/types.ts @@ -56,7 +56,8 @@ export interface QueryOptions { */ export type ExtractQueryResult = Options extends { readonly schema: Schema.Top -} ? QueryResult> +} + ? QueryResult> : QueryResult> // ============================================================================= @@ -68,9 +69,9 @@ export type ExtractQueryResult = Options extends { * either parsed numbers, raw strings (when no parser matches), or `null` (when * the metric is `N/A`). */ -export const ExplainCell = Schema.NullOr( - Schema.Union([Schema.String, Schema.Number]) -).annotate({ identifier: "ExplainCell" }) +export const ExplainCell = Schema.NullOr(Schema.Union([Schema.String, Schema.Number])).annotate({ + identifier: "ExplainCell" +}) export type ExplainCell = typeof ExplainCell.Type /** @@ -80,8 +81,7 @@ export type ExplainCell = typeof ExplainCell.Type * expanded metrics (durations renamed with a `_secs` suffix, `total → matched` * split into `_total` / `_matched`, etc.). */ -export const ExplainRow = Schema.Record(Schema.String, ExplainCell) - .annotate({ identifier: "ExplainRow" }) +export const ExplainRow = Schema.Record(Schema.String, ExplainCell).annotate({ identifier: "ExplainRow" }) export type ExplainRow = typeof ExplainRow.Type /** diff --git a/packages/amp/src/auth/error.ts b/packages/amp/src/auth/error.ts index fc34d3d..0d3202e 100644 --- a/packages/amp/src/auth/error.ts +++ b/packages/amp/src/auth/error.ts @@ -48,9 +48,7 @@ export const makeError = < * for programmatic error handling. Examples: `INVALID_SELECTOR`, * `DATASET_NOT_FOUND`, `METADATA_DB_ERROR` */ - code: Schema.Literal(code).pipe( - Schema.withConstructorDefault(Effect.succeed(code)) - ), + code: Schema.Literal(code).pipe(Schema.withConstructorDefault(Effect.succeed(code))), /** * Human-readable error message * @@ -58,21 +56,18 @@ export const makeError = < * over time. Use `error_code` for programmatic decisions. */ message: Schema.String - }).pipe(Schema.encodeKeys({ - code: "error_code", - message: "error_message" - })) as any + }).pipe( + Schema.encodeKeys({ + code: "error_code", + message: "error_message" + }) + ) as any // ============================================================================= // Error Details // ============================================================================= -export const DeviceFlowReason = Schema.Literals([ - "expired", - "pending", - "access_denied", - "slow_down" -]) +export const DeviceFlowReason = Schema.Literals(["expired", "pending", "access_denied", "slow_down"]) export type DeviceFlowReason = Schema.Schema.Type @@ -97,138 +92,99 @@ export type VerifyTokenFailureReason = Schema.Schema.Type().pipe( } }), Match.when({ code: "AUTH_NETWORK_ERROR" }, (error) => - error.isTimeout - ? "Authentication request timed out" - : "Could not connect to the authentication service"), + error.isTimeout ? "Authentication request timed out" : "Could not connect to the authentication service" + ), Match.when({ code: "AUTH_REQUEST_ERROR" }, () => "Failed to prepare the authentication request"), Match.when({ code: "AUTH_PROTOCOL_ERROR" }, () => "Authentication service returned an invalid response"), Match.when({ code: "AUTH_VERIFY_TOKEN_FAILED" }, (error) => { @@ -330,11 +285,13 @@ export const getUserSuggestion = Match.type().pipe( Match.when({ code: "AUTH_CACHE_ERROR" }, (error) => error.operation === "clear" ? "You may need to manually remove the credentials file" - : "Check file permissions in your configuration directory and try again"), + : "Check file permissions in your configuration directory and try again" + ), Match.when({ code: "AUTH_NETWORK_ERROR" }, (error) => error.isTimeout ? "The service may be experiencing high load. Please try again in a few moments" - : "Check your internet connection and try again"), + : "Check your internet connection and try again" + ), Match.when( { code: "AUTH_REQUEST_ERROR" }, () => "This is a client-side request construction problem. Please report it if it persists" diff --git a/packages/amp/src/auth/service.ts b/packages/amp/src/auth/service.ts index 0d73051..cc2454e 100644 --- a/packages/amp/src/auth/service.ts +++ b/packages/amp/src/auth/service.ts @@ -46,14 +46,14 @@ export const AUTH_PLATFORM_BASE_URL = new URL("https://auth.amp.thegraph.com/") // Models // ============================================================================= -export const CodeChallenge = NonEmptyTrimmedString.pipe( - Schema.brand("Amp/Auth/CodeChallenge") -).annotate({ identifier: "CodeChallenge" }) +export const CodeChallenge = NonEmptyTrimmedString.pipe(Schema.brand("Amp/Auth/CodeChallenge")).annotate({ + identifier: "CodeChallenge" +}) export type CodeChallenge = Schema.Schema.Type -export const CodeVerifier = NonEmptyTrimmedString.pipe( - Schema.brand("Amp/Auth/CodeVerifier") -).annotate({ identifier: "CodeVerifier" }) +export const CodeVerifier = NonEmptyTrimmedString.pipe(Schema.brand("Amp/Auth/CodeVerifier")).annotate({ + identifier: "CodeVerifier" +}) export type CodeVerifier = Schema.Schema.Type export const PKCEChallenge = Schema.Struct({ @@ -62,14 +62,14 @@ export const PKCEChallenge = Schema.Struct({ }).annotate({ identifier: "PKCEChallenge" }) export type PKCEChallenge = typeof PKCEChallenge.Type -export const DeviceCode = NonEmptyTrimmedString.pipe( - Schema.brand("Amp/Auth/DeviceCode") -).annotate({ identifier: "DeviceCode" }) +export const DeviceCode = NonEmptyTrimmedString.pipe(Schema.brand("Amp/Auth/DeviceCode")).annotate({ + identifier: "DeviceCode" +}) export type DeviceCode = Schema.Schema.Type -export const UserCode = NonEmptyTrimmedString.pipe( - Schema.brand("Amp/Auth/UserCode") -).annotate({ identifier: "UserCode" }) +export const UserCode = NonEmptyTrimmedString.pipe(Schema.brand("Amp/Auth/UserCode")).annotate({ + identifier: "UserCode" +}) export type UserCode = Schema.Schema.Type export const DeviceAuthorizationResponse = Schema.Struct({ @@ -88,12 +88,16 @@ export const DeviceAuthorizationResponse = Schema.Struct({ interval: Schema.Int.check(Schema.isGreaterThan(0)).annotate({ description: "Minimum polling interval in seconds" }) -}).pipe(Schema.encodeKeys({ - deviceCode: "device_code", - userCode: "user_code", - verificationUri: "verification_uri", - expiresIn: "expires_in" -})).annotate({ identifier: "DeviceAuthorizationResponse" }) +}) + .pipe( + Schema.encodeKeys({ + deviceCode: "device_code", + userCode: "user_code", + verificationUri: "verification_uri", + expiresIn: "expires_in" + }) + ) + .annotate({ identifier: "DeviceAuthorizationResponse" }) export type DeviceAuthorizationResponse = typeof DeviceAuthorizationResponse.Type export const DeviceTokenResponse = Schema.Struct({ @@ -111,13 +115,17 @@ export const DeviceTokenResponse = Schema.Struct({ expiresIn: Schema.Int.check(Schema.isGreaterThan(0)).annotate({ description: "Seconds until the token expires from receipt" }) -}).pipe(Schema.encodeKeys({ - accessToken: "access_token", - refreshToken: "refresh_token", - userId: "user_id", - userAccounts: "user_accounts", - expiresIn: "expires_in" -})).annotate({ identifier: "DeviceTokenResponse" }) +}) + .pipe( + Schema.encodeKeys({ + accessToken: "access_token", + refreshToken: "refresh_token", + userId: "user_id", + userAccounts: "user_accounts", + expiresIn: "expires_in" + }) + ) + .annotate({ identifier: "DeviceTokenResponse" }) export type DeviceTokenResponse = typeof DeviceTokenResponse.Type export const DeviceTokenPendingResponse = Schema.Struct({ @@ -157,10 +165,12 @@ export type GenerateTokenResponse = typeof GenerateTokenResponse.Type export const RefreshTokenRequest = Schema.Struct({ refreshToken: Schema.Redacted(RefreshToken), userId: UserId -}).pipe(Schema.encodeKeys({ - refreshToken: "refresh_token", - userId: "user_id" -})) +}).pipe( + Schema.encodeKeys({ + refreshToken: "refresh_token", + userId: "user_id" + }) +) export type RefreshTokenRequest = typeof RefreshTokenRequest.Type export const RefreshTokenResponse = Schema.Struct({ @@ -177,18 +187,20 @@ export const RefreshTokenResponse = Schema.Struct({ examples: [["cmfd6bf6u006vjx0b7xb2eybx", "0x5c8fA0bDf68C915a88cD68291fC7CF011C126C29"]] }) }).annotate({ description: "The user the access token belongs to" }) -}).pipe(Schema.encodeKeys({ - refreshToken: "refresh_token", - sessionUpdateAction: "session_update_action", - expiresIn: "expires_in" -})) +}).pipe( + Schema.encodeKeys({ + refreshToken: "refresh_token", + sessionUpdateAction: "session_update_action", + expiresIn: "expires_in" + }) +) export type RefreshTokenResponse = typeof RefreshTokenResponse.Type // ============================================================================= // Legacy Errors (kept for backwards compatibility) // ============================================================================= -export class VerifySignedAccessTokenError extends Schema.TaggedErrorClass( +export class VerifySignedAccessTokenError extends Schema.TaggedError( "Amp/Auth/VerifySignedAccessTokenError" )("VerifySignedAccessTokenError", { cause: Schema.Defect() }) {} @@ -196,69 +208,72 @@ export class VerifySignedAccessTokenError extends Schema.TaggedErrorClass - - readonly requestDeviceAuthorization: (codeChallenge: CodeChallenge) => Effect.Effect< - DeviceAuthorizationResponse, - | AuthNetworkError - | AuthProtocolError - | AuthRequestError - | AuthRefreshError - > - - readonly pollDeviceToken: (deviceCode: DeviceCode, codeVerifier: CodeVerifier) => Effect.Effect< - AuthInfo, - | AuthCacheError - | AuthNetworkError - | AuthProtocolError - | AuthRequestError - | AuthDeviceFlowError - > - - readonly refreshAccessToken: (authInfo: AuthInfo) => Effect.Effect< - AuthInfo, - | AuthNetworkError - | AuthProtocolError - | AuthRequestError - | AuthCacheError - | AuthTokenExpiredError - | AuthRateLimitError - | AuthRefreshError - | AuthUserMismatchError - > - - readonly generateAccessToken: (options: { - readonly authInfo: AuthInfo - readonly audience?: ReadonlyArray | undefined - readonly duration?: TokenDuration | undefined - }) => Effect.Effect< - GenerateTokenResponse, - | AuthNetworkError - | AuthProtocolError - | AuthRequestError - | AuthTokenExpiredError - | AuthRateLimitError - | AuthRefreshError - > - - readonly verifyAccessToken: ( - token: Redacted.Redacted, - issuer: string - ) => Effect.Effect +export class Auth extends Context.Service< + Auth, + { + readonly createChallenge: Effect.Effect + + readonly requestDeviceAuthorization: ( + codeChallenge: CodeChallenge + ) => Effect.Effect< + DeviceAuthorizationResponse, + AuthNetworkError | AuthProtocolError | AuthRequestError | AuthRefreshError + > + + readonly pollDeviceToken: ( + deviceCode: DeviceCode, + codeVerifier: CodeVerifier + ) => Effect.Effect< + AuthInfo, + AuthCacheError | AuthNetworkError | AuthProtocolError | AuthRequestError | AuthDeviceFlowError + > + + readonly refreshAccessToken: ( + authInfo: AuthInfo + ) => Effect.Effect< + AuthInfo, + | AuthNetworkError + | AuthProtocolError + | AuthRequestError + | AuthCacheError + | AuthTokenExpiredError + | AuthRateLimitError + | AuthRefreshError + | AuthUserMismatchError + > - readonly getCachedAuthInfo: Effect.Effect, AuthCacheError> + readonly generateAccessToken: (options: { + readonly authInfo: AuthInfo + readonly audience?: ReadonlyArray | undefined + readonly duration?: TokenDuration | undefined + }) => Effect.Effect< + GenerateTokenResponse, + | AuthNetworkError + | AuthProtocolError + | AuthRequestError + | AuthTokenExpiredError + | AuthRateLimitError + | AuthRefreshError + > - readonly setCachedAuthInfo: (authInfo: AuthInfo) => Effect.Effect + readonly verifyAccessToken: ( + token: Redacted.Redacted, + issuer: string + ) => Effect.Effect - readonly clearCachedAuthInfo: Effect.Effect -}>()("Amp/Auth") {} + readonly getCachedAuthInfo: Effect.Effect, AuthCacheError> + + readonly setCachedAuthInfo: (authInfo: AuthInfo) => Effect.Effect + + readonly clearCachedAuthInfo: Effect.Effect + } +>()("Amp/Auth") {} // ============================================================================= // Service Implementation // ============================================================================= -const make = Effect.gen(function*() { +const make = Effect.gen(function* () { const store = yield* KeyValueStore.KeyValueStore const kvs = KeyValueStore.toSchemaStore(store, AuthInfo) @@ -270,10 +285,7 @@ const make = Effect.gen(function*() { // Error Handling Helpers // ------------------------------------------------------------------------ - const makeAuthNetworkTimeoutError = ( - endpoint: string, - cause: unknown - ): AuthNetworkError => + const makeAuthNetworkTimeoutError = (endpoint: string, cause: unknown): AuthNetworkError => AuthNetworkError.make({ code: "AUTH_NETWORK_ERROR", message: `Request to ${endpoint} timed out`, @@ -282,10 +294,7 @@ const make = Effect.gen(function*() { cause: Option.some(cause) }) - const makeAuthNetworkRequestError = ( - endpoint: string, - cause: { readonly message: string } - ): AuthNetworkError => + const makeAuthNetworkRequestError = (endpoint: string, cause: { readonly message: string }): AuthNetworkError => AuthNetworkError.make({ code: "AUTH_NETWORK_ERROR", message: `Connection failed to ${endpoint}: ${cause.message}`, @@ -294,10 +303,7 @@ const make = Effect.gen(function*() { cause: Option.some(cause) }) - const makeAuthRefreshSchemaError = ( - endpoint: string, - cause: { readonly message: string } - ): AuthRefreshError => + const makeAuthRefreshSchemaError = (endpoint: string, cause: { readonly message: string }): AuthRefreshError => AuthRefreshError.make({ code: "AUTH_REFRESH_FAILED", message: `Failed to parse ${endpoint} response: ${cause.message}`, @@ -305,10 +311,7 @@ const make = Effect.gen(function*() { cause: Option.some(cause) }) - const makeAuthProtocolDecodeError = ( - endpoint: string, - cause: unknown - ): AuthProtocolError => + const makeAuthProtocolDecodeError = (endpoint: string, cause: unknown): AuthProtocolError => AuthProtocolError.make({ code: "AUTH_PROTOCOL_ERROR", message: `Authentication service returned an unreadable response for ${endpoint}`, @@ -317,10 +320,7 @@ const make = Effect.gen(function*() { cause: Option.some(cause) }) - const makeAuthProtocolEmptyBodyError = ( - endpoint: string, - cause: unknown - ): AuthProtocolError => + const makeAuthProtocolEmptyBodyError = (endpoint: string, cause: unknown): AuthProtocolError => AuthProtocolError.make({ code: "AUTH_PROTOCOL_ERROR", message: `Authentication service returned an empty response for ${endpoint}`, @@ -341,10 +341,7 @@ const make = Effect.gen(function*() { cause: Option.some(cause) }) - const makeAuthRequestEncodeError = ( - endpoint: string, - cause: unknown - ): AuthRequestError => + const makeAuthRequestEncodeError = (endpoint: string, cause: unknown): AuthRequestError => AuthRequestError.make({ code: "AUTH_REQUEST_ERROR", message: `Failed to encode the authentication request for ${endpoint}`, @@ -352,10 +349,7 @@ const make = Effect.gen(function*() { cause: Option.some(cause) }) - const makeAuthRequestInvalidUrlError = ( - endpoint: string, - cause: unknown - ): AuthRequestError => + const makeAuthRequestInvalidUrlError = (endpoint: string, cause: unknown): AuthRequestError => AuthRequestError.make({ code: "AUTH_REQUEST_ERROR", message: `Authentication endpoint is invalid for ${endpoint}`, @@ -406,10 +400,9 @@ const make = Effect.gen(function*() { const executeAuthenticatedRequest = ( request: HttpClientRequest.HttpClientRequest, endpoint: string, - decodeBody: (response: HttpClientResponse.HttpClientResponse) => Effect.Effect< - A, - Schema.SchemaError | HttpClientError.HttpClientError - > + decodeBody: ( + response: HttpClientResponse.HttpClientResponse + ) => Effect.Effect ) => httpClient.execute(request).pipe( Effect.timeout("15 seconds"), @@ -430,45 +423,49 @@ const make = Effect.gen(function*() { message: "Access token lacks required permissions (403 Forbidden)" }) ), - 429: Effect.fnUntraced(function*(response) { + 429: Effect.fnUntraced(function* (response) { const message = yield* extractErrorDescription(response) const retryAfter = Option.fromNullishOr(response.headers["retry-after"]).pipe( Option.flatMap((header) => { const parsed = Number.parseInt(header, 10) - return Number.isNaN(parsed) - ? Option.none() - : Option.some(Duration.seconds(parsed)) + return Number.isNaN(parsed) ? Option.none() : Option.some(Duration.seconds(parsed)) }), Option.getOrElse(() => Duration.minutes(1)) ) - return yield* Effect.fail(AuthRateLimitError.make({ - code: "AUTH_RATE_LIMITED", - message, - retryAfter - })) + return yield* Effect.fail( + AuthRateLimitError.make({ + code: "AUTH_RATE_LIMITED", + message, + retryAfter + }) + ) }), - orElse: Effect.fnUntraced(function*(response) { + orElse: Effect.fnUntraced(function* (response) { const message = yield* extractErrorDescription(response) - return yield* Effect.fail(AuthRefreshError.make({ - code: "AUTH_REFRESH_FAILED", - message, - status: Option.some(response.status), - cause: Option.none() - })) + return yield* Effect.fail( + AuthRefreshError.make({ + code: "AUTH_REFRESH_FAILED", + message, + status: Option.some(response.status), + cause: Option.none() + }) + ) }) }) ), Effect.unwrapReason("HttpClientError"), - Effect.catchTags(makeUnwrappedHttpClientErrorHandlers(endpoint, { - onSchemaError: (cause) => makeAuthRefreshSchemaError(endpoint, cause) - })) + Effect.catchTags( + makeUnwrappedHttpClientErrorHandlers(endpoint, { + onSchemaError: (cause) => makeAuthRefreshSchemaError(endpoint, cause) + }) + ) ) // ------------------------------------------------------------------------ // OAuth2 Authorization Code Flow with PKCE // ------------------------------------------------------------------------ - const createChallenge = Effect.gen(function*() { + const createChallenge = Effect.gen(function* () { const { codeChallenge, codeVerifier } = yield* pkceChallenge() return PKCEChallenge.make({ codeChallenge: CodeChallenge.make(codeChallenge), @@ -476,141 +473,156 @@ const make = Effect.gen(function*() { }) }).pipe(Effect.withSpan("Auth.createChallenge")) - const requestDeviceAuthorization = Effect.fn("Auth.requestDeviceAuthorization")( - function*(codeChallenge: string) { - const endpoint = "/api/v1/device/authorize" - return yield* httpClient.post(endpoint, { + const requestDeviceAuthorization = Effect.fn("Auth.requestDeviceAuthorization")(function* (codeChallenge: string) { + const endpoint = "/api/v1/device/authorize" + return yield* httpClient + .post(endpoint, { acceptJson: true, body: HttpBody.jsonUnsafe({ code_challenge: codeChallenge, code_challenge_method: "S256" }) - }).pipe( + }) + .pipe( Effect.timeout("30 seconds"), Effect.flatMap(HttpClientResponse.schemaBodyJson(DeviceAuthorizationResponse)), Effect.unwrapReason("HttpClientError"), - Effect.catchTags(makeUnwrappedHttpClientErrorHandlers(endpoint, { - onSchemaError: (cause) => makeAuthRefreshSchemaError(endpoint, cause) - })) + Effect.catchTags( + makeUnwrappedHttpClientErrorHandlers(endpoint, { + onSchemaError: (cause) => makeAuthRefreshSchemaError(endpoint, cause) + }) + ) ) - } - ) + }) - const pollDeviceToken = Effect.fn("Auth.pollDeviceToken")( - function*(deviceCode: DeviceCode, codeVerifier: CodeVerifier) { - const endpoint = "/api/v1/device/token" - const response = yield* httpClient.get(endpoint, { + const pollDeviceToken = Effect.fn("Auth.pollDeviceToken")(function* ( + deviceCode: DeviceCode, + codeVerifier: CodeVerifier + ) { + const endpoint = "/api/v1/device/token" + const response = yield* httpClient + .get(endpoint, { acceptJson: true, urlParams: UrlParams.fromInput({ "device_code": deviceCode, "code_verifier": codeVerifier }) - }).pipe( + }) + .pipe( Effect.timeout("10 seconds"), Effect.flatMap(HttpClientResponse.schemaBodyJson(DeviceTokenPollingResponse)), Effect.unwrapReason("HttpClientError"), - Effect.catchTags(makeUnwrappedHttpClientErrorHandlers(endpoint, { - onSchemaError: () => makeAuthDeviceFlowSchemaError() - })) + Effect.catchTags( + makeUnwrappedHttpClientErrorHandlers(endpoint, { + onSchemaError: () => makeAuthDeviceFlowSchemaError() + }) + ) ) - if (response._tag === "DeviceTokenPendingResponse") { - return yield* Effect.fail(AuthDeviceFlowError.make({ + if (response._tag === "DeviceTokenPendingResponse") { + return yield* Effect.fail( + AuthDeviceFlowError.make({ code: "AUTH_DEVICE_FLOW_ERROR", message: "Device authorization is still pending", reason: "pending", verificationUri: Option.none() - })) - } + }) + ) + } - if (response._tag === "DeviceTokenExpiredResponse") { - return yield* Effect.fail(AuthDeviceFlowError.make({ + if (response._tag === "DeviceTokenExpiredResponse") { + return yield* Effect.fail( + AuthDeviceFlowError.make({ code: "AUTH_DEVICE_FLOW_ERROR", message: "Device authorization code has expired", reason: "expired", verificationUri: Option.none() - })) - } + }) + ) + } - const authInfo = yield* makeAuthInfo({ - accessToken: response.accessToken, - refreshToken: response.refreshToken, - expiresIn: response.expiresIn, - userId: response.userId, - accounts: response.userAccounts - }) + const authInfo = yield* makeAuthInfo({ + accessToken: response.accessToken, + refreshToken: response.refreshToken, + expiresIn: response.expiresIn, + userId: response.userId, + accounts: response.userAccounts + }) - yield* setCachedAuthInfo(authInfo) + yield* setCachedAuthInfo(authInfo) - return authInfo - } - ) + return authInfo + }) // ------------------------------------------------------------------------ // OAuth2 Generate / Refresh Token // ------------------------------------------------------------------------ - const generateAccessToken = Effect.fn("Auth.generateAccessToken")( - function*({ authInfo, audience, duration }: { - readonly authInfo: AuthInfo - readonly audience?: ReadonlyArray | undefined - readonly duration?: TokenDuration | undefined - }) { - const endpoint = "/api/v1/auth/generate" - const request = HttpClientRequest.post(endpoint, { - body: HttpBody.jsonUnsafe(GenerateTokenRequest.make({ audience, duration })), - acceptJson: true - }).pipe(HttpClientRequest.bearerToken(authInfo.accessToken)) - - return yield* executeAuthenticatedRequest( - request, - endpoint, - HttpClientResponse.schemaBodyJson(GenerateTokenResponse) - ) - } - ) + const generateAccessToken = Effect.fn("Auth.generateAccessToken")(function* ({ + authInfo, + audience, + duration + }: { + readonly authInfo: AuthInfo + readonly audience?: ReadonlyArray | undefined + readonly duration?: TokenDuration | undefined + }) { + const endpoint = "/api/v1/auth/generate" + const request = HttpClientRequest.post(endpoint, { + body: HttpBody.jsonUnsafe(GenerateTokenRequest.make({ audience, duration })), + acceptJson: true + }).pipe(HttpClientRequest.bearerToken(authInfo.accessToken)) + + return yield* executeAuthenticatedRequest( + request, + endpoint, + HttpClientResponse.schemaBodyJson(GenerateTokenResponse) + ) + }) - const refreshAccessToken = Effect.fn("Auth.refreshAccessToken")( - function*(authInfo: AuthInfo) { - const endpoint = "/api/v1/auth/refresh" - const request = HttpClientRequest.post(endpoint, { - body: HttpBody.jsonUnsafe(RefreshTokenRequest.make({ + const refreshAccessToken = Effect.fn("Auth.refreshAccessToken")(function* (authInfo: AuthInfo) { + const endpoint = "/api/v1/auth/refresh" + const request = HttpClientRequest.post(endpoint, { + body: HttpBody.jsonUnsafe( + RefreshTokenRequest.make({ userId: authInfo.userId, refreshToken: authInfo.refreshToken - })), - acceptJson: true - }).pipe(HttpClientRequest.bearerToken(authInfo.accessToken)) - - const response = yield* executeAuthenticatedRequest( - request, - endpoint, - HttpClientResponse.schemaBodyJson(RefreshTokenResponse) - ) + }) + ), + acceptJson: true + }).pipe(HttpClientRequest.bearerToken(authInfo.accessToken)) - // Validate that the received user ID matches the cached user ID - if (response.user.id !== authInfo.userId) { - return yield* Effect.fail(AuthUserMismatchError.make({ + const response = yield* executeAuthenticatedRequest( + request, + endpoint, + HttpClientResponse.schemaBodyJson(RefreshTokenResponse) + ) + + // Validate that the received user ID matches the cached user ID + if (response.user.id !== authInfo.userId) { + return yield* Effect.fail( + AuthUserMismatchError.make({ code: "AUTH_USER_MISMATCH", message: `Expected user ID ${authInfo.userId} but received ${response.user.id}`, expectedUserId: authInfo.userId, receivedUserId: response.user.id - })) - } + }) + ) + } - const refreshedAuthInfo = yield* makeAuthInfo({ - accessToken: response.token, - refreshToken: response.refreshToken ?? Redacted.value(authInfo.refreshToken), - expiresIn: response.expiresIn, - userId: response.user.id, - accounts: response.user.accounts - }) + const refreshedAuthInfo = yield* makeAuthInfo({ + accessToken: response.token, + refreshToken: response.refreshToken ?? Redacted.value(authInfo.refreshToken), + expiresIn: response.expiresIn, + userId: response.user.id, + accounts: response.user.accounts + }) - // Reset the cached tokens - yield* setCachedAuthInfo(refreshedAuthInfo) + // Reset the cached tokens + yield* setCachedAuthInfo(refreshedAuthInfo) - return refreshedAuthInfo - } - ) + return refreshedAuthInfo + }) // ------------------------------------------------------------------------ // OAuth2 Token Verification @@ -618,76 +630,77 @@ const make = Effect.gen(function*() { const JWKS = Jose.createRemoteJWKSet(new URL("./.well-known/jwks.json", AUTH_PLATFORM_BASE_URL)) - const verifyAccessToken = Effect.fn("Auth.verifyAccessToken")( - function*(token: Redacted.Redacted, issuer: string) { - const result = yield* Effect.tryPromise({ - try: () => Jose.jwtVerify(Redacted.value(token), JWKS, { issuer }), - catch: (cause) => { - if (!(cause instanceof Jose.errors.JOSEError)) { + const verifyAccessToken = Effect.fn("Auth.verifyAccessToken")(function* ( + token: Redacted.Redacted, + issuer: string + ) { + const result = yield* Effect.tryPromise({ + try: () => Jose.jwtVerify(Redacted.value(token), JWKS, { issuer }), + catch: (cause) => { + if (!(cause instanceof Jose.errors.JOSEError)) { + return AuthVerifyTokenError.make({ + code: "AUTH_VERIFY_TOKEN_FAILED", + message: `Unknown verification error: ${String(cause)}`, + reason: "unknown", + claim: Option.none(), + cause: Option.some(cause) + }) + } + switch (cause.code) { + case "ERR_JWT_EXPIRED": + return AuthVerifyTokenError.make({ + code: "AUTH_VERIFY_TOKEN_FAILED", + message: `Token expired: ${cause.message}`, + reason: "expired", + claim: Option.fromNullishOr((cause as Jose.errors.JWTExpired).claim), + cause: Option.some(cause) + }) + case "ERR_JWS_SIGNATURE_VERIFICATION_FAILED": return AuthVerifyTokenError.make({ code: "AUTH_VERIFY_TOKEN_FAILED", - message: `Unknown verification error: ${String(cause)}`, + message: `Signature verification failed: ${cause.message}`, + reason: "invalid_signature", + claim: Option.none(), + cause: Option.some(cause) + }) + case "ERR_JWT_CLAIM_VALIDATION_FAILED": + return AuthVerifyTokenError.make({ + code: "AUTH_VERIFY_TOKEN_FAILED", + message: `Claim validation failed: ${(cause as Jose.errors.JWTClaimValidationFailed).claim} - ${ + (cause as Jose.errors.JWTClaimValidationFailed).reason + }`, + reason: "invalid_claims", + claim: Option.fromNullishOr((cause as Jose.errors.JWTClaimValidationFailed).claim), + cause: Option.some(cause) + }) + case "ERR_JWKS_NO_MATCHING_KEY": + case "ERR_JWKS_TIMEOUT": + return AuthVerifyTokenError.make({ + code: "AUTH_VERIFY_TOKEN_FAILED", + message: `JWKS error: ${cause.message}`, + reason: "jwks_error", + claim: Option.none(), + cause: Option.some(cause) + }) + default: + return AuthVerifyTokenError.make({ + code: "AUTH_VERIFY_TOKEN_FAILED", + message: `Verification error: ${cause.message}`, reason: "unknown", claim: Option.none(), cause: Option.some(cause) }) - } - switch (cause.code) { - case "ERR_JWT_EXPIRED": - return AuthVerifyTokenError.make({ - code: "AUTH_VERIFY_TOKEN_FAILED", - message: `Token expired: ${cause.message}`, - reason: "expired", - claim: Option.fromNullishOr((cause as Jose.errors.JWTExpired).claim), - cause: Option.some(cause) - }) - case "ERR_JWS_SIGNATURE_VERIFICATION_FAILED": - return AuthVerifyTokenError.make({ - code: "AUTH_VERIFY_TOKEN_FAILED", - message: `Signature verification failed: ${cause.message}`, - reason: "invalid_signature", - claim: Option.none(), - cause: Option.some(cause) - }) - case "ERR_JWT_CLAIM_VALIDATION_FAILED": - return AuthVerifyTokenError.make({ - code: "AUTH_VERIFY_TOKEN_FAILED", - message: `Claim validation failed: ${(cause as Jose.errors.JWTClaimValidationFailed).claim} - ${ - (cause as Jose.errors.JWTClaimValidationFailed).reason - }`, - reason: "invalid_claims", - claim: Option.fromNullishOr((cause as Jose.errors.JWTClaimValidationFailed).claim), - cause: Option.some(cause) - }) - case "ERR_JWKS_NO_MATCHING_KEY": - case "ERR_JWKS_TIMEOUT": - return AuthVerifyTokenError.make({ - code: "AUTH_VERIFY_TOKEN_FAILED", - message: `JWKS error: ${cause.message}`, - reason: "jwks_error", - claim: Option.none(), - cause: Option.some(cause) - }) - default: - return AuthVerifyTokenError.make({ - code: "AUTH_VERIFY_TOKEN_FAILED", - message: `Verification error: ${cause.message}`, - reason: "unknown", - claim: Option.none(), - cause: Option.some(cause) - }) - } } - }) - return result.payload - } - ) + } + }) + return result.payload + }) // ------------------------------------------------------------------------ // Cache Operations // ------------------------------------------------------------------------ - const getCachedAuthInfo = Effect.gen(function*() { + const getCachedAuthInfo = Effect.gen(function* () { const cacheResult = yield* kvs.get(AUTH_INFO_CACHE_KEY).pipe( // Treat "not found" as Option.none() before wrapping other errors Effect.catchIf( @@ -707,13 +720,13 @@ const make = Effect.gen(function*() { // Check if we need to refresh the token const needsRefresh = // Missing expiry field - refresh to populate it - Predicate.isNullish(cache.expiry) || + Predicate.isNullish(cache.expiry) // Missing accounts field - refresh to populate it - Predicate.isNullish(cache.accounts) || + || Predicate.isNullish(cache.accounts) // Token is expired - cache.expiry < now || + || cache.expiry < now // Token is expiring within 5 minutes - cache.expiry - now <= 5 * 60 * 1000 + || cache.expiry - now <= 5 * 60 * 1000 // If a refresh is required, perform the refresh request if (needsRefresh) { @@ -725,25 +738,20 @@ const make = Effect.gen(function*() { // Token is still valid, return as is return Option.some(cache) - }).pipe( - Effect.withSpan("AuthService.getCachedAuthInfo") - ) + }).pipe(Effect.withSpan("AuthService.getCachedAuthInfo")) - const setCachedAuthInfo = Effect.fn("Auth.setCachedAuthInfo")( - function*(authInfo: AuthInfo) { - yield* kvs.set(AUTH_INFO_CACHE_KEY, authInfo).pipe( - Effect.catchTags({ - SchemaError: (cause) => Effect.fail(makeAuthCacheError("write", cause)), - KeyValueStoreError: (cause) => Effect.fail(makeAuthCacheError("write", cause)) - }) - ) - } - ) + const setCachedAuthInfo = Effect.fn("Auth.setCachedAuthInfo")(function* (authInfo: AuthInfo) { + yield* kvs.set(AUTH_INFO_CACHE_KEY, authInfo).pipe( + Effect.catchTags({ + SchemaError: (cause) => Effect.fail(makeAuthCacheError("write", cause)), + KeyValueStoreError: (cause) => Effect.fail(makeAuthCacheError("write", cause)) + }) + ) + }) - const clearCachedAuthInfo = kvs.remove(AUTH_INFO_CACHE_KEY).pipe( - Effect.ignore, - Effect.withSpan("Auth.clearCachedAuthInfo") - ) + const clearCachedAuthInfo = kvs + .remove(AUTH_INFO_CACHE_KEY) + .pipe(Effect.ignore, Effect.withSpan("Auth.clearCachedAuthInfo")) return { createChallenge, @@ -758,11 +766,10 @@ const make = Effect.gen(function*() { } as const }) -export const layer: Layer.Layer< +export const layer: Layer.Layer = Layer.effect( Auth, - never, - HttpClient.HttpClient | KeyValueStore.KeyValueStore -> = Layer.effect(Auth, make) + make +) // ============================================================================= // Internal Utilities @@ -774,8 +781,10 @@ const extractErrorDescription = (response: HttpClientResponse.HttpClientResponse Effect.option, Effect.map( Option.flatMap((body) => - typeof body === "object" && body !== null && - "error_description" in body && typeof body.error_description === "string" + typeof body === "object" + && body !== null + && "error_description" in body + && typeof body.error_description === "string" ? Option.some(body.error_description) : Option.none() ) @@ -783,7 +792,7 @@ const extractErrorDescription = (response: HttpClientResponse.HttpClientResponse Effect.map(Option.getOrElse(() => "Failed to refresh token")) ) -const makeAuthInfo = Effect.fnUntraced(function*(params: { +const makeAuthInfo = Effect.fnUntraced(function* (params: { readonly accessToken: string readonly refreshToken: string readonly expiresIn: number @@ -791,9 +800,11 @@ const makeAuthInfo = Effect.fnUntraced(function*(params: { readonly accounts: ReadonlyArray }): Effect.fn.Return { const now = yield* DateTime.now - const expiry = DateTime.toEpochMillis(DateTime.add(now, { - seconds: params.expiresIn - })) + const expiry = DateTime.toEpochMillis( + DateTime.add(now, { + seconds: params.expiresIn + }) + ) const accessToken = AccessToken.make(params.accessToken) const refreshToken = RefreshToken.make(params.refreshToken) return AuthInfo.make({ diff --git a/packages/amp/src/cdc-stream/batch-store.ts b/packages/amp/src/cdc-stream/batch-store.ts index 71195a6..d27a34a 100644 --- a/packages/amp/src/cdc-stream/batch-store.ts +++ b/packages/amp/src/cdc-stream/batch-store.ts @@ -41,9 +41,7 @@ export interface BatchStoreService { * Lightweight operation — returns only IDs, not batch data. * Used to build the DeleteBatchIterator. */ - readonly seek: ( - range: TransactionIdRange - ) => Effect.Effect, BatchStoreError> + readonly seek: (range: TransactionIdRange) => Effect.Effect, BatchStoreError> /** * Load a single batch by transaction ID. @@ -61,15 +59,11 @@ export interface BatchStoreService { * Deletes all batches with IDs <= cutoff. Must be idempotent. * Best-effort — failures are logged but not fatal. */ - readonly prune: ( - cutoff: TransactionId - ) => Effect.Effect + readonly prune: (cutoff: TransactionId) => Effect.Effect } // ============================================================================= // Context.Tag // ============================================================================= -export class BatchStore extends Context.Service()( - "Amp/CdcStream/BatchStore" -) {} +export class BatchStore extends Context.Service()("Amp/CdcStream/BatchStore") {} diff --git a/packages/amp/src/cdc-stream/errors.ts b/packages/amp/src/cdc-stream/errors.ts index 53a583d..273d39d 100644 --- a/packages/amp/src/cdc-stream/errors.ts +++ b/packages/amp/src/cdc-stream/errors.ts @@ -13,13 +13,14 @@ import type { TransactionalStreamError } from "../transactional-stream/errors.ts /** * Error from BatchStore operations. */ -export class BatchStoreError extends Schema.TaggedErrorClass( - "Amp/CdcStream/BatchStoreError" -)("BatchStoreError", { - reason: Schema.String, - operation: Schema.Literals(["append", "seek", "load", "prune"]), - cause: Schema.optional(Schema.Defect()) -}) {} +export class BatchStoreError extends Schema.TaggedError("Amp/CdcStream/BatchStoreError")( + "BatchStoreError", + { + reason: Schema.String, + operation: Schema.Literals(["append", "seek", "load", "prune"]), + cause: Schema.optional(Schema.Defect()) + } +) {} // ============================================================================= // Combined Error Type @@ -28,6 +29,4 @@ export class BatchStoreError extends Schema.TaggedErrorClass( /** * Union of all CDC stream errors. */ -export type CdcStreamError = - | BatchStoreError - | TransactionalStreamError +export type CdcStreamError = BatchStoreError | TransactionalStreamError diff --git a/packages/amp/src/cdc-stream/memory-batch-store.ts b/packages/amp/src/cdc-stream/memory-batch-store.ts index c918d0c..03e6958 100644 --- a/packages/amp/src/cdc-stream/memory-batch-store.ts +++ b/packages/amp/src/cdc-stream/memory-batch-store.ts @@ -22,7 +22,7 @@ type BatchMap = ReadonlyMap // Implementation // ============================================================================= -const make = Effect.gen(function*() { +const make = Effect.gen(function* () { const mapRef = yield* Ref.make(new Map()) const append = (data: ReadonlyArray>, id: TransactionId) => @@ -39,10 +39,7 @@ const make = Effect.gen(function*() { }) ) - const load = (id: TransactionId) => - Ref.get(mapRef).pipe( - Effect.map((map) => map.get(id)) - ) + const load = (id: TransactionId) => Ref.get(mapRef).pipe(Effect.map((map) => map.get(id))) const prune = (cutoff: TransactionId) => Ref.update(mapRef, (map) => { diff --git a/packages/amp/src/cdc-stream/stream.ts b/packages/amp/src/cdc-stream/stream.ts index 170b005..489588c 100644 --- a/packages/amp/src/cdc-stream/stream.ts +++ b/packages/amp/src/cdc-stream/stream.ts @@ -57,9 +57,7 @@ export interface CdcStreamService { // Context.Tag // ============================================================================= -export class CdcStream extends Context.Service()( - "Amp/CdcStream" -) {} +export class CdcStream extends Context.Service()("Amp/CdcStream") {} // ============================================================================= // DeleteBatchIterator @@ -69,10 +67,7 @@ export class CdcStream extends Context.Service()( * Create a lazy batch iterator that loads batches one-by-one from the store. * Skips missing batches (watermark-only transactions). */ -const makeDeleteBatchIterator = ( - store: BatchStoreService, - ids: ReadonlyArray -): DeleteBatchIterator => { +const makeDeleteBatchIterator = (store: BatchStoreService, ids: ReadonlyArray): DeleteBatchIterator => { let cursor = 0 return { @@ -85,13 +80,9 @@ const makeDeleteBatchIterator = ( if (cursor >= ids.length) return Effect.succeed(undefined) const id = ids[cursor]! cursor++ - return store.load(id).pipe( - Effect.flatMap((batch) => - batch !== undefined - ? Effect.succeed([id, batch] as const) - : loop() - ) - ) + return store + .load(id) + .pipe(Effect.flatMap((batch) => (batch !== undefined ? Effect.succeed([id, batch] as const) : loop()))) } return loop() }) @@ -102,14 +93,14 @@ const makeDeleteBatchIterator = ( // Implementation // ============================================================================= -const make = Effect.gen(function*() { +const make = Effect.gen(function* () { const txStream = yield* TransactionalStream const batchStore = yield* BatchStore const streamCdc = (sql: string, options?: CdcStreamOptions) => { return txStream.streamTransactional(sql, options).pipe( Stream.mapEffect( - Effect.fnUntraced(function*([event, commit]): Effect.fn.Return< + Effect.fnUntraced(function* ([event, commit]): Effect.fn.Return< Option.Option, CdcStreamError > { @@ -149,11 +140,13 @@ const make = Effect.gen(function*() { // Handle batch pruning when retention window moves if (Option.isSome(event.prune)) { // Best-effort pruning - yield* batchStore.prune(event.prune.value).pipe( - Effect.catch((error) => - Effect.logWarning("Batch pruning failed (will retry on next watermark)", error) + yield* batchStore + .prune(event.prune.value) + .pipe( + Effect.catch((error) => + Effect.logWarning("Batch pruning failed (will retry on next watermark)", error) + ) ) - ) } // Watermarks are not exposed to CDC consumers // Still need to commit so the underlying TransactionalStream advances @@ -176,7 +169,7 @@ const make = Effect.gen(function*() { ): Effect.Effect => streamCdc(sql, options).pipe( Stream.runForEach( - Effect.fnUntraced(function*([event, commitHandle]) { + Effect.fnUntraced(function* ([event, commitHandle]) { yield* handler(event) yield* commitHandle.commit }) diff --git a/packages/amp/src/core/domain.ts b/packages/amp/src/core/domain.ts index 83e5592..f03c242 100644 --- a/packages/amp/src/core/domain.ts +++ b/packages/amp/src/core/domain.ts @@ -1,5 +1,4 @@ import * as Effect from "effect/Effect" -import * as Option from "effect/Option" import * as Schema from "effect/Schema" import * as SchemaGetter from "effect/SchemaGetter" import * as SchemaIssue from "effect/SchemaIssue" @@ -23,27 +22,25 @@ export type NonNegativeInt = typeof NonNegativeInt.Type * address is a unique, 42-character hexadecimal identifier (starting with `0x`) * used to send and receive funds. */ -export const Address = NonEmptyTrimmedString.check( - Schema.makeFilter((val) => isAddress(val)) -).pipe( - Schema.brand("Amp/Models/Address") -).annotate({ identifier: "Address" }) +export const Address = NonEmptyTrimmedString.check(Schema.makeFilter((val) => isAddress(val))) + .pipe(Schema.brand("Amp/Models/Address")) + .annotate({ identifier: "Address" }) export type Address = typeof Address.Type /** * A branded type representing an OAuth2 access token. */ -export const AccessToken = NonEmptyTrimmedString.pipe( - Schema.brand("Amp/Models/AccessToken") -).annotate({ identifier: "AccessToken" }) +export const AccessToken = NonEmptyTrimmedString.pipe(Schema.brand("Amp/Models/AccessToken")).annotate({ + identifier: "AccessToken" +}) export type AccessToken = typeof AccessToken.Type /** * A branded type representing an OAuth2 refresh token. */ -export const RefreshToken = NonEmptyTrimmedString.pipe( - Schema.brand("Amp/Models/RefreshToken") -).annotate({ identifier: "RefreshToken" }) +export const RefreshToken = NonEmptyTrimmedString.pipe(Schema.brand("Amp/Models/RefreshToken")).annotate({ + identifier: "RefreshToken" +}) export type RefreshToken = typeof RefreshToken.Type const TOKEN_DURATION_REGEX = @@ -53,29 +50,25 @@ const TOKEN_DURATION_REGEX = * A branded type representing the duration an OAuth2 access token should be * valid for. */ -export const TokenDuration = NonEmptyTrimmedString.check( - Schema.isPattern(TOKEN_DURATION_REGEX) -).pipe( - Schema.brand("TokenDuration") -).annotate({ - identifier: "TokenDuration", - examples: [ - "7 days" as TokenDuration, - "30 days" as TokenDuration, - "1 hour" as TokenDuration, - "1 year" as TokenDuration - ] -}) +export const TokenDuration = NonEmptyTrimmedString.check(Schema.isPattern(TOKEN_DURATION_REGEX)) + .pipe(Schema.brand("TokenDuration")) + .annotate({ + identifier: "TokenDuration", + examples: [ + "7 days" as TokenDuration, + "30 days" as TokenDuration, + "1 hour" as TokenDuration, + "1 year" as TokenDuration + ] + }) export type TokenDuration = typeof TokenDuration.Type /** * A branded type representing the identifier for an authenticated user. */ -export const UserId = NonEmptyTrimmedString.check( - Schema.isPattern(/^(c[a-z0-9]{24}|did:privy:c[a-z0-9]{24})$/) -).pipe( - Schema.brand("Amp/Models/UserId") -).annotate({ identifier: "UserId" }) +export const UserId = NonEmptyTrimmedString.check(Schema.isPattern(/^(c[a-z0-9]{24}|did:privy:c[a-z0-9]{24})$/)) + .pipe(Schema.brand("Amp/Models/UserId")) + .annotate({ identifier: "UserId" }) export type UserId = typeof UserId.Type /** @@ -85,9 +78,7 @@ export const AuthInfo = Schema.Struct({ accessToken: Schema.Redacted(AccessToken), refreshToken: Schema.Redacted(RefreshToken), userId: UserId, - accounts: Schema.optional(Schema.Array( - Schema.Union([NonEmptyTrimmedString, Address]) - )), + accounts: Schema.optional(Schema.Array(Schema.Union([NonEmptyTrimmedString, Address]))), expiry: Schema.optional(Schema.Int.check(Schema.isGreaterThan(0))) }).annotate({ identifier: "AuthInfo" }) export type AuthInfo = typeof AuthInfo.Type @@ -95,9 +86,7 @@ export type AuthInfo = typeof AuthInfo.Type /** * Represents a block number. */ -export const BlockNumber = NonNegativeInt.pipe( - Schema.brand("Amp/Models/BlockNumber") -).annotate({ +export const BlockNumber = NonNegativeInt.pipe(Schema.brand("Amp/Models/BlockNumber")).annotate({ identifier: "BlockNumber", description: "A block number" }) @@ -106,25 +95,21 @@ export type BlockNumber = typeof BlockNumber.Type /** * Represents a block hash. */ -export const BlockHash = NonEmptyTrimmedString.check( - Schema.isPattern(/^0x[a-z0-9]{64}/) -).pipe( - Schema.brand("Amp/Models/BlockHash") -).annotate({ identifier: "BlockHash" }) +export const BlockHash = NonEmptyTrimmedString.check(Schema.isPattern(/^0x[a-z0-9]{64}/)) + .pipe(Schema.brand("Amp/Models/BlockHash")) + .annotate({ identifier: "BlockHash" }) export type BlockHash = typeof BlockHash.Type /** * Represents a blockchain network. */ -export const Network = Schema.String.check( - Schema.isLowercased() -).pipe( - Schema.brand("Amp/Models/Network") -).annotate({ - title: "Network", - description: "a blockchain network", - examples: ["mainnet" as Network] -}) +export const Network = Schema.String.check(Schema.isLowercased()) + .pipe(Schema.brand("Amp/Models/Network")) + .annotate({ + title: "Network", + description: "a blockchain network", + examples: ["mainnet" as Network] + }) export type Network = typeof Network.Type /** @@ -147,10 +132,12 @@ export const BlockRange = Schema.Struct({ * The hash associated with the parent of the start block, if present */ prevHash: Schema.optional(BlockHash) -}).pipe(Schema.encodeKeys({ prevHash: "prev_hash" })).annotate({ - identifier: "BlockRange", - description: "A range of blocks on a given network" }) + .pipe(Schema.encodeKeys({ prevHash: "prev_hash" })) + .annotate({ + identifier: "BlockRange", + description: "A range of blocks on a given network" + }) export type BlockRange = typeof BlockRange.Type /** @@ -166,10 +153,12 @@ export const RecordBatchMetadata = Schema.Struct({ * Indicates whether this is the final record batch associated to the ranges. */ rangesComplete: Schema.Boolean -}).pipe(Schema.encodeKeys({ rangesComplete: "ranges_complete" })).annotate({ - identifier: "RecordBatchMetadata", - description: "Metadata carrying information about the block ranges covered by this record batch" }) + .pipe(Schema.encodeKeys({ rangesComplete: "ranges_complete" })) + .annotate({ + identifier: "RecordBatchMetadata", + description: "Metadata carrying information about the block ranges covered by this record batch" + }) export type RecordBatchMetadata = typeof RecordBatchMetadata.Type /** @@ -180,18 +169,16 @@ export type RecordBatchMetadata = typeof RecordBatchMetadata.Type export const RecordBatchMetadataFromUint8Array = Schema.Uint8Array.pipe( Schema.decodeTo( Schema.fromJsonString(RecordBatchMetadata), - SchemaTransformation.transformOrFail({ + SchemaTransformation.transformEffect({ encode: (decoded) => Effect.try({ try: () => new TextEncoder().encode(decoded), - catch: () => - new SchemaIssue.InvalidValue(Option.some(decoded), { message: "Failed to encode record batch metadata" }) + catch: () => new SchemaIssue.InvalidValue({ message: "Failed to encode record batch metadata" }, decoded) }), decode: (encoded) => Effect.try({ try: () => new TextDecoder().decode(encoded), - catch: () => - new SchemaIssue.InvalidValue(Option.some(encoded), { message: "Failed to decode record batch metadata" }) + catch: () => new SchemaIssue.InvalidValue({ message: "Failed to decode record batch metadata" }, encoded) }) }) ) @@ -203,58 +190,66 @@ export type RecordBatchMetadataFromUint8Array = typeof RecordBatchMetadataFromUi * * If not specified, defaults to `"_"`. */ -export const DatasetNamespace = Schema.NonEmptyString.check( - Schema.isPattern(/^[a-z0-9_]+$/) -).pipe( - Schema.brand("Amp/Models/Address") -).annotate({ - identifier: "DatasetNamespace", - description: "The namespace or owner of the dataset. If not specified, defaults to \"_\". " + - "Must contain only lowercase letters, digits, and underscores.", - examples: [ - "edgeandnode" as DatasetNamespace, - "0xdeadbeef" as DatasetNamespace, - "my_org" as DatasetNamespace, - "_" as DatasetNamespace - ] -}) +export const DatasetNamespace = Schema.NonEmptyString.check(Schema.isPattern(/^[a-z0-9_]+$/)) + .pipe(Schema.brand("Amp/Models/DatasetNamespace")) + .annotate({ + identifier: "DatasetNamespace", + description: + 'The namespace or owner of the dataset. If not specified, defaults to "_". ' + + "Must contain only lowercase letters, digits, and underscores.", + examples: [ + "edgeandnode" as DatasetNamespace, + "0xdeadbeef" as DatasetNamespace, + "my_org" as DatasetNamespace, + "_" as DatasetNamespace + ] + }) export type DatasetNamespace = typeof DatasetNamespace.Type /** * Represents the name of a dataset. */ -export const DatasetName = Schema.NonEmptyString.check( - Schema.isPattern(/^[a-z_][a-z0-9_]*$/) -).pipe( - Schema.brand("Amp/Models/DatasetName") -).annotate({ - identifier: "DatasetName", - description: "The name of the dataset. Must start with a lowercase letter or underscore, " + - "followed by lowercase letters, digits, or underscores.", - examples: ["uniswap" as DatasetName] -}) +export const DatasetName = Schema.NonEmptyString.check(Schema.isPattern(/^[a-z_][a-z0-9_]*$/)) + .pipe(Schema.brand("Amp/Models/DatasetName")) + .annotate({ + identifier: "DatasetName", + description: + "The name of the dataset. Must start with a lowercase letter or underscore, " + + "followed by lowercase letters, digits, or underscores.", + examples: ["uniswap" as DatasetName] + }) export type DatasetName = typeof DatasetName.Type /** - * Represents the kind of the dataset. - * - * Must be one of `"manifest"`, `"evm-rpc"`, `"eth-beacon"`, or `"firehose"`. + * Represents the kind of a raw (extraction) dataset. */ -export const DatasetKind = Schema.Literals([ - "manifest", - "evm-rpc", +export const RawDatasetKind = Schema.Literals([ "eth-beacon", - "firehose" -]).pipe(Schema.brand("Amp/Models/DatasetKind")).annotate({ - identifier: "DatasetKind", - description: "The kind of the dataset.", - examples: [ - "manifest" as DatasetKind, - "evm-rpc" as DatasetKind, - "eth-beacon" as DatasetKind, - "firehose" as DatasetKind - ] + "bittensor", + "evm-rpc", + "solana", + "tempo", + "bitcoin-rpc", + "stellar", + "phaser" +]).annotate({ + identifier: "RawDatasetKind", + description: "The kind of a raw (extraction) dataset." }) +export type RawDatasetKind = typeof RawDatasetKind.Type + +/** + * Represents the kind of the dataset. + * + * Must be `"manifest"` (derived), `"static"`, or one of the raw dataset kinds. + */ +export const DatasetKind = Schema.Literals(["manifest", "static", ...RawDatasetKind.literals]) + .pipe(Schema.brand("Amp/Models/DatasetKind")) + .annotate({ + identifier: "DatasetKind", + description: "The kind of the dataset.", + examples: ["manifest" as DatasetKind, "static" as DatasetKind, "evm-rpc" as DatasetKind, "solana" as DatasetKind] + }) export type DatasetKind = typeof DatasetKind.Type /** @@ -264,58 +259,52 @@ export const DatasetVersion = Schema.String.check( Schema.isPattern( /^(?0|[1-9]\d*)\.(?0|[1-9]\d*)\.(?0|[1-9]\d*)(?:-(?(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ ) -).pipe( - Schema.brand("Amp/Models/DatasetVersion") -).annotate({ - identifier: "DatasetVersion", - description: "The semantic version number for the dataset.", - examples: [ - "1.0.0" as DatasetVersion, - "1.0.1" as DatasetVersion, - "1.1.0" as DatasetVersion, - "1.0.0-dev123" as DatasetVersion, - "1.0.0+1234567890" as DatasetVersion - ] -}) +) + .pipe(Schema.brand("Amp/Models/DatasetVersion")) + .annotate({ + identifier: "DatasetVersion", + description: "The semantic version number for the dataset.", + examples: [ + "1.0.0" as DatasetVersion, + "1.0.1" as DatasetVersion, + "1.1.0" as DatasetVersion, + "1.0.0-dev123" as DatasetVersion, + "1.0.0+1234567890" as DatasetVersion + ] + }) export type DatasetVersion = typeof DatasetVersion.Type /** * Represents the 32-byte SHA-256 hash for the dataset. */ -export const DatasetHash = Schema.String.check( - Schema.isPattern(/^[0-9a-fA-F]{64}$/) -).pipe( - Schema.brand("Amp/Models/DatasetHash") -).annotate({ - identifier: "DatasetHash", - description: "A 32-byte SHA-256 hash (64 characters) for the dataset.", - examples: ["b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" as DatasetHash] -}) +export const DatasetHash = Schema.String.check(Schema.isPattern(/^[0-9a-fA-F]{64}$/)) + .pipe(Schema.brand("Amp/Models/DatasetHash")) + .annotate({ + identifier: "DatasetHash", + description: "A 32-byte SHA-256 hash (64 characters) for the dataset.", + examples: ["b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" as DatasetHash] + }) export type DatasetHash = typeof DatasetHash.Type /** * Represents a tag for a dataset version. */ -export const DatasetTag = Schema.Literals(["latest", "dev"]).pipe( - Schema.brand("Amp/Models/DatasetTag") -).annotate({ - identifier: "DatasetTag", - description: "A tag for a dataset version.", - examples: ["latest" as DatasetTag, "dev" as DatasetTag] -}) +export const DatasetTag = Schema.Literals(["latest", "dev"]) + .pipe(Schema.brand("Amp/Models/DatasetTag")) + .annotate({ + identifier: "DatasetTag", + description: "A tag for a dataset version.", + examples: ["latest" as DatasetTag, "dev" as DatasetTag] + }) export type DatasetTag = typeof DatasetTag.Type /** * Represents a dataset revision reference, which can be either a semver tag, * a 64-character hexadecimal hash, `"latest"`, or `"dev"`. */ -export const DatasetRevision = Schema.Union([ - DatasetVersion, - DatasetHash, - DatasetTag -]).annotate({ +export const DatasetRevision = Schema.Union([DatasetVersion, DatasetHash, DatasetTag]).annotate({ identifier: "DatasetRevision", - description: "A dataset revision reference (semver tag, 64 character hexadecimal hash, \"latest\", or \"dev\").", + description: 'A dataset revision reference (semver tag, 64 character hexadecimal hash, "latest", or "dev").', examples: [ DatasetVersion.make("1.0.0"), DatasetHash.make("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"), @@ -337,13 +326,10 @@ export const DatasetReferenceString = Schema.String.check( Schema.isPattern(/^[a-z0-9_]+\/[a-z_][a-z0-9_]*@.+$/) ).annotate({ identifier: "DatasetReferenceString", - description: "A dataset reference as a string in the format `/@`, " + - "where revision is a semver version, hash, \"latest\", or \"dev\"", - examples: [ - "edgeandnode/mainnet@1.0.0", - "edgeandnode/mainnet@latest", - "edgeandnode/mainnet@dev" - ] + description: + "A dataset reference as a string in the format `/@`, " + + 'where revision is a semver version, hash, "latest", or "dev"', + examples: ["edgeandnode/mainnet@1.0.0", "edgeandnode/mainnet@latest", "edgeandnode/mainnet@dev"] }) export type DatasetReferenceString = typeof DatasetReferenceString.Type @@ -370,7 +356,7 @@ const decodeDatasetReference = SchemaParser.decodeEffect(DatasetReference) export const DatasetReferenceFromString = Schema.String.pipe( Schema.decodeTo(DatasetReference, { encode: SchemaGetter.transform((ref) => `${ref.namespace}/${ref.name}@${ref.revision}`), - decode: SchemaGetter.transformOrFail((str) => { + decode: SchemaGetter.transformEffect((str) => { const at = str.lastIndexOf("@") const slash = str.indexOf("/") @@ -430,9 +416,7 @@ export type DatasetReadme = typeof DatasetReadme.Type /** * Represents additional description and details about the dataset. */ -export const DatasetDescription = Schema.String.check( - Schema.isMaxLength(1024) -).annotate({ +export const DatasetDescription = Schema.String.check(Schema.isMaxLength(1024)).annotate({ identifier: "DatasetDescription", title: "Description", description: "Additional description and details about the dataset." @@ -460,13 +444,10 @@ export type DatasetKeyword = typeof DatasetKeyword.Type export const DatasetSource = Schema.String.annotate({ identifier: "DatasetSource", title: "Source", - description: "Source of the dataset data. For example, the block or logs table that powers the " + - "dataset, or the 0x address of the smart contract being queried.", - examples: [ - "eth_mainnet_rpc.logs", - "arbitrum_one_rpc.blocks", - "0xc944e90c64b2c07662a292be6244bdf05cda44a7" - ] + description: + "Source of the dataset data. For example, the block or logs table that powers the " + + "dataset, or the 0x address of the smart contract being queried.", + examples: ["eth_mainnet_rpc.logs", "arbitrum_one_rpc.blocks", "0xc944e90c64b2c07662a292be6244bdf05cda44a7"] }) export type DatasetSource = typeof DatasetSource.Type @@ -559,18 +540,9 @@ export const DatasetConfig = Schema.Struct({ license: Schema.optional(DatasetLicense), private: Schema.optional(Schema.Boolean), startBlock: Schema.optional(Schema.Number), - dependencies: Schema.Record( - Schema.String, - DatasetReferenceFromString - ), - tables: Schema.optional(Schema.Record( - Schema.String, - TableDefinition - )), - functions: Schema.optional(Schema.Record( - Schema.String, - FunctionDefinition - )) + dependencies: Schema.Record(Schema.String, DatasetReferenceFromString), + tables: Schema.optional(Schema.Record(Schema.String, TableDefinition)), + functions: Schema.optional(Schema.Record(Schema.String, FunctionDefinition)) }).annotate({ identifier: "DatasetConfig", description: "Configuration associated with a dataset." @@ -584,10 +556,12 @@ export const TableInfo = Schema.Struct({ name: Schema.String, network: Network, activeLocation: Schema.optional(Schema.String) -}).pipe(Schema.encodeKeys({ activeLocation: "active_location" })).annotate({ - identifier: "TableInfo", - description: "Information about a table." }) + .pipe(Schema.encodeKeys({ activeLocation: "active_location" })) + .annotate({ + identifier: "TableInfo", + description: "Information about a table." + }) export type TableInfo = typeof TableInfo.Type /** @@ -651,23 +625,11 @@ export const TableSchema = Schema.Struct({ }) export type TableSchema = typeof TableSchema.Type -/** - * Represents a table schema with associated networks. - */ -export const TableSchemaWithNetworks = Schema.Struct({ - schema: TableSchema, - networks: Schema.Array(Schema.String) -}).annotate({ - identifier: "TableSchemaWithNetworks", - description: "A table schema with associated networks." -}) -export type TableSchemaWithNetworks = typeof TableSchemaWithNetworks.Type - /** * Represents the input SQL for a table. */ export const TableInput = Schema.Struct({ - sql: Schema.String + sql: Schema.String.check(Schema.isNonEmpty()) }).annotate({ identifier: "TableInput", description: "Input SQL for a table." @@ -675,47 +637,68 @@ export const TableInput = Schema.Struct({ export type TableInput = typeof TableInput.Type /** - * Represents a table. + * Represents the bloom filter configuration for a table column. + */ +export const BloomFilterColumnConfig = Schema.Struct({ + column: Schema.String, + ndv: Schema.optional(NonNegativeInt) +}).annotate({ + identifier: "BloomFilterColumnConfig", + description: "Bloom filter configuration for a table column." +}) +export type BloomFilterColumnConfig = typeof BloomFilterColumnConfig.Type + +/** + * Represents a table in a derived dataset. */ export const Table = Schema.Struct({ input: TableInput, schema: TableSchema, - network: Network -}).annotate({ - identifier: "Table", - description: "A table." + bloomFilterColumns: Schema.optional(Schema.Array(BloomFilterColumnConfig)) }) + .pipe(Schema.encodeKeys({ bloomFilterColumns: "bloom_filter_columns" })) + .annotate({ + identifier: "Table", + description: "A table in a derived dataset." + }) export type Table = typeof Table.Type /** - * Represents a table for a raw dataset. + * Represents the policy used to select segments for a raw dataset table. */ -export const RawDatasetTable = Schema.Struct({ - schema: TableSchema, - network: Network -}).annotate({ - identifier: "RawDatasetTable", - description: "A table for a raw dataset." +export const SegmentSelectionPolicy = Schema.Literals(["hash-linked", "ordered"]).annotate({ + identifier: "SegmentSelectionPolicy", + description: "The policy used to select segments for a raw dataset table." }) -export type RawDatasetTable = typeof RawDatasetTable.Type +export type SegmentSelectionPolicy = typeof SegmentSelectionPolicy.Type /** - * Represents the output schema for a query. + * Represents a table for a raw dataset. */ -export const OutputSchema = Schema.Struct({ +export const RawDatasetTable = Schema.Struct({ schema: TableSchema, - networks: Schema.Array(Schema.String) -}).annotate({ - identifier: "OutputSchema", - description: "The output schema for a query." -}) -export type OutputSchema = typeof OutputSchema.Type + network: Network, + sortedBy: Schema.optional(Schema.Array(Schema.String)), + bloomFilterColumns: Schema.optional(Schema.Array(BloomFilterColumnConfig)), + segmentSelectionPolicy: Schema.optional(SegmentSelectionPolicy) +}) + .pipe( + Schema.encodeKeys({ + sortedBy: "sorted_by", + bloomFilterColumns: "bloom_filter_columns", + segmentSelectionPolicy: "segment_selection_policy" + }) + ) + .annotate({ + identifier: "RawDatasetTable", + description: "A table for a raw dataset." + }) +export type RawDatasetTable = typeof RawDatasetTable.Type /** - * Represents information associated with a function. + * Represents information associated with a function in a derived dataset. */ export const FunctionManifest = Schema.Struct({ - name: Schema.String, source: FunctionSource, inputTypes: Schema.Array(Schema.String), outputType: Schema.String @@ -730,69 +713,96 @@ export type FunctionManifest = typeof FunctionManifest.Type */ export const DatasetDerived = Schema.Struct({ kind: Schema.Literal("manifest"), - startBlock: Schema.optional(Schema.NullOr(Schema.Number)), - dependencies: Schema.Record(Schema.String, DatasetReferenceFromString), - tables: Schema.Record(Schema.String, Table), - functions: Schema.Record(Schema.String, FunctionManifest) -}).pipe(Schema.encodeKeys({ startBlock: "start_block" })).annotate({ + dependencies: Schema.optional(Schema.Record(Schema.String, DatasetReferenceFromString)), + tables: Schema.optional(Schema.Record(Schema.String, Table)), + functions: Schema.optional(Schema.Record(Schema.String, FunctionManifest)) +}).annotate({ identifier: "DatasetDerived", - description: "A SQL-based derived datasets." + description: "A SQL-based derived dataset." }) export type DatasetDerived = typeof DatasetDerived.Type /** - * Represents an EVM RPC extraction dataset. + * Represents a selector for periodic state snapshots of a raw dataset. + * + * Either a bare selector string, or a selector with an explicit block interval. + */ +export const StateSnapshotSelector = Schema.Union([ + Schema.String, + Schema.Struct({ + selector: Schema.String, + blockInterval: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)) + }).pipe(Schema.encodeKeys({ blockInterval: "block_interval" })) +]).annotate({ + identifier: "StateSnapshotSelector", + description: "A selector for periodic state snapshots." +}) +export type StateSnapshotSelector = typeof StateSnapshotSelector.Type + +/** + * Represents the state snapshot configuration of a raw dataset. */ -export const DatasetEvmRpc = Schema.Struct({ - kind: Schema.Literal("evm-rpc"), - network: Network, - startBlock: Schema.optional(Schema.Number), - finalizedBlocksOnly: Schema.optional(Schema.Boolean), - tables: Schema.Record(Schema.String, RawDatasetTable) -}).pipe(Schema.encodeKeys({ - startBlock: "start_block", - finalizedBlocksOnly: "finalized_blocks_only" -})).annotate({ - identifier: "DatasetEvmRpc", - description: "An EVM RPC extraction dataset." +export const StateSnapshots = Schema.Struct({ + selectors: Schema.optional(Schema.Array(StateSnapshotSelector)) +}).annotate({ + identifier: "StateSnapshots", + description: "State snapshot configuration of a raw dataset." }) -export type DatasetEvmRpc = typeof DatasetEvmRpc.Type +export type StateSnapshots = typeof StateSnapshots.Type /** - * Represents an ETH beacon extraction dataset. + * Represents a raw (extraction) dataset, such as an EVM RPC, ETH beacon, or + * Solana dataset. */ -export const DatasetEthBeacon = Schema.Struct({ - kind: Schema.Literal("eth-beacon"), +export const DatasetRaw = Schema.Struct({ + kind: RawDatasetKind, network: Network, - startBlock: Schema.optional(Schema.Number), + startBlock: Schema.optional(NonNegativeInt), finalizedBlocksOnly: Schema.optional(Schema.Boolean), + stateSnapshots: Schema.optional(StateSnapshots), tables: Schema.Record(Schema.String, RawDatasetTable) -}).pipe(Schema.encodeKeys({ - startBlock: "start_block", - finalizedBlocksOnly: "finalized_blocks_only" -})).annotate({ - identifier: "DatasetEthBeacon", - description: "An ETH beacon extraction dataset." }) -export type DatasetEthBeacon = typeof DatasetEthBeacon.Type + .pipe( + Schema.encodeKeys({ + startBlock: "start_block", + finalizedBlocksOnly: "finalized_blocks_only", + stateSnapshots: "state_snapshots" + }) + ) + .annotate({ + identifier: "DatasetRaw", + description: "A raw (extraction) dataset." + }) +export type DatasetRaw = typeof DatasetRaw.Type /** - * Represents a Firehose extraction dataset. + * Represents a table for a static dataset, backed by a data file. */ -export const DatasetFirehose = Schema.Struct({ - kind: Schema.Literal("firehose"), - network: Network, - startBlock: Schema.optional(Schema.Number), - finalizedBlocksOnly: Schema.optional(Schema.Boolean), - tables: Schema.Record(Schema.String, RawDatasetTable) -}).pipe(Schema.encodeKeys({ - startBlock: "start_block", - finalizedBlocksOnly: "finalized_blocks_only" -})).annotate({ - identifier: "DatasetFirehose", - description: "A Firehose extraction dataset." +export const StaticDatasetTable = Schema.Struct({ + path: Schema.String.check(Schema.isNonEmpty()), + schema: TableSchema, + sortedBy: Schema.optional(Schema.Array(Schema.String)), + format: Schema.Literal("csv"), + hasHeader: Schema.Boolean +}) + .pipe(Schema.encodeKeys({ sortedBy: "sorted_by", hasHeader: "has_header" })) + .annotate({ + identifier: "StaticDatasetTable", + description: "A table for a static dataset, backed by a data file." + }) +export type StaticDatasetTable = typeof StaticDatasetTable.Type + +/** + * Represents a static dataset, backed by checked-in data files (e.g. CSV). + */ +export const DatasetStatic = Schema.Struct({ + kind: Schema.Literal("static"), + tables: Schema.Record(Schema.String, StaticDatasetTable) +}).annotate({ + identifier: "DatasetStatic", + description: "A static dataset, backed by checked-in data files." }) -export type DatasetFirehose = typeof DatasetFirehose.Type +export type DatasetStatic = typeof DatasetStatic.Type /** * Union type representing any dataset manifest kind. @@ -802,24 +812,16 @@ export type DatasetFirehose = typeof DatasetFirehose.Type * * Supported kinds: * - DatasetDerived (kind: "manifest") - SQL-based derived datasets - * - DatasetEvmRpc (kind: "evm-rpc") - EVM RPC extraction datasets - * - DatasetEthBeacon (kind: "eth-beacon") - ETH beacon extraction datasets - * - DatasetFirehose (kind: "firehose") - Firehose extraction datasets - */ -export const DatasetManifest = Schema.Union([ - DatasetDerived, - DatasetEvmRpc, - DatasetEthBeacon, - DatasetFirehose -]) + * - DatasetRaw (kind: see {@link RawDatasetKind}) - raw extraction datasets + * - DatasetStatic (kind: "static") - datasets backed by checked-in data files + */ +export const DatasetManifest = Schema.Union([DatasetDerived, DatasetRaw, DatasetStatic]) export type DatasetManifest = typeof DatasetManifest.Type /** * Represents the unique identifier for a job. */ -export const JobId = Schema.Number.pipe( - Schema.brand("Amp/Models/JobId") -).annotate({ +export const JobId = Schema.Number.pipe(Schema.brand("Amp/Models/JobId")).annotate({ identifier: "JobId", description: "The unique identifier for a job." }) @@ -835,14 +837,16 @@ export const JobStatus = Schema.Literals([ "STOPPED", "STOP_REQUESTED", "STOPPING", - "FAILED", + "ERROR", + "CANCELLED", + "FATAL", "UNKNOWN" -]).pipe( - Schema.brand("Amp/Models/JobStatus") -).annotate({ - identifier: "JobStatus", - description: "The status of a job." -}) +]) + .pipe(Schema.brand("Amp/Models/JobStatus")) + .annotate({ + identifier: "JobStatus", + description: "The status of a job." + }) export type JobStatus = typeof JobStatus.Type /** @@ -850,17 +854,23 @@ export type JobStatus = typeof JobStatus.Type */ export const JobInfo = Schema.Struct({ id: JobId, + idempotencyKey: Schema.String, status: JobStatus, createdAt: Schema.DateTimeUtc, updatedAt: Schema.DateTimeUtc, nodeId: Schema.String, descriptor: Schema.Any -}).pipe(Schema.encodeKeys({ - createdAt: "created_at", - updatedAt: "updated_at", - nodeId: "node_id" -})).annotate({ - identifier: "JobInfo", - description: "Information about a job." }) + .pipe( + Schema.encodeKeys({ + idempotencyKey: "idempotency_key", + createdAt: "created_at", + updatedAt: "updated_at", + nodeId: "node_id" + }) + ) + .annotate({ + identifier: "JobInfo", + description: "Information about a job." + }) export type JobInfo = typeof JobInfo.Type diff --git a/packages/amp/src/internal/arrow-flight-ipc/Decoder.ts b/packages/amp/src/internal/arrow-flight-ipc/Decoder.ts index 11f3d54..adc81f8 100644 --- a/packages/amp/src/internal/arrow-flight-ipc/Decoder.ts +++ b/packages/amp/src/internal/arrow-flight-ipc/Decoder.ts @@ -69,10 +69,7 @@ export class DictionaryRegistry { if (existing) { // Append delta values to existing dictionary const combinedValues = [...existing.values, ...dictionary.values] - this.dictionaries.set( - dictionary.id, - new DecodedDictionary(dictionary.id, combinedValues, dictionary.valueType) - ) + this.dictionaries.set(dictionary.id, new DecodedDictionary(dictionary.id, combinedValues, dictionary.valueType)) } else { // No existing dictionary, just set it this.dictionaries.set(dictionary.id, dictionary) @@ -114,10 +111,7 @@ export class DictionaryRegistry { * Dictionary batches need to know the value type, which is stored in the * schema field's type definition. */ -const findFieldByDictionaryId = ( - fields: ReadonlyArray, - dictionaryId: bigint -): ArrowField | undefined => { +const findFieldByDictionaryId = (fields: ReadonlyArray, dictionaryId: bigint): ArrowField | undefined => { for (const field of fields) { if (field.dictionaryEncoding?.id === dictionaryId) { return field @@ -208,10 +202,10 @@ export const decodeRecordBatch = ( if (recordBatch.compression) { const codecName = recordBatch.compression.codec === 0 ? "LZ4_FRAME" : "ZSTD" throw new Error( - `Compressed record batches are not currently supported. ` + - `This batch uses ${codecName} compression. ` + - `To process this data, the server should be configured to send uncompressed data, ` + - `or compression support needs to be added to this library.` + `Compressed record batches are not currently supported. ` + + `This batch uses ${codecName} compression. ` + + `To process this data, the server should be configured to send uncompressed data, ` + + `or compression support needs to be added to this library.` ) } @@ -258,9 +252,7 @@ export const decodeRecordBatch = ( * Dictionary-encoded fields store integer indices, so they have the same * buffer layout as an integer type: [validity, data]. */ -const getBufferTypesForDictionaryEncoding = ( - _indexType: IntType -): ReadonlyArray => { +const getBufferTypesForDictionaryEncoding = (_indexType: IntType): ReadonlyArray => { // Dictionary-encoded columns always have: validity bitmap + index data // The index type determines the bit width but not the buffer layout return [ diff --git a/packages/amp/src/internal/arrow-flight-ipc/Errors.ts b/packages/amp/src/internal/arrow-flight-ipc/Errors.ts index 6baaeb3..911a071 100644 --- a/packages/amp/src/internal/arrow-flight-ipc/Errors.ts +++ b/packages/amp/src/internal/arrow-flight-ipc/Errors.ts @@ -5,7 +5,7 @@ import * as Schema from "effect/Schema" * * @internal */ -export class InvalidArrowDataTypeError extends Schema.TaggedErrorClass( +export class InvalidArrowDataTypeError extends Schema.TaggedError( "Amp/InvalidArrowDataTypeError" )("InvalidArrowDataTypeError", { type: Schema.Number, @@ -18,11 +18,12 @@ export class InvalidArrowDataTypeError extends Schema.TaggedErrorClass( - "Amp/InvalidMessageTypeError" -)("InvalidMessageTypeError", { - value: Schema.Number -}) { +export class InvalidMessageTypeError extends Schema.TaggedError("Amp/InvalidMessageTypeError")( + "InvalidMessageTypeError", + { + value: Schema.Number + } +) { override get message(): string { return `Received invalid value for Arrow Flight message type: ${this.value} ` } @@ -34,13 +35,14 @@ export class InvalidMessageTypeError extends Schema.TaggedErrorClass( - "Amp/MissingFieldError" -)("MissingFieldError", { - fieldName: Schema.String, - fieldIndex: Schema.Number, - tableOffset: Schema.Number -}) { +export class MissingFieldError extends Schema.TaggedError("Amp/MissingFieldError")( + "MissingFieldError", + { + fieldName: Schema.String, + fieldIndex: Schema.Number, + tableOffset: Schema.Number + } +) { override get message(): string { return `Failed to find message field '${this.fieldName}' at index ${this.fieldIndex} (offset: ${this.tableOffset})` } @@ -52,7 +54,7 @@ export class MissingFieldError extends Schema.TaggedErrorClass( +export class UnexpectedMessageTypeError extends Schema.TaggedError( "Amp/UnexpectedMessageTypeError" )("UnexpectedMessageTypeError", { expected: Schema.Number, diff --git a/packages/amp/src/internal/arrow-flight-ipc/Json.ts b/packages/amp/src/internal/arrow-flight-ipc/Json.ts index a84a39f..90f05c2 100644 --- a/packages/amp/src/internal/arrow-flight-ipc/Json.ts +++ b/packages/amp/src/internal/arrow-flight-ipc/Json.ts @@ -29,7 +29,9 @@ const uint8ArrayToBase64 = (bytes: Uint8Array): string => { } const uint8ArrayToHex = (bytes: Uint8Array): string => { - return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("") + return Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join("") } const convertValue = ( @@ -41,22 +43,22 @@ const convertValue = ( return opts.bigIntHandling === "string" ? value.toString() : opts.bigIntHandling === "number" - ? Number(value) - : value + ? Number(value) + : value } if (value instanceof Date) { return opts.dateHandling === "iso" ? value.toISOString() : opts.dateHandling === "timestamp" - ? value.getTime() - : value + ? value.getTime() + : value } if (value instanceof Uint8Array) { return opts.binaryHandling === "base64" ? uint8ArrayToBase64(value) : opts.binaryHandling === "hex" - ? uint8ArrayToHex(value) - : Array.from(value) + ? uint8ArrayToHex(value) + : Array.from(value) } if (Array.isArray(value)) { return value.map((v) => convertValue(v, opts)) diff --git a/packages/amp/src/internal/arrow-flight-ipc/Readers.ts b/packages/amp/src/internal/arrow-flight-ipc/Readers.ts index a974148..d3a40c4 100644 --- a/packages/amp/src/internal/arrow-flight-ipc/Readers.ts +++ b/packages/amp/src/internal/arrow-flight-ipc/Readers.ts @@ -88,8 +88,8 @@ const bigintToNumberSafe = (value: bigint, context?: string): number => { if (value > MAX_SAFE_INTEGER || value < MIN_SAFE_INTEGER) { const contextMsg = context ? ` (${context})` : "" throw new RangeError( - `Value ${value}${contextMsg} exceeds safe integer range for Number conversion. ` + - `Use BigInt-based APIs for values outside the range [${Number.MIN_SAFE_INTEGER}, ${Number.MAX_SAFE_INTEGER}].` + `Value ${value}${contextMsg} exceeds safe integer range for Number conversion. ` + + `Use BigInt-based APIs for values outside the range [${Number.MIN_SAFE_INTEGER}, ${Number.MAX_SAFE_INTEGER}].` ) } return Number(value) @@ -1041,9 +1041,10 @@ export const readTimeValues = ( const values = new Array(length) for (let i = 0; i < length; i++) { if (validityChecker(i)) { - const raw = bitWidth === 32 - ? view.getInt32(i * BYTES_PER_INT32, true) - : bigintToNumberSafe(view.getBigInt64(i * BYTES_PER_INT64, true), `time[${i}]`) + const raw = + bitWidth === 32 + ? view.getInt32(i * BYTES_PER_INT32, true) + : bigintToNumberSafe(view.getBigInt64(i * BYTES_PER_INT64, true), `time[${i}]`) values[i] = raw * multiplier } else { values[i] = null @@ -1342,12 +1343,10 @@ const readStructValues = ( const readMapValues = ( column: DecodedColumn, registry?: DictionaryRegistry -): ReadonlyArray< - ReadonlyArray<{ - readonly key: unknown - readonly value: unknown - }> | null -> => { +): ReadonlyArray | null> => { const { buffers, children, node } = column const length = Number(node.length) const vc = createValidityChecker(buffers[0]) @@ -1385,9 +1384,8 @@ const readUnionValues = ( const { buffers, children, node } = column const length = Number(node.length) const typeIdBuffer = buffers[0] - const offsetView = mode === "DENSE" - ? new DataView(buffers[1].buffer, buffers[1].byteOffset, buffers[1].byteLength) - : null + const offsetView = + mode === "DENSE" ? new DataView(buffers[1].buffer, buffers[1].byteOffset, buffers[1].byteLength) : null const childValuesArrays = children.map((c) => readColumnValues(c, registry)) const typeIdToChild = new Map(typeIds.map((id, idx) => [id, idx])) const values = new Array(length) @@ -1473,18 +1471,13 @@ const readDictionaryEncodedValues = ( const dictionary = registry.get(dictionaryEncoding.id) if (!dictionary) { throw new Error( - `Dictionary with ID ${dictionaryEncoding.id} not found. ` + - `Make sure dictionary batches are processed before record batches that reference them.` + `Dictionary with ID ${dictionaryEncoding.id} not found. ` + + `Make sure dictionary batches are processed before record batches that reference them.` ) } // Read the indices - const indices = readDictionaryIndices( - buffers[0], - buffers[1], - length, - dictionaryEncoding.indexType - ) + const indices = readDictionaryIndices(buffers[0], buffers[1], length, dictionaryEncoding.indexType) // Look up values in the dictionary const dictValues = dictionary.values @@ -1558,10 +1551,7 @@ const readDictionaryEncodedValues = ( * * @see {@link https://arrow.apache.org/docs/format/Columnar.html | Arrow Columnar Format} */ -export const readColumnValues = ( - column: DecodedColumn, - registry?: DictionaryRegistry -): ReadonlyArray => { +export const readColumnValues = (column: DecodedColumn, registry?: DictionaryRegistry): ReadonlyArray => { const { buffers, field, node } = column const length = Number(node.length) const type = field.type @@ -1570,8 +1560,8 @@ export const readColumnValues = ( if (field.dictionaryEncoding) { if (!registry) { throw new Error( - `Column "${field.name}" is dictionary-encoded but no dictionary registry was provided. ` + - `Pass a DictionaryRegistry to readColumnValues for dictionary-encoded columns.` + `Column "${field.name}" is dictionary-encoded but no dictionary registry was provided. ` + + `Pass a DictionaryRegistry to readColumnValues for dictionary-encoded columns.` ) } return readDictionaryEncodedValues(column, field.dictionaryEncoding, registry) diff --git a/packages/amp/src/internal/arrow-flight-ipc/RecordBatch.ts b/packages/amp/src/internal/arrow-flight-ipc/RecordBatch.ts index 96208d0..0d40274 100644 --- a/packages/amp/src/internal/arrow-flight-ipc/RecordBatch.ts +++ b/packages/amp/src/internal/arrow-flight-ipc/RecordBatch.ts @@ -40,7 +40,7 @@ export const BufferType = { /** Type IDs for union types */ TYPE_ID: 4 } as const -export type BufferType = typeof BufferType[keyof typeof BufferType] +export type BufferType = (typeof BufferType)[keyof typeof BufferType] // ============================================================================= // Record Batch Types @@ -136,7 +136,7 @@ export const CompressionCodec = { LZ4_FRAME: 0, ZSTD: 1 } as const -export type CompressionCodec = typeof CompressionCodec[keyof typeof CompressionCodec] +export type CompressionCodec = (typeof CompressionCodec)[keyof typeof CompressionCodec] export const BodyCompressionMethod = { /** @@ -144,7 +144,7 @@ export const BodyCompressionMethod = { */ BUFFER: 0 } as const -export type BodyCompressionMethod = typeof BodyCompressionMethod[keyof typeof BodyCompressionMethod] +export type BodyCompressionMethod = (typeof BodyCompressionMethod)[keyof typeof BodyCompressionMethod] export class BodyCompression { /** @@ -332,11 +332,7 @@ export class DecodedRecordBatch { */ readonly columns: ReadonlyArray - constructor( - schema: ArrowSchema, - numRows: bigint, - columns: ReadonlyArray - ) { + constructor(schema: ArrowSchema, numRows: bigint, columns: ReadonlyArray) { this.schema = schema this.numRows = numRows this.columns = columns @@ -426,7 +422,7 @@ export class DecodedColumn { * 3: bodyLength (Int64) * 4: custom_metadata (vector offset) */ -export const parseRecordBatch = Effect.fn(function*(flightData: FlightData) { +export const parseRecordBatch = Effect.fn(function* (flightData: FlightData) { const reader = new FlatBufferReader(flightData.dataHeader) // The flatbuffer root table offset is at position 0 @@ -476,7 +472,7 @@ export const parseRecordBatch = Effect.fn(function*(flightData: FlightData) { * 2: buffers ([Buffer]) - buffer locations * 3: compression (BodyCompression) - optional */ -export const parseRecordBatchTable = Effect.fn(function*(reader: FlatBufferReader, offset: number) { +export const parseRecordBatchTable = Effect.fn(function* (reader: FlatBufferReader, offset: number) { // Parse length const lengthPosition = reader.getFieldPosition(offset, 0) const length = Predicate.isNotNull(lengthPosition) ? reader.readInt64(lengthPosition) : 0n @@ -540,12 +536,12 @@ export const parseRecordBatchTable = Effect.fn(function*(reader: FlatBufferReade const parseBodyCompression = (reader: FlatBufferReader, offset: number): BodyCompression => { const codecPosition = reader.getFieldPosition(offset, 0) const codec = Predicate.isNotNull(codecPosition) - ? reader.readInt8(codecPosition) as CompressionCodec + ? (reader.readInt8(codecPosition) as CompressionCodec) : CompressionCodec.LZ4_FRAME const methodPosition = reader.getFieldPosition(offset, 1) const method = Predicate.isNotNull(methodPosition) - ? reader.readInt8(methodPosition) as BodyCompressionMethod + ? (reader.readInt8(methodPosition) as BodyCompressionMethod) : BodyCompressionMethod.BUFFER return new BodyCompression(codec, method) @@ -562,7 +558,7 @@ const parseBodyCompression = (reader: FlatBufferReader, offset: number): BodyCom * 1: data (RecordBatch) - the dictionary values * 2: isDelta (Bool) - whether this is a delta update */ -export const parseDictionaryBatch = Effect.fn(function*(flightData: FlightData) { +export const parseDictionaryBatch = Effect.fn(function* (flightData: FlightData) { const reader = new FlatBufferReader(flightData.dataHeader) // The flatbuffer root table offset is at position 0 @@ -611,7 +607,7 @@ export const parseDictionaryBatch = Effect.fn(function*(flightData: FlightData) * 1: data (RecordBatch) - the dictionary values as a record batch * 2: isDelta (Bool) - whether this is a delta update (default: false) */ -const parseDictionaryBatchTable = Effect.fn(function*(reader: FlatBufferReader, offset: number) { +const parseDictionaryBatchTable = Effect.fn(function* (reader: FlatBufferReader, offset: number) { // Parse id const idPosition = reader.getFieldPosition(offset, 0) const id = Predicate.isNotNull(idPosition) ? reader.readInt64(idPosition) : 0n @@ -630,9 +626,7 @@ const parseDictionaryBatchTable = Effect.fn(function*(reader: FlatBufferReader, // Parse isDelta (default: false) const isDeltaPosition = reader.getFieldPosition(offset, 2) - const isDelta = Predicate.isNotNull(isDeltaPosition) - ? reader.readUint8(isDeltaPosition) !== 0 - : false + const isDelta = Predicate.isNotNull(isDeltaPosition) ? reader.readUint8(isDeltaPosition) !== 0 : false return new DictionaryBatch(id, data, isDelta) }) @@ -645,7 +639,7 @@ const parseDictionaryBatchTable = Effect.fn(function*(reader: FlatBufferReader, * Returns `true` if the provided `FlightData` header data buffer contains a * record batch message, otherwise returns `false`. */ -export const isRecordBatchMessage = Effect.fn(function*(flightData: FlightData) { +export const isRecordBatchMessage = Effect.fn(function* (flightData: FlightData) { const messageType = yield* getMessageType(flightData) return messageType === MessageHeaderType.RECORD_BATCH }) @@ -654,7 +648,7 @@ export const isRecordBatchMessage = Effect.fn(function*(flightData: FlightData) * Returns `true` if the provided `FlightData` header data buffer contains a * dictionary batch message, otherwise returns `false`. */ -export const isDictionaryBatchMessage = Effect.fn(function*(flightData: FlightData) { +export const isDictionaryBatchMessage = Effect.fn(function* (flightData: FlightData) { const messageType = yield* getMessageType(flightData) return messageType === MessageHeaderType.DICTIONARY_BATCH }) diff --git a/packages/amp/src/internal/arrow-flight-ipc/Schema.ts b/packages/amp/src/internal/arrow-flight-ipc/Schema.ts index 81a17ce..9c60623 100644 --- a/packages/amp/src/internal/arrow-flight-ipc/Schema.ts +++ b/packages/amp/src/internal/arrow-flight-ipc/Schema.ts @@ -32,7 +32,7 @@ export const MessageHeaderType = { TENSOR: 4, SPARSE_TENSOR: 5 } as const -export type MessageHeaderType = typeof MessageHeaderType[keyof typeof MessageHeaderType] +export type MessageHeaderType = (typeof MessageHeaderType)[keyof typeof MessageHeaderType] /** * A partial representation of the `FlightData` type. @@ -70,33 +70,33 @@ export const ArrowDataTypeEnum = { LARGE_UTF8: 20, LARGE_LIST: 21 } as const -export type ArrowDataTypeEnum = typeof ArrowDataTypeEnum[keyof typeof ArrowDataTypeEnum] +export type ArrowDataTypeEnum = (typeof ArrowDataTypeEnum)[keyof typeof ArrowDataTypeEnum] export const DateUnit = { DAY: 0, MILLISECOND: 1 } as const -export type DateUnit = typeof DateUnit[keyof typeof DateUnit] +export type DateUnit = (typeof DateUnit)[keyof typeof DateUnit] export const Endianness = { LITTLE: 0, BIG: 1 } as const -export type Endianness = typeof Endianness[keyof typeof Endianness] +export type Endianness = (typeof Endianness)[keyof typeof Endianness] export const IntervalUnit = { YEAR_MONTH: 0, DAY_TIME: 1, MONTH_DAY_NANO: 2 } as const -export type IntervalUnit = typeof IntervalUnit[keyof typeof IntervalUnit] +export type IntervalUnit = (typeof IntervalUnit)[keyof typeof IntervalUnit] export const Precision = { HALF: 0, SINGLE: 1, DOUBLE: 2 } as const -export type Precision = typeof Precision[keyof typeof Precision] +export type Precision = (typeof Precision)[keyof typeof Precision] export const TimeUnit = { SECOND: 0, @@ -104,13 +104,13 @@ export const TimeUnit = { MICROSECOND: 2, NANOSECOND: 3 } as const -export type TimeUnit = typeof TimeUnit[keyof typeof TimeUnit] +export type TimeUnit = (typeof TimeUnit)[keyof typeof TimeUnit] export const UnionMode = { SPARSE: 0, DENSE: 1 } as const -export type UnionMode = typeof UnionMode[keyof typeof UnionMode] +export type UnionMode = (typeof UnionMode)[keyof typeof UnionMode] // ============================================================================= // Schema Types @@ -120,11 +120,7 @@ export class ArrowSchema { readonly fields: ReadonlyArray readonly metadata: ReadonlyMap readonly endianness: Endianness - constructor( - fields: ReadonlyArray, - metadata: ReadonlyMap, - endianness: Endianness - ) { + constructor(fields: ReadonlyArray, metadata: ReadonlyMap, endianness: Endianness) { this.fields = fields this.metadata = metadata this.endianness = endianness @@ -389,7 +385,7 @@ export class UnionType { /** * Returns the Arrow Flight message type from the `FlightData` header. */ -export const getMessageType = Effect.fn(function*(flightData: FlightData) { +export const getMessageType = Effect.fn(function* (flightData: FlightData) { const reader = new FlatBufferReader(flightData.dataHeader) // The flatbuffer root table offset is at position 0 @@ -440,7 +436,7 @@ export const getMessageType = Effect.fn(function*(flightData: FlightData) { * 3: bodyLength (Int64) * 4: custom_metadata (vector offset) */ -export const parseSchema = Effect.fn(function*(flightData: FlightData) { +export const parseSchema = Effect.fn(function* (flightData: FlightData) { const reader = new FlatBufferReader(flightData.dataHeader) // The flatbuffer root table offset is at position 0 @@ -490,10 +486,7 @@ export const parseSchema = Effect.fn(function*(flightData: FlightData) { * 2: custom_metadata (vector of KeyValue) * 3: features (vector of Int64) - optional */ -const parseSchemaTable = Effect.fn(function*( - reader: FlatBufferReader, - offset: number -) { +const parseSchemaTable = Effect.fn(function* (reader: FlatBufferReader, offset: number) { // Parse the endianness const endiannessPosition = reader.getFieldPosition(offset, 0) const endianness = Predicate.isNotNull(endiannessPosition) @@ -535,22 +528,15 @@ const parseSchemaTable = Effect.fn(function*( * 5: children (vector of Field) * 6: custom_metadata (vector of KeyValue) */ -const parseField: ( - reader: FlatBufferReader, - offset: number -) => Effect.Effect = Effect.fn( - function*(reader, offset) { +const parseField: (reader: FlatBufferReader, offset: number) => Effect.Effect = + Effect.fn(function* (reader, offset) { // Parse field name const namePosition = reader.getFieldPosition(offset, 0) - const name = Predicate.isNotNull(namePosition) - ? reader.readString(namePosition) - : "" + const name = Predicate.isNotNull(namePosition) ? reader.readString(namePosition) : "" // Parse field nullability const nullabilityPosition = reader.getFieldPosition(offset, 1) - const nullable = Predicate.isNotNull(nullabilityPosition) - ? reader.readUint8(nullabilityPosition) !== 0 - : false + const nullable = Predicate.isNotNull(nullabilityPosition) ? reader.readUint8(nullabilityPosition) !== 0 : false // Parse type const typeEnumPosition = reader.getFieldPosition(offset, 2) @@ -558,9 +544,7 @@ const parseField: ( ? (reader.readUint8(typeEnumPosition) as ArrowDataTypeEnum) : ArrowDataTypeEnum.NONE const typePosition = reader.getFieldPosition(offset, 3) - const typeOffset = Predicate.isNotNull(typePosition) - ? reader.readOffset(typePosition) - : 0 + const typeOffset = Predicate.isNotNull(typePosition) ? reader.readOffset(typePosition) : 0 const type = yield* parseType(reader, typeEnum, typeOffset) // Parse dictionary encoding @@ -592,25 +576,13 @@ const parseField: ( parseKeyValueVector(reader, metadataPosition, metadata) } - return new ArrowField( - name, - type, - nullable, - metadata, - children, - dictionaryEncoding - ) - } -) + return new ArrowField(name, type, nullable, metadata, children, dictionaryEncoding) + }) /** * Parse type union based on the type enum value. */ -const parseType = Effect.fn(function*( - reader: FlatBufferReader, - typeEnum: ArrowDataTypeEnum, - offset: number -) { +const parseType = Effect.fn(function* (reader: FlatBufferReader, typeEnum: ArrowDataTypeEnum, offset: number) { switch (typeEnum) { case ArrowDataTypeEnum.NULL: { return NullType @@ -693,14 +665,10 @@ const parseType = Effect.fn(function*( */ const parseIntType = (reader: FlatBufferReader, offset: number): IntType => { const bitWidthPosition = reader.getFieldPosition(offset, 0) - const bitWidth = Predicate.isNotNull(bitWidthPosition) - ? (reader.readInt32(bitWidthPosition) as IntBitWidth) - : 32 + const bitWidth = Predicate.isNotNull(bitWidthPosition) ? (reader.readInt32(bitWidthPosition) as IntBitWidth) : 32 const isSignedPosition = reader.getFieldPosition(offset, 1) - const isSigned = Predicate.isNotNull(isSignedPosition) - ? reader.readUint8(isSignedPosition) !== 0 - : true + const isSigned = Predicate.isNotNull(isSignedPosition) ? reader.readUint8(isSignedPosition) !== 0 : true return new IntType(bitWidth, isSigned) } @@ -711,10 +679,7 @@ const parseIntType = (reader: FlatBufferReader, offset: number): IntType => { * The structure of the FloatingPoint vtable is as follows: * 0: precision (Precision enum) */ -const parseFloatingPointType = ( - reader: FlatBufferReader, - offset: number -): FloatingPointType => { +const parseFloatingPointType = (reader: FlatBufferReader, offset: number): FloatingPointType => { const precisionPosition = reader.getFieldPosition(offset, 0) const precisionEnum = Predicate.isNotNull(precisionPosition) ? (reader.readInt16(precisionPosition) as Precision) @@ -731,24 +696,15 @@ const parseFloatingPointType = ( * 1: scale (Int32) * 2: bitWidth (Int32) */ -const parseDecimalType = ( - reader: FlatBufferReader, - offset: number -): DecimalType => { +const parseDecimalType = (reader: FlatBufferReader, offset: number): DecimalType => { const precisionPosition = reader.getFieldPosition(offset, 0) - const precision = Predicate.isNotNull(precisionPosition) - ? reader.readInt32(precisionPosition) - : 0 + const precision = Predicate.isNotNull(precisionPosition) ? reader.readInt32(precisionPosition) : 0 const scalePosition = reader.getFieldPosition(offset, 1) - const scale = Predicate.isNotNull(scalePosition) - ? reader.readInt32(scalePosition) - : 0 + const scale = Predicate.isNotNull(scalePosition) ? reader.readInt32(scalePosition) : 0 const bitWidthPosition = reader.getFieldPosition(offset, 2) - const bitWidth = Predicate.isNotNull(bitWidthPosition) - ? reader.readInt32(bitWidthPosition) - : 128 + const bitWidth = Predicate.isNotNull(bitWidthPosition) ? reader.readInt32(bitWidthPosition) : 128 return new DecimalType(precision, scale, bitWidth) } @@ -761,9 +717,7 @@ const parseDecimalType = ( */ const parseFixedSizeBinaryType = (reader: FlatBufferReader, offset: number): FixedSizeBinaryType => { const byteWidthPosition = reader.getFieldPosition(offset, 0) - const byteWidth = Predicate.isNotNull(byteWidthPosition) - ? reader.readInt32(byteWidthPosition) - : 0 + const byteWidth = Predicate.isNotNull(byteWidthPosition) ? reader.readInt32(byteWidthPosition) : 0 return new FixedSizeBinaryType(byteWidth) } @@ -797,9 +751,7 @@ const parseTimeType = (reader: FlatBufferReader, offset: number): TimeType => { : TimeUnit.MILLISECOND const bitWidthPosition = reader.getFieldPosition(offset, 1) - const bitWidth = Predicate.isNotNull(bitWidthPosition) - ? (reader.readInt32(bitWidthPosition) as TimeBitWidth) - : 32 + const bitWidth = Predicate.isNotNull(bitWidthPosition) ? (reader.readInt32(bitWidthPosition) as TimeBitWidth) : 32 return new TimeType(unitEnum, bitWidth) } @@ -811,19 +763,14 @@ const parseTimeType = (reader: FlatBufferReader, offset: number): TimeType => { * 0: unit (TimeUnit enum) * 1: timezone (string) */ -const parseTimestampType = ( - reader: FlatBufferReader, - offset: number -): TimestampType => { +const parseTimestampType = (reader: FlatBufferReader, offset: number): TimestampType => { const unitPosition = reader.getFieldPosition(offset, 0) const unitEnum = Predicate.isNotNull(unitPosition) ? (reader.readInt16(unitPosition) as TimeUnit) : TimeUnit.MICROSECOND const timezonePosition = reader.getFieldPosition(offset, 1) - const timezone = Predicate.isNotNull(timezonePosition) - ? reader.readString(timezonePosition) - : null + const timezone = Predicate.isNotNull(timezonePosition) ? reader.readString(timezonePosition) : null return new TimestampType(unitEnum, timezone) } @@ -834,10 +781,7 @@ const parseTimestampType = ( * The structure of the Interval vtable is as follows: * 0: unit (IntervalUnit enum) */ -const parseIntervalType = ( - reader: FlatBufferReader, - offset: number -): IntervalType => { +const parseIntervalType = (reader: FlatBufferReader, offset: number): IntervalType => { const unitPosition = reader.getFieldPosition(offset, 0) const unitEnum = Predicate.isNotNull(unitPosition) ? (reader.readInt16(unitPosition) as IntervalUnit) @@ -852,10 +796,7 @@ const parseIntervalType = ( * The structure of the Duration vtable is as follows: * 0: unit (TimeUnit enum) */ -const parseDurationType = ( - reader: FlatBufferReader, - offset: number -): DurationType => { +const parseDurationType = (reader: FlatBufferReader, offset: number): DurationType => { const unitPosition = reader.getFieldPosition(offset, 0) const unitEnum = Predicate.isNotNull(unitPosition) ? (reader.readInt16(unitPosition) as TimeUnit) @@ -870,14 +811,9 @@ const parseDurationType = ( * The structure of the FixedSizeList vtable is as follows: * 0: listSize (Int32) */ -const parseFixedSizeListType = ( - reader: FlatBufferReader, - offset: number -): FixedSizeListType => { +const parseFixedSizeListType = (reader: FlatBufferReader, offset: number): FixedSizeListType => { const listSizePosition = reader.getFieldPosition(offset, 0) - const listSize = Predicate.isNotNull(listSizePosition) - ? reader.readInt32(listSizePosition) - : 0 + const listSize = Predicate.isNotNull(listSizePosition) ? reader.readInt32(listSizePosition) : 0 return new FixedSizeListType(listSize) } @@ -890,9 +826,7 @@ const parseFixedSizeListType = ( */ const parseMapType = (reader: FlatBufferReader, offset: number): MapType => { const keysSortedPosition = reader.getFieldPosition(offset, 0) - const keysSorted = Predicate.isNotNull(keysSortedPosition) - ? reader.readUint8(keysSortedPosition) !== 0 - : false + const keysSorted = Predicate.isNotNull(keysSortedPosition) ? reader.readUint8(keysSortedPosition) !== 0 : false return new MapType(keysSorted) } @@ -904,14 +838,9 @@ const parseMapType = (reader: FlatBufferReader, offset: number): MapType => { * 0: mode (UnionMode enum) * 1: typeIds (vector of Int32) */ -const parseUnionType = ( - reader: FlatBufferReader, - offset: number -): UnionType => { +const parseUnionType = (reader: FlatBufferReader, offset: number): UnionType => { const modePosition = reader.getFieldPosition(offset, 0) - const modeEnum = Predicate.isNotNull(modePosition) - ? (reader.readInt16(modePosition) as UnionMode) - : UnionMode.SPARSE + const modeEnum = Predicate.isNotNull(modePosition) ? (reader.readInt16(modePosition) as UnionMode) : UnionMode.SPARSE const typeIds: Array = [] const typeIdsPosition = reader.getFieldPosition(offset, 1) @@ -934,14 +863,9 @@ const parseUnionType = ( * 1: indexType (Int table) * 2: isOrdered (Bool) */ -const parseDictionaryEncoding = ( - reader: FlatBufferReader, - offset: number -): DictionaryEncoding => { +const parseDictionaryEncoding = (reader: FlatBufferReader, offset: number): DictionaryEncoding => { const idPosition = reader.getFieldPosition(offset, 0) - const id = Predicate.isNotNull(idPosition) - ? reader.readInt64(idPosition) - : 0n + const id = Predicate.isNotNull(idPosition) ? reader.readInt64(idPosition) : 0n const indexTypePosition = reader.getFieldPosition(offset, 1) let indexType: IntType @@ -953,9 +877,7 @@ const parseDictionaryEncoding = ( } const isOrderedPosition = reader.getFieldPosition(offset, 2) - const isOrdered = Predicate.isNotNull(isOrderedPosition) - ? reader.readUint8(isOrderedPosition) !== 0 - : false + const isOrdered = Predicate.isNotNull(isOrderedPosition) ? reader.readUint8(isOrderedPosition) !== 0 : false return new DictionaryEncoding(id, indexType, isOrdered) } @@ -963,11 +885,7 @@ const parseDictionaryEncoding = ( /** * Parses a KeyValue vector into a Map. */ -const parseKeyValueVector = ( - reader: FlatBufferReader, - pos: number, - map: Map -): void => { +const parseKeyValueVector = (reader: FlatBufferReader, pos: number, map: Map): void => { const vectorOffset = reader.readOffset(pos) const itemCount = reader.readVectorLength(vectorOffset) @@ -981,9 +899,7 @@ const parseKeyValueVector = ( if (Predicate.isNotNull(keyPosition)) { const key = reader.readString(keyPosition) - const value = Predicate.isNotNull(valuePosition) - ? reader.readString(valuePosition) - : "" + const value = Predicate.isNotNull(valuePosition) ? reader.readString(valuePosition) : "" map.set(key, value) } } @@ -997,7 +913,7 @@ const parseKeyValueVector = ( * Returns `true` if the provided `FlightData` header data buffer contains a * schema message, otherwise returns `false`. */ -export const isSchemaMessage = Effect.fn(function*(flightData: FlightData) { +export const isSchemaMessage = Effect.fn(function* (flightData: FlightData) { const messageType = yield* getMessageType(flightData) return messageType === MessageHeaderType.SCHEMA }) diff --git a/packages/amp/src/internal/pkce.ts b/packages/amp/src/internal/pkce.ts index b1a5256..d63e5f3 100644 --- a/packages/amp/src/internal/pkce.ts +++ b/packages/amp/src/internal/pkce.ts @@ -36,7 +36,7 @@ const getRandomValues = (size: number) => crypto.getRandomValues(new Uint8Array( const random = (size: number) => { const mask = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" /// Ensure an even distribution of randomly selected values from the mask - const evenDistCutoff = Math.pow(2, 8) - Math.pow(2, 8) % mask.length + const evenDistCutoff = Math.pow(2, 8) - (Math.pow(2, 8) % mask.length) let result = "" while (result.length < size) { @@ -61,10 +61,7 @@ const generateVerifier = (length: number): string => random(length) * Generate a PKCE code challenge from a code verifier. */ export const generateChallenge = async (code_verifier: string) => { - const buffer = await crypto.subtle.digest( - "SHA-256", - new TextEncoder().encode(code_verifier) - ) + const buffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(code_verifier)) // Generate base64url string. `btoa` is deprecated in Node.js but is used here // for web browser compatibility (which has no good replacement yet, see also // https://github.com/whatwg/html/issues/6811) @@ -79,15 +76,13 @@ export const generateChallenge = async (code_verifier: string) => { * * The `length` must be in the range of 43-128 (defaults to 43). */ -export const pkceChallenge = Effect.fnUntraced(function*(length?: number) { +export const pkceChallenge = Effect.fnUntraced(function* (length?: number) { if (!length) { length = 43 } if (length < 43 || length > 128) { - return yield* Effect.die( - `Expected a length between 43 and 128. Received ${length}.` - ) + return yield* Effect.die(`Expected a length between 43 and 128. Received ${length}.`) } const verifier = generateVerifier(length) diff --git a/packages/amp/src/protocol-stream/errors.ts b/packages/amp/src/protocol-stream/errors.ts index af5ecd1..40a4f42 100644 --- a/packages/amp/src/protocol-stream/errors.ts +++ b/packages/amp/src/protocol-stream/errors.ts @@ -18,7 +18,7 @@ import * as Schema from "effect/Schema" * * Each batch should contain at most one range per network. */ -export class DuplicateNetworkError extends Schema.TaggedErrorClass( +export class DuplicateNetworkError extends Schema.TaggedError( "Amp/ProtocolStream/DuplicateNetworkError" )("DuplicateNetworkError", { /** @@ -36,7 +36,7 @@ export class DuplicateNetworkError extends Schema.TaggedErrorClass( +export class NetworkCountChangedError extends Schema.TaggedError( "Amp/ProtocolStream/NetworkCountChangedError" )("NetworkCountChangedError", { /** @@ -58,7 +58,7 @@ export class NetworkCountChangedError extends Schema.TaggedErrorClass( +export class UnexpectedNetworkError extends Schema.TaggedError( "Amp/ProtocolStream/UnexpectedNetworkError" )("UnexpectedNetworkError", { /** @@ -76,7 +76,7 @@ export class UnexpectedNetworkError extends Schema.TaggedErrorClass 0) must have a prevHash to enable hash chain validation. */ -export class MissingPrevHashError extends Schema.TaggedErrorClass( +export class MissingPrevHashError extends Schema.TaggedError( "Amp/ProtocolStream/MissingPrevHashError" )("MissingPrevHashError", { /** @@ -98,7 +98,7 @@ export class MissingPrevHashError extends Schema.TaggedErrorClass( +export class InvalidPrevHashError extends Schema.TaggedError( "Amp/ProtocolStream/InvalidPrevHashError" )("InvalidPrevHashError", { /** @@ -117,7 +117,7 @@ export class InvalidPrevHashError extends Schema.TaggedErrorClass( +export class HashMismatchOnConsecutiveBlocksError extends Schema.TaggedError( "Amp/ProtocolStream/HashMismatchOnConsecutiveBlocksError" )("HashMismatchOnConsecutiveBlocksError", { /** @@ -144,14 +144,15 @@ export class HashMismatchOnConsecutiveBlocksError extends Schema.TaggedErrorClas * A backwards jump (incoming.start < prev.end + 1) indicates a reorg, which * requires different hashes. If hashes match, it's an invalid protocol state. */ -export class InvalidReorgError extends Schema.TaggedErrorClass( - "Amp/ProtocolStream/InvalidReorgError" -)("InvalidReorgError", { - /** - * The network where the invalid reorg was detected. - */ - network: Schema.String -}) { +export class InvalidReorgError extends Schema.TaggedError("Amp/ProtocolStream/InvalidReorgError")( + "InvalidReorgError", + { + /** + * The network where the invalid reorg was detected. + */ + network: Schema.String + } +) { override get message(): string { return `Invalid reorg detected on network ${this.network}: backwards jump with matching hashes` } @@ -162,9 +163,7 @@ export class InvalidReorgError extends Schema.TaggedErrorClass prev.end + 1) are always protocol violations. */ -export class GapError extends Schema.TaggedErrorClass( - "Amp/ProtocolStream/GapError" -)("GapError", { +export class GapError extends Schema.TaggedError("Amp/ProtocolStream/GapError")("GapError", { /** * The network where the gap was detected. */ @@ -203,7 +202,7 @@ export type ValidationError = /** * Represents a validation error wrapped for the ProtocolStream service. */ -export class ProtocolValidationError extends Schema.TaggedErrorClass( +export class ProtocolValidationError extends Schema.TaggedError( "Amp/ProtocolStream/ProtocolValidationError" )("ProtocolValidationError", { /** @@ -220,7 +219,7 @@ export class ProtocolValidationError extends Schema.TaggedErrorClass( +export class ProtocolArrowFlightError extends Schema.TaggedError( "Amp/ProtocolStream/ProtocolArrowFlightError" )("ProtocolArrowFlightError", { /** @@ -232,6 +231,4 @@ export class ProtocolArrowFlightError extends Schema.TaggedErrorClass -): ProtocolMessageWatermark => ProtocolMessageWatermark.make({ ranges: ranges }) +export const watermark = (ranges: ReadonlyArray): ProtocolMessageWatermark => + ProtocolMessageWatermark.make({ ranges: ranges }) diff --git a/packages/amp/src/protocol-stream/service.ts b/packages/amp/src/protocol-stream/service.ts index 421429a..14d1827 100644 --- a/packages/amp/src/protocol-stream/service.ts +++ b/packages/amp/src/protocol-stream/service.ts @@ -86,10 +86,7 @@ export interface ProtocolStreamService { * ) * ``` */ - readonly stream: ( - sql: string, - options?: ProtocolStreamOptions - ) => Stream.Stream + readonly stream: (sql: string, options?: ProtocolStreamOptions) => Stream.Stream } // ============================================================================= @@ -116,10 +113,7 @@ export interface ProtocolStreamService { * Effect.runPromise(program.pipe(Effect.provide(AppLayer))) * ``` */ -export class ProtocolStream extends Context.Service< - ProtocolStream, - ProtocolStreamService ->()("Amp/ProtocolStream") {} +export class ProtocolStream extends Context.Service()("Amp/ProtocolStream") {} // ============================================================================= // Implementation @@ -163,11 +157,7 @@ const detectReorgs = ( // Detect backwards jump (reorg indicator) if (incomingStart < prevEnd + 1) { invalidations.push( - makeInvalidationRange( - incomingRange.network, - incomingStart, - Math.max(incomingRange.numbers.end, prevEnd) - ) + makeInvalidationRange(incomingRange.network, incomingStart, Math.max(incomingRange.numbers.end, prevEnd)) ) } } @@ -178,13 +168,13 @@ const detectReorgs = ( /** * Create ProtocolStream service implementation. */ -const make = Effect.gen(function*() { +const make = Effect.gen(function* () { const arrowFlight = yield* ArrowFlight - const stream = (sql: string, options?: ProtocolStreamOptions): Stream.Stream< - ProtocolMessage, - ProtocolStreamError - > => { + const stream = ( + sql: string, + options?: ProtocolStreamOptions + ): Stream.Stream => { const rawStream = arrowFlight.streamQuery(sql, { schema: options?.schema, stream: true, @@ -202,45 +192,40 @@ const make = Effect.gen(function*() { // Process each batch with state tracking Stream.mapAccumEffect( () => initialState, - Effect.fnUntraced( - function*( - state: ProtocolStreamState, - queryResult: QueryResult> - ): Effect.fn.Return< - readonly [ProtocolStreamState, ReadonlyArray], - ProtocolStreamError - > { - const batchData = queryResult.data - const metadata = queryResult.metadata - const incoming = metadata.ranges + Effect.fnUntraced(function* ( + state: ProtocolStreamState, + queryResult: QueryResult> + ): Effect.fn.Return], ProtocolStreamError> { + const batchData = queryResult.data + const metadata = queryResult.metadata + const incoming = metadata.ranges - // Validate the incoming batch - yield* validateAll(state.previous, incoming).pipe( - Effect.mapError((error) => new ProtocolValidationError({ cause: error })) - ) + // Validate the incoming batch + yield* validateAll(state.previous, incoming).pipe( + Effect.mapError((error) => new ProtocolValidationError({ cause: error })) + ) - // Detect reorgs - const invalidations = state.initialized ? detectReorgs(state.previous, incoming) : [] + // Detect reorgs + const invalidations = state.initialized ? detectReorgs(state.previous, incoming) : [] - // Determine message type - let message: ProtocolMessage + // Determine message type + let message: ProtocolMessage - if (invalidations.length > 0) { - message = protocolReorg(state.previous, incoming, invalidations) - } else if (metadata.rangesComplete && batchData.length === 0) { - message = protocolWatermark(incoming) - } else { - message = protocolData(batchData, incoming) - } - - const newState: ProtocolStreamState = { - previous: incoming, - initialized: true - } + if (invalidations.length > 0) { + message = protocolReorg(state.previous, incoming, invalidations) + } else if (metadata.rangesComplete && batchData.length === 0) { + message = protocolWatermark(incoming) + } else { + message = protocolData(batchData, incoming) + } - return [newState, [message]] as const + const newState: ProtocolStreamState = { + previous: incoming, + initialized: true } - ) + + return [newState, [message]] as const + }) ), Stream.withSpan("ProtocolStream.stream") ) diff --git a/packages/amp/src/protocol-stream/validation.ts b/packages/amp/src/protocol-stream/validation.ts index e68403f..975a662 100644 --- a/packages/amp/src/protocol-stream/validation.ts +++ b/packages/amp/src/protocol-stream/validation.ts @@ -46,32 +46,32 @@ const isZeroHash = (hash: string): boolean => hash === ZERO_HASH * @param range - The block range to validate. * @returns An Effect that succeeds if valid, or fails with a validation error. */ -export const validatePrevHash = Effect.fnUntraced( - function*(range: BlockRange): Effect.fn.Return { - const isGenesis = range.numbers.start === 0 - - if (isGenesis) { - // Genesis blocks must have no prevHash or a zero hash - if (range.prevHash !== undefined && !isZeroHash(range.prevHash)) { - return yield* new InvalidPrevHashError({ network: range.network }) - } - } else { - // Non-genesis blocks must have a non-zero prevHash - if (range.prevHash === undefined) { - return yield* new MissingPrevHashError({ - network: range.network, - block: range.numbers.start - }) - } - if (isZeroHash(range.prevHash)) { - return yield* new MissingPrevHashError({ - network: range.network, - block: range.numbers.start - }) - } +export const validatePrevHash = Effect.fnUntraced(function* ( + range: BlockRange +): Effect.fn.Return { + const isGenesis = range.numbers.start === 0 + + if (isGenesis) { + // Genesis blocks must have no prevHash or a zero hash + if (range.prevHash !== undefined && !isZeroHash(range.prevHash)) { + return yield* new InvalidPrevHashError({ network: range.network }) + } + } else { + // Non-genesis blocks must have a non-zero prevHash + if (range.prevHash === undefined) { + return yield* new MissingPrevHashError({ + network: range.network, + block: range.numbers.start + }) + } + if (isZeroHash(range.prevHash)) { + return yield* new MissingPrevHashError({ + network: range.network, + block: range.numbers.start + }) } } -) +}) /** * Validates network consistency between previous and incoming batches. @@ -84,52 +84,50 @@ export const validatePrevHash = Effect.fnUntraced( * @param incoming - The incoming batch's block ranges. * @returns An Effect that succeeds if valid, or fails with a validation error. */ -export const validateNetworks = Effect.fnUntraced( - function*( - previous: ReadonlyArray, - incoming: ReadonlyArray - ): Effect.fn.Return { - // Check for duplicate networks in incoming batch - const incomingNetworks = new Set() - for (const range of incoming) { - if (incomingNetworks.has(range.network)) { - return yield* new DuplicateNetworkError({ network: range.network }) - } - incomingNetworks.add(range.network) +export const validateNetworks = Effect.fnUntraced(function* ( + previous: ReadonlyArray, + incoming: ReadonlyArray +): Effect.fn.Return { + // Check for duplicate networks in incoming batch + const incomingNetworks = new Set() + for (const range of incoming) { + if (incomingNetworks.has(range.network)) { + return yield* new DuplicateNetworkError({ network: range.network }) } + incomingNetworks.add(range.network) + } - // If this is the first batch, no further validation needed - if (previous.length === 0) { - return - } + // If this is the first batch, no further validation needed + if (previous.length === 0) { + return + } - // Check network count matches - if (previous.length !== incoming.length) { - return yield* new NetworkCountChangedError({ - expected: previous.length, - actual: incoming.length - }) - } + // Check network count matches + if (previous.length !== incoming.length) { + return yield* new NetworkCountChangedError({ + expected: previous.length, + actual: incoming.length + }) + } - // Check all incoming networks were in previous batch - const previousNetworks = new Set(previous.map((r) => r.network)) - for (const range of incoming) { - if (!previousNetworks.has(range.network)) { - return yield* new UnexpectedNetworkError({ network: range.network }) - } + // Check all incoming networks were in previous batch + const previousNetworks = new Set(previous.map((r) => r.network)) + for (const range of incoming) { + if (!previousNetworks.has(range.network)) { + return yield* new UnexpectedNetworkError({ network: range.network }) } } -) +}) /** * Checks if two block ranges are equal. */ export const blockRangeEquals = (a: BlockRange, b: BlockRange): boolean => - a.network === b.network && - a.numbers.start === b.numbers.start && - a.numbers.end === b.numbers.end && - a.hash === b.hash && - a.prevHash === b.prevHash + a.network === b.network + && a.numbers.start === b.numbers.start + && a.numbers.end === b.numbers.end + && a.hash === b.hash + && a.prevHash === b.prevHash /** * Validates block range consecutiveness and hash chain integrity. @@ -144,61 +142,59 @@ export const blockRangeEquals = (a: BlockRange, b: BlockRange): boolean => * @param incoming - The incoming batch's block ranges. * @returns An Effect that succeeds if valid, or fails with a validation error. */ -export const validateConsecutiveness = Effect.fnUntraced( - function*( - previous: ReadonlyArray, - incoming: ReadonlyArray - ): Effect.fn.Return { - // If this is the first batch, no consecutiveness check needed - if (previous.length === 0) { - return +export const validateConsecutiveness = Effect.fnUntraced(function* ( + previous: ReadonlyArray, + incoming: ReadonlyArray +): Effect.fn.Return { + // If this is the first batch, no consecutiveness check needed + if (previous.length === 0) { + return + } + + for (const incomingRange of incoming) { + // Find the previous range for this network + const prevRange = previous.find((p) => p.network === incomingRange.network) + if (!prevRange) { + // New network - should have been caught by validateNetworks + continue } - for (const incomingRange of incoming) { - // Find the previous range for this network - const prevRange = previous.find((p) => p.network === incomingRange.network) - if (!prevRange) { - // New network - should have been caught by validateNetworks - continue - } + // Identical ranges are allowed (watermarks can repeat) + if (blockRangeEquals(incomingRange, prevRange)) { + continue + } - // Identical ranges are allowed (watermarks can repeat) - if (blockRangeEquals(incomingRange, prevRange)) { - continue - } + const incomingStart = incomingRange.numbers.start + const prevEnd = prevRange.numbers.end + const expectedNextStart = (prevEnd + 1) as BlockNumber - const incomingStart = incomingRange.numbers.start - const prevEnd = prevRange.numbers.end - const expectedNextStart = (prevEnd + 1) as BlockNumber - - if (incomingStart === expectedNextStart) { - // Consecutive blocks: hash chain must match - // incoming.prevHash must equal prev.hash - if (incomingRange.prevHash !== prevRange.hash) { - return yield* new HashMismatchOnConsecutiveBlocksError({ - network: incomingRange.network, - expectedHash: prevRange.hash, - actualPrevHash: incomingRange.prevHash ?? "undefined" - }) - } - } else if (incomingStart < expectedNextStart) { - // Backwards jump: indicates a reorg - // Hash chain MUST mismatch for a valid reorg - if (incomingRange.prevHash === prevRange.hash) { - return yield* new InvalidReorgError({ network: incomingRange.network }) - } - // Hash mismatch is expected for reorg - this is valid - } else { - // Forward gap: always a protocol violation - return yield* new GapError({ + if (incomingStart === expectedNextStart) { + // Consecutive blocks: hash chain must match + // incoming.prevHash must equal prev.hash + if (incomingRange.prevHash !== prevRange.hash) { + return yield* new HashMismatchOnConsecutiveBlocksError({ network: incomingRange.network, - missingStart: expectedNextStart, - missingEnd: (incomingStart - 1) as BlockNumber + expectedHash: prevRange.hash, + actualPrevHash: incomingRange.prevHash ?? "undefined" }) } + } else if (incomingStart < expectedNextStart) { + // Backwards jump: indicates a reorg + // Hash chain MUST mismatch for a valid reorg + if (incomingRange.prevHash === prevRange.hash) { + return yield* new InvalidReorgError({ network: incomingRange.network }) + } + // Hash mismatch is expected for reorg - this is valid + } else { + // Forward gap: always a protocol violation + return yield* new GapError({ + network: incomingRange.network, + missingStart: expectedNextStart, + missingEnd: (incomingStart - 1) as BlockNumber + }) } } -) +}) /** * Runs all validation checks on incoming block ranges. @@ -207,20 +203,18 @@ export const validateConsecutiveness = Effect.fnUntraced( * @param incoming - The incoming batch's block ranges. * @returns An Effect that succeeds if all validations pass. */ -export const validateAll = Effect.fnUntraced( - function*( - previous: ReadonlyArray, - incoming: ReadonlyArray - ): Effect.fn.Return { - // Validate prevHash for all incoming ranges - for (const range of incoming) { - yield* validatePrevHash(range) - } +export const validateAll = Effect.fnUntraced(function* ( + previous: ReadonlyArray, + incoming: ReadonlyArray +): Effect.fn.Return { + // Validate prevHash for all incoming ranges + for (const range of incoming) { + yield* validatePrevHash(range) + } - // Validate network consistency - yield* validateNetworks(previous, incoming) + // Validate network consistency + yield* validateNetworks(previous, incoming) - // Validate consecutiveness - yield* validateConsecutiveness(previous, incoming) - } -) + // Validate consecutiveness + yield* validateConsecutiveness(previous, incoming) +}) diff --git a/packages/amp/src/registry/api.ts b/packages/amp/src/registry/api.ts index b3ae6ba..d222457 100644 --- a/packages/amp/src/registry/api.ts +++ b/packages/amp/src/registry/api.ts @@ -68,11 +68,7 @@ const getReadiness = HttpApiEndpoint.get("getReadiness", "/health/ready", { /** * Api group for checking API health. */ -export class HealthApiGroup extends HttpApiGroup.make("health") - .add(getHealth) - .add(getLiveness) - .add(getReadiness) -{} +export class HealthApiGroup extends HttpApiGroup.make("health").add(getHealth).add(getLiveness).add(getReadiness) {} // ============================================================================= // Datasets @@ -166,11 +162,7 @@ const searchDatasets = HttpApiEndpoint.get("searchDatasets", "/datasets/search", const aiSearchDatasets = HttpApiEndpoint.get("aiSearchDatasets", "/datasets/search/ai", { query: Domain.AiSearchDatasetsParams, success: Domain.DatasetAiSearchResponse, - error: [ - Errors.DatasetConversionError, - Errors.InvalidQueryParametersError, - Errors.RegistryDatabaseError - ] + error: [Errors.DatasetConversionError, Errors.InvalidQueryParametersError, Errors.RegistryDatabaseError] }) // ----------------------------------------------------------------------------- @@ -183,11 +175,7 @@ const listDatasetVersions = HttpApiEndpoint.get("listDatasetVersions", "/dataset name: DatasetNameParam }, success: Domain.DatasetListVersionsResponse, - error: [ - Errors.DatasetVersionConversionError, - Errors.InvalidSelectorError, - Errors.RegistryDatabaseError - ] + error: [Errors.DatasetVersionConversionError, Errors.InvalidSelectorError, Errors.RegistryDatabaseError] }) // ----------------------------------------------------------------------------- @@ -272,11 +260,7 @@ const listDatasetQueries = HttpApiEndpoint.get( revision: DatasetRevisionParam }, success: Domain.DatasetListQueriesResponse, - error: [ - Errors.InvalidSelectorError, - Errors.RegistryDatabaseError, - Errors.SavedQueryConversionError - ] + error: [Errors.InvalidSelectorError, Errors.RegistryDatabaseError, Errors.SavedQueryConversionError] } ) @@ -350,8 +334,7 @@ export class DatasetsApiGroup extends HttpApiGroup.make("datasets") .add(listLatestDatasetQueries) .add(listDatasetQueries) .add(getLatestDatasetManifest) - .add(getDatasetManifest) -{} + .add(getDatasetManifest) {} // ============================================================================= // Owned Datasets @@ -361,49 +344,41 @@ export class DatasetsApiGroup extends HttpApiGroup.make("datasets") // GET /api/vX/owners/{owner}/datasets // ----------------------------------------------------------------------------- -const listOwnedDatasets = HttpApiEndpoint.get( - "listOwnedDatasets", - "/owners/:owner/datasets", - { - params: { - owner: DatasetOwnerParam - }, - query: Domain.ListOwnedDatasetsParams, - success: Domain.ListMyDatasetsResponse, - error: [ - Errors.DatasetConversionError, - Errors.InvalidDatasetOwnerPathError, - Errors.InvalidQueryParametersError, - Errors.LimitInvalidError, - Errors.LimitTooLargeError, - Errors.RegistryDatabaseError - ] - } -) +const listOwnedDatasets = HttpApiEndpoint.get("listOwnedDatasets", "/owners/:owner/datasets", { + params: { + owner: DatasetOwnerParam + }, + query: Domain.ListOwnedDatasetsParams, + success: Domain.ListMyDatasetsResponse, + error: [ + Errors.DatasetConversionError, + Errors.InvalidDatasetOwnerPathError, + Errors.InvalidQueryParametersError, + Errors.LimitInvalidError, + Errors.LimitTooLargeError, + Errors.RegistryDatabaseError + ] +}) // ----------------------------------------------------------------------------- // GET /api/vX/owners/{owner}/datasets/search // ----------------------------------------------------------------------------- -const searchOwnedDatasets = HttpApiEndpoint.get( - "searchOwnedDatasets", - "/owners/:owner/datasets/search", - { - params: { - owner: DatasetOwnerParam - }, - query: Domain.SearchOwnedDatasetsParams, - success: Domain.DatasetSearchResponse, - error: [ - Errors.DatasetConversionError, - Errors.InvalidDatasetOwnerPathError, - Errors.InvalidQueryParametersError, - Errors.LimitInvalidError, - Errors.LimitTooLargeError, - Errors.RegistryDatabaseError - ] - } -) +const searchOwnedDatasets = HttpApiEndpoint.get("searchOwnedDatasets", "/owners/:owner/datasets/search", { + params: { + owner: DatasetOwnerParam + }, + query: Domain.SearchOwnedDatasetsParams, + success: Domain.DatasetSearchResponse, + error: [ + Errors.DatasetConversionError, + Errors.InvalidDatasetOwnerPathError, + Errors.InvalidQueryParametersError, + Errors.LimitInvalidError, + Errors.LimitTooLargeError, + Errors.RegistryDatabaseError + ] +}) // ----------------------------------------------------------------------------- // Group Definition @@ -414,8 +389,7 @@ const searchOwnedDatasets = HttpApiEndpoint.get( */ export class OwnedDatasetsApiGroup extends HttpApiGroup.make("ownedDatasets") .add(listOwnedDatasets) - .add(searchOwnedDatasets) -{} + .add(searchOwnedDatasets) {} // ============================================================================= // My Datasets @@ -425,81 +399,59 @@ export class OwnedDatasetsApiGroup extends HttpApiGroup.make("ownedDatasets") // GET /api/vX/owners/@me/datasets // ----------------------------------------------------------------------------- -const listMyDatasets = HttpApiEndpoint.get( - "listMyDatasets", - "/datasets", - { - query: Domain.ListOwnedDatasetsParams, - headers: Domain.BearerAuthHeader, - success: Domain.AuthUserOwnedDatasetListResponse, - error: [ - HttpApiError.Unauthorized, - Errors.DatasetConversionError, - Errors.InvalidQueryParametersError, - Errors.LimitInvalidError, - Errors.LimitTooLargeError, - Errors.RegistryDatabaseError - ] - } -) +const listMyDatasets = HttpApiEndpoint.get("listMyDatasets", "/datasets", { + query: Domain.ListOwnedDatasetsParams, + headers: Domain.BearerAuthHeader, + success: Domain.AuthUserOwnedDatasetListResponse, + error: [ + HttpApiError.Unauthorized, + Errors.DatasetConversionError, + Errors.InvalidQueryParametersError, + Errors.LimitInvalidError, + Errors.LimitTooLargeError, + Errors.RegistryDatabaseError + ] +}) // ----------------------------------------------------------------------------- // GET /api/vX/owners/@me/datasets/{namespace}/{name} // ----------------------------------------------------------------------------- -const getMyDatasetByFqdn = HttpApiEndpoint.get( - "getMyDatasetByFqdn", - "/datasets/:namespace/:name", - { - params: { - namespace: DatasetNamespaceParam, - name: DatasetNameParam - }, - headers: Domain.BearerAuthHeader, - success: Domain.Dataset, - error: [ - HttpApiError.Unauthorized, - Errors.DatasetConversionError, - Errors.DatasetNotFoundError, - Errors.InvalidDatasetSelectorError, - Errors.RegistryDatabaseError - ] - } -) +const getMyDatasetByFqdn = HttpApiEndpoint.get("getMyDatasetByFqdn", "/datasets/:namespace/:name", { + params: { + namespace: DatasetNamespaceParam, + name: DatasetNameParam + }, + headers: Domain.BearerAuthHeader, + success: Domain.Dataset, + error: [ + HttpApiError.Unauthorized, + Errors.DatasetConversionError, + Errors.DatasetNotFoundError, + Errors.InvalidDatasetSelectorError, + Errors.RegistryDatabaseError + ] +}) // ----------------------------------------------------------------------------- // GET /api/v1/owners/@me/datasets/counts/by-chain // ----------------------------------------------------------------------------- -const getMyDatasetCountsByChain = HttpApiEndpoint.get( - "getMyDatasetCountsByChain", - "/datasets/counts/by-chain", - { - headers: Domain.BearerAuthHeader, - success: Domain.OwnedDatasetCountsByChainResponse, - error: [ - HttpApiError.Unauthorized, - Errors.RegistryDatabaseError - ] - } -) +const getMyDatasetCountsByChain = HttpApiEndpoint.get("getMyDatasetCountsByChain", "/datasets/counts/by-chain", { + headers: Domain.BearerAuthHeader, + success: Domain.OwnedDatasetCountsByChainResponse, + error: [HttpApiError.Unauthorized, Errors.RegistryDatabaseError] +}) // ----------------------------------------------------------------------------- // GET /api/v1/owners/@me/datasets/counts/by-keyword // ----------------------------------------------------------------------------- -const getMyDatasetCountsByKeyword = HttpApiEndpoint.get( - "getMyDatasetCountsByKeyword", - "/datasets/counts/by-keyword", - { - headers: Domain.BearerAuthHeader, - success: Domain.OwnedDatasetCountsByKeywordResponse, - error: [ - HttpApiError.Unauthorized, - Errors.RegistryDatabaseError - ] - } -) +const getMyDatasetCountsByKeyword = HttpApiEndpoint.get("getMyDatasetCountsByKeyword", "/datasets/counts/by-keyword", { + headers: Domain.BearerAuthHeader, + success: Domain.OwnedDatasetCountsByKeywordResponse, + error: [HttpApiError.Unauthorized, Errors.RegistryDatabaseError] +}) // ----------------------------------------------------------------------------- // GET /api/v1/owners/@me/datasets/counts/by-last-updated @@ -511,10 +463,7 @@ const getMyDatasetCountsByLastUpdated = HttpApiEndpoint.get( { headers: Domain.BearerAuthHeader, success: Domain.OwnedDatasetsCountByLastUpdatedResponse, - error: [ - HttpApiError.Unauthorized, - Errors.RegistryDatabaseError - ] + error: [HttpApiError.Unauthorized, Errors.RegistryDatabaseError] } ) @@ -522,18 +471,11 @@ const getMyDatasetCountsByLastUpdated = HttpApiEndpoint.get( // GET /api/v1/owners/@me/datasets/counts/by-status // ----------------------------------------------------------------------------- -const getMyDatasetCountsByStatus = HttpApiEndpoint.get( - "getMyDatasetCountsByStatus", - "/datasets/counts/by-status", - { - headers: Domain.BearerAuthHeader, - success: Domain.OwnedDatasetCountsByStatusResponse, - error: [ - HttpApiError.Unauthorized, - Errors.RegistryDatabaseError - ] - } -) +const getMyDatasetCountsByStatus = HttpApiEndpoint.get("getMyDatasetCountsByStatus", "/datasets/counts/by-status", { + headers: Domain.BearerAuthHeader, + success: Domain.OwnedDatasetCountsByStatusResponse, + error: [HttpApiError.Unauthorized, Errors.RegistryDatabaseError] +}) // ----------------------------------------------------------------------------- // GET /api/v1/owners/@me/datasets/counts/by-visibility @@ -545,10 +487,7 @@ const getMyDatasetCountsByVisibility = HttpApiEndpoint.get( { headers: Domain.BearerAuthHeader, success: Domain.OwnedDatasetCountsByVisibilityResponse, - error: [ - HttpApiError.Unauthorized, - Errors.RegistryDatabaseError - ] + error: [HttpApiError.Unauthorized, Errors.RegistryDatabaseError] } ) @@ -556,23 +495,19 @@ const getMyDatasetCountsByVisibility = HttpApiEndpoint.get( // GET /api/vX/owners/@me/datasets/search // ----------------------------------------------------------------------------- -const searchMyDatasets = HttpApiEndpoint.get( - "searchMyDatasets", - "/datasets/search", - { - query: Domain.SearchOwnedDatasetsParams, - headers: Domain.BearerAuthHeader, - success: Domain.DatasetSearchResponse, - error: [ - HttpApiError.Unauthorized, - Errors.DatasetConversionError, - Errors.InvalidQueryParametersError, - Errors.LimitInvalidError, - Errors.LimitTooLargeError, - Errors.RegistryDatabaseError - ] - } -) +const searchMyDatasets = HttpApiEndpoint.get("searchMyDatasets", "/datasets/search", { + query: Domain.SearchOwnedDatasetsParams, + headers: Domain.BearerAuthHeader, + success: Domain.DatasetSearchResponse, + error: [ + HttpApiError.Unauthorized, + Errors.DatasetConversionError, + Errors.InvalidQueryParametersError, + Errors.LimitInvalidError, + Errors.LimitTooLargeError, + Errors.RegistryDatabaseError + ] +}) // ----------------------------------------------------------------------------- // GET /api/v1/owners/@me/datasets/{namespace}/{name}/versions/{version}/queries @@ -603,24 +538,20 @@ const listMyDatasetQueries = HttpApiEndpoint.get( // POST /api/v1/owners/@me/datasets/publish // ----------------------------------------------------------------------------- -const publishMyDataset = HttpApiEndpoint.post( - "publishMyDataset", - "/datasets/publish", - { - headers: Domain.BearerAuthHeader, - payload: Domain.InsertDatasetPayload, - success: Schema.Void.pipe(HttpApiSchema.status(201)), - error: [ - HttpApiError.Unauthorized, - Errors.DatasetConversionError, - Errors.InvalidManifestError, - Errors.InvalidRequestBodyError, - Errors.InvalidNamespaceError, - Errors.NamespaceAccessDeniedError, - Errors.RegistryDatabaseError - ] - } -) +const publishMyDataset = HttpApiEndpoint.post("publishMyDataset", "/datasets/publish", { + headers: Domain.BearerAuthHeader, + payload: Domain.InsertDatasetPayload, + success: Schema.Void.pipe(HttpApiSchema.status(201)), + error: [ + HttpApiError.Unauthorized, + Errors.DatasetConversionError, + Errors.InvalidManifestError, + Errors.InvalidRequestBodyError, + Errors.InvalidNamespaceError, + Errors.NamespaceAccessDeniedError, + Errors.RegistryDatabaseError + ] +}) // ----------------------------------------------------------------------------- // POST /api/v1/owners/@me/datasets/{namespace}/{name}/versions/publish @@ -653,26 +584,22 @@ const publishMyDatasetVersion = HttpApiEndpoint.post( // PUT /api/v1/owners/@me/datasets/{namespace}/{name} // ----------------------------------------------------------------------------- -const updateMyDatasetMetadata = HttpApiEndpoint.put( - "updateMyDatasetMetadata", - "/datasets/:namespace/:name", - { - params: { - namespace: DatasetNamespaceParam, - name: DatasetNameParam - }, - headers: Domain.BearerAuthHeader, - payload: Domain.UpdateDatasetMetadataPayload, - success: Domain.Dataset, - error: [ - HttpApiError.Unauthorized, - Errors.DatasetConversionError, - Errors.InvalidPathParametersError, - Errors.InvalidRequestBodyError, - Errors.RegistryDatabaseError - ] - } -) +const updateMyDatasetMetadata = HttpApiEndpoint.put("updateMyDatasetMetadata", "/datasets/:namespace/:name", { + params: { + namespace: DatasetNamespaceParam, + name: DatasetNameParam + }, + headers: Domain.BearerAuthHeader, + payload: Domain.UpdateDatasetMetadataPayload, + success: Domain.Dataset, + error: [ + HttpApiError.Unauthorized, + Errors.DatasetConversionError, + Errors.InvalidPathParametersError, + Errors.InvalidRequestBodyError, + Errors.RegistryDatabaseError + ] +}) // ----------------------------------------------------------------------------- // PATCH /api/v1/owners/@me/datasets/{namespace}/{name}/visibility @@ -773,8 +700,7 @@ export class MyDatasetsApiGroup extends HttpApiGroup.make("myDatasets") .add(updateMyDatasetMetadata) .add(updateMyDatasetVersionStatus) .add(archiveMyDatasetVersion) - .prefix("/owners/@me") -{} + .prefix("/owners/@me") {} // ============================================================================= // API Definition @@ -788,5 +714,4 @@ export class ApiV1 extends HttpApi.make("AmpRegistryApiV1") .add(DatasetsApiGroup) .add(OwnedDatasetsApiGroup) .add(MyDatasetsApiGroup) - .prefix("/api/v1") -{} + .prefix("/api/v1") {} diff --git a/packages/amp/src/registry/domain.ts b/packages/amp/src/registry/domain.ts index 8d50827..924e810 100644 --- a/packages/amp/src/registry/domain.ts +++ b/packages/amp/src/registry/domain.ts @@ -9,10 +9,9 @@ import * as Models from "../core/domain.ts" // General Schemas // ============================================================================= -export const PositiveIntFromString = Schema.NumberFromString.check( - Schema.isInt(), - Schema.isGreaterThan(0) -).annotate({ identifier: "PositiveIntFromString" }) +export const PositiveIntFromString = Schema.NumberFromString.check(Schema.isInt(), Schema.isGreaterThan(0)).annotate({ + identifier: "PositiveIntFromString" +}) export type PositiveIntFromString = typeof PositiveIntFromString.Type /** @@ -27,27 +26,17 @@ export type ServiceStatus = typeof ServiceStatus.Type /** * Time-based buckets for grouping datasets by last updated time */ -export const LastUpdatedBucket = Schema.Literals([ - "last_day", - "last_week", - "last_month", - "last_year" -]).annotate({ identifier: "LastUpdatedBucket" }) +export const LastUpdatedBucket = Schema.Literals(["last_day", "last_week", "last_month", "last_year"]).annotate({ + identifier: "LastUpdatedBucket" +}) export type LastUpdatedBucket = typeof LastUpdatedBucket.Type -export const DatasetSortBy = Schema.Literals([ - "namespace", - "name", - "owner", - "created_at", - "updated_at" -]).annotate({ identifier: "DatasetSortBy" }) +export const DatasetSortBy = Schema.Literals(["namespace", "name", "owner", "created_at", "updated_at"]).annotate({ + identifier: "DatasetSortBy" +}) export type DatasetSortBy = typeof DatasetSortBy.Type -export const DatasetSortDirection = Schema.Literals([ - "asc", - "desc" -]).annotate({ identifier: "DatasetSortDirection" }) +export const DatasetSortDirection = Schema.Literals(["asc", "desc"]).annotate({ identifier: "DatasetSortDirection" }) export type DatasetSortDirection = typeof DatasetSortDirection.Type /** @@ -66,12 +55,9 @@ export type DatasetVersionAncestry = typeof DatasetVersionAncestry.Type /** * Represents the status of a dataset version. */ -export const DatasetVersionStatus = Schema.Literals([ - "draft", - "published", - "deprecated", - "archived" -]).annotate({ identifier: "DatasetVersionStatus" }) +export const DatasetVersionStatus = Schema.Literals(["draft", "published", "deprecated", "archived"]).annotate({ + identifier: "DatasetVersionStatus" +}) export type DatasetVersionStatus = typeof DatasetVersionStatus.Type /** @@ -425,9 +411,7 @@ export type SearchMyDatasetsParams = typeof SearchMyDatasetsParams.Type * Represents a bearer token header. */ export const BearerAuthHeader = Schema.Struct({ - Authorization: Schema.String.check( - Schema.isStartsWith("Bearer") - ) + Authorization: Schema.String.check(Schema.isStartsWith("Bearer")) }).annotate({ identifier: "BearerAuthHeader" }) export type BearerAuthHeader = typeof BearerAuthHeader.Type diff --git a/packages/amp/src/registry/error.ts b/packages/amp/src/registry/error.ts index b4f6710..fecd011 100644 --- a/packages/amp/src/registry/error.ts +++ b/packages/amp/src/registry/error.ts @@ -25,7 +25,11 @@ export const makeError = < const Code extends string, const Tag extends string, const Fields extends Schema.Struct.Fields = {} ->(code: Code, tag: Tag, fields?: Fields): Schema.encodeKeys< +>( + code: Code, + tag: Tag, + fields?: Fields +): Schema.encodeKeys< Schema.Struct< Fields & { readonly _tag: Schema.withDecodingDefaultKey> @@ -86,23 +90,23 @@ export const makeError = < * and appears in both logs and error responses. */ requestId: Schema.optional(Schema.String) - }).pipe(Schema.encodeKeys({ - code: "error_code", - message: "error_message", - requestId: "request_id" - })) as any - -export const DatasetConversionError = makeError( - "DATASET_CONVERSION_ERROR", - "DatasetConversionError" -).annotate({ httpApiStatus: 500 }) + }).pipe( + Schema.encodeKeys({ + code: "error_code", + message: "error_message", + requestId: "request_id" + }) + ) as any + +export const DatasetConversionError = makeError("DATASET_CONVERSION_ERROR", "DatasetConversionError").annotate({ + httpApiStatus: 500 +}) export type DatasetConversionError = typeof DatasetConversionError.Type -export const DatasetNotFoundError = makeError( - "DATASET_NOT_FOUND", - "DatasetNotFoundError" -).annotate({ httpApiStatus: 404 }) +export const DatasetNotFoundError = makeError("DATASET_NOT_FOUND", "DatasetNotFoundError").annotate({ + httpApiStatus: 404 +}) export type DatasetNotFoundError = typeof DatasetNotFoundError.Type @@ -113,17 +117,13 @@ export const DatasetVersionConversionError = makeError( export type DatasetVersionConversionError = typeof DatasetVersionConversionError.Type -export const DatasetVersionNotFoundError = makeError( - "VERSION_NOT_FOUND", - "DatasetVersionNotFoundError" -).annotate({ httpApiStatus: 404 }) +export const DatasetVersionNotFoundError = makeError("VERSION_NOT_FOUND", "DatasetVersionNotFoundError").annotate({ + httpApiStatus: 404 +}) export type DatasetVersionNotFoundError = typeof DatasetVersionNotFoundError.Type -export const ForbiddenError = makeError( - "FORBIDDEN", - "ForbiddenError" -).annotate({ httpApiStatus: 403 }) +export const ForbiddenError = makeError("FORBIDDEN", "ForbiddenError").annotate({ httpApiStatus: 403 }) export type ForbiddenError = typeof ForbiddenError.Type @@ -134,10 +134,9 @@ export const InvalidDatasetOwnerPathError = makeError( export type InvalidDatasetOwnerPathError = typeof InvalidDatasetOwnerPathError.Type -export const InvalidDatasetReferenceError = makeError( - "INVALID_REFERENCE", - "InvalidDatasetReferenceError" -).annotate({ httpApiStatus: 500 }) +export const InvalidDatasetReferenceError = makeError("INVALID_REFERENCE", "InvalidDatasetReferenceError").annotate({ + httpApiStatus: 500 +}) export type InvalidDatasetReferenceError = typeof InvalidDatasetReferenceError.Type @@ -148,24 +147,21 @@ export const InvalidDatasetSelectorError = makeError( export type InvalidDatasetSelectorError = typeof InvalidDatasetSelectorError.Type -export const InvalidManifestError = makeError( - "INVALID_MANIFEST", - "InvalidManifestError" -).annotate({ httpApiStatus: 400 }) +export const InvalidManifestError = makeError("INVALID_MANIFEST", "InvalidManifestError").annotate({ + httpApiStatus: 400 +}) export type InvalidManifestError = typeof InvalidManifestError.Type -export const InvalidManifestHashError = makeError( - "INVALID_MANIFEST_HASH", - "InvalidManifestHashError" -).annotate({ httpApiStatus: 500 }) +export const InvalidManifestHashError = makeError("INVALID_MANIFEST_HASH", "InvalidManifestHashError").annotate({ + httpApiStatus: 500 +}) export type InvalidManifestHashError = typeof InvalidManifestHashError.Type -export const InvalidNamespaceError = makeError( - "INVALID_NAMESPACE", - "InvalidNamespaceError" -).annotate({ httpApiStatus: 400 }) +export const InvalidNamespaceError = makeError("INVALID_NAMESPACE", "InvalidNamespaceError").annotate({ + httpApiStatus: 400 +}) export type InvalidNamespaceError = typeof InvalidNamespaceError.Type @@ -176,24 +172,21 @@ export const InvalidQueryParametersError = makeError( export type InvalidQueryParametersError = typeof InvalidQueryParametersError.Type -export const InvalidPathParametersError = makeError( - "INVALID_PATH_PARAMETERS", - "InvalidPathParametersError" -).annotate({ httpApiStatus: 400 }) +export const InvalidPathParametersError = makeError("INVALID_PATH_PARAMETERS", "InvalidPathParametersError").annotate({ + httpApiStatus: 400 +}) export type InvalidPathParametersError = typeof InvalidPathParametersError.Type -export const InvalidRequestBodyError = makeError( - "INVALID_REQUEST_BODY", - "InvalidRequestBodyError" -).annotate({ httpApiStatus: 400 }) +export const InvalidRequestBodyError = makeError("INVALID_REQUEST_BODY", "InvalidRequestBodyError").annotate({ + httpApiStatus: 400 +}) export type InvalidRequestBodyError = typeof InvalidRequestBodyError.Type -export const InvalidSelectorError = makeError( - "INVALID_SELECTOR", - "InvalidSelectorError" -).annotate({ httpApiStatus: 400 }) +export const InvalidSelectorError = makeError("INVALID_SELECTOR", "InvalidSelectorError").annotate({ + httpApiStatus: 400 +}) export type InvalidSelectorError = typeof InvalidSelectorError.Type @@ -204,17 +197,11 @@ export const LatestDatasetVersionNotFoundError = makeError( export type LatestDatasetVersionNotFoundError = typeof LatestDatasetVersionNotFoundError.Type -export const LimitInvalidError = makeError( - "LIMIT_INVALID", - "LimitInvalidError" -).annotate({ httpApiStatus: 400 }) +export const LimitInvalidError = makeError("LIMIT_INVALID", "LimitInvalidError").annotate({ httpApiStatus: 400 }) export type LimitInvalidError = typeof LimitInvalidError.Type -export const LimitTooLargeError = makeError( - "LIMIT_TOO_LARGE", - "LimitTooLargeError" -).annotate({ httpApiStatus: 400 }) +export const LimitTooLargeError = makeError("LIMIT_TOO_LARGE", "LimitTooLargeError").annotate({ httpApiStatus: 400 }) export type LimitTooLargeError = typeof LimitTooLargeError.Type @@ -225,31 +212,27 @@ export const ManifestDeserializationError = makeError( export type ManifestDeserializationError = typeof ManifestDeserializationError.Type -export const ManifestNotFoundError = makeError( - "MANIFEST_NOT_FOUND", - "ManifestNotFoundError" -).annotate({ httpApiStatus: 404 }) +export const ManifestNotFoundError = makeError("MANIFEST_NOT_FOUND", "ManifestNotFoundError").annotate({ + httpApiStatus: 404 +}) export type ManifestNotFoundError = typeof ManifestNotFoundError.Type -export const ManifestRetrievalError = makeError( - "MANIFEST_RETRIEVAL_ERROR", - "ManifestRetrievalError" -).annotate({ httpApiStatus: 500 }) +export const ManifestRetrievalError = makeError("MANIFEST_RETRIEVAL_ERROR", "ManifestRetrievalError").annotate({ + httpApiStatus: 500 +}) export type ManifestRetrievalError = typeof ManifestRetrievalError.Type -export const NamespaceAccessDeniedError = makeError( - "NAMESPACE_ACCESS_DENIED", - "NamespaceAccessDeniedError" -).annotate({ httpApiStatus: 403 }) +export const NamespaceAccessDeniedError = makeError("NAMESPACE_ACCESS_DENIED", "NamespaceAccessDeniedError").annotate({ + httpApiStatus: 403 +}) export type NamespaceAccessDeniedError = typeof NamespaceAccessDeniedError.Type -export const RegistryDatabaseError = makeError( - "AMP_REGISTRY_DB_ERROR", - "RegistryDatabaseError" -).annotate({ httpApiStatus: 500 }) +export const RegistryDatabaseError = makeError("AMP_REGISTRY_DB_ERROR", "RegistryDatabaseError").annotate({ + httpApiStatus: 500 +}) export type RegistryDatabaseError = typeof RegistryDatabaseError.Type @@ -260,9 +243,8 @@ export const SavedQueryConversionError = makeError( export type SavedQueryConversionError = typeof SavedQueryConversionError.Type -export const ServiceUnavailableError = makeError( - "SERVICE_UNAVAILABLE", - "ServiceUnavailableError" -).annotate({ httpApiStatus: 503 }) +export const ServiceUnavailableError = makeError("SERVICE_UNAVAILABLE", "ServiceUnavailableError").annotate({ + httpApiStatus: 503 +}) export type ServiceUnavailableError = typeof ServiceUnavailableError.Type diff --git a/packages/amp/src/transactional-stream/algorithms.ts b/packages/amp/src/transactional-stream/algorithms.ts index 76dbdd4..62ac400 100644 --- a/packages/amp/src/transactional-stream/algorithms.ts +++ b/packages/amp/src/transactional-stream/algorithms.ts @@ -260,9 +260,7 @@ export const compressCommits = ( } // Remove any watermarks that were pruned - const filteredInsert = maxPrune !== undefined - ? insert.filter(([id]) => id > maxPrune!) - : insert + const filteredInsert = maxPrune !== undefined ? insert.filter(([id]) => id > maxPrune!) : insert return { insert: filteredInsert, diff --git a/packages/amp/src/transactional-stream/errors.ts b/packages/amp/src/transactional-stream/errors.ts index f70a496..81d7436 100644 --- a/packages/amp/src/transactional-stream/errors.ts +++ b/packages/amp/src/transactional-stream/errors.ts @@ -13,13 +13,14 @@ import type { ProtocolStreamError } from "../protocol-stream/errors.ts" /** * Error from StateStore operations. */ -export class StateStoreError extends Schema.TaggedErrorClass( - "Amp/TransactionalStream/StateStoreError" -)("StateStoreError", { - reason: Schema.String, - operation: Schema.Literals(["advance", "commit", "truncate", "load"]), - cause: Schema.optional(Schema.Defect()) -}) {} +export class StateStoreError extends Schema.TaggedError("Amp/TransactionalStream/StateStoreError")( + "StateStoreError", + { + reason: Schema.String, + operation: Schema.Literals(["advance", "commit", "truncate", "load"]), + cause: Schema.optional(Schema.Defect()) + } +) {} // ============================================================================= // Reorg Errors @@ -30,7 +31,7 @@ export class StateStoreError extends Schema.TaggedErrorClass( * This occurs when a reorg is so deep that there's no valid recovery point. * The stream must be restarted with fresh state. */ -export class UnrecoverableReorgError extends Schema.TaggedErrorClass( +export class UnrecoverableReorgError extends Schema.TaggedError( "Amp/TransactionalStream/UnrecoverableReorgError" )("UnrecoverableReorgError", { reason: Schema.String @@ -41,7 +42,7 @@ export class UnrecoverableReorgError extends Schema.TaggedErrorClass( +export class PartialReorgError extends Schema.TaggedError( "Amp/TransactionalStream/PartialReorgError" )("PartialReorgError", { reason: Schema.String, diff --git a/packages/amp/src/transactional-stream/memory-store.ts b/packages/amp/src/transactional-stream/memory-store.ts index 3e74c71..83e6042 100644 --- a/packages/amp/src/transactional-stream/memory-store.ts +++ b/packages/amp/src/transactional-stream/memory-store.ts @@ -55,7 +55,7 @@ const makeServiceFromRef = (stateRef: Ref.Ref): StateStoreService /** * Create InMemoryStateStore service implementation. */ -const makeWithInitialState = Effect.fnUntraced(function*(initial: StateSnapshot): Effect.fn.Return { +const makeWithInitialState = Effect.fnUntraced(function* (initial: StateSnapshot): Effect.fn.Return { const stateRef = yield* Ref.make(initial) return makeServiceFromRef(stateRef) }) @@ -123,10 +123,9 @@ export const layerWithState = (initial: StateSnapshot): Layer.Layer * expect(snapshot.next).toBe(5) * ``` */ -export class TestState extends Context.Service< - TestState, - { readonly get: Effect.Effect } ->()("Amp/TransactionalStream/TestState") {} +export class TestState extends Context.Service }>()( + "Amp/TransactionalStream/TestState" +) {} /** * Test layer providing both `StateStore` and `TestState`. @@ -151,7 +150,7 @@ export class TestState extends Context.Service< * ``` */ export const layerTest: Layer.Layer = Layer.effectContext( - Effect.gen(function*() { + Effect.gen(function* () { const ref = yield* Ref.make(emptySnapshot) const state = TestState.of({ @@ -160,9 +159,6 @@ export const layerTest: Layer.Layer = Layer.effectContex const store = StateStore.of(makeServiceFromRef(ref)) - return Context.mergeAll( - Context.make(StateStore, store), - Context.make(TestState, state) - ) + return Context.mergeAll(Context.make(StateStore, store), Context.make(TestState, state)) }) ) diff --git a/packages/amp/src/transactional-stream/state-actor.ts b/packages/amp/src/transactional-stream/state-actor.ts index 1b3a4be..49c1e17 100644 --- a/packages/amp/src/transactional-stream/state-actor.ts +++ b/packages/amp/src/transactional-stream/state-actor.ts @@ -51,9 +51,7 @@ interface StateContainer { /** * Action union for execute(). */ -export type Action = - | { readonly _tag: "Message"; readonly message: ProtocolMessage } - | { readonly _tag: "Rewind" } +export type Action = { readonly _tag: "Message"; readonly message: ProtocolMessage } | { readonly _tag: "Rewind" } /** * StateActor interface - manages transactional stream state. @@ -88,121 +86,120 @@ export interface StateActor { * @param retention - Retention window in blocks for pruning * @returns Effect that creates a StateActor */ -export const makeStateActor = Effect.fnUntraced( - function*(store: StateStoreService, retention: number): Effect.fn.Return { - // Load initial state from store - const snapshot = yield* store.load - - // Create mutable state container wrapped in Ref - const containerRef = yield* Ref.make({ - next: snapshot.next, - buffer: [...snapshot.buffer], - uncommitted: [] - }) - - // ========================================================================= - // watermark() - // ========================================================================= - - const watermark = Ref.get(containerRef).pipe( - Effect.map((state) => - state.buffer.length > 0 - ? state.buffer[state.buffer.length - 1] - : undefined - ) - ) - - // ========================================================================= - // peek() - // ========================================================================= - - const peek = Ref.get(containerRef).pipe(Effect.map((state) => state.next)) - - // ========================================================================= - // execute() - // ========================================================================= - - const execute = Effect.fnUntraced(function*(action: Action): Effect.fn.Return< - readonly [TransactionEvent, CommitHandle], - StateStoreError | UnrecoverableReorgError | PartialReorgError - > { - // 1. Pre-allocate monotonic ID - const id = yield* Ref.getAndUpdate(containerRef, (state) => ({ - ...state, - next: (state.next + 1) as TransactionId - })).pipe(Effect.map((state) => state.next)) - - const nextId = (id + 1) as TransactionId - - // Persist the new next ID immediately (ensures monotonicity survives crashes) - yield* store.advance(nextId) - - // 2. Execute action based on type - const event: TransactionEvent = yield* (() => { - switch (action._tag) { - case "Rewind": - return executeRewind(id, containerRef) - - case "Message": - return executeMessage(id, action.message, containerRef, store, retention) - } - })() +export const makeStateActor = Effect.fnUntraced(function* ( + store: StateStoreService, + retention: number +): Effect.fn.Return { + // Load initial state from store + const snapshot = yield* store.load - // 3. Return event with commit handle - const handle = makeCommitHandle(id, commit) + // Create mutable state container wrapped in Ref + const containerRef = yield* Ref.make({ + next: snapshot.next, + buffer: [...snapshot.buffer], + uncommitted: [] + }) - return [event, handle] as const - }) + // ========================================================================= + // watermark() + // ========================================================================= - // ========================================================================= - // commit() - // ========================================================================= + const watermark = Ref.get(containerRef).pipe( + Effect.map((state) => (state.buffer.length > 0 ? state.buffer[state.buffer.length - 1] : undefined)) + ) - const commit = Effect.fnUntraced(function*(id: TransactionId): Effect.fn.Return { - const state = yield* Ref.get(containerRef) + // ========================================================================= + // peek() + // ========================================================================= - // Find position where IDs become > id (all before this are <= id) - const pos = state.uncommitted.findIndex(([currentId]) => currentId > id) - const endIndex = pos === -1 ? state.uncommitted.length : pos + const peek = Ref.get(containerRef).pipe(Effect.map((state) => state.next)) - if (endIndex === 0) { - // Nothing to commit - return - } + // ========================================================================= + // execute() + // ========================================================================= - // Collect commits [0..endIndex) - const pending = state.uncommitted.slice(0, endIndex) - - // Compress and persist - const compressed = compressCommits(pending) - - if (compressed.insert.length > 0 || compressed.prune !== undefined) { - // Apply pruning to in-memory buffer - yield* Ref.update(containerRef, (s) => { - let buffer = s.buffer - if (compressed.prune !== undefined) { - buffer = buffer.filter(([bufferId]) => bufferId > compressed.prune!) - } - return { ...s, buffer } - }) - - // Persist to store - yield* store.commit({ - insert: compressed.insert, - prune: compressed.prune - }) + const execute = Effect.fnUntraced(function* ( + action: Action + ): Effect.fn.Return< + readonly [TransactionEvent, CommitHandle], + StateStoreError | UnrecoverableReorgError | PartialReorgError + > { + // 1. Pre-allocate monotonic ID + const id = yield* Ref.getAndUpdate(containerRef, (state) => ({ + ...state, + next: (state.next + 1) as TransactionId + })).pipe(Effect.map((state) => state.next)) + + const nextId = (id + 1) as TransactionId + + // Persist the new next ID immediately (ensures monotonicity survives crashes) + yield* store.advance(nextId) + + // 2. Execute action based on type + const event: TransactionEvent = yield* (() => { + switch (action._tag) { + case "Rewind": + return executeRewind(id, containerRef) + + case "Message": + return executeMessage(id, action.message, containerRef, store, retention) } + })() - // Remove committed from uncommitted queue - yield* Ref.update(containerRef, (s) => ({ - ...s, - uncommitted: s.uncommitted.slice(endIndex) - })) - }) + // 3. Return event with commit handle + const handle = makeCommitHandle(id, commit) - return { watermark, peek, execute, commit } - } -) + return [event, handle] as const + }) + + // ========================================================================= + // commit() + // ========================================================================= + + const commit = Effect.fnUntraced(function* (id: TransactionId): Effect.fn.Return { + const state = yield* Ref.get(containerRef) + + // Find position where IDs become > id (all before this are <= id) + const pos = state.uncommitted.findIndex(([currentId]) => currentId > id) + const endIndex = pos === -1 ? state.uncommitted.length : pos + + if (endIndex === 0) { + // Nothing to commit + return + } + + // Collect commits [0..endIndex) + const pending = state.uncommitted.slice(0, endIndex) + + // Compress and persist + const compressed = compressCommits(pending) + + if (compressed.insert.length > 0 || compressed.prune !== undefined) { + // Apply pruning to in-memory buffer + yield* Ref.update(containerRef, (s) => { + let buffer = s.buffer + if (compressed.prune !== undefined) { + buffer = buffer.filter(([bufferId]) => bufferId > compressed.prune!) + } + return { ...s, buffer } + }) + + // Persist to store + yield* store.commit({ + insert: compressed.insert, + prune: compressed.prune + }) + } + + // Remove committed from uncommitted queue + yield* Ref.update(containerRef, (s) => ({ + ...s, + uncommitted: s.uncommitted.slice(endIndex) + })) + }) + + return { watermark, peek, execute, commit } +}) // ============================================================================= // Helpers @@ -214,10 +211,7 @@ export const makeStateActor = Effect.fnUntraced( * @param afterId - The anchor transaction ID (invalidation starts at afterId + 1), or undefined to start from 0 * @param currentId - The current transaction ID being assigned to the undo event */ -const computeInvalidationRange = ( - afterId: TransactionId | undefined, - currentId: TransactionId -): TransactionIdRange => { +const computeInvalidationRange = (afterId: TransactionId | undefined, currentId: TransactionId): TransactionIdRange => { const start = (afterId !== undefined ? afterId + 1 : 0) as TransactionId const end = Math.max(start, currentId - 1) as TransactionId return { start, end } @@ -230,15 +224,13 @@ const computeInvalidationRange = ( /** * Execute a Rewind action. */ -const executeRewind = Effect.fnUntraced(function*( +const executeRewind = Effect.fnUntraced(function* ( id: TransactionId, containerRef: Ref.Ref ): Effect.fn.Return { const state = yield* Ref.get(containerRef) - const anchor = state.buffer.length > 0 - ? state.buffer[state.buffer.length - 1]![0] - : undefined + const anchor = state.buffer.length > 0 ? state.buffer[state.buffer.length - 1]![0] : undefined return undoEvent(id, rewindCause(), computeInvalidationRange(anchor, id)) }) @@ -246,7 +238,7 @@ const executeRewind = Effect.fnUntraced(function*( /** * Execute a Message action. */ -const executeMessage = Effect.fnUntraced(function*( +const executeMessage = Effect.fnUntraced(function* ( id: TransactionId, message: ProtocolMessage, containerRef: Ref.Ref, @@ -269,7 +261,7 @@ const executeMessage = Effect.fnUntraced(function*( /** * Execute a Watermark message. */ -const executeWatermark = Effect.fnUntraced(function*( +const executeWatermark = Effect.fnUntraced(function* ( id: TransactionId, ranges: ReadonlyArray, containerRef: Ref.Ref, @@ -297,7 +289,7 @@ const executeWatermark = Effect.fnUntraced(function*( /** * Execute a Reorg message. */ -const executeReorg = Effect.fnUntraced(function*( +const executeReorg = Effect.fnUntraced(function* ( id: TransactionId, message: Extract, containerRef: Ref.Ref, diff --git a/packages/amp/src/transactional-stream/state-store.ts b/packages/amp/src/transactional-stream/state-store.ts index 29befd9..f6830e4 100644 --- a/packages/amp/src/transactional-stream/state-store.ts +++ b/packages/amp/src/transactional-stream/state-store.ts @@ -123,10 +123,9 @@ export interface StateStoreService { * }) * ``` */ -export class StateStore extends Context.Service< - StateStore, - StateStoreService ->()("Amp/TransactionalStream/StateStore") {} +export class StateStore extends Context.Service()( + "Amp/TransactionalStream/StateStore" +) {} // ============================================================================= // Helpers diff --git a/packages/amp/src/transactional-stream/stream.ts b/packages/amp/src/transactional-stream/stream.ts index 6237e75..752d921 100644 --- a/packages/amp/src/transactional-stream/stream.ts +++ b/packages/amp/src/transactional-stream/stream.ts @@ -83,10 +83,7 @@ export interface TransactionalStreamService { readonly streamTransactional: ( sql: string, options?: TransactionalStreamOptions - ) => Stream.Stream< - readonly [TransactionEvent, CommitHandle], - TransactionalStreamError - > + ) => Stream.Stream /** * High-level consumer: auto-commit after callback succeeds. @@ -148,10 +145,9 @@ export interface TransactionalStreamService { * Effect.runPromise(program.pipe(Effect.provide(AppLayer))) * ``` */ -export class TransactionalStream extends Context.Service< - TransactionalStream, - TransactionalStreamService ->()("Amp/TransactionalStream") {} +export class TransactionalStream extends Context.Service()( + "Amp/TransactionalStream" +) {} // ============================================================================= // Implementation @@ -166,37 +162,31 @@ const DEFAULT_RETENTION = 128 * - Buffer is empty but next > 0 (crash before any watermark committed) * - Buffer has watermarks but next > last_watermark_id + 1 (crash after processing but before commit) */ -const needsRewind = ( - next: TransactionId, - lastWatermarkId: TransactionId | undefined -): boolean => { +const needsRewind = (next: TransactionId, lastWatermarkId: TransactionId | undefined): boolean => { if (lastWatermarkId === undefined) { // No watermarks - rewind if we've processed anything (next > 0) return next > 0 } // Rewind if next ID is beyond what we've committed - return next > (lastWatermarkId + 1) + return next > lastWatermarkId + 1 } /** * Create TransactionalStream service implementation. */ -const make = Effect.gen(function*() { +const make = Effect.gen(function* () { const protocolStreamService = yield* ProtocolStream const storeService = yield* StateStore const streamTransactional = ( sql: string, options?: TransactionalStreamOptions - ): Stream.Stream< - readonly [TransactionEvent, CommitHandle], - TransactionalStreamError - > => { + ): Stream.Stream => { const retention = options?.retention ?? DEFAULT_RETENTION // Create the stream with proper scoping return Stream.unwrap( - Effect.gen(function*() { + Effect.gen(function* () { // 1. Create StateActor const actor: StateActor = yield* makeStateActor(storeService, retention) @@ -205,18 +195,16 @@ const make = Effect.gen(function*() { const nextId = yield* actor.peek // 3. Determine resume cursor - const resumeWatermark: ReadonlyArray | undefined = watermark !== undefined - ? watermark[1] - : undefined + const resumeWatermark: ReadonlyArray | undefined = + watermark !== undefined ? watermark[1] : undefined // 4. Check if rewind is needed const lastWatermarkId = watermark?.[0] const shouldRewind = needsRewind(nextId, lastWatermarkId) // 5. Get protocol stream with resume cursor - const protocolOptions: ProtocolStreamOptions = resumeWatermark !== undefined - ? { schema: options?.schema, resumeWatermark } - : { schema: options?.schema } + const protocolOptions: ProtocolStreamOptions = + resumeWatermark !== undefined ? { schema: options?.schema, resumeWatermark } : { schema: options?.schema } const protocolStream = protocolStreamService.stream(sql, protocolOptions) // 6. Build transactional stream @@ -224,24 +212,16 @@ const make = Effect.gen(function*() { const rewindStream: Stream.Stream< readonly [TransactionEvent, CommitHandle], StateStoreError | UnrecoverableReorgError | PartialReorgError - > = shouldRewind - ? Stream.fromEffect( - actor.execute({ _tag: "Rewind" }) - ) - : Stream.empty + > = shouldRewind ? Stream.fromEffect(actor.execute({ _tag: "Rewind" })) : Stream.empty const messageStream: Stream.Stream< readonly [TransactionEvent, CommitHandle], ProtocolStreamError | StateStoreError | UnrecoverableReorgError | PartialReorgError - > = protocolStream.pipe( - Stream.mapEffect((message) => actor.execute({ _tag: "Message", message } as Action)) - ) + > = protocolStream.pipe(Stream.mapEffect((message) => actor.execute({ _tag: "Message", message } as Action))) return Stream.concat(rewindStream, messageStream) }) - ).pipe( - Stream.withSpan("TransactionalStream.streamTransactional") - ) + ).pipe(Stream.withSpan("TransactionalStream.streamTransactional")) } const forEach = ( @@ -250,12 +230,14 @@ const make = Effect.gen(function*() { handler: (event: TransactionEvent) => Effect.Effect ): Effect.Effect => streamTransactional(sql, options).pipe( - Stream.runForEach(Effect.fnUntraced(function*([event, commitHandle]: readonly [TransactionEvent, CommitHandle]) { - // Process the event - yield* handler(event) - // Auto-commit after successful processing - yield* commitHandle.commit - })), + Stream.runForEach( + Effect.fnUntraced(function* ([event, commitHandle]: readonly [TransactionEvent, CommitHandle]) { + // Process the event + yield* handler(event) + // Auto-commit after successful processing + yield* commitHandle.commit + }) + ), Effect.withSpan("TransactionalStream.forEach") ) diff --git a/packages/amp/src/transactional-stream/types.ts b/packages/amp/src/transactional-stream/types.ts index 1d122d6..a8144a0 100644 --- a/packages/amp/src/transactional-stream/types.ts +++ b/packages/amp/src/transactional-stream/types.ts @@ -16,9 +16,7 @@ import { InvalidationRange } from "../protocol-stream/messages.ts" * Transaction ID - monotonically increasing identifier for each event. * Guaranteed to be unique and never reused, even across crashes. */ -export const TransactionId = NonNegativeInt.pipe( - Schema.brand("Amp/TransactionalStream/TransactionId") -).annotate({ +export const TransactionId = NonNegativeInt.pipe(Schema.brand("Amp/TransactionalStream/TransactionId")).annotate({ identifier: "TransactionId", description: "Monotonically increasing transaction identifier" }) @@ -181,9 +179,7 @@ export const watermarkEvent = ( /** * Create a Reorg cause. */ -export const reorgCause = ( - invalidation: ReadonlyArray -): UndoCauseReorg => ({ +export const reorgCause = (invalidation: ReadonlyArray): UndoCauseReorg => ({ _tag: "Reorg", invalidation: invalidation as Array }) diff --git a/packages/amp/test/admin/service.test.ts b/packages/amp/test/admin/service.test.ts new file mode 100644 index 0000000..8441357 --- /dev/null +++ b/packages/amp/test/admin/service.test.ts @@ -0,0 +1,339 @@ +import * as AdminApi from "@edgeandnode/amp/admin/service" +import type * as Models from "@edgeandnode/amp/core/domain" +import { assert, describe, it } from "@effect/vitest" +import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" +import * as HttpClient from "effect/unstable/http/HttpClient" +import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" + +// Response payloads mirror the serialized shapes of the Amp admin API +// (see `docs/schemas/openapi/admin.spec.json` in the Amp repository). + +interface StubResponse { + readonly status: number + readonly body?: unknown +} + +const makeLayer = (routes: Record) => { + const client = HttpClient.make((request, url) => { + const route = routes[`${request.method} ${url.pathname}`] + const response = + route === undefined + ? new Response(null, { status: 404 }) + : new Response(route.body === undefined ? null : JSON.stringify(route.body), { + status: route.status, + headers: { "content-type": "application/json" } + }) + return Effect.succeed(HttpClientResponse.fromWeb(request, response)) + }) + return AdminApi.layer({ url: "http://localhost:1610" }).pipe( + Layer.provide(Layer.succeed(HttpClient.HttpClient, client)) + ) +} + +const hash = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" +const arrowSchema = { arrow: { fields: [{ name: "block_num", type: "UInt64", nullable: false }] } } + +describe("AdminApi", () => { + it.effect("registerDataset decodes the 201 registration response", () => + Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + const response = yield* admin.registerDataset( + "edgeandnode" as Models.DatasetNamespace, + "transfers" as Models.DatasetName, + hash as Models.DatasetHash + ) + assert.strictEqual(response.manifestHash, hash) + assert.strictEqual(response.kind, "manifest") + assert.strictEqual(response.version, undefined) + assert.deepStrictEqual(response.tables, ["transfers"]) + }).pipe( + Effect.provide( + makeLayer({ + "POST /datasets": { + status: 201, + body: { + namespace: "edgeandnode", + name: "transfers", + manifest_hash: hash, + kind: "manifest", + start_block: 0, + finalized_blocks_only: false, + tables: ["transfers"] + } + } + }) + ) + ) + ) + + it.effect("registerDataset decodes a namespace-not-found error", () => + Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + const error = yield* admin + .registerDataset( + "missing" as Models.DatasetNamespace, + "transfers" as Models.DatasetName, + hash as Models.DatasetHash + ) + .pipe(Effect.flip) + assert.strictEqual(error._tag, "NamespaceNotFoundError") + }).pipe( + Effect.provide( + makeLayer({ + "POST /datasets": { + status: 404, + body: { error_code: "NAMESPACE_NOT_FOUND", error_message: "namespace 'missing' not found" } + } + }) + ) + ) + ) + + it.effect("getDatasetVersions decodes version details and special tags", () => + Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + const response = yield* admin.getDatasetVersions( + "edgeandnode" as Models.DatasetNamespace, + "transfers" as Models.DatasetName + ) + assert.strictEqual(response.versions.length, 1) + assert.strictEqual(response.versions[0].version, "1.0.0") + assert.strictEqual(response.versions[0].manifestHash, hash) + assert.strictEqual(response.specialTags.latest, "1.0.0") + assert.strictEqual(response.specialTags.dev, hash) + }).pipe( + Effect.provide( + makeLayer({ + "GET /datasets/edgeandnode/transfers/versions": { + status: 200, + body: { + namespace: "edgeandnode", + name: "transfers", + versions: [ + { + version: "1.0.0", + manifest_hash: hash, + created_at: "2026-09-01T12:00:00+00:00", + updated_at: "2026-09-02T12:00:00+00:00" + } + ], + special_tags: { latest: "1.0.0", dev: hash } + } + } + }) + ) + ) + ) + + it.effect("getDatasetVersion decodes newer dataset kinds and tags", () => + Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + const response = yield* admin.getDatasetVersion( + "edgeandnode" as Models.DatasetNamespace, + "solana_mainnet" as Models.DatasetName, + "latest" as Models.DatasetRevision + ) + assert.strictEqual(response.kind, "solana") + assert.deepStrictEqual(response.tags, ["1.0.0", "latest"]) + }).pipe( + Effect.provide( + makeLayer({ + "GET /datasets/edgeandnode/solana_mainnet/versions/latest": { + status: 200, + body: { + namespace: "edgeandnode", + name: "solana_mainnet", + revision: "latest", + manifest_hash: hash, + kind: "solana", + start_block: 250000000, + finalized_blocks_only: true, + tables: ["blocks", "transactions"], + tags: ["1.0.0", "latest"] + } + } + }) + ) + ) + ) + + it.effect("getDatasetManifest decodes a derived manifest without table networks or function names", () => + Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + const manifest = yield* admin.getDatasetManifest( + "edgeandnode" as Models.DatasetNamespace, + "transfers" as Models.DatasetName, + "dev" as Models.DatasetRevision + ) + assert.strictEqual(manifest.kind, "manifest") + if (manifest.kind !== "manifest") return + assert.strictEqual(manifest.dependencies?.eth.name, "eth_mainnet") + assert.strictEqual(manifest.tables?.transfers.input.sql, "SELECT * FROM eth.logs") + assert.strictEqual(manifest.functions?.decode.outputType, "Utf8") + }).pipe( + Effect.provide( + makeLayer({ + "GET /datasets/edgeandnode/transfers/versions/dev/manifest": { + status: 200, + body: { + kind: "manifest", + dependencies: { eth: "edgeandnode/eth_mainnet@1.0.0" }, + tables: { + transfers: { + input: { sql: "SELECT * FROM eth.logs" }, + schema: arrowSchema, + bloom_filter_columns: [{ column: "address" }] + } + }, + functions: { + decode: { + inputTypes: ["Binary"], + outputType: "Utf8", + source: { source: "export default () => ''", filename: "decode.js" } + } + } + } + } + }) + ) + ) + ) + + it.effect("getDatasetManifest decodes raw and static manifests", () => + Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + const raw = yield* admin.getDatasetManifest( + "edgeandnode" as Models.DatasetNamespace, + "solana_mainnet" as Models.DatasetName, + "latest" as Models.DatasetRevision + ) + assert.strictEqual(raw.kind, "solana") + const fixture = yield* admin.getDatasetManifest( + "edgeandnode" as Models.DatasetNamespace, + "fixture" as Models.DatasetName, + "latest" as Models.DatasetRevision + ) + assert.strictEqual(fixture.kind, "static") + }).pipe( + Effect.provide( + makeLayer({ + "GET /datasets/edgeandnode/solana_mainnet/versions/latest/manifest": { + status: 200, + body: { + kind: "solana", + network: "solana-mainnet", + start_block: 250000000, + finalized_blocks_only: true, + state_snapshots: { selectors: ["accounts", { selector: "programs", block_interval: 100 }] }, + tables: { + blocks: { + schema: arrowSchema, + network: "solana-mainnet", + sorted_by: ["block_num"], + segment_selection_policy: "ordered" + } + } + } + }, + "GET /datasets/edgeandnode/fixture/versions/latest/manifest": { + status: 200, + body: { + kind: "static", + tables: { + tokens: { path: "data/tokens.csv", schema: arrowSchema, format: "csv", has_header: true } + } + } + } + }) + ) + ) + ) + + it.effect("getJobs decodes jobs in the newer terminal states", () => + Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + const response = yield* admin.getJobs({ status: "all" }) + assert.deepStrictEqual( + response.jobs.map((job) => job.status), + ["ERROR", "FATAL", "CANCELLED"] + ) + assert.strictEqual(response.jobs[0].idempotencyKey, hash) + assert.strictEqual(response.nextCursor, undefined) + }).pipe( + Effect.provide( + makeLayer({ + "GET /jobs": { + status: 200, + body: { + jobs: ["ERROR", "FATAL", "CANCELLED"].map((status, id) => ({ + id, + idempotency_key: hash, + created_at: "2026-09-01T12:00:00+00:00", + updated_at: "2026-09-02T12:00:00+00:00", + node_id: "worker-01", + status, + descriptor: null + })) + } + } + }) + ) + ) + ) + + it.effect("getOutputSchema decodes table schemas keyed by table name", () => + Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + const response = yield* admin.getOutputSchema({ tables: { transfers: "SELECT * FROM eth.logs" } }) + assert.strictEqual(response.schemas.transfers.arrow.fields[0].name, "block_num") + }).pipe( + Effect.provide( + makeLayer({ + "POST /schema": { status: 200, body: { schemas: { transfers: arrowSchema } } } + }) + ) + ) + ) + + it.effect("getOutputSchema decodes a missing dependency table as a 400 error", () => + Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + const error = yield* admin + .getOutputSchema({ tables: { transfers: "SELECT * FROM eth.missing" } }) + .pipe(Effect.flip) + assert.strictEqual(error._tag, "TableNotFoundInDatasetError") + }).pipe( + Effect.provide( + makeLayer({ + "POST /schema": { + status: 400, + body: { error_code: "TABLE_NOT_FOUND_IN_DATASET", error_message: "table 'missing' not found" } + } + }) + ) + ) + ) + + it.effect("getProviders keeps provider-specific configuration", () => + Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + const response = yield* admin.getProviders + const provider = response.providers[0] + assert.strictEqual(provider.name, "mainnet-rpc") + assert.strictEqual(provider.kind, "evm-rpc") + assert.strictEqual(provider["url"], "http://localhost:8545") + }).pipe( + Effect.provide( + makeLayer({ + "GET /providers": { + status: 200, + body: { + providers: [{ name: "mainnet-rpc", kind: "evm-rpc", network: "mainnet", url: "http://localhost:8545" }] + } + } + }) + ) + ) + ) +}) diff --git a/packages/amp/test/arrow-flight-ipc/roundtrip.test.ts b/packages/amp/test/arrow-flight-ipc/roundtrip.test.ts index 76763c3..53f5b71 100644 --- a/packages/amp/test/arrow-flight-ipc/roundtrip.test.ts +++ b/packages/amp/test/arrow-flight-ipc/roundtrip.test.ts @@ -15,10 +15,8 @@ import { formatComparisonErrors, verifyDecodedValues } from "../arrow-test-harne describe("FlightData roundtrip", () => { it.effect("a simple int32 column", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .int32("id") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().int32("id").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 10, @@ -45,13 +43,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("nullable int32 column", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .int32("id") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().int32("id").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 20, @@ -71,13 +68,12 @@ describe("FlightData roundtrip", () => { // Verify all values match const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("bool column", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .bool("flag") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().bool("flag").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 16, @@ -97,13 +93,12 @@ describe("FlightData roundtrip", () => { const expectedValues = generated.expectedValues.flag expect(expectedValues.some((v) => v === true)).toBe(true) expect(expectedValues.some((v) => v === false)).toBe(true) - })) + }) + ) it.effect("utf8 column", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .utf8("name") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().utf8("name").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 5, @@ -118,16 +113,12 @@ describe("FlightData roundtrip", () => { // Verify values match const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("multiple columns", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .int32("id") - .utf8("name") - .bool("active") - .int64("score") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().int32("id").utf8("name").bool("active").int64("score").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 10, @@ -164,10 +155,11 @@ describe("FlightData roundtrip", () => { // Verify all values match const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("all integer types", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const testSchema = SchemaBuilder.schema() .int8("i8") .int16("i16") @@ -191,14 +183,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("float types", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .float32("f32") - .float64("f64") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().float32("f32").float64("f64").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 20, @@ -212,14 +202,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("binary types", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .binary("bin") - .fixedSizeBinary("fixed", 8) - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().binary("bin").fixedSizeBinary("fixed", 8).build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 10, @@ -233,13 +221,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("decimal type", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .decimal("amount", 10, 2) - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().decimal("amount", 10, 2).build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 10, @@ -253,15 +240,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("temporal types", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .dateDay("day") - .dateMillisecond("ms_date") - .timestamp("ts") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().dateDay("day").dateMillisecond("ms_date").timestamp("ts").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 10, @@ -275,10 +259,11 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("list type", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const testSchema = SchemaBuilder.schema() .list("items", (b) => b.int32()) .build() @@ -295,15 +280,13 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("struct type", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const testSchema = SchemaBuilder.schema() - .struct("person", (b) => - b - .utf8("name") - .int32("age")) + .struct("person", (b) => b.utf8("name").int32("age")) .build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { @@ -318,15 +301,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("handles empty record batch (0 rows)", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .int32("id") - .utf8("name") - .bool("active") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().int32("id").utf8("name").bool("active").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 0, @@ -345,13 +325,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("handles empty strings (not null)", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .utf8("text") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().utf8("text").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 10, @@ -371,13 +350,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("handles empty binary (not null)", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .binary("data") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().binary("data").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 10, @@ -397,14 +375,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("handles all-null columns", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .int32("id") - .utf8("name") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().int32("id").utf8("name").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 10, @@ -426,10 +402,11 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("handles empty lists (not null)", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const testSchema = SchemaBuilder.schema() .list("items", (b) => b.int32()) .build() @@ -452,20 +429,19 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("handles deeply nested structures", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { // Struct containing a list of structs const testSchema = SchemaBuilder.schema() .struct("outer", (s) => s .utf8("name") .list("items", (l) => l.int32()) - .struct("nested", (n) => - n - .int32("value") - .utf8("label"))) + .struct("nested", (n) => n.int32("value").utf8("label")) + ) .build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { @@ -480,14 +456,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("handles single row", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .int32("id") - .utf8("name") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().int32("id").utf8("name").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 1, @@ -503,14 +477,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("handles large number of rows", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .int32("id") - .utf8("name") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().int32("id").utf8("name").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 10000, @@ -526,10 +498,11 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("handles large number of columns", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { // Create schema with 50 columns let builder = SchemaBuilder.schema() for (let i = 0; i < 50; i++) { @@ -553,12 +526,17 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("handles map type", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const testSchema = SchemaBuilder.schema() - .map("attributes", (k) => k.utf8(), (v) => v.int32()) + .map( + "attributes", + (k) => k.utf8(), + (v) => v.int32() + ) .build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { @@ -573,15 +551,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("multiple batches with varying row counts", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .int32("id") - .utf8("name") - .bool("active") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().int32("id").utf8("name").bool("active").build() const generated = yield* FlightDataGenerator.generateMultiBatchFlightData(testSchema, { rowsPerBatch: [5, 20, 1, 100, 50], @@ -602,14 +577,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, batch.expectedValues) expect(comparison.success, `Batch ${i}: ${formatComparisonErrors(comparison.errors)}`).toBe(true) } - })) + }) + ) it.effect("batch with empty batch in middle", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .int32("id") - .utf8("name") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().int32("id").utf8("name").build() const generated = yield* FlightDataGenerator.generateMultiBatchFlightData(testSchema, { rowsPerBatch: [10, 0, 10], @@ -631,13 +604,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, batch.expectedValues) expect(comparison.success, `Batch ${i}: ${formatComparisonErrors(comparison.errors)}`).toBe(true) } - })) + }) + ) it.effect("many small batches", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .int32("value") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().int32("value").build() // 100 batches of 10 rows each const generated = yield* FlightDataGenerator.generateMultiBatchFlightData(testSchema, { @@ -659,17 +631,15 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, batch.expectedValues) expect(comparison.success, `Batch ${i}: ${formatComparisonErrors(comparison.errors)}`).toBe(true) } - })) + }) + ) it.effect("multi-batch with complex nested types", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const testSchema = SchemaBuilder.schema() .int32("id") .list("tags", (b) => b.utf8()) - .struct("metadata", (s) => - s - .utf8("key") - .int32("count")) + .struct("metadata", (s) => s.utf8("key").int32("count")) .build() const generated = yield* FlightDataGenerator.generateMultiBatchFlightData(testSchema, { @@ -688,13 +658,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, batch.expectedValues) expect(comparison.success, `Batch ${i}: ${formatComparisonErrors(comparison.errors)}`).toBe(true) } - })) + }) + ) it.effect("float32 with special values (NaN, Infinity, -0)", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .float32("value") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().float32("value").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 100, @@ -718,13 +687,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("float64 with special values", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .float64("value") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().float64("value").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 100, @@ -741,14 +709,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("floats with extreme values", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .float32("f32") - .float64("f64") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().float32("f32").float64("f64").build() // Generate with special floats to test extreme values like MAX_VALUE, MIN_VALUE const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { @@ -767,13 +733,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("float32 with wide range values", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .float32("value") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().float32("value").build() // Test without special floats but with the expanded range const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { @@ -793,13 +758,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("float16 type", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .float16("half") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().float16("half").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 20, @@ -813,13 +777,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("large-utf8 type", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .largeUtf8("bigText") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().largeUtf8("bigText").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 10, @@ -833,13 +796,12 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("large-binary type", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .largeBinary("bigData") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().largeBinary("bigData").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { numRows: 10, @@ -853,10 +815,11 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("large-list type", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const testSchema = SchemaBuilder.schema() .largeList("bigList", (b) => b.int32()) .build() @@ -873,10 +836,11 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("fixed-size-list type", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const testSchema = SchemaBuilder.schema() .fixedSizeList("fixed3", 3, (b) => b.int32()) .build() @@ -893,10 +857,11 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("time types (all units)", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const testSchema = SchemaBuilder.schema() .timeSecond("timeSec") .timeMillisecond("timeMs") @@ -916,10 +881,11 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("duration type", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const testSchema = SchemaBuilder.schema() .duration("durSec", "SECOND") .duration("durMs", "MILLISECOND") @@ -939,10 +905,11 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("interval types (all units)", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const testSchema = SchemaBuilder.schema() .intervalYearMonth("intervalYM") .intervalDayTime("intervalDT") @@ -961,15 +928,13 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("sparse union type", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const testSchema = SchemaBuilder.schema() - .union("choice", "SPARSE", (u) => - u - .variant("intVal", (b) => b.int32()) - .variant("strVal", (b) => b.utf8())) + .union("choice", "SPARSE", (u) => u.variant("intVal", (b) => b.int32()).variant("strVal", (b) => b.utf8())) .build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { @@ -984,16 +949,18 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) it.effect("dense union type", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const testSchema = SchemaBuilder.schema() .union("tagged", "DENSE", (u) => u .variant("number", (b) => b.float64()) .variant("text", (b) => b.utf8()) - .variant("flag", (b) => b.bool())) + .variant("flag", (b) => b.bool()) + ) .build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { @@ -1008,5 +975,6 @@ describe("FlightData roundtrip", () => { const comparison = verifyDecodedValues(testSchema, decoded, generated.expectedValues) expect(comparison.success, formatComparisonErrors(comparison.errors)).toBe(true) - })) + }) + ) }) diff --git a/packages/amp/test/arrow-flight/service.test.ts b/packages/amp/test/arrow-flight/service.test.ts index 6f3c7d9..4437b38 100644 --- a/packages/amp/test/arrow-flight/service.test.ts +++ b/packages/amp/test/arrow-flight/service.test.ts @@ -23,7 +23,9 @@ const encoder = new TextEncoder() const recordBatchMetadata = encoder.encode(JSON.stringify({ ranges: [], ranges_complete: false })) const toHex = (bytes: Uint8Array): string => - Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("") + Array.from(bytes) + .map((byte) => byte.toString(16).padStart(2, "0")) + .join("") const makeTransport = (flightData: ReadonlyArray) => createRouterTransport((router) => { @@ -102,14 +104,12 @@ const makeCapturingTransport = (capturedSql: { value: string }) => describe("ArrowFlight", () => { it.effect("explain prepends EXPLAIN to the SQL by default", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const captured = { value: "" } const transport = makeCapturingTransport(captured) - const layer = ArrowFlight.layer.pipe( - Layer.provide(Layer.succeed(ArrowFlight.Transport, transport)) - ) + const layer = ArrowFlight.layer.pipe(Layer.provide(Layer.succeed(ArrowFlight.Transport, transport))) - const result = yield* Effect.gen(function*() { + const result = yield* Effect.gen(function* () { const flight = yield* ArrowFlight.ArrowFlight return yield* flight.explain("SELECT * FROM foo LIMIT 10") }).pipe(Effect.provide(layer)) @@ -117,17 +117,16 @@ describe("ArrowFlight", () => { expect(captured.value).toBe("EXPLAIN SELECT * FROM foo LIMIT 10") expect(result.rows).toEqual([]) expect(result.columns).toEqual([]) - })) + }) + ) it.effect("explain prepends EXPLAIN ANALYZE when analyze is true", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const captured = { value: "" } const transport = makeCapturingTransport(captured) - const layer = ArrowFlight.layer.pipe( - Layer.provide(Layer.succeed(ArrowFlight.Transport, transport)) - ) + const layer = ArrowFlight.layer.pipe(Layer.provide(Layer.succeed(ArrowFlight.Transport, transport))) - const result = yield* Effect.gen(function*() { + const result = yield* Effect.gen(function* () { const flight = yield* ArrowFlight.ArrowFlight return yield* flight.explain("SELECT 1", { analyze: true }) }).pipe(Effect.provide(layer)) @@ -135,13 +134,12 @@ describe("ArrowFlight", () => { expect(captured.value).toBe("EXPLAIN ANALYZE SELECT 1") expect(result.rows).toEqual([]) expect(result.columns).toEqual([]) - })) + }) + ) it.effect("passes binaryHandling to query output conversion", ({ expect }) => - Effect.gen(function*() { - const testSchema = SchemaBuilder.schema() - .binary("bin") - .build() + Effect.gen(function* () { + const testSchema = SchemaBuilder.schema().binary("bin").build() const generated = yield* FlightDataGenerator.generateFlightData(testSchema, { defaultNullRate: 0, @@ -159,16 +157,15 @@ describe("ArrowFlight", () => { toProtoFlightData(generated.schemaFlightData, new Uint8Array(0)), toProtoFlightData(generated.recordBatchFlightData, recordBatchMetadata) ]) - const layer = ArrowFlight.layer.pipe( - Layer.provide(Layer.succeed(ArrowFlight.Transport, transport)) - ) + const layer = ArrowFlight.layer.pipe(Layer.provide(Layer.succeed(ArrowFlight.Transport, transport))) - const results = yield* Effect.gen(function*() { + const results = yield* Effect.gen(function* () { const flight = yield* ArrowFlight.ArrowFlight return yield* flight.query("SELECT bin FROM test", { binaryHandling: "hex" }) }).pipe(Effect.provide(layer)) expect(results).toHaveLength(1) expect(results[0]?.data).toEqual([{ bin: toHex(firstExpected) }]) - })) + }) + ) }) diff --git a/packages/amp/test/arrow-test-harness/BufferUtils.ts b/packages/amp/test/arrow-test-harness/BufferUtils.ts index bd6fbcf..8d4d87c 100644 --- a/packages/amp/test/arrow-test-harness/BufferUtils.ts +++ b/packages/amp/test/arrow-test-harness/BufferUtils.ts @@ -28,9 +28,7 @@ export const padToAlignment = (buffer: Uint8Array): Uint8Array => { // Validity Bitmaps // ============================================================================= -export const createValidityBitmap = ( - values: ReadonlyArray -): { bitmap: Uint8Array; nullCount: number } => { +export const createValidityBitmap = (values: ReadonlyArray): { bitmap: Uint8Array; nullCount: number } => { let nullCount = 0 for (const value of values) { if (value === null) nullCount++ @@ -247,8 +245,8 @@ export const createFloat64DataBuffer = (values: ReadonlyArray): U const encodeFloat16 = (value: number): number => { if (value === 0) return 0 if (!Number.isFinite(value)) { - if (Number.isNaN(value)) return 0x7E00 - return value > 0 ? 0x7C00 : 0xFC00 + if (Number.isNaN(value)) return 0x7e00 + return value > 0 ? 0x7c00 : 0xfc00 } const sign = value < 0 ? 1 : 0 @@ -263,8 +261,8 @@ const encodeFloat16 = (value: number): number => { let mantissa = absValue / Math.pow(2, exponent) - 1 exponent += 15 - if (exponent >= 31) return (sign << 15) | 0x7C00 - if (exponent <= 0) return (sign << 15) + if (exponent >= 31) return (sign << 15) | 0x7c00 + if (exponent <= 0) return sign << 15 mantissa = Math.round(mantissa * 1024) @@ -273,7 +271,7 @@ const encodeFloat16 = (value: number): number => { mantissa = 0 exponent += 1 // Check if exponent overflowed to infinity - if (exponent >= 31) return (sign << 15) | 0x7C00 + if (exponent >= 31) return (sign << 15) | 0x7c00 } return (sign << 15) | (exponent << 10) | mantissa @@ -317,15 +315,15 @@ const bigIntToBytes = (value: bigint, byteWidth: number): Uint8Array => { const isNegative = value < 0n for (let i = 0; i < byteWidth; i++) { - bytes[i] = Number(v & 0xFFn) + bytes[i] = Number(v & 0xffn) v >>= 8n } if (isNegative) { let carry = 1 for (let i = 0; i < byteWidth; i++) { - const inverted = (~bytes[i] & 0xFF) + carry - bytes[i] = inverted & 0xFF + const inverted = (~bytes[i] & 0xff) + carry + bytes[i] = inverted & 0xff carry = inverted >> 8 } } @@ -333,10 +331,7 @@ const bigIntToBytes = (value: bigint, byteWidth: number): Uint8Array => { return bytes } -export const createDecimalDataBuffer = ( - values: ReadonlyArray, - bitWidth: 128 | 256 -): Uint8Array => { +export const createDecimalDataBuffer = (values: ReadonlyArray, bitWidth: 128 | 256): Uint8Array => { const byteWidth = bitWidth / 8 const buffer = new Uint8Array(align8(values.length * byteWidth)) diff --git a/packages/amp/test/arrow-test-harness/FlightDataGenerator.ts b/packages/amp/test/arrow-test-harness/FlightDataGenerator.ts index 4267f64..d7087ea 100644 --- a/packages/amp/test/arrow-test-harness/FlightDataGenerator.ts +++ b/packages/amp/test/arrow-test-harness/FlightDataGenerator.ts @@ -17,7 +17,7 @@ export const generateFlightData = ( const numRows = options.numRows ?? 100 const seed = typeof options.seed === "string" ? hashString(options.seed) : (options.seed ?? 42) - return Effect.gen(function*() { + return Effect.gen(function* () { const registry = yield* Types.GeneratorRegistry // Generate data for each field @@ -82,10 +82,7 @@ export const generateFlightData = ( expectedValues, schema } - }).pipe( - Effect.provide(GeneratorRegistry.Live), - Random.withSeed(seed) - ) + }).pipe(Effect.provide(GeneratorRegistry.Live), Random.withSeed(seed)) } const collectBuffers = ( @@ -137,10 +134,7 @@ const hasDataBuffer = (typeId: string): boolean => { } } -const getFieldConfig = ( - fieldName: string, - options: Types.BaseGeneratorOptions -): Types.FieldGeneratorConfig => { +const getFieldConfig = (fieldName: string, options: Types.BaseGeneratorOptions): Types.FieldGeneratorConfig => { const fieldConfig = options.fields?.[fieldName] ?? {} return { nullRate: fieldConfig.nullRate ?? options.defaultNullRate ?? 0.2, @@ -157,12 +151,10 @@ export const generateMultiBatchFlightData = ( schema: ArrowSchema, options: Types.MultiBatchGeneratorOptions ): Effect.Effect => { - const rowsPerBatch = Array.isArray(options.rowsPerBatch) - ? options.rowsPerBatch - : [options.rowsPerBatch] + const rowsPerBatch = Array.isArray(options.rowsPerBatch) ? options.rowsPerBatch : [options.rowsPerBatch] const seed = typeof options.seed === "string" ? hashString(options.seed) : (options.seed ?? 42) - return Effect.gen(function*() { + return Effect.gen(function* () { const registry = yield* Types.GeneratorRegistry const batches: Array = [] @@ -243,17 +235,14 @@ export const generateMultiBatchFlightData = ( schema, totalRows } - }).pipe( - Effect.provide(GeneratorRegistry.Live), - Random.withSeed(seed) - ) + }).pipe(Effect.provide(GeneratorRegistry.Live), Random.withSeed(seed)) } const hashString = (str: string): number => { let hash = 0 for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i) - hash = ((hash << 5) - hash) + char + hash = (hash << 5) - hash + char hash = hash & hash // Convert to 32-bit integer } return Math.abs(hash) diff --git a/packages/amp/test/arrow-test-harness/RandomUtils.ts b/packages/amp/test/arrow-test-harness/RandomUtils.ts index f3a86e7..3cf542a 100644 --- a/packages/amp/test/arrow-test-harness/RandomUtils.ts +++ b/packages/amp/test/arrow-test-harness/RandomUtils.ts @@ -17,7 +17,7 @@ import * as Random from "effect/Random" * Generate a random bigint in the range [min, max] inclusive. */ export const nextBigInt = (min: bigint, max: bigint): Effect.Effect => - Effect.gen(function*() { + Effect.gen(function* () { const range = max - min + 1n // For ranges that fit in a safe integer, use simple approach if (range <= BigInt(Number.MAX_SAFE_INTEGER)) { @@ -25,8 +25,8 @@ export const nextBigInt = (min: bigint, max: bigint): Effect.Effect => return min + BigInt(randomValue) } // For larger ranges, combine multiple random values - const high = yield* Random.nextIntBetween(0, 0x7FFFFFFF) - const low = yield* Random.nextIntBetween(0, 0xFFFFFFFF) + const high = yield* Random.nextIntBetween(0, 0x7fffffff) + const low = yield* Random.nextIntBetween(0, 0xffffffff) const combined = ((BigInt(high) << 32n) | BigInt(low >>> 0)) % range return min + combined }) @@ -53,7 +53,7 @@ const alphanumericChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0 * Generate a random alphanumeric string of the given length. */ export const nextString = (length: number): Effect.Effect => - Effect.gen(function*() { + Effect.gen(function* () { let result = "" for (let i = 0; i < length; i++) { const idx = yield* Random.nextIntBetween(0, alphanumericChars.length - 1) @@ -66,7 +66,7 @@ export const nextString = (length: number): Effect.Effect => * Generate a random string with length between min and max. */ export const nextStringBetween = (minLength: number, maxLength: number): Effect.Effect => - Effect.gen(function*() { + Effect.gen(function* () { const length = yield* Random.nextIntBetween(minLength, maxLength) return yield* nextString(length) }) @@ -75,7 +75,7 @@ export const nextStringBetween = (minLength: number, maxLength: number): Effect. * Generate random bytes of the given length. */ export const nextBytes = (length: number): Effect.Effect => - Effect.gen(function*() { + Effect.gen(function* () { const bytes = new Uint8Array(length) for (let i = 0; i < length; i++) { bytes[i] = yield* Random.nextIntBetween(0, 255) @@ -86,11 +86,8 @@ export const nextBytes = (length: number): Effect.Effect => /** * Generate random bytes with length between min and max. */ -export const nextBytesBetween = ( - minLength: number, - maxLength: number -): Effect.Effect => - Effect.gen(function*() { +export const nextBytesBetween = (minLength: number, maxLength: number): Effect.Effect => + Effect.gen(function* () { const length = yield* Random.nextIntBetween(minLength, maxLength) return yield* nextBytes(length) }) @@ -103,7 +100,7 @@ export const nextBytesBetween = ( * Generate a random Date within a reasonable range (1970-2100). */ export const nextDate = (): Effect.Effect => - Effect.gen(function*() { + Effect.gen(function* () { // Random timestamp between 1970-01-01 and 2100-01-01 const minMs = 0 const maxMs = 4102444800000 // 2100-01-01 @@ -128,11 +125,8 @@ export const nextTimeOfDayMs = (): Effect.Effect => Random.nextIntBetwee /** * Generate an array of random values. */ -export const nextArray = ( - length: number, - generator: Effect.Effect -): Effect.Effect> => - Effect.gen(function*() { +export const nextArray = (length: number, generator: Effect.Effect): Effect.Effect> => + Effect.gen(function* () { const result: Array = [] for (let i = 0; i < length; i++) { result.push(yield* generator) @@ -148,7 +142,7 @@ export const nextNullableArray = ( generator: Effect.Effect, nullRate: number ): Effect.Effect> => - Effect.gen(function*() { + Effect.gen(function* () { const result: Array = [] for (let i = 0; i < length; i++) { const isNull = yield* nextBoolWithProbability(nullRate) diff --git a/packages/amp/test/arrow-test-harness/SchemaBuilder.ts b/packages/amp/test/arrow-test-harness/SchemaBuilder.ts index b9d79fe..ebdfbfd 100644 --- a/packages/amp/test/arrow-test-harness/SchemaBuilder.ts +++ b/packages/amp/test/arrow-test-harness/SchemaBuilder.ts @@ -204,14 +204,7 @@ export class SchemaBuilder { const valueField = valueBuilder(new FieldBuilder("value")).build() // Map's child is an "entries" struct with "key" and "value" fields - const entriesField = new ArrowField( - "entries", - StructType, - false, - new Map(), - [keyField, valueField], - undefined - ) + const entriesField = new ArrowField("entries", StructType, false, new Map(), [keyField, valueField], undefined) return this.addField(name, new MapType(options?.keysSorted ?? false), { ...options, children: [entriesField] }) } diff --git a/packages/amp/test/arrow-test-harness/ValueComparison.ts b/packages/amp/test/arrow-test-harness/ValueComparison.ts index 472e378..b96e299 100644 --- a/packages/amp/test/arrow-test-harness/ValueComparison.ts +++ b/packages/amp/test/arrow-test-harness/ValueComparison.ts @@ -205,11 +205,7 @@ const datesEqual = (expected: unknown, actual: Date, unit: string): boolean => { return Number(expected as bigint) === actual.getTime() } -const timesEqual = ( - expected: number | bigint, - actual: number, - unit: string -): boolean => { +const timesEqual = (expected: number | bigint, actual: number, unit: string): boolean => { // Generator stores raw time value, reader converts to ms const toMs: Record = { "SECOND": 1000, @@ -232,10 +228,7 @@ const timestampsEqual = (expected: bigint, actual: Date, unit: string): boolean return expectedMs === actual.getTime() } -const durationsEqual = ( - expected: bigint, - actual: { value: bigint; unit: string } -): boolean => { +const durationsEqual = (expected: bigint, actual: { value: bigint; unit: string }): boolean => { return expected === actual.value } @@ -255,17 +248,12 @@ const intervalsEqual = (expected: unknown, actual: unknown, unit: string): boole return exp.months === act.months && exp.days === act.days && exp.nanoseconds === act.nanoseconds } -const listsEqual = ( - type: ArrowDataType, - expected: Array, - actual: Array -): boolean => { +const listsEqual = (type: ArrowDataType, expected: Array, actual: Array): boolean => { if (expected.length !== actual.length) return false // Get child type - for lists, the child is the item type - const childType = "children" in type - ? ((type as { children?: ReadonlyArray }).children?.[0]?.type) - : undefined + const childType = + "children" in type ? (type as { children?: ReadonlyArray }).children?.[0]?.type : undefined if (!childType) { // Fallback to simple comparison @@ -283,9 +271,7 @@ const structsEqual = ( expected: Record, actual: Record ): boolean => { - const children = "children" in type - ? ((type as { children?: ReadonlyArray }).children ?? []) - : [] + const children = "children" in type ? ((type as { children?: ReadonlyArray }).children ?? []) : [] for (const child of children) { if (!valuesEqual(child.type, expected[child.name], actual[child.name])) { @@ -295,11 +281,7 @@ const structsEqual = ( return true } -const mapsEqual = ( - type: ArrowDataType, - expected: Array, - actual: Array -): boolean => { +const mapsEqual = (type: ArrowDataType, expected: Array, actual: Array): boolean => { if (expected.length !== actual.length) return false for (let i = 0; i < expected.length; i++) { @@ -307,9 +289,8 @@ const mapsEqual = ( const actEntry = actual[i] as { key: unknown; value: unknown } // Get key/value types from map's entries child - const entriesField = "children" in type - ? ((type as { children?: ReadonlyArray }).children?.[0]) - : undefined + const entriesField = + "children" in type ? (type as { children?: ReadonlyArray }).children?.[0] : undefined if (entriesField && entriesField.children.length >= 2) { const keyType = entriesField.children[0].type @@ -325,10 +306,7 @@ const mapsEqual = ( return true } -const unionsEqual = ( - expected: unknown, - actual: unknown -): boolean => { +const unionsEqual = (expected: unknown, actual: unknown): boolean => { // For unions, we just compare the selected values directly // The type information of the selected variant would be needed for deep comparison // For now, use JSON comparison as a fallback diff --git a/packages/amp/test/arrow-test-harness/generators/decimal.ts b/packages/amp/test/arrow-test-harness/generators/decimal.ts index 74bfc70..c59f596 100644 --- a/packages/amp/test/arrow-test-harness/generators/decimal.ts +++ b/packages/amp/test/arrow-test-harness/generators/decimal.ts @@ -10,7 +10,7 @@ import type * as Types from "../Types.ts" export const decimalGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const type = field.type as Schema.DecimalType const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 diff --git a/packages/amp/test/arrow-test-harness/generators/nested.ts b/packages/amp/test/arrow-test-harness/generators/nested.ts index a3d614a..f704377 100644 --- a/packages/amp/test/arrow-test-harness/generators/nested.ts +++ b/packages/amp/test/arrow-test-harness/generators/nested.ts @@ -15,7 +15,7 @@ import * as Types from "../Types.ts" export const listGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const registry = yield* Types.GeneratorRegistry const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 const minItems = config.minLength ?? 0 @@ -36,9 +36,7 @@ export const listGenerator: Types.DataGenerator = { offsets.push(currentOffset) } else { // Handle case where minItems === maxItems (avoids empty range in nextIntBetween) - const listLength = minItems === maxItems - ? minItems - : yield* Random.nextIntBetween(minItems, maxItems) + const listLength = minItems === maxItems ? minItems : yield* Random.nextIntBetween(minItems, maxItems) currentOffset += listLength offsets.push(currentOffset) values.push([]) // placeholder @@ -79,7 +77,7 @@ export const listGenerator: Types.DataGenerator = { export const largeListGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const registry = yield* Types.GeneratorRegistry const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 const minItems = config.minLength ?? 0 @@ -99,9 +97,7 @@ export const largeListGenerator: Types.DataGenerator = { offsets.push(currentOffset) } else { // Handle case where minItems === maxItems (avoids empty range in nextIntBetween) - const listLength = minItems === maxItems - ? minItems - : yield* Random.nextIntBetween(minItems, maxItems) + const listLength = minItems === maxItems ? minItems : yield* Random.nextIntBetween(minItems, maxItems) currentOffset += BigInt(listLength) offsets.push(currentOffset) values.push([]) @@ -140,7 +136,7 @@ export const largeListGenerator: Types.DataGenerator = { export const fixedSizeListGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const registry = yield* Types.GeneratorRegistry const type = field.type as Schema.FixedSizeListType const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 @@ -192,7 +188,7 @@ export const fixedSizeListGenerator: Types.DataGenerator = { export const structGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const registry = yield* Types.GeneratorRegistry const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 const nestedConfig = config as Types.NestedFieldConfig @@ -246,7 +242,7 @@ export const structGenerator: Types.DataGenerator = { export const mapGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const registry = yield* Types.GeneratorRegistry const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 const minItems = config.minLength ?? 0 @@ -268,9 +264,7 @@ export const mapGenerator: Types.DataGenerator = { offsets.push(currentOffset) } else { // Handle case where minItems === maxItems (avoids empty range in nextIntBetween) - const mapSize = minItems === maxItems - ? minItems - : yield* Random.nextIntBetween(minItems, maxItems) + const mapSize = minItems === maxItems ? minItems : yield* Random.nextIntBetween(minItems, maxItems) currentOffset += mapSize offsets.push(currentOffset) values.push([]) @@ -329,7 +323,7 @@ export const mapGenerator: Types.DataGenerator = { export const unionGenerator: Types.DataGenerator = { generate: (field, numRows) => - Effect.gen(function*() { + Effect.gen(function* () { const registry = yield* Types.GeneratorRegistry const type = field.type as Schema.UnionType const isSparse = type.mode === "SPARSE" diff --git a/packages/amp/test/arrow-test-harness/generators/primitives.ts b/packages/amp/test/arrow-test-harness/generators/primitives.ts index b230e57..1a995dd 100644 --- a/packages/amp/test/arrow-test-harness/generators/primitives.ts +++ b/packages/amp/test/arrow-test-harness/generators/primitives.ts @@ -31,7 +31,7 @@ export const nullGenerator: Types.DataGenerator = { export const boolGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 const values: Array = [] @@ -75,7 +75,7 @@ const int64Ranges = { export const intGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const type = field.type as Schema.IntType const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 @@ -118,11 +118,12 @@ export const intGenerator: Types.DataGenerator = { } const { bitmap, nullCount } = BufferUtils.createValidityBitmap(values) - const data = type.bitWidth === 8 - ? BufferUtils.createInt8DataBuffer(values, type.isSigned) - : type.bitWidth === 16 - ? BufferUtils.createInt16DataBuffer(values, type.isSigned) - : BufferUtils.createInt32DataBuffer(values, type.isSigned) + const data = + type.bitWidth === 8 + ? BufferUtils.createInt8DataBuffer(values, type.isSigned) + : type.bitWidth === 16 + ? BufferUtils.createInt16DataBuffer(values, type.isSigned) + : BufferUtils.createInt32DataBuffer(values, type.isSigned) return { validity: bitmap, @@ -181,7 +182,7 @@ const FLOAT_RANGES = { export const floatGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const type = field.type as Schema.FloatingPointType const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 const includeSpecial = config.includeSpecialFloats ?? false @@ -209,11 +210,12 @@ export const floatGenerator: Types.DataGenerator = { } const { bitmap, nullCount } = BufferUtils.createValidityBitmap(values) - const data = type.precision === "HALF" - ? BufferUtils.createFloat16DataBuffer(values) - : type.precision === "SINGLE" - ? BufferUtils.createFloat32DataBuffer(values) - : BufferUtils.createFloat64DataBuffer(values) + const data = + type.precision === "HALF" + ? BufferUtils.createFloat16DataBuffer(values) + : type.precision === "SINGLE" + ? BufferUtils.createFloat32DataBuffer(values) + : BufferUtils.createFloat64DataBuffer(values) return { validity: bitmap, diff --git a/packages/amp/test/arrow-test-harness/generators/strings.ts b/packages/amp/test/arrow-test-harness/generators/strings.ts index 4110641..1bdfb91 100644 --- a/packages/amp/test/arrow-test-harness/generators/strings.ts +++ b/packages/amp/test/arrow-test-harness/generators/strings.ts @@ -14,7 +14,7 @@ import type * as Types from "../Types.ts" export const utf8Generator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 const minLength = config.minLength ?? 0 const maxLength = config.maxLength ?? 50 @@ -49,7 +49,7 @@ export const utf8Generator: Types.DataGenerator = { export const largeUtf8Generator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 const minLength = config.minLength ?? 0 const maxLength = config.maxLength ?? 50 @@ -84,7 +84,7 @@ export const largeUtf8Generator: Types.DataGenerator = { export const binaryGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 const minLength = config.minLength ?? 0 const maxLength = config.maxLength ?? 50 @@ -119,7 +119,7 @@ export const binaryGenerator: Types.DataGenerator = { export const largeBinaryGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 const minLength = config.minLength ?? 0 const maxLength = config.maxLength ?? 50 @@ -154,7 +154,7 @@ export const largeBinaryGenerator: Types.DataGenerator = { export const fixedSizeBinaryGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const type = field.type as Schema.FixedSizeBinaryType const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 const values: Array = [] diff --git a/packages/amp/test/arrow-test-harness/generators/temporal.ts b/packages/amp/test/arrow-test-harness/generators/temporal.ts index 3c291a7..49ae736 100644 --- a/packages/amp/test/arrow-test-harness/generators/temporal.ts +++ b/packages/amp/test/arrow-test-harness/generators/temporal.ts @@ -15,7 +15,7 @@ import type * as Types from "../Types.ts" export const dateGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const type = field.type as Schema.DateType const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 @@ -69,7 +69,7 @@ export const dateGenerator: Types.DataGenerator = { export const timeGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const type = field.type as Schema.TimeType const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 @@ -125,7 +125,7 @@ export const timeGenerator: Types.DataGenerator = { export const timestampGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const type = field.type as Schema.TimestampType const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 @@ -168,7 +168,7 @@ export const timestampGenerator: Types.DataGenerator = { export const durationGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const type = field.type as Schema.DurationType const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 @@ -207,7 +207,7 @@ export const durationGenerator: Types.DataGenerator = { export const intervalGenerator: Types.DataGenerator = { generate: (field, numRows, config) => - Effect.gen(function*() { + Effect.gen(function* () { const type = field.type as Schema.IntervalType const nullRate = field.nullable ? (config.nullRate ?? 0.2) : 0 diff --git a/packages/amp/test/cdc-stream/memory-batch-store.test.ts b/packages/amp/test/cdc-stream/memory-batch-store.test.ts index ced9349..166576a 100644 --- a/packages/amp/test/cdc-stream/memory-batch-store.test.ts +++ b/packages/amp/test/cdc-stream/memory-batch-store.test.ts @@ -14,7 +14,7 @@ import * as Effect from "effect/Effect" describe("BatchStore.append + load", () => { it.effect("stores data retrievable by load", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* BatchStore const data = [{ block: 1, address: "0xabc" }] @@ -22,18 +22,20 @@ describe("BatchStore.append + load", () => { const result = yield* store.load(1 as TransactionId) expect(result).toEqual(data) - }).pipe(Effect.provide(InMemoryBatchStore.layer))) + }).pipe(Effect.provide(InMemoryBatchStore.layer)) + ) it.effect("load returns undefined for missing IDs", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* BatchStore const result = yield* store.load(999 as TransactionId) expect(result).toBeUndefined() - }).pipe(Effect.provide(InMemoryBatchStore.layer))) + }).pipe(Effect.provide(InMemoryBatchStore.layer)) + ) it.effect("stores multiple batches independently", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* BatchStore const data1 = [{ block: 1 }] const data2 = [{ block: 2 }] @@ -43,7 +45,8 @@ describe("BatchStore.append + load", () => { expect(yield* store.load(1 as TransactionId)).toEqual(data1) expect(yield* store.load(2 as TransactionId)).toEqual(data2) - }).pipe(Effect.provide(InMemoryBatchStore.layer))) + }).pipe(Effect.provide(InMemoryBatchStore.layer)) + ) }) // ============================================================================= @@ -52,7 +55,7 @@ describe("BatchStore.append + load", () => { describe("BatchStore.seek", () => { it.effect("returns sorted IDs within range", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* BatchStore yield* store.append([{ a: 1 }], 3 as TransactionId) @@ -66,10 +69,11 @@ describe("BatchStore.seek", () => { } const result = yield* store.seek(range) expect(result).toEqual([1, 2, 3]) - }).pipe(Effect.provide(InMemoryBatchStore.layer))) + }).pipe(Effect.provide(InMemoryBatchStore.layer)) + ) it.effect("returns empty array for empty range", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* BatchStore yield* store.append([{ a: 1 }], 1 as TransactionId) @@ -80,10 +84,11 @@ describe("BatchStore.seek", () => { } const result = yield* store.seek(range) expect(result).toEqual([]) - }).pipe(Effect.provide(InMemoryBatchStore.layer))) + }).pipe(Effect.provide(InMemoryBatchStore.layer)) + ) it.effect("includes boundary IDs", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* BatchStore yield* store.append([{ a: 1 }], 1 as TransactionId) @@ -96,7 +101,8 @@ describe("BatchStore.seek", () => { } const result = yield* store.seek(range) expect(result).toEqual([1, 5, 10]) - }).pipe(Effect.provide(InMemoryBatchStore.layer))) + }).pipe(Effect.provide(InMemoryBatchStore.layer)) + ) }) // ============================================================================= @@ -105,7 +111,7 @@ describe("BatchStore.seek", () => { describe("BatchStore.prune", () => { it.effect("removes batches up to cutoff (inclusive)", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* BatchStore yield* store.append([{ a: 1 }], 1 as TransactionId) @@ -117,10 +123,11 @@ describe("BatchStore.prune", () => { expect(yield* store.load(1 as TransactionId)).toBeUndefined() expect(yield* store.load(2 as TransactionId)).toBeUndefined() expect(yield* store.load(3 as TransactionId)).toEqual([{ a: 3 }]) - }).pipe(Effect.provide(InMemoryBatchStore.layer))) + }).pipe(Effect.provide(InMemoryBatchStore.layer)) + ) it.effect("is idempotent", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* BatchStore yield* store.append([{ a: 1 }], 1 as TransactionId) @@ -131,5 +138,6 @@ describe("BatchStore.prune", () => { expect(yield* store.load(1 as TransactionId)).toBeUndefined() expect(yield* store.load(2 as TransactionId)).toEqual([{ a: 2 }]) - }).pipe(Effect.provide(InMemoryBatchStore.layer))) + }).pipe(Effect.provide(InMemoryBatchStore.layer)) + ) }) diff --git a/packages/amp/test/cdc-stream/stream.test.ts b/packages/amp/test/cdc-stream/stream.test.ts index d77ee05..a64eb9b 100644 --- a/packages/amp/test/cdc-stream/stream.test.ts +++ b/packages/amp/test/cdc-stream/stream.test.ts @@ -37,10 +37,7 @@ const makeBlockRange = (network: string, start: number, end: number): BlockRange /** * Create a mock CommitHandle that tracks whether commit was called. */ -const makeMockCommitHandle = ( - id: TransactionId, - commitRef: Ref.Ref> -): CommitHandle => ({ +const makeMockCommitHandle = (id: TransactionId, commitRef: Ref.Ref>): CommitHandle => ({ id, commit: Ref.update(commitRef, (ids) => [...ids, id]) }) @@ -51,38 +48,25 @@ const makeMockCommitHandle = ( const mockTransactionalStreamLayer = ( events: ReadonlyArray ): Layer.Layer => - Layer.succeed( - TransactionalStream, - { - streamTransactional: () => Stream.fromIterable(events), - forEach: () => Effect.void - } satisfies TransactionalStreamService - ) + Layer.succeed(TransactionalStream, { + streamTransactional: () => Stream.fromIterable(events), + forEach: () => Effect.void + } satisfies TransactionalStreamService) /** * Standard test layer: provides CdcStream only. */ -const makeTestLayer = ( - events: ReadonlyArray -) => - cdcStreamLayer.pipe( - Layer.provide(mockTransactionalStreamLayer(events)), - Layer.provide(InMemoryBatchStore.layer) - ) +const makeTestLayer = (events: ReadonlyArray) => + cdcStreamLayer.pipe(Layer.provide(mockTransactionalStreamLayer(events)), Layer.provide(InMemoryBatchStore.layer)) /** * Test layer that exposes both CdcStream AND BatchStore (for tests needing store access). * Uses Layer.merge so both services are available in the test context. */ -const makeTestLayerWithBatchStore = ( - events: ReadonlyArray -) => { +const makeTestLayerWithBatchStore = (events: ReadonlyArray) => { const batchStoreLayer = InMemoryBatchStore.layer return Layer.merge( - cdcStreamLayer.pipe( - Layer.provide(mockTransactionalStreamLayer(events)), - Layer.provide(batchStoreLayer) - ), + cdcStreamLayer.pipe(Layer.provide(mockTransactionalStreamLayer(events)), Layer.provide(batchStoreLayer)), batchStoreLayer ) } @@ -93,7 +77,7 @@ const makeTestLayerWithBatchStore = ( describe("CdcStream - Insert", () => { it.effect("Data events become Insert events with same data", () => - Effect.gen(function*() { + Effect.gen(function* () { const data = [{ block: 1, address: "0xabc" }] const ranges = [makeBlockRange("eth", 0, 10)] @@ -108,17 +92,23 @@ describe("CdcStream - Insert", () => { expect(cdcEvent.data).toEqual(data) expect(cdcEvent.ranges).toEqual(ranges) } - }).pipe(Effect.provide( - makeTestLayer([ - [dataEvent(1 as TransactionId, [{ block: 1, address: "0xabc" }], [makeBlockRange("eth", 0, 10)]), { - id: 1 as TransactionId, - commit: Effect.void - }] - ]) - ))) + }).pipe( + Effect.provide( + makeTestLayer([ + [ + dataEvent(1 as TransactionId, [{ block: 1, address: "0xabc" }], [makeBlockRange("eth", 0, 10)]), + { + id: 1 as TransactionId, + commit: Effect.void + } + ] + ]) + ) + ) + ) it.effect("Insert stores batch before emitting", () => - Effect.gen(function*() { + Effect.gen(function* () { const data = [{ block: 1 }] const cdc = yield* CdcStream @@ -130,14 +120,20 @@ describe("CdcStream - Insert", () => { // Verify batch was stored const stored = yield* batchStore.load(1 as TransactionId) expect(stored).toEqual(data) - }).pipe(Effect.provide( - makeTestLayerWithBatchStore([ - [dataEvent(1 as TransactionId, [{ block: 1 }], [makeBlockRange("eth", 0, 10)]), { - id: 1 as TransactionId, - commit: Effect.void - }] - ]) - ))) + }).pipe( + Effect.provide( + makeTestLayerWithBatchStore([ + [ + dataEvent(1 as TransactionId, [{ block: 1 }], [makeBlockRange("eth", 0, 10)]), + { + id: 1 as TransactionId, + commit: Effect.void + } + ] + ]) + ) + ) + ) }) // ============================================================================= @@ -146,7 +142,7 @@ describe("CdcStream - Insert", () => { describe("CdcStream - Delete", () => { it.effect("Undo events become Delete events with batch data", () => - Effect.gen(function*() { + Effect.gen(function* () { const data = [{ block: 1 }] const cdc = yield* CdcStream @@ -171,39 +167,54 @@ describe("CdcStream - Delete", () => { const next = yield* deleteEvent.batches.next expect(next).toBeUndefined() } - }).pipe(Effect.provide( - makeTestLayer([ - // First: Data event (stores batch) - [dataEvent(1 as TransactionId, [{ block: 1 }], [makeBlockRange("eth", 0, 10)]), { - id: 1 as TransactionId, - commit: Effect.void - }], - // Then: Undo event (should produce Delete with stored batch) - [undoEvent(2 as TransactionId, rewindCause(), { start: 1 as TransactionId, end: 1 as TransactionId }), { - id: 2 as TransactionId, - commit: Effect.void - }] - ]) - ))) + }).pipe( + Effect.provide( + makeTestLayer([ + // First: Data event (stores batch) + [ + dataEvent(1 as TransactionId, [{ block: 1 }], [makeBlockRange("eth", 0, 10)]), + { + id: 1 as TransactionId, + commit: Effect.void + } + ], + // Then: Undo event (should produce Delete with stored batch) + [ + undoEvent(2 as TransactionId, rewindCause(), { start: 1 as TransactionId, end: 1 as TransactionId }), + { + id: 2 as TransactionId, + commit: Effect.void + } + ] + ]) + ) + ) + ) it.effect("Undo with no matching batches is skipped", () => - Effect.gen(function*() { + Effect.gen(function* () { const cdc = yield* CdcStream const results = yield* cdc.streamCdc("SELECT 1").pipe(Stream.runCollect) // Undo for range with no stored batches should be filtered out expect(results.length).toBe(0) - }).pipe(Effect.provide( - makeTestLayer([ - [undoEvent(1 as TransactionId, rewindCause(), { start: 10 as TransactionId, end: 20 as TransactionId }), { - id: 1 as TransactionId, - commit: Effect.void - }] - ]) - ))) + }).pipe( + Effect.provide( + makeTestLayer([ + [ + undoEvent(1 as TransactionId, rewindCause(), { start: 10 as TransactionId, end: 20 as TransactionId }), + { + id: 1 as TransactionId, + commit: Effect.void + } + ] + ]) + ) + ) + ) it.effect("Delete iterator loads lazily and skips missing", () => - Effect.gen(function*() { + Effect.gen(function* () { const cdc = yield* CdcStream const results = yield* cdc.streamCdc("SELECT 1").pipe(Stream.runCollect) @@ -227,22 +238,34 @@ describe("CdcStream - Delete", () => { const third = yield* deleteEvent.batches.next expect(third).toBeUndefined() } - }).pipe(Effect.provide( - makeTestLayer([ - [dataEvent(1 as TransactionId, [{ a: 1 }], [makeBlockRange("eth", 0, 5)]), { - id: 1 as TransactionId, - commit: Effect.void - }], - [dataEvent(3 as TransactionId, [{ a: 3 }], [makeBlockRange("eth", 6, 10)]), { - id: 3 as TransactionId, - commit: Effect.void - }], - [undoEvent(4 as TransactionId, rewindCause(), { start: 1 as TransactionId, end: 3 as TransactionId }), { - id: 4 as TransactionId, - commit: Effect.void - }] - ]) - ))) + }).pipe( + Effect.provide( + makeTestLayer([ + [ + dataEvent(1 as TransactionId, [{ a: 1 }], [makeBlockRange("eth", 0, 5)]), + { + id: 1 as TransactionId, + commit: Effect.void + } + ], + [ + dataEvent(3 as TransactionId, [{ a: 3 }], [makeBlockRange("eth", 6, 10)]), + { + id: 3 as TransactionId, + commit: Effect.void + } + ], + [ + undoEvent(4 as TransactionId, rewindCause(), { start: 1 as TransactionId, end: 3 as TransactionId }), + { + id: 4 as TransactionId, + commit: Effect.void + } + ] + ]) + ) + ) + ) }) // ============================================================================= @@ -251,52 +274,56 @@ describe("CdcStream - Delete", () => { describe("CdcStream - Watermark", () => { it.effect("Watermark events are not exposed to consumer", () => - Effect.gen(function*() { + Effect.gen(function* () { const cdc = yield* CdcStream const results = yield* cdc.streamCdc("SELECT 1").pipe(Stream.runCollect) // Watermark should be filtered out expect(results.length).toBe(0) - }).pipe(Effect.provide( - makeTestLayer([ - [watermarkEvent(1 as TransactionId, [makeBlockRange("eth", 0, 10)], null), { - id: 1 as TransactionId, - commit: Effect.void - }] - ]) - ))) + }).pipe( + Effect.provide( + makeTestLayer([ + [ + watermarkEvent(1 as TransactionId, [makeBlockRange("eth", 0, 10)], null), + { + id: 1 as TransactionId, + commit: Effect.void + } + ] + ]) + ) + ) + ) it.effect("Watermark auto-commits", () => - Effect.gen(function*() { + Effect.gen(function* () { // Create a shared commitRef that both the mock and test can access const commitRef = yield* Ref.make>([]) const commit = makeMockCommitHandle(1 as TransactionId, commitRef) const testLayer = cdcStreamLayer.pipe( - Layer.provide(Layer.succeed( - TransactionalStream, - { + Layer.provide( + Layer.succeed(TransactionalStream, { streamTransactional: () => - Stream.make( - [watermarkEvent(1 as TransactionId, [makeBlockRange("eth", 0, 10)], null), commit] as const - ), + Stream.make([watermarkEvent(1 as TransactionId, [makeBlockRange("eth", 0, 10)], null), commit] as const), forEach: () => Effect.void - } satisfies TransactionalStreamService - )), + } satisfies TransactionalStreamService) + ), Layer.provide(InMemoryBatchStore.layer) ) - yield* Effect.gen(function*() { + yield* Effect.gen(function* () { const cdc = yield* CdcStream yield* cdc.streamCdc("SELECT 1").pipe(Stream.runDrain) }).pipe(Effect.provide(testLayer)) const committed = yield* Ref.get(commitRef) expect(committed).toEqual([1]) - })) + }) + ) it.effect("Watermark triggers prune when prune point present", () => - Effect.gen(function*() { + Effect.gen(function* () { const batchStore = yield* BatchStore const cdc = yield* CdcStream @@ -311,14 +338,20 @@ describe("CdcStream - Watermark", () => { expect(yield* batchStore.load(1 as TransactionId)).toBeUndefined() expect(yield* batchStore.load(2 as TransactionId)).toBeUndefined() expect(yield* batchStore.load(3 as TransactionId)).toEqual([{ a: 3 }]) - }).pipe(Effect.provide( - makeTestLayerWithBatchStore([ - [watermarkEvent(5 as TransactionId, [makeBlockRange("eth", 0, 10)], 2 as TransactionId), { - id: 5 as TransactionId, - commit: Effect.void - }] - ]) - ))) + }).pipe( + Effect.provide( + makeTestLayerWithBatchStore([ + [ + watermarkEvent(5 as TransactionId, [makeBlockRange("eth", 0, 10)], 2 as TransactionId), + { + id: 5 as TransactionId, + commit: Effect.void + } + ] + ]) + ) + ) + ) }) // ============================================================================= @@ -327,31 +360,29 @@ describe("CdcStream - Watermark", () => { describe("CdcStream.forEach", () => { it.effect("auto-commits after handler succeeds", () => - Effect.gen(function*() { + Effect.gen(function* () { const commitRef = yield* Ref.make>([]) const commit = makeMockCommitHandle(1 as TransactionId, commitRef) const handlerEvents = yield* Ref.make>([]) const testLayer = cdcStreamLayer.pipe( - Layer.provide(Layer.succeed( - TransactionalStream, - { + Layer.provide( + Layer.succeed(TransactionalStream, { streamTransactional: () => - Stream.make( - [dataEvent(1 as TransactionId, [{ block: 1 }], [makeBlockRange("eth", 0, 10)]), commit] as const - ), + Stream.make([ + dataEvent(1 as TransactionId, [{ block: 1 }], [makeBlockRange("eth", 0, 10)]), + commit + ] as const), forEach: () => Effect.void - } satisfies TransactionalStreamService - )), + } satisfies TransactionalStreamService) + ), Layer.provide(InMemoryBatchStore.layer) ) - yield* Effect.gen(function*() { + yield* Effect.gen(function* () { const cdc = yield* CdcStream - yield* cdc.forEach( - "SELECT 1", - { retention: 128 }, - (event) => Ref.update(handlerEvents, (events) => [...events, event._tag]) + yield* cdc.forEach("SELECT 1", { retention: 128 }, (event) => + Ref.update(handlerEvents, (events) => [...events, event._tag]) ) }).pipe(Effect.provide(testLayer)) @@ -361,5 +392,6 @@ describe("CdcStream.forEach", () => { // Verify commit was called (forEach auto-commits) const committed = yield* Ref.get(commitRef) expect(committed).toEqual([1]) - })) + }) + ) }) diff --git a/packages/amp/test/internal/explain/parser.test.ts b/packages/amp/test/internal/explain/parser.test.ts index 27a5896..b3fd33e 100644 --- a/packages/amp/test/internal/explain/parser.test.ts +++ b/packages/amp/test/internal/explain/parser.test.ts @@ -7,8 +7,8 @@ const cell = (row: ExplainRow, key: string): unknown => (row as Record { it("returns an empty list for empty input", () => { @@ -24,12 +24,7 @@ describe("parsePlan", () => { expect(root.name).toBe("CoalescePartitionsExec") expect(root.depth).toBe(0) expect(root.properties).toEqual({ fetch: "10" }) - expect(Object.keys(root.metrics)).toEqual([ - "output_rows", - "elapsed_compute", - "output_bytes", - "output_batches" - ]) + expect(Object.keys(root.metrics)).toEqual(["output_rows", "elapsed_compute", "output_bytes", "output_batches"]) const child = nodes[1]! expect(child.name).toBe("DataSourceExec") diff --git a/packages/amp/test/protocol-stream/reorg.test.ts b/packages/amp/test/protocol-stream/reorg.test.ts index f9c8479..aedeae2 100644 --- a/packages/amp/test/protocol-stream/reorg.test.ts +++ b/packages/amp/test/protocol-stream/reorg.test.ts @@ -46,12 +46,7 @@ const makeHash = (network: string, block: number, epoch: number = 0): BlockHash * Creates a BlockRange for testing. * Automatically generates hashes based on network, block numbers, and epoch. */ -const makeBlockRange = ( - network: string, - start: number, - end: number, - epoch: number = 0 -): BlockRange => ({ +const makeBlockRange = (network: string, start: number, end: number, epoch: number = 0): BlockRange => ({ network: network as Network, numbers: { start: start as BlockNumber, @@ -101,11 +96,11 @@ const detectReorgs = ( // Skip identical ranges (watermarks can repeat) if ( - incomingRange.network === prevRange.network && - incomingRange.numbers.start === prevRange.numbers.start && - incomingRange.numbers.end === prevRange.numbers.end && - incomingRange.hash === prevRange.hash && - incomingRange.prevHash === prevRange.prevHash + incomingRange.network === prevRange.network + && incomingRange.numbers.start === prevRange.numbers.start + && incomingRange.numbers.end === prevRange.numbers.end + && incomingRange.hash === prevRange.hash + && incomingRange.prevHash === prevRange.prevHash ) { continue } @@ -116,11 +111,7 @@ const detectReorgs = ( // Detect backwards jump (reorg indicator) if (incomingStart < prevEnd + 1) { invalidations.push( - makeInvalidationRange( - incomingRange.network, - incomingStart, - Math.max(incomingRange.numbers.end, prevEnd) - ) + makeInvalidationRange(incomingRange.network, incomingStart, Math.max(incomingRange.numbers.end, prevEnd)) ) } } @@ -140,7 +131,7 @@ describe("detectReorgs", () => { * Corresponds to Rust test: reorg_invalidates_affected_batches */ it.effect("detects reorg when backwards jump with hash mismatch occurs", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { // Previous: blocks 0-10 in epoch 0 const previous = [makeBlockRange("eth", 0, 10, 0)] @@ -153,13 +144,14 @@ describe("detectReorgs", () => { expect(invalidations[0]!.network).toBe("eth") expect(invalidations[0]!.start).toBe(5) // Start of reorg expect(invalidations[0]!.end).toBe(12) // End covers incoming range - })) + }) + ) /** * Tests that consecutive blocks with matching hash chains don't trigger reorg. */ it.effect("does not detect reorg for consecutive blocks with matching hashes", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { // Previous: blocks 0-10 in epoch 0 const previous = [makeBlockRange("eth", 0, 10, 0)] @@ -169,7 +161,8 @@ describe("detectReorgs", () => { const invalidations = detectReorgs(previous, incoming) expect(invalidations.length).toBe(0) - })) + }) + ) /** * Tests that watermarks don't invalidate previous batches when block ranges @@ -178,7 +171,7 @@ describe("detectReorgs", () => { * Corresponds to Rust test: reorg_does_not_invalidate_unaffected_batches */ it.effect("does not detect reorg for forward progress", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { // Previous: blocks 0-10 (finalized by watermark) const previous = [makeBlockRange("eth", 0, 10, 0)] @@ -188,19 +181,21 @@ describe("detectReorgs", () => { const invalidations = detectReorgs(previous, incoming) expect(invalidations.length).toBe(0) - })) + }) + ) /** * Tests identical range handling (watermark repeats). */ it.effect("does not detect reorg for identical ranges (watermark repeat)", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const range = makeBlockRange("eth", 0, 10, 0) const invalidations = detectReorgs([range], [range]) expect(invalidations.length).toBe(0) - })) + }) + ) }) // ============================================================================= @@ -215,12 +210,9 @@ describe("detectReorgs - multi-network", () => { * Corresponds to Rust test: multi_network_reorg_partial_invalidation */ it.effect("detects partial reorg when only one network reorgs", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { // Previous: both networks at blocks 0-10 - const previous = [ - makeBlockRange("eth", 0, 10, 0), - makeBlockRange("polygon", 0, 10, 0) - ] + const previous = [makeBlockRange("eth", 0, 10, 0), makeBlockRange("polygon", 0, 10, 0)] // Incoming: eth reorgs back to block 5, polygon continues normally const incoming = [ @@ -234,31 +226,27 @@ describe("detectReorgs - multi-network", () => { expect(invalidations.length).toBe(1) expect(invalidations[0]!.network).toBe("eth") expect(invalidations[0]!.start).toBe(5) - })) + }) + ) /** * Tests multi-network reorg where both networks reorg. */ it.effect("detects reorg on both networks when both reorg", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { // Previous: both networks at blocks 0-10 - const previous = [ - makeBlockRange("eth", 0, 10, 0), - makeBlockRange("polygon", 0, 10, 0) - ] + const previous = [makeBlockRange("eth", 0, 10, 0), makeBlockRange("polygon", 0, 10, 0)] // Incoming: both networks reorg - const incoming = [ - makeBlockRangeWithReorg("eth", 5, 12, 1, 1), - makeBlockRangeWithReorg("polygon", 7, 15, 1, 1) - ] + const incoming = [makeBlockRangeWithReorg("eth", 5, 12, 1, 1), makeBlockRangeWithReorg("polygon", 7, 15, 1, 1)] const invalidations = detectReorgs(previous, incoming) expect(invalidations.length).toBe(2) expect(invalidations.find((i) => i.network === "eth")).toBeDefined() expect(invalidations.find((i) => i.network === "polygon")).toBeDefined() - })) + }) + ) }) // ============================================================================= @@ -267,52 +255,58 @@ describe("detectReorgs - multi-network", () => { describe("invalidates", () => { it.effect("returns true when block range overlaps with invalidation range", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const invalidation = makeInvalidationRange("eth", 10, 20) const range = makeBlockRange("eth", 15, 25, 0) expect(invalidates(invalidation, range)).toBe(true) - })) + }) + ) it.effect("returns false when block range is before invalidation range", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const invalidation = makeInvalidationRange("eth", 20, 30) const range = makeBlockRange("eth", 5, 15, 0) expect(invalidates(invalidation, range)).toBe(false) - })) + }) + ) it.effect("returns false when block range is after invalidation range", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const invalidation = makeInvalidationRange("eth", 5, 15) const range = makeBlockRange("eth", 20, 30, 0) expect(invalidates(invalidation, range)).toBe(false) - })) + }) + ) it.effect("returns false when networks don't match", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const invalidation = makeInvalidationRange("eth", 10, 20) const range = makeBlockRange("polygon", 10, 20, 0) expect(invalidates(invalidation, range)).toBe(false) - })) + }) + ) it.effect("returns true when ranges are identical", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const invalidation = makeInvalidationRange("eth", 10, 20) const range = makeBlockRange("eth", 10, 20, 0) expect(invalidates(invalidation, range)).toBe(true) - })) + }) + ) it.effect("returns true when invalidation range contains block range", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const invalidation = makeInvalidationRange("eth", 5, 30) const range = makeBlockRange("eth", 10, 20, 0) expect(invalidates(invalidation, range)).toBe(true) - })) + }) + ) }) // ============================================================================= @@ -321,7 +315,7 @@ describe("invalidates", () => { describe("protocol messages", () => { it.effect("creates Data message with records and ranges", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const records = [{ id: 1 }, { id: 2 }] const ranges = [makeBlockRange("eth", 0, 10, 0)] @@ -330,20 +324,22 @@ describe("protocol messages", () => { expect(message._tag).toBe("Data") expect(message.data.length).toBe(2) expect(message.ranges.length).toBe(1) - })) + }) + ) it.effect("creates Watermark message with ranges", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const ranges = [makeBlockRange("eth", 0, 10, 0)] const message = watermark(ranges) expect(message._tag).toBe("Watermark") expect(message.ranges.length).toBe(1) - })) + }) + ) it.effect("creates Reorg message with previous, incoming, and invalidation", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const previous = [makeBlockRange("eth", 0, 10, 0)] const incoming = [makeBlockRangeWithReorg("eth", 5, 12, 1, 1)] const invalidation = [makeInvalidationRange("eth", 5, 12)] @@ -354,7 +350,8 @@ describe("protocol messages", () => { expect(message.previous.length).toBe(1) expect(message.incoming.length).toBe(1) expect(message.invalidation.length).toBe(1) - })) + }) + ) }) // ============================================================================= @@ -371,7 +368,7 @@ describe("deep reorg scenarios", () => { * Corresponds to Rust test: reorg_invalidates_multiple_consecutive_batches */ it.effect("calculates invalidation range for deep reorg", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { // Simulate state after multiple data batches: // Previous state shows blocks 31-40 were the last received const previous = [makeBlockRange("eth", 31, 40, 0)] @@ -386,7 +383,8 @@ describe("deep reorg scenarios", () => { expect(invalidations[0]!.start).toBe(15) // Reorg point // End is max of incoming.end (25) and previous.end (40) expect(invalidations[0]!.end).toBe(40) - })) + }) + ) /** * Tests consecutive reorgs - multiple reorgs in sequence. @@ -394,7 +392,7 @@ describe("deep reorg scenarios", () => { * Corresponds to Rust test: consecutive_reorgs_cumulative_invalidation */ it.effect("handles consecutive reorgs correctly", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { // First reorg scenario let previous = [makeBlockRange("eth", 21, 30, 0)] let incoming = [makeBlockRangeWithReorg("eth", 21, 25, 1, 1)] @@ -421,7 +419,8 @@ describe("deep reorg scenarios", () => { expect(invalidations.length).toBe(1) expect(invalidations[0]!.start).toBe(21) expect(invalidations[0]!.end).toBe(30) - })) + }) + ) /** * Tests reorg with backwards jump that succeeds validation. @@ -429,7 +428,7 @@ describe("deep reorg scenarios", () => { * Corresponds to Rust test: reorg_with_backwards_jump_succeeds */ it.effect("detects reorg with backwards jump", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { // Previous: blocks 11-20 const previous = [makeBlockRange("eth", 11, 20, 0)] @@ -442,7 +441,8 @@ describe("deep reorg scenarios", () => { expect(invalidations[0]!.network).toBe("eth") expect(invalidations[0]!.start).toBe(15) expect(invalidations[0]!.end).toBe(25) // max(incoming.end, previous.end) - })) + }) + ) }) // ============================================================================= @@ -451,17 +451,18 @@ describe("deep reorg scenarios", () => { describe("edge cases", () => { it.effect("handles empty previous ranges (first batch)", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const previous: Array = [] const incoming = [makeBlockRange("eth", 0, 10, 0)] const invalidations = detectReorgs(previous, incoming) expect(invalidations.length).toBe(0) - })) + }) + ) it.effect("handles new network in incoming (no reorg)", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const previous = [makeBlockRange("eth", 0, 10, 0)] const incoming = [ makeBlockRange("eth", 11, 20, 0), @@ -472,10 +473,11 @@ describe("edge cases", () => { // New networks don't cause reorg detection (handled by validateNetworks) expect(invalidations.length).toBe(0) - })) + }) + ) it.effect("handles single block ranges", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const previous = [makeBlockRange("eth", 10, 10, 0)] const incoming = [makeBlockRangeWithReorg("eth", 10, 12, 1, 1)] @@ -484,5 +486,6 @@ describe("edge cases", () => { expect(invalidations.length).toBe(1) expect(invalidations[0]!.start).toBe(10) expect(invalidations[0]!.end).toBe(12) - })) + }) + ) }) diff --git a/packages/amp/test/protocol-stream/validation.test.ts b/packages/amp/test/protocol-stream/validation.test.ts index 31ec6b6..dd7d32a 100644 --- a/packages/amp/test/protocol-stream/validation.test.ts +++ b/packages/amp/test/protocol-stream/validation.test.ts @@ -24,13 +24,7 @@ const HASH_A = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa const HASH_B = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" as BlockHash const HASH_C = "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" as BlockHash -const makeBlockRange = ( - network: string, - start: number, - end: number, - hash: string, - prevHash?: string -): BlockRange => ({ +const makeBlockRange = (network: string, start: number, end: number, hash: string, prevHash?: string): BlockRange => ({ network: network as Network, numbers: { start: start as BlockNumber, @@ -46,46 +40,52 @@ const makeBlockRange = ( describe("validatePrevHash", () => { it.effect("allows genesis block with no prevHash", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const range = makeBlockRange("eth", 0, 10, HASH_A) const result = yield* validatePrevHash(range) expect(result).toBeUndefined() - })) + }) + ) it.effect("allows genesis block with zero prevHash", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const range = makeBlockRange("eth", 0, 10, HASH_A, ZERO_HASH) const result = yield* validatePrevHash(range) expect(result).toBeUndefined() - })) + }) + ) it.effect("rejects genesis block with non-zero prevHash", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const range = makeBlockRange("eth", 0, 10, HASH_A, HASH_B) const result = yield* Effect.flip(validatePrevHash(range)) expect(result._tag).toBe("InvalidPrevHashError") - })) + }) + ) it.effect("allows non-genesis block with valid prevHash", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const range = makeBlockRange("eth", 100, 110, HASH_A, HASH_B) const result = yield* validatePrevHash(range) expect(result).toBeUndefined() - })) + }) + ) it.effect("rejects non-genesis block with no prevHash", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const range = makeBlockRange("eth", 100, 110, HASH_A) const result = yield* Effect.flip(validatePrevHash(range)) expect(result._tag).toBe("MissingPrevHashError") - })) + }) + ) it.effect("rejects non-genesis block with zero prevHash", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const range = makeBlockRange("eth", 100, 110, HASH_A, ZERO_HASH) const result = yield* Effect.flip(validatePrevHash(range)) expect(result._tag).toBe("MissingPrevHashError") - })) + }) + ) }) // ============================================================================= @@ -94,63 +94,50 @@ describe("validatePrevHash", () => { describe("validateNetworks", () => { it.effect("allows first batch with any networks", ({ expect }) => - Effect.gen(function*() { - const incoming = [ - makeBlockRange("eth", 0, 10, HASH_A), - makeBlockRange("polygon", 0, 10, HASH_B) - ] + Effect.gen(function* () { + const incoming = [makeBlockRange("eth", 0, 10, HASH_A), makeBlockRange("polygon", 0, 10, HASH_B)] const result = yield* validateNetworks([], incoming) expect(result).toBeUndefined() - })) + }) + ) it.effect("allows consistent networks across batches", ({ expect }) => - Effect.gen(function*() { - const previous = [ - makeBlockRange("eth", 0, 10, HASH_A), - makeBlockRange("polygon", 0, 10, HASH_B) - ] + Effect.gen(function* () { + const previous = [makeBlockRange("eth", 0, 10, HASH_A), makeBlockRange("polygon", 0, 10, HASH_B)] const incoming = [ makeBlockRange("eth", 11, 20, HASH_B, HASH_A), makeBlockRange("polygon", 11, 20, HASH_C, HASH_B) ] const result = yield* validateNetworks(previous, incoming) expect(result).toBeUndefined() - })) + }) + ) it.effect("rejects duplicate networks in batch", ({ expect }) => - Effect.gen(function*() { - const incoming = [ - makeBlockRange("eth", 0, 10, HASH_A), - makeBlockRange("eth", 0, 10, HASH_B) - ] + Effect.gen(function* () { + const incoming = [makeBlockRange("eth", 0, 10, HASH_A), makeBlockRange("eth", 0, 10, HASH_B)] const result = yield* Effect.flip(validateNetworks([], incoming)) expect(result._tag).toBe("DuplicateNetworkError") - })) + }) + ) it.effect("rejects network count change", ({ expect }) => - Effect.gen(function*() { - const previous = [ - makeBlockRange("eth", 0, 10, HASH_A) - ] - const incoming = [ - makeBlockRange("eth", 11, 20, HASH_B, HASH_A), - makeBlockRange("polygon", 0, 10, HASH_C) - ] + Effect.gen(function* () { + const previous = [makeBlockRange("eth", 0, 10, HASH_A)] + const incoming = [makeBlockRange("eth", 11, 20, HASH_B, HASH_A), makeBlockRange("polygon", 0, 10, HASH_C)] const result = yield* Effect.flip(validateNetworks(previous, incoming)) expect(result._tag).toBe("NetworkCountChangedError") - })) + }) + ) it.effect("rejects unexpected network", ({ expect }) => - Effect.gen(function*() { - const previous = [ - makeBlockRange("eth", 0, 10, HASH_A) - ] - const incoming = [ - makeBlockRange("polygon", 0, 10, HASH_B) - ] + Effect.gen(function* () { + const previous = [makeBlockRange("eth", 0, 10, HASH_A)] + const incoming = [makeBlockRange("polygon", 0, 10, HASH_B)] const result = yield* Effect.flip(validateNetworks(previous, incoming)) expect(result._tag).toBe("UnexpectedNetworkError") - })) + }) + ) }) // ============================================================================= @@ -159,56 +146,62 @@ describe("validateNetworks", () => { describe("validateConsecutiveness", () => { it.effect("allows first batch without validation", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const incoming = [makeBlockRange("eth", 0, 10, HASH_A)] const result = yield* validateConsecutiveness([], incoming) expect(result).toBeUndefined() - })) + }) + ) it.effect("allows consecutive blocks with matching hash chain", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const previous = [makeBlockRange("eth", 0, 10, HASH_A)] const incoming = [makeBlockRange("eth", 11, 20, HASH_B, HASH_A)] const result = yield* validateConsecutiveness(previous, incoming) expect(result).toBeUndefined() - })) + }) + ) it.effect("allows identical ranges (watermark repeat)", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const range = makeBlockRange("eth", 0, 10, HASH_A) const result = yield* validateConsecutiveness([range], [range]) expect(result).toBeUndefined() - })) + }) + ) it.effect("allows backwards jump with hash mismatch (reorg)", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const previous = [makeBlockRange("eth", 0, 10, HASH_A)] // Backwards jump (start=5 < prev.end+1=11) with different hash chain const incoming = [makeBlockRange("eth", 5, 12, HASH_C, HASH_B)] const result = yield* validateConsecutiveness(previous, incoming) expect(result).toBeUndefined() - })) + }) + ) it.effect("rejects consecutive blocks with hash mismatch", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const previous = [makeBlockRange("eth", 0, 10, HASH_A)] // Consecutive (start=11 == prev.end+1=11) but prevHash doesn't match const incoming = [makeBlockRange("eth", 11, 20, HASH_C, HASH_B)] const result = yield* Effect.flip(validateConsecutiveness(previous, incoming)) expect(result._tag).toBe("HashMismatchOnConsecutiveBlocksError") - })) + }) + ) it.effect("rejects backwards jump with matching hash (invalid reorg)", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const previous = [makeBlockRange("eth", 0, 10, HASH_A)] // Backwards jump but same hash chain - invalid const incoming = [makeBlockRange("eth", 5, 12, HASH_B, HASH_A)] const result = yield* Effect.flip(validateConsecutiveness(previous, incoming)) expect(result._tag).toBe("InvalidReorgError") - })) + }) + ) it.effect("rejects forward gap", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const previous = [makeBlockRange("eth", 0, 10, HASH_A)] // Gap: start=15 > prev.end+1=11 const incoming = [makeBlockRange("eth", 15, 20, HASH_B, HASH_A)] @@ -220,7 +213,8 @@ describe("validateConsecutiveness", () => { missingEnd: 14 }) ) - })) + }) + ) }) // ============================================================================= @@ -229,34 +223,38 @@ describe("validateConsecutiveness", () => { describe("validateAll", () => { it.effect("passes all validations for valid consecutive batch", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const previous = [makeBlockRange("eth", 100, 110, HASH_A, HASH_B)] const incoming = [makeBlockRange("eth", 111, 120, HASH_B, HASH_A)] const result = yield* validateAll(previous, incoming) expect(result).toBeUndefined() - })) + }) + ) it.effect("fails on prevHash validation", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const incoming = [makeBlockRange("eth", 100, 110, HASH_A)] // Missing prevHash const result = yield* Effect.flip(validateAll([], incoming)) expect(result._tag).toBe("MissingPrevHashError") - })) + }) + ) it.effect("fails on network validation", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const previous = [makeBlockRange("eth", 100, 110, HASH_A, HASH_B)] const incoming = [makeBlockRange("polygon", 100, 110, HASH_A, HASH_B)] const result = yield* Effect.flip(validateAll(previous, incoming)) expect(result._tag).toBe("UnexpectedNetworkError") - })) + }) + ) it.effect("fails on consecutiveness validation", ({ expect }) => - Effect.gen(function*() { + Effect.gen(function* () { const previous = [makeBlockRange("eth", 100, 110, HASH_A, HASH_B)] // Gap: start=115 > prev.end+1=111 const incoming = [makeBlockRange("eth", 115, 120, HASH_B, HASH_A)] const result = yield* Effect.flip(validateAll(previous, incoming)) expect(result._tag).toBe("GapError") - })) + }) + ) }) diff --git a/packages/amp/test/transactional-stream/algorithms.test.ts b/packages/amp/test/transactional-stream/algorithms.test.ts index eb355ff..ff0f181 100644 --- a/packages/amp/test/transactional-stream/algorithms.test.ts +++ b/packages/amp/test/transactional-stream/algorithms.test.ts @@ -96,10 +96,7 @@ describe("findRecoveryPoint", () => { ] // Reorg on eth at block 21, polygon at block 201 - const result = findRecoveryPoint(buffer, [ - makeInvalidation("eth", 21, 35), - makeInvalidation("polygon", 201, 350) - ]) + const result = findRecoveryPoint(buffer, [makeInvalidation("eth", 21, 35), makeInvalidation("polygon", 201, 350)]) expect(result?.[0]).toBe(2) }) @@ -111,10 +108,7 @@ describe("findRecoveryPoint", () => { }) it("handles invalidation that doesn't affect any watermark", () => { - const buffer = [ - makeWatermark(1, [makeBlockRange("eth", 0, 10)]), - makeWatermark(2, [makeBlockRange("eth", 11, 20)]) - ] + const buffer = [makeWatermark(1, [makeBlockRange("eth", 0, 10)]), makeWatermark(2, [makeBlockRange("eth", 11, 20)])] // Invalidation starts at block 100, well beyond our watermarks // But watermarks with start >= 100 would be affected @@ -252,15 +246,9 @@ describe("checkPartialReorg", () => { }) it("returns first partial network in multi-network scenario", () => { - const recoveryRanges = [ - makeBlockRange("eth", 0, 10), - makeBlockRange("polygon", 0, 100) - ] + const recoveryRanges = [makeBlockRange("eth", 0, 10), makeBlockRange("polygon", 0, 100)] // Both have partial reorg - const invalidation = [ - makeInvalidation("eth", 5, 20), - makeInvalidation("polygon", 50, 150) - ] + const invalidation = [makeInvalidation("eth", 5, 20), makeInvalidation("polygon", 50, 150)] const result = checkPartialReorg(recoveryRanges, invalidation) // Returns first one found (eth) @@ -292,12 +280,7 @@ describe("compressCommits", () => { }) it("collects single commit", () => { - const pending = [ - [ - 1 as TransactionId, - { ranges: [makeBlockRange("eth", 0, 10)], prune: undefined } - ] as const - ] + const pending = [[1 as TransactionId, { ranges: [makeBlockRange("eth", 0, 10)], prune: undefined }] as const] const result = compressCommits(pending) expect(result.insert).toHaveLength(1) diff --git a/packages/amp/test/transactional-stream/memory-store.test.ts b/packages/amp/test/transactional-stream/memory-store.test.ts index 3795928..1ca0fbb 100644 --- a/packages/amp/test/transactional-stream/memory-store.test.ts +++ b/packages/amp/test/transactional-stream/memory-store.test.ts @@ -32,16 +32,17 @@ const makeBlockRange = (network: string, start: number, end: number): BlockRange describe("InMemoryStateStore.layer", () => { it.effect("provides empty initial state", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const result = yield* store.load expect(result).toEqual(emptySnapshot) - }).pipe(Effect.provide(InMemoryStateStore.layer))) + }).pipe(Effect.provide(InMemoryStateStore.layer)) + ) }) describe("InMemoryStateStore.layerWithState", () => { it.effect("provides custom initial state", () => - Effect.gen(function*() { + Effect.gen(function* () { const initial: StateSnapshot = { buffer: [[5 as TransactionId, [makeBlockRange("eth", 0, 10)]]], next: 10 as TransactionId @@ -50,10 +51,15 @@ describe("InMemoryStateStore.layerWithState", () => { const store = yield* StateStore const result = yield* store.load expect(result).toEqual(initial) - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [[5 as TransactionId, [makeBlockRange("eth", 0, 10)]]], - next: 10 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [[5 as TransactionId, [makeBlockRange("eth", 0, 10)]]], + next: 10 as TransactionId + }) + ) + ) + ) }) // ============================================================================= @@ -62,17 +68,18 @@ describe("InMemoryStateStore.layerWithState", () => { describe("StateStore.advance", () => { it.effect("updates the next transaction ID", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore yield* store.advance(5 as TransactionId) const snapshot = yield* store.load expect(snapshot.next).toBe(5) - }).pipe(Effect.provide(InMemoryStateStore.layer))) + }).pipe(Effect.provide(InMemoryStateStore.layer)) + ) it.effect("can be called multiple times", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore yield* store.advance(1 as TransactionId) @@ -81,12 +88,13 @@ describe("StateStore.advance", () => { const snapshot = yield* store.load expect(snapshot.next).toBe(10) - }).pipe(Effect.provide(InMemoryStateStore.layer))) + }).pipe(Effect.provide(InMemoryStateStore.layer)) + ) }) describe("StateStore.commit", () => { it.effect("inserts watermarks to buffer", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore yield* store.commit({ @@ -102,10 +110,11 @@ describe("StateStore.commit", () => { expect(result).toHaveLength(2) expect(result[0]![0]).toBe(1) expect(result[1]![0]).toBe(2) - }).pipe(Effect.provide(InMemoryStateStore.layer))) + }).pipe(Effect.provide(InMemoryStateStore.layer)) + ) it.effect("prunes watermarks with ID <= prune point", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore yield* store.commit({ @@ -117,17 +126,22 @@ describe("StateStore.commit", () => { const result = snapshot.buffer expect(result).toHaveLength(1) expect(result[0]![0]).toBe(3) - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [ - [1 as TransactionId, [makeBlockRange("eth", 0, 10)]], - [2 as TransactionId, [makeBlockRange("eth", 11, 20)]], - [3 as TransactionId, [makeBlockRange("eth", 21, 30)]] - ], - next: 4 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [ + [1 as TransactionId, [makeBlockRange("eth", 0, 10)]], + [2 as TransactionId, [makeBlockRange("eth", 11, 20)]], + [3 as TransactionId, [makeBlockRange("eth", 21, 30)]] + ], + next: 4 as TransactionId + }) + ) + ) + ) it.effect("can insert and prune atomically", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore yield* store.commit({ @@ -139,18 +153,23 @@ describe("StateStore.commit", () => { const result = snapshot.buffer expect(result).toHaveLength(2) expect(result.map(([id]) => id)).toEqual([2, 3]) - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [ - [1 as TransactionId, [makeBlockRange("eth", 0, 10)]], - [2 as TransactionId, [makeBlockRange("eth", 11, 20)]] - ], - next: 3 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [ + [1 as TransactionId, [makeBlockRange("eth", 0, 10)]], + [2 as TransactionId, [makeBlockRange("eth", 11, 20)]] + ], + next: 3 as TransactionId + }) + ) + ) + ) }) describe("StateStore.truncate", () => { it.effect("removes watermarks with ID >= from", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore yield* store.truncate(2 as TransactionId) @@ -159,17 +178,22 @@ describe("StateStore.truncate", () => { const result = snapshot.buffer expect(result).toHaveLength(1) expect(result[0]![0]).toBe(1) - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [ - [1 as TransactionId, [makeBlockRange("eth", 0, 10)]], - [2 as TransactionId, [makeBlockRange("eth", 11, 20)]], - [3 as TransactionId, [makeBlockRange("eth", 21, 30)]] - ], - next: 4 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [ + [1 as TransactionId, [makeBlockRange("eth", 0, 10)]], + [2 as TransactionId, [makeBlockRange("eth", 11, 20)]], + [3 as TransactionId, [makeBlockRange("eth", 21, 30)]] + ], + next: 4 as TransactionId + }) + ) + ) + ) it.effect("removes all watermarks when truncating from 0", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore yield* store.truncate(0 as TransactionId) @@ -177,26 +201,36 @@ describe("StateStore.truncate", () => { const snapshot = yield* store.load const result = snapshot.buffer expect(result).toHaveLength(0) - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [ - [1 as TransactionId, [makeBlockRange("eth", 0, 10)]], - [2 as TransactionId, [makeBlockRange("eth", 11, 20)]] - ], - next: 3 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [ + [1 as TransactionId, [makeBlockRange("eth", 0, 10)]], + [2 as TransactionId, [makeBlockRange("eth", 11, 20)]] + ], + next: 3 as TransactionId + }) + ) + ) + ) it.effect("preserves next ID", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore yield* store.truncate(1 as TransactionId) const snapshot = yield* store.load expect(snapshot.next).toBe(10) - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [[1 as TransactionId, [makeBlockRange("eth", 0, 10)]]], - next: 10 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [[1 as TransactionId, [makeBlockRange("eth", 0, 10)]]], + next: 10 as TransactionId + }) + ) + ) + ) }) // ============================================================================= @@ -205,7 +239,7 @@ describe("StateStore.truncate", () => { describe("InMemoryStateStore.layerTest", () => { it.effect("exposes internal state via TestState", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const testState = yield* InMemoryStateStore.TestState @@ -218,10 +252,11 @@ describe("InMemoryStateStore.layerTest", () => { const result = yield* testState.get expect(result.next).toBe(5) expect(result.buffer).toHaveLength(1) - }).pipe(Effect.provide(InMemoryStateStore.layerTest))) + }).pipe(Effect.provide(InMemoryStateStore.layerTest)) + ) it.effect("provides StateStore via layer", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const testState = yield* InMemoryStateStore.TestState @@ -229,5 +264,6 @@ describe("InMemoryStateStore.layerTest", () => { const state = yield* testState.get expect(state.next).toBe(5) - }).pipe(Effect.provide(InMemoryStateStore.layerTest))) + }).pipe(Effect.provide(InMemoryStateStore.layerTest)) + ) }) diff --git a/packages/amp/test/transactional-stream/state-actor.test.ts b/packages/amp/test/transactional-stream/state-actor.test.ts index ac447a7..90d84ca 100644 --- a/packages/amp/test/transactional-stream/state-actor.test.ts +++ b/packages/amp/test/transactional-stream/state-actor.test.ts @@ -62,42 +62,57 @@ const reorgMessage = ( describe("StateActor.watermark", () => { it.effect("returns undefined for empty buffer", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) const result = yield* actor.watermark expect(result).toBeUndefined() - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [], - next: 0 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [], + next: 0 as TransactionId + }) + ) + ) + ) it.effect("returns last watermark from buffer", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) const result = yield* actor.watermark expect(result?.[0]).toBe(2) - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [ - [1 as TransactionId, [makeBlockRange("eth", 0, 10)]], - [2 as TransactionId, [makeBlockRange("eth", 11, 20)]] - ], - next: 3 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [ + [1 as TransactionId, [makeBlockRange("eth", 0, 10)]], + [2 as TransactionId, [makeBlockRange("eth", 11, 20)]] + ], + next: 3 as TransactionId + }) + ) + ) + ) }) describe("StateActor.peek", () => { it.effect("returns next transaction ID", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) const result = yield* actor.peek expect(result).toBe(10) - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [], - next: 10 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [], + next: 10 as TransactionId + }) + ) + ) + ) }) // ============================================================================= @@ -106,7 +121,7 @@ describe("StateActor.peek", () => { describe("StateActor.execute - Data", () => { it.effect("passes through data events with transaction ID", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) const [event] = yield* actor.execute({ @@ -118,13 +133,18 @@ describe("StateActor.execute - Data", () => { if (event._tag === "Data") { expect(event.data).toEqual([{ value: 1 }]) } - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [], - next: 5 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [], + next: 5 as TransactionId + }) + ) + ) + ) it.effect("increments transaction ID for each event", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) const [event1] = yield* actor.execute({ @@ -136,10 +156,15 @@ describe("StateActor.execute - Data", () => { message: dataMessage([{ value: 2 }], [makeBlockRange("eth", 11, 20)]) }) expect([event1.id, event2.id]).toEqual([0, 1]) - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [], - next: 0 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [], + next: 0 as TransactionId + }) + ) + ) + ) }) // ============================================================================= @@ -148,7 +173,7 @@ describe("StateActor.execute - Data", () => { describe("StateActor.execute - Watermark", () => { it.effect("emits watermark event with transaction ID", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) const [event] = yield* actor.execute({ @@ -157,13 +182,18 @@ describe("StateActor.execute - Watermark", () => { }) expect(event._tag).toBe("Watermark") expect(event.id).toBe(3) - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [], - next: 3 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [], + next: 3 as TransactionId + }) + ) + ) + ) it.effect("adds watermark to internal buffer", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) yield* actor.execute({ @@ -172,13 +202,18 @@ describe("StateActor.execute - Watermark", () => { }) const watermark = yield* actor.watermark expect(watermark?.[0]).toBe(0) - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [], - next: 0 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [], + next: 0 as TransactionId + }) + ) + ) + ) it.effect("computes prune point based on retention", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 50) // Add a watermark at block 200, which should prune old watermarks @@ -192,13 +227,18 @@ describe("StateActor.execute - Watermark", () => { // Watermarks 0 and 1 end at 10 and 20, both < 150, so prune up to 1 expect(event.prune._tag).toBe("Some") } - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [ - [0 as TransactionId, [makeBlockRange("eth", 0, 10)]], - [1 as TransactionId, [makeBlockRange("eth", 11, 20)]] - ], - next: 2 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [ + [0 as TransactionId, [makeBlockRange("eth", 0, 10)]], + [1 as TransactionId, [makeBlockRange("eth", 11, 20)]] + ], + next: 2 as TransactionId + }) + ) + ) + ) }) // ============================================================================= @@ -207,7 +247,7 @@ describe("StateActor.execute - Watermark", () => { describe("StateActor.execute - Rewind", () => { it.effect("emits Undo event with Rewind cause", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) const [event] = yield* actor.execute({ _tag: "Rewind" }) @@ -215,13 +255,18 @@ describe("StateActor.execute - Rewind", () => { if (event._tag === "Undo") { expect(event.cause._tag).toBe("Rewind") } - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [], - next: 5 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [], + next: 5 as TransactionId + }) + ) + ) + ) it.effect("computes invalidation range for empty buffer", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) const [event] = yield* actor.execute({ _tag: "Rewind" }) @@ -230,13 +275,18 @@ describe("StateActor.execute - Rewind", () => { expect(event.invalidate.start).toBe(0) expect(event.invalidate.end).toBe(4) } - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [], - next: 5 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [], + next: 5 as TransactionId + }) + ) + ) + ) it.effect("computes invalidation range from last watermark", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) const [event] = yield* actor.execute({ _tag: "Rewind" }) @@ -246,10 +296,15 @@ describe("StateActor.execute - Rewind", () => { expect(event.invalidate.start).toBe(4) expect(event.invalidate.end).toBe(6) } - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [[3 as TransactionId, [makeBlockRange("eth", 0, 10)]]], - next: 7 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [[3 as TransactionId, [makeBlockRange("eth", 0, 10)]]], + next: 7 as TransactionId + }) + ) + ) + ) }) // ============================================================================= @@ -258,7 +313,7 @@ describe("StateActor.execute - Rewind", () => { describe("StateActor.execute - Reorg", () => { it.effect("emits Undo event with Reorg cause", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) const [event] = yield* actor.execute({ @@ -273,13 +328,18 @@ describe("StateActor.execute - Reorg", () => { if (event._tag === "Undo") { expect(event.cause._tag).toBe("Reorg") } - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [[1 as TransactionId, [makeBlockRange("eth", 0, 10)]]], - next: 2 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [[1 as TransactionId, [makeBlockRange("eth", 0, 10)]]], + next: 2 as TransactionId + }) + ) + ) + ) it.effect("finds recovery point and truncates buffer", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) // Reorg at block 21 affects watermark id=3 @@ -296,17 +356,22 @@ describe("StateActor.execute - Reorg", () => { const result = yield* actor.watermark // Recovery point is id=2 (last unaffected), buffer truncated to keep only id=1,2 expect(result?.[0]).toBe(2) - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [ - [1 as TransactionId, [makeBlockRange("eth", 0, 10)]], - [2 as TransactionId, [makeBlockRange("eth", 11, 20)]], - [3 as TransactionId, [makeBlockRange("eth", 21, 30)]] - ], - next: 4 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [ + [1 as TransactionId, [makeBlockRange("eth", 0, 10)]], + [2 as TransactionId, [makeBlockRange("eth", 11, 20)]], + [3 as TransactionId, [makeBlockRange("eth", 21, 30)]] + ], + next: 4 as TransactionId + }) + ) + ) + ) it.effect("handles reorg with empty buffer", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) const [event] = yield* actor.execute({ @@ -321,15 +386,20 @@ describe("StateActor.execute - Reorg", () => { if (event._tag === "Undo") { expect(event.invalidate.start).toBe(0) } - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [], - next: 5 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [], + next: 5 as TransactionId + }) + ) + ) + ) it.effect("fails with UnrecoverableReorgError when all watermarks affected", () => - Effect.gen(function*() { + Effect.gen(function* () { const result = yield* Effect.exit( - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) @@ -350,18 +420,23 @@ describe("StateActor.execute - Reorg", () => { // Check that it's an UnrecoverableReorgError expect(error).toBeDefined() } - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [ - [1 as TransactionId, [makeBlockRange("eth", 100, 110)]], - [2 as TransactionId, [makeBlockRange("eth", 111, 120)]] - ], - next: 3 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [ + [1 as TransactionId, [makeBlockRange("eth", 100, 110)]], + [2 as TransactionId, [makeBlockRange("eth", 111, 120)]] + ], + next: 3 as TransactionId + }) + ) + ) + ) it.effect("fails with PartialReorgError when reorg point falls within watermark range", () => - Effect.gen(function*() { + Effect.gen(function* () { const result = yield* Effect.exit( - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) @@ -377,10 +452,15 @@ describe("StateActor.execute - Reorg", () => { }) ) expect(result._tag).toBe("Failure") - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [[1 as TransactionId, [makeBlockRange("eth", 0, 20)]]], - next: 2 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [[1 as TransactionId, [makeBlockRange("eth", 0, 20)]]], + next: 2 as TransactionId + }) + ) + ) + ) }) // ============================================================================= @@ -389,7 +469,7 @@ describe("StateActor.execute - Reorg", () => { describe("StateActor.commit", () => { it.effect("commits watermarks via commit handle", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) const [, handle] = yield* actor.execute({ @@ -402,13 +482,18 @@ describe("StateActor.commit", () => { // The watermark should now be committed const result = yield* actor.watermark expect(result?.[0]).toBe(0) - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [], - next: 0 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [], + next: 0 as TransactionId + }) + ) + ) + ) it.effect("batches multiple commits", () => - Effect.gen(function*() { + Effect.gen(function* () { const store = yield* StateStore const actor = yield* makeStateActor(store, 128) // Execute multiple watermarks without committing @@ -426,8 +511,13 @@ describe("StateActor.commit", () => { const watermark = yield* actor.watermark expect(watermark?.[0]).toBe(1) - }).pipe(Effect.provide(InMemoryStateStore.layerWithState({ - buffer: [], - next: 0 as TransactionId - })))) + }).pipe( + Effect.provide( + InMemoryStateStore.layerWithState({ + buffer: [], + next: 0 as TransactionId + }) + ) + ) + ) }) diff --git a/packages/cli/package.json b/packages/cli/package.json index 187bd6f..1c63a29 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -55,11 +55,11 @@ "effect": "catalog:" }, "devDependencies": { - "@connectrpc/connect": "^2.1.1", + "@connectrpc/connect": "^2.2.0", "@effect/platform-node": "catalog:", "effect": "catalog:" }, "dependencies": { - "open": "^11.0.0" + "open": "^11.0.4" } } diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 26ab4f2..82f4957 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -11,16 +11,14 @@ import PackageJson from "../package.json" with { type: "json" } import { AuthCommand } from "./commands/auth.ts" import { QueryCommand } from "./commands/query.ts" -const RootCommand = Command.make("amp").pipe( - Command.withSubcommands([AuthCommand, QueryCommand]) -) +const RootCommand = Command.make("amp").pipe(Command.withSubcommands([AuthCommand, QueryCommand])) const run = Command.run(RootCommand, { version: PackageJson["version"] }) const CliCacheLayer = Layer.unwrap( - Effect.gen(function*() { + Effect.gen(function* () { const path = yield* Path.Path const homeDirectory = NodeOS.homedir() @@ -32,17 +30,8 @@ const CliCacheLayer = Layer.unwrap( const HttpClientLayer = FetchHttpClient.layer -const AuthLayer = Auth.layer.pipe( - Layer.provide(CliCacheLayer), - Layer.provide(HttpClientLayer) -) +const AuthLayer = Auth.layer.pipe(Layer.provide(CliCacheLayer), Layer.provide(HttpClientLayer)) -const MainLayer = Layer.mergeAll( - AuthLayer, - HttpClientLayer -).pipe( - Layer.provideMerge(NodeServices.layer), - Layer.orDie -) +const MainLayer = Layer.mergeAll(AuthLayer, HttpClientLayer).pipe(Layer.provideMerge(NodeServices.layer), Layer.orDie) export const Cli = Effect.provide(run, MainLayer) diff --git a/packages/cli/src/commands/auth/login.ts b/packages/cli/src/commands/auth/login.ts index a26aa52..31531ed 100644 --- a/packages/cli/src/commands/auth/login.ts +++ b/packages/cli/src/commands/auth/login.ts @@ -27,7 +27,7 @@ export class LoginCommandError extends Data.TaggedError("LoginCommandError")<{ } const handleLoginCommand = Effect.fnUntraced( - function*() { + function* () { const auth = yield* Auth.Auth const authInfo = yield* auth.getCachedAuthInfo @@ -41,24 +41,21 @@ const handleLoginCommand = Effect.fnUntraced( // Perform OAuth2 PKCE flow const { codeChallenge, codeVerifier } = yield* auth.createChallenge - const { - expiresIn, - deviceCode, - interval, - userCode, - verificationUri - } = yield* auth.requestDeviceAuthorization(codeChallenge) + const { expiresIn, deviceCode, interval, userCode, verificationUri } = + yield* auth.requestDeviceAuthorization(codeChallenge) // Show the user the OAuth2 PKCE code - yield* Console.error(String.stripMargin( - `|Copy the following verification code and enter it in your browser: + yield* Console.error( + String.stripMargin( + `|Copy the following verification code and enter it in your browser: | | ${userCode} |` - )) + ) + ) // Ask if we should auto-open the user's browser - const autoOpenBrowser = yield* Prompt.confirm({ + const autoOpenBrowser = yield* Prompt.Confirm({ message: "Would you like to open your browser automatically?", initial: true }) @@ -69,31 +66,36 @@ const handleLoginCommand = Effect.fnUntraced( // If so, attempt to open the browser, falling back to a useful message yield* Effect.tryPromise(() => Open(verificationUri, { wait: false })).pipe( Effect.catchCause(() => - Console.error(String.stripMargin( - `|If the browser window does not open automatically, enter the verification code into the following URL: + Console.error( + String.stripMargin( + `|If the browser window does not open automatically, enter the verification code into the following URL: | | ${verificationUri} |` - )) + ) + ) ) ) } else { // If not, indicate that the user show navigate to the verification URL - yield* Console.error(String.stripMargin( - `|Enter the verification code into the following URL: + yield* Console.error( + String.stripMargin( + `|Enter the verification code into the following URL: | | ${verificationUri} |` - )) + ) + ) } // Initially starts polling with a faster exponential backoff (1s, 1.5s, 2.25s, ...), // but then caps at the server's requested interval, setting the maximum // number of polling attempts based on the device code's lifetime - const pollingSchedule = Schedule.exponential("1 second", 1.5).pipe( - Schedule.both(Schedule.spaced(Duration.seconds(interval))), - Schedule.either(Schedule.recurs(Math.floor(expiresIn / interval))) - ) + const pollingSchedule = Schedule.max([ + Schedule.exponential("1 second", 1.5), + Schedule.spaced(Duration.seconds(interval)), + Schedule.recurs(Math.floor(expiresIn / interval)) + ]) // Show a spinner while we wait const spinnerFiber = yield* Effect.forkChild(showSpinner("Waiting for the user to authenticate...")) @@ -128,7 +130,7 @@ export const LoginCommand = Command.make("login").pipe( const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] -const showSpinner = Effect.fnUntraced(function*(message: string) { +const showSpinner = Effect.fnUntraced(function* (message: string) { let index = 0 return yield* Effect.sync(() => { const frame = SPINNER_FRAMES[index] diff --git a/packages/cli/src/commands/auth/logout.ts b/packages/cli/src/commands/auth/logout.ts index 3cf91a0..8903ff1 100644 --- a/packages/cli/src/commands/auth/logout.ts +++ b/packages/cli/src/commands/auth/logout.ts @@ -15,10 +15,10 @@ export class LogoutCommandError extends Data.TaggedError("LogoutCommandError")<{ } const handleLogoutCommand = Effect.fnUntraced( - function*() { + function* () { const auth = yield* Auth.Auth - const shouldLogout = yield* Prompt.confirm({ + const shouldLogout = yield* Prompt.Confirm({ message: "Are you sure you want to logout of Amp?", initial: false }) diff --git a/packages/cli/src/commands/auth/token.ts b/packages/cli/src/commands/auth/token.ts index c0714d7..696409e 100644 --- a/packages/cli/src/commands/auth/token.ts +++ b/packages/cli/src/commands/auth/token.ts @@ -26,89 +26,83 @@ export class TokenCommandError extends Data.TaggedError("TokenCommandError")<{ override readonly [Runtime.errorReported] = false } -const audienceFlag = Flag.string("audience").pipe( +const audienceFlag = Flag.String("audience").pipe( Flag.withAlias("a"), - Flag.withDescription( - "URLs that are valid to use the generated access token. " + - "Becomes the JWT aud value" - ), + Flag.withDescription("URLs that are valid to use the generated access token. Becomes the JWT aud value"), Flag.atLeast(0) ) -const durationArg = Argument.string("duration").pipe( +const durationArg = Argument.String("duration").pipe( Argument.withDescription( - "Duration of the generated access token before it expires. " + - "Ex: \"7 days\", \"30 days\", \"1 hour\"" + 'Duration of the generated access token before it expires. Ex: "7 days", "30 days", "1 hour"' ), Argument.withSchema(Models.TokenDuration) ) -const handleTokenCommand = Effect.fnUntraced(function*({ audience, duration }: { - readonly audience: Array - readonly duration: Models.TokenDuration -}) { - const auth = yield* Auth.Auth +const handleTokenCommand = Effect.fnUntraced( + function* ({ audience, duration }: { readonly audience: Array; readonly duration: Models.TokenDuration }) { + const auth = yield* Auth.Auth - const authInfo = yield* auth.getCachedAuthInfo.pipe( - Effect.flatMap(Effect.fromOption), - Effect.catchTag( - "NoSuchElementError", - Effect.fnUntraced(function*() { - const errorMessage = [ - "You must be authenticated with Amp to generate an access token.", - "Run \"amp auth login\" to authenticate." - ].join(" ") - yield* Console.error(errorMessage) - return yield* new TokenCommandError({}) - }) + const authInfo = yield* auth.getCachedAuthInfo.pipe( + Effect.flatMap(Effect.fromOption), + Effect.catchTag( + "NoSuchElementError", + Effect.fnUntraced(function* () { + const errorMessage = [ + "You must be authenticated with Amp to generate an access token.", + 'Run "amp auth login" to authenticate.' + ].join(" ") + yield* Console.error(errorMessage) + return yield* new TokenCommandError({}) + }) + ) ) - ) - const response = yield* auth.generateAccessToken({ authInfo, audience, duration }).pipe( - Effect.catch(Effect.fnUntraced(function*(error) { - const userMessage = AuthError.getUserMessage(error) - const userSuggestion = AuthError.getUserSuggestion(error) - const errorMessage = `${userMessage}. ${userSuggestion}` - yield* Console.error(errorMessage) - return yield* new TokenCommandError({}) - })) - ) + const response = yield* auth.generateAccessToken({ authInfo, audience, duration }).pipe( + Effect.catch( + Effect.fnUntraced(function* (error) { + const userMessage = AuthError.getUserMessage(error) + const userSuggestion = AuthError.getUserSuggestion(error) + const errorMessage = `${userMessage}. ${userSuggestion}` + yield* Console.error(errorMessage) + return yield* new TokenCommandError({}) + }) + ) + ) - yield* auth.verifyAccessToken(Redacted.make(response.token), response.iss).pipe( - Effect.catch(Effect.fnUntraced(function*(error) { - const userMessage = AuthError.getUserMessage(error) - const userSuggestion = AuthError.getUserSuggestion(error) - const errorMessage = [ - "Failed to verify the signed token.", - userMessage, - userSuggestion - ].join("\n") - yield* Console.error(errorMessage) - return yield* new TokenCommandError({}) - })) - ) + yield* auth.verifyAccessToken(Redacted.make(response.token), response.iss).pipe( + Effect.catch( + Effect.fnUntraced(function* (error) { + const userMessage = AuthError.getUserMessage(error) + const userSuggestion = AuthError.getUserSuggestion(error) + const errorMessage = ["Failed to verify the signed token.", userMessage, userSuggestion].join("\n") + yield* Console.error(errorMessage) + return yield* new TokenCommandError({}) + }) + ) + ) - const expiresAt = DateTime.makeUnsafe(response.exp * 1000) - const formatDateTime = DateTime.formatLocal({ - timeStyle: "full", - dateStyle: "medium" - }) - const message = [ - "Access token generated successfully!", - "We do not store this value - make sure you store it securely.", - "You can use this token as an bearer authorization header in requests to Amp", - String.stripMargin( - `| token: ${response.token} + const expiresAt = DateTime.makeUnsafe(response.exp * 1000) + const formatDateTime = DateTime.formatLocal({ + timeStyle: "full", + dateStyle: "medium" + }) + const message = [ + "Access token generated successfully!", + "We do not store this value - make sure you store it securely.", + "You can use this token as an bearer authorization header in requests to Amp", + String.stripMargin( + `| token: ${response.token} | expires: ${formatDateTime(expiresAt)}` - ) - ].join("\n\n") + ) + ].join("\n\n") - yield* Console.error(message) -}, Effect.catchTag("AuthCacheError", (cause) => Effect.fail(new TokenCommandError({ cause })))) + yield* Console.error(message) + }, + Effect.catchTag("AuthCacheError", (cause) => Effect.fail(new TokenCommandError({ cause }))) +) export const TokenCommand = Command.make("token", { audience: audienceFlag, duration: durationArg }).pipe( - Command.withDescription( - "Generates an access token (Bearer JWT) to be used by your applictaion to interact with Amp" - ), + Command.withDescription("Generates an access token (Bearer JWT) to be used by your applictaion to interact with Amp"), Command.withHandler(handleTokenCommand) ) diff --git a/packages/cli/src/commands/query.ts b/packages/cli/src/commands/query.ts index 705839f..80179c9 100644 --- a/packages/cli/src/commands/query.ts +++ b/packages/cli/src/commands/query.ts @@ -22,61 +22,60 @@ export class QueryCommandError extends Data.TaggedError("QueryCommandError")<{ } // TODO(Chris): we should re-evaluate this format option -const formatFlag = Flag.choice("format", ResultFormats).pipe( +const formatFlag = Flag.Literals("format", ResultFormats).pipe( Flag.withAlias("f"), Flag.withDescription("The format to output the results in."), Flag.withDefault("table") ) -const limitFlag = Flag.integer("limit").pipe( +const limitFlag = Flag.Int("limit").pipe( Flag.withDescription("The number of rows to return from the query."), Flag.optional ) -const queryArg = Argument.string("query").pipe( - Argument.withDescription("The SQL query to execute.") -) +const queryArg = Argument.String("query").pipe(Argument.withDescription("The SQL query to execute.")) -const tokenFlag = Flag.redacted("token").pipe( +const tokenFlag = Flag.Redacted("token").pipe( Flag.withAlias("t"), Flag.withDescription("The bearer token to use for authentication."), Flag.optional ) -const queryCommandHandler = Effect.fnUntraced(function*(params: { - readonly format: ResultFormat - readonly limit: Option.Option - readonly query: string - readonly token: Option.Option> -}) { - const flight = yield* ArrowFlight.ArrowFlight +const queryCommandHandler = Effect.fnUntraced( + function* (params: { + readonly format: ResultFormat + readonly limit: Option.Option + readonly query: string + readonly token: Option.Option> + }) { + const flight = yield* ArrowFlight.ArrowFlight - const sql = Option.match(params.limit, { - onNone: () => params.query, - onSome: (limit) => `${params.query} LIMIT ${limit}` - }) + const sql = Option.match(params.limit, { + onNone: () => params.query, + onSome: (limit) => `${params.query} LIMIT ${limit}` + }) - const results = yield* flight.query(sql) + const results = yield* flight.query(sql) - const rows = results - .filter(({ data }) => data.length > 0) - .flatMap(({ data }) => data) + const rows = results.filter(({ data }) => data.length > 0).flatMap(({ data }) => data) - switch (params.format) { - case "json": { - return yield* Console.log(JSON.stringify(rows, null, 2)) - } - case "jsonl": { - return yield* Console.log(JSON.stringify(rows)) + switch (params.format) { + case "json": { + return yield* Console.log(JSON.stringify(rows, null, 2)) + } + case "jsonl": { + return yield* Console.log(JSON.stringify(rows)) + } + case "pretty": { + return yield* Console.log(rows) + } + case "table": { + return yield* Console.table(rows) + } } - case "pretty": { - return yield* Console.log(rows) - } - case "table": { - return yield* Console.table(rows) - } - } -}, Effect.mapError((cause) => new QueryCommandError({ cause }))) + }, + Effect.mapError((cause) => new QueryCommandError({ cause })) +) export const QueryCommand = Command.make("query", { format: formatFlag, @@ -87,15 +86,16 @@ export const QueryCommand = Command.make("query", { Command.withDescription("Execute a SQL query with Amp"), Command.withHandler(queryCommandHandler), Command.provide(({ token }) => { - const layerInterceptorAuth = Option - .match(token, { - onSome: (bearer) => ArrowFlight.layerInterceptorToken(bearer), - onNone: () => ArrowFlight.layerInterceptorBearerAuth - }) + const layerInterceptorAuth = Option.match(token, { + onSome: (bearer) => ArrowFlight.layerInterceptorToken(bearer), + onNone: () => ArrowFlight.layerInterceptorBearerAuth + }) return ArrowFlight.layer.pipe( - Layer.provide(NodeArrowFlight.layerTransportGrpc({ - baseUrl: "http://localhost:1602" - })), + Layer.provide( + NodeArrowFlight.layerTransportGrpc({ + baseUrl: "http://localhost:1602" + }) + ), Layer.provide(layerInterceptorAuth) ) }) diff --git a/packages/tools/oxc/oxlintrc.json b/packages/tools/oxc/oxlintrc.json index e6b1097..663f307 100644 --- a/packages/tools/oxc/oxlintrc.json +++ b/packages/tools/oxc/oxlintrc.json @@ -8,22 +8,28 @@ }, "rules": { // Effect custom rules - "effect/no-import-from-barrel-package": ["error", { - "checkPatterns": [ - "^effect$", - "^effect/(.+/)?[a-z][a-z0-9]*$", - "^@effect/[^/]+$", - "^@effect/[^/]+/(.+/)?[a-z][a-z0-9]*$" - ], - "checkRelativeIndexImports": true - }], + "effect/no-import-from-barrel-package": [ + "error", + { + "checkPatterns": [ + "^effect$", + "^effect/(.+/)?[a-z][a-z0-9]*$", + "^@effect/[^/]+$", + "^@effect/[^/]+/(.+/)?[a-z][a-z0-9]*$" + ], + "checkRelativeIndexImports": true + } + ], "effect/no-js-extension-imports": "error", "effect/no-opaque-instance-fields": "error", // Tune native rules // Import rules - "typescript/consistent-type-imports": ["error", { - "fixStyle": "inline-type-imports" - }], + "typescript/consistent-type-imports": [ + "error", + { + "fixStyle": "inline-type-imports" + } + ], "typescript/no-import-type-side-effects": "error", "import/no-duplicates": "error", "import/no-self-import": "error", @@ -68,13 +74,19 @@ "typescript/unified-signatures": "off", "typescript/no-empty-object-type": "off", "typescript/no-confusing-non-null-assertion": "off", - "typescript/array-type": ["error", { - "default": "generic", - "readonly": "generic" - }], - "typescript/no-unused-vars": ["error", { - "argsIgnorePattern": "^_", - "varsIgnorePattern": "^_" - }] + "typescript/array-type": [ + "error", + { + "default": "generic", + "readonly": "generic" + } + ], + "typescript/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ] } } diff --git a/packages/tools/oxc/src/oxlint/rules/no-import-from-barrel-package.ts b/packages/tools/oxc/src/oxlint/rules/no-import-from-barrel-package.ts index 9e996ea..14f61f1 100644 --- a/packages/tools/oxc/src/oxlint/rules/no-import-from-barrel-package.ts +++ b/packages/tools/oxc/src/oxlint/rules/no-import-from-barrel-package.ts @@ -10,9 +10,7 @@ interface RuleOptions { const extensions = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"] function getModuleName(specifier: ESTree.ImportSpecifier): string { - return specifier.imported.type === "Identifier" - ? specifier.imported.name - : specifier.imported.value + return specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value } function isRelativeImport(source: string): boolean { @@ -120,8 +118,7 @@ const rule: CreateRule = { for (const specifier of namespaceSpecifiers) { context.report({ node: specifier, - message: - `Do not use namespace import from barrel file "${importSource}", import from specific modules instead` + message: `Do not use namespace import from barrel file "${importSource}", import from specific modules instead` }) } diff --git a/packages/tools/oxc/src/oxlint/types.ts b/packages/tools/oxc/src/oxlint/types.ts index ab7d6c4..849447b 100644 --- a/packages/tools/oxc/src/oxlint/types.ts +++ b/packages/tools/oxc/src/oxlint/types.ts @@ -46,10 +46,7 @@ export namespace ESTree { local: Identifier } - export type ImportDeclarationSpecifier = - | ImportSpecifier - | ImportDefaultSpecifier - | ImportNamespaceSpecifier + export type ImportDeclarationSpecifier = ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier export interface ImportDeclaration extends BaseNode { type: "ImportDeclaration" diff --git a/packages/tools/oxc/test/no-import-from-barrel-package.test.ts b/packages/tools/oxc/test/no-import-from-barrel-package.test.ts index 9d95a7d..27b811f 100644 --- a/packages/tools/oxc/test/no-import-from-barrel-package.test.ts +++ b/packages/tools/oxc/test/no-import-from-barrel-package.test.ts @@ -9,11 +9,7 @@ describe("no-import-from-barrel-package", () => { ruleOptions: [{ checkPatterns: ["^effect$"] }] } - const createImportDeclaration = ( - source: string, - specifiers: Array, - importKind?: "type" | "value" - ) => { + const createImportDeclaration = (source: string, specifiers: Array, importKind?: "type" | "value") => { const base = { type: "ImportDeclaration" as const, source: { value: source }, @@ -23,11 +19,7 @@ describe("no-import-from-barrel-package", () => { return importKind !== undefined ? { ...base, importKind } : base } - const createNamedSpecifier = ( - name: string, - local?: string, - importKind?: "type" | "value" - ) => { + const createNamedSpecifier = (name: string, local?: string, importKind?: "type" | "value") => { const base = { type: "ImportSpecifier" as const, imported: { type: "Identifier" as const, name }, @@ -37,22 +29,16 @@ describe("no-import-from-barrel-package", () => { } it("should not report for imports from non-barrel packages", () => { - const node = createImportDeclaration("effect/Effect", [ - createNamedSpecifier("Effect") - ]) + const node = createImportDeclaration("effect/Effect", [createNamedSpecifier("Effect")]) const errors = runRule(rule, "ImportDeclaration", node, testOptions) expect(errors).toHaveLength(0) }) it("should report for named imports from effect barrel", () => { - const node = createImportDeclaration("effect", [ - createNamedSpecifier("Effect") - ]) + const node = createImportDeclaration("effect", [createNamedSpecifier("Effect")]) const errors = runRule(rule, "ImportDeclaration", node, testOptions) expect(errors).toHaveLength(1) - expect(errors[0].message).toBe( - `Use import * as Effect from "effect/Effect" instead` - ) + expect(errors[0].message).toBe(`Use import * as Effect from "effect/Effect" instead`) }) it("should report for multiple named imports from effect barrel", () => { @@ -66,32 +52,22 @@ describe("no-import-from-barrel-package", () => { }) it("should not report for type-only import declarations", () => { - const node = createImportDeclaration( - "effect", - [createNamedSpecifier("Effect")], - "type" - ) + const node = createImportDeclaration("effect", [createNamedSpecifier("Effect")], "type") const errors = runRule(rule, "ImportDeclaration", node, testOptions) expect(errors).toHaveLength(0) }) it("should not report for type imports within named specifiers", () => { - const node = createImportDeclaration("effect", [ - createNamedSpecifier("Effect", "Effect", "type") - ]) + const node = createImportDeclaration("effect", [createNamedSpecifier("Effect", "Effect", "type")]) const errors = runRule(rule, "ImportDeclaration", node, testOptions) expect(errors).toHaveLength(0) }) it("should handle aliased imports", () => { - const node = createImportDeclaration("effect", [ - createNamedSpecifier("Effect", "Eff") - ]) + const node = createImportDeclaration("effect", [createNamedSpecifier("Effect", "Eff")]) const errors = runRule(rule, "ImportDeclaration", node, testOptions) expect(errors).toHaveLength(1) - expect(errors[0].message).toBe( - `Use import * as Eff from "effect/Effect" instead` - ) + expect(errors[0].message).toBe(`Use import * as Eff from "effect/Effect" instead`) }) it("should report for namespace imports from barrel", () => { @@ -132,9 +108,7 @@ describe("no-import-from-barrel-package", () => { describe("configuration", () => { it("should match regex patterns", () => { - const node = createImportDeclaration("@myorg/utils", [ - createNamedSpecifier("helper") - ]) + const node = createImportDeclaration("@myorg/utils", [createNamedSpecifier("helper")]) // No patterns configured - doesn't match const defaultErrors = runRule(rule, "ImportDeclaration", node, testOptions) expect(defaultErrors).toHaveLength(0) @@ -148,9 +122,7 @@ describe("no-import-from-barrel-package", () => { }) it("should match exact package with pattern", () => { - const node = createImportDeclaration("lodash", [ - createNamedSpecifier("map") - ]) + const node = createImportDeclaration("lodash", [createNamedSpecifier("map")]) // No patterns - doesn't match const defaultErrors = runRule(rule, "ImportDeclaration", node, testOptions) expect(defaultErrors).toHaveLength(0) @@ -164,9 +136,7 @@ describe("no-import-from-barrel-package", () => { }) it("should disable relative index checking when configured", () => { - const node = createImportDeclaration("./index.ts", [ - createNamedSpecifier("foo") - ]) + const node = createImportDeclaration("./index.ts", [createNamedSpecifier("foo")]) // Default checks relative imports const defaultErrors = runRule(rule, "ImportDeclaration", node, testOptions) expect(defaultErrors).toHaveLength(1) diff --git a/packages/tools/oxc/test/no-opaque-instance-fields.test.ts b/packages/tools/oxc/test/no-opaque-instance-fields.test.ts index 11ad864..f56d3f8 100644 --- a/packages/tools/oxc/test/no-opaque-instance-fields.test.ts +++ b/packages/tools/oxc/test/no-opaque-instance-fields.test.ts @@ -33,65 +33,78 @@ describe("no-opaque-instance-fields", () => { }) const createSchemaOpaqueClass = (members: Array<{ type: string; static: boolean }>) => - createOpaqueClass({ - type: "MemberExpression", - object: { type: "Identifier", name: "Schema" }, - property: { type: "Identifier", name: "Opaque" } - }, members) + createOpaqueClass( + { + type: "MemberExpression", + object: { type: "Identifier", name: "Schema" }, + property: { type: "Identifier", name: "Opaque" } + }, + members + ) const createSchemaImport = () => ({ type: "ImportDeclaration", source: { type: "Literal", value: "effect" }, - specifiers: [{ - type: "ImportSpecifier", - imported: { type: "Identifier", name: "Schema" }, - local: { type: "Identifier", name: "Schema" }, - importKind: "value" - }], + specifiers: [ + { + type: "ImportSpecifier", + imported: { type: "Identifier", name: "Schema" }, + local: { type: "Identifier", name: "Schema" }, + importKind: "value" + } + ], importKind: "value" }) const createOpaqueImport = (localName = "Opaque") => ({ type: "ImportDeclaration", source: { type: "Literal", value: "effect/Schema" }, - specifiers: [{ - type: "ImportSpecifier", - imported: { type: "Identifier", name: "Opaque" }, - local: { type: "Identifier", name: localName }, - importKind: "value" - }], + specifiers: [ + { + type: "ImportSpecifier", + imported: { type: "Identifier", name: "Opaque" }, + local: { type: "Identifier", name: localName }, + importKind: "value" + } + ], importKind: "value" }) const createSchemaNamespaceImport = (localName = "S") => ({ type: "ImportDeclaration", source: { type: "Literal", value: "effect/Schema" }, - specifiers: [{ - type: "ImportNamespaceSpecifier", - local: { type: "Identifier", name: localName } - }], + specifiers: [ + { + type: "ImportNamespaceSpecifier", + local: { type: "Identifier", name: localName } + } + ], importKind: "value" }) const createOtherOpaqueImport = (localName = "Opaque") => ({ type: "ImportDeclaration", source: { type: "Literal", value: "other" }, - specifiers: [{ - type: "ImportSpecifier", - imported: { type: "Identifier", name: "Opaque" }, - local: { type: "Identifier", name: localName }, - importKind: "value" - }], + specifiers: [ + { + type: "ImportSpecifier", + imported: { type: "Identifier", name: "Opaque" }, + local: { type: "Identifier", name: localName }, + importKind: "value" + } + ], importKind: "value" }) const createOtherNamespaceImport = (localName = "S") => ({ type: "ImportDeclaration", source: { type: "Literal", value: "other" }, - specifiers: [{ - type: "ImportNamespaceSpecifier", - local: { type: "Identifier", name: localName } - }], + specifiers: [ + { + type: "ImportNamespaceSpecifier", + local: { type: "Identifier", name: localName } + } + ], importKind: "value" }) @@ -116,9 +129,7 @@ describe("no-opaque-instance-fields", () => { }) it("should not report for static fields in Schema.Opaque class", () => { - const node = createSchemaOpaqueClass([ - { type: "PropertyDefinition", static: true } - ]) + const node = createSchemaOpaqueClass([{ type: "PropertyDefinition", static: true }]) const errors = runRuleWithNodes([ { visitor: "ImportDeclaration", node: createSchemaImport() }, { visitor: "ClassDeclaration", node } @@ -127,9 +138,7 @@ describe("no-opaque-instance-fields", () => { }) it("should report for instance fields in Schema.Opaque class", () => { - const node = createSchemaOpaqueClass([ - { type: "PropertyDefinition", static: false } - ]) + const node = createSchemaOpaqueClass([{ type: "PropertyDefinition", static: false }]) const errors = runRuleWithNodes([ { visitor: "ImportDeclaration", node: createSchemaImport() }, { visitor: "ClassDeclaration", node } @@ -139,18 +148,15 @@ describe("no-opaque-instance-fields", () => { }) it("should not report when Schema.Opaque has no Schema import", () => { - const node = createSchemaOpaqueClass([ - { type: "PropertyDefinition", static: false } - ]) + const node = createSchemaOpaqueClass([{ type: "PropertyDefinition", static: false }]) const errors = runRule(rule, "ClassDeclaration", node) expect(errors).toHaveLength(0) }) it("should report for instance fields in Opaque class", () => { - const node = createOpaqueClass( - { type: "Identifier", name: "Opaque" }, - [{ type: "PropertyDefinition", static: false }] - ) + const node = createOpaqueClass({ type: "Identifier", name: "Opaque" }, [ + { type: "PropertyDefinition", static: false } + ]) const errors = runRuleWithNodes([ { visitor: "ImportDeclaration", node: createOpaqueImport() }, { visitor: "ClassDeclaration", node } @@ -175,10 +181,9 @@ describe("no-opaque-instance-fields", () => { }) it("should not report for Opaque import from other module", () => { - const node = createOpaqueClass( - { type: "Identifier", name: "Opaque" }, - [{ type: "PropertyDefinition", static: false }] - ) + const node = createOpaqueClass({ type: "Identifier", name: "Opaque" }, [ + { type: "PropertyDefinition", static: false } + ]) const errors = runRuleWithNodes([ { visitor: "ImportDeclaration", node: createOtherOpaqueImport() }, { visitor: "ClassDeclaration", node } @@ -216,9 +221,7 @@ describe("no-opaque-instance-fields", () => { }) it("should report for instance methods", () => { - const node = createSchemaOpaqueClass([ - { type: "MethodDefinition", static: false } - ]) + const node = createSchemaOpaqueClass([{ type: "MethodDefinition", static: false }]) const errors = runRuleWithNodes([ { visitor: "ImportDeclaration", node: createSchemaImport() }, { visitor: "ClassDeclaration", node } @@ -227,18 +230,14 @@ describe("no-opaque-instance-fields", () => { }) it("should not report for non-Schema.Opaque classes", () => { - const node = createRegularClass([ - { type: "PropertyDefinition", static: false } - ]) + const node = createRegularClass([{ type: "PropertyDefinition", static: false }]) const errors = runRule(rule, "ClassDeclaration", node) expect(errors).toHaveLength(0) }) it("should work with ClassExpression", () => { const node = { - ...createSchemaOpaqueClass([ - { type: "PropertyDefinition", static: false } - ]), + ...createSchemaOpaqueClass([{ type: "PropertyDefinition", static: false }]), type: "ClassExpression" } const errors = runRuleWithNodes([ diff --git a/packages/tools/oxc/test/utils.ts b/packages/tools/oxc/test/utils.ts index 23b0ff3..adeef7a 100644 --- a/packages/tools/oxc/test/utils.ts +++ b/packages/tools/oxc/test/utils.ts @@ -13,12 +13,7 @@ export interface TestContextOptions { } export const createTestContext = (options: TestContextOptions = {}) => { - const { - sourceCode = "", - filename = "/test/file.ts", - cwd = "/test", - ruleOptions = [] - } = options + const { sourceCode = "", filename = "/test/file.ts", cwd = "/test", ruleOptions = [] } = options const errors: Array = [] const context = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f0ee63..de0ecb4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,3 +1,161 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + pnpm: + specifier: 12.6.0 + version: 12.6.0 + +packages: + + '@pnpm/exe.android-arm64@12.6.0': + resolution: {integrity: sha512-kviIHft9h02q+7N2In7tSL9T/HRUSGwYmU74YPtsWU5ee7vgqPyC0JT6RRX6miW+NxX1CvHgQxg69qq1qTLJYQ==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.6.0': + resolution: {integrity: sha512-CT8aJKLq2mtZFE71pr4E5Z2xHL8uGnRGr+NclDqsXlVF4SVcQ2QAs14mWi0C8gjT4hbmi+Q0lJRRIkMAWfjjng==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.6.0': + resolution: {integrity: sha512-rafpVkjzugKBMxSvGQd5wK1x5eOTkD/+lcJIFHeQhvd+BJ8kDisOOvwhQDrpGd4vd2Fx+hhW0P2Ptt638OlhFg==} + cpu: [arm64] + os: [darwin] + + '@pnpm/exe.darwin-x64@12.6.0': + resolution: {integrity: sha512-72Jpuv1m24gI8zUNcaylYpBWEhBzz1VPhnDIU2GYM2eRP+KsOqVLwV78i+JrihI39zY2NNXmaN4eAKX1inpaDQ==} + cpu: [x64] + os: [darwin] + + '@pnpm/exe.freebsd-x64@12.6.0': + resolution: {integrity: sha512-LON4QgNy1w/XF4uySZIyC/5hEB0oGdelXsY9tSKqstER+2j2X2oHz67skD11RRd/HWFRtMr64shQZ4I9ZoZ0zg==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.6.0': + resolution: {integrity: sha512-BdDpX+DeaMUc5x6WNcE6FkmFy36NwrBbBvwsZU737Zt3u1fTY+iKFPchjEQNCM4rT6q6xvF2oN3ZwExGsJCkxQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-arm64@12.6.0': + resolution: {integrity: sha512-8h2sNoIhHDpHDYqZZCh9PXr6krqW10btb0GHVGuw710muDHlFAHMtUG2ogmaHge6zJG1xgY4Y7mwX5g/LWvrYg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-ppc64@12.6.0': + resolution: {integrity: sha512-ld6xgcEhFCFrsVsJhbmdmR714PnsEspO/Ij8w0/D7OSiz2hYGu3fZd5tKRxOI5m6H6Gl0/RRFAPncTHbsgXVjQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.6.0': + resolution: {integrity: sha512-HIbImydoC3J8NFt+8MXfhTCf5rO6NjCMyinSAHac0VmV8uCHw+kbQkyAzxV84rzz9ZOzmBOnj0b1N461UIYGsw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.6.0': + resolution: {integrity: sha512-DjSqT7+BZ/lWcDHwNMuzsRVfyxUimh+D54Xw4tSWbdVVQhtWtNMUyI93LVnwCmsoEpar2gC3SFeAgywx37svKQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.6.0': + resolution: {integrity: sha512-31lKeGPmRE6xfV6I3VjaEGVFRei14pl/zgoFDK4w+UkbJqOuNl2Ht3O1GuqNIg3Gl14qTEb4kZRAYFHXifDx9A==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-x64@12.6.0': + resolution: {integrity: sha512-qFWBneHJAJ73W4whtbaFOL1M/7DBC6ILHXuxc7ZPtEhfPuT1zeZiGrmKHoMAfJA+mcm6xhOFljqVTUS+00Jabw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.win32-arm64@12.6.0': + resolution: {integrity: sha512-OhfefXEEykZlslSUhR8PPRe6MVPV3Oink/dVfIecE5velpc7j80cZYsHlaDrtRDcm3TPwQHkaRW1SJ1efX9oFg==} + cpu: [arm64] + os: [win32] + + '@pnpm/exe.win32-x64@12.6.0': + resolution: {integrity: sha512-L2tuyrD2+Imgxs3VK/ST/2L3xD6vDP0ktJQxM0TaGw7TbAQEMq+RMc8iK6Ew7Id579uzjJe8jSfJID2ZGsOHCA==} + cpu: [x64] + os: [win32] + + pnpm@12.6.0: + resolution: {integrity: sha512-PvaPlRyxEawgS0paFvCy3fDaVqluBBPoHYVdnwtV75JnFHCQKOHNAMQFwsX7e56OxNxGd3yAXQNzwvL/AP0g7A==} + engines: {node: '>=18.*'} + hasBin: true + +snapshots: + + '@pnpm/exe.android-arm64@12.6.0': + optional: true + + '@pnpm/exe.android-x64@12.6.0': + optional: true + + '@pnpm/exe.darwin-arm64@12.6.0': + optional: true + + '@pnpm/exe.darwin-x64@12.6.0': + optional: true + + '@pnpm/exe.freebsd-x64@12.6.0': + optional: true + + '@pnpm/exe.linux-arm64-musl@12.6.0': + optional: true + + '@pnpm/exe.linux-arm64@12.6.0': + optional: true + + '@pnpm/exe.linux-ppc64@12.6.0': + optional: true + + '@pnpm/exe.linux-riscv64@12.6.0': + optional: true + + '@pnpm/exe.linux-s390x@12.6.0': + optional: true + + '@pnpm/exe.linux-x64-musl@12.6.0': + optional: true + + '@pnpm/exe.linux-x64@12.6.0': + optional: true + + '@pnpm/exe.win32-arm64@12.6.0': + optional: true + + '@pnpm/exe.win32-x64@12.6.0': + optional: true + + pnpm@12.6.0: + optionalDependencies: + '@pnpm/exe.android-arm64': 12.6.0 + '@pnpm/exe.android-x64': 12.6.0 + '@pnpm/exe.darwin-arm64': 12.6.0 + '@pnpm/exe.darwin-x64': 12.6.0 + '@pnpm/exe.freebsd-x64': 12.6.0 + '@pnpm/exe.linux-arm64': 12.6.0 + '@pnpm/exe.linux-arm64-musl': 12.6.0 + '@pnpm/exe.linux-ppc64': 12.6.0 + '@pnpm/exe.linux-riscv64': 12.6.0 + '@pnpm/exe.linux-s390x': 12.6.0 + '@pnpm/exe.linux-x64': 12.6.0 + '@pnpm/exe.linux-x64-musl': 12.6.0 + '@pnpm/exe.win32-arm64': 12.6.0 + '@pnpm/exe.win32-x64': 12.6.0 + +--- lockfileVersion: '9.0' settings: @@ -7,32 +165,32 @@ settings: catalogs: default: '@effect/language-service': - specifier: ^0.86.1 - version: 0.86.1 + specifier: ^0.87.2 + version: 0.87.2 '@effect/platform-node': - specifier: ^4.0.0-beta.85 - version: 4.0.0-beta.85 + specifier: ^4.0.0-rc.117 + version: 4.0.0-rc.117 '@effect/vitest': - specifier: ^4.0.0-beta.85 - version: 4.0.0-beta.85 + specifier: ^4.0.0-rc.117 + version: 4.0.0-rc.117 '@types/node': - specifier: ^26.0.0 - version: 26.0.0 + specifier: ^26.6.2 + version: 26.6.2 '@vitest/coverage-v8': - specifier: ^4.1.9 - version: 4.1.9 + specifier: ^5.0.2 + version: 5.0.2 '@vitest/ui': - specifier: ^4.1.9 - version: 4.1.9 + specifier: ^5.0.2 + version: 5.0.2 effect: - specifier: ^4.0.0-beta.85 - version: 4.0.0-beta.85 + specifier: ^4.0.0-rc.117 + version: 4.0.0-rc.117 viem: - specifier: ^2.53.1 - version: 2.53.1 + specifier: ^2.56.9 + version: 2.56.9 vitest: - specifier: ^4.1.9 - version: 4.1.9 + specifier: ^5.0.2 + version: 5.0.2 overrides: typescript: ^6.0.3 @@ -45,56 +203,56 @@ importers: specifier: workspace:^ version: link:packages/tools/oxc '@babel/cli': - specifier: ^8.0.1 - version: 8.0.1(@babel/core@8.0.1) + specifier: ^8.0.6 + version: 8.0.6(@babel/core@8.0.6) '@babel/core': - specifier: ^8.0.1 - version: 8.0.1 + specifier: ^8.0.6 + version: 8.0.6 '@babel/plugin-transform-export-namespace-from': specifier: ^8.0.1 - version: 8.0.1(@babel/core@8.0.1) + version: 8.0.1(@babel/core@8.0.6) '@babel/plugin-transform-modules-commonjs': specifier: ^8.0.1 - version: 8.0.1(@babel/core@8.0.1) + version: 8.0.1(@babel/core@8.0.6) '@effect/language-service': specifier: 'catalog:' - version: 0.86.1 + version: 0.87.2 '@effect/vitest': specifier: 'catalog:' - version: 4.0.0-beta.85(effect@4.0.0-beta.85)(vitest@4.1.9) + version: 4.0.0-rc.117(effect@4.0.0-rc.117)(vitest@5.0.2) '@types/node': specifier: 'catalog:' - version: 26.0.0 + version: 26.6.2 '@typescript/native-preview': - specifier: 7.0.0-dev.20260130.1 - version: 7.0.0-dev.20260130.1 + specifier: 7.0.0-dev.20260707.2 + version: 7.0.0-dev.20260707.2 '@vitest/coverage-v8': specifier: 'catalog:' - version: 4.1.9(vitest@4.1.9) + version: 5.0.2(vitest@5.0.2) '@vitest/ui': specifier: 'catalog:' - version: 4.1.9(vitest@4.1.9) + version: 5.0.2(vitest@5.0.2) babel-plugin-annotate-pure-calls: specifier: ^0.5.0 - version: 0.5.0(@babel/core@8.0.1) - dprint: - specifier: ^0.54.0 - version: 0.54.0 + version: 0.5.0(@babel/core@8.0.6) glob: specifier: ^13.0.6 version: 13.0.6 globals: - specifier: ^17.7.0 - version: 17.7.0 + specifier: ^17.12.0 + version: 17.12.0 madge: specifier: ^8.0.0 - version: 8.0.0(typescript@6.0.3) + version: 8.0.0(supports-color@7.2.0)(typescript@6.0.3) + oxfmt: + specifier: ^0.70.0 + version: 0.70.0 oxlint: - specifier: ^1.71.0 - version: 1.71.0 + specifier: ^1.85.0 + version: 1.85.0 rulesync: - specifier: ^8.31.0 - version: 8.31.0(valibot@1.2.0(typescript@6.0.3)) + specifier: ^19.0.0 + version: 19.0.0(@types/node@26.6.2)(ajv-formats@3.0.1(ajv@8.17.1))(ajv@8.17.1)(jose@6.2.12)(openapi-types@12.1.3)(supports-color@7.2.0)(valibot@1.5.0(typescript@6.0.3)) ts-patch: specifier: ^4.0.1 version: 4.0.1 @@ -103,10 +261,10 @@ importers: version: 6.0.3 vite-tsconfig-paths: specifier: ^6.1.1 - version: 6.1.1(typescript@6.0.3)(vite@7.3.1(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) + version: 6.1.1(supports-color@7.2.0)(typescript@6.0.3)(vite@7.3.1(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.9(@types/node@26.0.0)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(vite@7.3.1(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) + version: 5.0.2(@types/node@26.6.2)(@vitest/coverage-v8@5.0.2)(@vitest/ui@5.0.2)(vite@7.3.1(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0)) vitest-mock-express: specifier: ^2.2.0 version: 2.2.0 @@ -117,30 +275,30 @@ importers: specifier: ^2.7.0 version: 2.7.0 jose: - specifier: ^6.2.3 - version: 6.2.3 + specifier: ^6.2.12 + version: 6.2.12 viem: specifier: 'catalog:' - version: 2.53.1(typescript@6.0.3)(zod@4.4.3) + version: 2.56.9(typescript@6.0.3)(zod@4.4.3) devDependencies: '@bufbuild/buf': - specifier: ^1.71.0 - version: 1.71.0 + specifier: ^1.73.0 + version: 1.73.0 '@bufbuild/protobuf': - specifier: ^2.12.1 - version: 2.12.1 + specifier: ^2.15.0 + version: 2.15.0 '@bufbuild/protoc-gen-es': - specifier: ^2.12.1 - version: 2.12.1(@bufbuild/protobuf@2.12.1) + specifier: ^2.15.0 + version: 2.15.0(@bufbuild/protobuf@2.15.0)(supports-color@7.2.0) '@connectrpc/connect': - specifier: ^2.1.2 - version: 2.1.2(@bufbuild/protobuf@2.12.1) + specifier: ^2.2.0 + version: 2.2.0(@bufbuild/protobuf@2.15.0) '@connectrpc/connect-node': - specifier: ^2.1.2 - version: 2.1.2(@bufbuild/protobuf@2.12.1)(@connectrpc/connect@2.1.2(@bufbuild/protobuf@2.12.1)) + specifier: ^2.2.0 + version: 2.2.0(@bufbuild/protobuf@2.15.0)(@connectrpc/connect@2.2.0(@bufbuild/protobuf@2.15.0)) effect: specifier: 'catalog:' - version: 4.0.0-beta.85 + version: 4.0.0-rc.117 packages/cli: dependencies: @@ -148,27 +306,27 @@ importers: specifier: workspace:^ version: link:../amp open: - specifier: ^11.0.0 - version: 11.0.0 + specifier: ^11.0.4 + version: 11.0.4 devDependencies: '@connectrpc/connect': - specifier: ^2.1.1 - version: 2.1.1(@bufbuild/protobuf@2.12.1) + specifier: ^2.2.0 + version: 2.2.0(@bufbuild/protobuf@2.15.0) '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.85(effect@4.0.0-beta.85)(ioredis@5.10.1) + version: 4.0.0-rc.117(effect@4.0.0-rc.117)(redis@6.2.1) effect: specifier: 'catalog:' - version: 4.0.0-beta.85 + version: 4.0.0-rc.117 packages/tools/oxc: devDependencies: '@types/node': specifier: 'catalog:' - version: 26.0.0 + version: 26.6.2 vitest: specifier: 'catalog:' - version: 4.1.9(@types/node@26.0.0)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(vite@7.3.1(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) + version: 5.0.2(@types/node@26.6.2)(@vitest/coverage-v8@5.0.2)(@vitest/ui@5.0.2)(vite@7.3.1(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0)) scratchpad: dependencies: @@ -181,8 +339,24 @@ packages: '@adraffy/ens-normalize@1.11.1': resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} - '@babel/cli@8.0.1': - resolution: {integrity: sha512-cttmeKZGho8XpXNz3nvGMjEnpDW5qkPVGNW2buav2byKxArJnBROsJrLA7CioPQhjzsZ26aZRqg+Gvpk+FHGEg==} + '@apidevtools/json-schema-ref-parser@14.0.1': + resolution: {integrity: sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw==} + engines: {node: '>= 16'} + + '@apidevtools/openapi-schemas@2.1.0': + resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} + engines: {node: '>=10'} + + '@apidevtools/swagger-methods@3.0.2': + resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} + + '@apidevtools/swagger-parser@12.1.0': + resolution: {integrity: sha512-e5mJoswsnAX0jG+J09xHFYQXb/bUc5S3pLpMxUuRUA2H8T2kni3yEoyz2R3Dltw5f4A6j6rPNMpWTK+iVDFlng==} + peerDependencies: + openapi-types: '>=7' + + '@babel/cli@8.0.6': + resolution: {integrity: sha512-um8l/siZ6XioGNxbmEry1Z3u77Ye6bkDkg6lXewtZDacGNC+MLh85CWcnMV/A12i4eh77HkhkSKVlZtk4JsucA==} engines: {node: ^22.18.0 || >=24.11.0} hasBin: true peerDependencies: @@ -192,26 +366,38 @@ packages: resolution: {integrity: sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==} engines: {node: ^22.18.0 || >=24.11.0} - '@babel/compat-data@8.0.0': - resolution: {integrity: sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==} + '@babel/code-frame@8.0.6': + resolution: {integrity: sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/compat-data@8.0.5': + resolution: {integrity: sha512-YLsYoQMvL8l8WrGpN3Zj7O1wK5LEBN+cQtux7BcuHyxIXve724XG+zuJ1n3U1cUweRtTzQOA4IHbuQw3N34SZw==} engines: {node: ^22.18.0 || >=24.11.0} - '@babel/core@8.0.1': - resolution: {integrity: sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==} + '@babel/core@8.0.6': + resolution: {integrity: sha512-5zwYt1V4ji3mXKKRIAkohSJ29WepxNwxQi+7y7P5AcVPh1dcxnZBU2cfxHnPwS+u9+lZnXPsv30TQaF2xg+Tfg==} engines: {node: ^22.18.0 || >=24.11.0} '@babel/generator@8.0.0': resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==} engines: {node: ^22.18.0 || >=24.11.0} - '@babel/helper-compilation-targets@8.0.0': - resolution: {integrity: sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==} + '@babel/generator@8.0.6': + resolution: {integrity: sha512-/pcanjdRsonCexCuxgGD8fKnBLLzk2i9uM4kmcmGyyev2R5YDs+rafMYZ89RvaGY1mW8T7C5au0TyYaEeHBGng==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-compilation-targets@8.0.6': + resolution: {integrity: sha512-HTrtZ4+iA0+kzdbCgvdSk1U4pEl3BvpGnlUbualCGNbrVhhQvhgBzurTUmqpWyvyHbWYWaIfdx2mOlL9LJjsew==} engines: {node: ^22.18.0 || >=24.11.0} '@babel/helper-globals@8.0.0': resolution: {integrity: sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-globals@8.0.6': + resolution: {integrity: sha512-OdnA/N52LhzFpeUuVz5YGuSWjc7TPHiFmVYZbPlSgExocO2GwbxVpSni9MVs5wwW+wZ4kPuwBEBN9pIPDYz1mA==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-module-imports@8.0.0': resolution: {integrity: sha512-NZ7mSS93o4ndX4KrbD7W8Sf3QT8Qe24PrnFyUcuOPDzK6faqDFKjY9RG7he7+I7FdiQ4llpnosFqzrXa+Vy3Ew==} engines: {node: ^22.18.0 || >=24.11.0} @@ -232,24 +418,40 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@8.0.0': resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-string-parser@8.0.6': + resolution: {integrity: sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@8.0.2': resolution: {integrity: sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-validator-identifier@8.0.6': + resolution: {integrity: sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-validator-option@8.0.0': resolution: {integrity: sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==} engines: {node: ^22.18.0 || >=24.11.0} - '@babel/helpers@8.0.0': - resolution: {integrity: sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==} + '@babel/helpers@8.0.5': + resolution: {integrity: sha512-fQtPOXjYOYv85PIdwotp2TJGVYOycX0PQq+l844fFAxOULtBy8BVF35GyeueX0r4KvDthqPH5xAI1clQPk/2uA==} engines: {node: ^22.18.0 || >=24.11.0} '@babel/parser@7.29.3': @@ -257,11 +459,21 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.9': + resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/parser@8.0.0': resolution: {integrity: sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==} engines: {node: ^22.18.0 || >=24.11.0} hasBin: true + '@babel/parser@8.0.6': + resolution: {integrity: sha512-LpGDIYJAzc3Y3PT9x+FxBrGYu2PlWxLmyN7SwPTzaX0LSwEmFJBsRnPBqmaXF2r1v+Zhz6lNiEJ+7yKtM5+Ugg==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + '@babel/plugin-transform-export-namespace-from@8.0.1': resolution: {integrity: sha512-bFzznm46bvWGaTYKle3iolbBJ+oPBfUjwCPesxlFE3SQ7DaY9EHf/8Y5ZzrodKJi8JDdcAyaVWaDUSVyhULh0g==} engines: {node: ^22.18.0 || >=24.11.0} @@ -282,14 +494,26 @@ packages: resolution: {integrity: sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/traverse@8.0.6': + resolution: {integrity: sha512-ilYV+qLStGzSKLipJlETdvdtpK3rAuwfCmCoGKv15MU4EoeIuLkespSNoNLFGOPSciEHx9TLWtN5mv0fz5w5mQ==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/types@7.29.0': resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + '@babel/types@8.0.0': resolution: {integrity: sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/types@8.0.6': + resolution: {integrity: sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==} + engines: {node: ^22.18.0 || >=24.11.0} + '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} @@ -297,83 +521,78 @@ packages: '@borewit/text-codec@0.2.1': resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==} - '@bufbuild/buf-darwin-arm64@1.71.0': - resolution: {integrity: sha512-qZ7xZQyen/jOKFPVs3dlN9pMA56PI4YEo3r4/9ixtiH9gyFgfowR31axsocUgXGThjiN8mvOA8WfpG2tvaSvsw==} + '@bufbuild/buf-darwin-arm64@1.73.0': + resolution: {integrity: sha512-cDd3y376ecClhyytNLlY3AtsZqsqXvEkQjMvrahrv/xUWWhEIX3efD8y0uufXDe8Yuk3sEmj7J1Xlx73Y46SkA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - '@bufbuild/buf-darwin-x64@1.71.0': - resolution: {integrity: sha512-2w95pc3X+z06/J66i6uNzA8QPuVOpbPrwyb6tkK0AcJFNvKPVYr4BxVC2koyImrQ3rxY1n9q8qviWMjSvq9fOA==} + '@bufbuild/buf-darwin-x64@1.73.0': + resolution: {integrity: sha512-VTBTCwaqT7otsMGXlk3MRu6x5hp+YH9PaLKcyWAa8wEt0NnMzbXdkE8GiLgI8Qkl7S3+pC9YSTCE/I+O87Fvrw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - '@bufbuild/buf-linux-aarch64@1.71.0': - resolution: {integrity: sha512-dwxErryMI3MRwtP/IgfdrqEjiAmVpttGhmO3xihiJIV2EAXt9J5yjzHhEDvnSgQ6nmNjEvO5QczcIaQjZEwF6A==} + '@bufbuild/buf-linux-aarch64@1.73.0': + resolution: {integrity: sha512-HjoQ3wbLiUv5N5XLM3EAR2FY51Yn6t5MEXLPnS/Hx0KDkK/5eyIBZZ6toHBLjczAr1RTA1LagF38+lglZI+kvA==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - '@bufbuild/buf-linux-armv7@1.71.0': - resolution: {integrity: sha512-pfc+Qexm5C59VeRUjVmEvxkCXT5QbMR1R/CUtcSlk+spOFVwna0bSpkqIsky3kkHfzxiNSOsz3iki9/pAVX+CA==} + '@bufbuild/buf-linux-armv7@1.73.0': + resolution: {integrity: sha512-NsZuVg8hXfGcR96Is14ArKxZg6pdJb6kb63bx2g7q8Cjo+gRqZMBFKGvF/fnQMxTrRQAm6hzeF4a2vV4W3lszg==} engines: {node: '>=12'} cpu: [arm] os: [linux] - '@bufbuild/buf-linux-x64@1.71.0': - resolution: {integrity: sha512-Y7jLxr3wpMkpQSqZU/MrDmDSCkF4GxvhIL7wnNdSRpkhYAY6TPRHN+5nNgV7jp6mQ0zQSYh0MGxBeMgt/UVdmQ==} + '@bufbuild/buf-linux-x64@1.73.0': + resolution: {integrity: sha512-dMe6lBQrlHTU7DzqzaR7Udky7R78EDzzxtYZQdjkZun9jKdjGR4fLbPWEPyY4rLGVyk6UUnrtkf5qoIK2SC6qw==} engines: {node: '>=12'} cpu: [x64] os: [linux] - '@bufbuild/buf-win32-arm64@1.71.0': - resolution: {integrity: sha512-UrxtD99zLE1qImtQC/W3a9cuj0/kB53B1bK38kmCMRFow939FhdZtqTRjbnZWauRi/pzAsjDyPCvnTa2XKT8Cg==} + '@bufbuild/buf-win32-arm64@1.73.0': + resolution: {integrity: sha512-UmubCflc8zJ48bVW0WeGeVTkNuumJP//wCWKUmeTXT1iNlEqkpaXy6y7o8YJKHGWLfX0/1UV3oay9ti0EyojMw==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - '@bufbuild/buf-win32-x64@1.71.0': - resolution: {integrity: sha512-+npiOimJ7ggeLul3KFwSlOjZnAZYwt3el64dJ3nJQMnui0avyvsRmU02o1bZI5yUnBvhcnTWdEbfRXUnkkVtgQ==} + '@bufbuild/buf-win32-x64@1.73.0': + resolution: {integrity: sha512-/Buo10rXzqNLnFDh3BpzGdcrx6OalcU8o8gUyObCFZA5wKcUvBVNDhhpMmoB6YeiOPpPn47jHgW4bSSlypaaOQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] - '@bufbuild/buf@1.71.0': - resolution: {integrity: sha512-GDcjBCwLgHT/4nX4YSnYatZ7sDZDpHV6dxQvoT2/P6gKvV23O6hl8NryzLIRKmeau0FRXpQKHVy1dMfnBSpy+w==} + '@bufbuild/buf@1.73.0': + resolution: {integrity: sha512-W3whb4P1y3Hs9bqwCu9++W1v1ofHZ3dBUCe6JSZq0Ff5OK5UKKZuMN4iunPbgdyXY4+4ujEYf2ESrN/6MbcFSQ==} engines: {node: '>=12'} hasBin: true - '@bufbuild/protobuf@2.12.1': - resolution: {integrity: sha512-BvAMfS6LrgZiryOAZ4pBYucu4wG/Ei/9o9DZ9akbREnMLbPJiom2i8b9C8IsKErQoiKqVhrerzt3kOT/RrzLHg==} + '@bufbuild/protobuf@2.15.0': + resolution: {integrity: sha512-DAheWUkVr/SJTWCc+lg9dhY0eN4SaWlf4+bG1KzHeXbnqt0AfB/NX0Z+VunGlM1ki1B4zVvye27MpKh/svySUA==} - '@bufbuild/protoc-gen-es@2.12.1': - resolution: {integrity: sha512-SWa7XvRYRouMo+vBQmpNFZ+ZEqQ8AC0LpL4QWAo1gvstLhFh/Y7Nf/a+MK7ZxDq5LZSThwfk974L1sFxO3OaGw==} - engines: {node: '>=20'} + '@bufbuild/protoc-gen-es@2.15.0': + resolution: {integrity: sha512-FRhNTAZEXW6arWx2JxgtpuazhmewYctXugu5SfXGpXpLLCInDT1005fSKDzEM/70Es2oIn07PM4RvpwvDvR/fg==} + engines: {node: '>=22'} hasBin: true peerDependencies: - '@bufbuild/protobuf': 2.12.1 + '@bufbuild/protobuf': 2.15.0 peerDependenciesMeta: '@bufbuild/protobuf': optional: true - '@bufbuild/protoplugin@2.12.1': - resolution: {integrity: sha512-PY58KxQVAD1BnnKtStOctsMoegEVGfBnY5AOqVQOIu711nA13oYtTqJM8df5lUQg2J1DR3XxUXptE+fWX5oLdA==} - - '@connectrpc/connect-node@2.1.2': - resolution: {integrity: sha512-+i/aAOpsI8sIx1mbYp6d99zvxaUSF6t/jP9Ux9maAmjsZPgmIQ3JuIeYi0zJIP9zlCnBlJjkpPosshCgdRuThQ==} - engines: {node: '>=20'} - peerDependencies: - '@bufbuild/protobuf': ^2.7.0 - '@connectrpc/connect': 2.1.2 + '@bufbuild/protoplugin@2.15.0': + resolution: {integrity: sha512-vZhRoqB/jTHATqItRvJQf1zbxdrGRRhiusGty4+lKcfEFODG0PMXky2Ezv4WHHS6oSxq+5qShITuE7K1/82SuQ==} - '@connectrpc/connect@2.1.1': - resolution: {integrity: sha512-JzhkaTvM73m2K1URT6tv53k2RwngSmCXLZJgK580qNQOXRzZRR/BCMfZw3h+90JpnG6XksP5bYT+cz0rpUzUWQ==} + '@connectrpc/connect-node@2.2.0': + resolution: {integrity: sha512-E6uUO6e11WdynAuBjuN+0WpHjqxEM9BNDdfDIJHcn49Esg/WxSIbn2y1KFt9XL96IJi01qdBbvubcdpBz1X3Zw==} + engines: {node: '>=22'} peerDependencies: '@bufbuild/protobuf': ^2.7.0 + '@connectrpc/connect': 2.2.0 - '@connectrpc/connect@2.1.2': - resolution: {integrity: sha512-MXkBijtcX09R10Eb6sFeIetc6w6746eio6xtfuyVOH7oQAacT1X0GzMIQFux6Qy8cq3W/T5qX5Bei8YbFtmRGA==} + '@connectrpc/connect@2.2.0': + resolution: {integrity: sha512-M63OQ1ZFcPUm85CuewJ9G9CMV8LjzGR9935xOnXNQGSazAbwMb3x2W7vXBYM1zqrP7XFD8uxp/dUAhirclgwnQ==} peerDependencies: '@bufbuild/protobuf': ^2.7.0 @@ -381,90 +600,28 @@ packages: resolution: {integrity: sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==} engines: {node: '>=18'} - '@dprint/darwin-arm64@0.54.0': - resolution: {integrity: sha512-yqRI4enH+BDp+4+ZsPVdZM5h873JK1lN7li9l9A5u4C4cvh1oEsiBWAzEPccRkJ2ctF8LgaizBSxO38sqEVYbw==} - cpu: [arm64] - os: [darwin] - - '@dprint/darwin-x64@0.54.0': - resolution: {integrity: sha512-W9BARpgHypcQwatg5mnHaCpX6pLX5dBxxiv+tZKruhOmq8MKYOrAYDXlceMuHSowmWREfUF5yL4SRgXDGI6WQw==} - cpu: [x64] - os: [darwin] - - '@dprint/linux-arm64-glibc@0.54.0': - resolution: {integrity: sha512-VhM7p70VFuNqxZMdiv1e+nMboPj/hMFlTIBWrRaX7+6VThs9mJr9+94wrUeXgfnfsyaEKSbRFa/dru1PINoSNw==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@dprint/linux-arm64-musl@0.54.0': - resolution: {integrity: sha512-QS1A74Lv60/L9oemHCzbHgOLbV2smSJG5IxS5fjf8ZWetyUt918WDzIHBilz/+uiB+OlW2UVTsm952UG0YOrLw==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@dprint/linux-loong64-glibc@0.54.0': - resolution: {integrity: sha512-8Myka2/0KbhuZnEKL6jagPXTgDKVpd/tfXDRa0oibUBgaqOSku6iRMzHGa/PhqHL+s14Gcp+/cIHz0zU3Tkgug==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@dprint/linux-loong64-musl@0.54.0': - resolution: {integrity: sha512-/AN3xCuMhC4PK7Pbj7/4zBuhFGr4m0OHV/5uGTfzpkKX/3+AXoyKl7PbT2VlNMGXAK0kuRThfjtx23gIwlWk7Q==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@dprint/linux-riscv64-glibc@0.54.0': - resolution: {integrity: sha512-Aw2vXzzwFDpPbXh6ajsSabVCkCc66C3hCyMKprR/IxYvFtjYX80nh1ox0c7iaw6c4HacHMRLGw7FUSXvomPaEQ==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@dprint/linux-x64-glibc@0.54.0': - resolution: {integrity: sha512-zZqj3wQELOX8n6QfT2uuWoMf64Wv0lMXNyam3btm+PKkg0P6a54TPL09Bs9XsViOdxgTcamsiQ7HlErt/LEjIA==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@dprint/linux-x64-musl@0.54.0': - resolution: {integrity: sha512-it6Qdt06dyW2adbAXpOCb7/KQLxlm4i1UphUAWqWsZk4t3EYetyAza9J0g3Vu9itIWSEIo9MnccgANckQJ6+qw==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@dprint/win32-arm64@0.54.0': - resolution: {integrity: sha512-F5kjV/6I9YtNOTDWHUpTqM2HHHS510BPL7z4NJuU0nDnaVeks7GwNEltGr56CcsG8XQYhkiAsqZytPu6AhA2hQ==} - cpu: [arm64] - os: [win32] - - '@dprint/win32-x64@0.54.0': - resolution: {integrity: sha512-AAr2ye/DtgYXDplRoPS+5U++x7T6W4a3I9FvTFWFxziFmUptvAg5G2c4FcXoAduSruhYZJvjDZrLseR2c3IwXg==} - cpu: [x64] - os: [win32] - - '@effect/language-service@0.86.1': - resolution: {integrity: sha512-jfUuFdtNPKRZ4ZSrgTnfMHPIVq8L9cM+YGEylQ1lhIgNY23ZvfdPg0cUlWESd4q9aGvOWSA8E79AS0qe3j4ROw==} + '@effect/language-service@0.87.2': + resolution: {integrity: sha512-CfiSoaVQO8pZgaMZGw5VvMvRGybsJ2LaSDoLYaqiVGvo/YYPYVR2GzUKY0h1NtIweq/+SQ5XLrvtzPIttWQ0GQ==} hasBin: true - '@effect/platform-node-shared@4.0.0-beta.85': - resolution: {integrity: sha512-JXM5f2j2L2oGxuQG12sysPJeFYKtB388BruyHb/IfxJ/5Wx8KDo3eKLRe6tmg7y7BNeZTxEb14tPKC853+mqeg==} + '@effect/platform-node-shared@4.0.0-rc.117': + resolution: {integrity: sha512-GyRkEfLBG49MwJe+/XDPiXYcQ44RA9p2z0AurlsWhR1cd1PqOUMEZU1pORL+/R5hR8aH6ZuqoJYD8lAc6h+wlA==} engines: {node: '>=18.0.0'} peerDependencies: - effect: ^4.0.0-beta.85 + effect: ^4.0.0-rc.117 - '@effect/platform-node@4.0.0-beta.85': - resolution: {integrity: sha512-tfZ/nUGB6gsw/b7ZQuhIv6w0iXNKOYj5m7dltd3SouY3y1BvIYrS/KxIUi4s3w8Qfx83i9l17q2Wa2b9Fzth5A==} + '@effect/platform-node@4.0.0-rc.117': + resolution: {integrity: sha512-WbPcvQDkqXFnCeG6LohJ9eVNapCDhZbCpezhYKxm0IsZEk7swakRh7RkC1t2CXzd3hGmszTv2k9e/pXyZuL+wQ==} engines: {node: '>=18.0.0'} peerDependencies: - effect: ^4.0.0-beta.85 - ioredis: ^5.7.0 + effect: ^4.0.0-rc.117 + redis: '>=5.0.0 <7.0.0' - '@effect/vitest@4.0.0-beta.85': - resolution: {integrity: sha512-iQC5grIDhZFvvug1HdoMVk9S+UxurXMlhnQm31NBpRcEh/ulvK82wSbZ4X5BrsMJTqxJgrKeC27p64HMcpgM5g==} + '@effect/vitest@4.0.0-rc.117': + resolution: {integrity: sha512-jJg9M7T3OhTcvoD9Zi28S5+0H+axlSHAYjXM8TYeLJy/FVlPe0B/Vc3ZfaqUF0rwkfQ6ziyCdjiBS46+182H1Q==} peerDependencies: - effect: ^4.0.0-beta.85 - vitest: ^3.0.0 || ^4.0.0 + effect: ^4.0.0-rc.117 + vitest: '>=5.0.0 <6.0.0' '@esbuild/aix-ppc64@0.27.2': resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} @@ -628,12 +785,47 @@ packages: peerDependencies: hono: ^4 - '@ioredis/commands@1.5.1': - resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} + '@inquirer/ansi@2.0.8': + resolution: {integrity: sha512-WpQM+Ti6Z40EFwwt+uL2p4UabT+W179zHp6HhLVOzfbwnVn05IPO/eXIZXGNqcT1jbQ15SujNLzQ39k4QPPxBQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/checkbox@5.2.5': + resolution: {integrity: sha512-bRt8J8m+Fot9CXv+zNQGXUq2ET0MggR1fPz7v6edN6MFYmsbfGnMmkmWZJEegMKqrAC8ej/o1sqisHZXZJMAfQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@12.0.3': + resolution: {integrity: sha512-wsSy0sznmXwkty+2PzZwx00Cazc/E0r0B7mAzdGROz2Ct+DFZXaK7WDjGZvgjRldxH5ZhFVfF2lgkYrqgOw2KA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.9': + resolution: {integrity: sha512-EAWgUTGQ/Umgga51dE3B2PUHbufuXarDfg86uVgoSgNHNNQnyFKcOrQLWVqYMghuSyHh8+2HUH0Js9cTC1WAdg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/type@4.1.1': + resolution: {integrity: sha512-yJoHYrMnxIsJZCY+0Vb66Dy3he3kL3e2wOBKhoSwWWAzZAY82emlxwgprCtp6yRixvNRNq9ztfRWQYPNr3Go7A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/gen-mapping@0.4.0-beta.0': + resolution: {integrity: sha512-JdGNkbE4GlNPYQhM0L95fBQr7ctLZJ276QXQLTad4t1oSdnnCI3fDq9DW3BqYAWv8Wc3+HS+4Gsii1oPMCfz1w==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -641,11 +833,22 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@modelcontextprotocol/sdk@1.27.1': - resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==} + '@modelcontextprotocol/client@2.1.0': + resolution: {integrity: sha512-mDVhoy5WjDb0U+4dQPzLcciC2erSex/GRVQqnoZdiuMoE3YiTZdiS7ezNcFwX4XEOWOaj7jZr0kLYnILnL8orA==} + engines: {node: '>=20'} + + '@modelcontextprotocol/core@2.1.0': + resolution: {integrity: sha512-YdFj5gMRHr0gYNAhTbQDgjLHiEJdIAUYxomhKseUNye7ZGlISQmUoLh2sveFFCiQ7j2YMffT1kgSND0FSfIFjQ==} + engines: {node: '>=20'} + + '@modelcontextprotocol/sdk@1.29.0': + resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -654,8 +857,8 @@ packages: '@cfworker/json-schema': optional: true - '@modelcontextprotocol/sdk@1.29.0': - resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==} + '@modelcontextprotocol/sdk@1.30.0': + resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -664,35 +867,9 @@ packages: '@cfworker/json-schema': optional: true - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': - resolution: {integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==} - cpu: [arm64] - os: [darwin] - - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4': - resolution: {integrity: sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==} - cpu: [x64] - os: [darwin] - - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4': - resolution: {integrity: sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==} - cpu: [arm64] - os: [linux] - - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4': - resolution: {integrity: sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==} - cpu: [arm] - os: [linux] - - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4': - resolution: {integrity: sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==} - cpu: [x64] - os: [linux] - - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4': - resolution: {integrity: sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==} - cpu: [x64] - os: [win32] + '@modelcontextprotocol/server@2.1.0': + resolution: {integrity: sha512-FdvwZU5N03O77Suh35JqjAqq5R1ZTEPFtwu6N4BgBq28qrQtrCnpbLL5U2pwYCuE194jAw0xnlOVmIqhfoYuUQ==} + engines: {node: '>=20'} '@noble/ciphers@1.3.0': resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} @@ -737,6 +914,9 @@ packages: '@octokit/openapi-types@27.0.0': resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} + '@octokit/openapi-types@29.0.1': + resolution: {integrity: sha512-9qWOMFNxxLokERcms42rU0PTLqQmVs7g5E41TI4mCOxmpFayD1rfC7XxOL55cG9MBZLFlC31BrR37myMKardwg==} + '@octokit/plugin-paginate-rest@14.0.0': resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==} engines: {node: '>= 20'} @@ -759,6 +939,10 @@ packages: resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} engines: {node: '>= 20'} + '@octokit/request-error@7.1.2': + resolution: {integrity: sha512-XZRuT3xZ84D3gYErI1DZvhJ33dCWVV6uzBtWkaBB4TvA/L6eOeTZodxLFVB44bBEEo3vEx7y00UfX1tBLrtLRg==} + engines: {node: '>= 20'} + '@octokit/request@10.0.7': resolution: {integrity: sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==} engines: {node: '>= 20'} @@ -770,124 +954,249 @@ packages: '@octokit/types@16.0.0': resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} - '@oxlint/binding-android-arm-eabi@1.71.0': - resolution: {integrity: sha512-ImGmd1njEg4FEJH03jhRnveEegtO3czCtfptvaHivKAZQIYATbVFBrrzbaYMYv0oJioTnxZAZVSyV+oL7W8S2g==} + '@octokit/types@18.0.0': + resolution: {integrity: sha512-l6bAF43PNxkJp6g+W4PjoUSSkxHomXw2nOum5CTftJz1NlV3vu93NImgOYtLf6CbBUb5j+fiuzW0PPQ5JTSvZA==} + + '@oxfmt/binding-android-arm-eabi@0.70.0': + resolution: {integrity: sha512-Xd7YO4/T2axEj6FTLcj4Why3mTBqFMg+x24xtorT4Lb2+1g82090GH0a/4U1m0pABGYiix2bq1pqkYrmV3f0Sw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.71.0': - resolution: {integrity: sha512-4A5BEexBrwY1YFF8Kiq/lp/wQPRG79G3BWIE1FuWaM5MvmpYSd+7ZySVcKkHdwo0UDzdQGddp6pD9mpctMqLnw==} + '@oxfmt/binding-android-arm64@0.70.0': + resolution: {integrity: sha512-x9rlMYyKXdgKdYyUJzGsK1ZV8P4di/J32ipzcS6Jet6p9r9UAh28neXIMtdlSaJJycdi61Z4YkcLKLpk8ueFjg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.71.0': - resolution: {integrity: sha512-9wJA9GJulLwS2usU3CEisI/ESDO1n1z9eyTCvApMDrAkbJ1ve0mORgTMjcWWsKxkzkeZ2N/Gpra5IQE7x8tYgQ==} + '@oxfmt/binding-darwin-arm64@0.70.0': + resolution: {integrity: sha512-IUTUPvrBVYy7POh4stXzRdz4IVC/1QSaviCWoyenSlOhGu0X9j5K07vCTM9biLjAA2Zs31l0Rj5vvRpj9n95wA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.71.0': - resolution: {integrity: sha512-PlLCjS06V0PeJMAJwzjrExw1sYNW9Gch3JtNlcwwZDXGlTYDuwHNN89zYH8LTXFfgkVtsYvs2nv0FqrzyuFDzg==} + '@oxfmt/binding-darwin-x64@0.70.0': + resolution: {integrity: sha512-vw745q870oTd6J517O24asoX4/E+eK0nxYIFoedSLqgJ+nI5En7+ZS82iZSHZ69zevQrnOXiyHP01dA+t8xD8w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.71.0': - resolution: {integrity: sha512-Lhil7bWre0ncxbUoDoxfS0JzpTz17BRQKW7iwoAUY8GJ66+WwJEfYPCFJ1P0WgVZR5/O/b3Q2pENlHOjeXLOGQ==} + '@oxfmt/binding-freebsd-x64@0.70.0': + resolution: {integrity: sha512-NO14EgSM9dFkcg+MfGPxvsKqXYs9LKaxPrOKXpv1R0rLokGGFDcCq6dBMq18dE4wlpFOovX0UZY2uh1P30O7QA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.71.0': - resolution: {integrity: sha512-Oo9/L58PYD3RC0x05d2upAPLllHytTjHQGsnC06P6Ynn7jKkp5mdImQxXdJ3+FnBaKspNpGogzgVsi6g872LiA==} + '@oxfmt/binding-linux-arm-gnueabihf@0.70.0': + resolution: {integrity: sha512-139OEhHarj9CYoJ/i9gXlPv4KLBGtLj2toseOWYFf09QwlhklaZk+wW3aOvlqoeZtuayvkoSNVWra7WJ21s3VQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.71.0': - resolution: {integrity: sha512-mSHfyfgJrEbyIR29ejaeS50BdPk+GoNPlC1dckpDiUZbJAIel68sjSMdOt4WY0/gva+ECC7FNITQkxMJU+vSBw==} + '@oxfmt/binding-linux-arm-musleabihf@0.70.0': + resolution: {integrity: sha512-GEh2PY3IWTE0M24eNhTduountANSbWyDmMnzFSQE/nGg/bjPugbUgiGuFu+xdqcQSd/HKSwH80/F2yVVD48yhA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.71.0': - resolution: {integrity: sha512-n9yY4M2tiy3aij4AqtlnspzpfdpeT5JQfK2/w2d8oyp5W0FRwOb1dIeX99nORNcxGr08iD9bH8N5XFz3I2iy8w==} + '@oxfmt/binding-linux-arm64-gnu@0.70.0': + resolution: {integrity: sha512-En5i+UJmZSPxuSf47F2Hl5YOzKB0bicQLnGQkeTCMQ35cWLtbrSwACJKfiLqRZrk05DwSnsJkhBRaM3OURtIaA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.71.0': - resolution: {integrity: sha512-fJZrs5sDZtTaPIOiemRQQmo82Ezy+vOGXemPc4Ok7iVVsYsFa7SlW6Z5XN819VfsqBHRm3NJ3rTdnR8+bJYJdQ==} + '@oxfmt/binding-linux-arm64-musl@0.70.0': + resolution: {integrity: sha512-WWOoV5W9Im3flVwOVrWn/2DUlOF8v5vcCip+kcNuaMpulRCh6nzzt1Su2vcL2F908YJIXNV3HvegbBHuyLwKHg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.71.0': - resolution: {integrity: sha512-cwl7VKGERIy9p+G+AvZdfy/06q0aHXaTt/mMRReC751iuNYJgqKjB7NydXSS30nBT9vtr2tunciOtrR4fD6FUA==} + '@oxfmt/binding-linux-ppc64-gnu@0.70.0': + resolution: {integrity: sha512-YUouneIqW+5n7aE8xx/zeZ6/utr/KH7oykcGoFyd8Uz8uh591T1oKlnoWA3BsRq/ZR42oY1w4MUYvS/0e/MQOA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.71.0': - resolution: {integrity: sha512-eZ8ieVXvzGi8jr7+ybQGPK2STw3mldfxZlgA2738iflfB/rzA69sE6m5rDRpQaxC7dpm745Enlh1Tod0QAk9Gg==} + '@oxfmt/binding-linux-riscv64-gnu@0.70.0': + resolution: {integrity: sha512-iEnMf21S5aGVa4hViDGY8sAQ/AHyCu2JPyrQF8P06wtHhSkD1YJBeT4m/KiGewgf7+a5XCYSCRIPcRQa1xwEoQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.71.0': - resolution: {integrity: sha512-puMDbQYe6+NXwfMusojoA7CXGn2b3utukmd23PQqc1E3XhVCwyZ+FueSMzDYeNgDV2dUfIVXAAKZBcFDeCL6sA==} + '@oxfmt/binding-linux-riscv64-musl@0.70.0': + resolution: {integrity: sha512-91Sdniaj20fQzyMeCxMDzTP4c9s4RB8dGQ308xHhDR0n6U7+1Xq7N9klE7mfXq8iV3lRmIGSXi5X23Hn/0XX/g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.71.0': - resolution: {integrity: sha512-4NJLxBs1ujISCt3L/1FcywLs73PWtJuw+piD6feK2V6h6OS6P7xu9/sWt1DTRLibe6QCzmfZzmM/2HPORoV/Lg==} + '@oxfmt/binding-linux-s390x-gnu@0.70.0': + resolution: {integrity: sha512-uUV30M6E+2TKKGMaKiwfeL4RZrviHXlUxsrYJ/jFBb+1EZy+pnFT+hF73eeWdzh5NqPOAnw0iiMAIqjqiLZPFg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.71.0': - resolution: {integrity: sha512-cFDaiR8L3430qp88tfZnvFlt3KotFhR/DlbIL0nHOMMYiG/9Wy4l+6f7t8G8pTa9bd8Lt8+M0y/qjRQ/xcB74g==} + '@oxfmt/binding-linux-x64-gnu@0.70.0': + resolution: {integrity: sha512-ivMcX6kNDPhqtbOaBt/ItFlLlTlXNHLgRuNmxP6Na6UuYXRT10llpJcAPbGeRgjjb3Qzv4jwPp3fB0hui50WNQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.71.0': - resolution: {integrity: sha512-orfixdt76KlpNly9z0PkWBBNfwjKz+JFVLP/7wnVchlKNU9Dpt9InU/ZggeSej6fC7qwHmHNOGlhLnQXcYoGuA==} + '@oxfmt/binding-linux-x64-musl@0.70.0': + resolution: {integrity: sha512-w+S+fERxYmlZSyZlJK/U292FjyBoH8cCEj21/tYJX6atX5kNSn+HDkhlFQKT2zcMwUW0uAtUL/bOrlwJZwqRdA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.71.0': - resolution: {integrity: sha512-9emQu2lAp6yhPB3XuI+++vR+l/o6JR1X+EpxwcumPdQXBWXEPAsquPGL7l158EqU8SebQMXTUa/S5zN98juyHw==} + '@oxfmt/binding-openharmony-arm64@0.70.0': + resolution: {integrity: sha512-Zlom1Xkx257R8bk4ZI4zJsrGno2opknz1+5v5baka3nn4FPyvNSdh8JUL4CdN1S1OWRMvJ9UJQ+RfIqGGCUEfA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.71.0': - resolution: {integrity: sha512-bd5kI8spYwTm3BILDtGhi73zoup5dw8MlPQNT8YB3BD5UIsjNe3K9/4ctrzQMX4SZMoK5HgzVLkLJzacEXB7fA==} + '@oxfmt/binding-win32-arm64-msvc@0.70.0': + resolution: {integrity: sha512-FQgPW5R17vzt7cgrJ8eG/dqX00o2xHsqFeLfw4xzA9FRHpN/DjFo9YDonvIIXGxiEuS9F/jZPnGO+KHNKuCo4Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.71.0': - resolution: {integrity: sha512-W4HvOHGzVLHcrmFu+bMrJlho+/yrlX5ZNdJZqGe8MEldkQG+RHYhxxad9P4jvWAYFmIqUA5i9DQ8QsJqSU9GIw==} + '@oxfmt/binding-win32-ia32-msvc@0.70.0': + resolution: {integrity: sha512-ZfZublNhZ+XBndMiXhkiLlPE+XyGRDa0CweeTL6t1fZypfCh1LTg7e5CvnOeTBunq15MskOcRempumSPGAaCQA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.71.0': - resolution: {integrity: sha512-D2kyEIPHk/G/wiZLnwTVC/sVst+T/lKldVOjAFpgTIBUAOlry72e5OiapDbDBF4LfJLkN5ypJb/8Eu6yJzkveQ==} + '@oxfmt/binding-win32-x64-msvc@0.70.0': + resolution: {integrity: sha512-HlIZEn+WzLQL0DszNzldiRl/DPRCX5R0Vkt6qeUPR1YHwy52hZZo4x6HoTOVmKRP2wUiwPGtKsihNY/f8KRaBg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.85.0': + resolution: {integrity: sha512-q2KO/Zso9UT+OMn0NF9ywn4E4t0MI3yxiDhNyhsQ7DyQJrC4FhFE4TXOi4bktFnOWXTMds8qZSbpv2XwRaNOBg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.85.0': + resolution: {integrity: sha512-SxLN3ALjoT9NNdvpjEevGeHvfzTAFrF0NBYB5tzK7/GtCKMze3j1e/m/X2ozqGj2U9hfGG/dg/OG8vpVK4PiDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.85.0': + resolution: {integrity: sha512-Y/Sup/J4f0f9UGsSd/xyCNTeWL+gepO63GBdEDAfue9nBsnk9zMmnIXx1O6b1V8C90vB5nucYNZ0pbMXAp8zJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.85.0': + resolution: {integrity: sha512-ApOSNC04ynpDTwvBD+//0wyfODRSbEzvRoKpX8teffmc27z8AockwSNeMXGJXn5KP85eahDgR/2llICWLkzcnw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.85.0': + resolution: {integrity: sha512-bNrVrCOA/kHky3Tu79IXWXe5bhIgLXfUuUEDHlAGOHUk96MkvDZ1ecaQF19rwstrnaqfP1o9nBTqzIr9+ZHkUg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.85.0': + resolution: {integrity: sha512-NUrzOJ1s/EqsVvfn2L/1D8Wro2LPIZUbihL8kOJLh5fEdGEN3rdOGUYq3HwnUIL8sjpoP+4N6RaGrgmMJnaMPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.85.0': + resolution: {integrity: sha512-UJXrAT3E/RWkEqXLIs2ehETja1qfgkPb+5gwLIIS+o/6cf+grHvoOXTa5997a/YNQfcJS0DRBTOfZt95cvOI1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.85.0': + resolution: {integrity: sha512-lK40QLjI0HxigO7CjDDshEtfYIeiYS0020v5BHFPqN4uuQBQxd2K9LNom2dW15o9F1937quSCRVp4ZsVhdbYdg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.85.0': + resolution: {integrity: sha512-c2zbdBwGKreHXwRx3gWBuFGJxLhxgsg6YlZ+3H+RgRusU/UEV9jNwJ3HGYK+nRo0LvBa7mt6Kj86xoVotUo8cw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.85.0': + resolution: {integrity: sha512-tlt/Hy8lZ97/lCPmCgw/B3k/mwh+BzaIPbPkldZEly7TwLmx0xe2CQcaW2g/rR0dOgS9JNGCZsMEqLhUNMGvaw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.85.0': + resolution: {integrity: sha512-3tNR9Xey82X0zKuY1d8hJ6Rc9gwRDurmqGLnQZa5xqOXy8/YyiqFXjAtugkKLY82obOlpK1eSiDRlgcNPuxtIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.85.0': + resolution: {integrity: sha512-wbGRd5PqCcjkJFHhZuZ2OBSUQY9czlQsoA/cQQB9JK/L9mC5MQgGoKAh+xd8QjA5V+0D3j+Qd1lAWn1I8zlelA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.85.0': + resolution: {integrity: sha512-3Sn0kSrE4DPZCWV/8o+n4x3aFZxI9ulMnkYlwCbJ8eUVkwRK2IerohE/A/z3SNbCwoPFOCJmGE5Avrq0rrvdvQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.85.0': + resolution: {integrity: sha512-JY2pxxYfB62bAGfejljVCqc44etItehPuAyaeSAdMuEMtwNA00ggMnS66lC1oIhos6oOXUkuU6mZ9bpFh3BqWg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.85.0': + resolution: {integrity: sha512-5k74vZ6qJBjBHEOlBk9B/iv68Yu0F1Afw/vvT2ar6OGCqEeXLaSjXz2n/IPCbhLG22UoKoYEJTzpYraRdcp6PA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.85.0': + resolution: {integrity: sha512-GbAl5qt5TCkPLXTaIISZJnugrcBhra6rodcXc9jYt620UtdsTt71NlNmJmm0frxzFpd54x/G+MkitEJA8I/BoA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.85.0': + resolution: {integrity: sha512-kjmws5MK0et2swk4ND85D7NVQyDHw162i6whtZDLUA/lo6FQyBZDcmMRCMcVZcNrAhIaftVb00x9ChGDOjjNJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.85.0': + resolution: {integrity: sha512-eSsIJx9n4yxvOqYTZyPEMyEXRmE60XH7xGAU7i0Qbsn1lf6Za3CWJ9aRd82oSFKXaxhp+sA6/yMJVRIpLpna6A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.85.0': + resolution: {integrity: sha512-pBebIPUpKKhWrhSMWhy8TdAZBewiXnfxmaAGxhzxM1068GagqFaTwgKlU6e+UyJ2sPR+VoHouhXuGJkQjsrDvA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -895,6 +1204,42 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@redis/bloom@6.2.1': + resolution: {integrity: sha512-huQgNLaCIZfQ9SeLn4q9124uOUd8HbZDYHwwUzNcRgHqCHiHKl2dDxMqJCeWh8cMqZAoWuHR8XnWbDMIf+o7ag==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@redis/client': ^6.2.1 + + '@redis/client@6.2.1': + resolution: {integrity: sha512-LzxBY7SIBvvJiyCgcaJZZakE3fJrZZ++i24+EDW9fKpCl68D35uJcKFpZZwCfOoG9WZTbyZlMzMeM0gtOAMU9Q==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@node-rs/xxhash': ^1.1.0 + '@opentelemetry/api': '>=1 <2' + peerDependenciesMeta: + '@node-rs/xxhash': + optional: true + '@opentelemetry/api': + optional: true + + '@redis/json@6.2.1': + resolution: {integrity: sha512-AFIUJ8Gj0DaaSBHYuSt8+O0oYWM+50OK1c0OmodB7XERIA8+BbyV3O4v76f9iccWasd1/7qjfZTpuzexUaZtrQ==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@redis/client': ^6.2.1 + + '@redis/search@6.2.1': + resolution: {integrity: sha512-2vfOAOyYFE7UUw3sBBlkqqruBtOUS4HRY5MtW4hp83llrwvtrTE4r22CEqXddlV+54zkLxBE4nmsIJ/dpezQrQ==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@redis/client': ^6.2.1 + + '@redis/time-series@6.2.1': + resolution: {integrity: sha512-kiYniph04dJOole+L359B6C9E+jYS2uDP7hca6Onj0xF38ZIpyxARO0Iq0W4ZRn1e8Q6vqW00QFZVSMRA/2Ijw==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@redis/client': ^6.2.1 + '@rollup/rollup-android-arm-eabi@4.56.0': resolution: {integrity: sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==} cpu: [arm] @@ -1059,8 +1404,8 @@ packages: '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - '@toon-format/toon@2.3.0': - resolution: {integrity: sha512-/Ew9etdRQKVMnm9fDaCG0JjyAOK/O7T0M97oum1aW4W+UR8ZhVVPBanIV7oWgHBiGlnVxV9M55PWQCHofDV07w==} + '@toon-format/toon@4.1.1': + resolution: {integrity: sha512-SGCkS7IjVpwRmGPgnY8ENKpAf0EdAnZDOQkvFW0d2cgOpdn9FEFl7sTgryESyypXrWr0YajHGpwsAUX4zw9ZvA==} '@ts-graphviz/adapter@2.0.6': resolution: {integrity: sha512-kJ10lIMSWMJkLkkCG5gt927SnGZcBuG0s0HHswGzcHTgvtUe7yk5/3zTEr0bafzsodsOq5Gi6FhQeV775nC35Q==} @@ -1108,15 +1453,18 @@ packages: '@types/jsesc@2.5.1': resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - '@types/node@25.8.0': - resolution: {integrity: sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==} - '@types/node@26.0.0': resolution: {integrity: sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==} + '@types/node@26.6.2': + resolution: {integrity: sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==} + '@types/qs@6.14.0': resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} @@ -1161,43 +1509,51 @@ packages: resolution: {integrity: sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260130.1': - resolution: {integrity: sha512-Jo5kVoxaewKPn/3bKWyUB/gPR+Tjhj6isLc8VshV4OyFX4n6pkvVyk3ANivl7Kwmiv3WGKGUotbZ71DKCZATwA==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260707.2': + resolution: {integrity: sha512-wny2pgKjGbiZtnOIHVa3tXC1UfDqxNEFzyPGmiqybedG8hipG2Nfp0l5UxbaKCjkLacUpH/W5bP2hBOMVhCOzg==} + engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260130.1': - resolution: {integrity: sha512-dR0fjdcLykfiDOIKjZMGqPBHVl9Dd/C+jFU43Wr3dcPFPFf1oVYsaWAZBSkTXnN9QP8i0/ZV+ZUr1gDjoi3x0Q==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260707.2': + resolution: {integrity: sha512-Afc7M5zOwo+GpfcYwz5Z8HMB2tPVsui7nNIqEuuFB73MPdVqNn/Wmpe4tP4MRri0AtJnJknoHBaTJ/VDAp/Jhw==} + engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260130.1': - resolution: {integrity: sha512-P/1YTpIiFd2pPtHt4sKEmUTaKf1xvuuiV0TvhQ7n2gDYskNjZ66iWCC9w7okjgsmWE9JLh/IRrNcb9FKVk3SHw==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260707.2': + resolution: {integrity: sha512-iITBa2WjjTI5N9t5l7Z4KoOSI+2zBlhbvFzsD/f8qX8QoKjz/Y4DPyBDgezYi8nkqjjksbgSOJ3/ykzhwrB9cg==} + engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260130.1': - resolution: {integrity: sha512-wnx4bY/1u006U67fEkPtPVZ65VYMLgkFqOadGyrUxhtveR5WbbgFUuUBES0mPxvzS4ToZzn94jhcnAvN8VOTcA==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260707.2': + resolution: {integrity: sha512-hJm/UOqZTr9FHmR7uNm8VGX4oKtfWk0Jem0zPeJFNC8ckGUfSBueyiEYMZB+XmRc1aG4x1E46y3CplP4CLHvGQ==} + engines: {node: '>=16.20.0'} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260130.1': - resolution: {integrity: sha512-OgHVjivuOS22WIZvIm+Pnm7yqFLwonkIrBOxRdew/pPwVGLQVSo+bQ+RocQDj2VFYxXcHs2yXwCk3PDmwLIYYg==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260707.2': + resolution: {integrity: sha512-du0dzi6y97Po5vDNdPJTyyijHCpaS22JLRnKZEJXBDaO9gCIymOv/5QQokFRuOlQm0bWl3i9PF4OVdGP6uAOQA==} + engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260130.1': - resolution: {integrity: sha512-f/DUxQtIWkZq0eUjZHFmaSxterO/ccu1NxFk0L/Oqj7AfjWVDCqrLVgZJKjvwcG5TEb5AVt7GMUpGEAYZQiUvg==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260707.2': + resolution: {integrity: sha512-SsAwfhyHJ1akgBc+99z4+hwdbHsdWaKB8EwCNIMA6JfSLMeUjffrYvxu+vfMyxVtOVOz7RrRXRoiDiu4a2sCtg==} + engines: {node: '>=16.20.0'} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260130.1': - resolution: {integrity: sha512-Isr051Cq8RbXOUMYYmwLYw8yBGaEG/Zp0sp7HNeYhVVkc3/3KeveEqCk29q1QRwiBr7HnApdzJP7f+lSZk8gmg==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260707.2': + resolution: {integrity: sha512-DL4u27stv0fo71sVhOzHSwE+YMZsbBijVI+kg5dLDLilSH79WFTJ8RSQ46vJrCMt+Gjlv/JOZP1PuLJDfioYeQ==} + engines: {node: '>=16.20.0'} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260130.1': - resolution: {integrity: sha512-lvt9sECmBkrABxl3rMNRAX2unzhYcoNhlTyR7rOvbyM//QTXKUctVD7ByWBvk02et2caUUwIWq2vnygaeW8Mew==} + '@typescript/native-preview@7.0.0-dev.20260707.2': + resolution: {integrity: sha512-oUGp+Rep/hqMhPunyinsALUwSlzHINSxitifPiSaeqoKOKD2OlR9NE3TaPqwsl4NlGslsOSUXI1JotWQzpYCPg==} + engines: {node: '>=16.20.0'} hasBin: true '@typescript/vfs@1.6.2': @@ -1205,25 +1561,30 @@ packages: peerDependencies: typescript: ^6.0.3 - '@valibot/to-json-schema@1.7.1': - resolution: {integrity: sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A==} + '@valibot/to-json-schema@1.8.0': + resolution: {integrity: sha512-a0M+uwCuQZEPAo65NYkFSJ14O5c213KoSZmPdoCfuFvUhfULi4T2Z6Tpjv6lTVMW9RoLm74RpRAjNEa43KcN+g==} peerDependencies: - valibot: ^1.4.0 + valibot: ^1.5.0 - '@vitest/coverage-v8@4.1.9': - resolution: {integrity: sha512-G9/lgqibheLVBDRuya45EbsEXTYcWoSG+TLg7i2axuzx0Eq62eXn+aWXyaVdV5vKvFSWd6ywcX8hA7la9Pvu8g==} + '@vitest/coverage-v8@5.0.2': + resolution: {integrity: sha512-3ffHBEi8DOOBLwIGBhOBZbRfYFYWjMUuxZicONdhuFEFEG5AVsMOSrVeuROskqnqpbOmEJwxM2eTVZ9N3a1t+A==} peerDependencies: - '@vitest/browser': 4.1.9 - vitest: 4.1.9 + '@vitest/browser': 5.0.2 + vitest: 5.0.2 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.1.9': - resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==} + '@vitest/istanbul-lib-coverage@1.0.1': + resolution: {integrity: sha512-k3DJZ8LhMBK9NS4SclF1ASD3OgXEWDorbIcPTRDK0/Zae6fRvu+fJRxtFdLfHsa9Y24beCdPnoNZ4LviTNstfA==} + engines: {node: '>=22'} - '@vitest/mocker@4.1.9': - resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==} + '@vitest/istanbul-lib-report@1.0.1': + resolution: {integrity: sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==} + engines: {node: '>=22'} + + '@vitest/mocker@5.0.2': + resolution: {integrity: sha512-Z5FS00Q1SJHkB35xATsmWGdQ5WA1/0MV3CDjqyv7GavHv1OfOj145MNfHOlHk7QLes21dKFDHr8EO2zvL+9WGA==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1233,25 +1594,19 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.9': - resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==} - - '@vitest/runner@4.1.9': - resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==} + '@vitest/pretty-format@5.0.2': + resolution: {integrity: sha512-YHM+mQQ7N1ROHMugJ7P/M+fC/q1G4zs/iAMZZCvhHcY8WkwPQcXBHD+z18oz+808Cfa12K7jOr3XPQMcLIovjw==} - '@vitest/snapshot@4.1.9': - resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==} + '@vitest/spy@5.0.2': + resolution: {integrity: sha512-Ijc7T1nT9efNb5LxvjaBrEqw3f/QwUv5EE0nKqZxgqsaV/FxAAZ8baGylA8X/Z2oS4Lp+K74Jr6dTJsDKxJDeg==} - '@vitest/spy@4.1.9': - resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==} - - '@vitest/ui@4.1.9': - resolution: {integrity: sha512-U/cRvtqfEPj27FI1n9cyUvi4vXXdcLhjJiI+InYKdk8hP4VrS6RXOjGL7rfFaeBc37iRKANsR6eEzIoC7lmgBQ==} + '@vitest/ui@5.0.2': + resolution: {integrity: sha512-FRGWhLK/S+aaAtty/5d+jTtY5l+9pGNcjz6IHbyUiP3VWs6a+zIgU4lc1SzWCR7H+OXgetAI06i/VpCWQmVorQ==} peerDependencies: - vitest: 4.1.9 + vitest: 5.0.2 - '@vitest/utils@4.1.9': - resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==} + '@vitest/utils@5.0.2': + resolution: {integrity: sha512-mgpUtxFhKeOKVaArBVHNJGdYHAeRh135o59l13uVmPUCS1OUtis5pBqXEgE25iqRgFqEBwXvJlkhknRsYnMeqg==} '@vue/compiler-core@3.5.26': resolution: {integrity: sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==} @@ -1287,6 +1642,14 @@ packages: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -1317,10 +1680,6 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - app-module-path@2.2.0: resolution: {integrity: sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==} @@ -1338,8 +1697,8 @@ packages: resolution: {integrity: sha512-WHw67kLXYbZuHTmcdbIrVArCq5wxo6NEuj3hiYAWr8mwJeC+C2mMCIBIWCiDoCye/OF/xelc+teJ1ERoWmnEIA==} engines: {node: '>=18'} - ast-v8-to-istanbul@1.0.0: - resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} + ast-v8-to-istanbul@1.0.7: + resolution: {integrity: sha512-kFL68AG6ajd8fg248zwM9GQrUWEp79gsmjum34OEXjs4yHuUMZfYKwOLW9GMmB4oNvVrj+EAGxsP7ye2UR9UlA==} asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -1370,10 +1729,6 @@ packages: before-after-hook@4.0.0: resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -1419,6 +1774,9 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + call-me-maybe@1.0.2: + resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} + caniuse-lite@1.0.30001766: resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} @@ -1430,9 +1788,9 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} @@ -1442,6 +1800,10 @@ packages: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + cliui@9.0.1: resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} engines: {node: '>=20'} @@ -1542,8 +1904,8 @@ packages: resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} engines: {node: '>=18'} - default-browser@5.4.0: - resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==} + default-browser@5.5.1: + resolution: {integrity: sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==} engines: {node: '>=18'} defaults@1.0.4: @@ -1560,10 +1922,6 @@ packages: delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - denque@2.1.0: - resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} - engines: {node: '>=0.10'} - depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} @@ -1581,10 +1939,6 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - detective-amd@6.0.1: resolution: {integrity: sha512-TtyZ3OhwUoEEIhTFoc1C9IyJIud3y+xYkSRjmvCt65+ycQuc3VcBrPRTMWoO/AnuCyOB8T5gky+xf7Igxtjd3g==} engines: {node: '>=18'} @@ -1628,10 +1982,6 @@ packages: peerDependencies: typescript: ^6.0.3 - dprint@0.54.0: - resolution: {integrity: sha512-sIy25poR2gRP/tWPTgP0MPeJoJcpv0xzYDcsboapvthbEt1Qw3Al252CA0xFyIh2cYEGGKyBJtKokryv4ERlJw==} - hasBin: true - dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1639,11 +1989,11 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - effect@3.21.3: - resolution: {integrity: sha512-RqwU7WnJ6CqYhyjpOVJA5vh1Sgkn6eVECO6mnD0EjlbWcC2M3LJaPglXXr13Rdo/Y+B+wTEPzGRYFNL2xKxNeQ==} + effect@3.22.2: + resolution: {integrity: sha512-fm1CQXCs7GTMwpadZxwU5rnAb2bN6mbQpknq/pXBGCzSKkosNYb9k0wsoL0TfB10rSa5xOEZJjo77u/KLXSANg==} - effect@4.0.0-beta.85: - resolution: {integrity: sha512-Cjv9YQyv4CiIccmRAQIWAoeESCpCpiuHYY8zb5vqiYs3Ac2yE5RQAnKq7z4Ir/3VJYZb8kxh6rS8czDDnpTdkQ==} + effect@4.0.0-rc.117: + resolution: {integrity: sha512-UUyi9QiOW4nySFIBM3KnYbNMd9EZliOdKfBcyLD0mPMPheO2fkuXS5y0opjWc9oqzlJxPQICEGm8gGw3kh9j/w==} electron-to-chromium@1.5.278: resolution: {integrity: sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==} @@ -1678,8 +2028,8 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@2.1.0: - resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} @@ -1689,8 +2039,8 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - es-toolkit@1.47.1: - resolution: {integrity: sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q==} + es-toolkit@1.52.0: + resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==} esbuild@0.27.2: resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} @@ -1751,8 +2101,8 @@ packages: resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} express-rate-limit@8.2.1: @@ -1773,10 +2123,6 @@ packages: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} - fast-check@4.8.0: - resolution: {integrity: sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==} - engines: {node: '>=12.17.0'} - fast-content-type-parse@3.0.0: resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} @@ -1787,15 +2133,30 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fastmcp@4.3.0: - resolution: {integrity: sha512-Meg4i34UnJjCBmbfNAFFFe/8jzcm6Sy7FemHYCvMPe2S/sUAzsE1OwVbBmKS6NtzRh0zh/GoJi98YxuLgDAi+Q==} + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + + fastmcp@4.20.14: + resolution: {integrity: sha512-Cbw0oIJdpOnbFEign0h44hAQ71HTvMbSeS3irR/WtArOiI8nkg4e6E/P1WDaSDJ/V2qpL/77FwCQmeM3z+jYYg==} hasBin: true peerDependencies: - jose: ^5.0.0 + ajv: ^8.0.0 + ajv-formats: ^3.0.0 + jose: ^5.0.0 || ^6.0.0 peerDependenciesMeta: + ajv: + optional: true + ajv-formats: + optional: true jose: optional: true @@ -1811,8 +2172,8 @@ packages: picomatch: optional: true - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} figures@6.1.0: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} @@ -1835,11 +2196,12 @@ packages: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} - find-my-way-ts@0.1.6: - resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==} + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} - flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + flru@1.0.2: + resolution: {integrity: sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==} + engines: {node: '>=6'} follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} @@ -1928,12 +2290,12 @@ packages: resolution: {integrity: sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==} engines: {node: '>=16'} - globals@17.7.0: - resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} + globals@17.12.0: + resolution: {integrity: sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==} engines: {node: '>=18'} - globby@16.2.0: - resolution: {integrity: sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q==} + globby@16.2.4: + resolution: {integrity: sha512-c8B/VNLmxRcmqqenRA9t+9IyOjf9+V6lTxPaUJLqOCONdQkWZ0ETYgX0qbtJqPsgCNusT9MZ5Jeidw8Eb9tn2g==} engines: {node: '>=20'} globrex@0.1.2: @@ -1975,9 +2337,6 @@ packages: resolution: {integrity: sha512-eVkB/CYCCei7K2WElZW9yYQFWssG0DhaDhVvr7wy5jJ22K+ck8fWW0EsLpB0sITUTvPnc97+rrbQqIr5iqiy9Q==} engines: {node: '>=16.9.0'} - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - http-assert@1.5.0: resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} engines: {node: '>= 0.8'} @@ -2025,14 +2384,6 @@ packages: resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ini@7.0.0: - resolution: {integrity: sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w==} - engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} - - ioredis@5.10.1: - resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} - engines: {node: '>=12.22.0'} - ip-address@10.0.1: resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} engines: {node: '>= 12'} @@ -2041,10 +2392,6 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -2137,22 +2484,13 @@ packages: peerDependencies: ws: '*' - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - - istanbul-reports@3.2.0: - resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} - engines: {node: '>=8'} - jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true + jose@6.2.12: + resolution: {integrity: sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==} + jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} @@ -2167,6 +2505,10 @@ packages: resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} hasBin: true + js-yaml@5.4.2: + resolution: {integrity: sha512-m+aqu+LwO1O6sIopafj8HUVl5aawITwZQe/yHpMCKjaWBaA/d07B/QdMb3529REftiU+RMMHL3Vlsw3hON7vWg==} + hasBin: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -2209,15 +2551,6 @@ packages: resolution: {integrity: sha512-KDDuvpfqSK0ZKEO2gCPedNjl5wYpfj+HNiuVRlbhd1A88S3M0ySkdf2V/EJ4NWt5dwh5PXCdcenrKK2IQJAxsg==} engines: {node: '>= 18'} - kubernetes-types@1.30.0: - resolution: {integrity: sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==} - - lodash.defaults@4.2.0: - resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} - - lodash.isarguments@3.1.0: - resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} - log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} @@ -2239,19 +2572,18 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magicast@0.5.2: - resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + magic-string@1.4.2: + resolution: {integrity: sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==} - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} + magicast@0.5.5: + resolution: {integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==} math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - mcp-proxy@6.5.2: - resolution: {integrity: sha512-3sE9kedh81dqqpObzO0nUf8aAyGnzLVDVH97GCLL3fXCBOkXDWZBts63u0bM5lT1Q4FKZY0+E91dZ7Chb2ybsA==} + mcp-proxy@6.7.19: + resolution: {integrity: sha512-Nbfjj4VwVka1yAR/0fdD2G8borGOfjHEmfUzPFCH2nfQUNjWIij6s3gXMUhi2GUYE3FpUu8iFKE75ZQe/g1n8Q==} hasBin: true media-typer@1.1.0: @@ -2286,11 +2618,6 @@ packages: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} - mime@4.1.0: - resolution: {integrity: sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==} - engines: {node: '>=16'} - hasBin: true - mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -2317,6 +2644,9 @@ packages: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} + minisearch@7.2.0: + resolution: {integrity: sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==} + module-definition@6.0.1: resolution: {integrity: sha512-FeVc50FTfVVQnolk/WQT8MX+2WVcDnTGiq6Wo+/+lJ2ET1bRVi3HG3YlJUfqagNMc/kUlFSoR96AJkxGpKz13g==} engines: {node: '>=18'} @@ -2334,15 +2664,9 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msgpackr-extract@3.0.4: - resolution: {integrity: sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==} - hasBin: true - - msgpackr@2.0.4: - resolution: {integrity: sha512-o1C5KRmuRt+apqMr1HuGSqWStZoRBUpEsCsl15uM9VdAF1qHLtvMOU2En747EnTyEl6c4pzPewRMFF31s1CNbA==} - - multipasta@0.2.7: - resolution: {integrity: sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} @@ -2357,10 +2681,6 @@ packages: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} - node-gyp-build-optional-packages@5.2.2: - resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} - hasBin: true - node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -2368,10 +2688,6 @@ packages: resolution: {integrity: sha512-3VW/8JpPqPvnJvseXowjZcirPisssnBuDikk6JIZ8jQzF7KJQX52iPFX4RYYxLycYH7IbMRSPUOga/esVjy5Yg==} engines: {node: '>=18'} - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - npm-run-path@6.0.0: resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} engines: {node: '>=18'} @@ -2387,6 +2703,10 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + engines: {node: '>=12.20.0'} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -2398,28 +2718,44 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - open@11.0.0: - resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + open@11.0.4: + resolution: {integrity: sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==} engines: {node: '>=20'} + openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + ora@5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} - ox@0.14.29: - resolution: {integrity: sha512-M5j87Ec4V99MQdRct/g09eWXW60g6zhHTUs1lr4deUtrPDnezBdCJTgKd7pxqTpSZBFveV0ALi9jMMuT1qKyNg==} + ox@0.14.45: + resolution: {integrity: sha512-jpsQ+p0JZh9mKCxxzxz0d5qFHZZg2/O9xW18IpEC/dNXXPVqmDJ0c//9RMz0CILkbSGPA42+cbU0fC/ghwJ03w==} peerDependencies: typescript: ^6.0.3 peerDependenciesMeta: typescript: optional: true - oxlint@1.71.0: - resolution: {integrity: sha512-U1m1X+C0vDj7DC1e13IoZULzEcPczE7UOMTs8VlZGHUEIUaSTZKo5qkPsQEfzpgnQ29Pea/w3Xntk62UCecxZw==} + oxfmt@0.70.0: + resolution: {integrity: sha512-IsHxZ4y0wQLLMhnrJblBJgZsLDzfULrJnAw5j/QqsTlMa/m3AqsbToi+W71uhBGaqlqq/PbbjvHc09TJwdv3Tw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true + + oxlint@1.85.0: + resolution: {integrity: sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.22.1' + oxlint-tsgolint: '>=7.0.2001' vite-plus: '*' peerDependenciesMeta: oxlint-tsgolint: @@ -2475,6 +2811,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + pipenet@1.4.0: resolution: {integrity: sha512-Uc3EH2i8hnJUD0Eupj9z2jaZPjjAbooaiHGh0iFdExbE8/BDt6Lf0919Dtwx5VM83elHNWFzCOsvzsViTD5YZg==} engines: {node: '>=22.0.0'} @@ -2502,6 +2842,10 @@ packages: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} engines: {node: '>=20'} + powershell-utils@0.2.1: + resolution: {integrity: sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==} + engines: {node: '>=20'} + precinct@12.2.0: resolution: {integrity: sha512-NFBMuwIfaJ4SocE9YXPU/n4AcNSoFMVFjP72nvl3cx69j/ke61/hPOWFREVxLkFhhEGnA8ZuVfTqJBa+PK3b5w==} engines: {node: '>=18'} @@ -2532,9 +2876,6 @@ packages: pure-rand@6.1.0: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - pure-rand@8.4.0: - resolution: {integrity: sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==} - qs@6.14.1: resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} engines: {node: '>=0.6'} @@ -2561,17 +2902,13 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - redis-errors@1.2.0: - resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} - engines: {node: '>=4'} + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} + engines: {node: '>= 20.19.0'} - redis-parser@3.0.0: - resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} - engines: {node: '>=4'} + redis@6.2.1: + resolution: {integrity: sha512-Z9VHtgYs48PiQC77X9O2Er8Hj4T+5BtFjT91/vi5Is1D04N72cA946ZslM1ImJw8ZctFBZWAVjM7S5wJNeHMpg==} + engines: {node: '>= 20.0.0'} require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} @@ -2617,8 +2954,8 @@ packages: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} - rulesync@8.31.0: - resolution: {integrity: sha512-/h0SkguzxbG//e6svwKdExqfAxuq158LP1wt7OQYC7k5OmTPXHwHuC+HpMtwfqQhHjyWJlGei/6eBZ8HGUPa0Q==} + rulesync@19.0.0: + resolution: {integrity: sha512-zzGz8/V/bbJjhTHuUc8AUJTF3iPozG2b+IwJjiqYNtxGNTq4zgfQocXbzx2WoyTJo4xpvjWefUGthwzjL0Dqjg==} engines: {node: '>=22.0.0', pnpm: '>=10'} hasBin: true @@ -2684,9 +3021,6 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -2702,8 +3036,8 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} - smol-toml@1.6.1: - resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} + smol-toml@1.8.0: + resolution: {integrity: sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==} engines: {node: '>= 18'} source-map-js@1.2.1: @@ -2717,12 +3051,6 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - standard-as-callback@2.1.0: - resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} - statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -2731,8 +3059,8 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@4.1.0: - resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} stream-to-array@2.3.0: resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} @@ -2804,19 +3132,28 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinybench@6.2.0: + resolution: {integrity: sha512-78U2TlB2CnVenajOFzf3BKSm0J6oz5L0NV7g32LCPccvYc0lbWvys4d3uUUCS2B1N8PAf2+aekR8i1KbC3HO7Q==} + engines: {node: '>=20.0.0'} - tinyexec@1.0.2: - resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} engines: {node: '>=18'} tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinypool@2.1.2: + resolution: {integrity: sha512-9YodfrxS9g9IbFr/KOjE5bAeJ0p61n3bW6mqvy0jtoeKd1kTW1Cxm0oulm6KX2lyM9Gl6WIe8nEbY7LWv5ZJww==} + engines: {node: ^20.0.0 || >=22.0.0} + + tinyrainbow@3.1.1: + resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} tldjs@2.3.2: @@ -2835,10 +3172,6 @@ packages: resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} engines: {node: '>=14.16'} - toml@4.1.1: - resolution: {integrity: sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==} - engines: {node: '>=20'} - totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -2890,18 +3223,14 @@ packages: resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - undici@7.25.0: - resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} - engines: {node: '>=20.18.1'} + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} - undici@8.5.0: - resolution: {integrity: sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==} + undici@8.11.2: + resolution: {integrity: sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==} engines: {node: '>=22.19.0'} unicorn-magic@0.3.0: @@ -2931,12 +3260,8 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@14.0.1: - resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==} - hasBin: true - - valibot@1.2.0: - resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} + valibot@1.5.0: + resolution: {integrity: sha512-nil6AkP2TChWL43Z5uJ6GTxX01CUA+g8LWUM+N/rB9NBbkUMaUsi9PUNzlUPgoKASgmx9f7eGOYpJ04/fSa6FQ==} peerDependencies: typescript: ^6.0.3 peerDependenciesMeta: @@ -2947,8 +3272,12 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - viem@2.53.1: - resolution: {integrity: sha512-FhfJ/SW73CVosiyVLmIMVgKDRKYV1AGCLzZoHYvmNayyVff63Qi1ocPCk59LqC/cNw244RbBJjHnmxqXkE7NpA==} + verkit@0.3.2: + resolution: {integrity: sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==} + engines: {node: '>=18.12.0'} + + viem@2.56.9: + resolution: {integrity: sha512-UC1G0Qz+RbPKqwl6yRrp3dH6ltrOmUcFJMTcOyE1Mg0nO5lxrEBTONh/8OFWPnALGuN/O7LkNcz/IsSj83Svqw==} peerDependencies: typescript: ^6.0.3 peerDependenciesMeta: @@ -3003,23 +3332,23 @@ packages: vitest-mock-express@2.2.0: resolution: {integrity: sha512-JoKVdo9KM4K5wo9GcQ0GabiRFU5XP2yJVMFv9R/C6arcv5wTPoDAiama2jJelO/ieIq+XcGETLr/V4vjCuqofA==} - vitest@4.1.9: - resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + vitest@5.0.2: + resolution: {integrity: sha512-7MQrx9pDv5aHiUcovIb/70Ys3tgtkUVgCtledvKdCmEO+/1Dicq5ZqoSxOW034m03oqC+oHOKui2dM6qtMLoJg==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.9 - '@vitest/browser-preview': 4.1.9 - '@vitest/browser-webdriverio': 4.1.9 - '@vitest/coverage-istanbul': 4.1.9 - '@vitest/coverage-v8': 4.1.9 - '@vitest/ui': 4.1.9 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.2 + '@vitest/browser-preview': 5.0.2 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.2 + '@vitest/coverage-v8': 5.0.2 + '@vitest/ui': 5.0.2 happy-dom: '*' jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -3061,9 +3390,9 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} + why-is-node-running@3.2.2: + resolution: {integrity: sha512-NKUzAelcoCXhXL4dJzKIwXeR8iEVqsA0Lq6Vnd0UXvgaKbzVo4ZTHROF2Jidrv+SgxOQ03fMinnNhzZATxOD3A==} + engines: {node: '>=20.11'} hasBin: true wrap-ansi@9.0.2: @@ -3073,8 +3402,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.20.0: - resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -3085,8 +3414,8 @@ packages: utf-8-validate: optional: true - ws@8.20.1: - resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -3097,8 +3426,8 @@ packages: utf-8-validate: optional: true - wsl-utils@0.3.1: - resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + wsl-utils@1.0.0: + resolution: {integrity: sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==} engines: {node: '>=20'} xsschema@0.4.4: @@ -3150,44 +3479,67 @@ packages: peerDependencies: zod: ^3.25 || ^4 - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} - zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zod@4.6.5: + resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==} + snapshots: '@adraffy/ens-normalize@1.11.1': {} - '@babel/cli@8.0.1(@babel/core@8.0.1)': + '@apidevtools/json-schema-ref-parser@14.0.1': dependencies: - '@babel/core': 8.0.1 + '@types/json-schema': 7.0.15 + js-yaml: 4.2.0 + + '@apidevtools/openapi-schemas@2.1.0': {} + + '@apidevtools/swagger-methods@3.0.2': {} + + '@apidevtools/swagger-parser@12.1.0(openapi-types@12.1.3)': + dependencies: + '@apidevtools/json-schema-ref-parser': 14.0.1 + '@apidevtools/openapi-schemas': 2.1.0 + '@apidevtools/swagger-methods': 3.0.2 + ajv: 8.17.1 + ajv-draft-04: 1.0.0(ajv@8.17.1) + call-me-maybe: 1.0.2 + openapi-types: 12.1.3 + + '@babel/cli@8.0.6(@babel/core@8.0.6)': + dependencies: + '@babel/core': 8.0.6 '@jridgewell/trace-mapping': 0.3.31 + chokidar: 5.0.0 commander: 14.0.3 convert-source-map: 2.0.0 glob: 13.0.6 slash: 5.1.0 - optionalDependencies: - chokidar: 3.6.0 '@babel/code-frame@8.0.0': dependencies: '@babel/helper-validator-identifier': 8.0.2 js-tokens: 10.0.0 - '@babel/compat-data@8.0.0': {} + '@babel/code-frame@8.0.6': + dependencies: + '@babel/helper-validator-identifier': 8.0.6 + js-tokens: 10.0.0 + + '@babel/compat-data@8.0.5': {} - '@babel/core@8.0.1': + '@babel/core@8.0.6': dependencies: - '@babel/code-frame': 8.0.0 - '@babel/generator': 8.0.0 - '@babel/helper-compilation-targets': 8.0.0 - '@babel/helpers': 8.0.0 - '@babel/parser': 8.0.0 + '@babel/code-frame': 8.0.6 + '@babel/generator': 8.0.6 + '@babel/helper-compilation-targets': 8.0.6 + '@babel/helpers': 8.0.5 + '@babel/parser': 8.0.6 '@babel/template': 8.0.0 - '@babel/traverse': 8.0.0 - '@babel/types': 8.0.0 + '@babel/traverse': 8.0.6 + '@babel/types': 8.0.6 '@types/gensync': 1.0.5 convert-source-map: 2.0.0 empathic: 2.0.1 @@ -3195,7 +3547,7 @@ snapshots: import-meta-resolve: 4.2.0 json5: 2.2.3 obug: 2.1.1 - semver: 7.8.0 + verkit: 0.3.2 '@babel/generator@8.0.0': dependencies: @@ -3206,65 +3558,92 @@ snapshots: '@types/jsesc': 2.5.1 jsesc: 3.1.0 - '@babel/helper-compilation-targets@8.0.0': + '@babel/generator@8.0.6': + dependencies: + '@babel/parser': 8.0.6 + '@babel/types': 8.0.6 + '@jridgewell/gen-mapping': 0.4.0-beta.0 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@8.0.6': dependencies: - '@babel/compat-data': 8.0.0 + '@babel/compat-data': 8.0.5 '@babel/helper-validator-option': 8.0.0 browserslist: 4.28.1 - lru-cache: 11.2.4 - semver: 7.8.0 + flru: 1.0.2 + verkit: 0.3.2 '@babel/helper-globals@8.0.0': {} + '@babel/helper-globals@8.0.6': {} + '@babel/helper-module-imports@8.0.0': dependencies: '@babel/traverse': 8.0.0 '@babel/types': 8.0.0 - '@babel/helper-module-transforms@8.0.1(@babel/core@8.0.1)': + '@babel/helper-module-transforms@8.0.1(@babel/core@8.0.6)': dependencies: - '@babel/core': 8.0.1 + '@babel/core': 8.0.6 '@babel/helper-module-imports': 8.0.0 '@babel/helper-validator-identifier': 8.0.2 '@babel/traverse': 8.0.0 - '@babel/helper-plugin-utils@8.0.1(@babel/core@8.0.1)': + '@babel/helper-plugin-utils@8.0.1(@babel/core@8.0.6)': dependencies: - '@babel/core': 8.0.1 + '@babel/core': 8.0.6 '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} + '@babel/helper-string-parser@8.0.0': {} + '@babel/helper-string-parser@8.0.6': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-identifier@8.0.2': {} + '@babel/helper-validator-identifier@8.0.6': {} + '@babel/helper-validator-option@8.0.0': {} - '@babel/helpers@8.0.0': + '@babel/helpers@8.0.5': dependencies: '@babel/template': 8.0.0 - '@babel/types': 8.0.0 + '@babel/types': 8.0.6 '@babel/parser@7.29.3': dependencies: '@babel/types': 7.29.0 + '@babel/parser@7.29.9': + dependencies: + '@babel/types': 7.29.8 + '@babel/parser@8.0.0': dependencies: '@babel/types': 8.0.0 - '@babel/plugin-transform-export-namespace-from@8.0.1(@babel/core@8.0.1)': + '@babel/parser@8.0.6': + dependencies: + '@babel/types': 8.0.6 + + '@babel/plugin-transform-export-namespace-from@8.0.1(@babel/core@8.0.6)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 8.0.6 + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) - '@babel/plugin-transform-modules-commonjs@8.0.1(@babel/core@8.0.1)': + '@babel/plugin-transform-modules-commonjs@8.0.1(@babel/core@8.0.6)': dependencies: - '@babel/core': 8.0.1 - '@babel/helper-module-transforms': 8.0.1(@babel/core@8.0.1) - '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.1) + '@babel/core': 8.0.6 + '@babel/helper-module-transforms': 8.0.1(@babel/core@8.0.6) + '@babel/helper-plugin-utils': 8.0.1(@babel/core@8.0.6) '@babel/template@8.0.0': dependencies: @@ -3282,146 +3661,128 @@ snapshots: '@babel/types': 8.0.0 obug: 2.1.1 + '@babel/traverse@8.0.6': + dependencies: + '@babel/code-frame': 8.0.6 + '@babel/generator': 8.0.6 + '@babel/helper-globals': 8.0.6 + '@babel/parser': 8.0.6 + '@babel/template': 8.0.0 + '@babel/types': 8.0.6 + obug: 2.1.1 + '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/types@8.0.0': dependencies: '@babel/helper-string-parser': 8.0.0 '@babel/helper-validator-identifier': 8.0.2 + '@babel/types@8.0.6': + dependencies: + '@babel/helper-string-parser': 8.0.6 + '@babel/helper-validator-identifier': 8.0.6 + '@bcoe/v8-coverage@1.0.2': {} '@borewit/text-codec@0.2.1': {} - '@bufbuild/buf-darwin-arm64@1.71.0': + '@bufbuild/buf-darwin-arm64@1.73.0': optional: true - '@bufbuild/buf-darwin-x64@1.71.0': + '@bufbuild/buf-darwin-x64@1.73.0': optional: true - '@bufbuild/buf-linux-aarch64@1.71.0': + '@bufbuild/buf-linux-aarch64@1.73.0': optional: true - '@bufbuild/buf-linux-armv7@1.71.0': + '@bufbuild/buf-linux-armv7@1.73.0': optional: true - '@bufbuild/buf-linux-x64@1.71.0': + '@bufbuild/buf-linux-x64@1.73.0': optional: true - '@bufbuild/buf-win32-arm64@1.71.0': + '@bufbuild/buf-win32-arm64@1.73.0': optional: true - '@bufbuild/buf-win32-x64@1.71.0': + '@bufbuild/buf-win32-x64@1.73.0': optional: true - '@bufbuild/buf@1.71.0': + '@bufbuild/buf@1.73.0': optionalDependencies: - '@bufbuild/buf-darwin-arm64': 1.71.0 - '@bufbuild/buf-darwin-x64': 1.71.0 - '@bufbuild/buf-linux-aarch64': 1.71.0 - '@bufbuild/buf-linux-armv7': 1.71.0 - '@bufbuild/buf-linux-x64': 1.71.0 - '@bufbuild/buf-win32-arm64': 1.71.0 - '@bufbuild/buf-win32-x64': 1.71.0 + '@bufbuild/buf-darwin-arm64': 1.73.0 + '@bufbuild/buf-darwin-x64': 1.73.0 + '@bufbuild/buf-linux-aarch64': 1.73.0 + '@bufbuild/buf-linux-armv7': 1.73.0 + '@bufbuild/buf-linux-x64': 1.73.0 + '@bufbuild/buf-win32-arm64': 1.73.0 + '@bufbuild/buf-win32-x64': 1.73.0 - '@bufbuild/protobuf@2.12.1': {} + '@bufbuild/protobuf@2.15.0': {} - '@bufbuild/protoc-gen-es@2.12.1(@bufbuild/protobuf@2.12.1)': + '@bufbuild/protoc-gen-es@2.15.0(@bufbuild/protobuf@2.15.0)(supports-color@7.2.0)': dependencies: - '@bufbuild/protoplugin': 2.12.1 + '@bufbuild/protoplugin': 2.15.0(supports-color@7.2.0) optionalDependencies: - '@bufbuild/protobuf': 2.12.1 + '@bufbuild/protobuf': 2.15.0 transitivePeerDependencies: - supports-color - '@bufbuild/protoplugin@2.12.1': + '@bufbuild/protoplugin@2.15.0(supports-color@7.2.0)': dependencies: - '@bufbuild/protobuf': 2.12.1 - '@typescript/vfs': 1.6.2(typescript@6.0.3) + '@bufbuild/protobuf': 2.15.0 + '@typescript/vfs': 1.6.2(supports-color@7.2.0)(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@connectrpc/connect-node@2.1.2(@bufbuild/protobuf@2.12.1)(@connectrpc/connect@2.1.2(@bufbuild/protobuf@2.12.1))': + '@connectrpc/connect-node@2.2.0(@bufbuild/protobuf@2.15.0)(@connectrpc/connect@2.2.0(@bufbuild/protobuf@2.15.0))': dependencies: - '@bufbuild/protobuf': 2.12.1 - '@connectrpc/connect': 2.1.2(@bufbuild/protobuf@2.12.1) + '@bufbuild/protobuf': 2.15.0 + '@connectrpc/connect': 2.2.0(@bufbuild/protobuf@2.15.0) - '@connectrpc/connect@2.1.1(@bufbuild/protobuf@2.12.1)': + '@connectrpc/connect@2.2.0(@bufbuild/protobuf@2.15.0)': dependencies: - '@bufbuild/protobuf': 2.12.1 - - '@connectrpc/connect@2.1.2(@bufbuild/protobuf@2.12.1)': - dependencies: - '@bufbuild/protobuf': 2.12.1 + '@bufbuild/protobuf': 2.15.0 '@dependents/detective-less@5.0.1': dependencies: gonzales-pe: 4.3.0 node-source-walk: 7.0.1 - '@dprint/darwin-arm64@0.54.0': - optional: true - - '@dprint/darwin-x64@0.54.0': - optional: true - - '@dprint/linux-arm64-glibc@0.54.0': - optional: true + '@effect/language-service@0.87.2': {} - '@dprint/linux-arm64-musl@0.54.0': - optional: true - - '@dprint/linux-loong64-glibc@0.54.0': - optional: true - - '@dprint/linux-loong64-musl@0.54.0': - optional: true - - '@dprint/linux-riscv64-glibc@0.54.0': - optional: true - - '@dprint/linux-x64-glibc@0.54.0': - optional: true - - '@dprint/linux-x64-musl@0.54.0': - optional: true - - '@dprint/win32-arm64@0.54.0': - optional: true - - '@dprint/win32-x64@0.54.0': - optional: true - - '@effect/language-service@0.86.1': {} - - '@effect/platform-node-shared@4.0.0-beta.85(effect@4.0.0-beta.85)': + '@effect/platform-node-shared@4.0.0-rc.117(effect@4.0.0-rc.117)': dependencies: '@types/ws': 8.18.1 - effect: 4.0.0-beta.85 - ws: 8.20.0 + effect: 4.0.0-rc.117 + ws: 8.21.3 transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/platform-node@4.0.0-beta.85(effect@4.0.0-beta.85)(ioredis@5.10.1)': + '@effect/platform-node@4.0.0-rc.117(effect@4.0.0-rc.117)(redis@6.2.1)': dependencies: - '@effect/platform-node-shared': 4.0.0-beta.85(effect@4.0.0-beta.85) - effect: 4.0.0-beta.85 - ioredis: 5.10.1 - mime: 4.1.0 - undici: 8.5.0 + '@effect/platform-node-shared': 4.0.0-rc.117(effect@4.0.0-rc.117) + effect: 4.0.0-rc.117 + redis: 6.2.1 + undici: 8.11.2 transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/vitest@4.0.0-beta.85(effect@4.0.0-beta.85)(vitest@4.1.9)': + '@effect/vitest@4.0.0-rc.117(effect@4.0.0-rc.117)(vitest@5.0.2)': dependencies: - effect: 4.0.0-beta.85 - vitest: 4.1.9(@types/node@26.0.0)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(vite@7.3.1(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) + effect: 4.0.0-rc.117 + vitest: 5.0.2(@types/node@26.6.2)(@vitest/coverage-v8@5.0.2)(@vitest/ui@5.0.2)(vite@7.3.1(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0)) '@esbuild/aix-ppc64@0.27.2': optional: true @@ -3505,23 +3866,71 @@ snapshots: dependencies: hono: 4.11.8 - '@ioredis/commands@1.5.1': {} + '@inquirer/ansi@2.0.8': {} + + '@inquirer/checkbox@5.2.5(@types/node@26.6.2)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/core': 12.0.3(@types/node@26.6.2) + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@26.6.2) + optionalDependencies: + '@types/node': 26.6.2 + + '@inquirer/core@12.0.3(@types/node@26.6.2)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@26.6.2) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 26.6.2 + + '@inquirer/figures@2.0.9': {} + + '@inquirer/type@4.1.1(@types/node@26.6.2)': + optionalDependencies: + '@types/node': 26.6.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/gen-mapping@0.3.13': + '@jridgewell/gen-mapping@0.4.0-beta.0': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.6.0': {} + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@modelcontextprotocol/sdk@1.27.1(zod@4.3.6)': + '@modelcontextprotocol/client@2.1.0': + dependencies: + '@modelcontextprotocol/core': 2.1.0 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + jose: 6.2.3 + pkce-challenge: 5.0.1 + zod: 4.4.3 + + '@modelcontextprotocol/core@2.1.0': + dependencies: + zod: 4.4.3 + + '@modelcontextprotocol/sdk@1.29.0(supports-color@7.2.0)(zod@4.4.3)': dependencies: '@hono/node-server': 1.19.9(hono@4.11.8) ajv: 8.17.1 @@ -3531,19 +3940,19 @@ snapshots: cross-spawn: 7.0.6 eventsource: 3.0.7 eventsource-parser: 3.0.6 - express: 5.2.1 - express-rate-limit: 8.2.1(express@5.2.1) + express: 5.2.1(supports-color@7.2.0) + express-rate-limit: 8.2.1(express@5.2.1(supports-color@7.2.0)) hono: 4.11.8 jose: 6.2.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 - zod: 4.3.6 - zod-to-json-schema: 3.25.1(zod@4.3.6) + zod: 4.4.3 + zod-to-json-schema: 3.25.1(zod@4.4.3) transitivePeerDependencies: - supports-color - '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': + '@modelcontextprotocol/sdk@1.30.0(supports-color@7.2.0)(zod@4.6.5)': dependencies: '@hono/node-server': 1.19.9(hono@4.11.8) ajv: 8.17.1 @@ -3553,35 +3962,22 @@ snapshots: cross-spawn: 7.0.6 eventsource: 3.0.7 eventsource-parser: 3.0.6 - express: 5.2.1 - express-rate-limit: 8.2.1(express@5.2.1) + express: 5.2.1(supports-color@7.2.0) + express-rate-limit: 8.2.1(express@5.2.1(supports-color@7.2.0)) hono: 4.11.8 jose: 6.2.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 - zod: 4.4.3 - zod-to-json-schema: 3.25.1(zod@4.4.3) + zod: 4.6.5 + zod-to-json-schema: 3.25.1(zod@4.6.5) transitivePeerDependencies: - supports-color - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': - optional: true - - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4': - optional: true - - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4': - optional: true - - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4': - optional: true - - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4': - optional: true - - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4': - optional: true + '@modelcontextprotocol/server@2.1.0': + dependencies: + '@modelcontextprotocol/core': 2.1.0 + zod: 4.4.3 '@noble/ciphers@1.3.0': {} @@ -3628,6 +4024,8 @@ snapshots: '@octokit/openapi-types@27.0.0': {} + '@octokit/openapi-types@29.0.1': {} + '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)': dependencies: '@octokit/core': 7.0.6 @@ -3646,6 +4044,10 @@ snapshots: dependencies: '@octokit/types': 16.0.0 + '@octokit/request-error@7.1.2': + dependencies: + '@octokit/types': 18.0.0 + '@octokit/request@10.0.7': dependencies: '@octokit/endpoint': 11.0.2 @@ -3665,65 +4067,146 @@ snapshots: dependencies: '@octokit/openapi-types': 27.0.0 - '@oxlint/binding-android-arm-eabi@1.71.0': + '@octokit/types@18.0.0': + dependencies: + '@octokit/openapi-types': 29.0.1 + + '@oxfmt/binding-android-arm-eabi@0.70.0': + optional: true + + '@oxfmt/binding-android-arm64@0.70.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.70.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.70.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.70.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.70.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.70.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.70.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.70.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.70.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.70.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.70.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.70.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.70.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.70.0': optional: true - '@oxlint/binding-android-arm64@1.71.0': + '@oxfmt/binding-openharmony-arm64@0.70.0': optional: true - '@oxlint/binding-darwin-arm64@1.71.0': + '@oxfmt/binding-win32-arm64-msvc@0.70.0': optional: true - '@oxlint/binding-darwin-x64@1.71.0': + '@oxfmt/binding-win32-ia32-msvc@0.70.0': optional: true - '@oxlint/binding-freebsd-x64@1.71.0': + '@oxfmt/binding-win32-x64-msvc@0.70.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.71.0': + '@oxlint/binding-android-arm-eabi@1.85.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.71.0': + '@oxlint/binding-android-arm64@1.85.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.71.0': + '@oxlint/binding-darwin-arm64@1.85.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.71.0': + '@oxlint/binding-darwin-x64@1.85.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.71.0': + '@oxlint/binding-freebsd-x64@1.85.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.71.0': + '@oxlint/binding-linux-arm-gnueabihf@1.85.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.71.0': + '@oxlint/binding-linux-arm-musleabihf@1.85.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.71.0': + '@oxlint/binding-linux-arm64-gnu@1.85.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.71.0': + '@oxlint/binding-linux-arm64-musl@1.85.0': optional: true - '@oxlint/binding-linux-x64-musl@1.71.0': + '@oxlint/binding-linux-ppc64-gnu@1.85.0': optional: true - '@oxlint/binding-openharmony-arm64@1.71.0': + '@oxlint/binding-linux-riscv64-gnu@1.85.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.71.0': + '@oxlint/binding-linux-riscv64-musl@1.85.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.71.0': + '@oxlint/binding-linux-s390x-gnu@1.85.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.71.0': + '@oxlint/binding-linux-x64-gnu@1.85.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.85.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.85.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.85.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.85.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.85.0': optional: true '@polka/url@1.0.0-next.29': {} + '@redis/bloom@6.2.1(@redis/client@6.2.1)': + dependencies: + '@redis/client': 6.2.1 + + '@redis/client@6.2.1': + dependencies: + cluster-key-slot: 1.1.2 + + '@redis/json@6.2.1(@redis/client@6.2.1)': + dependencies: + '@redis/client': 6.2.1 + + '@redis/search@6.2.1(@redis/client@6.2.1)': + dependencies: + '@redis/client': 6.2.1 + + '@redis/time-series@6.2.1(@redis/client@6.2.1)': + dependencies: + '@redis/client': 6.2.1 + '@rollup/rollup-android-arm-eabi@4.56.0': optional: true @@ -3818,16 +4301,16 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@tokenizer/inflate@0.4.1': + '@tokenizer/inflate@0.4.1(supports-color@7.2.0)': dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) token-types: 6.1.2 transitivePeerDependencies: - supports-color '@tokenizer/token@0.3.0': {} - '@toon-format/toon@2.3.0': {} + '@toon-format/toon@4.1.1': {} '@ts-graphviz/adapter@2.0.6': dependencies: @@ -3847,7 +4330,7 @@ snapshots: '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 26.0.0 + '@types/node': 26.6.2 '@types/chai@5.2.3': dependencies: @@ -3856,7 +4339,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 26.0.0 + '@types/node': 26.6.2 '@types/deep-eql@4.0.2': {} @@ -3864,7 +4347,7 @@ snapshots: '@types/express-serve-static-core@4.19.7': dependencies: - '@types/node': 26.0.0 + '@types/node': 26.6.2 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 @@ -3882,16 +4365,18 @@ snapshots: '@types/jsesc@2.5.1': {} - '@types/mime@1.3.5': {} + '@types/json-schema@7.0.15': {} - '@types/node@25.8.0': - dependencies: - undici-types: 7.24.6 + '@types/mime@1.3.5': {} '@types/node@26.0.0': dependencies: undici-types: 8.3.0 + '@types/node@26.6.2': + dependencies: + undici-types: 8.9.0 + '@types/qs@6.14.0': {} '@types/range-parser@1.2.7': {} @@ -3899,27 +4384,27 @@ snapshots: '@types/send@0.17.6': dependencies: '@types/mime': 1.3.5 - '@types/node': 26.0.0 + '@types/node': 26.6.2 '@types/send@1.2.1': dependencies: - '@types/node': 26.0.0 + '@types/node': 26.6.2 '@types/serve-static@1.15.10': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 26.0.0 + '@types/node': 26.6.2 '@types/send': 0.17.6 '@types/ws@8.18.1': dependencies: - '@types/node': 25.8.0 + '@types/node': 26.0.0 - '@typescript-eslint/project-service@8.50.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.50.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@6.0.3) '@typescript-eslint/types': 8.50.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -3930,13 +4415,13 @@ snapshots: '@typescript-eslint/types@8.50.0': {} - '@typescript-eslint/typescript-estree@8.50.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.50.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.50.0(typescript@6.0.3) + '@typescript-eslint/project-service': 8.50.0(supports-color@7.2.0)(typescript@6.0.3) '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@6.0.3) '@typescript-eslint/types': 8.50.0 '@typescript-eslint/visitor-keys': 8.50.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) minimatch: 9.0.5 semver: 7.8.0 tinyglobby: 0.2.15 @@ -3950,113 +4435,96 @@ snapshots: '@typescript-eslint/types': 8.50.0 eslint-visitor-keys: 4.2.1 - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260130.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260707.2': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260130.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260707.2': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260130.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260707.2': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260130.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260707.2': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260130.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260707.2': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260130.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260707.2': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260130.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260707.2': optional: true - '@typescript/native-preview@7.0.0-dev.20260130.1': + '@typescript/native-preview@7.0.0-dev.20260707.2': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260130.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260130.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260130.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260130.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260130.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260130.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260130.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260707.2 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260707.2 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260707.2 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260707.2 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260707.2 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260707.2 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260707.2 - '@typescript/vfs@1.6.2(typescript@6.0.3)': + '@typescript/vfs@1.6.2(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@valibot/to-json-schema@1.7.1(valibot@1.2.0(typescript@6.0.3))': + '@valibot/to-json-schema@1.8.0(valibot@1.5.0(typescript@6.0.3))': dependencies: - valibot: 1.2.0(typescript@6.0.3) + valibot: 1.5.0(typescript@6.0.3) - '@vitest/coverage-v8@4.1.9(vitest@4.1.9)': + '@vitest/coverage-v8@5.0.2(vitest@5.0.2)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.9 - ast-v8-to-istanbul: 1.0.0 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-reports: 3.2.0 - magicast: 0.5.2 - obug: 2.1.1 - std-env: 4.1.0 - tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@26.0.0)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(vite@7.3.1(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/istanbul-lib-coverage': 1.0.1 + '@vitest/istanbul-lib-report': 1.0.1 + ast-v8-to-istanbul: 1.0.7 + magicast: 0.5.5 + obug: 2.2.1 + std-env: 4.2.0 + tinyrainbow: 3.1.1 + vitest: 5.0.2(@types/node@26.6.2)(@vitest/coverage-v8@5.0.2)(@vitest/ui@5.0.2)(vite@7.3.1(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0)) + + '@vitest/istanbul-lib-coverage@1.0.1': {} - '@vitest/expect@4.1.9': + '@vitest/istanbul-lib-report@1.0.1': dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.9 - '@vitest/utils': 4.1.9 - chai: 6.2.2 - tinyrainbow: 3.1.0 + '@vitest/istanbul-lib-coverage': 1.0.1 - '@vitest/mocker@4.1.9(vite@7.3.1(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0))': + '@vitest/mocker@5.0.2(vite@7.3.1(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0))': dependencies: - '@vitest/spy': 4.1.9 + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.2 estree-walker: 3.0.3 - magic-string: 0.30.21 + magic-string: 1.4.2 optionalDependencies: - vite: 7.3.1(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0) - - '@vitest/pretty-format@4.1.9': - dependencies: - tinyrainbow: 3.1.0 - - '@vitest/runner@4.1.9': - dependencies: - '@vitest/utils': 4.1.9 - pathe: 2.0.3 + vite: 7.3.1(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0) - '@vitest/snapshot@4.1.9': + '@vitest/pretty-format@5.0.2': dependencies: - '@vitest/pretty-format': 4.1.9 - '@vitest/utils': 4.1.9 - magic-string: 0.30.21 - pathe: 2.0.3 + tinyrainbow: 3.1.1 - '@vitest/spy@4.1.9': {} + '@vitest/spy@5.0.2': {} - '@vitest/ui@4.1.9(vitest@4.1.9)': + '@vitest/ui@5.0.2(vitest@5.0.2)': dependencies: - '@vitest/utils': 4.1.9 - fflate: 0.8.2 - flatted: 3.4.2 + '@vitest/utils': 5.0.2 + fflate: 0.8.3 + flatted: 3.4.4 pathe: 2.0.3 sirv: 3.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@26.0.0)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(vite@7.3.1(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) + tinyrainbow: 3.1.1 + vitest: 5.0.2(@types/node@26.6.2)(@vitest/coverage-v8@5.0.2)(@vitest/ui@5.0.2)(vite@7.3.1(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0)) - '@vitest/utils@4.1.9': + '@vitest/utils@5.0.2': dependencies: - '@vitest/pretty-format': 4.1.9 + '@vitest/pretty-format': 5.0.2 convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 '@vue/compiler-core@3.5.26': dependencies: @@ -4105,6 +4573,10 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 + ajv-draft-04@1.0.0(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + ajv-formats@3.0.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 @@ -4128,12 +4600,6 @@ snapshots: any-promise@1.3.0: {} - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - optional: true - app-module-path@2.2.0: {} argparse@1.0.10: @@ -4146,7 +4612,7 @@ snapshots: ast-module-types@6.0.1: {} - ast-v8-to-istanbul@1.0.0: + ast-v8-to-istanbul@1.0.7: dependencies: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 @@ -4154,17 +4620,17 @@ snapshots: asynckit@0.4.0: {} - axios@1.13.4(debug@4.4.3): + axios@1.13.4(debug@4.4.3(supports-color@7.2.0)): dependencies: - follow-redirects: 1.15.11(debug@4.4.3) + follow-redirects: 1.15.11(debug@4.4.3(supports-color@7.2.0)) form-data: 4.0.5 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug - babel-plugin-annotate-pure-calls@0.5.0(@babel/core@8.0.1): + babel-plugin-annotate-pure-calls@0.5.0(@babel/core@8.0.6): dependencies: - '@babel/core': 8.0.1 + '@babel/core': 8.0.6 balanced-match@1.0.2: {} @@ -4176,20 +4642,17 @@ snapshots: before-after-hook@4.0.0: {} - binary-extensions@2.3.0: - optional: true - bl@4.1.0: dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 - body-parser@2.2.2: + body-parser@2.2.2(supports-color@7.2.0): dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 @@ -4245,6 +4708,8 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + call-me-maybe@1.0.2: {} + caniuse-lite@1.0.30001766: {} chai@6.2.2: {} @@ -4254,18 +4719,9 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chokidar@3.6.0: + chokidar@5.0.0: dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - optional: true + readdirp: 5.1.1 cli-cursor@3.1.0: dependencies: @@ -4273,6 +4729,8 @@ snapshots: cli-spinners@2.9.2: {} + cli-width@4.1.0: {} + cliui@9.0.1: dependencies: string-width: 7.2.0 @@ -4335,9 +4793,11 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - debug@4.4.3: + debug@4.4.3(supports-color@7.2.0): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 deep-equal@1.0.1: {} @@ -4345,7 +4805,7 @@ snapshots: default-browser-id@5.0.1: {} - default-browser@5.4.0: + default-browser@5.5.1: dependencies: bundle-name: 4.1.0 default-browser-id: 5.0.1 @@ -4360,26 +4820,21 @@ snapshots: delegates@1.0.0: {} - denque@2.1.0: {} - depd@1.1.2: {} depd@2.0.0: {} - dependency-tree@11.2.0: + dependency-tree@11.2.0(supports-color@7.2.0): dependencies: commander: 12.1.0 filing-cabinet: 5.0.3 - precinct: 12.2.0 + precinct: 12.2.0(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color destroy@1.2.0: {} - detect-libc@2.1.2: - optional: true - detective-amd@6.0.1: dependencies: ast-module-types: 6.0.1 @@ -4414,16 +4869,16 @@ snapshots: detective-stylus@5.0.1: {} - detective-typescript@14.0.0(typescript@6.0.3): + detective-typescript@14.0.0(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/typescript-estree': 8.50.0(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.50.0(supports-color@7.2.0)(typescript@6.0.3) ast-module-types: 6.0.1 node-source-walk: 7.0.1 typescript: 6.0.3 transitivePeerDependencies: - supports-color - detective-vue2@2.2.0(typescript@6.0.3): + detective-vue2@2.2.0(supports-color@7.2.0)(typescript@6.0.3): dependencies: '@dependents/detective-less': 5.0.1 '@vue/compiler-sfc': 3.5.26 @@ -4431,25 +4886,11 @@ snapshots: detective-sass: 6.0.1 detective-scss: 5.0.1 detective-stylus: 5.0.1 - detective-typescript: 14.0.0(typescript@6.0.3) + detective-typescript: 14.0.0(supports-color@7.2.0)(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - dprint@0.54.0: - optionalDependencies: - '@dprint/darwin-arm64': 0.54.0 - '@dprint/darwin-x64': 0.54.0 - '@dprint/linux-arm64-glibc': 0.54.0 - '@dprint/linux-arm64-musl': 0.54.0 - '@dprint/linux-loong64-glibc': 0.54.0 - '@dprint/linux-loong64-musl': 0.54.0 - '@dprint/linux-riscv64-glibc': 0.54.0 - '@dprint/linux-x64-glibc': 0.54.0 - '@dprint/linux-x64-musl': 0.54.0 - '@dprint/win32-arm64': 0.54.0 - '@dprint/win32-x64': 0.54.0 - dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4458,23 +4899,12 @@ snapshots: ee-first@1.1.1: {} - effect@3.21.3: + effect@3.22.2: dependencies: '@standard-schema/spec': 1.1.0 fast-check: 3.23.2 - effect@4.0.0-beta.85: - dependencies: - '@standard-schema/spec': 1.1.0 - fast-check: 4.8.0 - find-my-way-ts: 0.1.6 - ini: 7.0.0 - kubernetes-types: 1.30.0 - msgpackr: 2.0.4 - multipasta: 0.2.7 - toml: 4.1.1 - uuid: 14.0.1 - yaml: 2.9.0 + effect@4.0.0-rc.117: {} electron-to-chromium@1.5.278: {} @@ -4499,7 +4929,7 @@ snapshots: es-errors@1.3.0: {} - es-module-lexer@2.1.0: {} + es-module-lexer@2.3.2: {} es-object-atoms@1.1.1: dependencies: @@ -4512,7 +4942,7 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - es-toolkit@1.47.1: {} + es-toolkit@1.52.0: {} esbuild@0.27.2: optionalDependencies: @@ -4594,27 +5024,27 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.1.2 - expect-type@1.3.0: {} + expect-type@1.4.0: {} - express-rate-limit@8.2.1(express@5.2.1): + express-rate-limit@8.2.1(express@5.2.1(supports-color@7.2.0)): dependencies: - express: 5.2.1 + express: 5.2.1(supports-color@7.2.0) ip-address: 10.0.1 - express@5.2.1: + express@5.2.1(supports-color@7.2.0): dependencies: accepts: 2.0.0 - body-parser: 2.2.2 + body-parser: 2.2.2(supports-color@7.2.0) content-disposition: 1.0.1 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 2.1.1 + finalhandler: 2.1.1(supports-color@7.2.0) fresh: 2.0.0 http-errors: 2.0.1 merge-descriptors: 2.0.0 @@ -4625,9 +5055,9 @@ snapshots: proxy-addr: 2.0.7 qs: 6.14.1 range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 + router: 2.2.0(supports-color@7.2.0) + send: 1.2.1(supports-color@7.2.0) + serve-static: 2.2.1(supports-color@7.2.0) statuses: 2.0.2 type-is: 2.0.1 vary: 1.1.2 @@ -4642,10 +5072,6 @@ snapshots: dependencies: pure-rand: 6.1.0 - fast-check@4.8.0: - dependencies: - pure-rand: 8.4.0 - fast-content-type-parse@3.0.0: {} fast-deep-equal@3.1.3: {} @@ -4658,29 +5084,44 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + fast-uri@3.1.0: {} - fastmcp@4.3.0(@valibot/to-json-schema@1.7.1(valibot@1.2.0(typescript@6.0.3)))(effect@3.21.3)(sury@10.0.4): + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + + fastmcp@4.20.14(@valibot/to-json-schema@1.8.0(valibot@1.5.0(typescript@6.0.3)))(ajv-formats@3.0.1(ajv@8.17.1))(ajv@8.17.1)(effect@3.22.2)(jose@6.2.12)(openapi-types@12.1.3)(supports-color@7.2.0)(sury@10.0.4): dependencies: - '@modelcontextprotocol/sdk': 1.27.1(zod@4.3.6) + '@apidevtools/swagger-parser': 12.1.0(openapi-types@12.1.3) + '@modelcontextprotocol/sdk': 1.29.0(supports-color@7.2.0)(zod@4.4.3) '@standard-schema/spec': 1.1.0 execa: 9.6.1 - file-type: 21.3.0 + file-type: 21.3.0(supports-color@7.2.0) fuse.js: 7.1.0 hono: 4.11.8 - mcp-proxy: 6.5.2 + mcp-proxy: 6.7.19(supports-color@7.2.0) strict-event-emitter-types: 2.0.0 - undici: 7.25.0 uri-templates: 0.2.0 - xsschema: 0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.2.0(typescript@6.0.3)))(effect@3.21.3)(sury@10.0.4)(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6) + xsschema: 0.4.4(@valibot/to-json-schema@1.8.0(valibot@1.5.0(typescript@6.0.3)))(effect@3.22.2)(sury@10.0.4)(zod-to-json-schema@3.25.1(zod@4.4.3))(zod@4.4.3) yargs: 18.0.0 - zod: 4.3.6 - zod-to-json-schema: 3.25.1(zod@4.3.6) + zod: 4.4.3 + zod-to-json-schema: 3.25.1(zod@4.4.3) + optionalDependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + jose: 6.2.12 transitivePeerDependencies: - '@cfworker/json-schema' - '@valibot/to-json-schema' - arktype - effect + - openapi-types - supports-color - sury @@ -4692,15 +5133,19 @@ snapshots: optionalDependencies: picomatch: 4.0.3 - fflate@0.8.2: {} + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + fflate@0.8.3: {} figures@6.1.0: dependencies: is-unicode-supported: 2.1.0 - file-type@21.3.0: + file-type@21.3.0(supports-color@7.2.0): dependencies: - '@tokenizer/inflate': 0.4.1 + '@tokenizer/inflate': 0.4.1(supports-color@7.2.0) strtok3: 10.3.4 token-types: 6.1.2 uint8array-extras: 1.5.0 @@ -4725,9 +5170,9 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@2.1.1: + finalhandler@2.1.1(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -4736,13 +5181,13 @@ snapshots: transitivePeerDependencies: - supports-color - find-my-way-ts@0.1.6: {} + flatted@3.4.4: {} - flatted@3.4.2: {} + flru@1.0.2: {} - follow-redirects@1.15.11(debug@4.4.3): + follow-redirects@1.15.11(debug@4.4.3(supports-color@7.2.0)): optionalDependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) form-data@4.0.5: dependencies: @@ -4828,14 +5273,15 @@ snapshots: kind-of: 6.0.3 which: 4.0.0 - globals@17.7.0: {} + globals@17.12.0: {} - globby@16.2.0: + globby@16.2.4: dependencies: '@sindresorhus/merge-streams': 4.0.0 fast-glob: 3.3.3 ignore: 7.0.5 is-path-inside: 4.0.0 + micromatch: 4.0.8 slash: 5.1.0 unicorn-magic: 0.4.0 @@ -4870,8 +5316,6 @@ snapshots: hono@4.11.8: {} - html-escaper@2.0.2: {} - http-assert@1.5.0: dependencies: deep-equal: 1.0.1 @@ -4920,31 +5364,10 @@ snapshots: ini@4.1.3: {} - ini@7.0.0: {} - - ioredis@5.10.1: - dependencies: - '@ioredis/commands': 1.5.1 - cluster-key-slot: 1.1.2 - debug: 4.4.3 - denque: 2.1.0 - lodash.defaults: 4.2.0 - lodash.isarguments: 3.1.0 - redis-errors: 1.2.0 - redis-parser: 3.0.0 - standard-as-callback: 2.1.0 - transitivePeerDependencies: - - supports-color - ip-address@10.0.1: {} ipaddr.js@1.9.1: {} - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - optional: true - is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -4997,25 +5420,14 @@ snapshots: isexe@3.1.1: {} - isows@1.0.7(ws@8.20.1): - dependencies: - ws: 8.20.1 - - istanbul-lib-coverage@3.2.2: {} - - istanbul-lib-report@3.0.1: - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - - istanbul-reports@3.2.0: + isows@1.0.7(ws@8.21.0): dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 + ws: 8.21.0 jiti@2.7.0: {} + jose@6.2.12: {} + jose@6.2.3: {} js-tokens@10.0.0: {} @@ -5029,6 +5441,10 @@ snapshots: dependencies: argparse: 2.0.1 + js-yaml@5.4.2: + dependencies: + argparse: 2.0.1 + jsesc@3.1.0: {} json-schema-traverse@1.0.0: {} @@ -5049,9 +5465,9 @@ snapshots: koa-compose@4.1.0: {} - koa-router@14.0.0: + koa-router@14.0.0(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) http-errors: 2.0.1 koa-compose: 4.1.0 path-to-regexp: 8.3.0 @@ -5079,12 +5495,6 @@ snapshots: type-is: 2.0.1 vary: 1.1.2 - kubernetes-types@1.30.0: {} - - lodash.defaults@4.2.0: {} - - lodash.isarguments@3.1.0: {} - log-symbols@4.1.0: dependencies: chalk: 4.1.2 @@ -5092,13 +5502,13 @@ snapshots: lru-cache@11.2.4: {} - madge@8.0.0(typescript@6.0.3): + madge@8.0.0(supports-color@7.2.0)(typescript@6.0.3): dependencies: chalk: 4.1.2 commander: 7.2.0 commondir: 1.0.1 - debug: 4.4.3 - dependency-tree: 11.2.0 + debug: 4.4.3(supports-color@7.2.0) + dependency-tree: 11.2.0(supports-color@7.2.0) ora: 5.4.1 pluralize: 8.0.0 pretty-ms: 7.0.1 @@ -5115,21 +5525,23 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magicast@0.5.2: + magic-string@1.4.2: dependencies: - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - source-map-js: 1.2.1 + '@jridgewell/sourcemap-codec': 1.6.0 - make-dir@4.0.0: + magicast@0.5.5: dependencies: - semver: 7.8.0 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 + source-map-js: 1.2.1 math-intrinsics@1.1.0: {} - mcp-proxy@6.5.2: + mcp-proxy@6.7.19(supports-color@7.2.0): dependencies: - pipenet: 1.4.0 + '@modelcontextprotocol/client': 2.1.0 + '@modelcontextprotocol/server': 2.1.0 + pipenet: 1.4.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -5156,8 +5568,6 @@ snapshots: dependencies: mime-db: 1.54.0 - mime@4.1.0: {} - mimic-fn@2.1.0: {} minimatch@10.2.5: @@ -5178,6 +5588,8 @@ snapshots: minipass@7.1.3: {} + minisearch@7.2.0: {} + module-definition@6.0.1: dependencies: ast-module-types: 6.0.1 @@ -5194,23 +5606,7 @@ snapshots: ms@2.1.3: {} - msgpackr-extract@3.0.4: - dependencies: - node-gyp-build-optional-packages: 5.2.2 - optionalDependencies: - '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.4 - '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.4 - '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.4 - '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.4 - '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.4 - '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.4 - optional: true - - msgpackr@2.0.4: - optionalDependencies: - msgpackr-extract: 3.0.4 - - multipasta@0.2.7: {} + mute-stream@3.0.0: {} nanoid@3.3.11: {} @@ -5218,20 +5614,12 @@ snapshots: negotiator@1.0.0: {} - node-gyp-build-optional-packages@5.2.2: - dependencies: - detect-libc: 2.1.2 - optional: true - node-releases@2.0.27: {} node-source-walk@7.0.1: dependencies: '@babel/parser': 7.29.3 - normalize-path@3.0.0: - optional: true - npm-run-path@6.0.0: dependencies: path-key: 4.0.0 @@ -5243,6 +5631,8 @@ snapshots: obug@2.1.1: {} + obug@2.2.1: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -5255,14 +5645,16 @@ snapshots: dependencies: mimic-fn: 2.1.0 - open@11.0.0: + open@11.0.4: dependencies: - default-browser: 5.4.0 + default-browser: 5.5.1 define-lazy-prop: 3.0.0 is-in-ssh: 1.0.0 is-inside-container: 1.0.0 - powershell-utils: 0.1.0 - wsl-utils: 0.3.1 + powershell-utils: 0.2.1 + wsl-utils: 1.0.0 + + openapi-types@12.1.3: {} ora@5.4.1: dependencies: @@ -5276,7 +5668,7 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 - ox@0.14.29(typescript@6.0.3)(zod@4.4.3): + ox@0.14.45(typescript@6.0.3)(zod@4.4.3): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -5291,27 +5683,51 @@ snapshots: transitivePeerDependencies: - zod - oxlint@1.71.0: + oxfmt@0.70.0: + dependencies: + tinypool: 2.1.2 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.70.0 + '@oxfmt/binding-android-arm64': 0.70.0 + '@oxfmt/binding-darwin-arm64': 0.70.0 + '@oxfmt/binding-darwin-x64': 0.70.0 + '@oxfmt/binding-freebsd-x64': 0.70.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.70.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.70.0 + '@oxfmt/binding-linux-arm64-gnu': 0.70.0 + '@oxfmt/binding-linux-arm64-musl': 0.70.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.70.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.70.0 + '@oxfmt/binding-linux-riscv64-musl': 0.70.0 + '@oxfmt/binding-linux-s390x-gnu': 0.70.0 + '@oxfmt/binding-linux-x64-gnu': 0.70.0 + '@oxfmt/binding-linux-x64-musl': 0.70.0 + '@oxfmt/binding-openharmony-arm64': 0.70.0 + '@oxfmt/binding-win32-arm64-msvc': 0.70.0 + '@oxfmt/binding-win32-ia32-msvc': 0.70.0 + '@oxfmt/binding-win32-x64-msvc': 0.70.0 + + oxlint@1.85.0: optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.71.0 - '@oxlint/binding-android-arm64': 1.71.0 - '@oxlint/binding-darwin-arm64': 1.71.0 - '@oxlint/binding-darwin-x64': 1.71.0 - '@oxlint/binding-freebsd-x64': 1.71.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.71.0 - '@oxlint/binding-linux-arm-musleabihf': 1.71.0 - '@oxlint/binding-linux-arm64-gnu': 1.71.0 - '@oxlint/binding-linux-arm64-musl': 1.71.0 - '@oxlint/binding-linux-ppc64-gnu': 1.71.0 - '@oxlint/binding-linux-riscv64-gnu': 1.71.0 - '@oxlint/binding-linux-riscv64-musl': 1.71.0 - '@oxlint/binding-linux-s390x-gnu': 1.71.0 - '@oxlint/binding-linux-x64-gnu': 1.71.0 - '@oxlint/binding-linux-x64-musl': 1.71.0 - '@oxlint/binding-openharmony-arm64': 1.71.0 - '@oxlint/binding-win32-arm64-msvc': 1.71.0 - '@oxlint/binding-win32-ia32-msvc': 1.71.0 - '@oxlint/binding-win32-x64-msvc': 1.71.0 + '@oxlint/binding-android-arm-eabi': 1.85.0 + '@oxlint/binding-android-arm64': 1.85.0 + '@oxlint/binding-darwin-arm64': 1.85.0 + '@oxlint/binding-darwin-x64': 1.85.0 + '@oxlint/binding-freebsd-x64': 1.85.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.85.0 + '@oxlint/binding-linux-arm-musleabihf': 1.85.0 + '@oxlint/binding-linux-arm64-gnu': 1.85.0 + '@oxlint/binding-linux-arm64-musl': 1.85.0 + '@oxlint/binding-linux-ppc64-gnu': 1.85.0 + '@oxlint/binding-linux-riscv64-gnu': 1.85.0 + '@oxlint/binding-linux-riscv64-musl': 1.85.0 + '@oxlint/binding-linux-s390x-gnu': 1.85.0 + '@oxlint/binding-linux-x64-gnu': 1.85.0 + '@oxlint/binding-linux-x64-musl': 1.85.0 + '@oxlint/binding-openharmony-arm64': 1.85.0 + '@oxlint/binding-win32-arm64-msvc': 1.85.0 + '@oxlint/binding-win32-ia32-msvc': 1.85.0 + '@oxlint/binding-win32-x64-msvc': 1.85.0 parse-ms@2.1.0: {} @@ -5342,13 +5758,15 @@ snapshots: picomatch@4.0.3: {} - pipenet@1.4.0: + picomatch@4.0.7: {} + + pipenet@1.4.0(supports-color@7.2.0): dependencies: - axios: 1.13.4(debug@4.4.3) - debug: 4.4.3 + axios: 1.13.4(debug@4.4.3(supports-color@7.2.0)) + debug: 4.4.3(supports-color@7.2.0) human-readable-ids: 1.0.4 koa: 3.1.1 - koa-router: 14.0.0 + koa-router: 14.0.0(supports-color@7.2.0) pump: 3.0.3 tldjs: 2.3.2 yargs: 18.0.0 @@ -5374,7 +5792,9 @@ snapshots: powershell-utils@0.1.0: {} - precinct@12.2.0: + powershell-utils@0.2.1: {} + + precinct@12.2.0(supports-color@7.2.0): dependencies: '@dependents/detective-less': 5.0.1 commander: 12.1.0 @@ -5385,8 +5805,8 @@ snapshots: detective-sass: 6.0.1 detective-scss: 5.0.1 detective-stylus: 5.0.1 - detective-typescript: 14.0.0(typescript@6.0.3) - detective-vue2: 2.2.0(typescript@6.0.3) + detective-typescript: 14.0.0(supports-color@7.2.0)(typescript@6.0.3) + detective-vue2: 2.2.0(supports-color@7.2.0)(typescript@6.0.3) module-definition: 6.0.1 node-source-walk: 7.0.1 postcss: 8.5.6 @@ -5418,8 +5838,6 @@ snapshots: pure-rand@6.1.0: {} - pure-rand@8.4.0: {} - qs@6.14.1: dependencies: side-channel: 1.1.0 @@ -5450,16 +5868,18 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - optional: true - - redis-errors@1.2.0: {} + readdirp@5.1.1: {} - redis-parser@3.0.0: + redis@6.2.1: dependencies: - redis-errors: 1.2.0 + '@redis/bloom': 6.2.1(@redis/client@6.2.1) + '@redis/client': 6.2.1 + '@redis/json': 6.2.1(@redis/client@6.2.1) + '@redis/search': 6.2.1(@redis/client@6.2.1) + '@redis/time-series': 6.2.1(@redis/client@6.2.1) + transitivePeerDependencies: + - '@node-rs/xxhash' + - '@opentelemetry/api' require-from-string@2.0.2: {} @@ -5523,9 +5943,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.56.0 fsevents: 2.3.3 - router@2.2.0: + router@2.2.0(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -5533,28 +5953,35 @@ snapshots: transitivePeerDependencies: - supports-color - rulesync@8.31.0(valibot@1.2.0(typescript@6.0.3)): + rulesync@19.0.0(@types/node@26.6.2)(ajv-formats@3.0.1(ajv@8.17.1))(ajv@8.17.1)(jose@6.2.12)(openapi-types@12.1.3)(supports-color@7.2.0)(valibot@1.5.0(typescript@6.0.3)): dependencies: - '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) - '@octokit/request-error': 7.1.0 + '@inquirer/checkbox': 5.2.5(@types/node@26.6.2) + '@modelcontextprotocol/sdk': 1.30.0(supports-color@7.2.0)(zod@4.6.5) + '@octokit/request-error': 7.1.2 '@octokit/rest': 22.0.1 - '@toon-format/toon': 2.3.0 - '@valibot/to-json-schema': 1.7.1(valibot@1.2.0(typescript@6.0.3)) + '@toon-format/toon': 4.1.1 + '@valibot/to-json-schema': 1.8.0(valibot@1.5.0(typescript@6.0.3)) + chokidar: 5.0.0 commander: 15.0.0 - effect: 3.21.3 - es-toolkit: 1.47.1 - fastmcp: 4.3.0(@valibot/to-json-schema@1.7.1(valibot@1.2.0(typescript@6.0.3)))(effect@3.21.3)(sury@10.0.4) - globby: 16.2.0 + effect: 3.22.2 + es-toolkit: 1.52.0 + fastmcp: 4.20.14(@valibot/to-json-schema@1.8.0(valibot@1.5.0(typescript@6.0.3)))(ajv-formats@3.0.1(ajv@8.17.1))(ajv@8.17.1)(effect@3.22.2)(jose@6.2.12)(openapi-types@12.1.3)(supports-color@7.2.0)(sury@10.0.4) + globby: 16.2.4 gray-matter: 4.0.3 - js-yaml: 4.2.0 + js-yaml: 5.4.2 jsonc-parser: 3.3.1 - smol-toml: 1.6.1 + minisearch: 7.2.0 + smol-toml: 1.8.0 sury: 10.0.4 - zod: 4.4.3 + zod: 4.6.5 transitivePeerDependencies: - '@cfworker/json-schema' + - '@types/node' + - ajv + - ajv-formats - arktype - jose + - openapi-types - rescript - supports-color - valibot @@ -5581,9 +6008,9 @@ snapshots: semver@7.8.0: {} - send@1.2.1: + send@1.2.1(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -5597,12 +6024,12 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@2.2.1: + serve-static@2.2.1(supports-color@7.2.0): dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 1.2.1 + send: 1.2.1(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -5642,8 +6069,6 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 - siginfo@2.0.0: {} - signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -5656,7 +6081,7 @@ snapshots: slash@5.1.0: {} - smol-toml@1.6.1: {} + smol-toml@1.8.0: {} source-map-js@1.2.1: {} @@ -5665,15 +6090,11 @@ snapshots: sprintf-js@1.0.3: {} - stackback@0.0.2: {} - - standard-as-callback@2.1.0: {} - statuses@1.5.0: {} statuses@2.0.2: {} - std-env@4.1.0: {} + std-env@4.2.0: {} stream-to-array@2.3.0: dependencies: @@ -5731,16 +6152,23 @@ snapshots: tapable@2.3.0: {} - tinybench@2.9.0: {} + tinybench@6.2.0: {} - tinyexec@1.0.2: {} + tinyexec@1.3.1: {} tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinyrainbow@3.1.0: {} + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinypool@2.1.2: {} + + tinyrainbow@3.1.1: {} tldjs@2.3.2: dependencies: @@ -5758,8 +6186,6 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 - toml@4.1.1: {} - totalist@3.0.1: {} ts-api-utils@2.1.0(typescript@6.0.3): @@ -5804,13 +6230,11 @@ snapshots: uint8array-extras@1.5.0: {} - undici-types@7.24.6: {} - undici-types@8.3.0: {} - undici@7.25.0: {} + undici-types@8.9.0: {} - undici@8.5.0: {} + undici@8.11.2: {} unicorn-magic@0.3.0: {} @@ -5830,24 +6254,24 @@ snapshots: util-deprecate@1.0.2: {} - uuid@14.0.1: {} - - valibot@1.2.0(typescript@6.0.3): + valibot@1.5.0(typescript@6.0.3): optionalDependencies: typescript: 6.0.3 vary@1.1.2: {} - viem@2.53.1(typescript@6.0.3)(zod@4.4.3): + verkit@0.3.2: {} + + viem@2.56.9(typescript@6.0.3)(zod@4.4.3): dependencies: '@noble/curves': 1.9.1 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 abitype: 1.2.3(typescript@6.0.3)(zod@4.4.3) - isows: 1.0.7(ws@8.20.1) - ox: 0.14.29(typescript@6.0.3)(zod@4.4.3) - ws: 8.20.1 + isows: 1.0.7(ws@8.21.0) + ox: 0.14.45(typescript@6.0.3)(zod@4.4.3) + ws: 8.21.0 optionalDependencies: typescript: 6.0.3 transitivePeerDependencies: @@ -5855,26 +6279,26 @@ snapshots: - utf-8-validate - zod - vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@7.3.1(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)): + vite-tsconfig-paths@6.1.1(supports-color@7.2.0)(typescript@6.0.3)(vite@7.3.1(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0)): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) globrex: 0.1.2 tsconfck: 3.1.6(typescript@6.0.3) - vite: 7.3.1(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0) + vite: 7.3.1(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color - typescript - vite@7.3.1(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0): + vite@7.3.1(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0): dependencies: esbuild: 0.27.2 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 postcss: 8.5.6 rollup: 4.56.0 - tinyglobby: 0.2.15 + tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 26.0.0 + '@types/node': 26.6.2 fsevents: 2.3.3 jiti: 2.7.0 yaml: 2.9.0 @@ -5883,32 +6307,26 @@ snapshots: dependencies: '@types/express': 4.17.25 - vitest@4.1.9(@types/node@26.0.0)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(vite@7.3.1(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)): - dependencies: - '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(vite@7.3.1(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.9 - '@vitest/runner': 4.1.9 - '@vitest/snapshot': 4.1.9 - '@vitest/spy': 4.1.9 - '@vitest/utils': 4.1.9 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.1.0 - vite: 7.3.1(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 + vitest@5.0.2(@types/node@26.6.2)(@vitest/coverage-v8@5.0.2)(@vitest/ui@5.0.2)(vite@7.3.1(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.2(vite@7.3.1(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0)) + chai: 6.2.2 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.2 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.2.0 + tinybench: 6.2.0 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 7.3.1(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0) + why-is-node-running: 3.2.2 optionalDependencies: - '@types/node': 26.0.0 - '@vitest/coverage-v8': 4.1.9(vitest@4.1.9) - '@vitest/ui': 4.1.9(vitest@4.1.9) + '@types/node': 26.6.2 + '@vitest/coverage-v8': 5.0.2(vitest@5.0.2) + '@vitest/ui': 5.0.2(vitest@5.0.2) transitivePeerDependencies: - msw @@ -5926,10 +6344,7 @@ snapshots: dependencies: isexe: 3.1.1 - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 + why-is-node-running@3.2.2: {} wrap-ansi@9.0.2: dependencies: @@ -5939,26 +6354,27 @@ snapshots: wrappy@1.0.2: {} - ws@8.20.0: {} + ws@8.21.0: {} - ws@8.20.1: {} + ws@8.21.3: {} - wsl-utils@0.3.1: + wsl-utils@1.0.0: dependencies: is-wsl: 3.1.0 powershell-utils: 0.1.0 - xsschema@0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.2.0(typescript@6.0.3)))(effect@3.21.3)(sury@10.0.4)(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6): + xsschema@0.4.4(@valibot/to-json-schema@1.8.0(valibot@1.5.0(typescript@6.0.3)))(effect@3.22.2)(sury@10.0.4)(zod-to-json-schema@3.25.1(zod@4.4.3))(zod@4.4.3): optionalDependencies: - '@valibot/to-json-schema': 1.7.1(valibot@1.2.0(typescript@6.0.3)) - effect: 3.21.3 + '@valibot/to-json-schema': 1.8.0(valibot@1.5.0(typescript@6.0.3)) + effect: 3.22.2 sury: 10.0.4 - zod: 4.3.6 - zod-to-json-schema: 3.25.1(zod@4.3.6) + zod: 4.4.3 + zod-to-json-schema: 3.25.1(zod@4.4.3) y18n@5.0.8: {} - yaml@2.9.0: {} + yaml@2.9.0: + optional: true yargs-parser@22.0.0: {} @@ -5973,14 +6389,14 @@ snapshots: yoctocolors@2.1.2: {} - zod-to-json-schema@3.25.1(zod@4.3.6): - dependencies: - zod: 4.3.6 - zod-to-json-schema@3.25.1(zod@4.4.3): dependencies: zod: 4.4.3 - zod@4.3.6: {} + zod-to-json-schema@3.25.1(zod@4.6.5): + dependencies: + zod: 4.6.5 zod@4.4.3: {} + + zod@4.6.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ae641f6..bdab9df 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,21 +9,20 @@ overrides: allowBuilds: '@bufbuild/buf': false - dprint: false esbuild: false msgpackr-extract: false tldjs: false catalog: - "@effect/language-service": ^0.86.1 - "@effect/platform-node": ^4.0.0-beta.85 - "@effect/vitest": ^4.0.0-beta.85 - "@vitest/coverage-v8": ^4.1.9 - "@vitest/ui": ^4.1.9 - "@types/node": ^26.0.0 - "effect": ^4.0.0-beta.85 - "viem": ^2.53.1 - "vitest": ^4.1.9 + "@effect/language-service": ^0.87.2 + "@effect/platform-node": ^4.0.0-rc.117 + "@effect/vitest": ^4.0.0-rc.117 + "@vitest/coverage-v8": ^5.0.2 + "@vitest/ui": ^5.0.2 + "@types/node": ^26.6.2 + "effect": ^4.0.0-rc.117 + "viem": ^2.56.9 + "vitest": ^5.0.2 minimumReleaseAgeExclude: - '@bufbuild/protobuf@2.12.1' @@ -50,4 +49,12 @@ minimumReleaseAgeExclude: - '@oxlint/binding-win32-x64-msvc@1.71.0' - globals@17.7.0 - oxlint@1.71.0 - - rulesync@8.31.0 + - rulesync@8.31.0 || 19.0.0 + - '@vitest/coverage-v8@5.0.2' + - '@vitest/mocker@5.0.2' + - '@vitest/pretty-format@5.0.2' + - '@vitest/spy@5.0.2' + - '@vitest/ui@5.0.2' + - '@vitest/utils@5.0.2' + - viem@2.56.9 + - vitest@5.0.2 diff --git a/scripts/circular.mjs b/scripts/circular.mjs index b39868d..eccb641 100644 --- a/scripts/circular.mjs +++ b/scripts/circular.mjs @@ -2,16 +2,13 @@ import * as glob from "glob" import madge from "madge" -madge( - glob.globSync(["packages/*/src/**/*.ts", "packages/ai/*/src/**/*.ts"]), - { - detectiveOptions: { - ts: { - skipTypeImports: true - } +madge(glob.globSync(["packages/*/src/**/*.ts", "packages/ai/*/src/**/*.ts"]), { + detectiveOptions: { + ts: { + skipTypeImports: true } } -).then((res) => { +}).then((res) => { const circular = res.circular() if (circular.length) { console.error("Circular dependencies found") diff --git a/specs/cdc-stream.md b/specs/cdc-stream.md index 7ac5467..a75d5f8 100644 --- a/specs/cdc-stream.md +++ b/specs/cdc-stream.md @@ -150,20 +150,19 @@ import type { TransactionalStreamError } from "../transactional-stream/errors.ts /** * Error from BatchStore operations. */ -export class BatchStoreError extends Schema.TaggedError( - "Amp/CdcStream/BatchStoreError" -)("BatchStoreError", { - reason: Schema.String, - operation: Schema.Literal("append", "seek", "load", "prune"), - cause: Schema.optional(Schema.Defect) -}) {} +export class BatchStoreError extends Schema.TaggedError("Amp/CdcStream/BatchStoreError")( + "BatchStoreError", + { + reason: Schema.String, + operation: Schema.Literal("append", "seek", "load", "prune"), + cause: Schema.optional(Schema.Defect) + } +) {} /** * Union of all CDC stream errors. */ -export type CdcStreamError = - | BatchStoreError - | TransactionalStreamError +export type CdcStreamError = BatchStoreError | TransactionalStreamError ``` ### batch-store.ts @@ -205,9 +204,7 @@ export interface BatchStoreService { * Lightweight operation — returns only IDs, not batch data. * Used to build the DeleteBatchIterator. */ - readonly seek: ( - range: TransactionIdRange - ) => Effect.Effect, BatchStoreError> + readonly seek: (range: TransactionIdRange) => Effect.Effect, BatchStoreError> /** * Load a single batch by transaction ID. @@ -225,15 +222,10 @@ export interface BatchStoreService { * Deletes all batches with IDs <= cutoff. Must be idempotent. * Best-effort — failures are logged but not fatal. */ - readonly prune: ( - cutoff: TransactionId - ) => Effect.Effect + readonly prune: (cutoff: TransactionId) => Effect.Effect } -export class BatchStore extends Context.Tag("Amp/CdcStream/BatchStore")< - BatchStore, - BatchStoreService ->() {} +export class BatchStore extends Context.Tag("Amp/CdcStream/BatchStore")() {} ``` **Key difference from Rust**: The Rust `BatchStore` stores `RecordBatch` (Arrow columnar format) and uses Arrow IPC serialization. The TypeScript SDK works with decoded JSON records (`ReadonlyArray>`), so the `BatchStore` stores decoded data directly. This avoids re-serialization overhead and aligns with how the rest of the TypeScript SDK handles data. @@ -249,7 +241,7 @@ import { BatchStore, type BatchStoreService } from "./batch-store.ts" type BatchMap = ReadonlyMap>> -const make = Effect.gen(function*() { +const make = Effect.gen(function* () { const mapRef = yield* Ref.make(new Map()) const append = (data: ReadonlyArray>, id: TransactionId) => @@ -266,10 +258,7 @@ const make = Effect.gen(function*() { }) ) - const load = (id: TransactionId) => - Ref.get(mapRef).pipe( - Effect.map((map) => map.get(id)) - ) + const load = (id: TransactionId) => Ref.get(mapRef).pipe(Effect.map((map) => map.get(id))) const prune = (cutoff: TransactionId) => Ref.update(mapRef, (map) => { @@ -328,10 +317,7 @@ export interface CdcStreamService { ) => Effect.Effect } -export class CdcStream extends Context.Tag("Amp/CdcStream")< - CdcStream, - CdcStreamService ->() {} +export class CdcStream extends Context.Tag("Amp/CdcStream")() {} ``` ### Implementation Logic (make) @@ -339,14 +325,14 @@ export class CdcStream extends Context.Tag("Amp/CdcStream")< The implementation wraps `TransactionalStream.streamTransactional()` and transforms events: ```typescript -const make = Effect.gen(function*() { +const make = Effect.gen(function* () { const txStream = yield* TransactionalStream const batchStore = yield* BatchStore const streamCdc = (sql: string, options?: CdcStreamOptions) => { return txStream.streamTransactional(sql, options).pipe( Stream.mapEffect( - Effect.fnUntraced(function*([event, commit]): Effect.fn.Return< + Effect.fnUntraced(function* ([event, commit]): Effect.fn.Return< Option.Option, CdcStreamError > { @@ -386,11 +372,13 @@ const make = Effect.gen(function*() { // Handle batch pruning when retention window moves if (Option.isSome(event.prune)) { // Best-effort pruning - yield* batchStore.prune(event.prune.value).pipe( - Effect.catchAll((error) => - Effect.logWarning("Batch pruning failed (will retry on next watermark)", error) + yield* batchStore + .prune(event.prune.value) + .pipe( + Effect.catchAll((error) => + Effect.logWarning("Batch pruning failed (will retry on next watermark)", error) + ) ) - ) } // Watermarks are not exposed to CDC consumers // Still need to commit so the underlying TransactionalStream advances @@ -413,7 +401,7 @@ const make = Effect.gen(function*() { ): Effect.Effect => streamCdc(sql, options).pipe( Stream.runForEach( - Effect.fnUntraced(function*([event, commitHandle]) { + Effect.fnUntraced(function* ([event, commitHandle]) { yield* handler(event) yield* commitHandle.commit }) @@ -424,11 +412,7 @@ const make = Effect.gen(function*() { return { streamCdc, forEach } satisfies CdcStreamService }) -export const layer: Layer.Layer< - CdcStream, - never, - TransactionalStream | BatchStore -> = Layer.effect(CdcStream, make) +export const layer: Layer.Layer = Layer.effect(CdcStream, make) ``` ### DeleteBatchIterator Implementation @@ -438,10 +422,7 @@ export const layer: Layer.Layer< * Create a lazy batch iterator that loads batches one-by-one from the store. * Skips missing batches (watermark-only transactions). */ -const makeDeleteBatchIterator = ( - store: BatchStoreService, - ids: ReadonlyArray -): DeleteBatchIterator => { +const makeDeleteBatchIterator = (store: BatchStoreService, ids: ReadonlyArray): DeleteBatchIterator => { // Mutable cursor via Ref not needed here — we use a simple closure // since the iterator is consumed linearly by a single consumer. let cursor = 0 @@ -456,10 +437,8 @@ const makeDeleteBatchIterator = ( const id = ids[cursor]! cursor++ return store.load(id).pipe( - Effect.flatMap((batch) => - batch !== undefined - ? Effect.succeed([id, batch] as const) - : loop() // Skip missing batches (watermarks) + Effect.flatMap( + (batch) => (batch !== undefined ? Effect.succeed([id, batch] as const) : loop()) // Skip missing batches (watermarks) ) ) } @@ -495,13 +474,13 @@ const DevLayer = CdcStream.layer.pipe( ) // Usage -const program = Effect.gen(function*() { +const program = Effect.gen(function* () { const cdc = yield* CdcStream yield* cdc.forEach( "SELECT * FROM eth.logs WHERE address = '0x...'", { retention: 128 }, - Effect.fnUntraced(function*(event) { + Effect.fnUntraced(function* (event) { switch (event._tag) { case "Insert": yield* forwardInsert(event.id, event.data) diff --git a/specs/transactional-stream.md b/specs/transactional-stream.md index af25efa..54aed5d 100644 --- a/specs/transactional-stream.md +++ b/specs/transactional-stream.md @@ -104,9 +104,7 @@ import { ```typescript // TransactionId - branded non-negative integer -export const TransactionId = Schema.NonNegativeInt.pipe( - Schema.brand("Amp/TransactionalStream/TransactionId") -) +export const TransactionId = Schema.NonNegativeInt.pipe(Schema.brand("Amp/TransactionalStream/TransactionId")) // UndoCause - discriminated union export const UndoCause = Schema.Union( @@ -133,11 +131,7 @@ export const TransactionEventWatermark = Schema.TaggedStruct("Watermark", { prune: Schema.OptionFromNullOr(TransactionId) }) -export const TransactionEvent = Schema.Union( - TransactionEventData, - TransactionEventUndo, - TransactionEventWatermark -) +export const TransactionEvent = Schema.Union(TransactionEventData, TransactionEventUndo, TransactionEventWatermark) ``` ### state-store.ts @@ -217,10 +211,7 @@ export interface StateStore { /** * StateStore Context.Tag - use this to depend on StateStore in Effects. */ -export class StateStore extends Context.Tag("Amp/TransactionalStream/StateStore")< - StateStore, - StateStore ->() {} +export class StateStore extends Context.Tag("Amp/TransactionalStream/StateStore")() {} // ============================================================================= // Empty State Helper @@ -261,7 +252,7 @@ import { type Commit, emptySnapshot, type StateSnapshot, StateStore } from "./st * ) * ``` */ -const make = Effect.gen(function*() { +const make = Effect.gen(function* () { const stateRef = yield* Ref.make(emptySnapshot) const advance = (next: TransactionId) => Ref.update(stateRef, (state) => ({ ...state, next })) @@ -302,12 +293,10 @@ export const layer: Layer.Layer = Layer.effect(StateStore, make) /** * Create layer with initial state (useful for testing). */ -export const layerWithState = ( - initial: StateSnapshot -): Layer.Layer => +export const layerWithState = (initial: StateSnapshot): Layer.Layer => Layer.effect( StateStore, - Effect.gen(function*() { + Effect.gen(function* () { const stateRef = yield* Ref.make(initial) // ... same implementation as above }) @@ -411,29 +400,21 @@ export interface StateActor { readonly peek: () => Effect.Effect /** Execute an action and return event with commit handle */ - readonly execute: (action: Action) => Effect.Effect< - [TransactionEvent, CommitHandle], - TransactionalStreamError - > + readonly execute: (action: Action) => Effect.Effect<[TransactionEvent, CommitHandle], TransactionalStreamError> /** Commit pending changes up to and including this ID */ readonly commit: (id: TransactionId) => Effect.Effect } // Action union for execute() -type Action = - | { readonly _tag: "Message"; readonly message: ProtocolMessage } - | { readonly _tag: "Rewind" } +type Action = { readonly _tag: "Message"; readonly message: ProtocolMessage } | { readonly _tag: "Rewind" } /** * Create a StateActor from a StateStore. * Called internally by TransactionalStream. */ -export const makeStateActor = ( - store: StateStore, - retention: number -): Effect.Effect => - Effect.gen(function*() { +export const makeStateActor = (store: StateStore, retention: number): Effect.Effect => + Effect.gen(function* () { // Load initial state from store const snapshot = yield* store.load() @@ -691,5 +672,5 @@ const manualProgram = Effect.gen(function*() { 1. Run `pnpm vitest run packages/amp/test/transactional-stream/` - all tests pass 2. Run `pnpm oxlint packages/amp/src/transactional-stream/` - no lint errors -3. Run `pnpm dprint check` - formatting correct +3. Run `pnpm oxfmt --check` - formatting correct 4. Verify reorg test parity with Rust scenarios diff --git a/tsconfig.base.json b/tsconfig.base.json index 3e62341..54d410e 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -30,13 +30,15 @@ "noErrorTruncation": true, // Do not truncate error messages. "types": [], // Disable automatic loading of `@types/*` packages. "jsx": "react-jsx", - "plugins": [{ - "name": "@effect/language-service", - "transform": "@effect/language-service/transform", - "namespaceImportPackages": ["effect", "@effect/*"], - "diagnosticSeverity": { - "globalErrorInEffectFailure": "off" // TODO: Check if we should fix the warnings this reports + "plugins": [ + { + "name": "@effect/language-service", + "transform": "@effect/language-service/transform", + "namespaceImportPackages": ["effect", "@effect/*"], + "diagnosticSeverity": { + "globalErrorInEffectFailure": "off" // TODO: Check if we should fix the warnings this reports + } } - }] + ] } } diff --git a/tsconfig.json b/tsconfig.json index 6e8add1..e08e01a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,7 @@ { "$schema": "http://json.schemastore.org/tsconfig", "extends": "./tsconfig.base.json", - "include": [ - "**/vitest.*.ts", - "./packages/**/*.ts", - "./packages/*/test/**/*.ts", - "./packages/*/package.json" - ], + "include": ["**/vitest.*.ts", "./packages/**/*.ts", "./packages/*/test/**/*.ts", "./packages/*/package.json"], "compilerOptions": { "rootDir": ".", "noEmit": true, @@ -22,9 +17,11 @@ "@edgeandnode/amp-cli": ["./packages/cli/src/index.ts"], "@edgeandnode/amp-cli/*": ["./packages/cli/src/*.ts"] }, - "plugins": [{ - "name": "@effect/language-service", - "namespaceImportPackages": [] - }] + "plugins": [ + { + "name": "@effect/language-service", + "namespaceImportPackages": [] + } + ] } } diff --git a/tsconfig.packages.json b/tsconfig.packages.json index fd972bc..aa99269 100644 --- a/tsconfig.packages.json +++ b/tsconfig.packages.json @@ -2,9 +2,5 @@ "$schema": "http://json.schemastore.org/tsconfig", "extends": "./tsconfig.base.json", "include": [], - "references": [ - { "path": "packages/amp" }, - { "path": "packages/cli" }, - { "path": "packages/tools/oxc" } - ] + "references": [{ "path": "packages/amp" }, { "path": "packages/cli" }, { "path": "packages/tools/oxc" }] } diff --git a/vitest.config.ts b/vitest.config.ts index 363918a..149934d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,9 +3,6 @@ import { defineConfig } from "vitest/config" export default defineConfig({ test: { globals: true, - projects: [ - "packages/*/vitest.config.ts", - "packages/tools/*/vitest.config.ts" - ] + projects: ["packages/*/vitest.config.ts", "packages/tools/*/vitest.config.ts"] } }) diff --git a/vitest.shared.ts b/vitest.shared.ts index 768549f..8d90a8e 100644 --- a/vitest.shared.ts +++ b/vitest.shared.ts @@ -10,9 +10,6 @@ const config: ViteUserConfig = { }, test: { setupFiles: [path.join(__dirname, "vitest.setup.ts")], - fakeTimers: { - toFake: undefined - }, sequence: { concurrent: true }, From c1f9b42fb8d474c7c5d503d0e08b62f1d340426d Mon Sep 17 00:00:00 2001 From: Chris Whited Date: Fri, 25 Sep 2026 11:23:33 -1000 Subject: [PATCH 2/4] chore(deps): add optional jwt to AdminApiService MakeOptions --- packages/amp/src/admin/service.ts | 38 ++++++---- packages/amp/test/admin/service.test.ts | 95 +++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 15 deletions(-) diff --git a/packages/amp/src/admin/service.ts b/packages/amp/src/admin/service.ts index 9b029f5..a543b36 100644 --- a/packages/amp/src/admin/service.ts +++ b/packages/amp/src/admin/service.ts @@ -185,6 +185,7 @@ export class AdminApi extends Context.Service< export interface MakeOptions { readonly url: string | URL + readonly jwt?: string | null | undefined } const make = Effect.fnUntraced(function* (options: MakeOptions) { @@ -192,23 +193,30 @@ const make = Effect.fnUntraced(function* (options: MakeOptions) { const auth = yield* Effect.serviceOption(Auth.Auth) + // An explicitly provided JWT takes precedence over the cached auth info, and + // is applied even when the Auth service is not available + const jwt = options.jwt + const transformClient = jwt + ? HttpClient.mapRequest(HttpClientRequest.bearerToken(jwt)) + : Option.match(auth, { + onNone: constUndefined, + onSome: (authService) => + HttpClient.mapRequestEffect( + Effect.fnUntraced(function* (request) { + const authInfo = yield* authService.getCachedAuthInfo.pipe( + // Treat cache errors as "no auth available" + Effect.catch(() => Effect.succeed(Option.none())) + ) + if (Option.isNone(authInfo)) return request + const token = authInfo.value.accessToken + return HttpClientRequest.bearerToken(request, token) + }) + ) + }) + const client = yield* HttpApiClient.make(Api.Api, { baseUrl: options.url, - transformClient: Option.match(auth, { - onNone: constUndefined, - onSome: (authService) => - HttpClient.mapRequestEffect( - Effect.fnUntraced(function* (request) { - const authInfo = yield* authService.getCachedAuthInfo.pipe( - // Treat cache errors as "no auth available" - Effect.catch(() => Effect.succeed(Option.none())) - ) - if (Option.isNone(authInfo)) return request - const token = authInfo.value.accessToken - return HttpClientRequest.bearerToken(request, token) - }) - ) - }) + transformClient }) // Dataset Operations diff --git a/packages/amp/test/admin/service.test.ts b/packages/amp/test/admin/service.test.ts index 8441357..18bd8d9 100644 --- a/packages/amp/test/admin/service.test.ts +++ b/packages/amp/test/admin/service.test.ts @@ -1,10 +1,13 @@ import * as AdminApi from "@edgeandnode/amp/admin/service" +import * as Auth from "@edgeandnode/amp/auth/service" import type * as Models from "@edgeandnode/amp/core/domain" import { assert, describe, it } from "@effect/vitest" import * as Effect from "effect/Effect" import * as Layer from "effect/Layer" +import * as Redacted from "effect/Redacted" import * as HttpClient from "effect/unstable/http/HttpClient" import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse" +import * as KeyValueStore from "effect/unstable/persistence/KeyValueStore" // Response payloads mirror the serialized shapes of the Amp admin API // (see `docs/schemas/openapi/admin.spec.json` in the Amp repository). @@ -337,3 +340,95 @@ describe("AdminApi", () => { ) ) }) + +describe("AdminApi authentication", () => { + const makeRecordingClient = (authorization: Array) => + Layer.succeed( + HttpClient.HttpClient, + HttpClient.make((request) => { + authorization.push(request.headers["authorization"]) + const body = JSON.stringify({ workers: [] }) + const response = new Response(body, { status: 200, headers: { "content-type": "application/json" } }) + return Effect.succeed(HttpClientResponse.fromWeb(request, response)) + }) + ) + + // Uses `layerAuth` alongside a separate `Auth` instance sharing the same + // in-memory store, so the test can seed the cached auth info. + const makeAuthLayer = (options: AdminApi.MakeOptions, authorization: Array) => + Layer.mergeAll(AdminApi.layerAuth(options), Auth.layer).pipe( + Layer.provide(KeyValueStore.layerMemory), + Layer.provide(makeRecordingClient(authorization)) + ) + + const seedCachedAuthInfo = Effect.gen(function* () { + const auth = yield* Auth.Auth + yield* auth.setCachedAuthInfo({ + accessToken: Redacted.make("cached-token" as Models.AccessToken), + refreshToken: Redacted.make("refresh-token" as Models.RefreshToken), + userId: "c0123456789abcdefghijklmn" as Models.UserId, + accounts: [], + // Far enough in the future that the cached token is not refreshed + expiry: Date.now() + 60 * 60 * 1000 + }) + }) + + it.effect("sends the provided jwt without the Auth service", () => { + const authorization: Array = [] + return Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + yield* admin.getWorkers + assert.deepStrictEqual(authorization, ["Bearer provided-jwt"]) + }).pipe( + Effect.provide( + AdminApi.layer({ url: "http://localhost:1610", jwt: "provided-jwt" }).pipe( + Layer.provide(makeRecordingClient(authorization)) + ) + ) + ) + }) + + it.effect("sends no authorization without a jwt or the Auth service", () => { + const authorization: Array = [] + return Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + yield* admin.getWorkers + assert.deepStrictEqual(authorization, [undefined]) + }).pipe( + Effect.provide( + AdminApi.layer({ url: "http://localhost:1610", jwt: null }).pipe( + Layer.provide(makeRecordingClient(authorization)) + ) + ) + ) + }) + + it.effect("prefers the provided jwt over the cached auth info", () => { + const authorization: Array = [] + return Effect.gen(function* () { + yield* seedCachedAuthInfo + const admin = yield* AdminApi.AdminApi + yield* admin.getWorkers + assert.deepStrictEqual(authorization, ["Bearer provided-jwt"]) + }).pipe(Effect.provide(makeAuthLayer({ url: "http://localhost:1610", jwt: "provided-jwt" }, authorization))) + }) + + it.effect("falls back to the cached auth info without a jwt", () => { + const authorization: Array = [] + return Effect.gen(function* () { + yield* seedCachedAuthInfo + const admin = yield* AdminApi.AdminApi + yield* admin.getWorkers + assert.deepStrictEqual(authorization, ["Bearer cached-token"]) + }).pipe(Effect.provide(makeAuthLayer({ url: "http://localhost:1610" }, authorization))) + }) + + it.effect("sends no authorization when nothing is cached and no jwt is provided", () => { + const authorization: Array = [] + return Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + yield* admin.getWorkers + assert.deepStrictEqual(authorization, [undefined]) + }).pipe(Effect.provide(makeAuthLayer({ url: "http://localhost:1610" }, authorization))) + }) +}) From 41b8267d4d765116dcb027dae9e1cdb92ed8f939 Mon Sep 17 00:00:00 2001 From: Chris Whited Date: Fri, 25 Sep 2026 12:09:51 -1000 Subject: [PATCH 3/4] chore(deps): add support for amp dataset lineage endpoint --- packages/amp/src/admin/api.ts | 37 +++++++- packages/amp/src/admin/domain.ts | 110 ++++++++++++++++++++++++ packages/amp/src/admin/error.ts | 10 +++ packages/amp/src/admin/service.ts | 45 +++++++++- packages/amp/test/admin/service.test.ts | 107 +++++++++++++++++++++++ 5 files changed, 306 insertions(+), 3 deletions(-) diff --git a/packages/amp/src/admin/api.ts b/packages/amp/src/admin/api.ts index a64fe07..8fa6d59 100644 --- a/packages/amp/src/admin/api.ts +++ b/packages/amp/src/admin/api.ts @@ -2,7 +2,7 @@ * This module contains the HttpApi definitions for the Amp Admin API. * * The Admin API provides operations for managing: - * - Datasets (registration, versioning, manifests) + * - Datasets (registration, versioning, manifests, lineage) * - Jobs (listing, stopping, deletion) * - Workers (listing) * - Providers (listing) @@ -55,6 +55,20 @@ const JobsQueryParams = Schema.Struct({ status: Schema.String.pipe(Schema.optional) }).pipe(Schema.encodeKeys({ lastJobId: "last_job_id" })) +/** + * A non-negative integer query parameter. + */ +const NonNegativeIntFromString = Schema.NumberFromString.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0)) + +/** + * Query parameters for getting the lineage graph of a dataset. + */ +const LineageQueryParams = Schema.Struct({ + direction: Schema.optional(Domain.LineageDirection), + maxDepth: Schema.optional(NonNegativeIntFromString), + maxNodes: Schema.optional(NonNegativeIntFromString) +}).pipe(Schema.encodeKeys({ maxDepth: "max_depth", maxNodes: "max_nodes" })) + // ============================================================================= // Dataset Endpoints // ============================================================================= @@ -166,6 +180,24 @@ const getDatasetManifest = HttpApiEndpoint.get( export type GetDatasetManifestError = (typeof getDatasetManifest)["~Error"]["Type"] +// GET /datasets/{namespace}/{name}/versions/{revision}/lineage - Get dataset lineage +const getDatasetLineage = HttpApiEndpoint.get( + "getDatasetLineage", + "/datasets/:namespace/:name/versions/:revision/lineage", + { + params: { + namespace: DatasetNamespaceParam, + name: DatasetNameParam, + revision: DatasetRevisionParam + }, + query: LineageQueryParams, + error: [Error.InvalidPathError, Error.DatasetNotFoundError, Error.ResolveRevisionError, Error.BuildLineageError], + success: Domain.GetDatasetLineageResponse + } +) + +export type GetDatasetLineageError = (typeof getDatasetLineage)["~Error"]["Type"] + // ============================================================================= // Job Endpoints // ============================================================================= @@ -333,7 +365,8 @@ export class DatasetGroup extends HttpApiGroup.make("dataset") .add(getDatasets) .add(getDatasetVersions) .add(getDatasetVersion) - .add(getDatasetManifest) {} + .add(getDatasetManifest) + .add(getDatasetLineage) {} /** * The api group for the job endpoints. diff --git a/packages/amp/src/admin/domain.ts b/packages/amp/src/admin/domain.ts index 3163d88..b0214e4 100644 --- a/packages/amp/src/admin/domain.ts +++ b/packages/amp/src/admin/domain.ts @@ -143,6 +143,116 @@ export const GetDatasetVersionsResponse = Schema.Struct({ export type GetDatasetVersionsResponse = typeof GetDatasetVersionsResponse.Type +/** + * The direction in which to traverse the lineage graph of a dataset. + * + * - `upstream`: walks from the anchor dataset back toward its sources + * - `downstream`: walks toward consumers of the anchor dataset + * - `both`: walks in both directions from the anchor dataset + */ +export const LineageDirection = Schema.Literals(["upstream", "downstream", "both"]).annotate({ + identifier: "LineageDirection" +}) + +export type LineageDirection = typeof LineageDirection.Type + +/** + * The resolution status of a node in the lineage graph. + */ +export const LineageNodeStatus = Schema.Literals(["ok", "missing"]).annotate({ identifier: "LineageNodeStatus" }) + +export type LineageNodeStatus = typeof LineageNodeStatus.Type + +/** + * A single dataset node in the lineage graph. + */ +export const LineageNode = Schema.Struct({ + /** + * The declared reference this node was reached by. + */ + dataset: Models.DatasetReferenceFromString, + /** + * The dataset kind, or `null` when the node is missing. + */ + kind: Schema.NullOr(Models.DatasetKind), + status: LineageNodeStatus, + /** + * The manifest hash this reference currently resolves to, if any. + */ + resolvedHash: Schema.NullOr(Models.DatasetHash), + /** + * The depth from the anchor dataset (the anchor has a depth of `0`). + */ + depth: Models.NonNegativeInt +}) + .pipe(Schema.encodeKeys({ resolvedHash: "resolved_hash" })) + .annotate({ identifier: "LineageNode" }) + +export type LineageNode = typeof LineageNode.Type + +/** + * A directed edge in the lineage graph, pointing from an upstream source to a + * downstream consumer. + */ +export const LineageEdge = Schema.Struct({ + /** + * The upstream source dataset, in its declared form. + */ + source: Models.DatasetReferenceFromString, + /** + * The downstream consumer dataset. + */ + target: Models.DatasetReferenceFromString, + /** + * The alias used by the consumer to reference the source. + */ + alias: Schema.String, + /** + * The concrete source reference the declared form currently resolves to, + * if any. + */ + resolvedSource: Schema.NullOr(Models.DatasetReferenceFromString) +}) + .pipe(Schema.encodeKeys({ resolvedSource: "resolved_source" })) + .annotate({ identifier: "LineageEdge" }) + +export type LineageEdge = typeof LineageEdge.Type + +/** + * Response schema for getting the lineage graph of a dataset. + * + * Edges always point from upstream sources to downstream consumers, + * regardless of the traversal direction. + */ +export const GetDatasetLineageResponse = Schema.Struct({ + /** + * The dataset the traversal was anchored at. + */ + anchor: Models.DatasetReferenceFromString, + /** + * All nodes reached during traversal. + */ + nodes: Schema.Array(LineageNode), + /** + * Directed edges pointing from upstream sources to downstream consumers. + */ + edges: Schema.Array(LineageEdge), + /** + * The maximum path length from the anchor observed in the response. + */ + depth: Models.NonNegativeInt, + /** + * Whether traversal was truncated because the maximum depth was reached. + */ + truncated: Schema.Boolean, + /** + * Nodes participating in a dependency cycle. Empty when the graph is acyclic. + */ + cycles: Schema.Array(Models.DatasetReferenceFromString) +}).annotate({ identifier: "GetDatasetLineageResponse" }) + +export type GetDatasetLineageResponse = typeof GetDatasetLineageResponse.Type + // ============================================================================= // Job Request/Response Schemas // ============================================================================= diff --git a/packages/amp/src/admin/error.ts b/packages/amp/src/admin/error.ts index 0a697b0..03da62f 100644 --- a/packages/amp/src/admin/error.ts +++ b/packages/amp/src/admin/error.ts @@ -168,6 +168,16 @@ export const GetManifestPathError = makeError("GET_MANIFEST_PATH_ERROR", "GetMan export type GetManifestPathError = typeof GetManifestPathError.Type +/** + * BuildLineageError - Failed to build the lineage graph of a dataset. + * + * Causes: + * - Registry or manifest store errors while traversing dataset dependencies + */ +export const BuildLineageError = makeError("BUILD_LINEAGE_ERROR", "BuildLineageError").annotate({ httpApiStatus: 500 }) + +export type BuildLineageError = typeof BuildLineageError.Type + /** * InvalidStoredDatasetNamespace - A stored dataset namespace is malformed. */ diff --git a/packages/amp/src/admin/service.ts b/packages/amp/src/admin/service.ts index a543b36..9db3bd8 100644 --- a/packages/amp/src/admin/service.ts +++ b/packages/amp/src/admin/service.ts @@ -2,7 +2,7 @@ * This module provides the AdminApi service for interacting with the Amp Admin API. * * The Admin API allows managing: - * - Datasets (registration, versioning, manifests) + * - Datasets (registration, versioning, manifests, lineage) * - Jobs (listing, stopping, deletion) * - Workers (listing) * - Providers (listing) @@ -108,6 +108,34 @@ export class AdminApi extends Context.Service< revision: Models.DatasetRevision ) => Effect.Effect + /** + * Get the lineage graph of a dataset version. + * + * Edges in the returned graph always point from upstream sources to + * downstream consumers, regardless of the traversal direction. + * + * @param namespace The namespace of the dataset. + * @param name The name of the dataset. + * @param revision The version/revision of the dataset. + * @param options The traversal options. + * @param options.direction The traversal direction (defaults to `"upstream"`). + * @param options.maxDepth The maximum traversal depth from the dataset (unlimited if omitted). + * @param options.maxNodes The maximum number of nodes per direction (unlimited if omitted). + * @return The dataset lineage graph. + */ + readonly getDatasetLineage: ( + namespace: Models.DatasetNamespace, + name: Models.DatasetName, + revision: Models.DatasetRevision, + options?: + | { + readonly direction?: Domain.LineageDirection | undefined + readonly maxDepth?: number | undefined + readonly maxNodes?: number | undefined + } + | undefined + ) => Effect.Effect + /** * Get all jobs with optional pagination and filtering. * @@ -230,6 +258,20 @@ const make = Effect.fnUntraced(function* (options: MakeOptions) { Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die) ) + const getDatasetLineage: Service["getDatasetLineage"] = Effect.fn("AdminApi.getDatasetLineage")( + function* (namespace, name, revision, lineageOptions) { + const params = { namespace, name, revision } + const query = { + direction: lineageOptions?.direction, + maxDepth: lineageOptions?.maxDepth, + maxNodes: lineageOptions?.maxNodes + } + yield* Effect.annotateCurrentSpan({ params, query }) + return yield* client.dataset.getDatasetLineage({ params, query }) + }, + Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die) + ) + const getDatasets: Service["getDatasets"] = client.dataset .getDatasets({}) .pipe(Effect.catchTag(["HttpClientError", "SchemaError"], Effect.die), Effect.withSpan("AdminApi.getDatasets")) @@ -334,6 +376,7 @@ const make = Effect.fnUntraced(function* (options: MakeOptions) { ) return AdminApi.of({ + getDatasetLineage, getDatasetManifest, getDatasets, getDatasetVersion, diff --git a/packages/amp/test/admin/service.test.ts b/packages/amp/test/admin/service.test.ts index 18bd8d9..09213b5 100644 --- a/packages/amp/test/admin/service.test.ts +++ b/packages/amp/test/admin/service.test.ts @@ -432,3 +432,110 @@ describe("AdminApi authentication", () => { }).pipe(Effect.provide(makeAuthLayer({ url: "http://localhost:1610" }, authorization))) }) }) + +describe("AdminApi lineage", () => { + const makeLineageLayer = (requests: Array, response: StubResponse) => + AdminApi.layer({ url: "http://localhost:1610" }).pipe( + Layer.provide( + Layer.succeed( + HttpClient.HttpClient, + HttpClient.make((request, url) => { + requests.push(url) + const body = new Response(JSON.stringify(response.body), { + status: response.status, + headers: { "content-type": "application/json" } + }) + return Effect.succeed(HttpClientResponse.fromWeb(request, body)) + }) + ) + ) + ) + + const lineage = { + anchor: "edgeandnode/transfers@1.0.0", + nodes: [ + { dataset: "edgeandnode/transfers@1.0.0", kind: "manifest", status: "ok", resolved_hash: hash, depth: 0 }, + { dataset: "edgeandnode/eth_mainnet@latest", kind: "evm-rpc", status: "ok", resolved_hash: hash, depth: 1 }, + { dataset: "edgeandnode/removed@1.0.0", kind: null, status: "missing", resolved_hash: null, depth: 1 } + ], + edges: [ + { + source: "edgeandnode/eth_mainnet@latest", + target: "edgeandnode/transfers@1.0.0", + alias: "eth", + resolved_source: `edgeandnode/eth_mainnet@${hash}` + }, + { + source: "edgeandnode/removed@1.0.0", + target: "edgeandnode/transfers@1.0.0", + alias: "removed", + resolved_source: null + } + ], + depth: 1, + truncated: false, + cycles: ["edgeandnode/transfers@1.0.0"] + } + + it.effect("getDatasetLineage decodes the lineage graph", () => { + const requests: Array = [] + return Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + const response = yield* admin.getDatasetLineage( + "edgeandnode" as Models.DatasetNamespace, + "transfers" as Models.DatasetName, + "1.0.0" as Models.DatasetRevision + ) + assert.strictEqual(requests[0].pathname, "/datasets/edgeandnode/transfers/versions/1.0.0/lineage") + assert.strictEqual(requests[0].search, "") + assert.deepStrictEqual(response.anchor, { namespace: "edgeandnode", name: "transfers", revision: "1.0.0" }) + assert.strictEqual(response.nodes.length, 3) + assert.strictEqual(response.nodes[1].kind, "evm-rpc") + assert.strictEqual(response.nodes[2].status, "missing") + assert.strictEqual(response.nodes[2].kind, null) + assert.strictEqual(response.nodes[2].resolvedHash, null) + assert.strictEqual(response.edges[0].resolvedSource?.revision, hash) + assert.strictEqual(response.edges[1].resolvedSource, null) + assert.strictEqual(response.cycles[0].name, "transfers") + }).pipe(Effect.provide(makeLineageLayer(requests, { status: 200, body: lineage }))) + }) + + it.effect("getDatasetLineage encodes the traversal options as query parameters", () => { + const requests: Array = [] + return Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + yield* admin.getDatasetLineage( + "edgeandnode" as Models.DatasetNamespace, + "transfers" as Models.DatasetName, + "latest" as Models.DatasetRevision, + { direction: "both", maxDepth: 2, maxNodes: 50 } + ) + const params = requests[0].searchParams + assert.strictEqual(params.get("direction"), "both") + assert.strictEqual(params.get("max_depth"), "2") + assert.strictEqual(params.get("max_nodes"), "50") + }).pipe(Effect.provide(makeLineageLayer(requests, { status: 200, body: lineage }))) + }) + + it.effect("getDatasetLineage decodes a lineage build failure", () => { + const requests: Array = [] + return Effect.gen(function* () { + const admin = yield* AdminApi.AdminApi + const error = yield* admin + .getDatasetLineage( + "edgeandnode" as Models.DatasetNamespace, + "transfers" as Models.DatasetName, + "1.0.0" as Models.DatasetRevision + ) + .pipe(Effect.flip) + assert.strictEqual(error._tag, "BuildLineageError") + }).pipe( + Effect.provide( + makeLineageLayer(requests, { + status: 500, + body: { error_code: "BUILD_LINEAGE_ERROR", error_message: "failed to build lineage graph" } + }) + ) + ) + }) +}) From cd25c1f787bbb9e49e3fd62bda596d59c70dbf55 Mon Sep 17 00:00:00 2001 From: Chris Whited Date: Fri, 25 Sep 2026 12:15:25 -1000 Subject: [PATCH 4/4] chore(deps): export lineage query params from domain --- packages/amp/src/admin/api.ts | 16 +--------------- packages/amp/src/admin/domain.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/amp/src/admin/api.ts b/packages/amp/src/admin/api.ts index 8fa6d59..16455fe 100644 --- a/packages/amp/src/admin/api.ts +++ b/packages/amp/src/admin/api.ts @@ -55,20 +55,6 @@ const JobsQueryParams = Schema.Struct({ status: Schema.String.pipe(Schema.optional) }).pipe(Schema.encodeKeys({ lastJobId: "last_job_id" })) -/** - * A non-negative integer query parameter. - */ -const NonNegativeIntFromString = Schema.NumberFromString.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0)) - -/** - * Query parameters for getting the lineage graph of a dataset. - */ -const LineageQueryParams = Schema.Struct({ - direction: Schema.optional(Domain.LineageDirection), - maxDepth: Schema.optional(NonNegativeIntFromString), - maxNodes: Schema.optional(NonNegativeIntFromString) -}).pipe(Schema.encodeKeys({ maxDepth: "max_depth", maxNodes: "max_nodes" })) - // ============================================================================= // Dataset Endpoints // ============================================================================= @@ -190,7 +176,7 @@ const getDatasetLineage = HttpApiEndpoint.get( name: DatasetNameParam, revision: DatasetRevisionParam }, - query: LineageQueryParams, + query: Domain.LineageQueryParams, error: [Error.InvalidPathError, Error.DatasetNotFoundError, Error.ResolveRevisionError, Error.BuildLineageError], success: Domain.GetDatasetLineageResponse } diff --git a/packages/amp/src/admin/domain.ts b/packages/amp/src/admin/domain.ts index b0214e4..a9e5dcf 100644 --- a/packages/amp/src/admin/domain.ts +++ b/packages/amp/src/admin/domain.ts @@ -253,6 +253,19 @@ export const GetDatasetLineageResponse = Schema.Struct({ export type GetDatasetLineageResponse = typeof GetDatasetLineageResponse.Type +/** + * A non-negative integer query parameter. + */ +export const NonNegativeIntFromString = Schema.NumberFromString.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0)) + +export const LineageQueryParams = Schema.Struct({ + direction: Schema.optional(LineageDirection), + maxDepth: Schema.optional(NonNegativeIntFromString), + maxNodes: Schema.optional(NonNegativeIntFromString) +}).pipe(Schema.encodeKeys({ maxDepth: "max_depth", maxNodes: "max_nodes" })) + +export type LineageQueryParams = typeof LineageQueryParams.Type + // ============================================================================= // Job Request/Response Schemas // =============================================================================