@selvajs/platform 0.14.2 → 0.15.0-beta.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 (196) hide show
  1. package/LICENSE +1 -1
  2. package/dist/auth.d.ts +60 -0
  3. package/dist/auth.d.ts.map +1 -0
  4. package/dist/auth.js +12 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/compute.d.ts +47 -0
  7. package/dist/compute.d.ts.map +1 -0
  8. package/dist/compute.js +11 -0
  9. package/dist/compute.js.map +1 -0
  10. package/dist/computeServer/index.d.ts +2 -0
  11. package/dist/computeServer/index.d.ts.map +1 -1
  12. package/dist/computeServer/index.js +6 -0
  13. package/dist/computeServer/index.js.map +1 -1
  14. package/dist/computeServer/interface.d.ts +10 -0
  15. package/dist/computeServer/interface.d.ts.map +1 -1
  16. package/dist/computeServer/secrets.d.ts +53 -0
  17. package/dist/computeServer/secrets.d.ts.map +1 -0
  18. package/dist/computeServer/secrets.js +67 -0
  19. package/dist/computeServer/secrets.js.map +1 -0
  20. package/dist/context.d.ts +7 -0
  21. package/dist/context.d.ts.map +1 -1
  22. package/dist/context.js +13 -0
  23. package/dist/context.js.map +1 -1
  24. package/dist/data/interface.d.ts +33 -0
  25. package/dist/data/interface.d.ts.map +1 -1
  26. package/dist/definitions/interface.d.ts +18 -0
  27. package/dist/definitions/interface.d.ts.map +1 -1
  28. package/dist/definitions/schemas.d.ts +1 -0
  29. package/dist/definitions/schemas.d.ts.map +1 -1
  30. package/dist/definitions/schemas.js +3 -0
  31. package/dist/definitions/schemas.js.map +1 -1
  32. package/dist/definitions/service.d.ts +62 -0
  33. package/dist/definitions/service.d.ts.map +1 -0
  34. package/dist/definitions/service.js +149 -0
  35. package/dist/definitions/service.js.map +1 -0
  36. package/dist/definitions/types.d.ts +24 -1
  37. package/dist/definitions/types.d.ts.map +1 -1
  38. package/dist/definitions.d.ts +146 -0
  39. package/dist/definitions.d.ts.map +1 -0
  40. package/dist/definitions.js +23 -0
  41. package/dist/definitions.js.map +1 -0
  42. package/dist/errors/interface.d.ts +44 -0
  43. package/dist/errors/interface.d.ts.map +1 -0
  44. package/dist/errors/interface.js +9 -0
  45. package/dist/errors/interface.js.map +1 -0
  46. package/dist/events/interface.d.ts +23 -0
  47. package/dist/events/interface.d.ts.map +1 -1
  48. package/dist/events/interface.js.map +1 -1
  49. package/dist/index.d.ts +14 -5
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +7 -2
  52. package/dist/index.js.map +1 -1
  53. package/dist/invites/schemas.d.ts +24 -0
  54. package/dist/invites/schemas.d.ts.map +1 -0
  55. package/dist/invites/schemas.js +17 -0
  56. package/dist/invites/schemas.js.map +1 -0
  57. package/dist/metrics/interface.d.ts +6 -0
  58. package/dist/metrics/interface.d.ts.map +1 -1
  59. package/dist/metrics/interface.js.map +1 -1
  60. package/dist/organizations/index.d.ts +4 -3
  61. package/dist/organizations/index.d.ts.map +1 -1
  62. package/dist/organizations/index.js +2 -1
  63. package/dist/organizations/index.js.map +1 -1
  64. package/dist/organizations/interface.d.ts +1 -1
  65. package/dist/organizations/interface.d.ts.map +1 -1
  66. package/dist/organizations/paths.d.ts +22 -0
  67. package/dist/organizations/paths.d.ts.map +1 -0
  68. package/dist/organizations/paths.js +43 -0
  69. package/dist/organizations/paths.js.map +1 -0
  70. package/dist/organizations/schemas.d.ts +10 -0
  71. package/dist/organizations/schemas.d.ts.map +1 -1
  72. package/dist/organizations/schemas.js +6 -0
  73. package/dist/organizations/schemas.js.map +1 -1
  74. package/dist/organizations/types.d.ts +10 -1
  75. package/dist/organizations/types.d.ts.map +1 -1
  76. package/dist/organizations.d.ts +93 -0
  77. package/dist/organizations.d.ts.map +1 -0
  78. package/dist/organizations.js +14 -0
  79. package/dist/organizations.js.map +1 -0
  80. package/dist/solveCache/interface.d.ts +86 -0
  81. package/dist/solveCache/interface.d.ts.map +1 -0
  82. package/dist/solveCache/interface.js +12 -0
  83. package/dist/solveCache/interface.js.map +1 -0
  84. package/dist/storage/assetClasses.d.ts +59 -0
  85. package/dist/storage/assetClasses.d.ts.map +1 -0
  86. package/dist/storage/assetClasses.js +86 -0
  87. package/dist/storage/assetClasses.js.map +1 -0
  88. package/dist/storage/cacheBust.d.ts +21 -0
  89. package/dist/storage/cacheBust.d.ts.map +1 -0
  90. package/dist/storage/cacheBust.js +29 -0
  91. package/dist/storage/cacheBust.js.map +1 -0
  92. package/dist/storage/image.d.ts +6 -1
  93. package/dist/storage/image.d.ts.map +1 -1
  94. package/dist/storage/image.js +8 -3
  95. package/dist/storage/image.js.map +1 -1
  96. package/dist/storage/index.d.ts +3 -0
  97. package/dist/storage/index.d.ts.map +1 -1
  98. package/dist/storage/index.js +2 -0
  99. package/dist/storage/index.js.map +1 -1
  100. package/dist/testing/adapterSuite.d.ts +33 -0
  101. package/dist/testing/adapterSuite.d.ts.map +1 -0
  102. package/dist/testing/adapterSuite.js +166 -0
  103. package/dist/testing/adapterSuite.js.map +1 -0
  104. package/dist/testing/suites/definitionStoreSuite.d.ts.map +1 -1
  105. package/dist/testing/suites/definitionStoreSuite.js +87 -0
  106. package/dist/testing/suites/definitionStoreSuite.js.map +1 -1
  107. package/dist/testing/suites/eventSinkSuite.js +1 -0
  108. package/dist/testing/suites/eventSinkSuite.js.map +1 -1
  109. package/dist/testing/suites/orgStoreSuite.d.ts.map +1 -1
  110. package/dist/testing/suites/orgStoreSuite.js +12 -0
  111. package/dist/testing/suites/orgStoreSuite.js.map +1 -1
  112. package/dist/testing/suites/platformPermissionStoreSuite.d.ts +9 -0
  113. package/dist/testing/suites/platformPermissionStoreSuite.d.ts.map +1 -1
  114. package/dist/testing/suites/platformPermissionStoreSuite.js +34 -1
  115. package/dist/testing/suites/platformPermissionStoreSuite.js.map +1 -1
  116. package/dist/testing/suites/runner.d.ts +21 -0
  117. package/dist/testing/suites/runner.d.ts.map +1 -0
  118. package/dist/testing/suites/runner.js +7 -0
  119. package/dist/testing/suites/runner.js.map +1 -0
  120. package/dist/testing/suites/storageProviderSuite.d.ts.map +1 -1
  121. package/dist/testing/suites/storageProviderSuite.js +18 -0
  122. package/dist/testing/suites/storageProviderSuite.js.map +1 -1
  123. package/package.json +9 -24
  124. package/src/access/index.ts +0 -23
  125. package/src/access/rules.ts +0 -276
  126. package/src/auth/index.ts +0 -8
  127. package/src/auth/interface.ts +0 -279
  128. package/src/auth/types.ts +0 -32
  129. package/src/bindings/index.ts +0 -2
  130. package/src/bindings/interface.ts +0 -61
  131. package/src/computeServer/index.ts +0 -17
  132. package/src/computeServer/interface.ts +0 -62
  133. package/src/computeServer/types.ts +0 -74
  134. package/src/computeServer/utils.ts +0 -112
  135. package/src/config.ts +0 -99
  136. package/src/context.ts +0 -66
  137. package/src/data/index.ts +0 -1
  138. package/src/data/interface.ts +0 -70
  139. package/src/definitions/index.ts +0 -26
  140. package/src/definitions/interface.ts +0 -78
  141. package/src/definitions/paths.ts +0 -31
  142. package/src/definitions/schemas.ts +0 -64
  143. package/src/definitions/types.ts +0 -111
  144. package/src/errors.ts +0 -16
  145. package/src/events/audit.ts +0 -67
  146. package/src/events/index.ts +0 -9
  147. package/src/events/interface.ts +0 -102
  148. package/src/index.ts +0 -219
  149. package/src/invites/index.ts +0 -2
  150. package/src/invites/interface.ts +0 -38
  151. package/src/invites/types.ts +0 -39
  152. package/src/metrics/interface.ts +0 -86
  153. package/src/organizations/index.ts +0 -14
  154. package/src/organizations/interface.ts +0 -68
  155. package/src/organizations/schemas.ts +0 -52
  156. package/src/organizations/types.ts +0 -31
  157. package/src/pagination.ts +0 -45
  158. package/src/permissions/index.ts +0 -3
  159. package/src/permissions/interface.ts +0 -60
  160. package/src/permissions/types.ts +0 -21
  161. package/src/platformProjects/index.ts +0 -3
  162. package/src/platformProjects/interface.ts +0 -27
  163. package/src/platformProjects/schemas.ts +0 -13
  164. package/src/platformProjects/types.ts +0 -22
  165. package/src/projects/index.ts +0 -4
  166. package/src/projects/interface.ts +0 -58
  167. package/src/projects/schemas.ts +0 -28
  168. package/src/projects/types.ts +0 -36
  169. package/src/shareLinks/index.ts +0 -5
  170. package/src/shareLinks/interface.ts +0 -35
  171. package/src/shareLinks/schemas.ts +0 -21
  172. package/src/shareLinks/types.ts +0 -34
  173. package/src/storage/image.ts +0 -77
  174. package/src/storage/index.ts +0 -9
  175. package/src/storage/interface.ts +0 -27
  176. package/src/testing/index.ts +0 -47
  177. package/src/testing/suites/authProviderSuite.ts +0 -207
  178. package/src/testing/suites/computeServerStoreSuite.ts +0 -195
  179. package/src/testing/suites/definitionStoreSuite.ts +0 -585
  180. package/src/testing/suites/emailLinkAuthSuite.ts +0 -83
  181. package/src/testing/suites/eventSinkSuite.ts +0 -379
  182. package/src/testing/suites/helpers.ts +0 -87
  183. package/src/testing/suites/inviteStoreSuite.ts +0 -143
  184. package/src/testing/suites/orgStoreSuite.ts +0 -412
  185. package/src/testing/suites/platformPermissionStoreSuite.ts +0 -196
  186. package/src/testing/suites/platformProjectGrantStoreSuite.ts +0 -166
  187. package/src/testing/suites/projectStoreSuite.ts +0 -381
  188. package/src/testing/suites/shareLinkStoreSuite.ts +0 -203
  189. package/src/testing/suites/solveMetricSinkSuite.ts +0 -135
  190. package/src/testing/suites/storageProviderSuite.ts +0 -175
  191. package/src/testing/suites/userProfileStoreSuite.ts +0 -134
  192. package/src/userProfile/index.ts +0 -3
  193. package/src/userProfile/interface.ts +0 -48
  194. package/src/userProfile/types.ts +0 -24
  195. package/src/utils/audit.ts +0 -46
  196. package/src/utils/index.ts +0 -2
