@endora-commerce/mod-assets-library 0.0.0-stage → 0.100.0
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 +21 -0
- package/README.md +61 -2
- package/dist/admin/api/assets-library-client.d.ts +29 -0
- package/dist/admin/api/assets-library-client.d.ts.map +1 -0
- package/dist/admin/api/assets-library-client.js +70 -0
- package/dist/admin/api/assets-library-client.js.map +1 -0
- package/dist/admin/components/AssetDetailDrawer.d.ts +8 -0
- package/dist/admin/components/AssetDetailDrawer.d.ts.map +1 -0
- package/dist/admin/components/AssetDetailDrawer.js +96 -0
- package/dist/admin/components/AssetDetailDrawer.js.map +1 -0
- package/dist/admin/components/FolderTree.d.ts +13 -0
- package/dist/admin/components/FolderTree.d.ts.map +1 -0
- package/dist/admin/components/FolderTree.js +71 -0
- package/dist/admin/components/FolderTree.js.map +1 -0
- package/dist/admin/index.d.ts +44 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +46 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/LibraryPage.d.ts +9 -0
- package/dist/admin/pages/LibraryPage.d.ts.map +1 -0
- package/dist/admin/pages/LibraryPage.js +163 -0
- package/dist/admin/pages/LibraryPage.js.map +1 -0
- package/dist/backend/entities/asset-folder.entity.d.ts +16 -0
- package/dist/backend/entities/asset-folder.entity.d.ts.map +1 -0
- package/dist/backend/entities/asset-folder.entity.js +57 -0
- package/dist/backend/entities/asset-folder.entity.js.map +1 -0
- package/dist/backend/entities/asset.entity.d.ts +38 -0
- package/dist/backend/entities/asset.entity.d.ts.map +1 -0
- package/dist/backend/entities/asset.entity.js +128 -0
- package/dist/backend/entities/asset.entity.js.map +1 -0
- package/dist/backend/index.d.ts +57 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +150 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/jobs/hard-delete-asset.job.d.ts +18 -0
- package/dist/backend/jobs/hard-delete-asset.job.d.ts.map +1 -0
- package/dist/backend/jobs/hard-delete-asset.job.js +91 -0
- package/dist/backend/jobs/hard-delete-asset.job.js.map +1 -0
- package/dist/backend/plugin.d.ts +47 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +96 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +15 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +189 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.public.d.ts +14 -0
- package/dist/backend/routes.public.d.ts.map +1 -0
- package/dist/backend/routes.public.js +99 -0
- package/dist/backend/routes.public.js.map +1 -0
- package/dist/backend/services/asset-read-port.d.ts +103 -0
- package/dist/backend/services/asset-read-port.d.ts.map +1 -0
- package/dist/backend/services/asset-read-port.js +167 -0
- package/dist/backend/services/asset-read-port.js.map +1 -0
- package/dist/backend/services/assets-library.service.d.ts +76 -0
- package/dist/backend/services/assets-library.service.d.ts.map +1 -0
- package/dist/backend/services/assets-library.service.js +261 -0
- package/dist/backend/services/assets-library.service.js.map +1 -0
- package/dist/backend/services/cms-body-resolver.d.ts +6 -0
- package/dist/backend/services/cms-body-resolver.d.ts.map +1 -0
- package/dist/backend/services/cms-body-resolver.js +44 -0
- package/dist/backend/services/cms-body-resolver.js.map +1 -0
- package/dist/backend/services/folders.service.d.ts +29 -0
- package/dist/backend/services/folders.service.d.ts.map +1 -0
- package/dist/backend/services/folders.service.js +231 -0
- package/dist/backend/services/folders.service.js.map +1 -0
- package/dist/backend/services/hmac.d.ts +28 -0
- package/dist/backend/services/hmac.d.ts.map +1 -0
- package/dist/backend/services/hmac.js +50 -0
- package/dist/backend/services/hmac.js.map +1 -0
- package/dist/backend/services/reference-registry.d.ts +36 -0
- package/dist/backend/services/reference-registry.d.ts.map +1 -0
- package/dist/backend/services/reference-registry.js +63 -0
- package/dist/backend/services/reference-registry.js.map +1 -0
- package/dist/backend/services/storage/adapter-registry.d.ts +77 -0
- package/dist/backend/services/storage/adapter-registry.d.ts.map +1 -0
- package/dist/backend/services/storage/adapter-registry.js +95 -0
- package/dist/backend/services/storage/adapter-registry.js.map +1 -0
- package/dist/backend/services/storage/errors.d.ts +18 -0
- package/dist/backend/services/storage/errors.d.ts.map +1 -0
- package/dist/backend/services/storage/errors.js +30 -0
- package/dist/backend/services/storage/errors.js.map +1 -0
- package/dist/backend/services/storage/gcs-adapter.d.ts +42 -0
- package/dist/backend/services/storage/gcs-adapter.d.ts.map +1 -0
- package/dist/backend/services/storage/gcs-adapter.js +106 -0
- package/dist/backend/services/storage/gcs-adapter.js.map +1 -0
- package/dist/backend/services/storage/legacy-resolver.d.ts +15 -0
- package/dist/backend/services/storage/legacy-resolver.d.ts.map +1 -0
- package/dist/backend/services/storage/legacy-resolver.js +32 -0
- package/dist/backend/services/storage/legacy-resolver.js.map +1 -0
- package/dist/backend/services/storage/local-fs-adapter.d.ts +59 -0
- package/dist/backend/services/storage/local-fs-adapter.d.ts.map +1 -0
- package/dist/backend/services/storage/local-fs-adapter.js +153 -0
- package/dist/backend/services/storage/local-fs-adapter.js.map +1 -0
- package/dist/backend/services/storage/locator.d.ts +5 -0
- package/dist/backend/services/storage/locator.d.ts.map +1 -0
- package/dist/backend/services/storage/locator.js +16 -0
- package/dist/backend/services/storage/locator.js.map +1 -0
- package/dist/backend/services/storage/object-storage-port.d.ts +46 -0
- package/dist/backend/services/storage/object-storage-port.d.ts.map +1 -0
- package/dist/backend/services/storage/object-storage-port.js +78 -0
- package/dist/backend/services/storage/object-storage-port.js.map +1 -0
- package/dist/backend/services/storage/public-url-base.d.ts +50 -0
- package/dist/backend/services/storage/public-url-base.d.ts.map +1 -0
- package/dist/backend/services/storage/public-url-base.js +70 -0
- package/dist/backend/services/storage/public-url-base.js.map +1 -0
- package/dist/backend/services/storage/s3-adapter.d.ts +50 -0
- package/dist/backend/services/storage/s3-adapter.d.ts.map +1 -0
- package/dist/backend/services/storage/s3-adapter.js +110 -0
- package/dist/backend/services/storage/s3-adapter.js.map +1 -0
- package/dist/backend/services/storage/settings-view.d.ts +4 -0
- package/dist/backend/services/storage/settings-view.d.ts.map +1 -0
- package/dist/backend/services/storage/settings-view.js +73 -0
- package/dist/backend/services/storage/settings-view.js.map +1 -0
- package/dist/backend/services/storage/storage-adapter.d.ts +62 -0
- package/dist/backend/services/storage/storage-adapter.d.ts.map +1 -0
- package/dist/backend/services/storage/storage-adapter.js +10 -0
- package/dist/backend/services/storage/storage-adapter.js.map +1 -0
- package/dist/backend/services/upload-pipeline.d.ts +41 -0
- package/dist/backend/services/upload-pipeline.d.ts.map +1 -0
- package/dist/backend/services/upload-pipeline.js +231 -0
- package/dist/backend/services/upload-pipeline.js.map +1 -0
- package/dist/manifest.d.ts +185 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +229 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260505T102206_assets_library_init.d.ts +35 -0
- package/dist/migrations/20260505T102206_assets_library_init.d.ts.map +1 -0
- package/dist/migrations/20260505T102206_assets_library_init.js +132 -0
- package/dist/migrations/20260505T102206_assets_library_init.js.map +1 -0
- package/dist/migrations/index.d.ts +27 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +29 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/assets-library/index.md +125 -0
- package/i18n/en.json +54 -0
- package/i18n/pl.json +54 -0
- package/package.json +99 -3
- package/tailwind.css +14 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hmac.d.ts","sourceRoot":"","sources":["../../../src/backend/services/hmac.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,gBAAgB;IAC/B,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,MAAM;IAMxC,MAAM,CAAC,OAAO,CAAC,MAAM,qBAAyC,GAAG,UAAU;IAa3E,IAAI,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM;IAMrC;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO;CAU1C"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// HMAC sign/verify for local-FS private asset URLs. Cloud backends use their
|
|
2
|
+
// own native signed URLs and do not consult this module.
|
|
3
|
+
//
|
|
4
|
+
// The signing key is sourced from the env var ASSETS_LIBRARY_HMAC_KEY (32-byte
|
|
5
|
+
// hex string). Rotating the key invalidates every outstanding private URL —
|
|
6
|
+
// that is the desired behaviour, not a bug.
|
|
7
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
8
|
+
export class HmacSigner {
|
|
9
|
+
key;
|
|
10
|
+
constructor(key) {
|
|
11
|
+
this.key = key;
|
|
12
|
+
if (key.length === 0) {
|
|
13
|
+
throw new Error('HmacSigner: key cannot be empty');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
static fromEnv(envKey = process.env['ASSETS_LIBRARY_HMAC_KEY']) {
|
|
17
|
+
if (!envKey || envKey.trim().length === 0) {
|
|
18
|
+
throw new Error('ASSETS_LIBRARY_HMAC_KEY is unset. Generate one with `openssl rand -hex 32` and add it to backend/.env.');
|
|
19
|
+
}
|
|
20
|
+
// Accept hex or raw — hex is what we document, but tolerate raw for tests.
|
|
21
|
+
const buf = /^[0-9a-fA-F]+$/.test(envKey) && envKey.length % 2 === 0
|
|
22
|
+
? Buffer.from(envKey, 'hex')
|
|
23
|
+
: Buffer.from(envKey, 'utf8');
|
|
24
|
+
return new HmacSigner(buf);
|
|
25
|
+
}
|
|
26
|
+
sign(input) {
|
|
27
|
+
return createHmac('sha256', this.key)
|
|
28
|
+
.update(`${input.assetId}\n${input.exp}`)
|
|
29
|
+
.digest('hex');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns true when `ctx.token` is a valid signature for
|
|
33
|
+
* (`ctx.assetId`, `ctx.exp`) and `ctx.exp >= ctx.nowSec`. Returns false on
|
|
34
|
+
* any failure (mismatch, expiry, malformed). Uses `timingSafeEqual`.
|
|
35
|
+
*/
|
|
36
|
+
verify(ctx) {
|
|
37
|
+
if (!Number.isFinite(ctx.exp) || ctx.exp < ctx.nowSec)
|
|
38
|
+
return false;
|
|
39
|
+
const expected = this.sign({ assetId: ctx.assetId, exp: ctx.exp });
|
|
40
|
+
if (expected.length !== ctx.token.length)
|
|
41
|
+
return false;
|
|
42
|
+
try {
|
|
43
|
+
return timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(ctx.token, 'hex'));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=hmac.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hmac.js","sourceRoot":"","sources":["../../../src/backend/services/hmac.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,yDAAyD;AACzD,EAAE;AACF,+EAA+E;AAC/E,4EAA4E;AAC5E,4CAA4C;AAE5C,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAmB1D,MAAM,OAAO,UAAU;IACQ;IAA7B,YAA6B,GAAW;QAAX,QAAG,GAAH,GAAG,CAAQ;QACtC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;QAC5D,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CACb,wGAAwG,CACzG,CAAC;QACJ,CAAC;QACD,2EAA2E;QAC3E,MAAM,GAAG,GAAG,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC;YAClE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;YAC5B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,OAAO,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,IAAI,CAAC,KAAuB;QAC1B,OAAO,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC;aAClC,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,KAAK,KAAK,CAAC,GAAG,EAAE,CAAC;aACxC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,GAAwB;QAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QACpE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;QACnE,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QACvD,IAAI,CAAC;YACH,OAAO,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QACtF,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AssetReference, AssetReferenceDescriptor, AssetReferenceRegistryPort } from '@endora-commerce/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Both shapes moved to `@endora-commerce/contracts` in feature 075's Phase P — four
|
|
4
|
+
* modules contribute a descriptor, so the descriptor is a boundary shape and
|
|
5
|
+
* not an internal. Re-exported here for the length of Phase P, which cuts no
|
|
6
|
+
* consumer.
|
|
7
|
+
*/
|
|
8
|
+
export type { AssetReference, AssetReferenceDescriptor };
|
|
9
|
+
/**
|
|
10
|
+
* Enumeration policy: **honoured** while the contributing module is absent
|
|
11
|
+
* (feature 072, D-39).
|
|
12
|
+
*
|
|
13
|
+
* D-39's default is to skip, and honouring needs a written reason. This registry
|
|
14
|
+
* is referential integrity, not a surface. If `blog` is switched off its posts
|
|
15
|
+
* still exist and still embed assets; skipping `blog`'s scanner would let an
|
|
16
|
+
* operator delete an asset that comes back as a broken image the moment `blog`
|
|
17
|
+
* is switched on again — a data loss caused by an action Constitution XVII
|
|
18
|
+
* promises is non-destructive and reversible.
|
|
19
|
+
*
|
|
20
|
+
* `skip` is right for surface-like contributions — an interceptor, a palette
|
|
21
|
+
* action, a storefront element — where a switched-off module must contribute
|
|
22
|
+
* nothing a user can see. Nobody sees these; they exist to refuse a delete.
|
|
23
|
+
*
|
|
24
|
+
* The owner is recorded on every descriptor even though this registry does not
|
|
25
|
+
* filter on it, because the alternative is a registry that could not express the
|
|
26
|
+
* decision either way, and because it attributes a 409 to a module.
|
|
27
|
+
*/
|
|
28
|
+
export declare class AssetReferenceRegistry implements AssetReferenceRegistryPort {
|
|
29
|
+
private readonly descriptors;
|
|
30
|
+
register(d: AssetReferenceDescriptor): void;
|
|
31
|
+
/** The contributing module of every registered descriptor, in registration order. */
|
|
32
|
+
owners(): readonly string[];
|
|
33
|
+
findReferences(assetId: string): Promise<AssetReference[]>;
|
|
34
|
+
findReferencesMany(assetIds: string[]): Promise<Map<string, AssetReference[]>>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=reference-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference-registry.d.ts","sourceRoot":"","sources":["../../../src/backend/services/reference-registry.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,cAAc,EACd,wBAAwB,EACxB,0BAA0B,EAC3B,MAAM,4BAA4B,CAAC;AAEpC;;;;;GAKG;AACH,YAAY,EAAE,cAAc,EAAE,wBAAwB,EAAE,CAAC;AAEzD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,sBAAuB,YAAW,0BAA0B;IACvE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkC;IAE9D,QAAQ,CAAC,CAAC,EAAE,wBAAwB,GAAG,IAAI;IAM3C,qFAAqF;IACrF,MAAM,IAAI,SAAS,MAAM,EAAE;IAIrB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAO1D,kBAAkB,CACtB,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;CAoB1C"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// AssetReferenceRegistry — feature 013 / research.md R10 / contracts/
|
|
2
|
+
// assets-library-reference-registry.contract.md.
|
|
3
|
+
//
|
|
4
|
+
// In-process registry of "who points at this asset" descriptors. Catalog
|
|
5
|
+
// (4 FK descriptors) and CMS (1 jsonb-body-scan descriptor) register at
|
|
6
|
+
// composition time. The Library consults the registry before every
|
|
7
|
+
// soft-delete attempt (FR-030).
|
|
8
|
+
/**
|
|
9
|
+
* Enumeration policy: **honoured** while the contributing module is absent
|
|
10
|
+
* (feature 072, D-39).
|
|
11
|
+
*
|
|
12
|
+
* D-39's default is to skip, and honouring needs a written reason. This registry
|
|
13
|
+
* is referential integrity, not a surface. If `blog` is switched off its posts
|
|
14
|
+
* still exist and still embed assets; skipping `blog`'s scanner would let an
|
|
15
|
+
* operator delete an asset that comes back as a broken image the moment `blog`
|
|
16
|
+
* is switched on again — a data loss caused by an action Constitution XVII
|
|
17
|
+
* promises is non-destructive and reversible.
|
|
18
|
+
*
|
|
19
|
+
* `skip` is right for surface-like contributions — an interceptor, a palette
|
|
20
|
+
* action, a storefront element — where a switched-off module must contribute
|
|
21
|
+
* nothing a user can see. Nobody sees these; they exist to refuse a delete.
|
|
22
|
+
*
|
|
23
|
+
* The owner is recorded on every descriptor even though this registry does not
|
|
24
|
+
* filter on it, because the alternative is a registry that could not express the
|
|
25
|
+
* decision either way, and because it attributes a 409 to a module.
|
|
26
|
+
*/
|
|
27
|
+
export class AssetReferenceRegistry {
|
|
28
|
+
descriptors = [];
|
|
29
|
+
register(d) {
|
|
30
|
+
if (!this.descriptors.includes(d)) {
|
|
31
|
+
this.descriptors.push(d);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** The contributing module of every registered descriptor, in registration order. */
|
|
35
|
+
owners() {
|
|
36
|
+
return this.descriptors.map((d) => d.ownerModuleId);
|
|
37
|
+
}
|
|
38
|
+
async findReferences(assetId) {
|
|
39
|
+
const all = await Promise.all(this.descriptors.map((d) => d.findReferences([assetId])));
|
|
40
|
+
return all.flat();
|
|
41
|
+
}
|
|
42
|
+
async findReferencesMany(assetIds) {
|
|
43
|
+
const out = new Map();
|
|
44
|
+
for (const id of assetIds)
|
|
45
|
+
out.set(id, []);
|
|
46
|
+
if (assetIds.length === 0)
|
|
47
|
+
return out;
|
|
48
|
+
const perDescriptor = await Promise.all(this.descriptors.map((d) => d.findReferences(assetIds)));
|
|
49
|
+
for (const refs of perDescriptor) {
|
|
50
|
+
for (const ref of refs) {
|
|
51
|
+
// Each descriptor is responsible for tagging every reference with the
|
|
52
|
+
// *original* asset id it found; the registry does not try to cross-
|
|
53
|
+
// correlate. Descriptors that batch via WHERE asset_id = ANY($1) get
|
|
54
|
+
// the asset_id back from the row trivially.
|
|
55
|
+
const list = out.get(ref.__assetId ?? '');
|
|
56
|
+
if (list)
|
|
57
|
+
list.push(ref);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=reference-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference-registry.js","sourceRoot":"","sources":["../../../src/backend/services/reference-registry.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,iDAAiD;AACjD,EAAE;AACF,yEAAyE;AACzE,wEAAwE;AACxE,mEAAmE;AACnE,gCAAgC;AAgBhC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,sBAAsB;IAChB,WAAW,GAA+B,EAAE,CAAC;IAE9D,QAAQ,CAAC,CAA2B;QAClC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YAClC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,qFAAqF;IACrF,MAAM;QACJ,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,OAAe;QAClC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,CAC3B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CACzD,CAAC;QACF,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;IAED,KAAK,CAAC,kBAAkB,CACtB,QAAkB;QAElB,MAAM,GAAG,GAAG,IAAI,GAAG,EAA4B,CAAC;QAChD,KAAK,MAAM,EAAE,IAAI,QAAQ;YAAE,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,GAAG,CAAC;QAEtC,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,GAAG,CACrC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CACxD,CAAC;QACF,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YACjC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,sEAAsE;gBACtE,oEAAoE;gBACpE,qEAAqE;gBACrE,4CAA4C;gBAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAE,GAA+C,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;gBACvF,IAAI,IAAI;oBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ObjectStorageBackendCode } from '@endora-commerce/contracts';
|
|
2
|
+
import type { StorageAdapter, StorageBackendCode } from './storage-adapter.js';
|
|
3
|
+
import { type LegacyAssetResolver } from './legacy-resolver.js';
|
|
4
|
+
import type { HmacSigner } from '../hmac.js';
|
|
5
|
+
/**
|
|
6
|
+
* The minimum settings surface AdapterRegistry needs. The composition root
|
|
7
|
+
* supplies an implementation backed by SettingsService (real) or by a static
|
|
8
|
+
* map (tests).
|
|
9
|
+
*/
|
|
10
|
+
export interface AdapterSettingsView {
|
|
11
|
+
/** Active adapter code: 'local' | 's3' | 'gcs'. */
|
|
12
|
+
activeAdapter(): Promise<'local' | 's3' | 'gcs'>;
|
|
13
|
+
/** Local-FS base directory (`assets.local.base_dir`). */
|
|
14
|
+
localBaseDir(): Promise<string>;
|
|
15
|
+
/** Public URL prefix for local-FS public assets. May be empty in dev. */
|
|
16
|
+
localPublicUrlBase(): Promise<string>;
|
|
17
|
+
/** Default TTL for private signed URLs (seconds). */
|
|
18
|
+
privateUrlTtlSec(): Promise<number>;
|
|
19
|
+
/** S3-adapter configuration. */
|
|
20
|
+
s3Config(): Promise<{
|
|
21
|
+
bucket: string;
|
|
22
|
+
region: string;
|
|
23
|
+
accessKeyId: string;
|
|
24
|
+
secretAccessKey: string;
|
|
25
|
+
endpoint?: string;
|
|
26
|
+
prefix?: string;
|
|
27
|
+
publicBaseUrl?: string;
|
|
28
|
+
}>;
|
|
29
|
+
/** GCS-adapter configuration. */
|
|
30
|
+
gcsConfig(): Promise<{
|
|
31
|
+
bucket: string;
|
|
32
|
+
serviceAccountJson?: string;
|
|
33
|
+
prefix?: string;
|
|
34
|
+
publicBaseUrl?: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
export interface AdapterRegistryOptions {
|
|
38
|
+
settings: AdapterSettingsView;
|
|
39
|
+
/** Provider; invoked on first need so tests that never sign a URL can skip env setup. */
|
|
40
|
+
signer: () => HmacSigner;
|
|
41
|
+
/**
|
|
42
|
+
* This deployment's resolved public API origin (D-223).
|
|
43
|
+
*
|
|
44
|
+
* Handed to every adapter it builds, and to the legacy resolver, so that the
|
|
45
|
+
* fallback base is decided at **construction** — never at the setting's
|
|
46
|
+
* declaration, which cannot read the environment, and never by a consumer,
|
|
47
|
+
* which cannot know which backend produced the URL or whether it is signed.
|
|
48
|
+
*/
|
|
49
|
+
publicApiBaseUrl: string;
|
|
50
|
+
}
|
|
51
|
+
export declare class AdapterRegistry {
|
|
52
|
+
private readonly opts;
|
|
53
|
+
private cached;
|
|
54
|
+
constructor(opts: AdapterRegistryOptions);
|
|
55
|
+
/** Drop the cached active adapter. Called when settings change. */
|
|
56
|
+
invalidate(): void;
|
|
57
|
+
/** Return the adapter currently selected by settings. */
|
|
58
|
+
getActive(): Promise<StorageAdapter>;
|
|
59
|
+
/**
|
|
60
|
+
* Return the adapter that owns the given asset's bytes. Use this for read /
|
|
61
|
+
* delete / setVisibility — never `getActive()`, which would silently route
|
|
62
|
+
* a previously-uploaded asset to the wrong backend after a switch (FR-017).
|
|
63
|
+
*
|
|
64
|
+
* **Overloaded on the code, because the union has exactly one cause.** The
|
|
65
|
+
* legacy arm is returned for `'legacy'` and for nothing else, so a caller
|
|
66
|
+
* naming a real backend gets a `StorageAdapter` and needs no probe. Both
|
|
67
|
+
* consumers of the byte surface used to write
|
|
68
|
+
* `if (!('open' in adapter) || typeof adapter.open !== 'function')` — the
|
|
69
|
+
* shape D-97.3 refuses on a *published* port — for a branch the type could
|
|
70
|
+
* have ruled out. `ObjectStoragePort` is what publishes that narrowing
|
|
71
|
+
* (`specs/110-instance-repository/` T118c).
|
|
72
|
+
*/
|
|
73
|
+
getForBackend(backend: ObjectStorageBackendCode): Promise<StorageAdapter>;
|
|
74
|
+
getForBackend(backend: StorageBackendCode): Promise<StorageAdapter | LegacyAssetResolver>;
|
|
75
|
+
private build;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=adapter-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-registry.d.ts","sourceRoot":"","sources":["../../../../src/backend/services/storage/adapter-registry.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAC3E,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAI/E,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,mDAAmD;IACnD,aAAa,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,GAAG,KAAK,CAAC,CAAC;IACjD,yDAAyD;IACzD,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAChC,yEAAyE;IACzE,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,qDAAqD;IACrD,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,gCAAgC;IAChC,QAAQ,IAAI,OAAO,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC,CAAC;IACH,iCAAiC;IACjC,SAAS,IAAI,OAAO,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,yFAAyF;IACzF,MAAM,EAAE,MAAM,UAAU,CAAC;IACzB;;;;;;;OAOG;IACH,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,eAAe;IAGd,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,MAAM,CAAsE;gBAEvD,IAAI,EAAE,sBAAsB;IAEzD,mEAAmE;IACnE,UAAU,IAAI,IAAI;IAIlB,yDAAyD;IACnD,SAAS,IAAI,OAAO,CAAC,cAAc,CAAC;IAQ1C;;;;;;;;;;;;;OAaG;IACG,aAAa,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,cAAc,CAAC;IACzE,aAAa,CACjB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,cAAc,GAAG,mBAAmB,CAAC;YASlC,KAAK;CAqDpB"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// AdapterRegistry — chooses the active StorageAdapter from settings and
|
|
2
|
+
// caches it for the process lifetime. Per-asset reads dispatch to the
|
|
3
|
+
// adapter that owns that asset's bytes (recorded in `Asset.storageBackend`),
|
|
4
|
+
// not to the active adapter. The active adapter is consulted only at upload
|
|
5
|
+
// time and for self-check.
|
|
6
|
+
//
|
|
7
|
+
// Cloud adapters (S3, GCS) are stub-imported here; their concrete classes
|
|
8
|
+
// land in Phase 5 (US3) — until then, attempting to switch the active
|
|
9
|
+
// adapter to s3/gcs raises a clear error.
|
|
10
|
+
import { LocalFsStorageAdapter } from './local-fs-adapter.js';
|
|
11
|
+
import { S3StorageAdapter } from './s3-adapter.js';
|
|
12
|
+
import { GcsStorageAdapter } from './gcs-adapter.js';
|
|
13
|
+
import { createLegacyAssetResolver } from './legacy-resolver.js';
|
|
14
|
+
import { ConfigurationError } from './errors.js';
|
|
15
|
+
export class AdapterRegistry {
|
|
16
|
+
opts;
|
|
17
|
+
cached = null;
|
|
18
|
+
constructor(opts) {
|
|
19
|
+
this.opts = opts;
|
|
20
|
+
}
|
|
21
|
+
/** Drop the cached active adapter. Called when settings change. */
|
|
22
|
+
invalidate() {
|
|
23
|
+
this.cached = null;
|
|
24
|
+
}
|
|
25
|
+
/** Return the adapter currently selected by settings. */
|
|
26
|
+
async getActive() {
|
|
27
|
+
if (this.cached)
|
|
28
|
+
return this.cached.adapter;
|
|
29
|
+
const code = await this.opts.settings.activeAdapter();
|
|
30
|
+
const adapter = await this.build(code);
|
|
31
|
+
this.cached = { code, adapter };
|
|
32
|
+
return adapter;
|
|
33
|
+
}
|
|
34
|
+
async getForBackend(backend) {
|
|
35
|
+
if (backend === 'legacy')
|
|
36
|
+
return createLegacyAssetResolver(this.opts.publicApiBaseUrl);
|
|
37
|
+
if (this.cached && this.cached.code === backend)
|
|
38
|
+
return this.cached.adapter;
|
|
39
|
+
return this.build(backend);
|
|
40
|
+
}
|
|
41
|
+
async build(code) {
|
|
42
|
+
if (code === 'local') {
|
|
43
|
+
const baseDir = await this.opts.settings.localBaseDir();
|
|
44
|
+
// Blank stays blank here on purpose: the adapter decides what a blank
|
|
45
|
+
// base means (D-223 — this deployment's public API origin), so there is
|
|
46
|
+
// one place that answers it rather than one per construction site.
|
|
47
|
+
const publicUrlBase = (await this.opts.settings.localPublicUrlBase()).trim();
|
|
48
|
+
const privateUrlTtlSec = await this.opts.settings.privateUrlTtlSec();
|
|
49
|
+
return new LocalFsStorageAdapter({
|
|
50
|
+
baseDir,
|
|
51
|
+
publicUrlBase,
|
|
52
|
+
publicApiBaseUrl: this.opts.publicApiBaseUrl,
|
|
53
|
+
privateUrlTtlSec,
|
|
54
|
+
signer: this.opts.signer(),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (code === 's3') {
|
|
58
|
+
const cfg = await this.opts.settings.s3Config();
|
|
59
|
+
const ttl = await this.opts.settings.privateUrlTtlSec();
|
|
60
|
+
return new S3StorageAdapter({
|
|
61
|
+
bucket: cfg.bucket,
|
|
62
|
+
region: cfg.region,
|
|
63
|
+
accessKeyId: cfg.accessKeyId,
|
|
64
|
+
secretAccessKey: cfg.secretAccessKey,
|
|
65
|
+
...(cfg.endpoint !== undefined && cfg.endpoint.length > 0
|
|
66
|
+
? { endpoint: cfg.endpoint }
|
|
67
|
+
: {}),
|
|
68
|
+
...(cfg.prefix !== undefined && cfg.prefix.length > 0 ? { prefix: cfg.prefix } : {}),
|
|
69
|
+
...(cfg.publicBaseUrl !== undefined && cfg.publicBaseUrl.length > 0
|
|
70
|
+
? { publicBaseUrl: cfg.publicBaseUrl }
|
|
71
|
+
: {}),
|
|
72
|
+
publicApiBaseUrl: this.opts.publicApiBaseUrl,
|
|
73
|
+
privateUrlTtlSec: ttl,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (code === 'gcs') {
|
|
77
|
+
const cfg = await this.opts.settings.gcsConfig();
|
|
78
|
+
const ttl = await this.opts.settings.privateUrlTtlSec();
|
|
79
|
+
return new GcsStorageAdapter({
|
|
80
|
+
bucket: cfg.bucket,
|
|
81
|
+
...(cfg.serviceAccountJson !== undefined && cfg.serviceAccountJson.length > 0
|
|
82
|
+
? { serviceAccountJson: cfg.serviceAccountJson }
|
|
83
|
+
: {}),
|
|
84
|
+
...(cfg.prefix !== undefined && cfg.prefix.length > 0 ? { prefix: cfg.prefix } : {}),
|
|
85
|
+
...(cfg.publicBaseUrl !== undefined && cfg.publicBaseUrl.length > 0
|
|
86
|
+
? { publicBaseUrl: cfg.publicBaseUrl }
|
|
87
|
+
: {}),
|
|
88
|
+
publicApiBaseUrl: this.opts.publicApiBaseUrl,
|
|
89
|
+
privateUrlTtlSec: ttl,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
throw new ConfigurationError(`Unknown storage backend: "${code}"`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=adapter-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-registry.js","sourceRoot":"","sources":["../../../../src/backend/services/storage/adapter-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class ConfigurationError extends Error {
|
|
2
|
+
readonly name = "ConfigurationError";
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class BackendUnavailableError extends Error {
|
|
6
|
+
readonly cause?: unknown | undefined;
|
|
7
|
+
readonly name = "BackendUnavailableError";
|
|
8
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
9
|
+
}
|
|
10
|
+
export declare class LocatorMissingError extends Error {
|
|
11
|
+
readonly name = "LocatorMissingError";
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class LegacyAssetCannotHardenError extends Error {
|
|
15
|
+
readonly name = "LegacyAssetCannotHardenError";
|
|
16
|
+
constructor();
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../../src/backend/services/storage/errors.ts"],"names":[],"mappings":"AAIA,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,SAAkB,IAAI,wBAAwB;gBAClC,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,uBAAwB,SAAQ,KAAK;aAI9B,KAAK,CAAC,EAAE,OAAO;IAHjC,SAAkB,IAAI,6BAA6B;gBAEjD,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,OAAO,YAAA;CAIlC;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAkB,IAAI,yBAAyB;gBACnC,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,4BAA6B,SAAQ,KAAK;IACrD,SAAkB,IAAI,kCAAkC;;CAMzD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Adapter-side error classes — the upload pipeline / resolveUrl path map these
|
|
2
|
+
// onto the platform's standard HTTP error envelope codes (see contracts/
|
|
3
|
+
// assets-library-admin-http.contract.md).
|
|
4
|
+
export class ConfigurationError extends Error {
|
|
5
|
+
name = 'ConfigurationError';
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export class BackendUnavailableError extends Error {
|
|
11
|
+
cause;
|
|
12
|
+
name = 'BackendUnavailableError';
|
|
13
|
+
constructor(message, cause) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.cause = cause;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class LocatorMissingError extends Error {
|
|
19
|
+
name = 'LocatorMissingError';
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export class LegacyAssetCannotHardenError extends Error {
|
|
25
|
+
name = 'LegacyAssetCannotHardenError';
|
|
26
|
+
constructor() {
|
|
27
|
+
super('Cannot mark a `legacy` (pre-013 absolute-URL) asset as private — re-upload it through the active adapter first.');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../../src/backend/services/storage/errors.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,yEAAyE;AACzE,0CAA0C;AAE1C,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IACzB,IAAI,GAAG,oBAAoB,CAAC;IAC9C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;CACF;AAED,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAI9B;IAHA,IAAI,GAAG,yBAAyB,CAAC;IACnD,YACE,OAAe,EACC,KAAe;QAE/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,UAAK,GAAL,KAAK,CAAU;IAGjC,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC1B,IAAI,GAAG,qBAAqB,CAAC;IAC/C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;CACF;AAED,MAAM,OAAO,4BAA6B,SAAQ,KAAK;IACnC,IAAI,GAAG,8BAA8B,CAAC;IACxD;QACE,KAAK,CACH,iHAAiH,CAClH,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { StorageAdapter, StorageAdapterSelfCheck, StorageNewLocatorInput, StoragePutInput, StorageResolveUrlInput, StorageResolveUrlOutput } from './storage-adapter.js';
|
|
2
|
+
export interface GcsAdapterConfig {
|
|
3
|
+
bucket: string;
|
|
4
|
+
serviceAccountJson?: string;
|
|
5
|
+
prefix?: string;
|
|
6
|
+
/**
|
|
7
|
+
* `assets.gcs.public_base_url` — a CDN or custom domain in front of the
|
|
8
|
+
* bucket. Blank means the bucket's own origin, which is already absolute.
|
|
9
|
+
*/
|
|
10
|
+
publicBaseUrl?: string;
|
|
11
|
+
/**
|
|
12
|
+
* This deployment's resolved public API origin — D-223's fallback base, with
|
|
13
|
+
* the same bound as the S3 adapter's: it rescues a `publicBaseUrl` written as
|
|
14
|
+
* a path, and a blank one keeps resolving to the bucket, which serves the
|
|
15
|
+
* bytes this API does not.
|
|
16
|
+
*/
|
|
17
|
+
publicApiBaseUrl: string;
|
|
18
|
+
privateUrlTtlSec: number;
|
|
19
|
+
}
|
|
20
|
+
export declare class GcsStorageAdapter implements StorageAdapter {
|
|
21
|
+
private readonly cfg;
|
|
22
|
+
readonly code: "gcs";
|
|
23
|
+
private readonly bucket;
|
|
24
|
+
/** The absolute base public object URLs are built on — decided once (D-223). */
|
|
25
|
+
private readonly publicBase;
|
|
26
|
+
constructor(cfg: GcsAdapterConfig);
|
|
27
|
+
selfCheck(): Promise<StorageAdapterSelfCheck>;
|
|
28
|
+
newLocator(input: StorageNewLocatorInput): string;
|
|
29
|
+
put(input: StoragePutInput): Promise<void>;
|
|
30
|
+
resolveUrl(input: StorageResolveUrlInput): Promise<StorageResolveUrlOutput>;
|
|
31
|
+
open(input: {
|
|
32
|
+
locator: string;
|
|
33
|
+
}): Promise<NodeJS.ReadableStream>;
|
|
34
|
+
delete(input: {
|
|
35
|
+
locator: string;
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
setVisibility(input: {
|
|
38
|
+
locator: string;
|
|
39
|
+
visibility: 'public' | 'private';
|
|
40
|
+
}): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=gcs-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gcs-adapter.d.ts","sourceRoot":"","sources":["../../../../src/backend/services/storage/gcs-adapter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,cAAc,EACd,uBAAuB,EACvB,sBAAsB,EACtB,eAAe,EACf,sBAAsB,EACtB,uBAAuB,EACxB,MAAM,sBAAsB,CAAC;AAS9B,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,iBAAkB,YAAW,cAAc;IAM1C,OAAO,CAAC,QAAQ,CAAC,GAAG;IALhC,QAAQ,CAAC,IAAI,EAAG,KAAK,CAAU;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,gFAAgF;IAChF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAEP,GAAG,EAAE,gBAAgB;IAyB5C,SAAS,IAAI,OAAO,CAAC,uBAAuB,CAAC;IASnD,UAAU,CAAC,KAAK,EAAE,sBAAsB,GAAG,MAAM;IAM3C,GAAG,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB1C,UAAU,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAiB3E,IAAI,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC;IAIhE,MAAM,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAWjD,aAAa,CAAC,KAAK,EAAE;QACzB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,QAAQ,GAAG,SAAS,CAAC;KAClC,GAAG,OAAO,CAAC,IAAI,CAAC;CAelB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// GCS storage adapter — feature 013 / US3 / T076.
|
|
2
|
+
// Wraps @google-cloud/storage. Storage layout matches the local-FS / S3
|
|
3
|
+
// sharded scheme; private URLs use V4 signed URLs.
|
|
4
|
+
import { Storage } from '@google-cloud/storage';
|
|
5
|
+
import { computeLocator } from './locator.js';
|
|
6
|
+
import { resolvePublicUrlBase } from './public-url-base.js';
|
|
7
|
+
import { BackendUnavailableError, ConfigurationError, LocatorMissingError, } from './errors.js';
|
|
8
|
+
export class GcsStorageAdapter {
|
|
9
|
+
cfg;
|
|
10
|
+
code = 'gcs';
|
|
11
|
+
bucket;
|
|
12
|
+
/** The absolute base public object URLs are built on — decided once (D-223). */
|
|
13
|
+
publicBase;
|
|
14
|
+
constructor(cfg) {
|
|
15
|
+
this.cfg = cfg;
|
|
16
|
+
if (!cfg.bucket)
|
|
17
|
+
throw new ConfigurationError('GcsStorageAdapter: bucket is required');
|
|
18
|
+
const configuredBase = (cfg.publicBaseUrl ?? '').trim();
|
|
19
|
+
this.publicBase = resolvePublicUrlBase(configuredBase.length > 0
|
|
20
|
+
? configuredBase
|
|
21
|
+
: `https://storage.googleapis.com/${cfg.bucket}`, cfg.publicApiBaseUrl);
|
|
22
|
+
let credentials;
|
|
23
|
+
if (cfg.serviceAccountJson && cfg.serviceAccountJson.trim().length > 0) {
|
|
24
|
+
try {
|
|
25
|
+
credentials = JSON.parse(cfg.serviceAccountJson);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
throw new ConfigurationError(`GcsStorageAdapter: serviceAccountJson is not valid JSON (${e.message})`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const storage = new Storage({
|
|
32
|
+
...(credentials ? { credentials } : {}),
|
|
33
|
+
});
|
|
34
|
+
this.bucket = storage.bucket(cfg.bucket);
|
|
35
|
+
}
|
|
36
|
+
async selfCheck() {
|
|
37
|
+
try {
|
|
38
|
+
const [exists] = await this.bucket.exists();
|
|
39
|
+
return exists ? { ok: true } : { ok: false, reason: `bucket "${this.cfg.bucket}" not found` };
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
return { ok: false, reason: e instanceof Error ? e.message : String(e) };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
newLocator(input) {
|
|
46
|
+
const tail = computeLocator(input);
|
|
47
|
+
const prefix = (this.cfg.prefix ?? '').replace(/^\/+|\/+$/g, '');
|
|
48
|
+
return prefix.length > 0 ? `${prefix}/${tail}` : tail;
|
|
49
|
+
}
|
|
50
|
+
async put(input) {
|
|
51
|
+
if (!input.locator)
|
|
52
|
+
throw new LocatorMissingError('GcsStorageAdapter.put: empty locator');
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const writeStream = this.bucket.file(input.locator).createWriteStream({
|
|
55
|
+
metadata: { contentType: input.mimeType },
|
|
56
|
+
...(input.visibility === 'public' ? { predefinedAcl: 'publicRead' } : { predefinedAcl: 'private' }),
|
|
57
|
+
resumable: false,
|
|
58
|
+
});
|
|
59
|
+
input.stream.pipe(writeStream);
|
|
60
|
+
writeStream.on('finish', () => resolve());
|
|
61
|
+
writeStream.on('error', (err) => reject(new BackendUnavailableError(`GcsStorageAdapter.put: failed to persist ${input.locator}`, err)));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async resolveUrl(input) {
|
|
65
|
+
if (input.visibility === 'public') {
|
|
66
|
+
return { url: `${this.publicBase}/${input.locator}`, expiresAt: null };
|
|
67
|
+
}
|
|
68
|
+
const ttl = input.ttlSec ?? this.cfg.privateUrlTtlSec;
|
|
69
|
+
if (!Number.isInteger(ttl) || ttl <= 0) {
|
|
70
|
+
throw new ConfigurationError(`GcsStorageAdapter.resolveUrl: invalid TTL ${ttl}`);
|
|
71
|
+
}
|
|
72
|
+
const expiresAt = new Date(Date.now() + ttl * 1000);
|
|
73
|
+
const [url] = await this.bucket.file(input.locator).getSignedUrl({
|
|
74
|
+
version: 'v4',
|
|
75
|
+
action: 'read',
|
|
76
|
+
expires: expiresAt.getTime(),
|
|
77
|
+
});
|
|
78
|
+
return { url, expiresAt };
|
|
79
|
+
}
|
|
80
|
+
async open(input) {
|
|
81
|
+
return this.bucket.file(input.locator).createReadStream();
|
|
82
|
+
}
|
|
83
|
+
async delete(input) {
|
|
84
|
+
try {
|
|
85
|
+
await this.bucket.file(input.locator).delete({ ignoreNotFound: false });
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
throw new BackendUnavailableError(`GcsStorageAdapter.delete: failed to remove ${input.locator}`, e);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async setVisibility(input) {
|
|
92
|
+
const acl = this.bucket.file(input.locator).acl;
|
|
93
|
+
try {
|
|
94
|
+
if (input.visibility === 'public') {
|
|
95
|
+
await acl.add({ entity: 'allUsers', role: 'READER' });
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
await acl.delete({ entity: 'allUsers' });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
throw new BackendUnavailableError(`GcsStorageAdapter.setVisibility: failed on ${input.locator}`, e);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=gcs-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gcs-adapter.js","sourceRoot":"","sources":["../../../../src/backend/services/storage/gcs-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StorageResolveUrlInput, StorageResolveUrlOutput } from './storage-adapter.js';
|
|
2
|
+
export interface LegacyAssetResolver {
|
|
3
|
+
resolveUrl(input: StorageResolveUrlInput): StorageResolveUrlOutput;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Build the resolver for `storage_backend='legacy'` rows on this deployment's
|
|
7
|
+
* public API origin.
|
|
8
|
+
*
|
|
9
|
+
* A factory rather than the module-level constant it used to be, for the same
|
|
10
|
+
* reason every adapter in this directory takes the origin at construction: the
|
|
11
|
+
* origin is a deployment fact, and a URL builder that cannot see it is a URL
|
|
12
|
+
* builder whose answer is relative.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createLegacyAssetResolver(publicApiBaseUrl: string): LegacyAssetResolver;
|
|
15
|
+
//# sourceMappingURL=legacy-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy-resolver.d.ts","sourceRoot":"","sources":["../../../../src/backend/services/storage/legacy-resolver.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,sBAAsB,EACtB,uBAAuB,EACxB,MAAM,sBAAsB,CAAC;AAI9B,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,KAAK,EAAE,sBAAsB,GAAG,uBAAuB,CAAC;CACpE;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,gBAAgB,EAAE,MAAM,GAAG,mBAAmB,CASvF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Legacy locator resolver — handles Asset rows whose `storage_backend='legacy'`
|
|
2
|
+
// because their pre-013 `storage_url` was a URL we don't own. The runtime serves
|
|
3
|
+
// their URLs verbatim for `public`; refuses to flip them to `private` (we cannot
|
|
4
|
+
// sign a URL we didn't issue). See research.md R11.
|
|
5
|
+
//
|
|
6
|
+
// **Verbatim, but absolute** (D-223). A legacy row that carries a full URL is
|
|
7
|
+
// passed through untouched; one that carries a host-relative path — the shape
|
|
8
|
+
// the composition root's `absolutizePublicUrl` used to rescue for two of its
|
|
9
|
+
// four consumers and not for the other two — is rebased here, so the answer no
|
|
10
|
+
// longer depends on which root composed the platform.
|
|
11
|
+
import { LegacyAssetCannotHardenError } from './errors.js';
|
|
12
|
+
import { absolutizeAssetUrl } from './public-url-base.js';
|
|
13
|
+
/**
|
|
14
|
+
* Build the resolver for `storage_backend='legacy'` rows on this deployment's
|
|
15
|
+
* public API origin.
|
|
16
|
+
*
|
|
17
|
+
* A factory rather than the module-level constant it used to be, for the same
|
|
18
|
+
* reason every adapter in this directory takes the origin at construction: the
|
|
19
|
+
* origin is a deployment fact, and a URL builder that cannot see it is a URL
|
|
20
|
+
* builder whose answer is relative.
|
|
21
|
+
*/
|
|
22
|
+
export function createLegacyAssetResolver(publicApiBaseUrl) {
|
|
23
|
+
return {
|
|
24
|
+
resolveUrl(input) {
|
|
25
|
+
if (input.visibility === 'private') {
|
|
26
|
+
throw new LegacyAssetCannotHardenError();
|
|
27
|
+
}
|
|
28
|
+
return { url: absolutizeAssetUrl(input.locator, publicApiBaseUrl), expiresAt: null };
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=legacy-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy-resolver.js","sourceRoot":"","sources":["../../../../src/backend/services/storage/legacy-resolver.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,iFAAiF;AACjF,iFAAiF;AACjF,oDAAoD;AACpD,EAAE;AACF,8EAA8E;AAC9E,8EAA8E;AAC9E,6EAA6E;AAC7E,+EAA+E;AAC/E,sDAAsD;AAMtD,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAM1D;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB,CAAC,gBAAwB;IAChE,OAAO;QACL,UAAU,CAAC,KAA6B;YACtC,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBACnC,MAAM,IAAI,4BAA4B,EAAE,CAAC;YAC3C,CAAC;YACD,OAAO,EAAE,GAAG,EAAE,kBAAkB,CAAC,KAAK,CAAC,OAAO,EAAE,gBAAgB,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QACvF,CAAC;KACF,CAAC;AACJ,CAAC"}
|