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.
Files changed (295) hide show
  1. package/CHANGELOG.md +1432 -0
  2. package/LICENSE +661 -0
  3. package/README.md +305 -0
  4. package/common/css/materialize.1.0.0.min.css +13 -0
  5. package/dist/auth-header-hooks.d.ts +84 -0
  6. package/dist/auth-header-hooks.d.ts.map +1 -0
  7. package/dist/auth-header-hooks.js +136 -0
  8. package/dist/auth-header-hooks.js.map +1 -0
  9. package/dist/authorize.d.ts +45 -0
  10. package/dist/authorize.d.ts.map +1 -0
  11. package/dist/authorize.js +76 -0
  12. package/dist/authorize.js.map +1 -0
  13. package/dist/backends/filesystem.d.ts +1179 -0
  14. package/dist/backends/filesystem.d.ts.map +1 -0
  15. package/dist/backends/filesystem.js +2727 -0
  16. package/dist/backends/filesystem.js.map +1 -0
  17. package/dist/backends/postgres.d.ts +833 -0
  18. package/dist/backends/postgres.d.ts.map +1 -0
  19. package/dist/backends/postgres.js +2164 -0
  20. package/dist/backends/postgres.js.map +1 -0
  21. package/dist/backends/postgresSchema.d.ts +36 -0
  22. package/dist/backends/postgresSchema.d.ts.map +1 -0
  23. package/dist/backends/postgresSchema.js +203 -0
  24. package/dist/backends/postgresSchema.js.map +1 -0
  25. package/dist/config.default.d.ts +360 -0
  26. package/dist/config.default.d.ts.map +1 -0
  27. package/dist/config.default.js +493 -0
  28. package/dist/config.default.js.map +1 -0
  29. package/dist/corsProxy.d.ts +45 -0
  30. package/dist/corsProxy.d.ts.map +1 -0
  31. package/dist/corsProxy.js +278 -0
  32. package/dist/corsProxy.js.map +1 -0
  33. package/dist/digest.d.ts +45 -0
  34. package/dist/digest.d.ts.map +1 -0
  35. package/dist/digest.js +174 -0
  36. package/dist/digest.js.map +1 -0
  37. package/dist/errors.d.ts +654 -0
  38. package/dist/errors.d.ts.map +1 -0
  39. package/dist/errors.js +960 -0
  40. package/dist/errors.js.map +1 -0
  41. package/dist/index.d.ts +22 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +21 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/lib/atomicFile.d.ts +61 -0
  46. package/dist/lib/atomicFile.d.ts.map +1 -0
  47. package/dist/lib/atomicFile.js +150 -0
  48. package/dist/lib/atomicFile.js.map +1 -0
  49. package/dist/lib/backendRegistry.d.ts +59 -0
  50. package/dist/lib/backendRegistry.d.ts.map +1 -0
  51. package/dist/lib/backendRegistry.js +92 -0
  52. package/dist/lib/backendRegistry.js.map +1 -0
  53. package/dist/lib/backendUsage.d.ts +28 -0
  54. package/dist/lib/backendUsage.d.ts.map +1 -0
  55. package/dist/lib/backendUsage.js +55 -0
  56. package/dist/lib/backendUsage.js.map +1 -0
  57. package/dist/lib/backends.d.ts +142 -0
  58. package/dist/lib/backends.d.ts.map +1 -0
  59. package/dist/lib/backends.js +272 -0
  60. package/dist/lib/backends.js.map +1 -0
  61. package/dist/lib/blindedIndex.d.ts +156 -0
  62. package/dist/lib/blindedIndex.d.ts.map +1 -0
  63. package/dist/lib/blindedIndex.js +338 -0
  64. package/dist/lib/blindedIndex.js.map +1 -0
  65. package/dist/lib/collectionDescription.d.ts +33 -0
  66. package/dist/lib/collectionDescription.d.ts.map +1 -0
  67. package/dist/lib/collectionDescription.js +21 -0
  68. package/dist/lib/collectionDescription.js.map +1 -0
  69. package/dist/lib/cursor.d.ts +18 -0
  70. package/dist/lib/cursor.d.ts.map +1 -0
  71. package/dist/lib/cursor.js +50 -0
  72. package/dist/lib/cursor.js.map +1 -0
  73. package/dist/lib/edvEnvelope.d.ts +50 -0
  74. package/dist/lib/edvEnvelope.d.ts.map +1 -0
  75. package/dist/lib/edvEnvelope.js +106 -0
  76. package/dist/lib/edvEnvelope.js.map +1 -0
  77. package/dist/lib/encryption.d.ts +192 -0
  78. package/dist/lib/encryption.d.ts.map +1 -0
  79. package/dist/lib/encryption.js +383 -0
  80. package/dist/lib/encryption.js.map +1 -0
  81. package/dist/lib/etag.d.ts +33 -0
  82. package/dist/lib/etag.d.ts.map +1 -0
  83. package/dist/lib/etag.js +36 -0
  84. package/dist/lib/etag.js.map +1 -0
  85. package/dist/lib/exportManifest.d.ts +29 -0
  86. package/dist/lib/exportManifest.d.ts.map +1 -0
  87. package/dist/lib/exportManifest.js +77 -0
  88. package/dist/lib/exportManifest.js.map +1 -0
  89. package/dist/lib/importTar.d.ts +81 -0
  90. package/dist/lib/importTar.d.ts.map +1 -0
  91. package/dist/lib/importTar.js +301 -0
  92. package/dist/lib/importTar.js.map +1 -0
  93. package/dist/lib/isJson.d.ts +16 -0
  94. package/dist/lib/isJson.d.ts.map +1 -0
  95. package/dist/lib/isJson.js +17 -0
  96. package/dist/lib/isJson.js.map +1 -0
  97. package/dist/lib/keyEpoch.d.ts +38 -0
  98. package/dist/lib/keyEpoch.d.ts.map +1 -0
  99. package/dist/lib/keyEpoch.js +75 -0
  100. package/dist/lib/keyEpoch.js.map +1 -0
  101. package/dist/lib/keyedMutex.d.ts +28 -0
  102. package/dist/lib/keyedMutex.d.ts.map +1 -0
  103. package/dist/lib/keyedMutex.js +45 -0
  104. package/dist/lib/keyedMutex.js.map +1 -0
  105. package/dist/lib/kmsModule.d.ts +62 -0
  106. package/dist/lib/kmsModule.d.ts.map +1 -0
  107. package/dist/lib/kmsModule.js +327 -0
  108. package/dist/lib/kmsModule.js.map +1 -0
  109. package/dist/lib/kmsRecordCipher.d.ts +82 -0
  110. package/dist/lib/kmsRecordCipher.d.ts.map +1 -0
  111. package/dist/lib/kmsRecordCipher.js +226 -0
  112. package/dist/lib/kmsRecordCipher.js.map +1 -0
  113. package/dist/lib/pagination.d.ts +19 -0
  114. package/dist/lib/pagination.d.ts.map +1 -0
  115. package/dist/lib/pagination.js +21 -0
  116. package/dist/lib/pagination.js.map +1 -0
  117. package/dist/lib/paths.d.ts +273 -0
  118. package/dist/lib/paths.d.ts.map +1 -0
  119. package/dist/lib/paths.js +270 -0
  120. package/dist/lib/paths.js.map +1 -0
  121. package/dist/lib/preconditions.d.ts +65 -0
  122. package/dist/lib/preconditions.d.ts.map +1 -0
  123. package/dist/lib/preconditions.js +112 -0
  124. package/dist/lib/preconditions.js.map +1 -0
  125. package/dist/lib/resourceFileName.d.ts +41 -0
  126. package/dist/lib/resourceFileName.d.ts.map +1 -0
  127. package/dist/lib/resourceFileName.js +58 -0
  128. package/dist/lib/resourceFileName.js.map +1 -0
  129. package/dist/lib/revocations.d.ts +49 -0
  130. package/dist/lib/revocations.d.ts.map +1 -0
  131. package/dist/lib/revocations.js +86 -0
  132. package/dist/lib/revocations.js.map +1 -0
  133. package/dist/lib/validateDid.d.ts +19 -0
  134. package/dist/lib/validateDid.d.ts.map +1 -0
  135. package/dist/lib/validateDid.js +35 -0
  136. package/dist/lib/validateDid.js.map +1 -0
  137. package/dist/lib/validateId.d.ts +66 -0
  138. package/dist/lib/validateId.d.ts.map +1 -0
  139. package/dist/lib/validateId.js +126 -0
  140. package/dist/lib/validateId.js.map +1 -0
  141. package/dist/plugin.d.ts +115 -0
  142. package/dist/plugin.d.ts.map +1 -0
  143. package/dist/plugin.js +134 -0
  144. package/dist/plugin.js.map +1 -0
  145. package/dist/policy.d.ts +78 -0
  146. package/dist/policy.d.ts.map +1 -0
  147. package/dist/policy.js +115 -0
  148. package/dist/policy.js.map +1 -0
  149. package/dist/provisioning.d.ts +38 -0
  150. package/dist/provisioning.d.ts.map +1 -0
  151. package/dist/provisioning.js +72 -0
  152. package/dist/provisioning.js.map +1 -0
  153. package/dist/requests/BackendRequest.d.ts +61 -0
  154. package/dist/requests/BackendRequest.d.ts.map +1 -0
  155. package/dist/requests/BackendRequest.js +152 -0
  156. package/dist/requests/BackendRequest.js.map +1 -0
  157. package/dist/requests/CollectionRequest.d.ts +234 -0
  158. package/dist/requests/CollectionRequest.d.ts.map +1 -0
  159. package/dist/requests/CollectionRequest.js +705 -0
  160. package/dist/requests/CollectionRequest.js.map +1 -0
  161. package/dist/requests/KeyRequest.d.ts +115 -0
  162. package/dist/requests/KeyRequest.d.ts.map +1 -0
  163. package/dist/requests/KeyRequest.js +468 -0
  164. package/dist/requests/KeyRequest.js.map +1 -0
  165. package/dist/requests/KeystoreRequest.d.ts +99 -0
  166. package/dist/requests/KeystoreRequest.d.ts.map +1 -0
  167. package/dist/requests/KeystoreRequest.js +247 -0
  168. package/dist/requests/KeystoreRequest.js.map +1 -0
  169. package/dist/requests/PolicyRequest.d.ts +53 -0
  170. package/dist/requests/PolicyRequest.d.ts.map +1 -0
  171. package/dist/requests/PolicyRequest.js +124 -0
  172. package/dist/requests/PolicyRequest.js.map +1 -0
  173. package/dist/requests/ResourceRequest.d.ts +134 -0
  174. package/dist/requests/ResourceRequest.d.ts.map +1 -0
  175. package/dist/requests/ResourceRequest.js +548 -0
  176. package/dist/requests/ResourceRequest.js.map +1 -0
  177. package/dist/requests/RevocationRequest.d.ts +67 -0
  178. package/dist/requests/RevocationRequest.d.ts.map +1 -0
  179. package/dist/requests/RevocationRequest.js +187 -0
  180. package/dist/requests/RevocationRequest.js.map +1 -0
  181. package/dist/requests/SpaceRequest.d.ts +204 -0
  182. package/dist/requests/SpaceRequest.d.ts.map +1 -0
  183. package/dist/requests/SpaceRequest.js +520 -0
  184. package/dist/requests/SpaceRequest.js.map +1 -0
  185. package/dist/requests/SpacesRepositoryRequest.d.ts +50 -0
  186. package/dist/requests/SpacesRepositoryRequest.d.ts.map +1 -0
  187. package/dist/requests/SpacesRepositoryRequest.js +159 -0
  188. package/dist/requests/SpacesRepositoryRequest.js.map +1 -0
  189. package/dist/requests/collectionContext.d.ts +21 -0
  190. package/dist/requests/collectionContext.d.ts.map +1 -0
  191. package/dist/requests/collectionContext.js +30 -0
  192. package/dist/requests/collectionContext.js.map +1 -0
  193. package/dist/requests/controllerConsent.d.ts +58 -0
  194. package/dist/requests/controllerConsent.d.ts.map +1 -0
  195. package/dist/requests/controllerConsent.js +73 -0
  196. package/dist/requests/controllerConsent.js.map +1 -0
  197. package/dist/requests/keystoreContext.d.ts +68 -0
  198. package/dist/requests/keystoreContext.d.ts.map +1 -0
  199. package/dist/requests/keystoreContext.js +87 -0
  200. package/dist/requests/keystoreContext.js.map +1 -0
  201. package/dist/requests/resourceInput.d.ts +19 -0
  202. package/dist/requests/resourceInput.d.ts.map +1 -0
  203. package/dist/requests/resourceInput.js +102 -0
  204. package/dist/requests/resourceInput.js.map +1 -0
  205. package/dist/requests/spaceContext.d.ts +127 -0
  206. package/dist/requests/spaceContext.d.ts.map +1 -0
  207. package/dist/requests/spaceContext.js +201 -0
  208. package/dist/requests/spaceContext.js.map +1 -0
  209. package/dist/routes.d.ts +55 -0
  210. package/dist/routes.d.ts.map +1 -0
  211. package/dist/routes.js +306 -0
  212. package/dist/routes.js.map +1 -0
  213. package/dist/server.d.ts +20 -0
  214. package/dist/server.d.ts.map +1 -0
  215. package/dist/server.js +60 -0
  216. package/dist/server.js.map +1 -0
  217. package/dist/start.d.ts +7 -0
  218. package/dist/start.d.ts.map +1 -0
  219. package/dist/start.js +61 -0
  220. package/dist/start.js.map +1 -0
  221. package/dist/storage.d.ts +29 -0
  222. package/dist/storage.d.ts.map +1 -0
  223. package/dist/storage.js +47 -0
  224. package/dist/storage.js.map +1 -0
  225. package/dist/types.d.ts +915 -0
  226. package/dist/types.d.ts.map +1 -0
  227. package/dist/types.js +2 -0
  228. package/dist/types.js.map +1 -0
  229. package/dist/views/home.hbs +9 -0
  230. package/dist/views/templates/main.hbs +12 -0
  231. package/dist/zcap.d.ts +267 -0
  232. package/dist/zcap.d.ts.map +1 -0
  233. package/dist/zcap.js +478 -0
  234. package/dist/zcap.js.map +1 -0
  235. package/package.json +109 -0
  236. package/src/auth-header-hooks.ts +163 -0
  237. package/src/authorize.ts +117 -0
  238. package/src/backends/filesystem.ts +3626 -0
  239. package/src/backends/postgres.ts +3073 -0
  240. package/src/backends/postgresSchema.ts +231 -0
  241. package/src/config.default.ts +657 -0
  242. package/src/corsProxy.ts +326 -0
  243. package/src/declarations.d.ts +28 -0
  244. package/src/digest.ts +223 -0
  245. package/src/errors.ts +1122 -0
  246. package/src/index.ts +21 -0
  247. package/src/lib/atomicFile.ts +176 -0
  248. package/src/lib/backendRegistry.ts +139 -0
  249. package/src/lib/backendUsage.ts +79 -0
  250. package/src/lib/backends.ts +351 -0
  251. package/src/lib/blindedIndex.ts +466 -0
  252. package/src/lib/collectionDescription.ts +43 -0
  253. package/src/lib/cursor.ts +52 -0
  254. package/src/lib/edvEnvelope.ts +120 -0
  255. package/src/lib/encryption.ts +472 -0
  256. package/src/lib/etag.ts +40 -0
  257. package/src/lib/exportManifest.ts +115 -0
  258. package/src/lib/importTar.ts +381 -0
  259. package/src/lib/isJson.ts +18 -0
  260. package/src/lib/keyEpoch.ts +89 -0
  261. package/src/lib/keyedMutex.ts +45 -0
  262. package/src/lib/kmsModule.ts +451 -0
  263. package/src/lib/kmsRecordCipher.ts +292 -0
  264. package/src/lib/pagination.ts +22 -0
  265. package/src/lib/paths.ts +382 -0
  266. package/src/lib/preconditions.ts +145 -0
  267. package/src/lib/resourceFileName.ts +69 -0
  268. package/src/lib/revocations.ts +116 -0
  269. package/src/lib/validateDid.ts +41 -0
  270. package/src/lib/validateId.ts +148 -0
  271. package/src/plugin.ts +288 -0
  272. package/src/policy.ts +176 -0
  273. package/src/provisioning.ts +98 -0
  274. package/src/requests/BackendRequest.ts +219 -0
  275. package/src/requests/CollectionRequest.ts +899 -0
  276. package/src/requests/KeyRequest.ts +617 -0
  277. package/src/requests/KeystoreRequest.ts +325 -0
  278. package/src/requests/PolicyRequest.ts +169 -0
  279. package/src/requests/ResourceRequest.ts +649 -0
  280. package/src/requests/RevocationRequest.ts +267 -0
  281. package/src/requests/SpaceRequest.ts +686 -0
  282. package/src/requests/SpacesRepositoryRequest.ts +194 -0
  283. package/src/requests/collectionContext.ts +41 -0
  284. package/src/requests/controllerConsent.ts +108 -0
  285. package/src/requests/keystoreContext.ts +124 -0
  286. package/src/requests/resourceInput.ts +118 -0
  287. package/src/requests/spaceContext.ts +311 -0
  288. package/src/routes.ts +420 -0
  289. package/src/server.ts +69 -0
  290. package/src/start.ts +71 -0
  291. package/src/storage.ts +60 -0
  292. package/src/types.ts +1016 -0
  293. package/src/views/home.hbs +9 -0
  294. package/src/views/templates/main.hbs +12 -0
  295. 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"}