@opendatalabs/personal-server-ts-core 0.0.1-canary.016adf9 → 0.0.1-canary.052d47e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/index.d.ts +361 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +1213 -0
- package/dist/api/index.js.map +1 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/request.d.ts +28 -0
- package/dist/auth/request.d.ts.map +1 -0
- package/dist/auth/request.js +106 -0
- package/dist/auth/request.js.map +1 -0
- package/dist/client.d.ts +233 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +243 -0
- package/dist/client.js.map +1 -0
- package/dist/config/defaults.d.ts +5 -5
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +3 -5
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +0 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +0 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/paths.d.ts.map +1 -1
- package/dist/config/paths.js +1 -9
- package/dist/config/paths.js.map +1 -1
- package/dist/contracts/access-logs.d.ts +8 -0
- package/dist/contracts/access-logs.d.ts.map +1 -0
- package/dist/contracts/access-logs.js +7 -0
- package/dist/contracts/access-logs.js.map +1 -0
- package/dist/contracts/auth-device.d.ts +70 -0
- package/dist/contracts/auth-device.d.ts.map +1 -0
- package/dist/contracts/auth-device.js +163 -0
- package/dist/contracts/auth-device.js.map +1 -0
- package/dist/contracts/binary.d.ts +66 -0
- package/dist/contracts/binary.d.ts.map +1 -0
- package/dist/contracts/binary.js +123 -0
- package/dist/contracts/binary.js.map +1 -0
- package/dist/contracts/config.d.ts +5 -0
- package/dist/contracts/config.d.ts.map +1 -0
- package/dist/contracts/config.js +30 -0
- package/dist/contracts/config.js.map +1 -0
- package/dist/contracts/data.d.ts +165 -0
- package/dist/contracts/data.d.ts.map +1 -0
- package/dist/contracts/data.js +371 -0
- package/dist/contracts/data.js.map +1 -0
- package/dist/contracts/grants.d.ts +45 -0
- package/dist/contracts/grants.d.ts.map +1 -0
- package/dist/contracts/grants.js +177 -0
- package/dist/contracts/grants.js.map +1 -0
- package/dist/contracts/http.d.ts +21 -0
- package/dist/contracts/http.d.ts.map +1 -0
- package/dist/contracts/http.js +49 -0
- package/dist/contracts/http.js.map +1 -0
- package/dist/contracts/index.d.ts +16 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +10 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/oauth.d.ts +38 -0
- package/dist/contracts/oauth.d.ts.map +1 -0
- package/dist/contracts/oauth.js +130 -0
- package/dist/contracts/oauth.js.map +1 -0
- package/dist/contracts/sync.d.ts +10 -0
- package/dist/contracts/sync.d.ts.map +1 -0
- package/dist/contracts/sync.js +34 -0
- package/dist/contracts/sync.js.map +1 -0
- package/dist/derivatives/api.d.ts +48 -0
- package/dist/derivatives/api.d.ts.map +1 -0
- package/dist/derivatives/api.js +366 -0
- package/dist/derivatives/api.js.map +1 -0
- package/dist/derivatives/compute.d.ts +106 -0
- package/dist/derivatives/compute.d.ts.map +1 -0
- package/dist/derivatives/compute.js +414 -0
- package/dist/derivatives/compute.js.map +1 -0
- package/dist/derivatives/e2ee/aad.d.ts +22 -0
- package/dist/derivatives/e2ee/aad.d.ts.map +1 -0
- package/dist/derivatives/e2ee/aad.js +32 -0
- package/dist/derivatives/e2ee/aad.js.map +1 -0
- package/dist/derivatives/e2ee/attestation.d.ts +134 -0
- package/dist/derivatives/e2ee/attestation.d.ts.map +1 -0
- package/dist/derivatives/e2ee/attestation.js +248 -0
- package/dist/derivatives/e2ee/attestation.js.map +1 -0
- package/dist/derivatives/e2ee/index.d.ts +6 -0
- package/dist/derivatives/e2ee/index.d.ts.map +1 -0
- package/dist/derivatives/e2ee/index.js +6 -0
- package/dist/derivatives/e2ee/index.js.map +1 -0
- package/dist/derivatives/e2ee/jcs.d.ts +17 -0
- package/dist/derivatives/e2ee/jcs.d.ts.map +1 -0
- package/dist/derivatives/e2ee/jcs.js +55 -0
- package/dist/derivatives/e2ee/jcs.js.map +1 -0
- package/dist/derivatives/e2ee/phala.d.ts +62 -0
- package/dist/derivatives/e2ee/phala.d.ts.map +1 -0
- package/dist/derivatives/e2ee/phala.js +168 -0
- package/dist/derivatives/e2ee/phala.js.map +1 -0
- package/dist/derivatives/e2ee/suite.d.ts +56 -0
- package/dist/derivatives/e2ee/suite.d.ts.map +1 -0
- package/dist/derivatives/e2ee/suite.js +164 -0
- package/dist/derivatives/e2ee/suite.js.map +1 -0
- package/dist/derivatives/index.d.ts +10 -0
- package/dist/derivatives/index.d.ts.map +1 -0
- package/dist/derivatives/index.js +10 -0
- package/dist/derivatives/index.js.map +1 -0
- package/dist/derivatives/inference.d.ts +151 -0
- package/dist/derivatives/inference.d.ts.map +1 -0
- package/dist/derivatives/inference.js +262 -0
- package/dist/derivatives/inference.js.map +1 -0
- package/dist/derivatives/prompt.d.ts +64 -0
- package/dist/derivatives/prompt.d.ts.map +1 -0
- package/dist/derivatives/prompt.js +193 -0
- package/dist/derivatives/prompt.js.map +1 -0
- package/dist/derivatives/registration.d.ts +54 -0
- package/dist/derivatives/registration.d.ts.map +1 -0
- package/dist/derivatives/registration.js +180 -0
- package/dist/derivatives/registration.js.map +1 -0
- package/dist/derivatives/scheduler.d.ts +84 -0
- package/dist/derivatives/scheduler.d.ts.map +1 -0
- package/dist/derivatives/scheduler.js +257 -0
- package/dist/derivatives/scheduler.js.map +1 -0
- package/dist/derivatives/store.d.ts +14 -0
- package/dist/derivatives/store.d.ts.map +1 -0
- package/dist/derivatives/store.js +77 -0
- package/dist/derivatives/store.js.map +1 -0
- package/dist/derivatives/types.d.ts +92 -0
- package/dist/derivatives/types.d.ts.map +1 -0
- package/dist/derivatives/types.js +27 -0
- package/dist/derivatives/types.js.map +1 -0
- package/dist/errors/catalog.d.ts +84 -0
- package/dist/errors/catalog.d.ts.map +1 -1
- package/dist/errors/catalog.js +119 -0
- package/dist/errors/catalog.js.map +1 -1
- package/dist/grants/index.d.ts +2 -4
- package/dist/grants/index.d.ts.map +1 -1
- package/dist/grants/index.js +1 -2
- package/dist/grants/index.js.map +1 -1
- package/dist/keys/index.d.ts +2 -2
- package/dist/keys/index.d.ts.map +1 -1
- package/dist/keys/index.js +1 -2
- package/dist/keys/index.js.map +1 -1
- package/dist/keys/public-only-account.d.ts +8 -0
- package/dist/keys/public-only-account.d.ts.map +1 -0
- package/dist/keys/public-only-account.js +21 -0
- package/dist/keys/public-only-account.js.map +1 -0
- package/dist/keys/server-account.d.ts +7 -11
- package/dist/keys/server-account.d.ts.map +1 -1
- package/dist/keys/server-account.js +1 -47
- package/dist/keys/server-account.js.map +1 -1
- package/dist/lineage/attestation.d.ts +42 -0
- package/dist/lineage/attestation.d.ts.map +1 -0
- package/dist/lineage/attestation.js +25 -0
- package/dist/lineage/attestation.js.map +1 -0
- package/dist/lineage/gateway.d.ts +105 -0
- package/dist/lineage/gateway.d.ts.map +1 -0
- package/dist/lineage/gateway.js +242 -0
- package/dist/lineage/gateway.js.map +1 -0
- package/dist/lineage/index.d.ts +5 -0
- package/dist/lineage/index.d.ts.map +1 -0
- package/dist/lineage/index.js +5 -0
- package/dist/lineage/index.js.map +1 -0
- package/dist/lineage/lineage.d.ts +141 -0
- package/dist/lineage/lineage.d.ts.map +1 -0
- package/dist/lineage/lineage.js +255 -0
- package/dist/lineage/lineage.js.map +1 -0
- package/dist/logger/index.d.ts +6 -2
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +2 -6
- package/dist/logger/index.js.map +1 -1
- package/dist/logging/access-log.d.ts +10 -2
- package/dist/logging/access-log.d.ts.map +1 -1
- package/dist/logging/access-log.js +1 -24
- package/dist/logging/access-log.js.map +1 -1
- package/dist/logging/access-reader.d.ts +0 -5
- package/dist/logging/access-reader.d.ts.map +1 -1
- package/dist/logging/access-reader.js +1 -49
- package/dist/logging/access-reader.js.map +1 -1
- package/dist/mcp/activity.d.ts +68 -0
- package/dist/mcp/activity.d.ts.map +1 -0
- package/dist/mcp/activity.js +111 -0
- package/dist/mcp/activity.js.map +1 -0
- package/dist/mcp/builder-registration.d.ts +24 -0
- package/dist/mcp/builder-registration.d.ts.map +1 -0
- package/dist/mcp/builder-registration.js +89 -0
- package/dist/mcp/builder-registration.js.map +1 -0
- package/dist/mcp/connection-api.d.ts +170 -0
- package/dist/mcp/connection-api.d.ts.map +1 -0
- package/dist/mcp/connection-api.js +379 -0
- package/dist/mcp/connection-api.js.map +1 -0
- package/dist/mcp/grantee.d.ts +52 -0
- package/dist/mcp/grantee.d.ts.map +1 -0
- package/dist/mcp/grantee.js +91 -0
- package/dist/mcp/grantee.js.map +1 -0
- package/dist/mcp/index.d.ts +11 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/read-client.d.ts +174 -0
- package/dist/mcp/read-client.d.ts.map +1 -0
- package/dist/mcp/read-client.js +576 -0
- package/dist/mcp/read-client.js.map +1 -0
- package/dist/mcp/resources.d.ts +37 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +114 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/search/index.d.ts +34 -0
- package/dist/mcp/search/index.d.ts.map +1 -0
- package/dist/mcp/search/index.js +156 -0
- package/dist/mcp/search/index.js.map +1 -0
- package/dist/mcp/server.d.ts +53 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +307 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/session.d.ts +160 -0
- package/dist/mcp/session.d.ts.map +1 -0
- package/dist/mcp/session.js +243 -0
- package/dist/mcp/session.js.map +1 -0
- package/dist/mcp/store.d.ts +13 -0
- package/dist/mcp/store.d.ts.map +1 -0
- package/dist/mcp/store.js +98 -0
- package/dist/mcp/store.js.map +1 -0
- package/dist/mcp/tools.d.ts +70 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +1346 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/types.d.ts +97 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +24 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/payment/index.d.ts +2 -0
- package/dist/payment/index.d.ts.map +1 -0
- package/dist/payment/index.js +2 -0
- package/dist/payment/index.js.map +1 -0
- package/dist/payment/x402.d.ts +155 -0
- package/dist/payment/x402.d.ts.map +1 -0
- package/dist/payment/x402.js +402 -0
- package/dist/payment/x402.js.map +1 -0
- package/dist/policy/data-read.d.ts +32 -0
- package/dist/policy/data-read.d.ts.map +1 -0
- package/dist/policy/data-read.js +128 -0
- package/dist/policy/data-read.js.map +1 -0
- package/dist/policy/data-write.d.ts +69 -0
- package/dist/policy/data-write.d.ts.map +1 -0
- package/dist/policy/data-write.js +131 -0
- package/dist/policy/data-write.js.map +1 -0
- package/dist/policy/index.d.ts +4 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +4 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/policy/signed-artifacts.d.ts +17 -0
- package/dist/policy/signed-artifacts.d.ts.map +1 -0
- package/dist/policy/signed-artifacts.js +110 -0
- package/dist/policy/signed-artifacts.js.map +1 -0
- package/dist/ports/index.d.ts +239 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +7 -0
- package/dist/ports/index.js.map +1 -0
- package/dist/schemas/server-config.d.ts +55 -0
- package/dist/schemas/server-config.d.ts.map +1 -1
- package/dist/schemas/server-config.js +107 -4
- package/dist/schemas/server-config.js.map +1 -1
- package/dist/signing/index.d.ts +1 -2
- package/dist/signing/index.d.ts.map +1 -1
- package/dist/signing/index.js +0 -1
- package/dist/signing/index.js.map +1 -1
- package/dist/signing/request-signer.d.ts +10 -2
- package/dist/signing/request-signer.d.ts.map +1 -1
- package/dist/signing/request-signer.js +6 -31
- package/dist/signing/request-signer.js.map +1 -1
- package/dist/signing/signer.d.ts +28 -2
- package/dist/signing/signer.d.ts.map +1 -1
- package/dist/signing/signer.js +32 -13
- package/dist/signing/signer.js.map +1 -1
- package/dist/storage/adapters/index.d.ts +2 -1
- package/dist/storage/adapters/index.d.ts.map +1 -1
- package/dist/storage/adapters/index.js +2 -1
- package/dist/storage/adapters/index.js.map +1 -1
- package/dist/storage/adapters/interface.d.ts +10 -0
- package/dist/storage/adapters/interface.d.ts.map +1 -1
- package/dist/storage/adapters/sdk.d.ts +23 -0
- package/dist/storage/adapters/sdk.d.ts.map +1 -0
- package/dist/storage/adapters/sdk.js +73 -0
- package/dist/storage/adapters/sdk.js.map +1 -0
- package/dist/storage/adapters/vana.d.ts +10 -30
- package/dist/storage/adapters/vana.d.ts.map +1 -1
- package/dist/storage/adapters/vana.js +94 -84
- package/dist/storage/adapters/vana.js.map +1 -1
- package/dist/storage/blocks/build.d.ts +20 -0
- package/dist/storage/blocks/build.d.ts.map +1 -0
- package/dist/storage/blocks/build.js +378 -0
- package/dist/storage/blocks/build.js.map +1 -0
- package/dist/storage/blocks/cursor.d.ts +33 -0
- package/dist/storage/blocks/cursor.d.ts.map +1 -0
- package/dist/storage/blocks/cursor.js +142 -0
- package/dist/storage/blocks/cursor.js.map +1 -0
- package/dist/storage/blocks/errors.d.ts +6 -0
- package/dist/storage/blocks/errors.d.ts.map +1 -0
- package/dist/storage/blocks/errors.js +9 -0
- package/dist/storage/blocks/errors.js.map +1 -0
- package/dist/storage/blocks/index.d.ts +6 -0
- package/dist/storage/blocks/index.d.ts.map +1 -0
- package/dist/storage/blocks/index.js +5 -0
- package/dist/storage/blocks/index.js.map +1 -0
- package/dist/storage/blocks/select.d.ts +29 -0
- package/dist/storage/blocks/select.d.ts.map +1 -0
- package/dist/storage/blocks/select.js +120 -0
- package/dist/storage/blocks/select.js.map +1 -0
- package/dist/storage/blocks/types.d.ts +56 -0
- package/dist/storage/blocks/types.d.ts.map +1 -0
- package/dist/storage/blocks/types.js +2 -0
- package/dist/{grants → storage/blocks}/types.js.map +1 -1
- package/dist/storage/hierarchy/index.d.ts +0 -1
- package/dist/storage/hierarchy/index.d.ts.map +1 -1
- package/dist/storage/hierarchy/index.js +0 -1
- package/dist/storage/hierarchy/index.js.map +1 -1
- package/dist/storage/hierarchy/manager.d.ts +0 -14
- package/dist/storage/hierarchy/manager.d.ts.map +1 -1
- package/dist/storage/hierarchy/manager.js +1 -59
- package/dist/storage/hierarchy/manager.js.map +1 -1
- package/dist/storage/hierarchy/paths.d.ts.map +1 -1
- package/dist/storage/hierarchy/paths.js +11 -4
- package/dist/storage/hierarchy/paths.js.map +1 -1
- package/dist/storage/index/index.d.ts +2 -3
- package/dist/storage/index/index.d.ts.map +1 -1
- package/dist/storage/index/index.js +1 -2
- package/dist/storage/index/index.js.map +1 -1
- package/dist/storage/index/manager.d.ts +32 -6
- package/dist/storage/index/manager.d.ts.map +1 -1
- package/dist/storage/index/manager.js +1 -131
- package/dist/storage/index/manager.js.map +1 -1
- package/dist/storage/index/schema.d.ts +1 -3
- package/dist/storage/index/schema.d.ts.map +1 -1
- package/dist/storage/index/schema.js +1 -26
- package/dist/storage/index/schema.js.map +1 -1
- package/dist/storage/index/types.d.ts +17 -0
- package/dist/storage/index/types.d.ts.map +1 -1
- package/dist/storage/preview.d.ts +12 -0
- package/dist/storage/preview.d.ts.map +1 -0
- package/dist/storage/preview.js +239 -0
- package/dist/storage/preview.js.map +1 -0
- package/dist/sync/cursor.d.ts +5 -7
- package/dist/sync/cursor.d.ts.map +1 -1
- package/dist/sync/cursor.js +1 -18
- package/dist/sync/cursor.js.map +1 -1
- package/dist/sync/data-point-feed.d.ts +35 -0
- package/dist/sync/data-point-feed.d.ts.map +1 -0
- package/dist/sync/data-point-feed.js +122 -0
- package/dist/sync/data-point-feed.js.map +1 -0
- package/dist/sync/data-point-id.d.ts +7 -0
- package/dist/sync/data-point-id.d.ts.map +1 -0
- package/dist/sync/data-point-id.js +16 -0
- package/dist/sync/data-point-id.js.map +1 -0
- package/dist/sync/delete-data-port.d.ts +50 -0
- package/dist/sync/delete-data-port.d.ts.map +1 -0
- package/dist/sync/delete-data-port.js +244 -0
- package/dist/sync/delete-data-port.js.map +1 -0
- package/dist/sync/engine/sync-manager.d.ts +29 -0
- package/dist/sync/engine/sync-manager.d.ts.map +1 -1
- package/dist/sync/engine/sync-manager.js +156 -45
- package/dist/sync/engine/sync-manager.js.map +1 -1
- package/dist/sync/index.d.ts +11 -2
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +8 -1
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/issues.d.ts +55 -0
- package/dist/sync/issues.d.ts.map +1 -0
- package/dist/sync/issues.js +168 -0
- package/dist/sync/issues.js.map +1 -0
- package/dist/sync/pending-blob-deletions.d.ts +29 -0
- package/dist/sync/pending-blob-deletions.d.ts.map +1 -0
- package/dist/sync/pending-blob-deletions.js +116 -0
- package/dist/sync/pending-blob-deletions.js.map +1 -0
- package/dist/sync/retry-memory.d.ts +33 -0
- package/dist/sync/retry-memory.d.ts.map +1 -0
- package/dist/sync/retry-memory.js +68 -0
- package/dist/sync/retry-memory.js.map +1 -0
- package/dist/sync/scope-deletions.d.ts +155 -0
- package/dist/sync/scope-deletions.d.ts.map +1 -0
- package/dist/sync/scope-deletions.js +225 -0
- package/dist/sync/scope-deletions.js.map +1 -0
- package/dist/sync/tombstone.d.ts +27 -0
- package/dist/sync/tombstone.d.ts.map +1 -0
- package/dist/sync/tombstone.js +28 -0
- package/dist/sync/tombstone.js.map +1 -0
- package/dist/sync/types.d.ts +9 -24
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/sync/workers/delete.d.ts +163 -0
- package/dist/sync/workers/delete.d.ts.map +1 -0
- package/dist/sync/workers/delete.js +474 -0
- package/dist/sync/workers/delete.js.map +1 -0
- package/dist/sync/workers/download.d.ts +115 -18
- package/dist/sync/workers/download.d.ts.map +1 -1
- package/dist/sync/workers/download.js +523 -54
- package/dist/sync/workers/download.js.map +1 -1
- package/dist/sync/workers/upload.d.ts +73 -18
- package/dist/sync/workers/upload.d.ts.map +1 -1
- package/dist/sync/workers/upload.js +382 -45
- package/dist/sync/workers/upload.js.map +1 -1
- package/dist/test-utils/e2ee-fixtures.d.ts +94 -0
- package/dist/test-utils/e2ee-fixtures.d.ts.map +1 -0
- package/dist/test-utils/e2ee-fixtures.js +116 -0
- package/dist/test-utils/e2ee-fixtures.js.map +1 -0
- package/dist/test-utils/e2ee-gateway.d.ts +61 -0
- package/dist/test-utils/e2ee-gateway.d.ts.map +1 -0
- package/dist/test-utils/e2ee-gateway.js +248 -0
- package/dist/test-utils/e2ee-gateway.js.map +1 -0
- package/dist/test-utils/index.d.ts +3 -0
- package/dist/test-utils/index.d.ts.map +1 -1
- package/dist/test-utils/index.js +3 -0
- package/dist/test-utils/index.js.map +1 -1
- package/dist/test-utils/memory-storage.d.ts +12 -0
- package/dist/test-utils/memory-storage.d.ts.map +1 -0
- package/dist/test-utils/memory-storage.js +146 -0
- package/dist/test-utils/memory-storage.js.map +1 -0
- package/dist/test-utils/wallet.d.ts +7 -0
- package/dist/test-utils/wallet.d.ts.map +1 -1
- package/dist/test-utils/wallet.js +51 -27
- package/dist/test-utils/wallet.js.map +1 -1
- package/dist/write/attribution.d.ts +185 -0
- package/dist/write/attribution.d.ts.map +1 -0
- package/dist/write/attribution.js +450 -0
- package/dist/write/attribution.js.map +1 -0
- package/dist/write/authorize.d.ts +77 -0
- package/dist/write/authorize.d.ts.map +1 -0
- package/dist/write/authorize.js +109 -0
- package/dist/write/authorize.js.map +1 -0
- package/dist/write/index.d.ts +5 -0
- package/dist/write/index.d.ts.map +1 -0
- package/dist/write/index.js +5 -0
- package/dist/write/index.js.map +1 -0
- package/dist/write/route.d.ts +61 -0
- package/dist/write/route.d.ts.map +1 -0
- package/dist/write/route.js +132 -0
- package/dist/write/route.js.map +1 -0
- package/dist/write/session.d.ts +96 -0
- package/dist/write/session.d.ts.map +1 -0
- package/dist/write/session.js +136 -0
- package/dist/write/session.js.map +1 -0
- package/package.json +63 -27
- package/dist/auth/web3-signed.d.ts +0 -48
- package/dist/auth/web3-signed.d.ts.map +0 -1
- package/dist/auth/web3-signed.js +0 -110
- package/dist/auth/web3-signed.js.map +0 -1
- package/dist/config/loader.d.ts +0 -8
- package/dist/config/loader.d.ts.map +0 -1
- package/dist/config/loader.js +0 -74
- package/dist/config/loader.js.map +0 -1
- package/dist/gateway/client.d.ts +0 -98
- package/dist/gateway/client.d.ts.map +0 -1
- package/dist/gateway/client.js +0 -171
- package/dist/gateway/client.js.map +0 -1
- package/dist/grants/eip712.d.ts +0 -28
- package/dist/grants/eip712.d.ts.map +0 -1
- package/dist/grants/eip712.js +0 -30
- package/dist/grants/eip712.js.map +0 -1
- package/dist/grants/types.d.ts +0 -29
- package/dist/grants/types.d.ts.map +0 -1
- package/dist/grants/types.js +0 -6
- package/dist/grants/verify.d.ts +0 -24
- package/dist/grants/verify.d.ts.map +0 -1
- package/dist/grants/verify.js +0 -71
- package/dist/grants/verify.js.map +0 -1
- package/dist/keys/derive.d.ts +0 -18
- package/dist/keys/derive.d.ts.map +0 -1
- package/dist/keys/derive.js +0 -43
- package/dist/keys/derive.js.map +0 -1
- package/dist/schemas/data-file.d.ts +0 -20
- package/dist/schemas/data-file.d.ts.map +0 -1
- package/dist/schemas/data-file.js +0 -23
- package/dist/schemas/data-file.js.map +0 -1
- package/dist/scopes/index.d.ts +0 -3
- package/dist/scopes/index.d.ts.map +0 -1
- package/dist/scopes/index.js +0 -3
- package/dist/scopes/index.js.map +0 -1
- package/dist/scopes/match.d.ts +0 -13
- package/dist/scopes/match.d.ts.map +0 -1
- package/dist/scopes/match.js +0 -23
- package/dist/scopes/match.js.map +0 -1
- package/dist/scopes/parse.d.ts +0 -12
- package/dist/scopes/parse.d.ts.map +0 -1
- package/dist/scopes/parse.js +0 -29
- package/dist/scopes/parse.js.map +0 -1
- package/dist/signing/eip712.d.ts +0 -69
- package/dist/signing/eip712.d.ts.map +0 -1
- package/dist/signing/eip712.js +0 -68
- package/dist/signing/eip712.js.map +0 -1
- package/dist/storage/encryption/decrypt.d.ts +0 -10
- package/dist/storage/encryption/decrypt.d.ts.map +0 -1
- package/dist/storage/encryption/decrypt.js +0 -19
- package/dist/storage/encryption/decrypt.js.map +0 -1
- package/dist/storage/encryption/encrypt.d.ts +0 -10
- package/dist/storage/encryption/encrypt.d.ts.map +0 -1
- package/dist/storage/encryption/encrypt.js +0 -19
- package/dist/storage/encryption/encrypt.js.map +0 -1
- package/dist/storage/encryption/index.d.ts +0 -3
- package/dist/storage/encryption/index.d.ts.map +0 -1
- package/dist/storage/encryption/index.js +0 -3
- package/dist/storage/encryption/index.js.map +0 -1
|
@@ -0,0 +1,1213 @@
|
|
|
1
|
+
import { DataDeletedError, DeleteTombstoneFailedError, GrantRequiredError, InvalidCascadeError, LineageCascadeUnavailableError, LineageGatewayError, LineageUnavailableError, ProtocolError, ServerNotConfiguredError, } from "../errors/catalog.js";
|
|
2
|
+
import { computeDataPointId } from "../sync/data-point-id.js";
|
|
3
|
+
import { isEntryCoveredByTombstone, } from "../sync/scope-deletions.js";
|
|
4
|
+
import { deleteScope as deleteScopeLocally, } from "../sync/workers/delete.js";
|
|
5
|
+
import { ingestDataContract, ingestBinaryDataContract, listAccessLogsContract, listDataScopesContract, listDataVersionsContract, parseDataScopeContract, parseJsonObjectBody, readDataContract, syncFileContract, triggerSyncContract, getSyncStatusContract, configReadErrorContract, configWriteErrorContract, createGrantContract, listGrantsContract, oauthTokenContract, revokeGrantContract, validateServerConfigContract, verifyGrantContract, decodeBinaryEnvelope, isBinaryEnvelope, parseMetadataHeader, stringifyMetadataHeader, IngestPersistedError, } from "../contracts/index.js";
|
|
6
|
+
import { WRITER_ATTRIBUTION_KEY, } from "../write/attribution.js";
|
|
7
|
+
import { extractLineageField, LINEAGE_KEY, prepareLineage, } from "../lineage/lineage.js";
|
|
8
|
+
import { binaryFilename, binaryMimeType, isJsonContentType, } from "../contracts/binary.js";
|
|
9
|
+
import { buildChallenge, parsePaymentHeader, verifyPayment, } from "../payment/index.js";
|
|
10
|
+
function jsonResponse(body, init) {
|
|
11
|
+
const headers = new Headers(init?.headers);
|
|
12
|
+
headers.set("Content-Type", "application/json");
|
|
13
|
+
return new Response(JSON.stringify(body), { ...init, headers });
|
|
14
|
+
}
|
|
15
|
+
// X402 spec convention: payment responses are JSON, base64-encoded into a
|
|
16
|
+
// single header value. Returns undefined on encode failure so callers can
|
|
17
|
+
// just omit the header rather than fail the read. Uses btoa + TextEncoder
|
|
18
|
+
// to stay portable across Node and web runtimes.
|
|
19
|
+
function encodePaymentResponseHeader(body) {
|
|
20
|
+
try {
|
|
21
|
+
const json = JSON.stringify(body ?? null);
|
|
22
|
+
const bytes = new TextEncoder().encode(json);
|
|
23
|
+
let binary = "";
|
|
24
|
+
for (const b of bytes)
|
|
25
|
+
binary += String.fromCharCode(b);
|
|
26
|
+
return btoa(binary);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function contractResponse(result) {
|
|
33
|
+
return jsonResponse(result.body, { status: result.status });
|
|
34
|
+
}
|
|
35
|
+
function contractErrorResponse(err) {
|
|
36
|
+
return jsonResponse(err.body, { status: err.status });
|
|
37
|
+
}
|
|
38
|
+
function protocolErrorResponse(err) {
|
|
39
|
+
return jsonResponse(err.toJSON(), { status: err.code });
|
|
40
|
+
}
|
|
41
|
+
function errorResponse(status, errorCode, message) {
|
|
42
|
+
return jsonResponse({
|
|
43
|
+
error: {
|
|
44
|
+
code: status,
|
|
45
|
+
errorCode,
|
|
46
|
+
message,
|
|
47
|
+
},
|
|
48
|
+
}, { status });
|
|
49
|
+
}
|
|
50
|
+
function methodNotAllowed() {
|
|
51
|
+
return errorResponse(405, "METHOD_NOT_ALLOWED", "Method not allowed");
|
|
52
|
+
}
|
|
53
|
+
function notFound() {
|
|
54
|
+
return errorResponse(404, "NOT_FOUND", "Not found");
|
|
55
|
+
}
|
|
56
|
+
async function withApiErrors(handler) {
|
|
57
|
+
try {
|
|
58
|
+
return await handler();
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
if (err instanceof ProtocolError) {
|
|
62
|
+
return protocolErrorResponse(err);
|
|
63
|
+
}
|
|
64
|
+
return errorResponse(500, "INTERNAL_ERROR", "Internal server error");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function normalizeLimit(value, fallback) {
|
|
68
|
+
if (value === null)
|
|
69
|
+
return fallback;
|
|
70
|
+
const parsed = Number(value);
|
|
71
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
72
|
+
}
|
|
73
|
+
function stripBasePath(pathname, basePath) {
|
|
74
|
+
if (!basePath || basePath === "/")
|
|
75
|
+
return pathname;
|
|
76
|
+
if (pathname === basePath)
|
|
77
|
+
return "/";
|
|
78
|
+
if (pathname.startsWith(`${basePath}/`)) {
|
|
79
|
+
return pathname.slice(basePath.length);
|
|
80
|
+
}
|
|
81
|
+
return pathname;
|
|
82
|
+
}
|
|
83
|
+
function decodePathPart(value) {
|
|
84
|
+
return decodeURIComponent(value ?? "");
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The grant the caller nominates for a read: `?grantId=` or the
|
|
88
|
+
* `x-ps-grant-id` header, the SDK's shape on every read route. Exported so
|
|
89
|
+
* the derivatives status route authorizes with exactly the same inputs as
|
|
90
|
+
* a data read — a credential that reads /v1/data/<scope> must not be
|
|
91
|
+
* refused by /v1/derivatives/status.
|
|
92
|
+
*/
|
|
93
|
+
export function selectedGrantId(request, url) {
|
|
94
|
+
return (url.searchParams.get("grantId") ??
|
|
95
|
+
request.headers.get("x-ps-grant-id") ??
|
|
96
|
+
undefined);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Run the X402 dispatch for one read:
|
|
100
|
+
*
|
|
101
|
+
* X-PAYMENT absent / malformed / fails local validation
|
|
102
|
+
* → challenge: fresh 402 with current fee + accessRecord
|
|
103
|
+
* X-PAYMENT valid, forward to gateway succeeds
|
|
104
|
+
* → ok: caller proceeds to read the data
|
|
105
|
+
* X-PAYMENT valid, gateway returns 4xx/5xx
|
|
106
|
+
* → gateway-error: relay the gateway's body verbatim so the builder
|
|
107
|
+
* can dispatch (insufficient balance vs. replay vs. race etc.)
|
|
108
|
+
*
|
|
109
|
+
* The gateway POST goes through `fetch` directly (not `gateway.payForOperation`)
|
|
110
|
+
* because the SDK client throws plain Errors and loses the body — which is
|
|
111
|
+
* exactly what we need to distinguish gateway 402 (insufficient balance) from
|
|
112
|
+
* 409 (replay) from 400 (amount mismatch).
|
|
113
|
+
*/
|
|
114
|
+
export async function handleX402Cycle(input) {
|
|
115
|
+
const { deps, gateway, gatewayConfig, gatewayUrl, builder, scope } = input;
|
|
116
|
+
// The gateway lowercases opId before EIP-712 recovery — we must do the
|
|
117
|
+
// same in the challenge so the builder signs over the canonical form.
|
|
118
|
+
const opIdLower = input.grantId.toLowerCase();
|
|
119
|
+
// Live grant — re-fetched every cycle. The fee.totalDue is a snapshot
|
|
120
|
+
// (SDK comment: "clients shouldn't cache"); paymentStatus may have just
|
|
121
|
+
// flipped if a concurrent payer paid the registration fee.
|
|
122
|
+
const grant = await gateway.getGrant(opIdLower);
|
|
123
|
+
if (!grant) {
|
|
124
|
+
return {
|
|
125
|
+
kind: "challenge",
|
|
126
|
+
body: {
|
|
127
|
+
x402Version: 1,
|
|
128
|
+
error: "PAYMENT_REQUIRED",
|
|
129
|
+
accepts: [],
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// Bind the accessRecord to the entry being served, if it exists and has
|
|
134
|
+
// been registered with DPv2 yet.
|
|
135
|
+
const entryRow = deps.storage.findEntry({
|
|
136
|
+
scope,
|
|
137
|
+
fileId: input.fileIdParam,
|
|
138
|
+
at: input.atParam,
|
|
139
|
+
});
|
|
140
|
+
const entryForChallenge = entryRow
|
|
141
|
+
? {
|
|
142
|
+
dataPointId: entryRow.dataPointId,
|
|
143
|
+
scope,
|
|
144
|
+
version: entryRow.version,
|
|
145
|
+
}
|
|
146
|
+
: undefined;
|
|
147
|
+
// `grant` is narrowed to non-null above, but that narrowing is not carried
|
|
148
|
+
// into this closure (vana-sdk 3.14.0 made getGrant return `... | null`), so
|
|
149
|
+
// capture the guaranteed-present value via a typed alias for the closure.
|
|
150
|
+
const liveGrant = grant;
|
|
151
|
+
async function buildFreshChallenge() {
|
|
152
|
+
return buildChallenge({
|
|
153
|
+
builder,
|
|
154
|
+
grantId: opIdLower,
|
|
155
|
+
grant: liveGrant,
|
|
156
|
+
network: deps.network ?? `vana:${gatewayConfig.chainId}`,
|
|
157
|
+
gatewayConfig,
|
|
158
|
+
serverSigner: deps.serverSigner,
|
|
159
|
+
serverOwner: deps.serverOwner,
|
|
160
|
+
entry: entryForChallenge,
|
|
161
|
+
generateNonce: deps.generatePaymentNonce,
|
|
162
|
+
generateRecordIdFn: deps.generateRecordId,
|
|
163
|
+
now: deps.now,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const headerValue = input.request.headers.get("x-payment");
|
|
167
|
+
const parsed = parsePaymentHeader(headerValue);
|
|
168
|
+
if (!parsed) {
|
|
169
|
+
return { kind: "challenge", body: await buildFreshChallenge() };
|
|
170
|
+
}
|
|
171
|
+
if (!deps.serverAddress) {
|
|
172
|
+
// Can't validate accessRecord recovery without our own address; fail
|
|
173
|
+
// closed by reissuing a challenge.
|
|
174
|
+
deps.logger?.error?.({ scope, grantId: opIdLower }, "X402 enabled but serverAddress is not configured — cannot verify accessRecord");
|
|
175
|
+
return { kind: "challenge", body: await buildFreshChallenge() };
|
|
176
|
+
}
|
|
177
|
+
const verify = await verifyPayment({
|
|
178
|
+
builder,
|
|
179
|
+
grantId: opIdLower,
|
|
180
|
+
grant,
|
|
181
|
+
entry: entryForChallenge,
|
|
182
|
+
serverAddress: deps.serverAddress,
|
|
183
|
+
gatewayConfig,
|
|
184
|
+
serverOwner: deps.serverOwner,
|
|
185
|
+
payment: parsed,
|
|
186
|
+
});
|
|
187
|
+
if (!verify.ok) {
|
|
188
|
+
deps.logger?.info?.({ scope, grantId: opIdLower, reason: verify.reason }, "X402 payment verification failed; reissuing challenge");
|
|
189
|
+
return { kind: "challenge", body: await buildFreshChallenge() };
|
|
190
|
+
}
|
|
191
|
+
// Forward to gateway via direct fetch (preserves error body). The SDK
|
|
192
|
+
// client wraps the same endpoint but throws away the structured response
|
|
193
|
+
// on non-2xx, which is exactly the info we need to distinguish gateway
|
|
194
|
+
// 402 (insufficient balance) from 409 (replay / race) from 400 (mismatch).
|
|
195
|
+
const doFetch = deps.paymentFetch ?? fetch;
|
|
196
|
+
const body = {
|
|
197
|
+
payerAddress: verify.payment.payload.message.payerAddress,
|
|
198
|
+
opType: verify.payment.payload.message.opType,
|
|
199
|
+
opId: verify.payment.payload.message.opId,
|
|
200
|
+
asset: verify.payment.payload.message.asset,
|
|
201
|
+
amount: verify.payment.payload.message.amount,
|
|
202
|
+
paymentNonce: verify.payment.payload.message.paymentNonce,
|
|
203
|
+
};
|
|
204
|
+
if (verify.payment.payload.accessRecord) {
|
|
205
|
+
body["accessRecord"] = verify.payment.payload.accessRecord;
|
|
206
|
+
}
|
|
207
|
+
let gatewayRes;
|
|
208
|
+
try {
|
|
209
|
+
gatewayRes = await doFetch(`${gatewayUrl.replace(/\/+$/, "")}/v1/escrow/pay`, {
|
|
210
|
+
method: "POST",
|
|
211
|
+
headers: {
|
|
212
|
+
"Content-Type": "application/json",
|
|
213
|
+
Authorization: `Web3Signed ${verify.payment.payload.signature}`,
|
|
214
|
+
},
|
|
215
|
+
body: JSON.stringify(body),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
deps.logger?.error?.({
|
|
220
|
+
scope,
|
|
221
|
+
grantId: opIdLower,
|
|
222
|
+
error: err.message,
|
|
223
|
+
}, "X402 gateway forward threw; reissuing challenge");
|
|
224
|
+
return { kind: "challenge", body: await buildFreshChallenge() };
|
|
225
|
+
}
|
|
226
|
+
if (gatewayRes.ok) {
|
|
227
|
+
// Preserve the gateway's structured success body so the request
|
|
228
|
+
// handler can echo it back via the X-PAYMENT-RESPONSE header (canonical
|
|
229
|
+
// X402 convention). Empty/malformed body → propagate as null; the
|
|
230
|
+
// header still gets set so builders can tell payment succeeded.
|
|
231
|
+
let payResponseBody = null;
|
|
232
|
+
try {
|
|
233
|
+
payResponseBody = await gatewayRes.json();
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// Body unreadable but status was 2xx — treat as success with no payload.
|
|
237
|
+
}
|
|
238
|
+
return { kind: "ok", payResponse: payResponseBody };
|
|
239
|
+
}
|
|
240
|
+
// Read the gateway's structured error body if there is one.
|
|
241
|
+
let errorBody;
|
|
242
|
+
try {
|
|
243
|
+
errorBody = await gatewayRes.json();
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
errorBody = { error: gatewayRes.statusText };
|
|
247
|
+
}
|
|
248
|
+
// 409 (replay or race) and 400 (amount mismatch / shape error) are
|
|
249
|
+
// recoverable with a fresh challenge — the builder re-signs with the
|
|
250
|
+
// new state and retries. 402 (insufficient balance) is relayed verbatim
|
|
251
|
+
// because the builder needs to fix their escrow, not their signature.
|
|
252
|
+
// 5xx is treated like 402-relayed since the personal server can't
|
|
253
|
+
// distinguish "transient" from "permanent" without more info.
|
|
254
|
+
if (gatewayRes.status === 409 || gatewayRes.status === 400) {
|
|
255
|
+
deps.logger?.info?.({ scope, grantId: opIdLower, status: gatewayRes.status, errorBody }, "Gateway rejected X-PAYMENT; reissuing X402 challenge");
|
|
256
|
+
return { kind: "challenge", body: await buildFreshChallenge() };
|
|
257
|
+
}
|
|
258
|
+
return { kind: "gateway-error", status: gatewayRes.status, body: errorBody };
|
|
259
|
+
}
|
|
260
|
+
function collectedAt(now) {
|
|
261
|
+
return now()
|
|
262
|
+
.toISOString()
|
|
263
|
+
.replace(/\.\d{3}Z$/, "Z");
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* The deletion that applies to a read of `scope`, or null when the read may
|
|
267
|
+
* be served. `entry` is the local version the read would serve (undefined
|
|
268
|
+
* on a local miss).
|
|
269
|
+
*
|
|
270
|
+
* - A local copy is refused when the gateway holds a tombstone that covers
|
|
271
|
+
* it (see `isEntryCoveredByTombstone`: registry versions and the ingest
|
|
272
|
+
* marker, never clocks). A local re-add on top of the tombstone is served.
|
|
273
|
+
* - A local miss asks the tracker to consult the gateway (bounded by its
|
|
274
|
+
* per-scope verdict cache and failure back-off): the local index cannot
|
|
275
|
+
* tell "deleted long ago" from "never had it".
|
|
276
|
+
* Gateway unreachability never fails the read: the tracker answers from its
|
|
277
|
+
* last known state, which is the documented offline behaviour.
|
|
278
|
+
*/
|
|
279
|
+
export async function resolveReadDeletion(deps, scope, entry) {
|
|
280
|
+
if (!deps.scopeDeletions)
|
|
281
|
+
return null;
|
|
282
|
+
const verdict = await deps.scopeDeletions.resolve(scope, {
|
|
283
|
+
consultGateway: entry ? "if-stale" : "always",
|
|
284
|
+
});
|
|
285
|
+
if (!verdict.deleted)
|
|
286
|
+
return null;
|
|
287
|
+
if (entry && !isEntryCoveredByTombstone(entry, verdict)) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
scope,
|
|
292
|
+
dataPointId: deps.serverOwner
|
|
293
|
+
? computeDataPointId(deps.serverOwner, scope)
|
|
294
|
+
: null,
|
|
295
|
+
deletedAt: verdict.deletedAt,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Throw 410 DATA_DELETED when `resolveReadDeletion` says the read must not be
|
|
300
|
+
* served. Call it before any payment step: a builder must never be charged
|
|
301
|
+
* for a scope the owner deleted.
|
|
302
|
+
*/
|
|
303
|
+
export async function assertScopeNotDeleted(deps, scope, entry) {
|
|
304
|
+
const deletion = await resolveReadDeletion(deps, scope, entry);
|
|
305
|
+
if (deletion)
|
|
306
|
+
throw new DataDeletedError(deletion);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Discovery filter for the scope and version listings: an entry a read would
|
|
310
|
+
* refuse with 410 is not listed either. Undefined (no filtering) when no
|
|
311
|
+
* deletion tracker is wired.
|
|
312
|
+
*/
|
|
313
|
+
function discoveryVisibility(deps) {
|
|
314
|
+
if (!deps.scopeDeletions)
|
|
315
|
+
return undefined;
|
|
316
|
+
return async (scope, entry) => (await resolveReadDeletion(deps, scope, entry)) === null;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* The causal marker a fresh ingest carries: the tombstone version this
|
|
320
|
+
* replica currently knows for the scope, if any, so the new entry is a
|
|
321
|
+
* deliberate re-add that survives that deletion (and only that one). Uses
|
|
322
|
+
* the same verdict path as reads, so it costs a gateway lookup only when the
|
|
323
|
+
* tracker's view is stale. An unknown or unsafe version leaves the marker
|
|
324
|
+
* null: the entry is then covered, never resurrected on a guess.
|
|
325
|
+
*/
|
|
326
|
+
async function ingestTombstoneMarker(deps, scope) {
|
|
327
|
+
if (!deps.scopeDeletions)
|
|
328
|
+
return null;
|
|
329
|
+
const verdict = await deps.scopeDeletions.resolve(scope);
|
|
330
|
+
if (!verdict.deleted || verdict.version === null)
|
|
331
|
+
return null;
|
|
332
|
+
const version = Number(verdict.version);
|
|
333
|
+
return Number.isSafeInteger(version) ? version : null;
|
|
334
|
+
}
|
|
335
|
+
// The delete worker wants a full Logger; the API logger is all-optional.
|
|
336
|
+
function apiLoggerAsLogger(logger) {
|
|
337
|
+
const noop = () => undefined;
|
|
338
|
+
return {
|
|
339
|
+
debug: (payload, message) => (logger?.debug ?? noop)(payload, message ?? ""),
|
|
340
|
+
info: (payload, message) => (logger?.info ?? noop)(payload, message ?? ""),
|
|
341
|
+
warn: (payload, message) => (logger?.warn ?? noop)(payload, message ?? ""),
|
|
342
|
+
error: (payload, message) => (logger?.error ?? noop)(payload, message ?? ""),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function notifyDataWritten(deps, event) {
|
|
346
|
+
if (!deps.onDataWritten)
|
|
347
|
+
return;
|
|
348
|
+
try {
|
|
349
|
+
deps.onDataWritten(event);
|
|
350
|
+
}
|
|
351
|
+
catch (err) {
|
|
352
|
+
deps.logger?.warn?.({
|
|
353
|
+
scope: event.scope,
|
|
354
|
+
error: err instanceof Error ? err.message : String(err),
|
|
355
|
+
}, "onDataWritten hook failed; record already stored");
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function notifyNewData(syncManager) {
|
|
359
|
+
if (!syncManager)
|
|
360
|
+
return;
|
|
361
|
+
if (syncManager.notifyNewData) {
|
|
362
|
+
syncManager.notifyNewData();
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
void syncManager.trigger().catch(() => undefined);
|
|
366
|
+
}
|
|
367
|
+
function shouldReportReadFulfillment(grantId) {
|
|
368
|
+
return (grantId !== "unknown" && grantId !== "owner" && grantId !== "policy-bypass");
|
|
369
|
+
}
|
|
370
|
+
function warnReadFulfillmentReporterFailed(deps, event, err) {
|
|
371
|
+
deps.logger?.warn?.({
|
|
372
|
+
builder: event.builder,
|
|
373
|
+
error: err instanceof Error ? err.message : String(err),
|
|
374
|
+
grantId: event.grantId,
|
|
375
|
+
logId: event.logId,
|
|
376
|
+
scope: event.scope,
|
|
377
|
+
}, "Read fulfillment reporter failed");
|
|
378
|
+
}
|
|
379
|
+
export function reportPersonalServerReadFulfillment(deps, event) {
|
|
380
|
+
if (!deps.readFulfillmentReporter ||
|
|
381
|
+
!shouldReportReadFulfillment(event.grantId)) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
try {
|
|
385
|
+
void Promise.resolve(deps.readFulfillmentReporter.report(event)).catch((err) => warnReadFulfillmentReporterFailed(deps, event, err));
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
warnReadFulfillmentReporterFailed(deps, event, err);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Validate a write's caller-supplied `lineage` (JSON body top level, or the
|
|
393
|
+
* binary metadata object) into the `$lineage` record to stamp. `undefined`
|
|
394
|
+
* (or null) means a root record. Throws ProtocolErrors; the write handler
|
|
395
|
+
* lets them propagate before anything is stored.
|
|
396
|
+
*/
|
|
397
|
+
async function prepareWriteLineage(deps, scope, field) {
|
|
398
|
+
if (field === undefined || field === null)
|
|
399
|
+
return undefined;
|
|
400
|
+
return prepareLineage({
|
|
401
|
+
scope,
|
|
402
|
+
field,
|
|
403
|
+
serverOwner: deps.serverOwner,
|
|
404
|
+
storage: deps.storage,
|
|
405
|
+
gateway: deps.lineageGateway,
|
|
406
|
+
now: deps.now ?? (() => new Date()),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* The lineage view a read auth result entitles the caller to. The auth port
|
|
411
|
+
* answers an owner read with no result or with the "owner" / "policy-bypass"
|
|
412
|
+
* sentinels (see api-auth): those get the full view. A builder read carries
|
|
413
|
+
* the grant the read policy resolved, and that grant names the view. A
|
|
414
|
+
* builder result with no resolved grant (or an "unknown" one) proves
|
|
415
|
+
* nothing and is refused: a missing grantId must never widen to the full
|
|
416
|
+
* view.
|
|
417
|
+
*/
|
|
418
|
+
function resolveLineageGrantView(authResult) {
|
|
419
|
+
if (authResult === undefined ||
|
|
420
|
+
authResult.grantId === "owner" ||
|
|
421
|
+
authResult.grantId === "policy-bypass") {
|
|
422
|
+
return { grantId: undefined };
|
|
423
|
+
}
|
|
424
|
+
const grantId = authResult.grantId;
|
|
425
|
+
if (typeof grantId !== "string" || grantId === "" || grantId === "unknown") {
|
|
426
|
+
throw new GrantRequiredError({
|
|
427
|
+
reason: "a lineage read by a builder needs a resolved grant",
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return { grantId };
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* True when a read auth result entitles the caller to the owner's view of a
|
|
434
|
+
* record — the same convention as resolveLineageGrantView: no result, or the
|
|
435
|
+
* "owner" / "policy-bypass" sentinels. Everything else is a grantee.
|
|
436
|
+
*/
|
|
437
|
+
export function isOwnerView(authResult) {
|
|
438
|
+
return (authResult === undefined ||
|
|
439
|
+
authResult.grantId === "owner" ||
|
|
440
|
+
authResult.grantId === "policy-bypass");
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* The envelope view a grantee read is entitled to. The server-stamped
|
|
444
|
+
* `$writtenBy` (writing builder's address, grantId, signature) and
|
|
445
|
+
* `$lineage` (the owner's source data-point ids, dictionary-attackable back
|
|
446
|
+
* to scopes since dataPointId = keccak(owner, scope)) never leave the server
|
|
447
|
+
* on a grantee read: the owner reads them from storage, and grantees get the
|
|
448
|
+
* graph only through the grant-redacted lineage endpoint. When `$lineage` is
|
|
449
|
+
* present, the caller-supplied lineage field it mirrors (top-level `lineage`
|
|
450
|
+
* on a JSON record, `metadata.lineage` on a binary one) carries the same ids
|
|
451
|
+
* and is dropped with it; without the stamp such a field is ordinary user
|
|
452
|
+
* data and stays. Redaction is at serve time only — the stored envelope, its
|
|
453
|
+
* dataHash commitment, and the owner's view are unchanged.
|
|
454
|
+
*/
|
|
455
|
+
export function redactEnvelopeForGrantee(envelope) {
|
|
456
|
+
const { [WRITER_ATTRIBUTION_KEY]: attribution, [LINEAGE_KEY]: storedLineage, ...rest } = envelope.data;
|
|
457
|
+
if (attribution === undefined && storedLineage === undefined) {
|
|
458
|
+
return envelope;
|
|
459
|
+
}
|
|
460
|
+
let data = rest;
|
|
461
|
+
if (storedLineage !== undefined) {
|
|
462
|
+
if (isBinaryEnvelope(envelope)) {
|
|
463
|
+
const metadata = data["metadata"];
|
|
464
|
+
if (metadata !== null &&
|
|
465
|
+
typeof metadata === "object" &&
|
|
466
|
+
!Array.isArray(metadata)) {
|
|
467
|
+
const { lineage: _consumed, ...metadataRest } = metadata;
|
|
468
|
+
if (Object.keys(metadataRest).length === 0) {
|
|
469
|
+
// The lineage was the whole metadata object; leaving `{}` behind
|
|
470
|
+
// would still disclose that a metadata object existed.
|
|
471
|
+
const { metadata: _emptied, ...dataRest } = data;
|
|
472
|
+
data = dataRest;
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
data = { ...data, metadata: metadataRest };
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
const { lineage: _consumed, ...dataRest } = data;
|
|
481
|
+
data = dataRest;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return { ...envelope, data };
|
|
485
|
+
}
|
|
486
|
+
export async function handlePersonalServerDataRequest(request, deps, options = {}) {
|
|
487
|
+
return withApiErrors(async () => {
|
|
488
|
+
const url = new URL(request.url);
|
|
489
|
+
const pathname = stripBasePath(url.pathname, options.basePath);
|
|
490
|
+
if (pathname === "/" || pathname === "") {
|
|
491
|
+
if (request.method !== "GET")
|
|
492
|
+
return methodNotAllowed();
|
|
493
|
+
await deps.auth.authorizeBuilderList(request);
|
|
494
|
+
const result = await listDataScopesContract({
|
|
495
|
+
storage: deps.storage,
|
|
496
|
+
scopePrefix: url.searchParams.get("scopePrefix") ?? undefined,
|
|
497
|
+
limit: normalizeLimit(url.searchParams.get("limit"), 20),
|
|
498
|
+
offset: normalizeLimit(url.searchParams.get("offset"), 0),
|
|
499
|
+
isVisible: discoveryVisibility(deps),
|
|
500
|
+
});
|
|
501
|
+
return jsonResponse(result.response);
|
|
502
|
+
}
|
|
503
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
504
|
+
if (parts.length === 2 && parts[1] === "versions") {
|
|
505
|
+
if (request.method !== "GET")
|
|
506
|
+
return methodNotAllowed();
|
|
507
|
+
await deps.auth.authorizeBuilderList(request);
|
|
508
|
+
const result = await listDataVersionsContract({
|
|
509
|
+
storage: deps.storage,
|
|
510
|
+
scopeParam: decodePathPart(parts[0]),
|
|
511
|
+
limit: normalizeLimit(url.searchParams.get("limit"), 20),
|
|
512
|
+
offset: normalizeLimit(url.searchParams.get("offset"), 0),
|
|
513
|
+
isVisible: discoveryVisibility(deps),
|
|
514
|
+
});
|
|
515
|
+
if (!result.ok)
|
|
516
|
+
return contractErrorResponse(result);
|
|
517
|
+
if (result.response.total === 0) {
|
|
518
|
+
// Nothing visible: answer 410 rather than an empty list when that is
|
|
519
|
+
// because the scope is tombstoned, exactly as a read would.
|
|
520
|
+
await assertScopeNotDeleted(deps, result.scope, undefined);
|
|
521
|
+
}
|
|
522
|
+
return jsonResponse(result.response);
|
|
523
|
+
}
|
|
524
|
+
if ((parts.length === 2 || parts.length === 3) && parts[1] === "lineage") {
|
|
525
|
+
if (request.method !== "GET")
|
|
526
|
+
return methodNotAllowed();
|
|
527
|
+
const scopeResult = parseDataScopeContract(decodePathPart(parts[0]));
|
|
528
|
+
if (!scopeResult.ok)
|
|
529
|
+
return contractErrorResponse(scopeResult);
|
|
530
|
+
// The version is a PATH segment (/v1/data/:scope/lineage/:version), so
|
|
531
|
+
// it is inside the signed request uri: a builder's signature for one
|
|
532
|
+
// version cannot be replayed to read another. The grant view is the
|
|
533
|
+
// signed grantId claim the read auth already honours. Query
|
|
534
|
+
// parameters would be unsigned inputs to the view and are refused.
|
|
535
|
+
if (url.searchParams.has("version")) {
|
|
536
|
+
return errorResponse(400, "INVALID_VERSION", "version is a path segment (/v1/data/:scope/lineage/:version), not a query parameter");
|
|
537
|
+
}
|
|
538
|
+
if (url.search.length > 0) {
|
|
539
|
+
return errorResponse(400, "INVALID_QUERY", "lineage reads take no query parameters; the version is a path segment and the grant view is the signed grantId claim");
|
|
540
|
+
}
|
|
541
|
+
const version = parts.length === 3 ? decodePathPart(parts[2]) : undefined;
|
|
542
|
+
if (version !== undefined && !/^[1-9]\d*$/.test(version)) {
|
|
543
|
+
return errorResponse(400, "INVALID_VERSION", "version must be a positive decimal integer");
|
|
544
|
+
}
|
|
545
|
+
// Same gate as a read of the scope: the owner, or a builder whose grant
|
|
546
|
+
// covers it. The gateway then attests the view that grant sees, so a
|
|
547
|
+
// builder only learns the scopes of nodes its grant covers.
|
|
548
|
+
const authResult = await deps.auth.authorizeBuilderRead({
|
|
549
|
+
request,
|
|
550
|
+
scope: scopeResult.scope,
|
|
551
|
+
grantId: selectedGrantId(request, url),
|
|
552
|
+
});
|
|
553
|
+
if (!deps.serverOwner) {
|
|
554
|
+
throw new ServerNotConfiguredError({
|
|
555
|
+
reason: "serverOwner is required to resolve the data point id",
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (!deps.lineageGateway)
|
|
559
|
+
throw new LineageUnavailableError();
|
|
560
|
+
const { grantId } = resolveLineageGrantView(authResult);
|
|
561
|
+
let result;
|
|
562
|
+
try {
|
|
563
|
+
result = await deps.lineageGateway.getLineage({
|
|
564
|
+
dataPointId: computeDataPointId(deps.serverOwner, scopeResult.scope),
|
|
565
|
+
version,
|
|
566
|
+
grantId,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
catch (err) {
|
|
570
|
+
// Transport failures (DNS, refused, timeout) are gateway errors to
|
|
571
|
+
// the caller, not internal ones: same 502 as a gateway error body.
|
|
572
|
+
if (err instanceof ProtocolError)
|
|
573
|
+
throw err;
|
|
574
|
+
throw new LineageGatewayError({
|
|
575
|
+
status: 0,
|
|
576
|
+
body: { error: err instanceof Error ? err.message : String(err) },
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
if (!result.ok) {
|
|
580
|
+
if (result.status === 404) {
|
|
581
|
+
return errorResponse(404, "NOT_FOUND", version
|
|
582
|
+
? `Scope "${scopeResult.scope}" has no registered version ${version}`
|
|
583
|
+
: `Scope "${scopeResult.scope}" is not registered at the gateway`);
|
|
584
|
+
}
|
|
585
|
+
if (result.status === 403) {
|
|
586
|
+
throw new ProtocolError(403, "LINEAGE_FORBIDDEN", "The gateway refused the lineage view for this grant", { gateway: result.body });
|
|
587
|
+
}
|
|
588
|
+
throw new LineageGatewayError({
|
|
589
|
+
status: result.status,
|
|
590
|
+
body: result.body,
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
// A lineage view discloses the derivation graph, so a served view is
|
|
594
|
+
// logged like a data read (refusals above return without an entry).
|
|
595
|
+
await deps.accessLogWriter.write({
|
|
596
|
+
logId: deps.createLogId?.() ?? crypto.randomUUID(),
|
|
597
|
+
grantId: authResult?.grantId ?? "owner",
|
|
598
|
+
builder: authResult?.builder ?? "unknown",
|
|
599
|
+
action: "lineage",
|
|
600
|
+
scope: scopeResult.scope,
|
|
601
|
+
timestamp: (deps.now ?? (() => new Date()))().toISOString(),
|
|
602
|
+
ipAddress: request.headers.get("x-forwarded-for") ??
|
|
603
|
+
request.headers.get("x-real-ip") ??
|
|
604
|
+
"unknown",
|
|
605
|
+
userAgent: request.headers.get("user-agent") ?? "unknown",
|
|
606
|
+
});
|
|
607
|
+
return jsonResponse({ data: result.data, proof: result.proof });
|
|
608
|
+
}
|
|
609
|
+
if (parts.length !== 1)
|
|
610
|
+
return notFound();
|
|
611
|
+
const scopeParam = decodePathPart(parts[0]);
|
|
612
|
+
if (request.method === "GET") {
|
|
613
|
+
const scopeResult = parseDataScopeContract(scopeParam);
|
|
614
|
+
if (!scopeResult.ok)
|
|
615
|
+
return contractErrorResponse(scopeResult);
|
|
616
|
+
const selectedEntry = deps.storage.findEntry({
|
|
617
|
+
scope: scopeResult.scope,
|
|
618
|
+
fileId: url.searchParams.get("fileId") ?? undefined,
|
|
619
|
+
at: url.searchParams.get("at") ?? undefined,
|
|
620
|
+
});
|
|
621
|
+
// Holds the base64-encoded gateway payForOperation response body when
|
|
622
|
+
// X402 succeeds — emitted on the final read response as X-PAYMENT-RESPONSE.
|
|
623
|
+
let paymentResponseHeader;
|
|
624
|
+
const grantId = selectedGrantId(request, url);
|
|
625
|
+
const authResult = await deps.auth.authorizeBuilderRead({
|
|
626
|
+
request,
|
|
627
|
+
scope: scopeResult.scope,
|
|
628
|
+
grantId,
|
|
629
|
+
fileId: url.searchParams.get("fileId") ?? selectedEntry?.fileId ?? undefined,
|
|
630
|
+
at: url.searchParams.get("at") ?? undefined,
|
|
631
|
+
});
|
|
632
|
+
// Deletion gate, after auth (so unauthenticated callers learn nothing)
|
|
633
|
+
// and before payment (so nobody is charged for deleted data). Covers
|
|
634
|
+
// both a stale local copy of a tombstoned scope and a local miss.
|
|
635
|
+
await assertScopeNotDeleted(deps, scopeResult.scope, selectedEntry);
|
|
636
|
+
// X402 payment dance for builder reads. Owner-exempt reads (the
|
|
637
|
+
// grantId sentinels "owner" / "policy-bypass") skip payment entirely
|
|
638
|
+
// since there's no payable op to attach the payment to.
|
|
639
|
+
//
|
|
640
|
+
// The grantId we pay against is the one verifyDataReadPolicy resolved
|
|
641
|
+
// from the Web3Signed payload (authoritative source) — not the URL /
|
|
642
|
+
// header hint, which may be absent when the payload carries it.
|
|
643
|
+
const isOwnerSignal = authResult?.grantId === "owner" ||
|
|
644
|
+
authResult?.grantId === "policy-bypass";
|
|
645
|
+
const builder = authResult?.builder;
|
|
646
|
+
const resolvedGrantId = !isOwnerSignal && authResult?.grantId ? authResult.grantId : undefined;
|
|
647
|
+
const willCharge = Boolean(deps.paymentEnabled &&
|
|
648
|
+
!isOwnerSignal &&
|
|
649
|
+
typeof builder === "string" &&
|
|
650
|
+
builder.startsWith("0x") &&
|
|
651
|
+
resolvedGrantId &&
|
|
652
|
+
deps.gateway &&
|
|
653
|
+
deps.gatewayConfig &&
|
|
654
|
+
deps.gatewayUrl);
|
|
655
|
+
// Diagnostic: makes it explicit why a read is/isn't charged. x402 has
|
|
656
|
+
// many "served free" branches (owner read, payment disabled, missing
|
|
657
|
+
// gateway deps); this surfaces the deciding inputs.
|
|
658
|
+
//
|
|
659
|
+
// Routing: when a logger is wired (the Node server always provides one),
|
|
660
|
+
// log the full detail through it at debug level so the operator's level
|
|
661
|
+
// controls + redaction apply and access metadata (scope/grantId/builder)
|
|
662
|
+
// doesn't unconditionally hit stdout. The browser PS-Lite runtime wires
|
|
663
|
+
// no logger; there we fall back to console but log ONLY the non-identifying
|
|
664
|
+
// decision booleans — never scope/grantId/builder.
|
|
665
|
+
if (deps.logger?.debug) {
|
|
666
|
+
deps.logger.debug({
|
|
667
|
+
scope: scopeResult.scope,
|
|
668
|
+
grantId: authResult?.grantId,
|
|
669
|
+
isOwnerSignal,
|
|
670
|
+
builder,
|
|
671
|
+
resolvedGrantId,
|
|
672
|
+
paymentEnabled: Boolean(deps.paymentEnabled),
|
|
673
|
+
hasGateway: Boolean(deps.gateway),
|
|
674
|
+
hasGatewayConfig: Boolean(deps.gatewayConfig),
|
|
675
|
+
hasGatewayUrl: Boolean(deps.gatewayUrl),
|
|
676
|
+
willCharge,
|
|
677
|
+
}, "[x402-gate] read");
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
console.info("[x402-gate] read", {
|
|
681
|
+
isOwnerSignal,
|
|
682
|
+
paymentEnabled: Boolean(deps.paymentEnabled),
|
|
683
|
+
hasGateway: Boolean(deps.gateway),
|
|
684
|
+
hasGatewayConfig: Boolean(deps.gatewayConfig),
|
|
685
|
+
hasGatewayUrl: Boolean(deps.gatewayUrl),
|
|
686
|
+
willCharge,
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
// Keep the full condition inline (not `if (willCharge)`) so TS narrows
|
|
690
|
+
// deps.gateway/gatewayConfig/gatewayUrl to non-undefined in the block.
|
|
691
|
+
if (deps.paymentEnabled &&
|
|
692
|
+
!isOwnerSignal &&
|
|
693
|
+
typeof builder === "string" &&
|
|
694
|
+
builder.startsWith("0x") &&
|
|
695
|
+
resolvedGrantId &&
|
|
696
|
+
deps.gateway &&
|
|
697
|
+
deps.gatewayConfig &&
|
|
698
|
+
deps.gatewayUrl) {
|
|
699
|
+
const x402Result = await handleX402Cycle({
|
|
700
|
+
deps,
|
|
701
|
+
request,
|
|
702
|
+
scope: scopeResult.scope,
|
|
703
|
+
fileIdParam: url.searchParams.get("fileId") ?? undefined,
|
|
704
|
+
atParam: url.searchParams.get("at") ?? undefined,
|
|
705
|
+
grantId: resolvedGrantId,
|
|
706
|
+
builder: builder,
|
|
707
|
+
gateway: deps.gateway,
|
|
708
|
+
gatewayConfig: deps.gatewayConfig,
|
|
709
|
+
gatewayUrl: deps.gatewayUrl,
|
|
710
|
+
});
|
|
711
|
+
if (x402Result.kind === "challenge") {
|
|
712
|
+
return jsonResponse(x402Result.body, { status: 402 });
|
|
713
|
+
}
|
|
714
|
+
if (x402Result.kind === "gateway-error") {
|
|
715
|
+
// Relay the gateway's structured body verbatim so the builder can
|
|
716
|
+
// dispatch on it (insufficient balance vs. replay vs. race etc.).
|
|
717
|
+
return jsonResponse(x402Result.body, { status: x402Result.status });
|
|
718
|
+
}
|
|
719
|
+
// x402Result.kind === "ok" — payment accepted, proceed to read.
|
|
720
|
+
// The gateway's success body is forwarded back to the builder via
|
|
721
|
+
// X-PAYMENT-RESPONSE (canonical X402 convention) so callers can see
|
|
722
|
+
// breakdown / paidAt / paymentNonce without a second gateway round-trip.
|
|
723
|
+
paymentResponseHeader = encodePaymentResponseHeader(x402Result.payResponse);
|
|
724
|
+
}
|
|
725
|
+
const result = await readDataContract({
|
|
726
|
+
storage: deps.storage,
|
|
727
|
+
scopeParam: scopeResult.scope,
|
|
728
|
+
fileId: url.searchParams.get("fileId") ?? undefined,
|
|
729
|
+
at: url.searchParams.get("at") ?? undefined,
|
|
730
|
+
});
|
|
731
|
+
if (!result.ok) {
|
|
732
|
+
if (result.status === 404) {
|
|
733
|
+
// The entry selected above can vanish before the read (a sync
|
|
734
|
+
// reconcile applying a tombstone concurrently); answer 410, not
|
|
735
|
+
// 404, when that is why.
|
|
736
|
+
await assertScopeNotDeleted(deps, scopeResult.scope, undefined);
|
|
737
|
+
}
|
|
738
|
+
return contractErrorResponse(result);
|
|
739
|
+
}
|
|
740
|
+
const servedEnvelope = isOwnerView(authResult)
|
|
741
|
+
? result.envelope
|
|
742
|
+
: redactEnvelopeForGrantee(result.envelope);
|
|
743
|
+
const logId = deps.createLogId?.() ?? crypto.randomUUID();
|
|
744
|
+
const timestamp = (deps.now ?? (() => new Date()))().toISOString();
|
|
745
|
+
const ipAddress = request.headers.get("x-forwarded-for") ??
|
|
746
|
+
request.headers.get("x-real-ip") ??
|
|
747
|
+
"unknown";
|
|
748
|
+
const userAgent = request.headers.get("user-agent") ?? "unknown";
|
|
749
|
+
const loggedGrantId = authResult?.grantId ?? grantId ?? "unknown";
|
|
750
|
+
const loggedBuilder = authResult?.builder ?? "unknown";
|
|
751
|
+
await deps.accessLogWriter.write({
|
|
752
|
+
logId,
|
|
753
|
+
grantId: loggedGrantId,
|
|
754
|
+
builder: loggedBuilder,
|
|
755
|
+
action: "read",
|
|
756
|
+
scope: scopeResult.scope,
|
|
757
|
+
timestamp,
|
|
758
|
+
ipAddress,
|
|
759
|
+
userAgent,
|
|
760
|
+
});
|
|
761
|
+
const reportReadFulfillment = () => {
|
|
762
|
+
if (!authResult?.grantId || !authResult.builder)
|
|
763
|
+
return;
|
|
764
|
+
reportPersonalServerReadFulfillment(deps, {
|
|
765
|
+
builder: authResult.builder,
|
|
766
|
+
fileId: url.searchParams.get("fileId") ??
|
|
767
|
+
selectedEntry?.fileId ??
|
|
768
|
+
undefined,
|
|
769
|
+
grantId: authResult.grantId,
|
|
770
|
+
ipAddress,
|
|
771
|
+
logId,
|
|
772
|
+
scope: scopeResult.scope,
|
|
773
|
+
servedAt: timestamp,
|
|
774
|
+
userAgent,
|
|
775
|
+
});
|
|
776
|
+
};
|
|
777
|
+
const headers = {};
|
|
778
|
+
if (paymentResponseHeader) {
|
|
779
|
+
headers["X-PAYMENT-RESPONSE"] = paymentResponseHeader;
|
|
780
|
+
}
|
|
781
|
+
const wantsRawContent = url.searchParams.get("content") === "raw";
|
|
782
|
+
// `?content=raw` streams the decoded bytes of a binary envelope with its
|
|
783
|
+
// original media type, so a builder can download the file directly. The
|
|
784
|
+
// X-PAYMENT-RESPONSE header (if any) rides along on the raw response too.
|
|
785
|
+
if (wantsRawContent) {
|
|
786
|
+
if (!isBinaryEnvelope(servedEnvelope)) {
|
|
787
|
+
return jsonResponse({
|
|
788
|
+
error: "NOT_BINARY_SCOPE",
|
|
789
|
+
message: `Scope "${scopeResult.scope}" does not expose raw binary content.`,
|
|
790
|
+
}, { status: 400, headers });
|
|
791
|
+
}
|
|
792
|
+
const decoded = decodeBinaryEnvelope(servedEnvelope);
|
|
793
|
+
headers["Content-Type"] = decoded.mimeType;
|
|
794
|
+
headers["Content-Length"] = String(decoded.bytes.length);
|
|
795
|
+
if (decoded.filename) {
|
|
796
|
+
headers["Content-Disposition"] =
|
|
797
|
+
`attachment; filename="${decoded.filename}"`;
|
|
798
|
+
}
|
|
799
|
+
if (decoded.metadata !== undefined) {
|
|
800
|
+
headers["X-Vana-Metadata"] = stringifyMetadataHeader(decoded.metadata);
|
|
801
|
+
}
|
|
802
|
+
const response = new Response(decoded.bytes, {
|
|
803
|
+
status: 200,
|
|
804
|
+
headers,
|
|
805
|
+
});
|
|
806
|
+
return response;
|
|
807
|
+
}
|
|
808
|
+
const response = jsonResponse(servedEnvelope, { headers });
|
|
809
|
+
reportReadFulfillment();
|
|
810
|
+
return response;
|
|
811
|
+
}
|
|
812
|
+
if (request.method === "POST") {
|
|
813
|
+
// Delegated writes: an auth port that supports write sessions handles
|
|
814
|
+
// both paths in authorizeWrite (builder session token -> write policy +
|
|
815
|
+
// attribution; anything else -> owner path). Ports without it keep
|
|
816
|
+
// today's owner-only gate.
|
|
817
|
+
let writeAuth;
|
|
818
|
+
if (deps.auth.authorizeWrite) {
|
|
819
|
+
writeAuth =
|
|
820
|
+
(await deps.auth.authorizeWrite({ request, scope: scopeParam })) ??
|
|
821
|
+
undefined;
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
await deps.auth.authorizeOwner(request);
|
|
825
|
+
}
|
|
826
|
+
// A write that fails before commit hands the proof back (replay guard)
|
|
827
|
+
// so the builder's retry with the same still-valid proof is accepted;
|
|
828
|
+
// after commit the proof stays consumed (a retry would be a duplicate).
|
|
829
|
+
let committed = false;
|
|
830
|
+
const failWrite = async (response) => {
|
|
831
|
+
await writeAuth?.releaseProof?.();
|
|
832
|
+
return response;
|
|
833
|
+
};
|
|
834
|
+
try {
|
|
835
|
+
const scopeResult = parseDataScopeContract(scopeParam);
|
|
836
|
+
if (!scopeResult.ok)
|
|
837
|
+
return failWrite(contractErrorResponse(scopeResult));
|
|
838
|
+
const collectedAtValue = collectedAt(deps.now ?? (() => new Date()));
|
|
839
|
+
const status = deps.syncManager ? "syncing" : "stored";
|
|
840
|
+
const afterTombstoneVersion = await ingestTombstoneMarker(deps, scopeResult.scope);
|
|
841
|
+
// Builder writes land in the same access log as builder reads, so the
|
|
842
|
+
// owner sees who wrote what under which grant.
|
|
843
|
+
// Best-effort: it runs AFTER the record is committed, so a log failure
|
|
844
|
+
// must not turn a successful write into a 500 (the builder would retry
|
|
845
|
+
// and store a duplicate). The ingest log line above still names the
|
|
846
|
+
// builder; the failure itself is logged for the owner.
|
|
847
|
+
const logBuilderWrite = async () => {
|
|
848
|
+
if (!writeAuth)
|
|
849
|
+
return;
|
|
850
|
+
try {
|
|
851
|
+
await deps.accessLogWriter.write({
|
|
852
|
+
logId: deps.createLogId?.() ?? crypto.randomUUID(),
|
|
853
|
+
grantId: writeAuth.grantId,
|
|
854
|
+
builder: writeAuth.builder,
|
|
855
|
+
action: "write",
|
|
856
|
+
scope: scopeResult.scope,
|
|
857
|
+
timestamp: (deps.now ?? (() => new Date()))().toISOString(),
|
|
858
|
+
ipAddress: request.headers.get("x-forwarded-for") ??
|
|
859
|
+
request.headers.get("x-real-ip") ??
|
|
860
|
+
"unknown",
|
|
861
|
+
userAgent: request.headers.get("user-agent") ?? "unknown",
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
catch (err) {
|
|
865
|
+
deps.logger?.warn?.({
|
|
866
|
+
scope: scopeResult.scope,
|
|
867
|
+
builder: writeAuth.builder,
|
|
868
|
+
grantId: writeAuth.grantId,
|
|
869
|
+
error: err instanceof Error ? err.message : String(err),
|
|
870
|
+
}, "Builder write access-log entry failed; record already stored");
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
// Binary / unstructured data (e.g. a PDF): the body is raw bytes. DPv2
|
|
874
|
+
// data points are scope-addressed and carry no schemaId, so unstructured
|
|
875
|
+
// data needs no schema at all — we ingest it schemaless. (Structured JSON
|
|
876
|
+
// below still resolves a schema for validation/metadata.)
|
|
877
|
+
if (!isJsonContentType(request)) {
|
|
878
|
+
const bytes = new Uint8Array(await request.arrayBuffer());
|
|
879
|
+
const metadata = parseMetadataHeader(request.headers.get("x-vana-metadata"));
|
|
880
|
+
// A binary derivative names its sources inside the metadata
|
|
881
|
+
// object; the validated mirror lands at the top of the record.
|
|
882
|
+
const lineage = await prepareWriteLineage(deps, scopeResult.scope, extractLineageField(metadata));
|
|
883
|
+
const result = await ingestBinaryDataContract({
|
|
884
|
+
storage: deps.storage,
|
|
885
|
+
scopeParam: scopeResult.scope,
|
|
886
|
+
bytes,
|
|
887
|
+
mimeType: binaryMimeType(request),
|
|
888
|
+
filename: binaryFilename(request),
|
|
889
|
+
metadata,
|
|
890
|
+
collectedAt: collectedAtValue,
|
|
891
|
+
status,
|
|
892
|
+
attribution: writeAuth?.attribution,
|
|
893
|
+
lineage,
|
|
894
|
+
afterTombstoneVersion,
|
|
895
|
+
});
|
|
896
|
+
if (!result.ok)
|
|
897
|
+
return failWrite(contractErrorResponse(result));
|
|
898
|
+
committed = true;
|
|
899
|
+
deps.logger?.info?.({
|
|
900
|
+
scope: scopeResult.scope,
|
|
901
|
+
collectedAt: collectedAtValue,
|
|
902
|
+
path: result.writeResult.relativePath,
|
|
903
|
+
mimeType: binaryMimeType(request),
|
|
904
|
+
sizeBytes: bytes.length,
|
|
905
|
+
...(writeAuth ? { builder: writeAuth.builder } : {}),
|
|
906
|
+
}, "Binary data file ingested");
|
|
907
|
+
await logBuilderWrite();
|
|
908
|
+
notifyNewData(deps.syncManager);
|
|
909
|
+
notifyDataWritten(deps, {
|
|
910
|
+
scope: scopeResult.scope,
|
|
911
|
+
collectedAt: collectedAtValue,
|
|
912
|
+
lineageSources: lineage?.sources,
|
|
913
|
+
});
|
|
914
|
+
return jsonResponse(result.response, { status: 201 });
|
|
915
|
+
}
|
|
916
|
+
const parsed = await parseJsonObjectBody(request, "Request body must be valid JSON");
|
|
917
|
+
if (!parsed.ok)
|
|
918
|
+
return failWrite(contractResponse(parsed.result));
|
|
919
|
+
// Derivative writes: validate the caller's `lineage` before anything
|
|
920
|
+
// is stored (a failure here throws, releasing the builder's proof).
|
|
921
|
+
const lineage = await prepareWriteLineage(deps, scopeResult.scope, extractLineageField(parsed.body));
|
|
922
|
+
// DPv2 is scope-addressed and the gateway records no schemas, so JSON
|
|
923
|
+
// ingest is schemaless too — no lookup, no schemaId/$schema stamped.
|
|
924
|
+
const result = await ingestDataContract({
|
|
925
|
+
storage: deps.storage,
|
|
926
|
+
scopeParam: scopeResult.scope,
|
|
927
|
+
body: parsed.body,
|
|
928
|
+
collectedAt: collectedAtValue,
|
|
929
|
+
status,
|
|
930
|
+
attribution: writeAuth?.attribution,
|
|
931
|
+
lineage,
|
|
932
|
+
afterTombstoneVersion,
|
|
933
|
+
});
|
|
934
|
+
if (!result.ok)
|
|
935
|
+
return failWrite(contractErrorResponse(result));
|
|
936
|
+
committed = true;
|
|
937
|
+
deps.logger?.info?.({
|
|
938
|
+
scope: scopeResult.scope,
|
|
939
|
+
collectedAt: collectedAtValue,
|
|
940
|
+
path: result.writeResult.relativePath,
|
|
941
|
+
...(writeAuth ? { builder: writeAuth.builder } : {}),
|
|
942
|
+
}, "Data file ingested");
|
|
943
|
+
await logBuilderWrite();
|
|
944
|
+
notifyNewData(deps.syncManager);
|
|
945
|
+
notifyDataWritten(deps, {
|
|
946
|
+
scope: scopeResult.scope,
|
|
947
|
+
collectedAt: collectedAtValue,
|
|
948
|
+
lineageSources: lineage?.sources,
|
|
949
|
+
});
|
|
950
|
+
return jsonResponse(result.response, { status: 201 });
|
|
951
|
+
}
|
|
952
|
+
catch (err) {
|
|
953
|
+
// An envelope that reached storage before indexing failed is
|
|
954
|
+
// persisted (a re-index surfaces it): keep the proof consumed so a
|
|
955
|
+
// retry cannot store it twice; the builder needs a fresh proof.
|
|
956
|
+
if (err instanceof IngestPersistedError) {
|
|
957
|
+
deps.logger?.error?.({
|
|
958
|
+
scope: scopeParam,
|
|
959
|
+
path: err.relativePath,
|
|
960
|
+
error: err.cause instanceof Error
|
|
961
|
+
? err.cause.message
|
|
962
|
+
: String(err.cause),
|
|
963
|
+
}, "Envelope written but indexing failed; record persisted unindexed");
|
|
964
|
+
}
|
|
965
|
+
else if (!committed) {
|
|
966
|
+
await writeAuth?.releaseProof?.();
|
|
967
|
+
}
|
|
968
|
+
throw err;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
if (request.method === "DELETE") {
|
|
972
|
+
await deps.auth.authorizeOwner(request);
|
|
973
|
+
const cascade = url.searchParams.get("cascade");
|
|
974
|
+
if (cascade !== null && cascade !== "lineage") {
|
|
975
|
+
throw new InvalidCascadeError({ cascade });
|
|
976
|
+
}
|
|
977
|
+
// Parse the scope first so an invalid scope still 400s without a
|
|
978
|
+
// wasted remote call.
|
|
979
|
+
const parsed = parseDataScopeContract(scopeParam);
|
|
980
|
+
if (!parsed.ok)
|
|
981
|
+
return contractErrorResponse(parsed);
|
|
982
|
+
if (cascade === "lineage") {
|
|
983
|
+
// Specified (docs/derivative-data-api.md, "Delete") but not
|
|
984
|
+
// implemented here. The durable per-scope delete below is what a
|
|
985
|
+
// cascade node needs; the missing part is the lineage walk itself
|
|
986
|
+
// (owner view of the gateway graph, deepest derivatives first,
|
|
987
|
+
// truncation and cross-owner handling, all-or-nothing before the
|
|
988
|
+
// first tombstone). Until that lands the only honest answer is 501.
|
|
989
|
+
throw new LineageCascadeUnavailableError({ scope: parsed.scope });
|
|
990
|
+
}
|
|
991
|
+
// Durable order: gateway tombstone -> storage blobs -> local copy. The
|
|
992
|
+
// sync manager owns the remote ports; without one (sync disabled or
|
|
993
|
+
// no owner key material) only the local copy goes and the result says
|
|
994
|
+
// so (`durable: false`) rather than pretending.
|
|
995
|
+
const result = deps.syncManager?.deleteScope
|
|
996
|
+
? await deps.syncManager.deleteScope(parsed.scope)
|
|
997
|
+
: await deleteScopeLocally({
|
|
998
|
+
storage: deps.storage,
|
|
999
|
+
serverOwner: deps.serverOwner,
|
|
1000
|
+
deleteData: null,
|
|
1001
|
+
logger: apiLoggerAsLogger(deps.logger),
|
|
1002
|
+
}, parsed.scope);
|
|
1003
|
+
if (result.steps.gateway.status === "failed") {
|
|
1004
|
+
// Nothing was deleted: the local copy is kept on purpose so sync
|
|
1005
|
+
// cannot resurrect a half-deleted scope. Surface it as an upstream
|
|
1006
|
+
// failure with the per-step result attached.
|
|
1007
|
+
throw new DeleteTombstoneFailedError({
|
|
1008
|
+
scope: parsed.scope,
|
|
1009
|
+
result,
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
// Audit entry, best-effort: the deletion above is irreversible and
|
|
1013
|
+
// already done, so a failed log write must not turn it into an error
|
|
1014
|
+
// response that invites a misleading retry. The failure is logged.
|
|
1015
|
+
try {
|
|
1016
|
+
await deps.accessLogWriter.write({
|
|
1017
|
+
logId: deps.createLogId?.() ?? crypto.randomUUID(),
|
|
1018
|
+
grantId: "owner",
|
|
1019
|
+
builder: deps.serverOwner ?? "owner",
|
|
1020
|
+
action: "delete",
|
|
1021
|
+
scope: parsed.scope,
|
|
1022
|
+
timestamp: (deps.now ?? (() => new Date()))().toISOString(),
|
|
1023
|
+
ipAddress: request.headers.get("x-forwarded-for") ??
|
|
1024
|
+
request.headers.get("x-real-ip") ??
|
|
1025
|
+
"unknown",
|
|
1026
|
+
userAgent: request.headers.get("user-agent") ?? "unknown",
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
catch (err) {
|
|
1030
|
+
deps.logger?.warn?.({
|
|
1031
|
+
scope: parsed.scope,
|
|
1032
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1033
|
+
}, "Delete access-log entry failed; scope already deleted");
|
|
1034
|
+
}
|
|
1035
|
+
deps.logger?.info?.({
|
|
1036
|
+
scope: parsed.scope,
|
|
1037
|
+
durable: result.durable,
|
|
1038
|
+
gateway: result.steps.gateway.status,
|
|
1039
|
+
storage: result.steps.storage.status,
|
|
1040
|
+
local: result.steps.local.status,
|
|
1041
|
+
deletedCount: result.steps.local.deletedCount,
|
|
1042
|
+
}, "Scope deleted");
|
|
1043
|
+
return jsonResponse(result, { status: 200 });
|
|
1044
|
+
}
|
|
1045
|
+
return methodNotAllowed();
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
export async function handlePersonalServerAccessLogsRequest(request, deps, options = {}) {
|
|
1049
|
+
return withApiErrors(async () => {
|
|
1050
|
+
const url = new URL(request.url);
|
|
1051
|
+
const pathname = stripBasePath(url.pathname, options.basePath);
|
|
1052
|
+
if (pathname !== "/" && pathname !== "")
|
|
1053
|
+
return notFound();
|
|
1054
|
+
if (request.method !== "GET")
|
|
1055
|
+
return methodNotAllowed();
|
|
1056
|
+
await deps.auth.authorizeOwner(request);
|
|
1057
|
+
return contractResponse(await listAccessLogsContract({
|
|
1058
|
+
accessLogReader: deps.accessLogReader,
|
|
1059
|
+
limit: url.searchParams.get("limit"),
|
|
1060
|
+
offset: url.searchParams.get("offset"),
|
|
1061
|
+
}));
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
export async function handlePersonalServerSyncRequest(request, deps, options = {}) {
|
|
1065
|
+
return withApiErrors(async () => {
|
|
1066
|
+
const url = new URL(request.url);
|
|
1067
|
+
const pathname = stripBasePath(url.pathname, options.basePath);
|
|
1068
|
+
if (pathname === "/trigger") {
|
|
1069
|
+
if (request.method !== "POST")
|
|
1070
|
+
return methodNotAllowed();
|
|
1071
|
+
await deps.auth.authorizeOwner(request);
|
|
1072
|
+
return contractResponse(await triggerSyncContract(deps.syncManager));
|
|
1073
|
+
}
|
|
1074
|
+
if (pathname === "/status") {
|
|
1075
|
+
if (request.method !== "GET")
|
|
1076
|
+
return methodNotAllowed();
|
|
1077
|
+
await deps.auth.authorizeOwner(request);
|
|
1078
|
+
return contractResponse(getSyncStatusContract(deps.syncManager));
|
|
1079
|
+
}
|
|
1080
|
+
if (pathname.startsWith("/file/")) {
|
|
1081
|
+
if (request.method !== "POST")
|
|
1082
|
+
return methodNotAllowed();
|
|
1083
|
+
await deps.auth.authorizeOwner(request);
|
|
1084
|
+
const fileId = decodeURIComponent(pathname.slice("/file/".length));
|
|
1085
|
+
deps.logger?.info?.({ fileId }, "File sync requested, triggering full sync");
|
|
1086
|
+
return contractResponse(await syncFileContract({
|
|
1087
|
+
fileId,
|
|
1088
|
+
syncManager: deps.syncManager,
|
|
1089
|
+
}));
|
|
1090
|
+
}
|
|
1091
|
+
return notFound();
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
export async function handlePersonalServerGrantsRequest(request, deps, options = {}) {
|
|
1095
|
+
return withApiErrors(async () => {
|
|
1096
|
+
const url = new URL(request.url);
|
|
1097
|
+
const pathname = stripBasePath(url.pathname, options.basePath);
|
|
1098
|
+
if (pathname === "/" || pathname === "") {
|
|
1099
|
+
await deps.auth.authorizeOwner(request);
|
|
1100
|
+
if (!deps.gateway) {
|
|
1101
|
+
return errorResponse(500, "SERVER_NOT_CONFIGURED", "Gateway is not configured");
|
|
1102
|
+
}
|
|
1103
|
+
if (request.method === "GET") {
|
|
1104
|
+
return contractResponse(await listGrantsContract({
|
|
1105
|
+
gateway: deps.gateway,
|
|
1106
|
+
serverOwner: deps.serverOwner,
|
|
1107
|
+
}));
|
|
1108
|
+
}
|
|
1109
|
+
if (request.method === "POST") {
|
|
1110
|
+
const parsed = await parseJsonObjectBody(request);
|
|
1111
|
+
if (!parsed.ok)
|
|
1112
|
+
return contractResponse(parsed.result);
|
|
1113
|
+
// Canary createGrant doesn't need a clock — grantVersion defaults
|
|
1114
|
+
// to "1" and callers re-registering pass a strictly higher value.
|
|
1115
|
+
return contractResponse(await createGrantContract({
|
|
1116
|
+
gateway: deps.gateway,
|
|
1117
|
+
serverOwner: deps.serverOwner,
|
|
1118
|
+
serverSigner: deps.serverSigner,
|
|
1119
|
+
body: parsed.body,
|
|
1120
|
+
}));
|
|
1121
|
+
}
|
|
1122
|
+
return methodNotAllowed();
|
|
1123
|
+
}
|
|
1124
|
+
if (pathname === "/verify") {
|
|
1125
|
+
if (request.method !== "POST")
|
|
1126
|
+
return methodNotAllowed();
|
|
1127
|
+
const parsed = await parseJsonObjectBody(request);
|
|
1128
|
+
if (!parsed.ok)
|
|
1129
|
+
return contractResponse(parsed.result);
|
|
1130
|
+
return contractResponse(await verifyGrantContract({
|
|
1131
|
+
body: parsed.body,
|
|
1132
|
+
gatewayConfig: deps.gatewayConfig,
|
|
1133
|
+
}));
|
|
1134
|
+
}
|
|
1135
|
+
if (pathname.startsWith("/")) {
|
|
1136
|
+
if (request.method !== "DELETE")
|
|
1137
|
+
return methodNotAllowed();
|
|
1138
|
+
await deps.auth.authorizeOwner(request);
|
|
1139
|
+
if (!deps.gateway) {
|
|
1140
|
+
return errorResponse(500, "SERVER_NOT_CONFIGURED", "Gateway is not configured");
|
|
1141
|
+
}
|
|
1142
|
+
return contractResponse(await revokeGrantContract({
|
|
1143
|
+
gateway: deps.gateway,
|
|
1144
|
+
serverOwner: deps.serverOwner,
|
|
1145
|
+
serverSigner: deps.serverSigner,
|
|
1146
|
+
grantId: decodeURIComponent(pathname.slice(1)),
|
|
1147
|
+
}));
|
|
1148
|
+
}
|
|
1149
|
+
return notFound();
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
export async function handlePersonalServerConfigRequest(request, deps) {
|
|
1153
|
+
return withApiErrors(async () => {
|
|
1154
|
+
await deps.auth.authorizeOwner(request);
|
|
1155
|
+
if (request.method === "GET") {
|
|
1156
|
+
try {
|
|
1157
|
+
return jsonResponse(await deps.readConfig());
|
|
1158
|
+
}
|
|
1159
|
+
catch (err) {
|
|
1160
|
+
const kind = err instanceof Error &&
|
|
1161
|
+
"code" in err &&
|
|
1162
|
+
err.code === "ENOENT"
|
|
1163
|
+
? "not-found"
|
|
1164
|
+
: "read";
|
|
1165
|
+
return contractResponse(configReadErrorContract(kind));
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
if (request.method === "PUT") {
|
|
1169
|
+
const parsed = await parseJsonObjectBody(request);
|
|
1170
|
+
if (!parsed.ok)
|
|
1171
|
+
return contractResponse(parsed.result);
|
|
1172
|
+
const result = validateServerConfigContract(parsed.body);
|
|
1173
|
+
if (!result.ok)
|
|
1174
|
+
return contractResponse(result);
|
|
1175
|
+
try {
|
|
1176
|
+
await deps.writeConfig(result.body.config);
|
|
1177
|
+
}
|
|
1178
|
+
catch {
|
|
1179
|
+
return contractResponse(configWriteErrorContract());
|
|
1180
|
+
}
|
|
1181
|
+
return contractResponse(result);
|
|
1182
|
+
}
|
|
1183
|
+
return methodNotAllowed();
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
export async function handlePersonalServerOauthTokenRequest(request, deps) {
|
|
1187
|
+
return withApiErrors(async () => {
|
|
1188
|
+
if (request.method !== "POST")
|
|
1189
|
+
return methodNotAllowed();
|
|
1190
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
1191
|
+
if (!contentType.includes("application/x-www-form-urlencoded")) {
|
|
1192
|
+
return jsonResponse({
|
|
1193
|
+
error: "invalid_request",
|
|
1194
|
+
error_description: "Content-Type must be application/x-www-form-urlencoded",
|
|
1195
|
+
}, { status: 400 });
|
|
1196
|
+
}
|
|
1197
|
+
const result = await oauthTokenContract({
|
|
1198
|
+
body: new URLSearchParams(await request.text()),
|
|
1199
|
+
authorizationHeader: request.headers.get("authorization"),
|
|
1200
|
+
tokenStore: deps.tokenStore,
|
|
1201
|
+
controlPlaneSecret: deps.controlPlaneSecret,
|
|
1202
|
+
deviceSessions: deps.deviceSessions,
|
|
1203
|
+
randomToken: deps.randomToken,
|
|
1204
|
+
now: deps.now,
|
|
1205
|
+
safeCompare: deps.safeCompare,
|
|
1206
|
+
});
|
|
1207
|
+
return jsonResponse(result.body, {
|
|
1208
|
+
status: result.status,
|
|
1209
|
+
headers: result.headers,
|
|
1210
|
+
});
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
//# sourceMappingURL=index.js.map
|