@venizia/ignis 0.2.0-23 → 0.2.0-24
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/dist/components/static-asset/index.d.ts +1 -0
- package/dist/components/static-asset/index.d.ts.map +1 -1
- package/dist/components/static-asset/index.js +1 -0
- package/dist/components/static-asset/index.js.map +1 -1
- package/dist/components/static-asset/ingest.d.ts +58 -0
- package/dist/components/static-asset/ingest.d.ts.map +1 -0
- package/dist/components/static-asset/ingest.js +82 -0
- package/dist/components/static-asset/ingest.js.map +1 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/static-asset/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/static-asset/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC"}
|
|
@@ -19,4 +19,5 @@ __exportStar(require("./component"), exports);
|
|
|
19
19
|
__exportStar(require("./controller"), exports);
|
|
20
20
|
__exportStar(require("./models"), exports);
|
|
21
21
|
__exportStar(require("./repositories"), exports);
|
|
22
|
+
__exportStar(require("./ingest"), exports);
|
|
22
23
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/static-asset/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,8CAA4B;AAC5B,+CAA6B;AAC7B,2CAAyB;AACzB,iDAA+B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/static-asset/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,8CAA4B;AAC5B,+CAA6B;AAC7B,2CAAyB;AACzB,iDAA+B;AAC/B,2CAAyB"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type IBucketRef, type IObjectRef, type IFileStat, type IStorageHelper, type IUploadResult, type IUrlSafetyPolicy } from '@venizia/ignis-helpers';
|
|
2
|
+
/**
|
|
3
|
+
* What was stored, and what the backend reports about it. The stat is returned because `getStat`
|
|
4
|
+
* already ran to size the result - a caller writing its own row would otherwise pay for it twice.
|
|
5
|
+
*
|
|
6
|
+
* This does NOT write a meta link. The columns an application attaches to an object - a principal,
|
|
7
|
+
* a variant - come from its own domain, not from the object, so the row stays the caller's.
|
|
8
|
+
*/
|
|
9
|
+
export interface IIngestFromUrlResult {
|
|
10
|
+
upload: IUploadResult;
|
|
11
|
+
stat: IFileStat;
|
|
12
|
+
}
|
|
13
|
+
/** Where a fetched object lands, and what it is attached to. */
|
|
14
|
+
export interface IIngestFromUrlOptions {
|
|
15
|
+
helper: IStorageHelper;
|
|
16
|
+
url: string;
|
|
17
|
+
bucket: IBucketRef;
|
|
18
|
+
/**
|
|
19
|
+
* Decides the stored key.
|
|
20
|
+
*
|
|
21
|
+
* **Absent, the key comes from the remote url's last path segment - which on an untrusted url is
|
|
22
|
+
* named by whoever supplied it.** Pass `resolveKey` whenever the url is not your own; the
|
|
23
|
+
* validation below bounds what it can do, but it does not make the remote name yours.
|
|
24
|
+
*/
|
|
25
|
+
resolveKey?: (opts: {
|
|
26
|
+
url: URL;
|
|
27
|
+
contentType: string;
|
|
28
|
+
}) => string;
|
|
29
|
+
folderPath?: string;
|
|
30
|
+
/** Absent leaves IGNIS's own default: https only, no private address, 3 hops, 10s, 10 MB. */
|
|
31
|
+
policy?: IUrlSafetyPolicy;
|
|
32
|
+
/** Folder nesting the key may carry. Absent uses `BaseStorageHelper.DEFAULT_MAX_FOLDER_DEPTH`. */
|
|
33
|
+
maxFolderDepth?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Where the object is served from. **Required**: an absent hook used to yield an empty link, which
|
|
36
|
+
* is exactly the value that satisfies a NOT NULL column and points nowhere.
|
|
37
|
+
*/
|
|
38
|
+
normalizeLinkFn: (opts: {
|
|
39
|
+
bucket: IBucketRef;
|
|
40
|
+
object: IObjectRef;
|
|
41
|
+
}) => string;
|
|
42
|
+
}
|
|
43
|
+
/** Storing an object that came from somewhere else. */
|
|
44
|
+
export declare class AssetIngest {
|
|
45
|
+
/**
|
|
46
|
+
* Fetches an untrusted url and stores what comes back, without the bytes passing through this
|
|
47
|
+
* process: the guarded body is capped and handed to `writeStream` as a stream.
|
|
48
|
+
*
|
|
49
|
+
* The url is guarded by `UrlIngest.fetchGuarded` - scheme allow-list, every resolved address
|
|
50
|
+
* checked, redirects walked by hand with a per-hop re-check, and a timeout. A rebinding window
|
|
51
|
+
* remains open between the check and the connect; see `UrlIngest.assertPublicHost`.
|
|
52
|
+
*
|
|
53
|
+
* The served content type is decided from the stored KEY, never from what the remote host
|
|
54
|
+
* claimed - the same rule the asset routes apply, and for the same reason.
|
|
55
|
+
*/
|
|
56
|
+
static fromUrl(opts: IIngestFromUrlOptions): Promise<IIngestFromUrlResult>;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=ingest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../../src/components/static-asset/ingest.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACtB,MAAM,wBAAwB,CAAC;AAEhC;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,gEAAgE;AAChE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,cAAc,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,UAAU,CAAC;IAEnB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,GAAG,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAE1B,kGAAkG;IAClG,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,eAAe,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,UAAU,CAAA;KAAE,KAAK,MAAM,CAAC;CAC/E;AAUD,uDAAuD;AACvD,qBAAa,WAAW;IACtB;;;;;;;;;;OAUG;WACU,OAAO,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAkEjF"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AssetIngest = void 0;
|
|
4
|
+
const core_1 = require("@venizia/ignis-helpers/core");
|
|
5
|
+
const ignis_helpers_1 = require("@venizia/ignis-helpers");
|
|
6
|
+
const readKeyFromUrl = (opts) => {
|
|
7
|
+
const { url, folderPath } = opts;
|
|
8
|
+
const lastSegment = url.pathname.split('/').filter(Boolean).pop() ?? 'object';
|
|
9
|
+
const normalized = lastSegment.toLowerCase().replace(/ /g, '_');
|
|
10
|
+
return folderPath ? `${folderPath}/${normalized}` : normalized;
|
|
11
|
+
};
|
|
12
|
+
/** Storing an object that came from somewhere else. */
|
|
13
|
+
class AssetIngest {
|
|
14
|
+
/**
|
|
15
|
+
* Fetches an untrusted url and stores what comes back, without the bytes passing through this
|
|
16
|
+
* process: the guarded body is capped and handed to `writeStream` as a stream.
|
|
17
|
+
*
|
|
18
|
+
* The url is guarded by `UrlIngest.fetchGuarded` - scheme allow-list, every resolved address
|
|
19
|
+
* checked, redirects walked by hand with a per-hop re-check, and a timeout. A rebinding window
|
|
20
|
+
* remains open between the check and the connect; see `UrlIngest.assertPublicHost`.
|
|
21
|
+
*
|
|
22
|
+
* The served content type is decided from the stored KEY, never from what the remote host
|
|
23
|
+
* claimed - the same rule the asset routes apply, and for the same reason.
|
|
24
|
+
*/
|
|
25
|
+
static async fromUrl(opts) {
|
|
26
|
+
const { helper, url, bucket, folderPath, policy, resolveKey, normalizeLinkFn } = opts;
|
|
27
|
+
const response = await ignis_helpers_1.UrlIngest.fetchGuarded({ url, policy });
|
|
28
|
+
const remoteType = response.headers.get('content-type')?.split(';')[0]?.trim() ?? '';
|
|
29
|
+
const parsed = new URL(url);
|
|
30
|
+
const key = resolveKey
|
|
31
|
+
? resolveKey({ url: parsed, contentType: remoteType })
|
|
32
|
+
: readKeyFromUrl({ url: parsed, folderPath });
|
|
33
|
+
// The helper's own default, not an unbounded one: an object arriving through ingest must obey
|
|
34
|
+
// the same depth rule as one arriving through the upload route.
|
|
35
|
+
if (!helper.isValidObjectKey({ object: { key }, maxDepth: opts.maxFolderDepth })) {
|
|
36
|
+
await response.body?.cancel();
|
|
37
|
+
throw (0, core_1.getError)({ message: `[AssetIngest.fromUrl] Invalid object key | key: ${key}` });
|
|
38
|
+
}
|
|
39
|
+
// Refused BEFORE the write starts, so a body that declares its size never opens a multipart
|
|
40
|
+
// upload at all. A body that lies or omits the header is caught mid-flight by `capStream`, and
|
|
41
|
+
// Bun's client answers that with `AbortMultipartUpload` - measured against a recording endpoint,
|
|
42
|
+
// not assumed - so no orphaned parts are left behind either way.
|
|
43
|
+
const declaredLength = Number(response.headers.get('content-length') ?? 0);
|
|
44
|
+
const maxBytes = policy?.maxBytes ?? ignis_helpers_1.UrlSafetyDefaults.MAX_BYTES;
|
|
45
|
+
if (declaredLength > maxBytes) {
|
|
46
|
+
await response.body?.cancel();
|
|
47
|
+
throw (0, core_1.getError)({
|
|
48
|
+
error: ignis_helpers_1.UrlSafetyErrors.URL_REFUSED,
|
|
49
|
+
message: `[AssetIngest.fromUrl] Body exceeds the cap | declared: ${declaredLength} | max: ${maxBytes}`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (!response.body) {
|
|
53
|
+
throw (0, core_1.getError)({ message: `[AssetIngest.fromUrl] Response carried no body | url: ${url}` });
|
|
54
|
+
}
|
|
55
|
+
const object = { key };
|
|
56
|
+
const contentType = helper.getMimeType({ filename: key });
|
|
57
|
+
await helper.writeStream({
|
|
58
|
+
bucket,
|
|
59
|
+
object,
|
|
60
|
+
source: ignis_helpers_1.UrlIngest.capStream({ source: response.body, policy }),
|
|
61
|
+
contentType,
|
|
62
|
+
maxFolderDepth: opts.maxFolderDepth,
|
|
63
|
+
});
|
|
64
|
+
const stat = await helper.getStat({ bucket, object });
|
|
65
|
+
const link = normalizeLinkFn({ bucket, object });
|
|
66
|
+
if (!link) {
|
|
67
|
+
throw (0, core_1.getError)({
|
|
68
|
+
message: `[AssetIngest.fromUrl] normalizeLinkFn returned an empty link | key: ${key}`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
upload: {
|
|
73
|
+
bucket: { name: bucket.name },
|
|
74
|
+
object: { key, size: stat.size, contentType },
|
|
75
|
+
link,
|
|
76
|
+
},
|
|
77
|
+
stat,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.AssetIngest = AssetIngest;
|
|
82
|
+
//# sourceMappingURL=ingest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../../src/components/static-asset/ingest.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@venizia/ignis",
|
|
3
|
-
"version": "0.2.0-
|
|
3
|
+
"version": "0.2.0-24",
|
|
4
4
|
"description": "High-performance TypeScript server infrastructure combining LoopBack 4 enterprise architecture (decorator-based DI, repository pattern, component system) with Hono speed (~140k req/s). Features auto-generated OpenAPI docs, Drizzle ORM type-safe SQL, JWT/Basic authentication, Casbin authorization, convention-based bootstrapping, and pluggable components for health checks, Swagger UI, mail, Socket.IO, and static assets. Built for Bun and Node.js.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"access-control",
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
"dependencies": {
|
|
201
201
|
"@venizia/ignis-connectors": "^0.2.0-23",
|
|
202
202
|
"@venizia/ignis-filter": "^0.2.0-7",
|
|
203
|
-
"@venizia/ignis-helpers": "^0.2.0-
|
|
203
|
+
"@venizia/ignis-helpers": "^0.2.0-18",
|
|
204
204
|
"@venizia/ignis-inversion": "^0.2.0-5",
|
|
205
205
|
"@venizia/ignis-kernel": "^0.2.0-23",
|
|
206
206
|
"lodash": "^4.18.1",
|