@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.
- package/LICENSE +1 -1
- package/dist/auth.d.ts +60 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +12 -0
- package/dist/auth.js.map +1 -0
- package/dist/compute.d.ts +47 -0
- package/dist/compute.d.ts.map +1 -0
- package/dist/compute.js +11 -0
- package/dist/compute.js.map +1 -0
- package/dist/computeServer/index.d.ts +2 -0
- package/dist/computeServer/index.d.ts.map +1 -1
- package/dist/computeServer/index.js +6 -0
- package/dist/computeServer/index.js.map +1 -1
- package/dist/computeServer/interface.d.ts +10 -0
- package/dist/computeServer/interface.d.ts.map +1 -1
- package/dist/computeServer/secrets.d.ts +53 -0
- package/dist/computeServer/secrets.d.ts.map +1 -0
- package/dist/computeServer/secrets.js +67 -0
- package/dist/computeServer/secrets.js.map +1 -0
- package/dist/context.d.ts +7 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +13 -0
- package/dist/context.js.map +1 -1
- package/dist/data/interface.d.ts +33 -0
- package/dist/data/interface.d.ts.map +1 -1
- package/dist/definitions/interface.d.ts +18 -0
- package/dist/definitions/interface.d.ts.map +1 -1
- package/dist/definitions/schemas.d.ts +1 -0
- package/dist/definitions/schemas.d.ts.map +1 -1
- package/dist/definitions/schemas.js +3 -0
- package/dist/definitions/schemas.js.map +1 -1
- package/dist/definitions/service.d.ts +62 -0
- package/dist/definitions/service.d.ts.map +1 -0
- package/dist/definitions/service.js +149 -0
- package/dist/definitions/service.js.map +1 -0
- package/dist/definitions/types.d.ts +24 -1
- package/dist/definitions/types.d.ts.map +1 -1
- package/dist/definitions.d.ts +146 -0
- package/dist/definitions.d.ts.map +1 -0
- package/dist/definitions.js +23 -0
- package/dist/definitions.js.map +1 -0
- package/dist/errors/interface.d.ts +44 -0
- package/dist/errors/interface.d.ts.map +1 -0
- package/dist/errors/interface.js +9 -0
- package/dist/errors/interface.js.map +1 -0
- package/dist/events/interface.d.ts +23 -0
- package/dist/events/interface.d.ts.map +1 -1
- package/dist/events/interface.js.map +1 -1
- package/dist/index.d.ts +14 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/invites/schemas.d.ts +24 -0
- package/dist/invites/schemas.d.ts.map +1 -0
- package/dist/invites/schemas.js +17 -0
- package/dist/invites/schemas.js.map +1 -0
- package/dist/metrics/interface.d.ts +6 -0
- package/dist/metrics/interface.d.ts.map +1 -1
- package/dist/metrics/interface.js.map +1 -1
- package/dist/organizations/index.d.ts +4 -3
- package/dist/organizations/index.d.ts.map +1 -1
- package/dist/organizations/index.js +2 -1
- package/dist/organizations/index.js.map +1 -1
- package/dist/organizations/interface.d.ts +1 -1
- package/dist/organizations/interface.d.ts.map +1 -1
- package/dist/organizations/paths.d.ts +22 -0
- package/dist/organizations/paths.d.ts.map +1 -0
- package/dist/organizations/paths.js +43 -0
- package/dist/organizations/paths.js.map +1 -0
- package/dist/organizations/schemas.d.ts +10 -0
- package/dist/organizations/schemas.d.ts.map +1 -1
- package/dist/organizations/schemas.js +6 -0
- package/dist/organizations/schemas.js.map +1 -1
- package/dist/organizations/types.d.ts +10 -1
- package/dist/organizations/types.d.ts.map +1 -1
- package/dist/organizations.d.ts +93 -0
- package/dist/organizations.d.ts.map +1 -0
- package/dist/organizations.js +14 -0
- package/dist/organizations.js.map +1 -0
- package/dist/solveCache/interface.d.ts +86 -0
- package/dist/solveCache/interface.d.ts.map +1 -0
- package/dist/solveCache/interface.js +12 -0
- package/dist/solveCache/interface.js.map +1 -0
- package/dist/storage/assetClasses.d.ts +59 -0
- package/dist/storage/assetClasses.d.ts.map +1 -0
- package/dist/storage/assetClasses.js +86 -0
- package/dist/storage/assetClasses.js.map +1 -0
- package/dist/storage/cacheBust.d.ts +21 -0
- package/dist/storage/cacheBust.d.ts.map +1 -0
- package/dist/storage/cacheBust.js +29 -0
- package/dist/storage/cacheBust.js.map +1 -0
- package/dist/storage/image.d.ts +6 -1
- package/dist/storage/image.d.ts.map +1 -1
- package/dist/storage/image.js +8 -3
- package/dist/storage/image.js.map +1 -1
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -1
- package/dist/testing/adapterSuite.d.ts +33 -0
- package/dist/testing/adapterSuite.d.ts.map +1 -0
- package/dist/testing/adapterSuite.js +166 -0
- package/dist/testing/adapterSuite.js.map +1 -0
- package/dist/testing/suites/definitionStoreSuite.d.ts.map +1 -1
- package/dist/testing/suites/definitionStoreSuite.js +87 -0
- package/dist/testing/suites/definitionStoreSuite.js.map +1 -1
- package/dist/testing/suites/eventSinkSuite.js +1 -0
- package/dist/testing/suites/eventSinkSuite.js.map +1 -1
- package/dist/testing/suites/orgStoreSuite.d.ts.map +1 -1
- package/dist/testing/suites/orgStoreSuite.js +12 -0
- package/dist/testing/suites/orgStoreSuite.js.map +1 -1
- package/dist/testing/suites/platformPermissionStoreSuite.d.ts +9 -0
- package/dist/testing/suites/platformPermissionStoreSuite.d.ts.map +1 -1
- package/dist/testing/suites/platformPermissionStoreSuite.js +34 -1
- package/dist/testing/suites/platformPermissionStoreSuite.js.map +1 -1
- package/dist/testing/suites/runner.d.ts +21 -0
- package/dist/testing/suites/runner.d.ts.map +1 -0
- package/dist/testing/suites/runner.js +7 -0
- package/dist/testing/suites/runner.js.map +1 -0
- package/dist/testing/suites/storageProviderSuite.d.ts.map +1 -1
- package/dist/testing/suites/storageProviderSuite.js +18 -0
- package/dist/testing/suites/storageProviderSuite.js.map +1 -1
- package/package.json +9 -24
- package/src/access/index.ts +0 -23
- package/src/access/rules.ts +0 -276
- package/src/auth/index.ts +0 -8
- package/src/auth/interface.ts +0 -279
- package/src/auth/types.ts +0 -32
- package/src/bindings/index.ts +0 -2
- package/src/bindings/interface.ts +0 -61
- package/src/computeServer/index.ts +0 -17
- package/src/computeServer/interface.ts +0 -62
- package/src/computeServer/types.ts +0 -74
- package/src/computeServer/utils.ts +0 -112
- package/src/config.ts +0 -99
- package/src/context.ts +0 -66
- package/src/data/index.ts +0 -1
- package/src/data/interface.ts +0 -70
- package/src/definitions/index.ts +0 -26
- package/src/definitions/interface.ts +0 -78
- package/src/definitions/paths.ts +0 -31
- package/src/definitions/schemas.ts +0 -64
- package/src/definitions/types.ts +0 -111
- package/src/errors.ts +0 -16
- package/src/events/audit.ts +0 -67
- package/src/events/index.ts +0 -9
- package/src/events/interface.ts +0 -102
- package/src/index.ts +0 -219
- package/src/invites/index.ts +0 -2
- package/src/invites/interface.ts +0 -38
- package/src/invites/types.ts +0 -39
- package/src/metrics/interface.ts +0 -86
- package/src/organizations/index.ts +0 -14
- package/src/organizations/interface.ts +0 -68
- package/src/organizations/schemas.ts +0 -52
- package/src/organizations/types.ts +0 -31
- package/src/pagination.ts +0 -45
- package/src/permissions/index.ts +0 -3
- package/src/permissions/interface.ts +0 -60
- package/src/permissions/types.ts +0 -21
- package/src/platformProjects/index.ts +0 -3
- package/src/platformProjects/interface.ts +0 -27
- package/src/platformProjects/schemas.ts +0 -13
- package/src/platformProjects/types.ts +0 -22
- package/src/projects/index.ts +0 -4
- package/src/projects/interface.ts +0 -58
- package/src/projects/schemas.ts +0 -28
- package/src/projects/types.ts +0 -36
- package/src/shareLinks/index.ts +0 -5
- package/src/shareLinks/interface.ts +0 -35
- package/src/shareLinks/schemas.ts +0 -21
- package/src/shareLinks/types.ts +0 -34
- package/src/storage/image.ts +0 -77
- package/src/storage/index.ts +0 -9
- package/src/storage/interface.ts +0 -27
- package/src/testing/index.ts +0 -47
- package/src/testing/suites/authProviderSuite.ts +0 -207
- package/src/testing/suites/computeServerStoreSuite.ts +0 -195
- package/src/testing/suites/definitionStoreSuite.ts +0 -585
- package/src/testing/suites/emailLinkAuthSuite.ts +0 -83
- package/src/testing/suites/eventSinkSuite.ts +0 -379
- package/src/testing/suites/helpers.ts +0 -87
- package/src/testing/suites/inviteStoreSuite.ts +0 -143
- package/src/testing/suites/orgStoreSuite.ts +0 -412
- package/src/testing/suites/platformPermissionStoreSuite.ts +0 -196
- package/src/testing/suites/platformProjectGrantStoreSuite.ts +0 -166
- package/src/testing/suites/projectStoreSuite.ts +0 -381
- package/src/testing/suites/shareLinkStoreSuite.ts +0 -203
- package/src/testing/suites/solveMetricSinkSuite.ts +0 -135
- package/src/testing/suites/storageProviderSuite.ts +0 -175
- package/src/testing/suites/userProfileStoreSuite.ts +0 -134
- package/src/userProfile/index.ts +0 -3
- package/src/userProfile/interface.ts +0 -48
- package/src/userProfile/types.ts +0 -24
- package/src/utils/audit.ts +0 -46
- 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"}
|
package/dist/storage/image.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
|
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"}
|
package/dist/storage/image.js
CHANGED
|
@@ -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
|
-
/**
|
|
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,
|
|
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"}
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -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"}
|
package/dist/storage/index.js
CHANGED
|
@@ -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":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAwB/C,SAAS,GAAG,CAAC,MAAc,EAAE,cAA6C,CAAC,gBAAgB,CAAC;IAC3F,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AAChC,CAAC;AAED,SAAS,MAAM,CAAC,YAAuC,EAAE;IACxD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,OAAO;QACN,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,YAAY,EAAE;QACtC,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,WAAW;QAC7C,OAAO,EAAE,SAAS,CAAC,OAAO,IAAI,QAAQ;QACtC,OAAO,EAAE,SAAS,CAAC,OAAO,IAAI,IAAI;QAClC,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE;QAC/C,OAAO,EAAE,SAAS,CAAC,OAAO,IAAI,EAAE;QAChC,UAAU,EAAE,SAAS,CAAC,UAAU,IAAI,EAAE;QACtC,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,OAAO;QACnC,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,GAAG;QACrC,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,GAAG;QACrC,GAAG,SAAS;KACZ,CAAC;AACH,CAAC;AAED,4EAA4E;AAC5E,SAAS,YAAY;IACpB,OAAO,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,IAAuC;IACpF,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC3C,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAExC,QAAQ,CAAC,iCAAiC,IAAI,EAAE,EAAE,GAAG,EAAE;QACtD,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;YAChD,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC;YAClC,MAAM,GAAG,GAAG,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACnC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;YACnC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;YAC7C,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;YAClD,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC;YAClC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAC,CAAC;YACzD,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC;QACxB,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;YAChD,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC;YAClC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;YACvE,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;YAEzE,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;YACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC5C,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC9B,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;YAC7D,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC;YAClC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;YACvE,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;YAEzE,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;YACnE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC5C,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC9B,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;YACnD,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC;YAClC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;YAC1E,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;YAE1E,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;YAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC5C,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;YACzD,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YACrE,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;YAEvC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YACtE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;YAC7C,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1C,MAAM,CAAC,GAAI,CAAC,SAAS,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;YAC5E,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC;YAClC,IAAI,MAAe,CAAC;YACpB,IAAI,CAAC;gBACJ,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YACvE,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,MAAM,GAAG,GAAG,CAAC;YACd,CAAC;YACD,MAAM,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,CAAC;YAC5B,MAAM,CAAC,GAAG,MAA6B,CAAC;YACxC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;YACvD,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC;YAClC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;YAExE,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;YAC/C,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAEtE,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;YACxD,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;YAChE,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC;YAClC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAEpE,MAAM,EAAE,GAAiB,EAAE,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;YACvF,MAAM,EAAE,GAAiB,EAAE,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;YACvF,MAAM,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YAChD,MAAM,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YAEhD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;YAC5C,MAAM,CAAC,GAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;YAC5D,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC;YAClC,MAAM,KAAK,GAAiB,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;YAC5F,MAAM,IAAI,GAAiB,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;YAC3F,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;YAE7E,MAAM,KAAK,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;YACvD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;YAC5C,MAAM,CAAC,GAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;YAC1C,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC;YAClC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;YACrD,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;YACnC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;YAC5C,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC;QACxB,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;YAC1E,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC;YAClC,MAAM,GAAG,GAAG,0BAA0B,CAAC;YACvC,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAExC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;YAClG,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;YACrG,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;YAE9F,MAAM,MAAM,GAAG,0BAA0B,CAAC;YAC1C,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,gBAAgB,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;YACnE,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;YACvC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClD,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;YACzD,MAAM,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC;YAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC5B,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YAC1F,CAAC;YAED,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;YACxD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,UAAU,EAAE,CAAC;YAEtC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;YACnF,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAC3D,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC/C,CAAC;QACF,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -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;
|
|
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();
|