@@ -0,0 +1,86 @@
1
+ import { ALL_ORG_ASSET_KINDS } from '../organizations/schemas.js';
2
+ import { COVER_IMAGE_EXTENSIONS } from '../definitions/types.js';
3
+ // Org branding kinds (logo, favicon, …) are the public set. Built from the
4
+ // schema enum so adding a kind there extends this pattern automatically.
5
+ const BRANDING_KINDS = ALL_ORG_ASSET_KINDS.join('|');
6
+ // Cover extensions are derived from the canonical `COVER_IMAGE_EXTENSIONS`
7
+ // (definitions/types.ts) — strip the leading dot and escape so adding a new
8
+ // cover format there propagates here instead of silently 404ing legit covers.
9
+ const COVER_EXTS = COVER_IMAGE_EXTENSIONS.map((ext) => ext.replace(/^\./, '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
10
+ // Reusable segment alphabets. `SAFE_SEGMENT` mirrors the `assertSafeKey`
11
+ // alphabet in the path helpers (traversal/empty segments are rejected
12
+ // separately by `hasUnsafeSegment`); `GUID` is the canonical definition guid
13
+ // shape — kept in sync with `UUID_REGEX` in definitions/schemas.ts.
14
+ const SAFE_SEGMENT = String.raw `[A-Za-z0-9._-]+`;
15
+ const GUID = String.raw `[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`;
16
+ /**
17
+ * The closed registry of servable asset classes. Order matters only for
18
+ * readability — patterns are mutually exclusive by prefix. Anything that
19
+ * matches no class is unservable (the route 404s), which keeps the proxy
20
+ * default-deny: a new blob type is opt-in by adding an entry here.
21
+ */
22
+ export const ASSET_CLASSES = [
23
+ {
24
+ id: 'org-branding',
25
+ visibility: 'public',
26
+ scope: 'none',
27
+ pattern: new RegExp(String.raw `^orgs\/(${SAFE_SEGMENT})\/branding\/(?:${BRANDING_KINDS})\.webp$`)
28
+ },
29
+ {
30
+ id: 'org-private',
31
+ visibility: 'org',
32
+ scope: 'org',
33
+ pattern: new RegExp(String.raw `^orgs\/(${SAFE_SEGMENT})\/private\/${SAFE_SEGMENT}$`)
34
+ },
35
+ {
36
+ id: 'definition-cover',
37
+ visibility: 'project',
38
+ scope: 'project',
39
+ pattern: new RegExp(String.raw `^definitions\/(${GUID})\/cover\.(?:${COVER_EXTS})$`, 'i')
40
+ }
41
+ ];
42
+ /**
43
+ * Reject any path that contains a traversal, empty, or backslash segment
44
+ * *before* it can match a class. The class patterns use a permissive segment
45
+ * alphabet (`[A-Za-z0-9._-]+`) so dotted ids and filenames work, but that
46
+ * alphabet also admits a bare `.` or `..` *as a whole segment* — which would
47
+ * let `orgs/../branding/logo.webp` classify as public branding with `..`
48
+ * captured as the orgId. This mirrors `assertSafeKey` in the path builders:
49
+ * the registry is the read-side gate, those are the write-side gate, and both
50
+ * forbid the same segments so a path that can't be *written* can't be *read*.
51
+ */
52
+ function hasUnsafeSegment(storagePath) {
53
+ if (storagePath.includes('\\'))
54
+ return true;
55
+ for (const segment of storagePath.split('/')) {
56
+ if (segment === '' || segment === '.' || segment === '..')
57
+ return true;
58
+ }
59
+ return false;
60
+ }
61
+ /**
62
+ * Classify a storage path into its asset class and scope id, or null when no
63
+ * class matches (or the path is unsafe). Callers — `getPublicUrl` (to pick CDN
64
+ * vs proxy) and the serving route (to pick the auth check) — consult this so
65
+ * URL generation and authorization never drift apart.
66
+ *
67
+ * For `org-branding` the orgId is captured as group 1 but the class is public,
68
+ * so `scopeId` is reported as the captured id only when the class actually
69
+ * authorizes against it (`scope !== 'none'`); public classes report null.
70
+ */
71
+ export function classifyAssetPath(storagePath) {
72
+ if (hasUnsafeSegment(storagePath))
73
+ return null;
74
+ for (const cls of ASSET_CLASSES) {
75
+ const m = cls.pattern.exec(storagePath);
76
+ if (!m)
77
+ continue;
78
+ return { class: cls, scopeId: cls.scope === 'none' ? null : (m[1] ?? null) };
79
+ }
80
+ return null;
81
+ }
82
+ /** True when the path belongs to a `public` asset class (CDN-servable, no auth). */
83
+ export function isPublicAssetPath(storagePath) {
84
+ return classifyAssetPath(storagePath)?.class.visibility === 'public';
85
+ }
86
+ //# sourceMappingURL=assetClasses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assetClasses.js","sourceRoot":"","sources":["../../src/storage/assetClasses.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AA4CjE,2EAA2E;AAC3E,yEAAyE;AACzE,MAAM,cAAc,GAAG,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAErD,2EAA2E;AAC3E,4EAA4E;AAC5E,8EAA8E;AAC9E,MAAM,UAAU,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CACrD,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAC7D,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEZ,yEAAyE;AACzE,sEAAsE;AACtE,6EAA6E;AAC7E,oEAAoE;AACpE,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAA,iBAAiB,CAAC;AACjD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAA,8DAA8D,CAAC;AAEtF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAA0B;IACnD;QACC,EAAE,EAAE,cAAc;QAClB,UAAU,EAAE,QAAQ;QACpB,KAAK,EAAE,MAAM;QACb,OAAO,EAAE,IAAI,MAAM,CAClB,MAAM,CAAC,GAAG,CAAA,WAAW,YAAY,mBAAmB,cAAc,UAAU,CAC5E;KACD;IACD;QACC,EAAE,EAAE,aAAa;QACjB,UAAU,EAAE,KAAK;QACjB,KAAK,EAAE,KAAK;QACZ,OAAO,EAAE,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAA,WAAW,YAAY,eAAe,YAAY,GAAG,CAAC;KACpF;IACD;QACC,EAAE,EAAE,kBAAkB;QACtB,UAAU,EAAE,SAAS;QACrB,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAA,kBAAkB,IAAI,gBAAgB,UAAU,IAAI,EAAE,GAAG,CAAC;KACxF;CACD,CAAC;AAEF;;;;;;;;;GASG;AACH,SAAS,gBAAgB,CAAC,WAAmB;IAC5C,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,KAAK,MAAM,OAAO,IAAI,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9C,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;IACxE,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACpD,IAAI,gBAAgB,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/C,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACxC,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC;IAC9E,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACpD,OAAO,iBAAiB,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,QAAQ,CAAC;AACtE,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Append a cache-busting token to a stored-asset URL.
3
+ *
4
+ * Mutable blobs are overwritten **in place** (`IStorageProvider.put` is upsert),
5
+ * so a re-upload keeps the same storage path — and therefore the same public
6
+ * URL. A browser/CDN that cached the old bytes under that URL keeps serving them
7
+ * until the TTL expires, which reads as "the replace didn't work". Appending a
8
+ * token that changes only when the content changes gives each new version a
9
+ * distinct URL (forcing a refetch) while letting unchanged assets stay cached.
10
+ *
11
+ * Generic over asset kind: the caller supplies the token from whatever it has
12
+ * that tracks the blob's version — a content hash, the owning record's
13
+ * `updatedAt`, a version counter. The serving route ignores the query string
14
+ * (SvelteKit's `[...path]` param never includes it), so this is purely a
15
+ * client-cache key and needs no route support.
16
+ *
17
+ * Empty/whitespace tokens are dropped (returns the URL unchanged) so a missing
18
+ * token degrades to "no cache-bust" rather than `?v=`.
19
+ */
20
+ export declare function withCacheBust(url: string, token: string | number | undefined | null): string;
21
+ //# sourceMappingURL=cacheBust.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cacheBust.d.ts","sourceRoot":"","sources":["../../src/storage/cacheBust.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAM5F"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Append a cache-busting token to a stored-asset URL.
3
+ *
4
+ * Mutable blobs are overwritten **in place** (`IStorageProvider.put` is upsert),
5
+ * so a re-upload keeps the same storage path — and therefore the same public
6
+ * URL. A browser/CDN that cached the old bytes under that URL keeps serving them
7
+ * until the TTL expires, which reads as "the replace didn't work". Appending a
8
+ * token that changes only when the content changes gives each new version a
9
+ * distinct URL (forcing a refetch) while letting unchanged assets stay cached.
10
+ *
11
+ * Generic over asset kind: the caller supplies the token from whatever it has
12
+ * that tracks the blob's version — a content hash, the owning record's
13
+ * `updatedAt`, a version counter. The serving route ignores the query string
14
+ * (SvelteKit's `[...path]` param never includes it), so this is purely a
15
+ * client-cache key and needs no route support.
16
+ *
17
+ * Empty/whitespace tokens are dropped (returns the URL unchanged) so a missing
18
+ * token degrades to "no cache-bust" rather than `?v=`.
19
+ */
20
+ export function withCacheBust(url, token) {
21
+ if (token === undefined || token === null)
22
+ return url;
23
+ const value = String(token).trim();
24
+ if (value === '')
25
+ return url;
26
+ const sep = url.includes('?') ? '&' : '?';
27
+ return `${url}${sep}v=${encodeURIComponent(value)}`;
28
+ }
29
+ //# sourceMappingURL=cacheBust.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cacheBust.js","sourceRoot":"","sources":["../../src/storage/cacheBust.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,KAAyC;IACnF,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,GAAG,CAAC;IACtD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,GAAG,CAAC;IAC7B,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1C,OAAO,GAAG,GAAG,GAAG,GAAG,KAAK,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;AACrD,CAAC"}
@@ -18,7 +18,12 @@ export interface TranscodeResult {
18
18
  }
19
19
  /** True if the input looks like an image to process — by content type or path extension. */
20
20
  export declare function isImageUpload(contentType: string | undefined, storagePath: string): boolean;
21
- /** Rewrite a path's extension to `.webp`. Unchanged if not a recognized image extension. */
21
+ /**
22
+ * Rewrite a path's extension to `.webp`. Unchanged if not a recognized image
23
+ * extension. SVG is included: every upload (including SVG) is rasterized to
24
+ * WebP, so a `.svg` input is stored as `.webp` — no vector blob is ever
25
+ * persisted, which keeps the served bytes XSS-free without a sanitizer.
26
+ */
22
27
  export declare function toWebpPath(storagePath: string): string;
23
28
  /**
24
29
  * If the input is an image, transcode to WebP (capped + re-encoded) and
@@ -1 +1 @@
1
- {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/storage/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,eAAO,MAAM,eAAe,OAAO,CAAC;AACpC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;CACb;AAED,4FAA4F;AAC5F,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAG3F;AAED,4FAA4F;AAC5F,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC3C,IAAI,EAAE,UAAU,EAChB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,eAAe,CAAC,CAgB1B"}
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/storage/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,eAAO,MAAM,eAAe,OAAO,CAAC;AACpC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;CACb;AAED,4FAA4F;AAC5F,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAG3F;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC3C,IAAI,EAAE,UAAU,EAChB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,eAAe,CAAC,CAgB1B"}
@@ -14,11 +14,16 @@ export const IMAGE_WEBP_QUALITY = 85;
14
14
  export function isImageUpload(contentType, storagePath) {
15
15
  if (contentType?.startsWith('image/'))
16
16
  return true;
17
- return /\.(webp|png|jpe?g|gif|bmp|tif?f)$/i.test(storagePath);
17
+ return /\.(webp|png|jpe?g|gif|bmp|tif?f|svg)$/i.test(storagePath);
18
18
  }
19
- /** Rewrite a path's extension to `.webp`. Unchanged if not a recognized image extension. */
19
+ /**
20
+ * Rewrite a path's extension to `.webp`. Unchanged if not a recognized image
21
+ * extension. SVG is included: every upload (including SVG) is rasterized to
22
+ * WebP, so a `.svg` input is stored as `.webp` — no vector blob is ever
23
+ * persisted, which keeps the served bytes XSS-free without a sanitizer.
24
+ */
20
25
  export function toWebpPath(storagePath) {
21
- return storagePath.replace(/\.(png|jpe?g|gif|bmp|tif?f)$/i, '.webp');
26
+ return storagePath.replace(/\.(png|jpe?g|gif|bmp|tif?f|svg)$/i, '.webp');
22
27
  }
23
28
  /**
24
29
  * If the input is an image, transcode to WebP (capped + re-encoded) and
@@ -1 +1 @@
1
- {"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/storage/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AACpC,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AASrC,4FAA4F;AAC5F,MAAM,UAAU,aAAa,CAAC,WAA+B,EAAE,WAAmB;IACjF,IAAI,WAAW,EAAE,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACnD,OAAO,oCAAoC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AAC/D,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,UAAU,CAAC,WAAmB;IAC7C,OAAO,WAAW,CAAC,OAAO,CAAC,+BAA+B,EAAE,OAAO,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC3C,IAAgB,EAChB,WAA+B,EAC/B,WAAmB;IAEnB,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC;QAC9C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,IAAI,0BAA0B,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC5F,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAC;IAChC,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;SAC/C,MAAM,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC;SAC5D,IAAI,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;SACrC,QAAQ,EAAE,CAAC;IAEb,OAAO;QACN,IAAI,EAAE,IAAI,UAAU,CAAC,UAAU,CAAC;QAChC,WAAW,EAAE,YAAY;QACzB,IAAI,EAAE,UAAU,CAAC,WAAW,CAAC;KAC7B,CAAC;AACH,CAAC;AAGD,IAAI,WAAW,GAAmB,IAAI,CAAC;AAEvC,KAAK,UAAU,SAAS;IACvB,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IACpC,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAoC,CAAC;QACvE,WAAW,GAAG,GAAG,CAAC,OAAO,IAAK,GAA0B,CAAC;QACzD,OAAO,WAAW,CAAC;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACd,sGAAsG,EACtG,EAAE,KAAK,EAAE,GAAY,EAAE,CACvB,CAAC;IACH,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/storage/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AACpC,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AASrC,4FAA4F;AAC5F,MAAM,UAAU,aAAa,CAAC,WAA+B,EAAE,WAAmB;IACjF,IAAI,WAAW,EAAE,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACnD,OAAO,wCAAwC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AACnE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,WAAmB;IAC7C,OAAO,WAAW,CAAC,OAAO,CAAC,mCAAmC,EAAE,OAAO,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC3C,IAAgB,EAChB,WAA+B,EAC/B,WAAmB;IAEnB,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC;QAC9C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,IAAI,0BAA0B,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC5F,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAC;IAChC,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;SAC/C,MAAM,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC;SAC5D,IAAI,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;SACrC,QAAQ,EAAE,CAAC;IAEb,OAAO;QACN,IAAI,EAAE,IAAI,UAAU,CAAC,UAAU,CAAC;QAChC,WAAW,EAAE,YAAY;QACzB,IAAI,EAAE,UAAU,CAAC,WAAW,CAAC;KAC7B,CAAC;AACH,CAAC;AAGD,IAAI,WAAW,GAAmB,IAAI,CAAC;AAEvC,KAAK,UAAU,SAAS;IACvB,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IACpC,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAoC,CAAC;QACvE,WAAW,GAAG,GAAG,CAAC,OAAO,IAAK,GAA0B,CAAC;QACzD,OAAO,WAAW,CAAC;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACd,sGAAsG,EACtG,EAAE,KAAK,EAAE,GAAY,EAAE,CACvB,CAAC;IACH,CAAC;AACF,CAAC"}
@@ -1,4 +1,7 @@
1
1
  export type { IStorageProvider } from './interface.js';
2
2
  export { IMAGE_MAX_WIDTH, IMAGE_WEBP_QUALITY, isImageUpload, toWebpPath, transcodeImageIfNeeded } from './image.js';
3
3
  export type { TranscodeResult } from './image.js';
4
+ export { ASSET_CLASSES, classifyAssetPath, isPublicAssetPath } from './assetClasses.js';
5
+ export type { AssetVisibility, AssetScope, AssetClass, AssetMatch } from './assetClasses.js';
6
+ export { withCacheBust } from './cacheBust.js';
4
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EACN,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,sBAAsB,EACtB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EACN,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,sBAAsB,EACtB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACxF,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
@@ -1,2 +1,4 @@
1
1
  export { IMAGE_MAX_WIDTH, IMAGE_WEBP_QUALITY, isImageUpload, toWebpPath, transcodeImageIfNeeded } from './image.js';
2
+ export { ASSET_CLASSES, classifyAssetPath, isPublicAssetPath } from './assetClasses.js';
3
+ export { withCacheBust } from './cacheBust.js';
2
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,sBAAsB,EACtB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,sBAAsB,EACtB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAExF,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Adapter conformance suite for IDefinitionStore.
3
+ *
4
+ * The goal is to prove every adapter behaves identically from the consuming
5
+ * app's perspective — `ctx` scoping, `pending` filtering, `listStalePending`
6
+ * cutoff, history mutations, and error shapes.
7
+ *
8
+ * Runner-agnostic: callers inject a `{ describe, it, expect }` trio so this
9
+ * package stays test-framework free. For vitest or jest, pass the globals
10
+ * directly; for node:test, wrap them.
11
+ */
12
+ import type { IDefinitionStore } from '../data/interface.js';
13
+ export interface ConformanceRunner {
14
+ describe: (name: string, fn: () => void) => void;
15
+ it: (name: string, fn: () => Promise<void> | void) => void;
16
+ expect: <T>(actual: T) => {
17
+ toBe: (expected: T) => void;
18
+ toEqual: (expected: unknown) => void;
19
+ toBeNull: () => void;
20
+ toBeTruthy: () => void;
21
+ toContain: (expected: unknown) => void;
22
+ };
23
+ }
24
+ export interface DefinitionStoreConformanceOptions {
25
+ /** Name to show in the test output (e.g. "local-provider"). */
26
+ name: string;
27
+ /** Factory that returns a fresh, empty store per test. */
28
+ createStore: () => Promise<IDefinitionStore> | IDefinitionStore;
29
+ /** Test runner globals injected from the host package. */
30
+ runner: ConformanceRunner;
31
+ }
32
+ export declare function runDefinitionStoreConformance(opts: DefinitionStoreConformanceOptions): void;
33
+ //# sourceMappingURL=adapterSuite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapterSuite.d.ts","sourceRoot":"","sources":["../../src/testing/adapterSuite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAM7D,MAAM,WAAW,iBAAiB;IACjC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IACjD,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IAC3D,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK;QACzB,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAC;QAC5B,OAAO,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;QACrC,QAAQ,EAAE,MAAM,IAAI,CAAC;QACrB,UAAU,EAAE,MAAM,IAAI,CAAC;QACvB,SAAS,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;KACvC,CAAC;CACF;AAED,MAAM,WAAW,iCAAiC;IACjD,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,WAAW,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;IAChE,0DAA0D;IAC1D,MAAM,EAAE,iBAAiB,CAAC;CAC1B;AA4BD,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,iCAAiC,GAAG,IAAI,CA0J3F"}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Adapter conformance suite for IDefinitionStore.
3
+ *
4
+ * The goal is to prove every adapter behaves identically from the consuming
5
+ * app's perspective — `ctx` scoping, `pending` filtering, `listStalePending`
6
+ * cutoff, history mutations, and error shapes.
7
+ *
8
+ * Runner-agnostic: callers inject a `{ describe, it, expect }` trio so this
9
+ * package stays test-framework free. For vitest or jest, pass the globals
10
+ * directly; for node:test, wrap them.
11
+ */
12
+ import { SYSTEM_CONTEXT } from '../context.js';
13
+ function ctx(userId, permissions = ['platform_admin']) {
14
+ return { userId, permissions };
15
+ }
16
+ function record(overrides = {}) {
17
+ const now = new Date().toISOString();
18
+ return {
19
+ guid: overrides.guid ?? cryptoRandom(),
20
+ projectId: overrides.projectId ?? 'project-1',
21
+ ownerId: overrides.ownerId ?? 'user-1',
22
+ fileExt: overrides.fileExt ?? 'gh',
23
+ meta: overrides.meta ?? { displayName: 'Test' },
24
+ history: overrides.history ?? [],
25
+ maxHistory: overrides.maxHistory ?? 10,
26
+ status: overrides.status ?? 'ready',
27
+ createdAt: overrides.createdAt ?? now,
28
+ updatedAt: overrides.updatedAt ?? now,
29
+ ...overrides
30
+ };
31
+ }
32
+ // Minimal UUID-ish helper — good enough for tests, no crypto import needed.
33
+ function cryptoRandom() {
34
+ return `test-${Math.random().toString(36).slice(2, 10)}`;
35
+ }
36
+ export function runDefinitionStoreConformance(opts) {
37
+ const { name, createStore, runner } = opts;
38
+ const { describe, it, expect } = runner;
39
+ describe(`IDefinitionStore conformance: ${name}`, () => {
40
+ it('create + get returns the record', async () => {
41
+ const store = await createStore();
42
+ const rec = record({ guid: 'g1' });
43
+ await store.create(ctx('u1'), rec);
44
+ const got = await store.get(ctx('u1'), 'g1');
45
+ expect(got?.guid).toBe('g1');
46
+ });
47
+ it('get returns null for missing guid', async () => {
48
+ const store = await createStore();
49
+ const got = await store.get(ctx('u1'), 'does-not-exist');
50
+ expect(got).toBeNull();
51
+ });
52
+ it('list filters pending by default', async () => {
53
+ const store = await createStore();
54
+ await store.create(ctx('u1'), record({ guid: 'r1', status: 'ready' }));
55
+ await store.create(ctx('u1'), record({ guid: 'p1', status: 'pending' }));
56
+ const page = await store.list(ctx('u1'));
57
+ const guids = page.items.map((r) => r.guid);
58
+ expect(guids).toContain('r1');
59
+ expect(guids.includes('p1')).toBe(false);
60
+ });
61
+ it('list with includePending returns pending too', async () => {
62
+ const store = await createStore();
63
+ await store.create(ctx('u1'), record({ guid: 'r1', status: 'ready' }));
64
+ await store.create(ctx('u1'), record({ guid: 'p1', status: 'pending' }));
65
+ const page = await store.list(ctx('u1'), { includePending: true });
66
+ const guids = page.items.map((r) => r.guid);
67
+ expect(guids).toContain('r1');
68
+ expect(guids).toContain('p1');
69
+ });
70
+ it('listByProject filters by projectId', async () => {
71
+ const store = await createStore();
72
+ await store.create(ctx('u1'), record({ guid: 'a', projectId: 'proj-a' }));
73
+ await store.create(ctx('u1'), record({ guid: 'b', projectId: 'proj-b' }));
74
+ const page = await store.listByProject(ctx('u1'), 'proj-a');
75
+ const guids = page.items.map((r) => r.guid);
76
+ expect(guids).toEqual(['a']);
77
+ });
78
+ it('update applies patch and bumps updatedAt', async () => {
79
+ const store = await createStore();
80
+ const created = record({ guid: 'u1', meta: { displayName: 'Old' } });
81
+ await store.create(ctx('u1'), created);
82
+ await store.update(ctx('u1'), 'u1', { meta: { displayName: 'New' } });
83
+ const got = await store.get(ctx('u1'), 'u1');
84
+ expect(got?.meta.displayName).toBe('New');
85
+ expect(got.updatedAt >= created.updatedAt).toBe(true);
86
+ });
87
+ it('update on missing guid throws ProviderError with status 404', async () => {
88
+ const store = await createStore();
89
+ let thrown;
90
+ try {
91
+ await store.update(ctx('u1'), 'nope', { meta: { displayName: 'X' } });
92
+ }
93
+ catch (err) {
94
+ thrown = err;
95
+ }
96
+ expect(thrown).toBeTruthy();
97
+ const e = thrown;
98
+ expect(e.status).toBe(404);
99
+ });
100
+ it('status flip pending → ready via update', async () => {
101
+ const store = await createStore();
102
+ await store.create(ctx('u1'), record({ guid: 'x', status: 'pending' }));
103
+ const beforeFlip = await store.list(ctx('u1'));
104
+ expect(beforeFlip.items.map((r) => r.guid).includes('x')).toBe(false);
105
+ await store.update(ctx('u1'), 'x', { status: 'ready' });
106
+ const afterFlip = await store.list(ctx('u1'));
107
+ expect(afterFlip.items.map((r) => r.guid)).toContain('x');
108
+ });
109
+ it('addHistoryEntry prepends entries (newest first)', async () => {
110
+ const store = await createStore();
111
+ await store.create(ctx('u1'), record({ guid: 'h', maxHistory: 0 }));
112
+ const e1 = { ref: 'r1', originalName: 'a.gh', archivedAt: '2024-01-01' };
113
+ const e2 = { ref: 'r2', originalName: 'b.gh', archivedAt: '2024-01-02' };
114
+ await store.addHistoryEntry(ctx('u1'), 'h', e1);
115
+ await store.addHistoryEntry(ctx('u1'), 'h', e2);
116
+ const got = await store.get(ctx('u1'), 'h');
117
+ expect(got.history.map((h) => h.ref)).toEqual(['r2', 'r1']);
118
+ });
119
+ it('removeHistoryEntry removes the matching ref', async () => {
120
+ const store = await createStore();
121
+ const entry = { ref: 'keep', originalName: 'a.gh', archivedAt: '2024-01-01' };
122
+ const drop = { ref: 'drop', originalName: 'b.gh', archivedAt: '2024-01-02' };
123
+ await store.create(ctx('u1'), record({ guid: 'h', history: [drop, entry] }));
124
+ await store.removeHistoryEntry(ctx('u1'), 'h', 'drop');
125
+ const got = await store.get(ctx('u1'), 'h');
126
+ expect(got.history.map((h) => h.ref)).toEqual(['keep']);
127
+ });
128
+ it('delete removes the record', async () => {
129
+ const store = await createStore();
130
+ await store.create(ctx('u1'), record({ guid: 'd' }));
131
+ await store.delete(ctx('u1'), 'd');
132
+ const got = await store.get(ctx('u1'), 'd');
133
+ expect(got).toBeNull();
134
+ });
135
+ it('listStalePending returns only pending + older than cutoff', async () => {
136
+ const store = await createStore();
137
+ const old = '2024-01-01T00:00:00.000Z';
138
+ const recent = new Date().toISOString();
139
+ await store.create(ctx('u1'), record({ guid: 'old-pending', status: 'pending', createdAt: old }));
140
+ await store.create(ctx('u1'), record({ guid: 'new-pending', status: 'pending', createdAt: recent }));
141
+ await store.create(ctx('u1'), record({ guid: 'old-ready', status: 'ready', createdAt: old }));
142
+ const cutoff = '2024-06-01T00:00:00.000Z';
143
+ const stale = await store.listStalePending(SYSTEM_CONTEXT, cutoff);
144
+ const guids = stale.map((r) => r.guid);
145
+ expect(guids).toContain('old-pending');
146
+ expect(guids.includes('new-pending')).toBe(false);
147
+ expect(guids.includes('old-ready')).toBe(false);
148
+ });
149
+ it('pagination respects limit and nextCursor', async () => {
150
+ const store = await createStore();
151
+ for (let i = 0; i < 5; i++) {
152
+ await store.create(ctx('u1'), record({ guid: `p${i}`, meta: { displayName: `N${i}` } }));
153
+ }
154
+ const first = await store.list(ctx('u1'), { limit: 2 });
155
+ expect(first.items.length).toBe(2);
156
+ expect(first.nextCursor).toBeTruthy();
157
+ const second = await store.list(ctx('u1'), { limit: 2, cursor: first.nextCursor });
158
+ expect(second.items.length).toBe(2);
159
+ const firstGuids = new Set(first.items.map((r) => r.guid));
160
+ for (const item of second.items) {
161
+ expect(firstGuids.has(item.guid)).toBe(false);
162
+ }
163
+ });
164
+ });
165
+ }
166
+ //# sourceMappingURL=adapterSuite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapterSuite.js","sourceRoot":"","sources":["../../src/testing/adapterSuite.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"definitionStoreSuite.d.ts","sourceRoot":"","sources":["../../../src/testing/suites/definitionStoreSuite.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAKvE;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yFAAyF;IACzF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,iCAAiC;IACjD,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,WAAW,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;IAChE;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,GAAG,mBAAmB,CAAC;IACvE;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AA0ED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,iCAAiC,GAAG,IAAI,CAsc3F"}
1
+ {"version":3,"file":"definitionStoreSuite.d.ts","sourceRoot":"","sources":["../../../src/testing/suites/definitionStoreSuite.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAKvE;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yFAAyF;IACzF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,iCAAiC;IACjD,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,WAAW,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;IAChE;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,GAAG,mBAAmB,CAAC;IACvE;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AA2ED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,iCAAiC,GAAG,IAAI,CAwjB3F"}
@@ -38,6 +38,7 @@ function record(scope, overrides = {}) {
38
38
  displayName: overrides.displayName ?? 'Test',
39
39
  status: overrides.status ?? 'published',
40
40
  solveCount: overrides.solveCount ?? 0,
41
+ nextVersionNumber: overrides.nextVersionNumber ?? 2,
41
42
  liveVersionId: overrides.liveVersionId ?? null,
42
43
  draftVersionId: overrides.draftVersionId ?? null,
43
44
  createdAt: overrides.createdAt ?? now,
@@ -139,6 +140,23 @@ export function runDefinitionStoreConformance(opts) {
139
140
  expect(got?.displayName).toBe('New');
140
141
  expect(got.updatedAt >= created.updatedAt).toBe(true);
141
142
  });
143
+ it('solveCacheLimit round-trips: absent → inherit, set → value, null → cleared', async () => {
144
+ const store = await createStore();
145
+ const scope = await scopeFor();
146
+ const guid = makeUuid();
147
+ await store.create(ctx(scope.ownerId), record(scope, { guid }));
148
+ // Absent on create → undefined (inherit the global default).
149
+ expect((await store.get(ctx(scope.ownerId), guid))?.solveCacheLimit).toBeUndefined();
150
+ // Set a per-definition quota.
151
+ await store.update(ctx(scope.ownerId), guid, { solveCacheLimit: 25 });
152
+ expect((await store.get(ctx(scope.ownerId), guid))?.solveCacheLimit).toBe(25);
153
+ // 0 means "caching off" and must persist as 0 (distinct from absent/inherit).
154
+ await store.update(ctx(scope.ownerId), guid, { solveCacheLimit: 0 });
155
+ expect((await store.get(ctx(scope.ownerId), guid))?.solveCacheLimit).toBe(0);
156
+ // null clears back to inherit.
157
+ await store.update(ctx(scope.ownerId), guid, { solveCacheLimit: null });
158
+ expect((await store.get(ctx(scope.ownerId), guid))?.solveCacheLimit).toBeUndefined();
159
+ });
142
160
  it('update on missing guid throws ProviderError with status 404', async () => {
143
161
  const store = await createStore();
144
162
  const scope = await scopeFor();
@@ -185,6 +203,45 @@ export function runDefinitionStoreConformance(opts) {
185
203
  }
186
204
  expect(threw).toBe(false);
187
205
  });
206
+ it('reserveNextVersionNumber returns the record counter then advances it', async () => {
207
+ const store = await createStore();
208
+ const scope = await scopeFor();
209
+ const guid = makeUuid();
210
+ await store.create(ctx(scope.ownerId), record(scope, { guid, nextVersionNumber: 2 }));
211
+ const first = await store.reserveNextVersionNumber(ctx(scope.ownerId), guid);
212
+ const second = await store.reserveNextVersionNumber(ctx(scope.ownerId), guid);
213
+ expect(first).toBe(2);
214
+ expect(second).toBe(3);
215
+ // Counter persisted, never reused.
216
+ const got = await store.get(ctx(scope.ownerId), guid);
217
+ expect(got?.nextVersionNumber).toBe(4);
218
+ });
219
+ it('reserveNextVersionNumber mints a FRESH number after delete-latest (fileKeys never collide)', async () => {
220
+ // The whole reason the counter exists: max(existing)+1 reused a number
221
+ // after deleting the latest version, colliding the fileKey. The monotonic
222
+ // counter must hand out a number BEYOND any deleted version's.
223
+ const store = await createStore();
224
+ const scope = await scopeFor();
225
+ const guid = makeUuid();
226
+ await store.create(ctx(scope.ownerId), record(scope, { guid, nextVersionNumber: 2 }));
227
+ // Simulate an upload of v2 (reserve, create), then delete it.
228
+ const n2 = await store.reserveNextVersionNumber(ctx(scope.ownerId), guid);
229
+ expect(n2).toBe(2);
230
+ const v2 = version(guid, n2, scope.ownerId);
231
+ await store.createVersion(ctx(scope.ownerId), v2);
232
+ await store.deleteVersion(ctx(scope.ownerId), v2.id);
233
+ // The next reservation must be 3 — NOT 2 again — even though the highest
234
+ // surviving version number is now 1 (v1 was never created here; the point
235
+ // is the counter ignores the version list entirely).
236
+ const n3 = await store.reserveNextVersionNumber(ctx(scope.ownerId), guid);
237
+ expect(n3).toBe(3);
238
+ expect(n3).not.toBe(n2);
239
+ });
240
+ it('reserveNextVersionNumber throws for a missing definition', async () => {
241
+ const store = await createStore();
242
+ const scope = await scopeFor();
243
+ await expect(store.reserveNextVersionNumber(ctx(scope.ownerId), makeUuid())).rejects.toThrow();
244
+ });
188
245
  it('update advances updatedBy to the caller', async () => {
189
246
  const store = await createStore();
190
247
  const scope = await scopeFor();
@@ -398,6 +455,36 @@ export function runDefinitionStoreConformance(opts) {
398
455
  });
399
456
  expect(byProject.items.map((r) => r.guid)).not.toContain(guid);
400
457
  });
458
+ it('deleteByProject soft-deletes every definition in the project, leaving others', async () => {
459
+ const store = await createStore();
460
+ const scope = await scopeFor();
461
+ // Two published definitions in the target project, one in another —
462
+ // the cascade must tombstone the target project's and leave the rest.
463
+ const a = makeUuid();
464
+ const b = makeUuid();
465
+ const other = makeUuid();
466
+ await store.create(ctx(scope.ownerId), record(scope, { guid: a, projectId: scope.projectId, status: 'published' }));
467
+ await store.create(ctx(scope.ownerId), record(scope, { guid: b, projectId: scope.projectId, status: 'published' }));
468
+ await store.create(ctx(scope.ownerId), record(scope, { guid: other, projectId: scope.secondaryProjectId, status: 'published' }));
469
+ await store.deleteByProject(ctx(scope.ownerId), scope.projectId);
470
+ // Both target-project definitions gone from every read path…
471
+ expect(await store.get(ctx(scope.ownerId), a)).toBeNull();
472
+ expect(await store.get(ctx(scope.ownerId), b)).toBeNull();
473
+ const targetList = await store.listByProject(ctx(scope.ownerId), scope.projectId, {
474
+ limit: 500
475
+ });
476
+ expect(targetList.items).toHaveLength(0);
477
+ const all = await store.list(ctx(scope.ownerId), { limit: 500 });
478
+ expect(all.items.map((r) => r.guid)).not.toContain(a);
479
+ expect(all.items.map((r) => r.guid)).not.toContain(b);
480
+ // …but the other project's definition survives.
481
+ expect(await store.get(ctx(scope.ownerId), other)).not.toBeNull();
482
+ });
483
+ it('deleteByProject is a no-op when the project has no definitions', async () => {
484
+ const store = await createStore();
485
+ const scope = await scopeFor();
486
+ await expect(store.deleteByProject(ctx(scope.ownerId), scope.secondaryProjectId)).resolves.toBeUndefined();
487
+ });
401
488
  it('listPublic returns published records, not pending', async () => {
402
489
  const store = await createStore();
403
490
  const scope = await scopeFor();