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,833 @@
1
+ /**
2
+ * PostgreSQL persistence backend: stores Spaces, Collections, and Resources as
3
+ * rows (schema in postgresSchema.ts), and WebKMS keystores in a sibling table
4
+ * tree, implementing the same StorageBackend contract as the filesystem
5
+ * backend (types.ts). Selected by configuration (`DATABASE_URL`) and injected
6
+ * the same way (`createApp({ backend })`).
7
+ *
8
+ * Design departures from the filesystem backend (deliberate):
9
+ * - Quota accounting is transactional (`spaces.usage_bytes`, maintained in the
10
+ * same transaction as every write/delete), making the per-Space capacity a
11
+ * HARD limit under concurrency. "Usage" is exactly the stored content bytes;
12
+ * descriptions, policies, and metadata are not counted (a divergence from
13
+ * the filesystem's `du`, which counts every file).
14
+ * - Conditional writes use row locks (`SELECT ... FOR UPDATE`) and
15
+ * transactions instead of the single-process `KeyedMutex`, so two server
16
+ * processes sharing one database get correct conditional writes.
17
+ * - Blobs are buffered single-`bytea` writes bounded by `maxUploadBytes`; an
18
+ * unset cap defaults to `DEFAULT_MAX_UPLOAD_BYTES` rather than "unbounded"
19
+ * (unbounded buffering into a `bytea` is a footgun). Chunked-row streaming
20
+ * is a planned follow-up increment.
21
+ * - `exportSpace` / `importSpace` speak the same tar dialect as the
22
+ * filesystem backend (same file-name codecs, same manifest), so archives
23
+ * migrate between the two backends in either direction; the Postgres import
24
+ * apply loop additionally runs in a single transaction (atomic rollback).
25
+ */
26
+ import { Readable } from 'node:stream';
27
+ import type { FastifyBaseLogger } from 'fastify';
28
+ import type { BlindedIndexQuery, BlindedIndexQueryPage } from '../lib/blindedIndex.js';
29
+ import type { SpaceDescription, CollectionDescription, CollectionSummary, CollectionResourcesList, ResourceResult, ResourceMetadata, ResourceMetadataCustom, ResourceInput, ImportStats, PolicyDocument, BackendDescriptor, BackendUsage, StorageBackend, StoredBackendRecord, KeystoreConfig, KmsKeyRecord, RevocationRecord, RevocationScope, CapabilitySummary, IDID } from '../types.js';
30
+ export declare class PostgresBackend implements StorageBackend {
31
+ logger: FastifyBaseLogger;
32
+ /**
33
+ * Per-Space storage capacity, in bytes (spec "Quotas"). `undefined` means no
34
+ * configured limit. Unlike the filesystem backend's `du`-sampled soft limit,
35
+ * this is enforced transactionally on every content write -- a HARD limit
36
+ * under concurrency.
37
+ */
38
+ capacityBytes?: number;
39
+ /**
40
+ * Largest single upload accepted, in bytes (spec "Quotas",
41
+ * `maxUploadBytes`). Always set on this backend: an unconfigured cap
42
+ * defaults to `DEFAULT_MAX_UPLOAD_BYTES`, because every blob write buffers
43
+ * through memory on the single-`bytea` path.
44
+ */
45
+ maxUploadBytes: number;
46
+ /**
47
+ * Max Spaces a single controller may create (spec "Quotas", a default-on
48
+ * count quota). `undefined` means no cap. Enforced transactionally on the
49
+ * Space create path (`writeSpace`), serialized per controller by an advisory
50
+ * lock -- a HARD limit under concurrency, like the byte quota. The
51
+ * constructor normalizes an unset option to
52
+ * {@link DEFAULT_MAX_SPACES_PER_CONTROLLER} and a non-finite option
53
+ * (`Infinity`) to `undefined`.
54
+ */
55
+ maxSpacesPerController?: number;
56
+ /**
57
+ * Max Collections a single Space may hold (spec "Quotas", a default-on count
58
+ * quota). `undefined` means no cap. Enforced on the Collection create path,
59
+ * serialized per Space by the space row lock. Normalized like
60
+ * {@link maxSpacesPerController}.
61
+ */
62
+ maxCollectionsPerSpace?: number;
63
+ /**
64
+ * Max live Resources a single Space may hold across all its Collections (spec
65
+ * "Quotas", a default-on count quota). `undefined` means no cap. Enforced on
66
+ * the Resource create path (a tombstone does not count). Normalized like
67
+ * {@link maxSpacesPerController}.
68
+ */
69
+ maxResourcesPerSpace?: number;
70
+ private _pool;
71
+ private _schema?;
72
+ /**
73
+ * @param options {object}
74
+ * @param options.connectionString {string} a `postgres://` URL
75
+ * @param [options.schema] {string} Postgres schema to operate in (set as
76
+ * the connection `search_path`; created by `init()` if absent). Used for
77
+ * test isolation; production uses the default `public`.
78
+ * @param [options.logger] {FastifyBaseLogger}
79
+ * @param [options.capacityBytes] {number} per-Space quota in bytes; a
80
+ * finite value is enforced, `undefined` or a non-finite value means no
81
+ * configured limit
82
+ * @param [options.maxUploadBytes] {number} per-upload cap in bytes;
83
+ * `undefined` applies the `DEFAULT_MAX_UPLOAD_BYTES` default. A non-finite
84
+ * value (`Infinity`, from `MAX_UPLOAD_BYTES=unlimited`) throws: this backend
85
+ * buffers each upload in memory as a single `bytea`, so an unbounded cap is
86
+ * not supported.
87
+ * @param [options.maxSpacesPerController] {number} max Spaces per
88
+ * controller (spec "Quotas"); `undefined` applies the default-on limit,
89
+ * `Infinity` means no cap
90
+ * @param [options.maxCollectionsPerSpace] {number} max Collections per
91
+ * Space; `undefined` applies the default-on limit, `Infinity` means no cap
92
+ * @param [options.maxResourcesPerSpace] {number} max live Resources per
93
+ * Space; `undefined` applies the default-on limit, `Infinity` means no cap
94
+ */
95
+ constructor({ connectionString, schema, logger, capacityBytes, maxUploadBytes, maxSpacesPerController, maxCollectionsPerSpace, maxResourcesPerSpace }: {
96
+ connectionString: string;
97
+ schema?: string;
98
+ logger?: FastifyBaseLogger;
99
+ capacityBytes?: number;
100
+ maxUploadBytes?: number;
101
+ maxSpacesPerController?: number;
102
+ maxCollectionsPerSpace?: number;
103
+ maxResourcesPerSpace?: number;
104
+ });
105
+ /**
106
+ * Connects and applies schema migrations (idempotent, advisory-locked; see
107
+ * postgresSchema.ts). Called once by the `createApp` composition before the
108
+ * server starts listening.
109
+ * @returns {Promise<void>}
110
+ */
111
+ init(): Promise<void>;
112
+ /**
113
+ * Drains the connection pool. Wired to the Fastify `onClose` hook by the
114
+ * plugin composition.
115
+ * @returns {Promise<void>}
116
+ */
117
+ close(): Promise<void>;
118
+ /**
119
+ * Runs `fn` inside one transaction on a dedicated client, committing on
120
+ * success and rolling back on any throw.
121
+ * @param fn {(client: pg.PoolClient) => Promise<T>}
122
+ * @returns {Promise<T>}
123
+ */
124
+ private _withTransaction;
125
+ /**
126
+ * Self-description advertised at `GET /space/:spaceId/backends`. Same
127
+ * affordances as the filesystem backend: conditional writes (ETag
128
+ * validators, row-locked preconditions) and the `changes` and
129
+ * `blinded-index` query profiles.
130
+ * @returns {Required<Omit<BackendDescriptor, 'provider' | 'connection'>>}
131
+ */
132
+ describe(): Required<Omit<BackendDescriptor, 'provider' | 'connection'>>;
133
+ /**
134
+ * Ensures the `spaces` row for `spaceId` exists (a placeholder with a NULL
135
+ * description when the Space was never described -- the analogue of the
136
+ * filesystem creating a Space directory on a sub-Space write).
137
+ * @param options {object}
138
+ * @param options.client {pg.PoolClient}
139
+ * @param options.spaceId {string}
140
+ * @returns {Promise<void>}
141
+ */
142
+ private _ensureSpaceRow;
143
+ /**
144
+ * Ensures the `collections` row (and its parent `spaces` row) exists,
145
+ * placeholder-description like `_ensureSpaceRow`.
146
+ * @param options {object}
147
+ * @param options.client {pg.PoolClient}
148
+ * @param options.spaceId {string}
149
+ * @param options.collectionId {string}
150
+ * @returns {Promise<void>}
151
+ */
152
+ private _ensureCollectionRow;
153
+ /**
154
+ * Applies a usage delta to the Space's transactional quota counter,
155
+ * enforcing the configured capacity in the same statement (the hard-limit
156
+ * departure from the filesystem's `du`-sampled soft check). Zero rows
157
+ * updated with the row present means the write would not fit:
158
+ * `QuotaExceededError` (507), rolling back the enclosing transaction.
159
+ * MUST run inside the same transaction as the content mutation.
160
+ * @param options {object}
161
+ * @param options.client {pg.PoolClient}
162
+ * @param options.spaceId {string}
163
+ * @param options.delta {number} signed byte delta (new minus old size)
164
+ * @returns {Promise<void>}
165
+ */
166
+ private _applyUsageDelta;
167
+ /**
168
+ * Reports the Space's usage from the transactional counter (no measurement
169
+ * pass, no cache). The per-Collection breakdown is computed on demand with
170
+ * one aggregate query.
171
+ * @param options {object}
172
+ * @param options.spaceId {string}
173
+ * @param [options.includeCollections] {boolean}
174
+ * @returns {Promise<BackendUsage>}
175
+ */
176
+ reportUsage({ spaceId, includeCollections }: {
177
+ spaceId: string;
178
+ includeCollections?: boolean;
179
+ }): Promise<BackendUsage>;
180
+ /**
181
+ * Reports a single Collection's usage (its `SUM(size_bytes)` slice), with
182
+ * `state` / `limit` derived from the Space total (the quota is a per-Space
183
+ * limit).
184
+ * @param options {object}
185
+ * @param options.spaceId {string}
186
+ * @param options.collectionId {string}
187
+ * @returns {Promise<BackendUsage>}
188
+ */
189
+ reportCollectionUsage({ spaceId, collectionId }: {
190
+ spaceId: string;
191
+ collectionId: string;
192
+ }): Promise<BackendUsage>;
193
+ /**
194
+ * Builds the shared quota-report condition fields (see
195
+ * `lib/backendUsage.ts`).
196
+ * @param options {object}
197
+ * @param options.usageBytes {number}
198
+ * @param options.spaceTotalBytes {number}
199
+ * @returns {Omit<BackendUsage, 'measuredAt' | 'usageByCollection'>}
200
+ */
201
+ private _backendUsageFields;
202
+ /**
203
+ * @param options {object}
204
+ * @param options.spaceId {string}
205
+ * @param options.spaceDescription {SpaceDescription}
206
+ * @param [options.createdBy] {string} DID of the invoker, recorded as the
207
+ * Space's `createdBy` on first write only
208
+ * @returns {Promise<void>}
209
+ */
210
+ writeSpace({ spaceId, spaceDescription, createdBy }: {
211
+ spaceId: string;
212
+ spaceDescription: SpaceDescription;
213
+ createdBy?: IDID;
214
+ }): Promise<void>;
215
+ /**
216
+ * @param options {object}
217
+ * @param options.spaceId {string}
218
+ * @returns {Promise<SpaceDescription|undefined>} falsy when the Space does
219
+ * not exist or is a placeholder row without a description
220
+ */
221
+ getSpaceDescription({ spaceId }: {
222
+ spaceId: string;
223
+ }): Promise<SpaceDescription | undefined>;
224
+ /**
225
+ * Deletes the Space row; collections, resources, policies, and backend
226
+ * records go with it via `ON DELETE CASCADE` (keystores are a sibling tree
227
+ * and are deliberately untouched). Idempotent.
228
+ * @param options {object}
229
+ * @param options.spaceId {string}
230
+ * @returns {Promise<void>}
231
+ */
232
+ deleteSpace({ spaceId }: {
233
+ spaceId: string;
234
+ }): Promise<void>;
235
+ /**
236
+ * Every described Space, sorted by id (byte order via `COLLATE "C"`).
237
+ * Placeholder rows without a description are skipped, like a Space dir
238
+ * without a readable description file.
239
+ * @returns {Promise<SpaceDescription[]>}
240
+ */
241
+ listSpaces(): Promise<SpaceDescription[]>;
242
+ /**
243
+ * @param options {object}
244
+ * @param options.spaceId {string}
245
+ * @param options.collectionId {string}
246
+ * @param options.collectionDescription {CollectionDescription}
247
+ * @param [options.createdBy] {string} DID of the invoker, recorded as the
248
+ * Collection's `createdBy` on first write only
249
+ * @param [options.ifMatch] {string} an `If-Match` compare-and-swap on the
250
+ * current description version (the `key-epochs` feature); a stale validator
251
+ * throws `PreconditionFailedError` (412)
252
+ * @returns {Promise<{ version: number }>} the Collection's new description
253
+ * version (the `ETag` validator)
254
+ */
255
+ writeCollection({ spaceId, collectionId, collectionDescription, createdBy, ifMatch, assertTransition }: {
256
+ spaceId: string;
257
+ collectionId: string;
258
+ collectionDescription: CollectionDescription;
259
+ createdBy?: IDID;
260
+ ifMatch?: string;
261
+ assertTransition?: (prior?: CollectionDescription & {
262
+ descriptionVersion?: number;
263
+ }) => void;
264
+ }): Promise<{
265
+ version: number;
266
+ }>;
267
+ /**
268
+ * The one Collection-description upsert statement, shared by
269
+ * `writeCollection` and the import apply loop. `createdBy` is resolved
270
+ * within this same statement, in one round trip (no separate read-then-write
271
+ * race with a concurrent write for the same id):
272
+ * - `collections.description IS NULL` means there is no prior description
273
+ * row (a placeholder row created by a sub-Resource write before any
274
+ * Collection Description was written) -- this write IS the create, so it
275
+ * behaves like the insert branch: attach the `createdBy` parameter when
276
+ * present, otherwise omit the key entirely (never store it as JSON
277
+ * `null`).
278
+ * - Otherwise a prior description exists, and its `createdBy` -- present or
279
+ * absent -- is preserved verbatim via the jsonb `?` key-existence
280
+ * operator; the `createdBy` parameter is ignored entirely (never
281
+ * backfilled).
282
+ * Any `createdBy` embedded in `collectionDescription` itself is discarded
283
+ * here: `writeCollection` passes the invoker DID via the `createdBy`
284
+ * parameter instead, and the import apply loop passes the imported
285
+ * document's own `createdBy` so a restored Collection keeps its original
286
+ * creator (that import call is always a create -- the caller skips existing
287
+ * Collections -- so there is no prior row to preserve instead).
288
+ * @param options {object}
289
+ * @param options.queryable {Queryable}
290
+ * @param options.spaceId {string}
291
+ * @param options.collectionId {string}
292
+ * @param options.collectionDescription {CollectionDescription}
293
+ * @param [options.createdBy] {string} resolved creator to fall back to
294
+ * when there is no prior row
295
+ * @param [options.descriptionVersion] {number} the monotonic description
296
+ * version to stamp (the `ETag` validator; the `key-epochs` feature), kept
297
+ * OUT of the `description` jsonb -- it lives in the `description_version`
298
+ * column and travels only as the `ETag` header. When omitted (the import
299
+ * path), a `_version` on the incoming archived description is used, else 1.
300
+ * @returns {Promise<void>}
301
+ */
302
+ private _upsertCollection;
303
+ /**
304
+ * @param options {object}
305
+ * @param options.spaceId {string}
306
+ * @param options.collectionId {string}
307
+ * @returns {Promise<(CollectionDescription & { descriptionVersion?: number })
308
+ * | undefined>} `descriptionVersion` is the out-of-band `ETag` validator.
309
+ */
310
+ getCollectionDescription({ spaceId, collectionId }: {
311
+ spaceId: string;
312
+ collectionId: string;
313
+ }): Promise<(CollectionDescription & {
314
+ descriptionVersion?: number;
315
+ }) | undefined>;
316
+ /**
317
+ * Deletes the Collection row (resources cascade), its policies, and
318
+ * subtracts the Collection's stored bytes from the Space usage counter --
319
+ * all in one transaction. Idempotent.
320
+ * @param options {object}
321
+ * @param options.spaceId {string}
322
+ * @param options.collectionId {string}
323
+ * @returns {Promise<void>}
324
+ */
325
+ deleteCollection({ spaceId, collectionId }: {
326
+ spaceId: string;
327
+ collectionId: string;
328
+ }): Promise<void>;
329
+ /**
330
+ * Every Collection row in the Space, sorted by id. A row without a
331
+ * description (created by a sub-Collection write) falls back to the id for
332
+ * its `name`, like a description-less directory on the filesystem.
333
+ * @param options {object}
334
+ * @param options.spaceId {string}
335
+ * @returns {Promise<CollectionSummary[]>}
336
+ */
337
+ listCollections({ spaceId }: {
338
+ spaceId: string;
339
+ }): Promise<CollectionSummary[]>;
340
+ /**
341
+ * Lists a Collection's Resources, cursor-paginated with the same keyset
342
+ * (ascending `resourceId`, byte order), cursor codec, clamps, and `next`
343
+ * construction as the filesystem backend. Tombstones are invisible.
344
+ * @param options {object}
345
+ * @param options.spaceId {string}
346
+ * @param options.collectionId {string}
347
+ * @param [options.limit] {number}
348
+ * @param [options.cursor] {string}
349
+ * @param [options.collectionDescription] {CollectionDescription}
350
+ * @returns {Promise<CollectionResourcesList>}
351
+ */
352
+ listCollectionItems({ spaceId, collectionId, limit, cursor, collectionDescription: providedDescription }: {
353
+ spaceId: string;
354
+ collectionId: string;
355
+ limit?: number;
356
+ cursor?: string;
357
+ collectionDescription?: CollectionDescription;
358
+ }): Promise<CollectionResourcesList>;
359
+ /**
360
+ * Writes a Resource representation as one transaction: row lock, shared
361
+ * precondition evaluation (exact filesystem semantics -- a tombstone counts
362
+ * as "not exists", `ifNoneMatch` precedence per RFC9110), monotonic
363
+ * `version` bump continuing through delete/recreate, and the transactional
364
+ * quota delta. JSON is stored as its serialized UTF-8 bytes; blobs buffer
365
+ * through the capped accumulator.
366
+ * @param options {object}
367
+ * @param options.spaceId {string}
368
+ * @param options.collectionId {string}
369
+ * @param options.resourceId {string}
370
+ * @param options.input {ResourceInput}
371
+ * @param [options.createdBy] {string} DID of the invoker, recorded as the
372
+ * Resource's `createdBy` on first write only
373
+ * @param [options.ifMatch] {string}
374
+ * @param [options.ifNoneMatch] {boolean}
375
+ * @returns {Promise<{ version: number }>}
376
+ */
377
+ writeResource({ spaceId, collectionId, resourceId, input, createdBy, epoch, ifMatch, ifNoneMatch }: {
378
+ spaceId: string;
379
+ collectionId: string;
380
+ resourceId: string;
381
+ input: ResourceInput;
382
+ createdBy?: IDID;
383
+ epoch?: string;
384
+ ifMatch?: string;
385
+ ifNoneMatch?: boolean;
386
+ }): Promise<{
387
+ version: number;
388
+ }>;
389
+ /**
390
+ * @param options {object}
391
+ * @param options.spaceId {string}
392
+ * @param options.collectionId {string}
393
+ * @param options.resourceId {string}
394
+ * @param [options.contentType] {string} advisory; ignored for lookup
395
+ * @returns {Promise<ResourceResult>}
396
+ */
397
+ getResource({ spaceId, collectionId, resourceId }: {
398
+ spaceId: string;
399
+ collectionId: string;
400
+ resourceId: string;
401
+ contentType?: string;
402
+ }): Promise<ResourceResult>;
403
+ /**
404
+ * Soft-deletes a Resource into a tombstone row: content dropped, `deleted`
405
+ * set, `version` bumped (so the change feed surfaces it), last-known
406
+ * `content_type` retained, `custom` / `meta_version` dropped, and the freed
407
+ * bytes subtracted from the quota counter -- one transaction. Idempotent on
408
+ * an absent Resource or an existing tombstone.
409
+ * @param options {object}
410
+ * @param options.spaceId {string}
411
+ * @param options.collectionId {string}
412
+ * @param options.resourceId {string}
413
+ * @param [options.ifMatch] {string}
414
+ * @returns {Promise<void>}
415
+ */
416
+ deleteResource({ spaceId, collectionId, resourceId, ifMatch }: {
417
+ spaceId: string;
418
+ collectionId: string;
419
+ resourceId: string;
420
+ ifMatch?: string;
421
+ }): Promise<void>;
422
+ /**
423
+ * Reads the metadata of a Resource's current representation. Tombstones and
424
+ * absent Resources resolve `undefined`. `custom` is included only when
425
+ * non-empty, verbatim (`{ name, tags }` or the opaque envelope).
426
+ * @param options {object}
427
+ * @param options.spaceId {string}
428
+ * @param options.collectionId {string}
429
+ * @param options.resourceId {string}
430
+ * @returns {Promise<(ResourceMetadata & { createdBy?: IDID; version?:
431
+ * number; metaVersion?: number }) | undefined>}
432
+ */
433
+ getResourceMetadata({ spaceId, collectionId, resourceId }: {
434
+ spaceId: string;
435
+ collectionId: string;
436
+ resourceId: string;
437
+ }): Promise<(ResourceMetadata & {
438
+ createdBy?: IDID;
439
+ version?: number;
440
+ metaVersion?: number;
441
+ }) | undefined>;
442
+ /**
443
+ * Replaces the user-writable `custom` object (full replacement; `{}`
444
+ * clears), bumping `updatedAt` and the independent `metaVersion` -- one
445
+ * row-locked transaction, preconditions evaluated on the current
446
+ * `metaVersion` via the shared helper. Resolves `undefined` (no create) for
447
+ * an absent or tombstoned Resource.
448
+ * @param options {object}
449
+ * @param options.spaceId {string}
450
+ * @param options.collectionId {string}
451
+ * @param options.resourceId {string}
452
+ * @param options.custom {ResourceMetadataCustom | Record<string, unknown>}
453
+ * @param [options.ifMatch] {string}
454
+ * @param [options.ifNoneMatch] {boolean}
455
+ * @returns {Promise<{ metaVersion: number } | undefined>}
456
+ */
457
+ writeResourceMetadata({ spaceId, collectionId, resourceId, custom, epoch, ifMatch, ifNoneMatch }: {
458
+ spaceId: string;
459
+ collectionId: string;
460
+ resourceId: string;
461
+ custom: ResourceMetadataCustom | Record<string, unknown>;
462
+ epoch?: string;
463
+ ifMatch?: string;
464
+ ifNoneMatch?: boolean;
465
+ }): Promise<{
466
+ metaVersion: number;
467
+ } | undefined>;
468
+ /**
469
+ * Replication change feed (the `changes` query profile): one indexed keyset
470
+ * query over `(updatedAt, resourceId)`, tombstones included, JSON documents
471
+ * only, bodies parsed for the returned page.
472
+ * @param options {object}
473
+ * @param options.spaceId {string}
474
+ * @param options.collectionId {string}
475
+ * @param [options.checkpoint] {{ id: string, updatedAt: string }}
476
+ * @param options.limit {number}
477
+ * @returns {Promise<{ documents: Array<object>, checkpoint: object | null }>}
478
+ */
479
+ changesSince({ spaceId, collectionId, checkpoint, limit }: {
480
+ spaceId: string;
481
+ collectionId: string;
482
+ checkpoint?: {
483
+ id: string;
484
+ updatedAt: string;
485
+ };
486
+ limit: number;
487
+ }): Promise<{
488
+ documents: Array<{
489
+ resourceId: string;
490
+ version: number;
491
+ metaVersion?: number;
492
+ createdBy?: IDID;
493
+ updatedAt: string;
494
+ deleted: boolean;
495
+ data?: unknown;
496
+ custom?: ResourceMetadataCustom | Record<string, unknown>;
497
+ epoch?: string;
498
+ }>;
499
+ checkpoint: {
500
+ id: string;
501
+ updatedAt: string;
502
+ } | null;
503
+ }>;
504
+ /**
505
+ * Blinded-index query (the `blinded-index` query profile; see the
506
+ * `StorageBackend.queryByBlindedIndex` contract). Selects the Collection's
507
+ * live JSON rows, parses each body, and hands the candidates to the shared
508
+ * evaluator (`lib/blindedIndex.ts`) for matching, ordering, and cursor
509
+ * pagination -- identical semantics to the filesystem backend. A full scan
510
+ * of the Collection per call, deliberate for this teaching backend; an
511
+ * indexed variant would flatten the blinded attributes into an indexed
512
+ * token side-table (the bedrock-edv-storage strategy). Unparsable JSON is
513
+ * skipped.
514
+ * @param options {object}
515
+ * @param options.spaceId {string}
516
+ * @param options.collectionId {string}
517
+ * @param options.query {BlindedIndexQuery}
518
+ * @param [options.count] {boolean} return only the match count
519
+ * @param [options.limit] {number} requested page size
520
+ * @param [options.cursor] {string} opaque cursor from a prior page
521
+ * @returns {Promise<{ count: number } | BlindedIndexQueryPage>}
522
+ */
523
+ queryByBlindedIndex({ spaceId, collectionId, query, count, limit, cursor }: {
524
+ spaceId: string;
525
+ collectionId: string;
526
+ query: BlindedIndexQuery;
527
+ count?: boolean;
528
+ limit?: number;
529
+ cursor?: string;
530
+ }): Promise<{
531
+ count: number;
532
+ } | BlindedIndexQueryPage>;
533
+ /**
534
+ * Maps the optional-id policy addressing onto the sentinel-column primary
535
+ * key: Space policy `('', '')`, Collection policy `(cid, '')`, Resource
536
+ * policy `(cid, rid)`.
537
+ * @param options {object}
538
+ * @param [options.collectionId] {string}
539
+ * @param [options.resourceId] {string}
540
+ * @returns {{ collectionKey: string, resourceKey: string }}
541
+ */
542
+ private _policyKey;
543
+ /**
544
+ * @param options {object}
545
+ * @param options.spaceId {string}
546
+ * @param [options.collectionId] {string}
547
+ * @param [options.resourceId] {string}
548
+ * @returns {Promise<PolicyDocument|undefined>}
549
+ */
550
+ getPolicy({ spaceId, collectionId, resourceId }: {
551
+ spaceId: string;
552
+ collectionId?: string;
553
+ resourceId?: string;
554
+ }): Promise<PolicyDocument | undefined>;
555
+ /**
556
+ * @param options {object}
557
+ * @param options.spaceId {string}
558
+ * @param [options.collectionId] {string}
559
+ * @param [options.resourceId] {string}
560
+ * @param options.policy {PolicyDocument}
561
+ * @returns {Promise<void>}
562
+ */
563
+ writePolicy({ spaceId, collectionId, resourceId, policy }: {
564
+ spaceId: string;
565
+ collectionId?: string;
566
+ resourceId?: string;
567
+ policy: PolicyDocument;
568
+ }): Promise<void>;
569
+ /**
570
+ * The one policy upsert statement, shared by `writePolicy` and the import
571
+ * apply loop; keys through `_policyKey` so the sentinel convention lives in
572
+ * one place.
573
+ * @param options {object}
574
+ * @param options.queryable {Queryable}
575
+ * @param options.spaceId {string}
576
+ * @param [options.collectionId] {string}
577
+ * @param [options.resourceId] {string}
578
+ * @param options.policy {PolicyDocument}
579
+ * @returns {Promise<void>}
580
+ */
581
+ private _upsertPolicy;
582
+ /**
583
+ * @param options {object}
584
+ * @param options.spaceId {string}
585
+ * @param [options.collectionId] {string}
586
+ * @param [options.resourceId] {string}
587
+ * @returns {Promise<void>} idempotent
588
+ */
589
+ deletePolicy({ spaceId, collectionId, resourceId }: {
590
+ spaceId: string;
591
+ collectionId?: string;
592
+ resourceId?: string;
593
+ }): Promise<void>;
594
+ /**
595
+ * Persists a full (secret-bearing) backend-registration record. Upsert.
596
+ * @param options {object}
597
+ * @param options.spaceId {string}
598
+ * @param options.backendId {string}
599
+ * @param options.record {StoredBackendRecord}
600
+ * @returns {Promise<void>}
601
+ */
602
+ writeBackend({ spaceId, backendId, record }: {
603
+ spaceId: string;
604
+ backendId: string;
605
+ record: StoredBackendRecord;
606
+ }): Promise<void>;
607
+ /**
608
+ * The full (secret-bearing) record, or `undefined`. The only method that
609
+ * exposes secret connection material -- internal use.
610
+ * @param options {object}
611
+ * @param options.spaceId {string}
612
+ * @param options.backendId {string}
613
+ * @returns {Promise<StoredBackendRecord|undefined>}
614
+ */
615
+ getBackend({ spaceId, backendId }: {
616
+ spaceId: string;
617
+ backendId: string;
618
+ }): Promise<StoredBackendRecord | undefined>;
619
+ /**
620
+ * The Space's registered backends, **sanitized** (mapped through
621
+ * `sanitizeBackendRecord` -- the secret boundary is unchanged), sorted by id.
622
+ * @param options {object}
623
+ * @param options.spaceId {string}
624
+ * @returns {Promise<BackendDescriptor[]>}
625
+ */
626
+ listBackends({ spaceId }: {
627
+ spaceId: string;
628
+ }): Promise<BackendDescriptor[]>;
629
+ /**
630
+ * @param options {object}
631
+ * @param options.spaceId {string}
632
+ * @param options.backendId {string}
633
+ * @returns {Promise<void>} idempotent
634
+ */
635
+ deleteBackend({ spaceId, backendId }: {
636
+ spaceId: string;
637
+ backendId: string;
638
+ }): Promise<void>;
639
+ /**
640
+ * Persists a keystore config unconditionally (the create path; local ids
641
+ * are server-generated random values). The queryable/gated fields
642
+ * (`controller`, `sequence`, `kmsModule`) are denormalized alongside the
643
+ * verbatim config.
644
+ * @param options {object}
645
+ * @param options.keystoreId {string}
646
+ * @param options.config {KeystoreConfig}
647
+ * @returns {Promise<void>}
648
+ */
649
+ writeKeystore({ keystoreId, config }: {
650
+ keystoreId: string;
651
+ config: KeystoreConfig;
652
+ }): Promise<void>;
653
+ /**
654
+ * @param options {object}
655
+ * @param options.keystoreId {string}
656
+ * @returns {Promise<KeystoreConfig|undefined>}
657
+ */
658
+ getKeystore({ keystoreId }: {
659
+ keystoreId: string;
660
+ }): Promise<KeystoreConfig | undefined>;
661
+ /**
662
+ * Replaces a keystore config, gated atomically in one conditional `UPDATE`:
663
+ * the row must exist with `sequence` exactly one less than the incoming
664
+ * config's and an unchanged `kmsModule`. Zero rows updated -- missing
665
+ * keystore, stale sequence, or module change alike -- rejects with the
666
+ * protocol's single merged 409 (`KeystoreStateConflictError`).
667
+ * @param options {object}
668
+ * @param options.keystoreId {string}
669
+ * @param options.config {KeystoreConfig}
670
+ * @returns {Promise<void>}
671
+ */
672
+ updateKeystore({ keystoreId, config }: {
673
+ keystoreId: string;
674
+ config: KeystoreConfig;
675
+ }): Promise<void>;
676
+ /**
677
+ * Every stored keystore config whose `controller` matches, sorted by local
678
+ * id (the request layer caps the wire result).
679
+ * @param options {object}
680
+ * @param options.controller {IDID}
681
+ * @returns {Promise<KeystoreConfig[]>}
682
+ */
683
+ listKeystoresByController({ controller }: {
684
+ controller: IDID;
685
+ }): Promise<KeystoreConfig[]>;
686
+ /**
687
+ * Inserts a key record, create-only: the primary key enforces the
688
+ * `(keystoreId, localId)` uniqueness atomically; a duplicate rejects with
689
+ * the protocol's 409 (`KeyIdConflictError`). The record is stored verbatim
690
+ * (opaque to storage -- the at-rest cipher applies above the backend).
691
+ * @param options {object}
692
+ * @param options.keystoreId {string}
693
+ * @param options.localId {string}
694
+ * @param options.record {KmsKeyRecord}
695
+ * @returns {Promise<void>}
696
+ */
697
+ insertKey({ keystoreId, localId, record }: {
698
+ keystoreId: string;
699
+ localId: string;
700
+ record: KmsKeyRecord;
701
+ }): Promise<void>;
702
+ /**
703
+ * @param options {object}
704
+ * @param options.keystoreId {string}
705
+ * @param options.localId {string}
706
+ * @returns {Promise<KmsKeyRecord|undefined>}
707
+ */
708
+ getKey({ keystoreId, localId }: {
709
+ keystoreId: string;
710
+ localId: string;
711
+ }): Promise<KmsKeyRecord | undefined>;
712
+ /**
713
+ * Every stored key record under the keystore, sorted by local id (the request
714
+ * layer caps and paginates the wire result). An empty keystore resolves an
715
+ * empty list. The record is returned verbatim -- the at-rest cipher applies
716
+ * above the backend.
717
+ * @param options {object}
718
+ * @param options.keystoreId {string}
719
+ * @returns {Promise<Array<{ localId: string, record: KmsKeyRecord }>>}
720
+ */
721
+ listKeys({ keystoreId }: {
722
+ keystoreId: string;
723
+ }): Promise<Array<{
724
+ localId: string;
725
+ record: KmsKeyRecord;
726
+ }>>;
727
+ /**
728
+ * Resolves a revocation scope to the table it lives in, the scope column
729
+ * within that table, and the scope id value. The returned `table` and
730
+ * `column` are server-chosen constants (never user input), so callers may
731
+ * safely interpolate them into a SQL template; `id` remains a bound value.
732
+ * @param scope {RevocationScope}
733
+ * @returns {{ table: string, column: string, id: string }}
734
+ */
735
+ private _revocationTable;
736
+ /**
737
+ * Inserts a revocation record, create-only on
738
+ * `(scope id, delegator, capability.id)`; a duplicate rejects with the
739
+ * protocol's 409 (`DuplicateRevocationError`).
740
+ * @param options {object}
741
+ * @param options.scope {RevocationScope}
742
+ * @param options.record {RevocationRecord}
743
+ * @returns {Promise<void>}
744
+ */
745
+ insertRevocation({ scope, record }: {
746
+ scope: RevocationScope;
747
+ record: RevocationRecord;
748
+ }): Promise<void>;
749
+ /**
750
+ * True when any of the given capabilities has a stored, unexpired
751
+ * revocation under the scope. A single read-only SELECT: rows past their
752
+ * `meta.expires` GC horizon are filtered out in the predicate rather than
753
+ * pruned here -- this runs on every delegated-chain verification, so it
754
+ * must not write; `insertRevocation` prunes on the (rare) write path
755
+ * instead. ISO-8601 strings compare correctly under the column's byte-order
756
+ * collation.
757
+ * @param options {object}
758
+ * @param options.scope {RevocationScope}
759
+ * @param options.capabilities {CapabilitySummary[]}
760
+ * @returns {Promise<boolean>}
761
+ */
762
+ isRevoked({ scope, capabilities }: {
763
+ scope: RevocationScope;
764
+ capabilities: CapabilitySummary[];
765
+ }): Promise<boolean>;
766
+ /**
767
+ * Builds the filesystem-dialect sidecar object for a resource row (the
768
+ * `.meta.<id>.json` shape), field order matching the filesystem writer so
769
+ * archives stay as close to byte-compatible as jsonb round-tripping allows.
770
+ * @param row {Omit<ResourceRow, 'content'>}
771
+ * @returns {SidecarShape}
772
+ */
773
+ private _sidecarFor;
774
+ /**
775
+ * Exports the Space as a tar stream in the exact filesystem on-disk layout
776
+ * (same file-name codecs, same manifest), so the archive imports into
777
+ * either backend. Backend registration records are excluded (secret
778
+ * material), exactly as on the filesystem.
779
+ * @param options {object}
780
+ * @param options.spaceId {string}
781
+ * @returns {Promise<Readable>} tar-stream pack
782
+ */
783
+ exportSpace({ spaceId }: {
784
+ spaceId: string;
785
+ }): Promise<Readable>;
786
+ /**
787
+ * Fetches one resource's content bytes for the export pack loop. A row
788
+ * deleted or tombstoned between the metadata pass and this read (the export
789
+ * is not one transaction, same as the filesystem's racy directory walk)
790
+ * yields an empty body rather than failing the whole archive.
791
+ * @param options {object}
792
+ * @param options.spaceId {string}
793
+ * @param options.collectionId {string}
794
+ * @param options.resourceId {string}
795
+ * @returns {Promise<Buffer>}
796
+ */
797
+ private _resourceContent;
798
+ /**
799
+ * Merges a WAS space-export tarball into an existing Space with the same
800
+ * three-invariant pre-flight (per-entry 413, fail-closed 422 encryption
801
+ * conformance, cumulative 507) and skip-not-overwrite merge semantics as
802
+ * the filesystem backend -- including tombstone carry-over and "a tombstone
803
+ * blocks resurrection". One strict improvement: the entire apply loop runs
804
+ * in a single transaction, so a mid-import failure leaves the Space
805
+ * untouched atomically.
806
+ * @param options {object}
807
+ * @param options.spaceId {string}
808
+ * @param options.tarStream {Readable}
809
+ * @returns {Promise<ImportStats>}
810
+ */
811
+ importSpace({ spaceId, tarStream }: {
812
+ spaceId: string;
813
+ tarStream: Readable;
814
+ }): Promise<ImportStats>;
815
+ /**
816
+ * Inserts one archived resource (or orphan tombstone) row for the import
817
+ * apply loop. Timestamps, versions, `createdBy`, and `custom` come from the
818
+ * archive's sidecar when present; an archive resource without a sidecar is
819
+ * treated as a fresh first write on this backend (version 1, no
820
+ * `createdBy`).
821
+ * @param options {object}
822
+ * @param options.client {pg.PoolClient}
823
+ * @param options.spaceId {string}
824
+ * @param options.collectionId {string}
825
+ * @param options.resourceId {string}
826
+ * @param options.contentType {string}
827
+ * @param options.body {Buffer|null} `null` re-creates a tombstone
828
+ * @param [options.sidecar] {SidecarShape}
829
+ * @returns {Promise<void>}
830
+ */
831
+ private _insertImportedResource;
832
+ }
833
+ //# sourceMappingURL=postgres.d.ts.map