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,231 @@
1
+ /**
2
+ * PostgreSQL schema for the `PostgresBackend`: an ordered list of migration
3
+ * scripts plus the tiny hand-rolled runner that applies them idempotently
4
+ * (inside an advisory-locked transaction) on backend `init()`. The whole
5
+ * schema is readable in this one file; there is no external migration tool.
6
+ *
7
+ * Collation note: every identifier or ISO-8601 timestamp column that
8
+ * participates in an `ORDER BY` or keyset `>` comparison is declared
9
+ * `COLLATE "C"` (byte order, which for UTF-8 is code-point order) so Postgres
10
+ * ordering agrees with the JS code-unit comparisons the filesystem backend and
11
+ * the pagination cursors use. (The two orders could theoretically diverge for
12
+ * supplementary-plane characters -- unreachable here because the request layer
13
+ * validates ids as URL-safe ASCII.) Timestamps are stored as the fixed-width
14
+ * ISO-8601 strings the wire model uses (`new Date().toISOString()`), not
15
+ * `timestamptz`, so change-feed checkpoints round-trip byte-identically.
16
+ */
17
+ import type pg from 'pg'
18
+
19
+ /**
20
+ * Ordered migration scripts. Version `n` is `MIGRATIONS[n - 1]`; append only,
21
+ * never edit an applied entry.
22
+ */
23
+ export const MIGRATIONS: string[] = [
24
+ // v1: the full WAS + WebKMS surface.
25
+ `
26
+ -- The Spaces tree. 'description' is NULL for a placeholder row created by a
27
+ -- write below the Space level (a resource/policy write to a Space whose
28
+ -- description was never written) -- the analogue of a Space directory
29
+ -- without a '.space.' file; getSpaceDescription treats it as absent.
30
+ -- 'usage_bytes' is the transactional quota counter (spec "Quotas"),
31
+ -- maintained in the same transaction as every content write and delete.
32
+ CREATE TABLE spaces (
33
+ space_id text COLLATE "C" PRIMARY KEY,
34
+ description jsonb,
35
+ usage_bytes bigint NOT NULL DEFAULT 0
36
+ );
37
+
38
+ CREATE TABLE collections (
39
+ space_id text COLLATE "C" NOT NULL
40
+ REFERENCES spaces ON DELETE CASCADE,
41
+ collection_id text COLLATE "C" NOT NULL,
42
+ description jsonb,
43
+ PRIMARY KEY (space_id, collection_id)
44
+ );
45
+
46
+ -- One row per Resource, live or tombstoned. 'content' is the byte-for-byte
47
+ -- representation (JSON stored as its serialized UTF-8 bytes, NOT jsonb --
48
+ -- jsonb normalization would break byte fidelity); NULL on a tombstone.
49
+ -- 'content_type' records the last-known type on a tombstone. 'version' /
50
+ -- 'meta_version' are the two ETag validators; 'custom' is the user-writable
51
+ -- metadata (or the opaque encryption envelope on an encrypted Collection).
52
+ CREATE TABLE resources (
53
+ space_id text COLLATE "C" NOT NULL,
54
+ collection_id text COLLATE "C" NOT NULL,
55
+ resource_id text COLLATE "C" NOT NULL,
56
+ content_type text NOT NULL,
57
+ content bytea,
58
+ is_json boolean NOT NULL,
59
+ size_bytes bigint NOT NULL DEFAULT 0,
60
+ version integer NOT NULL,
61
+ meta_version integer,
62
+ custom jsonb,
63
+ deleted boolean NOT NULL DEFAULT false,
64
+ created_at text COLLATE "C" NOT NULL,
65
+ updated_at text COLLATE "C" NOT NULL,
66
+ PRIMARY KEY (space_id, collection_id, resource_id),
67
+ FOREIGN KEY (space_id, collection_id)
68
+ REFERENCES collections ON DELETE CASCADE
69
+ );
70
+
71
+ -- The changes-feed keyset: (updatedAt, resourceId) ascending within a
72
+ -- collection. Also serves the tie-broken seek.
73
+ CREATE INDEX resources_changes_idx
74
+ ON resources (space_id, collection_id, updated_at, resource_id);
75
+
76
+ -- Policies for all three levels in one table; '' sentinel columns keep the
77
+ -- primary key total (Postgres PKs reject NULL). Space policy: ('', '');
78
+ -- collection policy: (cid, ''); resource policy: (cid, rid).
79
+ CREATE TABLE policies (
80
+ space_id text COLLATE "C" NOT NULL
81
+ REFERENCES spaces ON DELETE CASCADE,
82
+ collection_id text COLLATE "C" NOT NULL DEFAULT '',
83
+ resource_id text COLLATE "C" NOT NULL DEFAULT '',
84
+ policy jsonb NOT NULL,
85
+ PRIMARY KEY (space_id, collection_id, resource_id)
86
+ );
87
+
88
+ -- Registered external backend records; 'record' is the full secret-bearing
89
+ -- StoredBackendRecord (same custody posture as the filesystem file).
90
+ CREATE TABLE backend_records (
91
+ space_id text COLLATE "C" NOT NULL REFERENCES spaces ON DELETE CASCADE,
92
+ backend_id text COLLATE "C" NOT NULL,
93
+ record jsonb NOT NULL,
94
+ PRIMARY KEY (space_id, backend_id)
95
+ );
96
+
97
+ -- WebKMS facet: a sibling tree to spaces, exactly as on the filesystem.
98
+ -- 'controller' / 'sequence' / 'kms_module' are denormalized from the
99
+ -- verbatim config for the list filter and the update gates.
100
+ CREATE TABLE keystores (
101
+ keystore_id text COLLATE "C" PRIMARY KEY,
102
+ controller text NOT NULL,
103
+ sequence integer NOT NULL,
104
+ kms_module text NOT NULL,
105
+ config jsonb NOT NULL
106
+ );
107
+ CREATE INDEX keystores_controller_idx ON keystores (controller);
108
+
109
+ -- 'record' is the opaque (possibly envelope-encrypted above the backend)
110
+ -- KmsKeyRecord, stored verbatim; the primary key is the create-only gate.
111
+ CREATE TABLE kms_keys (
112
+ keystore_id text COLLATE "C" NOT NULL
113
+ REFERENCES keystores ON DELETE CASCADE,
114
+ local_id text COLLATE "C" NOT NULL,
115
+ record jsonb NOT NULL,
116
+ PRIMARY KEY (keystore_id, local_id)
117
+ );
118
+
119
+ -- 'expires' is meta.expires (the GC horizon), NULL = never.
120
+ CREATE TABLE revocations (
121
+ keystore_id text COLLATE "C" NOT NULL
122
+ REFERENCES keystores ON DELETE CASCADE,
123
+ delegator text COLLATE "C" NOT NULL,
124
+ capability_id text COLLATE "C" NOT NULL,
125
+ record jsonb NOT NULL,
126
+ expires text COLLATE "C",
127
+ PRIMARY KEY (keystore_id, delegator, capability_id)
128
+ );
129
+ `,
130
+ // v2: denormalize a Space's controller onto its own column for the Spaces
131
+ // count quota (spec "Quotas"). The controller also lives inside 'description'
132
+ // (description->>'controller'), but a dedicated, indexed column keeps the
133
+ // per-controller COUNT(*) cheap and lockable. Backfill from existing rows;
134
+ // writeSpace maintains it on every insert and update.
135
+ `
136
+ ALTER TABLE spaces ADD COLUMN controller text;
137
+ UPDATE spaces SET controller = description->>'controller';
138
+ CREATE INDEX spaces_controller_idx ON spaces (controller);
139
+ `,
140
+ // v3: record a Resource's creator -- the DID of the invoker of its FIRST
141
+ // content write, set once and preserved verbatim thereafter (the spec's
142
+ // OPTIONAL `createdBy`). NULL for a pre-existing row and for one written by
143
+ // a caller with no resolved invoker. Not `COLLATE "C"`: unlike `created_at` /
144
+ // `updated_at`, this column never participates in an `ORDER BY` or keyset
145
+ // comparison.
146
+ `
147
+ ALTER TABLE resources ADD COLUMN created_by text;
148
+ `,
149
+ // v4: Space-scoped zcap revocations -- the WAS route families' sibling of the
150
+ // keystore-scoped `revocations` table above. A separate table (rather than a
151
+ // nullable-FK union over one table) lets each keep its own `ON DELETE CASCADE`
152
+ // to its parent and its own composite primary key; deleting a Space here
153
+ // deletes its revocations. Columns mirror `revocations`, with 'space_id'
154
+ // referencing the spaces tree in place of 'keystore_id'.
155
+ `
156
+ CREATE TABLE space_revocations (
157
+ space_id text COLLATE "C" NOT NULL REFERENCES spaces ON DELETE CASCADE,
158
+ delegator text COLLATE "C" NOT NULL,
159
+ capability_id text COLLATE "C" NOT NULL,
160
+ record jsonb NOT NULL,
161
+ expires text COLLATE "C",
162
+ PRIMARY KEY (space_id, delegator, capability_id)
163
+ );
164
+ `,
165
+ // v5: the client-declared key epoch a Resource's content was encrypted under
166
+ // (multi-recipient encrypted Collections). Stored opaquely: the server never
167
+ // computes or verifies it. NULL when no epoch was declared (a plaintext
168
+ // Collection, or an encrypted write that omitted the stamp). Not `COLLATE "C"`
169
+ // -- it never participates in an `ORDER BY` or keyset comparison.
170
+ `
171
+ ALTER TABLE resources ADD COLUMN epoch text;
172
+ `,
173
+ // v6: the monotonic Collection Description version -- the ETag validator behind
174
+ // conditional (`If-Match`) Collection Description writes, so concurrent
175
+ // recipient edits compare-and-swap instead of clobbering. Kept out of the
176
+ // stored 'description' jsonb (it travels only as the `ETag` header). Backfill
177
+ // existing rows to 1; writeCollection bumps it on every write.
178
+ `
179
+ ALTER TABLE collections ADD COLUMN description_version integer NOT NULL DEFAULT 1;
180
+ `
181
+ ]
182
+
183
+ /**
184
+ * Applies any not-yet-applied migrations, inside a transaction holding a
185
+ * schema-scoped advisory lock so concurrent server instances sharing one
186
+ * database serialize their startup migration runs. Idempotent: applied
187
+ * versions are recorded in `schema_migrations` and skipped on the next run.
188
+ * @param options {object}
189
+ * @param options.client {pg.PoolClient} a dedicated client (not the pool);
190
+ * the caller is responsible for releasing it
191
+ * @returns {Promise<void>}
192
+ */
193
+ export async function applyMigrations({
194
+ client
195
+ }: {
196
+ client: pg.PoolClient
197
+ }): Promise<void> {
198
+ await client.query('BEGIN')
199
+ try {
200
+ // Scope the advisory lock to the active schema so parallel test schemas
201
+ // in one database do not serialize against each other.
202
+ await client.query(
203
+ `SELECT pg_advisory_xact_lock(hashtext(current_schema() || ':was-migrations'))`
204
+ )
205
+ await client.query(`
206
+ CREATE TABLE IF NOT EXISTS schema_migrations (
207
+ version integer PRIMARY KEY,
208
+ applied_at timestamptz NOT NULL DEFAULT now()
209
+ )
210
+ `)
211
+ const { rows } = await client.query<{ version: number }>(
212
+ 'SELECT version FROM schema_migrations'
213
+ )
214
+ const applied = new Set(rows.map(row => row.version))
215
+ for (let index = 0; index < MIGRATIONS.length; index++) {
216
+ const version = index + 1
217
+ if (applied.has(version)) {
218
+ continue
219
+ }
220
+ await client.query(MIGRATIONS[index]!)
221
+ await client.query(
222
+ 'INSERT INTO schema_migrations (version) VALUES ($1)',
223
+ [version]
224
+ )
225
+ }
226
+ await client.query('COMMIT')
227
+ } catch (err) {
228
+ await client.query('ROLLBACK')
229
+ throw err
230
+ }
231
+ }