was-teaching-server 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1432 -0
- package/LICENSE +661 -0
- package/README.md +305 -0
- package/common/css/materialize.1.0.0.min.css +13 -0
- package/dist/auth-header-hooks.d.ts +84 -0
- package/dist/auth-header-hooks.d.ts.map +1 -0
- package/dist/auth-header-hooks.js +136 -0
- package/dist/auth-header-hooks.js.map +1 -0
- package/dist/authorize.d.ts +45 -0
- package/dist/authorize.d.ts.map +1 -0
- package/dist/authorize.js +76 -0
- package/dist/authorize.js.map +1 -0
- package/dist/backends/filesystem.d.ts +1179 -0
- package/dist/backends/filesystem.d.ts.map +1 -0
- package/dist/backends/filesystem.js +2727 -0
- package/dist/backends/filesystem.js.map +1 -0
- package/dist/backends/postgres.d.ts +833 -0
- package/dist/backends/postgres.d.ts.map +1 -0
- package/dist/backends/postgres.js +2164 -0
- package/dist/backends/postgres.js.map +1 -0
- package/dist/backends/postgresSchema.d.ts +36 -0
- package/dist/backends/postgresSchema.d.ts.map +1 -0
- package/dist/backends/postgresSchema.js +203 -0
- package/dist/backends/postgresSchema.js.map +1 -0
- package/dist/config.default.d.ts +360 -0
- package/dist/config.default.d.ts.map +1 -0
- package/dist/config.default.js +493 -0
- package/dist/config.default.js.map +1 -0
- package/dist/corsProxy.d.ts +45 -0
- package/dist/corsProxy.d.ts.map +1 -0
- package/dist/corsProxy.js +278 -0
- package/dist/corsProxy.js.map +1 -0
- package/dist/digest.d.ts +45 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +174 -0
- package/dist/digest.js.map +1 -0
- package/dist/errors.d.ts +654 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +960 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/atomicFile.d.ts +61 -0
- package/dist/lib/atomicFile.d.ts.map +1 -0
- package/dist/lib/atomicFile.js +150 -0
- package/dist/lib/atomicFile.js.map +1 -0
- package/dist/lib/backendRegistry.d.ts +59 -0
- package/dist/lib/backendRegistry.d.ts.map +1 -0
- package/dist/lib/backendRegistry.js +92 -0
- package/dist/lib/backendRegistry.js.map +1 -0
- package/dist/lib/backendUsage.d.ts +28 -0
- package/dist/lib/backendUsage.d.ts.map +1 -0
- package/dist/lib/backendUsage.js +55 -0
- package/dist/lib/backendUsage.js.map +1 -0
- package/dist/lib/backends.d.ts +142 -0
- package/dist/lib/backends.d.ts.map +1 -0
- package/dist/lib/backends.js +272 -0
- package/dist/lib/backends.js.map +1 -0
- package/dist/lib/blindedIndex.d.ts +156 -0
- package/dist/lib/blindedIndex.d.ts.map +1 -0
- package/dist/lib/blindedIndex.js +338 -0
- package/dist/lib/blindedIndex.js.map +1 -0
- package/dist/lib/collectionDescription.d.ts +33 -0
- package/dist/lib/collectionDescription.d.ts.map +1 -0
- package/dist/lib/collectionDescription.js +21 -0
- package/dist/lib/collectionDescription.js.map +1 -0
- package/dist/lib/cursor.d.ts +18 -0
- package/dist/lib/cursor.d.ts.map +1 -0
- package/dist/lib/cursor.js +50 -0
- package/dist/lib/cursor.js.map +1 -0
- package/dist/lib/edvEnvelope.d.ts +50 -0
- package/dist/lib/edvEnvelope.d.ts.map +1 -0
- package/dist/lib/edvEnvelope.js +106 -0
- package/dist/lib/edvEnvelope.js.map +1 -0
- package/dist/lib/encryption.d.ts +192 -0
- package/dist/lib/encryption.d.ts.map +1 -0
- package/dist/lib/encryption.js +383 -0
- package/dist/lib/encryption.js.map +1 -0
- package/dist/lib/etag.d.ts +33 -0
- package/dist/lib/etag.d.ts.map +1 -0
- package/dist/lib/etag.js +36 -0
- package/dist/lib/etag.js.map +1 -0
- package/dist/lib/exportManifest.d.ts +29 -0
- package/dist/lib/exportManifest.d.ts.map +1 -0
- package/dist/lib/exportManifest.js +77 -0
- package/dist/lib/exportManifest.js.map +1 -0
- package/dist/lib/importTar.d.ts +81 -0
- package/dist/lib/importTar.d.ts.map +1 -0
- package/dist/lib/importTar.js +301 -0
- package/dist/lib/importTar.js.map +1 -0
- package/dist/lib/isJson.d.ts +16 -0
- package/dist/lib/isJson.d.ts.map +1 -0
- package/dist/lib/isJson.js +17 -0
- package/dist/lib/isJson.js.map +1 -0
- package/dist/lib/keyEpoch.d.ts +38 -0
- package/dist/lib/keyEpoch.d.ts.map +1 -0
- package/dist/lib/keyEpoch.js +75 -0
- package/dist/lib/keyEpoch.js.map +1 -0
- package/dist/lib/keyedMutex.d.ts +28 -0
- package/dist/lib/keyedMutex.d.ts.map +1 -0
- package/dist/lib/keyedMutex.js +45 -0
- package/dist/lib/keyedMutex.js.map +1 -0
- package/dist/lib/kmsModule.d.ts +62 -0
- package/dist/lib/kmsModule.d.ts.map +1 -0
- package/dist/lib/kmsModule.js +327 -0
- package/dist/lib/kmsModule.js.map +1 -0
- package/dist/lib/kmsRecordCipher.d.ts +82 -0
- package/dist/lib/kmsRecordCipher.d.ts.map +1 -0
- package/dist/lib/kmsRecordCipher.js +226 -0
- package/dist/lib/kmsRecordCipher.js.map +1 -0
- package/dist/lib/pagination.d.ts +19 -0
- package/dist/lib/pagination.d.ts.map +1 -0
- package/dist/lib/pagination.js +21 -0
- package/dist/lib/pagination.js.map +1 -0
- package/dist/lib/paths.d.ts +273 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +270 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/preconditions.d.ts +65 -0
- package/dist/lib/preconditions.d.ts.map +1 -0
- package/dist/lib/preconditions.js +112 -0
- package/dist/lib/preconditions.js.map +1 -0
- package/dist/lib/resourceFileName.d.ts +41 -0
- package/dist/lib/resourceFileName.d.ts.map +1 -0
- package/dist/lib/resourceFileName.js +58 -0
- package/dist/lib/resourceFileName.js.map +1 -0
- package/dist/lib/revocations.d.ts +49 -0
- package/dist/lib/revocations.d.ts.map +1 -0
- package/dist/lib/revocations.js +86 -0
- package/dist/lib/revocations.js.map +1 -0
- package/dist/lib/validateDid.d.ts +19 -0
- package/dist/lib/validateDid.d.ts.map +1 -0
- package/dist/lib/validateDid.js +35 -0
- package/dist/lib/validateDid.js.map +1 -0
- package/dist/lib/validateId.d.ts +66 -0
- package/dist/lib/validateId.d.ts.map +1 -0
- package/dist/lib/validateId.js +126 -0
- package/dist/lib/validateId.js.map +1 -0
- package/dist/plugin.d.ts +115 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +134 -0
- package/dist/plugin.js.map +1 -0
- package/dist/policy.d.ts +78 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +115 -0
- package/dist/policy.js.map +1 -0
- package/dist/provisioning.d.ts +38 -0
- package/dist/provisioning.d.ts.map +1 -0
- package/dist/provisioning.js +72 -0
- package/dist/provisioning.js.map +1 -0
- package/dist/requests/BackendRequest.d.ts +61 -0
- package/dist/requests/BackendRequest.d.ts.map +1 -0
- package/dist/requests/BackendRequest.js +152 -0
- package/dist/requests/BackendRequest.js.map +1 -0
- package/dist/requests/CollectionRequest.d.ts +234 -0
- package/dist/requests/CollectionRequest.d.ts.map +1 -0
- package/dist/requests/CollectionRequest.js +705 -0
- package/dist/requests/CollectionRequest.js.map +1 -0
- package/dist/requests/KeyRequest.d.ts +115 -0
- package/dist/requests/KeyRequest.d.ts.map +1 -0
- package/dist/requests/KeyRequest.js +468 -0
- package/dist/requests/KeyRequest.js.map +1 -0
- package/dist/requests/KeystoreRequest.d.ts +99 -0
- package/dist/requests/KeystoreRequest.d.ts.map +1 -0
- package/dist/requests/KeystoreRequest.js +247 -0
- package/dist/requests/KeystoreRequest.js.map +1 -0
- package/dist/requests/PolicyRequest.d.ts +53 -0
- package/dist/requests/PolicyRequest.d.ts.map +1 -0
- package/dist/requests/PolicyRequest.js +124 -0
- package/dist/requests/PolicyRequest.js.map +1 -0
- package/dist/requests/ResourceRequest.d.ts +134 -0
- package/dist/requests/ResourceRequest.d.ts.map +1 -0
- package/dist/requests/ResourceRequest.js +548 -0
- package/dist/requests/ResourceRequest.js.map +1 -0
- package/dist/requests/RevocationRequest.d.ts +67 -0
- package/dist/requests/RevocationRequest.d.ts.map +1 -0
- package/dist/requests/RevocationRequest.js +187 -0
- package/dist/requests/RevocationRequest.js.map +1 -0
- package/dist/requests/SpaceRequest.d.ts +204 -0
- package/dist/requests/SpaceRequest.d.ts.map +1 -0
- package/dist/requests/SpaceRequest.js +520 -0
- package/dist/requests/SpaceRequest.js.map +1 -0
- package/dist/requests/SpacesRepositoryRequest.d.ts +50 -0
- package/dist/requests/SpacesRepositoryRequest.d.ts.map +1 -0
- package/dist/requests/SpacesRepositoryRequest.js +159 -0
- package/dist/requests/SpacesRepositoryRequest.js.map +1 -0
- package/dist/requests/collectionContext.d.ts +21 -0
- package/dist/requests/collectionContext.d.ts.map +1 -0
- package/dist/requests/collectionContext.js +30 -0
- package/dist/requests/collectionContext.js.map +1 -0
- package/dist/requests/controllerConsent.d.ts +58 -0
- package/dist/requests/controllerConsent.d.ts.map +1 -0
- package/dist/requests/controllerConsent.js +73 -0
- package/dist/requests/controllerConsent.js.map +1 -0
- package/dist/requests/keystoreContext.d.ts +68 -0
- package/dist/requests/keystoreContext.d.ts.map +1 -0
- package/dist/requests/keystoreContext.js +87 -0
- package/dist/requests/keystoreContext.js.map +1 -0
- package/dist/requests/resourceInput.d.ts +19 -0
- package/dist/requests/resourceInput.d.ts.map +1 -0
- package/dist/requests/resourceInput.js +102 -0
- package/dist/requests/resourceInput.js.map +1 -0
- package/dist/requests/spaceContext.d.ts +127 -0
- package/dist/requests/spaceContext.d.ts.map +1 -0
- package/dist/requests/spaceContext.js +201 -0
- package/dist/requests/spaceContext.js.map +1 -0
- package/dist/routes.d.ts +55 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +306 -0
- package/dist/routes.js.map +1 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +60 -0
- package/dist/server.js.map +1 -0
- package/dist/start.d.ts +7 -0
- package/dist/start.d.ts.map +1 -0
- package/dist/start.js +61 -0
- package/dist/start.js.map +1 -0
- package/dist/storage.d.ts +29 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +47 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +915 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/views/home.hbs +9 -0
- package/dist/views/templates/main.hbs +12 -0
- package/dist/zcap.d.ts +267 -0
- package/dist/zcap.d.ts.map +1 -0
- package/dist/zcap.js +478 -0
- package/dist/zcap.js.map +1 -0
- package/package.json +109 -0
- package/src/auth-header-hooks.ts +163 -0
- package/src/authorize.ts +117 -0
- package/src/backends/filesystem.ts +3626 -0
- package/src/backends/postgres.ts +3073 -0
- package/src/backends/postgresSchema.ts +231 -0
- package/src/config.default.ts +657 -0
- package/src/corsProxy.ts +326 -0
- package/src/declarations.d.ts +28 -0
- package/src/digest.ts +223 -0
- package/src/errors.ts +1122 -0
- package/src/index.ts +21 -0
- package/src/lib/atomicFile.ts +176 -0
- package/src/lib/backendRegistry.ts +139 -0
- package/src/lib/backendUsage.ts +79 -0
- package/src/lib/backends.ts +351 -0
- package/src/lib/blindedIndex.ts +466 -0
- package/src/lib/collectionDescription.ts +43 -0
- package/src/lib/cursor.ts +52 -0
- package/src/lib/edvEnvelope.ts +120 -0
- package/src/lib/encryption.ts +472 -0
- package/src/lib/etag.ts +40 -0
- package/src/lib/exportManifest.ts +115 -0
- package/src/lib/importTar.ts +381 -0
- package/src/lib/isJson.ts +18 -0
- package/src/lib/keyEpoch.ts +89 -0
- package/src/lib/keyedMutex.ts +45 -0
- package/src/lib/kmsModule.ts +451 -0
- package/src/lib/kmsRecordCipher.ts +292 -0
- package/src/lib/pagination.ts +22 -0
- package/src/lib/paths.ts +382 -0
- package/src/lib/preconditions.ts +145 -0
- package/src/lib/resourceFileName.ts +69 -0
- package/src/lib/revocations.ts +116 -0
- package/src/lib/validateDid.ts +41 -0
- package/src/lib/validateId.ts +148 -0
- package/src/plugin.ts +288 -0
- package/src/policy.ts +176 -0
- package/src/provisioning.ts +98 -0
- package/src/requests/BackendRequest.ts +219 -0
- package/src/requests/CollectionRequest.ts +899 -0
- package/src/requests/KeyRequest.ts +617 -0
- package/src/requests/KeystoreRequest.ts +325 -0
- package/src/requests/PolicyRequest.ts +169 -0
- package/src/requests/ResourceRequest.ts +649 -0
- package/src/requests/RevocationRequest.ts +267 -0
- package/src/requests/SpaceRequest.ts +686 -0
- package/src/requests/SpacesRepositoryRequest.ts +194 -0
- package/src/requests/collectionContext.ts +41 -0
- package/src/requests/controllerConsent.ts +108 -0
- package/src/requests/keystoreContext.ts +124 -0
- package/src/requests/resourceInput.ts +118 -0
- package/src/requests/spaceContext.ts +311 -0
- package/src/routes.ts +420 -0
- package/src/server.ts +69 -0
- package/src/start.ts +71 -0
- package/src/storage.ts +60 -0
- package/src/types.ts +1016 -0
- package/src/views/home.hbs +9 -0
- package/src/views/templates/main.hbs +12 -0
- package/src/zcap.ts +657 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection backend selection helpers (spec "Backends" / "Collection Backend
|
|
3
|
+
* Selected"). A Collection carries an optional `backend` object whose `id` MUST
|
|
4
|
+
* name one of the Space's backends-available; this module centralizes the three
|
|
5
|
+
* operations that need that list: enumerating the available backends, validating
|
|
6
|
+
* (and default-filling) a client-supplied `backend` on create/update, and
|
|
7
|
+
* resolving a stored `{ id }` to its full descriptor for `GET .../backend`.
|
|
8
|
+
*
|
|
9
|
+
* This reference server ships a single server-configured backend (the active
|
|
10
|
+
* `StorageBackend`, registered as `default`), so the available list has one
|
|
11
|
+
* entry; the indirection leaves room for multiple registered backends later.
|
|
12
|
+
*/
|
|
13
|
+
import type { BackendDescriptor, BackendRegistration, CollectionDescription, StorageBackend, StoredBackendRecord } from '../types.js';
|
|
14
|
+
/** The conventional id of the server-assigned default backend (spec). */
|
|
15
|
+
export declare const DEFAULT_BACKEND_ID = "default";
|
|
16
|
+
/**
|
|
17
|
+
* Validates a client-supplied Collection `backend` value and returns the
|
|
18
|
+
* normalized `{ id }` to persist. An absent value defaults to the server's
|
|
19
|
+
* default backend. A present-but-malformed value (not an object, or no string
|
|
20
|
+
* `id`) is `invalid-request-body` (400); a well-formed value whose `id` is not
|
|
21
|
+
* in the Space's backends-available (the server `default` plus its registered
|
|
22
|
+
* `external` backends) is `unsupported-backend` (409). A registered `external`
|
|
23
|
+
* backend is now selectable here -- routing its data plane to the resolved
|
|
24
|
+
* adapter is the resolver's job (lib/backendRegistry.ts).
|
|
25
|
+
*
|
|
26
|
+
* @param options {object}
|
|
27
|
+
* @param options.storage {StorageBackend} the request's storage backend
|
|
28
|
+
* @param options.spaceId {string} the Space whose backends-available is checked
|
|
29
|
+
* @param [options.backend] {unknown} the request body's `backend` value
|
|
30
|
+
* @param [options.requestName] {string} request name for the 400 error title
|
|
31
|
+
* @returns {Promise<{ id: string }>} the normalized backend reference to store
|
|
32
|
+
*/
|
|
33
|
+
export declare function assertSupportedBackend({ storage, spaceId, backend, requestName }: {
|
|
34
|
+
storage: StorageBackend;
|
|
35
|
+
spaceId: string;
|
|
36
|
+
backend?: unknown;
|
|
37
|
+
requestName?: string;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
id: string;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Resolves a Collection's selected backend to its full descriptor, for the
|
|
43
|
+
* `GET .../backend` ("Collection Backend Selected") response, consulting the
|
|
44
|
+
* Space's backends-available (server `default` plus registered `external`
|
|
45
|
+
* records). A stored description without a `backend` (created before the property
|
|
46
|
+
* existed) defaults to the server's default backend.
|
|
47
|
+
*
|
|
48
|
+
* @param options {object}
|
|
49
|
+
* @param options.storage {StorageBackend} the request's storage backend
|
|
50
|
+
* @param options.spaceId {string} the Space whose backends-available is checked
|
|
51
|
+
* @param options.collectionDescription {CollectionDescription}
|
|
52
|
+
* @returns {Promise<BackendDescriptor>}
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveBackendDescriptor({ storage, spaceId, collectionDescription }: {
|
|
55
|
+
storage: StorageBackend;
|
|
56
|
+
spaceId: string;
|
|
57
|
+
collectionDescription: CollectionDescription;
|
|
58
|
+
}): Promise<BackendDescriptor>;
|
|
59
|
+
/**
|
|
60
|
+
* Projects a full (secret-bearing) `StoredBackendRecord` down to the sanitized
|
|
61
|
+
* `BackendDescriptor` that every client-facing read path returns: the public
|
|
62
|
+
* descriptor fields plus a `connection` reduced to its public subset (no
|
|
63
|
+
* `authorizationCode` / `refreshToken` / etc.). The single projection shared by
|
|
64
|
+
* `FileSystemBackend.listBackends` and the register (`POST`/`PUT`) responses, so
|
|
65
|
+
* the secret material has exactly one place it could leak -- and does not.
|
|
66
|
+
* @param record {StoredBackendRecord}
|
|
67
|
+
* @returns {BackendDescriptor}
|
|
68
|
+
*/
|
|
69
|
+
export declare function sanitizeBackendRecord(record: StoredBackendRecord): BackendDescriptor;
|
|
70
|
+
/**
|
|
71
|
+
* The backends advertised at `GET /space/:spaceId/backends`: the server's
|
|
72
|
+
* `default` backend first, followed by the Space's sanitized registered
|
|
73
|
+
* `external` backends. Also the single source of a Space's selectable backends
|
|
74
|
+
* for `assertSupportedBackend` / `resolveBackendDescriptor`.
|
|
75
|
+
* @param options {object}
|
|
76
|
+
* @param options.storage {StorageBackend}
|
|
77
|
+
* @param options.spaceId {string}
|
|
78
|
+
* @returns {Promise<BackendDescriptor[]>}
|
|
79
|
+
*/
|
|
80
|
+
export declare function listRegisteredBackends({ storage, spaceId }: {
|
|
81
|
+
storage: StorageBackend;
|
|
82
|
+
spaceId: string;
|
|
83
|
+
}): Promise<BackendDescriptor[]>;
|
|
84
|
+
/**
|
|
85
|
+
* Validates a backend-registration request body and returns the typed
|
|
86
|
+
* `BackendRegistration`. Enforces: an object body; a string `id`; a non-empty
|
|
87
|
+
* string `provider`; `managedBy` absent or `'external'` (a client may not
|
|
88
|
+
* register a `server` backend); and a `connection` object with a string `kind`.
|
|
89
|
+
* Throws `InvalidRequestBodyError` (400) with the offending `#/...` pointer.
|
|
90
|
+
* Does not check `id` URL-safety -- callers run `assertValidBackendId`.
|
|
91
|
+
* @param body {unknown} the parsed request body
|
|
92
|
+
* @param options {object}
|
|
93
|
+
* @param [options.requestName] {string} request name for the 400 error title
|
|
94
|
+
* @returns {BackendRegistration}
|
|
95
|
+
*/
|
|
96
|
+
export declare function parseBackendRegistration(body: unknown, { requestName }?: {
|
|
97
|
+
requestName?: string;
|
|
98
|
+
}): BackendRegistration;
|
|
99
|
+
/**
|
|
100
|
+
* Enforces the optional server-wide registration allowlist (config
|
|
101
|
+
* `WAS_ENABLED_BACKENDS`): the set of backend `provider` names a client may
|
|
102
|
+
* register. An `undefined` allowlist is **permissive** -- any provider passes,
|
|
103
|
+
* preserving the prior behavior where any provider could be registered. When the
|
|
104
|
+
* allowlist is configured, a `provider` outside it is rejected with
|
|
105
|
+
* `unsupported-backend` (409) pointing at `#/provider`. This is the fail-fast
|
|
106
|
+
* registration gate; the resolver's data-plane factory check
|
|
107
|
+
* (lib/backendRegistry.ts) remains the backstop for a provider with no live
|
|
108
|
+
* adapter.
|
|
109
|
+
* @param options {object}
|
|
110
|
+
* @param options.provider {string} the registration's `provider`
|
|
111
|
+
* @param [options.enabledProviders] {string[]} the configured allowlist, or
|
|
112
|
+
* `undefined` for permissive
|
|
113
|
+
* @returns {void}
|
|
114
|
+
*/
|
|
115
|
+
export declare function assertProviderAllowed({ provider, enabledProviders }: {
|
|
116
|
+
provider: string;
|
|
117
|
+
enabledProviders?: string[];
|
|
118
|
+
}): void;
|
|
119
|
+
/**
|
|
120
|
+
* Asserts a registered-backend id is a single URL-safe path segment (it becomes
|
|
121
|
+
* both a URL segment and a `.backend.<id>.json` filename). A backend id is a
|
|
122
|
+
* body field, so a failure is `invalid-request-body` (400) pointing at `#/id`
|
|
123
|
+
* rather than a URL-param id error.
|
|
124
|
+
* @param id {string}
|
|
125
|
+
* @param options {object}
|
|
126
|
+
* @param [options.requestName] {string} request name for the 400 error title
|
|
127
|
+
* @returns {void}
|
|
128
|
+
*/
|
|
129
|
+
export declare function assertValidBackendId(id: string, { requestName }?: {
|
|
130
|
+
requestName?: string;
|
|
131
|
+
}): void;
|
|
132
|
+
/**
|
|
133
|
+
* Assembles the full `StoredBackendRecord` to persist from a validated
|
|
134
|
+
* `BackendRegistration`: the descriptor fields, `managedBy: 'external'`, default
|
|
135
|
+
* `storageMode` / `features`, and the full (secret-bearing) connection stamped
|
|
136
|
+
* with `status: 'registered'` and the registration timestamp. The record is
|
|
137
|
+
* inert until the live provider adapter (future work) connects it.
|
|
138
|
+
* @param registration {BackendRegistration}
|
|
139
|
+
* @returns {StoredBackendRecord}
|
|
140
|
+
*/
|
|
141
|
+
export declare function buildBackendRecord(registration: BackendRegistration): StoredBackendRecord;
|
|
142
|
+
//# sourceMappingURL=backends.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backends.d.ts","sourceRoot":"","sources":["../../src/lib/backends.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EACV,iBAAiB,EAEjB,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACpB,MAAM,aAAa,CAAA;AAIpB,yEAAyE;AACzE,eAAO,MAAM,kBAAkB,YAAY,CAAA;AAgB3C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,sBAAsB,CAAC,EAC3C,OAAO,EACP,OAAO,EACP,OAAO,EACP,WAAW,EACZ,EAAE;IACD,OAAO,EAAE,cAAc,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAqB1B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAAC,EAC7C,OAAO,EACP,OAAO,EACP,qBAAqB,EACtB,EAAE;IACD,OAAO,EAAE,cAAc,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,qBAAqB,EAAE,qBAAqB,CAAA;CAC7C,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAO7B;AAWD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,mBAAmB,GAC1B,iBAAiB,CA2BnB;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAAC,EAC3C,OAAO,EACP,OAAO,EACR,EAAE;IACD,OAAO,EAAE,cAAc,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAE/B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,OAAO,EACb,EAAE,WAAW,EAAE,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7C,mBAAmB,CA2DrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,QAAQ,EACR,gBAAgB,EACjB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC5B,GAAG,IAAI,CAWP;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,MAAM,EACV,EAAE,WAAW,EAAE,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7C,IAAI,CAQN;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,mBAAmB,GAChC,mBAAmB,CAerB"}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { InvalidRequestBodyError, UnsupportedBackendError } from '../errors.js';
|
|
2
|
+
import { isUrlSafeSegment } from './validateId.js';
|
|
3
|
+
/** The conventional id of the server-assigned default backend (spec). */
|
|
4
|
+
export const DEFAULT_BACKEND_ID = 'default';
|
|
5
|
+
// `listRegisteredBackends` (defined below, with the registration helpers) is the
|
|
6
|
+
// single source of a Space's selectable backends -- the server `default` plus
|
|
7
|
+
// its registered `external` records -- used by both the selection-validation
|
|
8
|
+
// (`assertSupportedBackend`) and descriptor-resolution (`resolveBackendDescriptor`)
|
|
9
|
+
// paths below.
|
|
10
|
+
/** The public (secret-free) subset of a connection a sanitized read may carry. */
|
|
11
|
+
const PUBLIC_CONNECTION_FIELDS = [
|
|
12
|
+
'account',
|
|
13
|
+
'scope',
|
|
14
|
+
'connectedAt',
|
|
15
|
+
'rootFolderName'
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Validates a client-supplied Collection `backend` value and returns the
|
|
19
|
+
* normalized `{ id }` to persist. An absent value defaults to the server's
|
|
20
|
+
* default backend. A present-but-malformed value (not an object, or no string
|
|
21
|
+
* `id`) is `invalid-request-body` (400); a well-formed value whose `id` is not
|
|
22
|
+
* in the Space's backends-available (the server `default` plus its registered
|
|
23
|
+
* `external` backends) is `unsupported-backend` (409). A registered `external`
|
|
24
|
+
* backend is now selectable here -- routing its data plane to the resolved
|
|
25
|
+
* adapter is the resolver's job (lib/backendRegistry.ts).
|
|
26
|
+
*
|
|
27
|
+
* @param options {object}
|
|
28
|
+
* @param options.storage {StorageBackend} the request's storage backend
|
|
29
|
+
* @param options.spaceId {string} the Space whose backends-available is checked
|
|
30
|
+
* @param [options.backend] {unknown} the request body's `backend` value
|
|
31
|
+
* @param [options.requestName] {string} request name for the 400 error title
|
|
32
|
+
* @returns {Promise<{ id: string }>} the normalized backend reference to store
|
|
33
|
+
*/
|
|
34
|
+
export async function assertSupportedBackend({ storage, spaceId, backend, requestName }) {
|
|
35
|
+
if (backend === undefined) {
|
|
36
|
+
return { id: DEFAULT_BACKEND_ID };
|
|
37
|
+
}
|
|
38
|
+
if (typeof backend !== 'object' ||
|
|
39
|
+
backend === null ||
|
|
40
|
+
typeof backend.id !== 'string') {
|
|
41
|
+
throw new InvalidRequestBodyError({
|
|
42
|
+
requestName,
|
|
43
|
+
detail: 'Collection "backend" must be an object with a string "id".',
|
|
44
|
+
pointer: '#/backend'
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const { id } = backend;
|
|
48
|
+
const available = await listRegisteredBackends({ storage, spaceId });
|
|
49
|
+
if (!available.some(entry => entry.id === id)) {
|
|
50
|
+
throw new UnsupportedBackendError({ backendId: id });
|
|
51
|
+
}
|
|
52
|
+
return { id };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolves a Collection's selected backend to its full descriptor, for the
|
|
56
|
+
* `GET .../backend` ("Collection Backend Selected") response, consulting the
|
|
57
|
+
* Space's backends-available (server `default` plus registered `external`
|
|
58
|
+
* records). A stored description without a `backend` (created before the property
|
|
59
|
+
* existed) defaults to the server's default backend.
|
|
60
|
+
*
|
|
61
|
+
* @param options {object}
|
|
62
|
+
* @param options.storage {StorageBackend} the request's storage backend
|
|
63
|
+
* @param options.spaceId {string} the Space whose backends-available is checked
|
|
64
|
+
* @param options.collectionDescription {CollectionDescription}
|
|
65
|
+
* @returns {Promise<BackendDescriptor>}
|
|
66
|
+
*/
|
|
67
|
+
export async function resolveBackendDescriptor({ storage, spaceId, collectionDescription }) {
|
|
68
|
+
const id = collectionDescription.backend?.id ?? DEFAULT_BACKEND_ID;
|
|
69
|
+
const available = await listRegisteredBackends({ storage, spaceId });
|
|
70
|
+
const descriptor = available.find(entry => entry.id === id);
|
|
71
|
+
// A stored backend id should always resolve (it was validated on write); fall
|
|
72
|
+
// back to the default descriptor defensively rather than returning undefined.
|
|
73
|
+
return descriptor ?? storage.describe();
|
|
74
|
+
}
|
|
75
|
+
// --- External backend registration (spec "Backends") ---
|
|
76
|
+
//
|
|
77
|
+
// A registered `external` backend is both listed (below) and now *selectable* as
|
|
78
|
+
// a Collection's `backend` (`assertSupportedBackend` / `resolveBackendDescriptor`
|
|
79
|
+
// above consult `listRegisteredBackends`). Routing the selected Collection's data
|
|
80
|
+
// plane to the live provider adapter is the resolver's job
|
|
81
|
+
// (lib/backendRegistry.ts); a selected backend with no registered provider
|
|
82
|
+
// adapter fails closed there.
|
|
83
|
+
/**
|
|
84
|
+
* Projects a full (secret-bearing) `StoredBackendRecord` down to the sanitized
|
|
85
|
+
* `BackendDescriptor` that every client-facing read path returns: the public
|
|
86
|
+
* descriptor fields plus a `connection` reduced to its public subset (no
|
|
87
|
+
* `authorizationCode` / `refreshToken` / etc.). The single projection shared by
|
|
88
|
+
* `FileSystemBackend.listBackends` and the register (`POST`/`PUT`) responses, so
|
|
89
|
+
* the secret material has exactly one place it could leak -- and does not.
|
|
90
|
+
* @param record {StoredBackendRecord}
|
|
91
|
+
* @returns {BackendDescriptor}
|
|
92
|
+
*/
|
|
93
|
+
export function sanitizeBackendRecord(record) {
|
|
94
|
+
const { connection } = record;
|
|
95
|
+
const publicConnection = {
|
|
96
|
+
kind: connection.kind,
|
|
97
|
+
status: connection.status ?? 'registered'
|
|
98
|
+
};
|
|
99
|
+
for (const field of PUBLIC_CONNECTION_FIELDS) {
|
|
100
|
+
const value = connection[field];
|
|
101
|
+
if (typeof value === 'string') {
|
|
102
|
+
publicConnection[field] = value;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
id: record.id,
|
|
107
|
+
...(record.name !== undefined && { name: record.name }),
|
|
108
|
+
managedBy: record.managedBy,
|
|
109
|
+
...(record.storageMode !== undefined && {
|
|
110
|
+
storageMode: record.storageMode
|
|
111
|
+
}),
|
|
112
|
+
...(record.persistence !== undefined && {
|
|
113
|
+
persistence: record.persistence
|
|
114
|
+
}),
|
|
115
|
+
...(record.features !== undefined && { features: record.features }),
|
|
116
|
+
provider: record.provider,
|
|
117
|
+
connection: publicConnection
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The backends advertised at `GET /space/:spaceId/backends`: the server's
|
|
122
|
+
* `default` backend first, followed by the Space's sanitized registered
|
|
123
|
+
* `external` backends. Also the single source of a Space's selectable backends
|
|
124
|
+
* for `assertSupportedBackend` / `resolveBackendDescriptor`.
|
|
125
|
+
* @param options {object}
|
|
126
|
+
* @param options.storage {StorageBackend}
|
|
127
|
+
* @param options.spaceId {string}
|
|
128
|
+
* @returns {Promise<BackendDescriptor[]>}
|
|
129
|
+
*/
|
|
130
|
+
export async function listRegisteredBackends({ storage, spaceId }) {
|
|
131
|
+
return [storage.describe(), ...(await storage.listBackends({ spaceId }))];
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Validates a backend-registration request body and returns the typed
|
|
135
|
+
* `BackendRegistration`. Enforces: an object body; a string `id`; a non-empty
|
|
136
|
+
* string `provider`; `managedBy` absent or `'external'` (a client may not
|
|
137
|
+
* register a `server` backend); and a `connection` object with a string `kind`.
|
|
138
|
+
* Throws `InvalidRequestBodyError` (400) with the offending `#/...` pointer.
|
|
139
|
+
* Does not check `id` URL-safety -- callers run `assertValidBackendId`.
|
|
140
|
+
* @param body {unknown} the parsed request body
|
|
141
|
+
* @param options {object}
|
|
142
|
+
* @param [options.requestName] {string} request name for the 400 error title
|
|
143
|
+
* @returns {BackendRegistration}
|
|
144
|
+
*/
|
|
145
|
+
export function parseBackendRegistration(body, { requestName } = {}) {
|
|
146
|
+
if (typeof body !== 'object' || body === null) {
|
|
147
|
+
throw new InvalidRequestBodyError({
|
|
148
|
+
requestName,
|
|
149
|
+
detail: 'Backend registration body must be a JSON object.',
|
|
150
|
+
pointer: '#'
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const candidate = body;
|
|
154
|
+
if (typeof candidate.id !== 'string') {
|
|
155
|
+
throw new InvalidRequestBodyError({
|
|
156
|
+
requestName,
|
|
157
|
+
detail: 'Backend registration requires a string "id".',
|
|
158
|
+
pointer: '#/id'
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (typeof candidate.provider !== 'string' ||
|
|
162
|
+
candidate.provider.length === 0) {
|
|
163
|
+
throw new InvalidRequestBodyError({
|
|
164
|
+
requestName,
|
|
165
|
+
detail: 'Backend registration requires a non-empty string "provider".',
|
|
166
|
+
pointer: '#/provider'
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (candidate.managedBy !== undefined && candidate.managedBy !== 'external') {
|
|
170
|
+
throw new InvalidRequestBodyError({
|
|
171
|
+
requestName,
|
|
172
|
+
detail: 'Backend "managedBy" must be "external"; server backends are not client-registered.',
|
|
173
|
+
pointer: '#/managedBy'
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const connection = candidate.connection;
|
|
177
|
+
if (typeof connection !== 'object' ||
|
|
178
|
+
connection === null ||
|
|
179
|
+
typeof connection.kind !== 'string') {
|
|
180
|
+
throw new InvalidRequestBodyError({
|
|
181
|
+
requestName,
|
|
182
|
+
detail: 'Backend "connection" must be an object with a string "kind".',
|
|
183
|
+
pointer: '#/connection'
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
id: candidate.id,
|
|
188
|
+
...(typeof candidate.name === 'string' && { name: candidate.name }),
|
|
189
|
+
managedBy: 'external',
|
|
190
|
+
provider: candidate.provider,
|
|
191
|
+
...(Array.isArray(candidate.storageMode) && {
|
|
192
|
+
storageMode: candidate.storageMode
|
|
193
|
+
}),
|
|
194
|
+
...(Array.isArray(candidate.features) && {
|
|
195
|
+
features: candidate.features
|
|
196
|
+
}),
|
|
197
|
+
connection: connection
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Enforces the optional server-wide registration allowlist (config
|
|
202
|
+
* `WAS_ENABLED_BACKENDS`): the set of backend `provider` names a client may
|
|
203
|
+
* register. An `undefined` allowlist is **permissive** -- any provider passes,
|
|
204
|
+
* preserving the prior behavior where any provider could be registered. When the
|
|
205
|
+
* allowlist is configured, a `provider` outside it is rejected with
|
|
206
|
+
* `unsupported-backend` (409) pointing at `#/provider`. This is the fail-fast
|
|
207
|
+
* registration gate; the resolver's data-plane factory check
|
|
208
|
+
* (lib/backendRegistry.ts) remains the backstop for a provider with no live
|
|
209
|
+
* adapter.
|
|
210
|
+
* @param options {object}
|
|
211
|
+
* @param options.provider {string} the registration's `provider`
|
|
212
|
+
* @param [options.enabledProviders] {string[]} the configured allowlist, or
|
|
213
|
+
* `undefined` for permissive
|
|
214
|
+
* @returns {void}
|
|
215
|
+
*/
|
|
216
|
+
export function assertProviderAllowed({ provider, enabledProviders }) {
|
|
217
|
+
if (enabledProviders === undefined) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (!enabledProviders.includes(provider)) {
|
|
221
|
+
throw new UnsupportedBackendError({
|
|
222
|
+
backendId: provider,
|
|
223
|
+
detail: `Backend provider '${provider}' is not enabled on this server.`,
|
|
224
|
+
pointer: '#/provider'
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Asserts a registered-backend id is a single URL-safe path segment (it becomes
|
|
230
|
+
* both a URL segment and a `.backend.<id>.json` filename). A backend id is a
|
|
231
|
+
* body field, so a failure is `invalid-request-body` (400) pointing at `#/id`
|
|
232
|
+
* rather than a URL-param id error.
|
|
233
|
+
* @param id {string}
|
|
234
|
+
* @param options {object}
|
|
235
|
+
* @param [options.requestName] {string} request name for the 400 error title
|
|
236
|
+
* @returns {void}
|
|
237
|
+
*/
|
|
238
|
+
export function assertValidBackendId(id, { requestName } = {}) {
|
|
239
|
+
if (!isUrlSafeSegment(id)) {
|
|
240
|
+
throw new InvalidRequestBodyError({
|
|
241
|
+
requestName,
|
|
242
|
+
detail: 'Backend "id" must be a single, URL-safe path segment.',
|
|
243
|
+
pointer: '#/id'
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Assembles the full `StoredBackendRecord` to persist from a validated
|
|
249
|
+
* `BackendRegistration`: the descriptor fields, `managedBy: 'external'`, default
|
|
250
|
+
* `storageMode` / `features`, and the full (secret-bearing) connection stamped
|
|
251
|
+
* with `status: 'registered'` and the registration timestamp. The record is
|
|
252
|
+
* inert until the live provider adapter (future work) connects it.
|
|
253
|
+
* @param registration {BackendRegistration}
|
|
254
|
+
* @returns {StoredBackendRecord}
|
|
255
|
+
*/
|
|
256
|
+
export function buildBackendRecord(registration) {
|
|
257
|
+
const { id, name, provider, storageMode, features, connection } = registration;
|
|
258
|
+
return {
|
|
259
|
+
id,
|
|
260
|
+
...(name !== undefined && { name }),
|
|
261
|
+
managedBy: 'external',
|
|
262
|
+
provider,
|
|
263
|
+
storageMode: storageMode ?? ['document', 'blob'],
|
|
264
|
+
features: features ?? [],
|
|
265
|
+
connection: {
|
|
266
|
+
...connection,
|
|
267
|
+
status: 'registered',
|
|
268
|
+
connectedAt: new Date().toISOString()
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=backends.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backends.js","sourceRoot":"","sources":["../../src/lib/backends.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A validated blinded-index query: the HMAC key id naming which blinded index
|
|
3
|
+
* to search, and exactly one of `equals` / `has`. All names and values are
|
|
4
|
+
* opaque blinded (base64url) strings.
|
|
5
|
+
*/
|
|
6
|
+
export interface BlindedIndexQuery {
|
|
7
|
+
index: string;
|
|
8
|
+
equals?: Array<Record<string, string>>;
|
|
9
|
+
has?: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A page of matching documents: the stored JSON bodies verbatim (EDV encrypted
|
|
13
|
+
* documents pass through untouched), in ascending `resourceId` order.
|
|
14
|
+
* `cursor` is present if and only if a further page may follow (`hasMore`);
|
|
15
|
+
* echo it back in the next query body to resume.
|
|
16
|
+
*/
|
|
17
|
+
export interface BlindedIndexQueryPage {
|
|
18
|
+
documents: unknown[];
|
|
19
|
+
hasMore: boolean;
|
|
20
|
+
cursor?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Validates and normalizes the `blinded-index` profile's query body fields
|
|
24
|
+
* (everything besides `profile`), throwing `invalid-request-body` (400) on a
|
|
25
|
+
* malformed query. Mirrors the EDV query schema (`{index, equals | has,
|
|
26
|
+
* count, limit}`, with `equals` and `has` mutually exclusive and exactly one
|
|
27
|
+
* required) plus the WAS `cursor`. `limit` is coerced leniently like the
|
|
28
|
+
* `changes` profile (a non-numeric or `< 1` value falls back to the default;
|
|
29
|
+
* the backend clamps an oversized one), and the opaque `cursor` is validated
|
|
30
|
+
* by the backend's cursor decode (`invalid-cursor` 400).
|
|
31
|
+
*
|
|
32
|
+
* @param options {object}
|
|
33
|
+
* @param options.body {object} the parsed query POST body
|
|
34
|
+
* @param [options.requestName] {string}
|
|
35
|
+
* @returns {{ query: BlindedIndexQuery, count: boolean, limit?: number, cursor?: string }}
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseBlindedIndexQueryBody({ body, requestName }: {
|
|
38
|
+
body: {
|
|
39
|
+
index?: unknown;
|
|
40
|
+
equals?: unknown;
|
|
41
|
+
has?: unknown;
|
|
42
|
+
count?: unknown;
|
|
43
|
+
limit?: unknown;
|
|
44
|
+
cursor?: unknown;
|
|
45
|
+
};
|
|
46
|
+
requestName?: string;
|
|
47
|
+
}): {
|
|
48
|
+
query: BlindedIndexQuery;
|
|
49
|
+
count: boolean;
|
|
50
|
+
limit?: number;
|
|
51
|
+
cursor?: string;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* True when a stored JSON document matches a blinded-index query. The document
|
|
55
|
+
* must carry an `indexed` entry for the queried `index` (HMAC key id); then
|
|
56
|
+
* `equals` matches when SOME array element has ALL its `{name: value}` pairs
|
|
57
|
+
* present among that index's blinded attributes (an empty element matches
|
|
58
|
+
* nothing -- the reference servers' Mongo `$all: []` parity, which the EDV
|
|
59
|
+
* client emits when querying a never-indexed attribute), and `has` matches
|
|
60
|
+
* when ALL named attributes are present (from any indexed entry). Purely
|
|
61
|
+
* structural string matching -- no decoding, no crypto.
|
|
62
|
+
*
|
|
63
|
+
* @param options {object}
|
|
64
|
+
* @param options.document {unknown} the stored (parsed) resource JSON
|
|
65
|
+
* @param options.query {BlindedIndexQuery}
|
|
66
|
+
* @returns {boolean}
|
|
67
|
+
*/
|
|
68
|
+
export declare function matchesBlindedIndexQuery({ document, query }: {
|
|
69
|
+
document: unknown;
|
|
70
|
+
query: BlindedIndexQuery;
|
|
71
|
+
}): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Evaluates a blinded-index query over a Collection's candidate documents and
|
|
74
|
+
* shapes the result: a bare `{count}` for a count query, else a page of
|
|
75
|
+
* matching documents in ascending `resourceId` (code-unit) order with the
|
|
76
|
+
* standard opaque-cursor pagination (`limit` clamped to `[1, MAX_PAGE_SIZE]`,
|
|
77
|
+
* default `DEFAULT_PAGE_SIZE`; `cursor` resumes strictly after its anchor id,
|
|
78
|
+
* so paging stays correct if the anchor was deleted between pages). A
|
|
79
|
+
* malformed `cursor` rejects with `invalid-cursor` (400).
|
|
80
|
+
*
|
|
81
|
+
* Backends call this with every live JSON document of the Collection -- an
|
|
82
|
+
* O(n) full scan, deliberate for these teaching backends (an indexed backend
|
|
83
|
+
* would answer from flattened `hmacId:name:value` attribute tokens, the
|
|
84
|
+
* bedrock-edv-storage strategy).
|
|
85
|
+
*
|
|
86
|
+
* @param options {object}
|
|
87
|
+
* @param options.candidates {Array<{resourceId: string, document: unknown}>}
|
|
88
|
+
* @param options.query {BlindedIndexQuery}
|
|
89
|
+
* @param [options.count] {boolean} return only the match count
|
|
90
|
+
* @param [options.limit] {number} requested page size
|
|
91
|
+
* @param [options.cursor] {string} opaque cursor from a prior page
|
|
92
|
+
* @returns {{ count: number } | BlindedIndexQueryPage}
|
|
93
|
+
*/
|
|
94
|
+
export declare function runBlindedIndexQuery({ candidates, query, count, limit, cursor }: {
|
|
95
|
+
candidates: Array<{
|
|
96
|
+
resourceId: string;
|
|
97
|
+
document: unknown;
|
|
98
|
+
}>;
|
|
99
|
+
query: BlindedIndexQuery;
|
|
100
|
+
count?: boolean;
|
|
101
|
+
limit?: number;
|
|
102
|
+
cursor?: string;
|
|
103
|
+
}): {
|
|
104
|
+
count: number;
|
|
105
|
+
} | BlindedIndexQueryPage;
|
|
106
|
+
/**
|
|
107
|
+
* Collects a document's `unique: true` blinded attributes as
|
|
108
|
+
* `(hmac id, name, value)` triples -- the claims the EDV unique-attribute
|
|
109
|
+
* invariant protects. Malformed entries and attributes are skipped, never
|
|
110
|
+
* thrown on; an attribute counts only when `unique` is exactly `true` and
|
|
111
|
+
* both `name` and `value` are strings.
|
|
112
|
+
*
|
|
113
|
+
* @param options {object}
|
|
114
|
+
* @param options.document {unknown} the (parsed) document JSON
|
|
115
|
+
* @returns {Array<{ hmacId: string, name: string, value: string }>}
|
|
116
|
+
*/
|
|
117
|
+
export declare function collectUniqueBlindedTerms({ document }: {
|
|
118
|
+
document: unknown;
|
|
119
|
+
}): Array<{
|
|
120
|
+
hmacId: string;
|
|
121
|
+
name: string;
|
|
122
|
+
value: string;
|
|
123
|
+
}>;
|
|
124
|
+
/**
|
|
125
|
+
* Enforces the EDV unique-attribute invariant on a write: a `unique: true`
|
|
126
|
+
* blinded `(hmac id, name, value)` triple may be claimed by at most one live
|
|
127
|
+
* document per Collection. Following the reference servers
|
|
128
|
+
* (`@interop/edv-server`, bedrock-edv-storage's unique index over
|
|
129
|
+
* `uniqueAttributes`), a conflict requires the attribute be marked
|
|
130
|
+
* `unique: true` on BOTH sides -- an existing document carrying the same
|
|
131
|
+
* triple without `unique` does not conflict -- and the triple is keyed on the
|
|
132
|
+
* full `(hmac id, name, value)`, so the same blinded pair under a different
|
|
133
|
+
* HMAC key is no conflict. `candidates` MUST already exclude the document
|
|
134
|
+
* being written (its own resource id), so an update keeping its own unique
|
|
135
|
+
* attribute never self-conflicts. Throws `UniqueAttributeConflictError`
|
|
136
|
+
* (409) on a conflict; a document with no unique attributes never throws.
|
|
137
|
+
*
|
|
138
|
+
* The caller is responsible for making the check-and-write atomic (the
|
|
139
|
+
* filesystem backend serializes unique-carrying writes per Collection; the
|
|
140
|
+
* Postgres backend takes a per-Collection advisory lock in the write
|
|
141
|
+
* transaction).
|
|
142
|
+
*
|
|
143
|
+
* @param options {object}
|
|
144
|
+
* @param options.document {unknown} the incoming (parsed) document JSON
|
|
145
|
+
* @param options.candidates {Array<{resourceId: string, document: unknown}>}
|
|
146
|
+
* the Collection's other live JSON documents
|
|
147
|
+
* @returns {void}
|
|
148
|
+
*/
|
|
149
|
+
export declare function assertNoUniqueBlindedConflict({ document, candidates }: {
|
|
150
|
+
document: unknown;
|
|
151
|
+
candidates: Array<{
|
|
152
|
+
resourceId: string;
|
|
153
|
+
document: unknown;
|
|
154
|
+
}>;
|
|
155
|
+
}): void;
|
|
156
|
+
//# sourceMappingURL=blindedIndex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blindedIndex.d.ts","sourceRoot":"","sources":["../../src/lib/blindedIndex.ts"],"names":[],"mappings":"AAgCA;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACtC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;CACf;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,OAAO,EAAE,CAAA;IACpB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAQD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,0BAA0B,CAAC,EACzC,IAAI,EACJ,WAAW,EACZ,EAAE;IACD,IAAI,EAAE;QACJ,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,GAAG,CAAC,EAAE,OAAO,CAAA;QACb,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,MAAM,CAAC,EAAE,OAAO,CAAA;KACjB,CAAA;IACD,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG;IACF,KAAK,EAAE,iBAAiB,CAAA;IACxB,KAAK,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAyFA;AAmDD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EACR,KAAK,EACN,EAAE;IACD,QAAQ,EAAE,OAAO,CAAA;IACjB,KAAK,EAAE,iBAAiB,CAAA;CACzB,GAAG,OAAO,CA2BV;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,UAAU,EACV,KAAK,EACL,KAAK,EACL,KAAK,EACL,MAAM,EACP,EAAE;IACD,UAAU,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IAC5D,KAAK,EAAE,iBAAiB,CAAA;IACxB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,qBAAqB,CA0C5C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,QAAQ,EACT,EAAE;IACD,QAAQ,EAAE,OAAO,CAAA;CAClB,GAAG,KAAK,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAqCzD;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,6BAA6B,CAAC,EAC5C,QAAQ,EACR,UAAU,EACX,EAAE;IACD,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CAC7D,GAAG,IAAI,CAkBP"}
|