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,134 @@
1
+ /**
2
+ * Request handlers for Resource operations: create-by-id, get, and delete a
3
+ * Resource (JSON object or binary blob).
4
+ */
5
+ import type { FastifyReply, FastifyRequest } from 'fastify';
6
+ export declare class ResourceRequest {
7
+ /**
8
+ * PUT /space/:spaceId/:collectionId/:resourceId
9
+ * Request handler for "Create (or Update) Resource by Id" request
10
+ * Before this, `parseAuthHeaders()` hook executed, resulting in:
11
+ * request.zcap: {
12
+ * keyId, headers, signature, created, expires, invocation, digest
13
+ * }
14
+ *
15
+ * @param request {import('fastify').FastifyRequest}
16
+ * @param reply {import('fastify').FastifyReply}
17
+ * @returns {Promise<FastifyReply>}
18
+ */
19
+ static put(request: FastifyRequest<{
20
+ Params: {
21
+ spaceId: string;
22
+ collectionId: string;
23
+ resourceId: string;
24
+ };
25
+ }>, reply: FastifyReply): Promise<FastifyReply>;
26
+ /**
27
+ * GET /space/:spaceId/:collectionId/:resourceId
28
+ * Request handler for "Get Resource" request
29
+ * Before this, `parseAuthHeaders()` hook executed, resulting in:
30
+ * request.zcap: {
31
+ * keyId, headers, signature, created, expires, invocation, digest
32
+ * }
33
+ *
34
+ * @param request {import('fastify').FastifyRequest}
35
+ * @param reply {import('fastify').FastifyReply}
36
+ * @returns {Promise<FastifyReply>}
37
+ */
38
+ static get(request: FastifyRequest<{
39
+ Params: {
40
+ spaceId: string;
41
+ collectionId: string;
42
+ resourceId: string;
43
+ };
44
+ }>, reply: FastifyReply): Promise<FastifyReply>;
45
+ /**
46
+ * HEAD /space/:spaceId/:collectionId/:resourceId
47
+ * Request handler for "Head Resource" request: the same authorization as Get
48
+ * Resource but with no response body. Per spec "Content Types and
49
+ * Representations", the response `Content-Type` and `Content-Length`
50
+ * correspond to the `contentType` and `size` of the Resource's Metadata object
51
+ * (the bytes a GET would return). HEAD is a safe method, authorized as a read
52
+ * (capability-or-policy), the same as GET; it reads only the Metadata so it
53
+ * never opens the resource byte stream.
54
+ *
55
+ * Registered explicitly (ahead of the GET route) rather than relying on
56
+ * Fastify's auto-exposed HEAD, which would share the GET handler and stream
57
+ * the whole body -- yielding no `Content-Length` for a streamed representation.
58
+ *
59
+ * @param request {import('fastify').FastifyRequest}
60
+ * @param reply {import('fastify').FastifyReply}
61
+ * @returns {Promise<FastifyReply>}
62
+ */
63
+ static head(request: FastifyRequest<{
64
+ Params: {
65
+ spaceId: string;
66
+ collectionId: string;
67
+ resourceId: string;
68
+ };
69
+ }>, reply: FastifyReply): Promise<FastifyReply>;
70
+ /**
71
+ * GET /space/:spaceId/:collectionId/:resourceId/meta
72
+ * Request handler for "Read Resource Metadata" request. Returns the REQUIRED
73
+ * server-managed fields (`contentType`, `size`), the OPTIONAL `createdAt` /
74
+ * `updatedAt` timestamps, and the user-writable `custom` object (omitted when
75
+ * empty). Authorization is capability-or-policy, the same as Get Resource:
76
+ * metadata reveals nothing beyond what a GET of the resource itself exposes via
77
+ * Content-Type / Content-Length, so a `PublicCanRead` policy also grants
78
+ * metadata reads.
79
+ *
80
+ * @param request {import('fastify').FastifyRequest}
81
+ * @param reply {import('fastify').FastifyReply}
82
+ * @returns {Promise<FastifyReply>}
83
+ */
84
+ static getMeta(request: FastifyRequest<{
85
+ Params: {
86
+ spaceId: string;
87
+ collectionId: string;
88
+ resourceId: string;
89
+ };
90
+ }>, reply: FastifyReply): Promise<FastifyReply>;
91
+ /**
92
+ * PUT /space/:spaceId/:collectionId/:resourceId/meta
93
+ * Request handler for "Update Resource Metadata" request. A full replacement
94
+ * of the Metadata object's user-writable `custom` object (any property omitted
95
+ * is cleared; a body with no `custom` clears them all). Server-managed
96
+ * properties are untouched, and any top-level property other than `custom` in
97
+ * the body is ignored (so a client may GET-modify-PUT the whole object). Does
98
+ * NOT create: a `PUT` to the `/meta` of a nonexistent Resource is a 404.
99
+ * Authorization is capability-only (the `PUT` action), the same as Put
100
+ * Resource. Returns 204.
101
+ *
102
+ * @param request {import('fastify').FastifyRequest}
103
+ * @param reply {import('fastify').FastifyReply}
104
+ * @returns {Promise<FastifyReply>}
105
+ */
106
+ static putMeta(request: FastifyRequest<{
107
+ Params: {
108
+ spaceId: string;
109
+ collectionId: string;
110
+ resourceId: string;
111
+ };
112
+ Body: unknown;
113
+ }>, reply: FastifyReply): Promise<FastifyReply>;
114
+ /**
115
+ * DELETE /space/:spaceId/:collectionId/:resourceId
116
+ * Request handler for "Delete Resource" request
117
+ * Before this, `parseAuthHeaders()` hook executed, resulting in:
118
+ * request.zcap: {
119
+ * keyId, headers, signature, created, expires, invocation, digest
120
+ * }
121
+ *
122
+ * @param request {import('fastify').FastifyRequest}
123
+ * @param reply {import('fastify').FastifyReply}
124
+ * @returns {Promise<FastifyReply>}
125
+ */
126
+ static delete(request: FastifyRequest<{
127
+ Params: {
128
+ spaceId: string;
129
+ collectionId: string;
130
+ resourceId: string;
131
+ };
132
+ }>, reply: FastifyReply): Promise<FastifyReply>;
133
+ }
134
+ //# sourceMappingURL=ResourceRequest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResourceRequest.d.ts","sourceRoot":"","sources":["../../src/requests/ResourceRequest.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AA6F3D,qBAAa,eAAe;IAC1B;;;;;;;;;;;OAWG;WACU,GAAG,CACd,OAAO,EAAE,cAAc,CAAC;QACtB,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,CAAA;KACtE,CAAC,EACF,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,YAAY,CAAC;IA+ExB;;;;;;;;;;;OAWG;WACU,GAAG,CACd,OAAO,EAAE,cAAc,CAAC;QACtB,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,CAAA;KACtE,CAAC,EACF,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,YAAY,CAAC;IAgExB;;;;;;;;;;;;;;;;;OAiBG;WACU,IAAI,CACf,OAAO,EAAE,cAAc,CAAC;QACtB,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,CAAA;KACtE,CAAC,EACF,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,YAAY,CAAC;IAmExB;;;;;;;;;;;;;OAaG;WACU,OAAO,CAClB,OAAO,EAAE,cAAc,CAAC;QACtB,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,CAAA;KACtE,CAAC,EACF,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,YAAY,CAAC;IAoExB;;;;;;;;;;;;;;OAcG;WACU,OAAO,CAClB,OAAO,EAAE,cAAc,CAAC;QACtB,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,CAAA;QACrE,IAAI,EAAE,OAAO,CAAA;KACd,CAAC,EACF,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,YAAY,CAAC;IA2GxB;;;;;;;;;;;OAWG;WACU,MAAM,CACjB,OAAO,EAAE,cAAc,CAAC;QACtB,MAAM,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,CAAA;KACtE,CAAC,EACF,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,YAAY,CAAC;CAmDzB"}
@@ -0,0 +1,548 @@
1
+ import { fetchSpaceAndAuthorize, fetchSpaceAndVerify } from './spaceContext.js';
2
+ import { getCollectionOrThrow } from './collectionContext.js';
3
+ import { resolveResourceInput } from './resourceInput.js';
4
+ import { invokerDid } from '../auth-header-hooks.js';
5
+ import { resolveBackend } from '../lib/backendRegistry.js';
6
+ import { assertEncryptedWriteConforms, assertEncryptedMetaConforms } from '../lib/encryption.js';
7
+ import { assertValidIds } from '../lib/validateId.js';
8
+ import { resourcePath, metaPath } from '../lib/paths.js';
9
+ import { formatEtag, parseWritePreconditions } from '../lib/etag.js';
10
+ import { parseKeyEpochHeader, parseMetaEpoch } from '../lib/keyEpoch.js';
11
+ import { InvalidRequestBodyError, ResourceNotFoundError, rethrowOrWrapStorageError } from '../errors.js';
12
+ /**
13
+ * Validates and extracts the user-writable `custom` object from an Update
14
+ * Resource Metadata request body on a **plaintext** Collection. The body MUST be
15
+ * a JSON object; any top-level property other than `custom` is ignored (so a
16
+ * client may GET-modify-PUT the whole Metadata object). A missing `custom`
17
+ * clears all user-writable properties (returns `{}`). Throws
18
+ * `InvalidRequestBodyError` (400) when the body or `custom` shape is wrong.
19
+ *
20
+ * On an **encrypted** Collection this shape check does not apply -- `custom` is
21
+ * the opaque encryption envelope, validated structurally by
22
+ * {@link assertEncryptedMetaConforms} instead (a `422` on non-conformance).
23
+ * `putMeta` branches on the Collection's `encryption` marker after authorization.
24
+ * @param body {unknown} the parsed request body
25
+ * @returns {ResourceMetadataCustom}
26
+ */
27
+ function parseCustomMetadata(body) {
28
+ const requestName = 'Update Resource Metadata';
29
+ if (typeof body !== 'object' || body === null || Array.isArray(body)) {
30
+ throw new InvalidRequestBodyError({
31
+ requestName,
32
+ detail: 'Request body must be a JSON object.'
33
+ });
34
+ }
35
+ const { custom } = body;
36
+ if (custom === undefined) {
37
+ return {};
38
+ }
39
+ if (typeof custom !== 'object' || custom === null || Array.isArray(custom)) {
40
+ throw new InvalidRequestBodyError({
41
+ requestName,
42
+ detail: 'The `custom` property must be a JSON object.',
43
+ pointer: '/custom'
44
+ });
45
+ }
46
+ const { name, tags } = custom;
47
+ if (name !== undefined && typeof name !== 'string') {
48
+ throw new InvalidRequestBodyError({
49
+ requestName,
50
+ detail: 'The `custom.name` property must be a string.',
51
+ pointer: '/custom/name'
52
+ });
53
+ }
54
+ if (tags !== undefined &&
55
+ (typeof tags !== 'object' || tags === null || Array.isArray(tags))) {
56
+ throw new InvalidRequestBodyError({
57
+ requestName,
58
+ detail: 'The `custom.tags` property must be a JSON object.',
59
+ pointer: '/custom/tags'
60
+ });
61
+ }
62
+ // Tag values MUST be strings (spec: values SHOULD be strings; the wire type
63
+ // models them as `Record<string, string>`).
64
+ if (tags !== undefined &&
65
+ Object.values(tags).some(value => typeof value !== 'string')) {
66
+ throw new InvalidRequestBodyError({
67
+ requestName,
68
+ detail: 'Every `custom.tags` value must be a string.',
69
+ pointer: '/custom/tags'
70
+ });
71
+ }
72
+ return {
73
+ ...(name !== undefined && { name }),
74
+ ...(tags !== undefined && { tags: tags })
75
+ };
76
+ }
77
+ export class ResourceRequest {
78
+ /**
79
+ * PUT /space/:spaceId/:collectionId/:resourceId
80
+ * Request handler for "Create (or Update) Resource by Id" request
81
+ * Before this, `parseAuthHeaders()` hook executed, resulting in:
82
+ * request.zcap: {
83
+ * keyId, headers, signature, created, expires, invocation, digest
84
+ * }
85
+ *
86
+ * @param request {import('fastify').FastifyRequest}
87
+ * @param reply {import('fastify').FastifyReply}
88
+ * @returns {Promise<FastifyReply>}
89
+ */
90
+ static async put(request, reply) {
91
+ const { params: { spaceId, collectionId, resourceId } } = request;
92
+ const { storage } = request.server;
93
+ const requestName = 'Put Resource';
94
+ // Reject path-traversal / non-URL-safe ids before any storage access.
95
+ assertValidIds({ spaceId, collectionId, resourceId }, { requestName });
96
+ // Verify (capability-only): creating/updating a Resource requires a valid
97
+ // capability invocation; no access-control-policy fallback.
98
+ await fetchSpaceAndVerify({
99
+ request,
100
+ spaceId,
101
+ targetPath: resourcePath({ spaceId, collectionId, resourceId }),
102
+ requestName
103
+ });
104
+ // zCap checks out, continue
105
+ // Fetch collection by id
106
+ const collectionDescription = await getCollectionOrThrow({
107
+ storage,
108
+ spaceId,
109
+ collectionId,
110
+ requestName
111
+ });
112
+ // Fail-closed encryption enforcement: if the Collection declares a recognized
113
+ // `encryption` scheme, the content write MUST be a conforming envelope of it
114
+ // (right media type + envelope shape), else `encryption-scheme-mismatch`
115
+ // (422). Runs after auth (above) and the 404, before the body is resolved --
116
+ // so a wrong content type is rejected without consuming the upload, and the
117
+ // 422 is only observable by a caller already authorized to write here.
118
+ assertEncryptedWriteConforms({
119
+ collectionDescription,
120
+ contentType: request.headers['content-type'],
121
+ body: request.body
122
+ });
123
+ // Route resource bytes to the Collection's selected (data-plane) backend.
124
+ const dataBackend = await resolveBackend({
125
+ request,
126
+ spaceId,
127
+ collectionId,
128
+ collectionDescription
129
+ });
130
+ const input = await resolveResourceInput(request, dataBackend);
131
+ // A content write into an encrypted Collection MAY declare the key epoch it
132
+ // encrypted under via the `WAS-Key-Epoch` header (the `key-epochs` feature);
133
+ // the server stores it opaquely and clears it when absent (the new
134
+ // ciphertext's epoch is unknown -- a stale stamp is worse than none).
135
+ // Advisory, non-signature-covered metadata.
136
+ const { epoch } = parseKeyEpochHeader({
137
+ headers: request.headers,
138
+ requestName
139
+ });
140
+ // Surface any `If-Match` / `If-None-Match` write precondition to the storage
141
+ // layer, which evaluates it atomically with the write (returning 412
142
+ // `precondition-failed` on a mismatch -- rethrown unchanged below).
143
+ let written;
144
+ try {
145
+ written = await dataBackend.writeResource({
146
+ spaceId,
147
+ collectionId,
148
+ resourceId,
149
+ input,
150
+ createdBy: invokerDid(request),
151
+ epoch,
152
+ ...parseWritePreconditions(request.headers)
153
+ });
154
+ }
155
+ catch (err) {
156
+ rethrowOrWrapStorageError({ err, requestName });
157
+ }
158
+ // Return the new ETag so a client can chain a subsequent conditional write.
159
+ return reply.status(204).header('etag', formatEtag(written.version)).send();
160
+ }
161
+ /**
162
+ * GET /space/:spaceId/:collectionId/:resourceId
163
+ * Request handler for "Get Resource" request
164
+ * Before this, `parseAuthHeaders()` hook executed, resulting in:
165
+ * request.zcap: {
166
+ * keyId, headers, signature, created, expires, invocation, digest
167
+ * }
168
+ *
169
+ * @param request {import('fastify').FastifyRequest}
170
+ * @param reply {import('fastify').FastifyReply}
171
+ * @returns {Promise<FastifyReply>}
172
+ */
173
+ static async get(request, reply) {
174
+ const { params: { spaceId, collectionId, resourceId } } = request;
175
+ const { storage } = request.server;
176
+ const requestName = 'Get Resource';
177
+ // Reject path-traversal / non-URL-safe ids before any storage access.
178
+ assertValidIds({ spaceId, collectionId, resourceId }, { requestName });
179
+ // Authorize (capability-or-policy): capability invocation first, then fall
180
+ // back to the effective access-control policy (e.g. a world-readable
181
+ // Resource). Throws on denial.
182
+ await fetchSpaceAndAuthorize({
183
+ request,
184
+ spaceId,
185
+ collectionId,
186
+ resourceId,
187
+ targetPath: resourcePath({ spaceId, collectionId, resourceId }),
188
+ requestName
189
+ });
190
+ // authorized, continue
191
+ // Fetch collection by id
192
+ const collectionDescription = await getCollectionOrThrow({
193
+ storage,
194
+ spaceId,
195
+ collectionId,
196
+ requestName
197
+ });
198
+ // Read the bytes from the Collection's selected (data-plane) backend.
199
+ const dataBackend = await resolveBackend({
200
+ request,
201
+ spaceId,
202
+ collectionId,
203
+ collectionDescription
204
+ });
205
+ const contentType = request.headers['content-type'];
206
+ let result;
207
+ try {
208
+ result = await dataBackend.getResource({
209
+ spaceId,
210
+ collectionId,
211
+ resourceId,
212
+ contentType
213
+ });
214
+ }
215
+ catch (err) {
216
+ // Rethrow a typed ProblemError unchanged -- `getResource`'s
217
+ // ResourceNotFoundError (404) for an absent resource, or a typed fault a
218
+ // data-plane backend raises -- and wrap anything unexpected as a 500.
219
+ rethrowOrWrapStorageError({ err, requestName });
220
+ }
221
+ const getReply = reply.status(200).type(result.storedResourceType);
222
+ // Surface the ETag validator (the conditional-writes feature) when the
223
+ // backend tracks a version for this Resource.
224
+ if (result.version !== undefined) {
225
+ getReply.header('etag', formatEtag(result.version));
226
+ }
227
+ return getReply.send(result.resourceStream);
228
+ }
229
+ /**
230
+ * HEAD /space/:spaceId/:collectionId/:resourceId
231
+ * Request handler for "Head Resource" request: the same authorization as Get
232
+ * Resource but with no response body. Per spec "Content Types and
233
+ * Representations", the response `Content-Type` and `Content-Length`
234
+ * correspond to the `contentType` and `size` of the Resource's Metadata object
235
+ * (the bytes a GET would return). HEAD is a safe method, authorized as a read
236
+ * (capability-or-policy), the same as GET; it reads only the Metadata so it
237
+ * never opens the resource byte stream.
238
+ *
239
+ * Registered explicitly (ahead of the GET route) rather than relying on
240
+ * Fastify's auto-exposed HEAD, which would share the GET handler and stream
241
+ * the whole body -- yielding no `Content-Length` for a streamed representation.
242
+ *
243
+ * @param request {import('fastify').FastifyRequest}
244
+ * @param reply {import('fastify').FastifyReply}
245
+ * @returns {Promise<FastifyReply>}
246
+ */
247
+ static async head(request, reply) {
248
+ const { params: { spaceId, collectionId, resourceId } } = request;
249
+ const { storage } = request.server;
250
+ const requestName = 'Head Resource';
251
+ // Reject path-traversal / non-URL-safe ids before any storage access.
252
+ assertValidIds({ spaceId, collectionId, resourceId }, { requestName });
253
+ // Authorize (capability-or-policy): the same read decision as Get Resource,
254
+ // against the same target (a HEAD reveals nothing a GET would not).
255
+ await fetchSpaceAndAuthorize({
256
+ request,
257
+ spaceId,
258
+ collectionId,
259
+ resourceId,
260
+ targetPath: resourcePath({ spaceId, collectionId, resourceId }),
261
+ requestName
262
+ });
263
+ // authorized, continue
264
+ // Fetch collection by id
265
+ const collectionDescription = await getCollectionOrThrow({
266
+ storage,
267
+ spaceId,
268
+ collectionId,
269
+ requestName
270
+ });
271
+ // Read Metadata from the Collection's selected (data-plane) backend.
272
+ const dataBackend = await resolveBackend({
273
+ request,
274
+ spaceId,
275
+ collectionId,
276
+ collectionDescription
277
+ });
278
+ let metadata;
279
+ try {
280
+ metadata = await dataBackend.getResourceMetadata({
281
+ spaceId,
282
+ collectionId,
283
+ resourceId
284
+ });
285
+ }
286
+ catch (err) {
287
+ // Rethrow a typed ProblemError from the data-plane backend unchanged;
288
+ // wrap anything unexpected as a 500.
289
+ rethrowOrWrapStorageError({ err, requestName });
290
+ }
291
+ if (!metadata) {
292
+ throw new ResourceNotFoundError({ requestName });
293
+ }
294
+ // Set the payload headers a GET would send, but send no body. Fastify keeps
295
+ // a manually-set `Content-Length` on a bodyless send (it is not recomputed
296
+ // to 0).
297
+ const headReply = reply
298
+ .status(200)
299
+ .type(metadata.contentType)
300
+ .header('content-length', metadata.size);
301
+ if (metadata.version !== undefined) {
302
+ headReply.header('etag', formatEtag(metadata.version));
303
+ }
304
+ return headReply.send();
305
+ }
306
+ /**
307
+ * GET /space/:spaceId/:collectionId/:resourceId/meta
308
+ * Request handler for "Read Resource Metadata" request. Returns the REQUIRED
309
+ * server-managed fields (`contentType`, `size`), the OPTIONAL `createdAt` /
310
+ * `updatedAt` timestamps, and the user-writable `custom` object (omitted when
311
+ * empty). Authorization is capability-or-policy, the same as Get Resource:
312
+ * metadata reveals nothing beyond what a GET of the resource itself exposes via
313
+ * Content-Type / Content-Length, so a `PublicCanRead` policy also grants
314
+ * metadata reads.
315
+ *
316
+ * @param request {import('fastify').FastifyRequest}
317
+ * @param reply {import('fastify').FastifyReply}
318
+ * @returns {Promise<FastifyReply>}
319
+ */
320
+ static async getMeta(request, reply) {
321
+ const { params: { spaceId, collectionId, resourceId } } = request;
322
+ const { storage } = request.server;
323
+ const requestName = 'Get Resource Metadata';
324
+ // Reject path-traversal / non-URL-safe ids before any storage access.
325
+ assertValidIds({ spaceId, collectionId, resourceId }, { requestName });
326
+ // Authorize (capability-or-policy): the capability's `invocationTarget` is
327
+ // the full `/meta` URL (matching the request URL), and the policy level
328
+ // resolves at the resource as for Get Resource.
329
+ await fetchSpaceAndAuthorize({
330
+ request,
331
+ spaceId,
332
+ collectionId,
333
+ resourceId,
334
+ targetPath: metaPath({ spaceId, collectionId, resourceId }),
335
+ requestName
336
+ });
337
+ // authorized, continue
338
+ // Fetch collection by id
339
+ const collectionDescription = await getCollectionOrThrow({
340
+ storage,
341
+ spaceId,
342
+ collectionId,
343
+ requestName
344
+ });
345
+ // Read Metadata from the Collection's selected (data-plane) backend.
346
+ const dataBackend = await resolveBackend({
347
+ request,
348
+ spaceId,
349
+ collectionId,
350
+ collectionDescription
351
+ });
352
+ let metadata;
353
+ try {
354
+ metadata = await dataBackend.getResourceMetadata({
355
+ spaceId,
356
+ collectionId,
357
+ resourceId
358
+ });
359
+ }
360
+ catch (err) {
361
+ // Rethrow a typed ProblemError from the data-plane backend unchanged;
362
+ // wrap anything unexpected as a 500.
363
+ rethrowOrWrapStorageError({ err, requestName });
364
+ }
365
+ if (!metadata) {
366
+ throw new ResourceNotFoundError({ requestName });
367
+ }
368
+ // `version` (content) and `metaVersion` (metadata) are out-of-band ETag
369
+ // validators, not part of the Resource Metadata wire body, so strip both
370
+ // before serializing. The `/meta` sub-resource carries its OWN ETag
371
+ // (`metaVersion`, V2) so a metadata-only edit does not disturb the content
372
+ // ETag; it is present only once metadata has been written.
373
+ const { version: _version, metaVersion, ...metadataBody } = metadata;
374
+ const metaReply = reply.status(200).type('application/json');
375
+ if (metaVersion !== undefined) {
376
+ metaReply.header('etag', formatEtag(metaVersion));
377
+ }
378
+ return metaReply.send(JSON.stringify(metadataBody));
379
+ }
380
+ /**
381
+ * PUT /space/:spaceId/:collectionId/:resourceId/meta
382
+ * Request handler for "Update Resource Metadata" request. A full replacement
383
+ * of the Metadata object's user-writable `custom` object (any property omitted
384
+ * is cleared; a body with no `custom` clears them all). Server-managed
385
+ * properties are untouched, and any top-level property other than `custom` in
386
+ * the body is ignored (so a client may GET-modify-PUT the whole object). Does
387
+ * NOT create: a `PUT` to the `/meta` of a nonexistent Resource is a 404.
388
+ * Authorization is capability-only (the `PUT` action), the same as Put
389
+ * Resource. Returns 204.
390
+ *
391
+ * @param request {import('fastify').FastifyRequest}
392
+ * @param reply {import('fastify').FastifyReply}
393
+ * @returns {Promise<FastifyReply>}
394
+ */
395
+ static async putMeta(request, reply) {
396
+ const { params: { spaceId, collectionId, resourceId } } = request;
397
+ const { storage } = request.server;
398
+ const requestName = 'Update Resource Metadata';
399
+ // Reject path-traversal / non-URL-safe ids before any storage access.
400
+ assertValidIds({ spaceId, collectionId, resourceId }, { requestName });
401
+ // Pre-auth body shape (400): the body MUST be a JSON object. The deeper
402
+ // `custom` shape check is deferred until after authorization, where the
403
+ // Collection's `encryption` marker decides whether `custom` is a plaintext
404
+ // `{ name, tags }` (validated by `parseCustomMetadata`) or an opaque envelope
405
+ // (validated structurally by `assertEncryptedMetaConforms`) -- neither is
406
+ // knowable before reading the Collection Description, and gating the check on
407
+ // auth keeps a 422/400 observable only to a caller authorized to write here.
408
+ if (typeof request.body !== 'object' ||
409
+ request.body === null ||
410
+ Array.isArray(request.body)) {
411
+ throw new InvalidRequestBodyError({
412
+ requestName,
413
+ detail: 'Request body must be a JSON object.'
414
+ });
415
+ }
416
+ // Verify (capability-only): writing metadata requires a valid capability
417
+ // invocation (the `PUT` action); no access-control-policy fallback.
418
+ await fetchSpaceAndVerify({
419
+ request,
420
+ spaceId,
421
+ targetPath: metaPath({ spaceId, collectionId, resourceId }),
422
+ requestName
423
+ });
424
+ // zCap checks out, continue
425
+ // Fetch collection by id
426
+ const collectionDescription = await getCollectionOrThrow({
427
+ storage,
428
+ spaceId,
429
+ collectionId,
430
+ requestName
431
+ });
432
+ // Branch on the Collection's encryption marker. On an encrypted Collection
433
+ // the `custom` value MUST be a conforming envelope of the scheme (stored
434
+ // opaquely, `422` on a plaintext/malformed value); on a plaintext Collection
435
+ // it MUST be a well-formed `{ name, tags }` object (`400` otherwise).
436
+ let custom;
437
+ if (collectionDescription.encryption?.scheme !== undefined) {
438
+ const rawCustom = request.body.custom;
439
+ assertEncryptedMetaConforms({ collectionDescription, custom: rawCustom });
440
+ custom = rawCustom;
441
+ }
442
+ else {
443
+ custom = parseCustomMetadata(request.body);
444
+ }
445
+ // The key-epoch stamp (the `key-epochs` feature) MAY also be declared here
446
+ // as a top-level `epoch` member (a sibling of `custom`). Unlike `custom`
447
+ // (full-replace), an omitted `epoch` PRESERVES the stored value -- it
448
+ // describes the content write, not the metadata write. A present value must
449
+ // be a non-empty string (400).
450
+ const { epoch } = parseMetaEpoch({
451
+ body: request.body,
452
+ requestName
453
+ });
454
+ // Write Metadata to the Collection's selected (data-plane) backend. An
455
+ // `If-Match` / `If-None-Match` precondition (the `conditional-writes`
456
+ // feature) is evaluated on the `/meta` `metaVersion` atomically with the
457
+ // write; a mismatch surfaces as 412 `precondition-failed` (rethrown unchanged).
458
+ const dataBackend = await resolveBackend({
459
+ request,
460
+ spaceId,
461
+ collectionId,
462
+ collectionDescription
463
+ });
464
+ let written;
465
+ try {
466
+ written = await dataBackend.writeResourceMetadata({
467
+ spaceId,
468
+ collectionId,
469
+ resourceId,
470
+ custom,
471
+ epoch,
472
+ ...parseWritePreconditions(request.headers)
473
+ });
474
+ }
475
+ catch (err) {
476
+ rethrowOrWrapStorageError({ err, requestName });
477
+ }
478
+ // A Metadata object cannot exist apart from its Resource: a PUT to the
479
+ // `/meta` of a nonexistent Resource is a 404 (this operation does not create).
480
+ if (!written) {
481
+ throw new ResourceNotFoundError({ requestName });
482
+ }
483
+ // Return the new `/meta` ETag (`metaVersion`) so a client can chain a
484
+ // subsequent conditional metadata write.
485
+ return reply
486
+ .status(204)
487
+ .header('etag', formatEtag(written.metaVersion))
488
+ .send();
489
+ }
490
+ /**
491
+ * DELETE /space/:spaceId/:collectionId/:resourceId
492
+ * Request handler for "Delete Resource" request
493
+ * Before this, `parseAuthHeaders()` hook executed, resulting in:
494
+ * request.zcap: {
495
+ * keyId, headers, signature, created, expires, invocation, digest
496
+ * }
497
+ *
498
+ * @param request {import('fastify').FastifyRequest}
499
+ * @param reply {import('fastify').FastifyReply}
500
+ * @returns {Promise<FastifyReply>}
501
+ */
502
+ static async delete(request, reply) {
503
+ const { params: { spaceId, collectionId, resourceId } } = request;
504
+ const { storage } = request.server;
505
+ const requestName = 'Delete Resource';
506
+ // Reject path-traversal / non-URL-safe ids before any storage access.
507
+ assertValidIds({ spaceId, collectionId, resourceId }, { requestName });
508
+ // Verify (capability-only): deleting a Resource requires a valid capability
509
+ // invocation; no access-control-policy fallback.
510
+ await fetchSpaceAndVerify({
511
+ request,
512
+ spaceId,
513
+ targetPath: resourcePath({ spaceId, collectionId, resourceId }),
514
+ requestName
515
+ });
516
+ // Fetch collection by id
517
+ const collectionDescription = await getCollectionOrThrow({
518
+ storage,
519
+ spaceId,
520
+ collectionId,
521
+ requestName
522
+ });
523
+ // zCap checks out, continue. An `If-Match` precondition is evaluated by the
524
+ // storage layer atomically with the removal; a mismatch surfaces as 412
525
+ // `precondition-failed` (rethrown unchanged below).
526
+ const { ifMatch } = parseWritePreconditions(request.headers);
527
+ // Delete from the Collection's selected (data-plane) backend.
528
+ const dataBackend = await resolveBackend({
529
+ request,
530
+ spaceId,
531
+ collectionId,
532
+ collectionDescription
533
+ });
534
+ try {
535
+ await dataBackend.deleteResource({
536
+ spaceId,
537
+ collectionId,
538
+ resourceId,
539
+ ifMatch
540
+ });
541
+ }
542
+ catch (err) {
543
+ rethrowOrWrapStorageError({ err, requestName });
544
+ }
545
+ return reply.status(204).send();
546
+ }
547
+ }
548
+ //# sourceMappingURL=ResourceRequest.js.map