@gloocan/cat-inspector 0.1.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/dist/artifact-helpers.d.ts +4 -0
- package/dist/artifact-helpers.d.ts.map +1 -0
- package/dist/artifact-helpers.js +21 -0
- package/dist/artifact-helpers.js.map +1 -0
- package/dist/ast/get-all-ts-files.d.ts +5 -0
- package/dist/ast/get-all-ts-files.d.ts.map +1 -0
- package/dist/ast/get-all-ts-files.js +36 -0
- package/dist/ast/get-all-ts-files.js.map +1 -0
- package/dist/ast/merge-ast.d.ts +3 -0
- package/dist/ast/merge-ast.d.ts.map +1 -0
- package/dist/ast/merge-ast.js +40 -0
- package/dist/ast/merge-ast.js.map +1 -0
- package/dist/ast/run-ast-scanner.d.ts +15 -0
- package/dist/ast/run-ast-scanner.d.ts.map +1 -0
- package/dist/ast/run-ast-scanner.js +44 -0
- package/dist/ast/run-ast-scanner.js.map +1 -0
- package/dist/ast/scan-for-labels.d.ts +20 -0
- package/dist/ast/scan-for-labels.d.ts.map +1 -0
- package/dist/ast/scan-for-labels.js +88 -0
- package/dist/ast/scan-for-labels.js.map +1 -0
- package/dist/ast/scan-for-returns.d.ts +13 -0
- package/dist/ast/scan-for-returns.d.ts.map +1 -0
- package/dist/ast/scan-for-returns.js +48 -0
- package/dist/ast/scan-for-returns.js.map +1 -0
- package/dist/ast/type-expand.d.ts +16 -0
- package/dist/ast/type-expand.d.ts.map +1 -0
- package/dist/ast/type-expand.js +118 -0
- package/dist/ast/type-expand.js.map +1 -0
- package/dist/ast/visit-node.d.ts +31 -0
- package/dist/ast/visit-node.d.ts.map +1 -0
- package/dist/ast/visit-node.js +298 -0
- package/dist/ast/visit-node.js.map +1 -0
- package/dist/bootstrap.d.ts +105 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +121 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/catalog-bootstrap-cache.d.ts +23 -0
- package/dist/catalog-bootstrap-cache.d.ts.map +1 -0
- package/dist/catalog-bootstrap-cache.js +54 -0
- package/dist/catalog-bootstrap-cache.js.map +1 -0
- package/dist/catalog-fingerprint.d.ts +20 -0
- package/dist/catalog-fingerprint.d.ts.map +1 -0
- package/dist/catalog-fingerprint.js +57 -0
- package/dist/catalog-fingerprint.js.map +1 -0
- package/dist/coverage/compute-coverage.d.ts +41 -0
- package/dist/coverage/compute-coverage.d.ts.map +1 -0
- package/dist/coverage/compute-coverage.js +229 -0
- package/dist/coverage/compute-coverage.js.map +1 -0
- package/dist/coverage/scan-all-service-candidates.d.ts +15 -0
- package/dist/coverage/scan-all-service-candidates.d.ts.map +1 -0
- package/dist/coverage/scan-all-service-candidates.js +73 -0
- package/dist/coverage/scan-all-service-candidates.js.map +1 -0
- package/dist/coverage/scan-express-candidates.d.ts +17 -0
- package/dist/coverage/scan-express-candidates.d.ts.map +1 -0
- package/dist/coverage/scan-express-candidates.js +300 -0
- package/dist/coverage/scan-express-candidates.js.map +1 -0
- package/dist/coverage/scan-reachable-services.d.ts +13 -0
- package/dist/coverage/scan-reachable-services.d.ts.map +1 -0
- package/dist/coverage/scan-reachable-services.js +469 -0
- package/dist/coverage/scan-reachable-services.js.map +1 -0
- package/dist/coverage/types.d.ts +32 -0
- package/dist/coverage/types.d.ts.map +1 -0
- package/dist/coverage/types.js +2 -0
- package/dist/coverage/types.js.map +1 -0
- package/dist/decorators/cat-class.d.ts +11 -0
- package/dist/decorators/cat-class.d.ts.map +1 -0
- package/dist/decorators/cat-class.js +20 -0
- package/dist/decorators/cat-class.js.map +1 -0
- package/dist/decorators/cat.d.ts +3 -0
- package/dist/decorators/cat.d.ts.map +1 -0
- package/dist/decorators/cat.js +102 -0
- package/dist/decorators/cat.js.map +1 -0
- package/dist/express-inspector-correlation.d.ts +23 -0
- package/dist/express-inspector-correlation.d.ts.map +1 -0
- package/dist/express-inspector-correlation.js +50 -0
- package/dist/express-inspector-correlation.js.map +1 -0
- package/dist/express-playground-mocks.d.ts +28 -0
- package/dist/express-playground-mocks.d.ts.map +1 -0
- package/dist/express-playground-mocks.js +55 -0
- package/dist/express-playground-mocks.js.map +1 -0
- package/dist/express-qa-host-minio-upload.d.ts +16 -0
- package/dist/express-qa-host-minio-upload.d.ts.map +1 -0
- package/dist/express-qa-host-minio-upload.js +56 -0
- package/dist/express-qa-host-minio-upload.js.map +1 -0
- package/dist/express.d.ts +21 -0
- package/dist/express.d.ts.map +1 -0
- package/dist/express.js +167 -0
- package/dist/express.js.map +1 -0
- package/dist/functional.d.ts +15 -0
- package/dist/functional.d.ts.map +1 -0
- package/dist/functional.js +165 -0
- package/dist/functional.js.map +1 -0
- package/dist/graph/relationships.d.ts +9 -0
- package/dist/graph/relationships.d.ts.map +1 -0
- package/dist/graph/relationships.js +92 -0
- package/dist/graph/relationships.js.map +1 -0
- package/dist/http-bridge-registry.d.ts +26 -0
- package/dist/http-bridge-registry.d.ts.map +1 -0
- package/dist/http-bridge-registry.js +39 -0
- package/dist/http-bridge-registry.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/invoke-policy.d.ts +34 -0
- package/dist/invoke-policy.d.ts.map +1 -0
- package/dist/invoke-policy.js +72 -0
- package/dist/invoke-policy.js.map +1 -0
- package/dist/invoke-runtime-config.d.ts +4 -0
- package/dist/invoke-runtime-config.d.ts.map +1 -0
- package/dist/invoke-runtime-config.js +15 -0
- package/dist/invoke-runtime-config.js.map +1 -0
- package/dist/jobs/in-memory-job-registry.d.ts +21 -0
- package/dist/jobs/in-memory-job-registry.d.ts.map +1 -0
- package/dist/jobs/in-memory-job-registry.js +44 -0
- package/dist/jobs/in-memory-job-registry.js.map +1 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +17 -0
- package/dist/logger.js.map +1 -0
- package/dist/openapi/registry-to-openapi.d.ts +11 -0
- package/dist/openapi/registry-to-openapi.d.ts.map +1 -0
- package/dist/openapi/registry-to-openapi.js +61 -0
- package/dist/openapi/registry-to-openapi.js.map +1 -0
- package/dist/registry-state.d.ts +107 -0
- package/dist/registry-state.d.ts.map +1 -0
- package/dist/registry-state.js +302 -0
- package/dist/registry-state.js.map +1 -0
- package/dist/return.d.ts +14 -0
- package/dist/return.d.ts.map +1 -0
- package/dist/return.js +107 -0
- package/dist/return.js.map +1 -0
- package/dist/rpc.d.ts +7 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +287 -0
- package/dist/rpc.js.map +1 -0
- package/dist/serialize-rpc-result.d.ts +38 -0
- package/dist/serialize-rpc-result.d.ts.map +1 -0
- package/dist/serialize-rpc-result.js +132 -0
- package/dist/serialize-rpc-result.js.map +1 -0
- package/dist/session-store.d.ts +9 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/session-store.js +19 -0
- package/dist/session-store.js.map +1 -0
- package/dist/source-utils.d.ts +19 -0
- package/dist/source-utils.d.ts.map +1 -0
- package/dist/source-utils.js +65 -0
- package/dist/source-utils.js.map +1 -0
- package/dist/transport/remote-bridge-cli.d.ts +2 -0
- package/dist/transport/remote-bridge-cli.d.ts.map +1 -0
- package/dist/transport/remote-bridge-cli.js +12 -0
- package/dist/transport/remote-bridge-cli.js.map +1 -0
- package/dist/transport/remote-inspector-bridge.d.ts +18 -0
- package/dist/transport/remote-inspector-bridge.d.ts.map +1 -0
- package/dist/transport/remote-inspector-bridge.js +71 -0
- package/dist/transport/remote-inspector-bridge.js.map +1 -0
- package/dist/transport/socket-io-playground.d.ts +78 -0
- package/dist/transport/socket-io-playground.d.ts.map +1 -0
- package/dist/transport/socket-io-playground.js +875 -0
- package/dist/transport/socket-io-playground.js.map +1 -0
- package/dist/transport/socket-io.d.ts +4 -0
- package/dist/transport/socket-io.d.ts.map +1 -0
- package/dist/transport/socket-io.js +4 -0
- package/dist/transport/socket-io.js.map +1 -0
- package/dist/transport/ws-server.d.ts +42 -0
- package/dist/transport/ws-server.d.ts.map +1 -0
- package/dist/transport/ws-server.js +429 -0
- package/dist/transport/ws-server.js.map +1 -0
- package/dist/type-string-normalize.d.ts +24 -0
- package/dist/type-string-normalize.d.ts.map +1 -0
- package/dist/type-string-normalize.js +178 -0
- package/dist/type-string-normalize.js.map +1 -0
- package/dist/types.d.ts +218 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/upload/fetch-file-url.d.ts +24 -0
- package/dist/upload/fetch-file-url.d.ts.map +1 -0
- package/dist/upload/fetch-file-url.js +130 -0
- package/dist/upload/fetch-file-url.js.map +1 -0
- package/dist/upload/host-minio-client.d.ts +30 -0
- package/dist/upload/host-minio-client.d.ts.map +1 -0
- package/dist/upload/host-minio-client.js +56 -0
- package/dist/upload/host-minio-client.js.map +1 -0
- package/dist/upload/materialize.d.ts +110 -0
- package/dist/upload/materialize.d.ts.map +1 -0
- package/dist/upload/materialize.js +466 -0
- package/dist/upload/materialize.js.map +1 -0
- package/dist/upload/upload-store.d.ts +44 -0
- package/dist/upload/upload-store.d.ts.map +1 -0
- package/dist/upload/upload-store.js +86 -0
- package/dist/upload/upload-store.js.map +1 -0
- package/dist/validate-params-json-schema.d.ts +13 -0
- package/dist/validate-params-json-schema.d.ts.map +1 -0
- package/dist/validate-params-json-schema.js +37 -0
- package/dist/validate-params-json-schema.js.map +1 -0
- package/dist/validate-return-json-schema.d.ts +9 -0
- package/dist/validate-return-json-schema.d.ts.map +1 -0
- package/dist/validate-return-json-schema.js +33 -0
- package/dist/validate-return-json-schema.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Parse `HostMinioOptions.endpoint` into Minio `Client` fields (hostname, port, TLS). */
|
|
2
|
+
export function parseHostMinioEndpoint(host) {
|
|
3
|
+
const raw = host.endpoint.trim();
|
|
4
|
+
if (!raw)
|
|
5
|
+
throw new Error('hostMinio.endpoint is required');
|
|
6
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `http://${raw}`;
|
|
7
|
+
let u;
|
|
8
|
+
try {
|
|
9
|
+
u = new URL(withScheme);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
throw new Error(`hostMinio.endpoint is not a valid URL or host:port: ${raw}`);
|
|
13
|
+
}
|
|
14
|
+
const port = u.port !== ''
|
|
15
|
+
? Number(u.port)
|
|
16
|
+
: u.protocol === 'https:'
|
|
17
|
+
? 443
|
|
18
|
+
: u.protocol === 'http:'
|
|
19
|
+
? 80
|
|
20
|
+
: 9000;
|
|
21
|
+
const useSSL = u.protocol === 'https:';
|
|
22
|
+
const endPoint = u.hostname;
|
|
23
|
+
if (!endPoint)
|
|
24
|
+
throw new Error('hostMinio.endpoint must include a hostname');
|
|
25
|
+
return {
|
|
26
|
+
endPoint,
|
|
27
|
+
port,
|
|
28
|
+
useSSL,
|
|
29
|
+
accessKey: host.accessKeyId,
|
|
30
|
+
secretKey: host.secretAccessKey,
|
|
31
|
+
region: host.region,
|
|
32
|
+
pathStyle: host.forcePathStyle ?? false,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* `putObject` then `presignedGetObject`. Uses dynamic `import('minio')` so consumers can keep `minio` as optional peer.
|
|
37
|
+
*/
|
|
38
|
+
export async function putBufferAndPresignGetUrl(host, input) {
|
|
39
|
+
const cfg = parseHostMinioEndpoint(host);
|
|
40
|
+
const Minio = await import('minio');
|
|
41
|
+
const client = new Minio.Client({
|
|
42
|
+
endPoint: cfg.endPoint,
|
|
43
|
+
port: cfg.port,
|
|
44
|
+
useSSL: cfg.useSSL,
|
|
45
|
+
accessKey: cfg.accessKey,
|
|
46
|
+
secretKey: cfg.secretKey,
|
|
47
|
+
...(cfg.region ? { region: cfg.region } : {}),
|
|
48
|
+
pathStyle: cfg.pathStyle,
|
|
49
|
+
});
|
|
50
|
+
const meta = { 'Content-Type': input.contentType };
|
|
51
|
+
await client.putObject(host.bucket, input.objectKey, input.buffer, input.buffer.length, meta);
|
|
52
|
+
const getExpiry = input.getExpirySeconds ?? 24 * 60 * 60;
|
|
53
|
+
const getUrl = await client.presignedGetObject(host.bucket, input.objectKey, getExpiry);
|
|
54
|
+
return { getUrl, objectKey: input.objectKey };
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=host-minio-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-minio-client.js","sourceRoot":"","sources":["../../src/upload/host-minio-client.ts"],"names":[],"mappings":"AAeA,0FAA0F;AAC1F,MAAM,UAAU,sBAAsB,CAAC,IAAsB;IAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;IAChC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAA;IAC3D,MAAM,UAAU,GAAG,0BAA0B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,GAAG,EAAE,CAAA;IAC/E,IAAI,CAAM,CAAA;IACV,IAAI,CAAC;QACJ,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAA;IACxB,CAAC;IAAC,MAAM,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,uDAAuD,GAAG,EAAE,CAAC,CAAA;IAC9E,CAAC;IACD,MAAM,IAAI,GACT,CAAC,CAAC,IAAI,KAAK,EAAE;QACZ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;QAChB,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ;YACxB,CAAC,CAAC,GAAG;YACL,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO;gBACvB,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,IAAI,CAAA;IACV,MAAM,MAAM,GAAG,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAA;IACtC,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAA;IAC3B,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;IAC5E,OAAO;QACN,QAAQ;QACR,IAAI;QACJ,MAAM;QACN,SAAS,EAAE,IAAI,CAAC,WAAW;QAC3B,SAAS,EAAE,IAAI,CAAC,eAAe;QAC/B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,SAAS,EAAE,IAAI,CAAC,cAAc,IAAI,KAAK;KACvC,CAAA;AACF,CAAC;AAUD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC9C,IAAsB,EACtB,KAAkC;IAElC,MAAM,GAAG,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAA;IACxC,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAA;IACnC,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC;QAC/B,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,SAAS,EAAE,GAAG,CAAC,SAAS;KACxB,CAAC,CAAA;IACF,MAAM,IAAI,GAA2B,EAAE,cAAc,EAAE,KAAK,CAAC,WAAW,EAAE,CAAA;IAC1E,MAAM,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAC7F,MAAM,SAAS,GAAG,KAAK,CAAC,gBAAgB,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,CAAA;IACxD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;IACvF,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAA;AAC9C,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { QaFileWireMode, QaMediaUploadTarget, RegistryEntry } from '../types.js';
|
|
2
|
+
import type { InMemoryUploadStore } from './upload-store.js';
|
|
3
|
+
import { type FetchFileUrlOptions } from './fetch-file-url.js';
|
|
4
|
+
type QaFileRef = {
|
|
5
|
+
__qaFileRef: string;
|
|
6
|
+
};
|
|
7
|
+
type QaFileRefs = {
|
|
8
|
+
__qaFileRefs: string[];
|
|
9
|
+
};
|
|
10
|
+
type QaFileUrl = {
|
|
11
|
+
__qaFileUrl: string;
|
|
12
|
+
};
|
|
13
|
+
type QaFileUrls = {
|
|
14
|
+
__qaFileUrls: string[];
|
|
15
|
+
};
|
|
16
|
+
export type MaterializeServiceWireOptions = {
|
|
17
|
+
/** Default `ref` when omitted. */
|
|
18
|
+
qaFileWire?: {
|
|
19
|
+
mode?: QaFileWireMode;
|
|
20
|
+
};
|
|
21
|
+
uploadStore?: InMemoryUploadStore | null;
|
|
22
|
+
fileUrl?: FetchFileUrlOptions | null;
|
|
23
|
+
};
|
|
24
|
+
export declare function materializeServiceArgsForInvoke(options: {
|
|
25
|
+
entry: RegistryEntry;
|
|
26
|
+
args: unknown[];
|
|
27
|
+
socketId: string;
|
|
28
|
+
materializeAs?: 'file' | 'buffer';
|
|
29
|
+
} & MaterializeServiceWireOptions): Promise<unknown[]>;
|
|
30
|
+
export type MulterLikeFile = {
|
|
31
|
+
fieldname: string;
|
|
32
|
+
originalname: string;
|
|
33
|
+
mimetype: string;
|
|
34
|
+
size: number;
|
|
35
|
+
buffer: Buffer;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* When materialized uploads use a single field name, `files` is `Record<field, MulterLikeFile[]>`.
|
|
39
|
+
* Flatten to `MulterLikeFile[]` on the payload so mock `req.files` is an array (handlers often use
|
|
40
|
+
* `(req.files as []).map(...)` like multer `.array()`).
|
|
41
|
+
*/
|
|
42
|
+
export declare function normalizeExpressPayloadFilesForPlayground(payload: {
|
|
43
|
+
files?: unknown;
|
|
44
|
+
}): void;
|
|
45
|
+
/**
|
|
46
|
+
* Turn wire payloads on the express RPC payload (`files` / `filesMany` with `__qaFileRef` or
|
|
47
|
+
* `__qaFileUrl`) into multer-like `req.file` / `req.files` on the host before the handler runs.
|
|
48
|
+
* When all uploads share one field name, `req.files` is set to a **flat array** of parts; multiple
|
|
49
|
+
* field names keep a **record** keyed by field. Call {@link normalizeExpressPayloadFilesForPlayground}
|
|
50
|
+
* before building the mock `req` if you merge payloads outside this helper.
|
|
51
|
+
*
|
|
52
|
+
* URL mode: the **client** already uploaded bytes and sent a GET URL (same as service RPC); this
|
|
53
|
+
* step **fetches** those URLs (`fetchFileUrl`) — it does not presign again. Ref mode: reads from
|
|
54
|
+
* `uploadStore` by `__qaFileRef`. Nested `filePaths` inside `express.body` are not handled here
|
|
55
|
+
* (would be a separate protocol/UI phase if product needs it).
|
|
56
|
+
*/
|
|
57
|
+
export declare function materializeExpressPayloadForInvoke(options: {
|
|
58
|
+
socketId: string;
|
|
59
|
+
uploadStore?: InMemoryUploadStore | null;
|
|
60
|
+
fileUrl?: FetchFileUrlOptions | null;
|
|
61
|
+
qaFileWire?: {
|
|
62
|
+
mode?: QaFileWireMode;
|
|
63
|
+
};
|
|
64
|
+
expressPayload: {
|
|
65
|
+
headers?: Record<string, string>;
|
|
66
|
+
body?: unknown;
|
|
67
|
+
method?: string;
|
|
68
|
+
path?: string;
|
|
69
|
+
} & {
|
|
70
|
+
files?: Array<{
|
|
71
|
+
fieldName: string;
|
|
72
|
+
ref: QaFileRef | QaFileUrl;
|
|
73
|
+
}>;
|
|
74
|
+
filesMany?: Array<{
|
|
75
|
+
fieldName: string;
|
|
76
|
+
refs: Array<QaFileRef | QaFileUrl>;
|
|
77
|
+
} | {
|
|
78
|
+
fieldName: string;
|
|
79
|
+
refs: QaFileRefs | QaFileUrls;
|
|
80
|
+
}>;
|
|
81
|
+
};
|
|
82
|
+
}): Promise<{
|
|
83
|
+
file?: MulterLikeFile;
|
|
84
|
+
files?: Record<string, MulterLikeFile[]> | MulterLikeFile[];
|
|
85
|
+
} & typeof options.expressPayload>;
|
|
86
|
+
/** Public slice of fileUrl options safe for BOOTSTRAP / catalog (no secrets in fileUrl itself). */
|
|
87
|
+
export declare function publicFileUrlCatalogSlice(opts: FetchFileUrlOptions | null | undefined): FetchFileUrlOptions | undefined;
|
|
88
|
+
export type CatalogWireExtras = {
|
|
89
|
+
qaFileWire: {
|
|
90
|
+
mode: QaFileWireMode;
|
|
91
|
+
};
|
|
92
|
+
qaMediaUpload?: {
|
|
93
|
+
target: QaMediaUploadTarget;
|
|
94
|
+
};
|
|
95
|
+
fileUrl?: FetchFileUrlOptions;
|
|
96
|
+
qaMediaUploadHostUploadUrl?: string;
|
|
97
|
+
};
|
|
98
|
+
export declare function buildCatalogWireExtras(options: {
|
|
99
|
+
qaFileWire?: {
|
|
100
|
+
mode?: QaFileWireMode;
|
|
101
|
+
};
|
|
102
|
+
qaMediaUpload?: {
|
|
103
|
+
target: QaMediaUploadTarget;
|
|
104
|
+
};
|
|
105
|
+
fileUrl?: FetchFileUrlOptions | null;
|
|
106
|
+
qaMediaUploadHostUploadUrl?: string;
|
|
107
|
+
}): CatalogWireExtras;
|
|
108
|
+
export declare function enrichRegistryParamsWithWireHints(registry: Record<string, RegistryEntry>, extras: CatalogWireExtras): Record<string, RegistryEntry>;
|
|
109
|
+
export {};
|
|
110
|
+
//# sourceMappingURL=materialize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"materialize.d.ts","sourceRoot":"","sources":["../../src/upload/materialize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { fetchFileUrl } from './fetch-file-url.js';
|
|
2
|
+
function isQaFileRef(v) {
|
|
3
|
+
return Boolean(v && typeof v === 'object' && typeof v.__qaFileRef === 'string');
|
|
4
|
+
}
|
|
5
|
+
function isQaFileRefs(v) {
|
|
6
|
+
return Boolean(v &&
|
|
7
|
+
typeof v === 'object' &&
|
|
8
|
+
Array.isArray(v.__qaFileRefs) &&
|
|
9
|
+
v.__qaFileRefs.every((x) => typeof x === 'string'));
|
|
10
|
+
}
|
|
11
|
+
function isArrayOfQaFileRef(v) {
|
|
12
|
+
return Array.isArray(v) && v.every((x) => isQaFileRef(x));
|
|
13
|
+
}
|
|
14
|
+
function isQaFileUrl(v) {
|
|
15
|
+
return Boolean(v && typeof v === 'object' && typeof v.__qaFileUrl === 'string');
|
|
16
|
+
}
|
|
17
|
+
function isQaFileUrls(v) {
|
|
18
|
+
return Boolean(v &&
|
|
19
|
+
typeof v === 'object' &&
|
|
20
|
+
Array.isArray(v.__qaFileUrls) &&
|
|
21
|
+
v.__qaFileUrls.every((x) => typeof x === 'string'));
|
|
22
|
+
}
|
|
23
|
+
function isArrayOfQaFileUrl(v) {
|
|
24
|
+
return Array.isArray(v) && v.every((x) => isQaFileUrl(x));
|
|
25
|
+
}
|
|
26
|
+
function assertLeafRefUrlExclusivity(leaf) {
|
|
27
|
+
if (!leaf || typeof leaf !== 'object')
|
|
28
|
+
return;
|
|
29
|
+
const o = leaf;
|
|
30
|
+
const hasRef = typeof o.__qaFileRef === 'string';
|
|
31
|
+
const hasUrl = typeof o.__qaFileUrl === 'string';
|
|
32
|
+
if (hasRef && hasUrl) {
|
|
33
|
+
throw new Error('FILE_REF_AND_URL: both __qaFileRef and __qaFileUrl on same value');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function getUploadOrThrow(store, socketId, uploadId) {
|
|
37
|
+
const u = store.get(socketId, uploadId, { consume: true });
|
|
38
|
+
if (!u)
|
|
39
|
+
throw new Error(`UPLOAD_NOT_FOUND: ${uploadId}`);
|
|
40
|
+
return u;
|
|
41
|
+
}
|
|
42
|
+
function bufferToFile(u) {
|
|
43
|
+
const FileCtor = globalThis.File;
|
|
44
|
+
if (!FileCtor)
|
|
45
|
+
throw new Error('FILE_UNAVAILABLE: global File is not available in this Node runtime');
|
|
46
|
+
return new FileCtor([u.buffer], u.filename, { type: u.contentType });
|
|
47
|
+
}
|
|
48
|
+
function bufferToFileFromParts(parts, as) {
|
|
49
|
+
if (as === 'buffer')
|
|
50
|
+
return parts.buffer;
|
|
51
|
+
const FileCtor = globalThis.File;
|
|
52
|
+
if (!FileCtor)
|
|
53
|
+
throw new Error('FILE_UNAVAILABLE: global File is not available in this Node runtime');
|
|
54
|
+
return new FileCtor([parts.buffer], parts.filename, { type: parts.contentType });
|
|
55
|
+
}
|
|
56
|
+
function materializeSingleRef(store, socketId, uploadId, as) {
|
|
57
|
+
const u = getUploadOrThrow(store, socketId, uploadId);
|
|
58
|
+
return as === 'file' ? bufferToFile(u) : u.buffer;
|
|
59
|
+
}
|
|
60
|
+
function materializeManyRef(store, socketId, uploadIds, as) {
|
|
61
|
+
return uploadIds.map((id) => materializeSingleRef(store, socketId, id, as));
|
|
62
|
+
}
|
|
63
|
+
async function materializeSingleUrl(url, fileUrl, as) {
|
|
64
|
+
const fetched = await fetchFileUrl(url, fileUrl);
|
|
65
|
+
return bufferToFileFromParts({
|
|
66
|
+
buffer: fetched.buffer,
|
|
67
|
+
filename: fetched.filename,
|
|
68
|
+
contentType: fetched.contentType,
|
|
69
|
+
}, as);
|
|
70
|
+
}
|
|
71
|
+
async function materializeManyUrl(urls, fileUrl, as) {
|
|
72
|
+
const out = [];
|
|
73
|
+
for (const u of urls) {
|
|
74
|
+
out.push(await materializeSingleUrl(u, fileUrl, as));
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
function effectiveWireMode(wire) {
|
|
79
|
+
return wire?.mode ?? 'ref';
|
|
80
|
+
}
|
|
81
|
+
function assertModeAllowsRef(mode) {
|
|
82
|
+
if (mode === 'url')
|
|
83
|
+
throw new Error('FILE_REF_NOT_ALLOWED: qaFileWire.mode is url');
|
|
84
|
+
}
|
|
85
|
+
function assertModeAllowsUrl(mode) {
|
|
86
|
+
if (mode === 'ref')
|
|
87
|
+
throw new Error('FILE_URL_NOT_ALLOWED: qaFileWire.mode is ref');
|
|
88
|
+
}
|
|
89
|
+
function parsePath(path) {
|
|
90
|
+
const out = [];
|
|
91
|
+
const parts = path.split('.');
|
|
92
|
+
for (const part of parts) {
|
|
93
|
+
const m = /^([^\[]+)(?:\[(\d+)\])?$/.exec(part);
|
|
94
|
+
if (!m)
|
|
95
|
+
continue;
|
|
96
|
+
out.push({ kind: 'prop', key: m[1] });
|
|
97
|
+
if (m[2] !== undefined)
|
|
98
|
+
out.push({ kind: 'index', index: Number(m[2]) });
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
function getAt(root, tokens) {
|
|
103
|
+
let cur = root;
|
|
104
|
+
for (const t of tokens) {
|
|
105
|
+
if (cur === null || cur === undefined)
|
|
106
|
+
return undefined;
|
|
107
|
+
if (t.kind === 'prop')
|
|
108
|
+
cur = cur[t.key];
|
|
109
|
+
else
|
|
110
|
+
cur = cur[t.index];
|
|
111
|
+
}
|
|
112
|
+
return cur;
|
|
113
|
+
}
|
|
114
|
+
function setAt(root, tokens, value) {
|
|
115
|
+
if (tokens.length === 0)
|
|
116
|
+
return;
|
|
117
|
+
let cur = root;
|
|
118
|
+
for (let i = 0; i < tokens.length - 1; i++) {
|
|
119
|
+
const t = tokens[i];
|
|
120
|
+
if (t.kind === 'prop') {
|
|
121
|
+
if (cur[t.key] === undefined)
|
|
122
|
+
cur[t.key] = {};
|
|
123
|
+
cur = cur[t.key];
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
if (!Array.isArray(cur))
|
|
127
|
+
throw new Error('PATH_NOT_ARRAY');
|
|
128
|
+
if (cur[t.index] === undefined)
|
|
129
|
+
cur[t.index] = {};
|
|
130
|
+
cur = cur[t.index];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const last = tokens[tokens.length - 1];
|
|
134
|
+
if (last.kind === 'prop')
|
|
135
|
+
cur[last.key] = value;
|
|
136
|
+
else {
|
|
137
|
+
if (!Array.isArray(cur))
|
|
138
|
+
throw new Error('PATH_NOT_ARRAY');
|
|
139
|
+
cur[last.index] = value;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export async function materializeServiceArgsForInvoke(options) {
|
|
143
|
+
const { entry, args, socketId } = options;
|
|
144
|
+
const materializeAs = options.materializeAs ?? 'buffer';
|
|
145
|
+
const uploadStore = options.uploadStore ?? null;
|
|
146
|
+
const fileUrl = options.fileUrl ?? null;
|
|
147
|
+
const mode = effectiveWireMode(options.qaFileWire);
|
|
148
|
+
const out = [...args];
|
|
149
|
+
for (let i = 0; i < entry.params.length; i++) {
|
|
150
|
+
const p = entry.params[i];
|
|
151
|
+
const v = out[i];
|
|
152
|
+
if (p.kind === 'file') {
|
|
153
|
+
assertLeafRefUrlExclusivity(v);
|
|
154
|
+
if (isQaFileRef(v)) {
|
|
155
|
+
assertModeAllowsRef(mode);
|
|
156
|
+
if (!uploadStore)
|
|
157
|
+
throw new Error('FILE_REF_NOT_ALLOWED: upload store not configured');
|
|
158
|
+
out[i] = materializeSingleRef(uploadStore, socketId, v.__qaFileRef, materializeAs);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (isQaFileUrl(v)) {
|
|
162
|
+
assertModeAllowsUrl(mode);
|
|
163
|
+
if (!fileUrl?.allowedHosts?.length) {
|
|
164
|
+
throw new Error('FILE_URL_MATERIALIZE_DISABLED: fileUrl.allowedHosts not configured');
|
|
165
|
+
}
|
|
166
|
+
out[i] = await materializeSingleUrl(v.__qaFileUrl, fileUrl, materializeAs);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (p.kind === 'files') {
|
|
171
|
+
if (isQaFileRefs(v)) {
|
|
172
|
+
assertModeAllowsRef(mode);
|
|
173
|
+
if (!uploadStore)
|
|
174
|
+
throw new Error('FILE_REF_NOT_ALLOWED: upload store not configured');
|
|
175
|
+
out[i] = materializeManyRef(uploadStore, socketId, v.__qaFileRefs, materializeAs);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (isArrayOfQaFileRef(v)) {
|
|
179
|
+
assertModeAllowsRef(mode);
|
|
180
|
+
if (!uploadStore)
|
|
181
|
+
throw new Error('FILE_REF_NOT_ALLOWED: upload store not configured');
|
|
182
|
+
out[i] = materializeManyRef(uploadStore, socketId, v.map((x) => x.__qaFileRef), materializeAs);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (isQaFileUrls(v)) {
|
|
186
|
+
assertModeAllowsUrl(mode);
|
|
187
|
+
if (!fileUrl?.allowedHosts?.length) {
|
|
188
|
+
throw new Error('FILE_URL_MATERIALIZE_DISABLED: fileUrl.allowedHosts not configured');
|
|
189
|
+
}
|
|
190
|
+
out[i] = await materializeManyUrl(v.__qaFileUrls, fileUrl, materializeAs);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (isArrayOfQaFileUrl(v)) {
|
|
194
|
+
assertModeAllowsUrl(mode);
|
|
195
|
+
if (!fileUrl?.allowedHosts?.length) {
|
|
196
|
+
throw new Error('FILE_URL_MATERIALIZE_DISABLED: fileUrl.allowedHosts not configured');
|
|
197
|
+
}
|
|
198
|
+
out[i] = await materializeManyUrl(v.map((x) => x.__qaFileUrl), fileUrl, materializeAs);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (p.filePaths && p.filePaths.length && v && typeof v === 'object') {
|
|
203
|
+
for (const path of p.filePaths) {
|
|
204
|
+
const tokens = parsePath(path);
|
|
205
|
+
const leaf = getAt(v, tokens);
|
|
206
|
+
assertLeafRefUrlExclusivity(leaf);
|
|
207
|
+
if (isQaFileRef(leaf)) {
|
|
208
|
+
assertModeAllowsRef(mode);
|
|
209
|
+
if (!uploadStore)
|
|
210
|
+
throw new Error('FILE_REF_NOT_ALLOWED: upload store not configured');
|
|
211
|
+
setAt(v, tokens, materializeSingleRef(uploadStore, socketId, leaf.__qaFileRef, materializeAs));
|
|
212
|
+
}
|
|
213
|
+
else if (isQaFileUrl(leaf)) {
|
|
214
|
+
assertModeAllowsUrl(mode);
|
|
215
|
+
if (!fileUrl?.allowedHosts?.length) {
|
|
216
|
+
throw new Error('FILE_URL_MATERIALIZE_DISABLED: fileUrl.allowedHosts not configured');
|
|
217
|
+
}
|
|
218
|
+
setAt(v, tokens, await materializeSingleUrl(leaf.__qaFileUrl, fileUrl, materializeAs));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (p.fileArrayPaths && p.fileArrayPaths.length && v && typeof v === 'object') {
|
|
223
|
+
for (const path of p.fileArrayPaths) {
|
|
224
|
+
const tokens = parsePath(path);
|
|
225
|
+
const leaf = getAt(v, tokens);
|
|
226
|
+
if (isQaFileRefs(leaf)) {
|
|
227
|
+
assertModeAllowsRef(mode);
|
|
228
|
+
if (!uploadStore)
|
|
229
|
+
throw new Error('FILE_REF_NOT_ALLOWED: upload store not configured');
|
|
230
|
+
setAt(v, tokens, materializeManyRef(uploadStore, socketId, leaf.__qaFileRefs, materializeAs));
|
|
231
|
+
}
|
|
232
|
+
else if (isArrayOfQaFileRef(leaf)) {
|
|
233
|
+
assertModeAllowsRef(mode);
|
|
234
|
+
if (!uploadStore)
|
|
235
|
+
throw new Error('FILE_REF_NOT_ALLOWED: upload store not configured');
|
|
236
|
+
setAt(v, tokens, materializeManyRef(uploadStore, socketId, leaf.map((x) => x.__qaFileRef), materializeAs));
|
|
237
|
+
}
|
|
238
|
+
else if (isQaFileUrls(leaf)) {
|
|
239
|
+
assertModeAllowsUrl(mode);
|
|
240
|
+
if (!fileUrl?.allowedHosts?.length) {
|
|
241
|
+
throw new Error('FILE_URL_MATERIALIZE_DISABLED: fileUrl.allowedHosts not configured');
|
|
242
|
+
}
|
|
243
|
+
setAt(v, tokens, await materializeManyUrl(leaf.__qaFileUrls, fileUrl, materializeAs));
|
|
244
|
+
}
|
|
245
|
+
else if (isArrayOfQaFileUrl(leaf)) {
|
|
246
|
+
assertModeAllowsUrl(mode);
|
|
247
|
+
if (!fileUrl?.allowedHosts?.length) {
|
|
248
|
+
throw new Error('FILE_URL_MATERIALIZE_DISABLED: fileUrl.allowedHosts not configured');
|
|
249
|
+
}
|
|
250
|
+
setAt(v, tokens, await materializeManyUrl(leaf.map((x) => x.__qaFileUrl), fileUrl, materializeAs));
|
|
251
|
+
}
|
|
252
|
+
else if (leaf && typeof leaf === 'object') {
|
|
253
|
+
assertLeafRefUrlExclusivity(leaf);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* When materialized uploads use a single field name, `files` is `Record<field, MulterLikeFile[]>`.
|
|
262
|
+
* Flatten to `MulterLikeFile[]` on the payload so mock `req.files` is an array (handlers often use
|
|
263
|
+
* `(req.files as []).map(...)` like multer `.array()`).
|
|
264
|
+
*/
|
|
265
|
+
export function normalizeExpressPayloadFilesForPlayground(payload) {
|
|
266
|
+
const raw = payload.files;
|
|
267
|
+
if (!raw || Array.isArray(raw) || typeof raw !== 'object')
|
|
268
|
+
return;
|
|
269
|
+
const record = raw;
|
|
270
|
+
const keys = Object.keys(record);
|
|
271
|
+
if (keys.length !== 1)
|
|
272
|
+
return;
|
|
273
|
+
const arr = record[keys[0]];
|
|
274
|
+
if (Array.isArray(arr))
|
|
275
|
+
payload.files = arr;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Turn wire payloads on the express RPC payload (`files` / `filesMany` with `__qaFileRef` or
|
|
279
|
+
* `__qaFileUrl`) into multer-like `req.file` / `req.files` on the host before the handler runs.
|
|
280
|
+
* When all uploads share one field name, `req.files` is set to a **flat array** of parts; multiple
|
|
281
|
+
* field names keep a **record** keyed by field. Call {@link normalizeExpressPayloadFilesForPlayground}
|
|
282
|
+
* before building the mock `req` if you merge payloads outside this helper.
|
|
283
|
+
*
|
|
284
|
+
* URL mode: the **client** already uploaded bytes and sent a GET URL (same as service RPC); this
|
|
285
|
+
* step **fetches** those URLs (`fetchFileUrl`) — it does not presign again. Ref mode: reads from
|
|
286
|
+
* `uploadStore` by `__qaFileRef`. Nested `filePaths` inside `express.body` are not handled here
|
|
287
|
+
* (would be a separate protocol/UI phase if product needs it).
|
|
288
|
+
*/
|
|
289
|
+
export async function materializeExpressPayloadForInvoke(options) {
|
|
290
|
+
const { socketId, uploadStore } = options;
|
|
291
|
+
const fileUrl = options.fileUrl ?? null;
|
|
292
|
+
const mode = effectiveWireMode(options.qaFileWire);
|
|
293
|
+
const payload = { ...options.expressPayload };
|
|
294
|
+
let single;
|
|
295
|
+
const filesByField = {};
|
|
296
|
+
if (Array.isArray(payload.files)) {
|
|
297
|
+
for (const f of payload.files) {
|
|
298
|
+
if (!f || typeof f !== 'object')
|
|
299
|
+
continue;
|
|
300
|
+
const ref = f.ref;
|
|
301
|
+
assertLeafRefUrlExclusivity(ref);
|
|
302
|
+
if (isQaFileRef(ref)) {
|
|
303
|
+
assertModeAllowsRef(mode);
|
|
304
|
+
if (!uploadStore)
|
|
305
|
+
throw new Error('FILE_REF_NOT_ALLOWED: upload store not configured');
|
|
306
|
+
const u = getUploadOrThrow(uploadStore, socketId, ref.__qaFileRef);
|
|
307
|
+
const mf = {
|
|
308
|
+
fieldname: f.fieldName,
|
|
309
|
+
originalname: u.filename,
|
|
310
|
+
mimetype: u.contentType,
|
|
311
|
+
size: u.sizeBytes,
|
|
312
|
+
buffer: u.buffer,
|
|
313
|
+
};
|
|
314
|
+
single = mf;
|
|
315
|
+
filesByField[f.fieldName] = [mf];
|
|
316
|
+
}
|
|
317
|
+
else if (isQaFileUrl(ref)) {
|
|
318
|
+
assertModeAllowsUrl(mode);
|
|
319
|
+
if (!fileUrl?.allowedHosts?.length) {
|
|
320
|
+
throw new Error('FILE_URL_MATERIALIZE_DISABLED: fileUrl.allowedHosts not configured');
|
|
321
|
+
}
|
|
322
|
+
const fetched = await fetchFileUrl(ref.__qaFileUrl, fileUrl);
|
|
323
|
+
const mf = {
|
|
324
|
+
fieldname: f.fieldName,
|
|
325
|
+
originalname: fetched.filename,
|
|
326
|
+
mimetype: fetched.contentType,
|
|
327
|
+
size: fetched.buffer.byteLength,
|
|
328
|
+
buffer: fetched.buffer,
|
|
329
|
+
};
|
|
330
|
+
single = mf;
|
|
331
|
+
filesByField[f.fieldName] = [mf];
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (Array.isArray(payload.filesMany)) {
|
|
336
|
+
for (const group of payload.filesMany) {
|
|
337
|
+
if (!group || typeof group !== 'object')
|
|
338
|
+
continue;
|
|
339
|
+
const fieldName = group.fieldName;
|
|
340
|
+
if (typeof fieldName !== 'string' || !fieldName)
|
|
341
|
+
continue;
|
|
342
|
+
const refsVal = group.refs;
|
|
343
|
+
const list = [];
|
|
344
|
+
if (isQaFileRefs(refsVal)) {
|
|
345
|
+
assertModeAllowsRef(mode);
|
|
346
|
+
if (!uploadStore)
|
|
347
|
+
throw new Error('FILE_REF_NOT_ALLOWED: upload store not configured');
|
|
348
|
+
for (const id of refsVal.__qaFileRefs) {
|
|
349
|
+
const u = getUploadOrThrow(uploadStore, socketId, id);
|
|
350
|
+
list.push({
|
|
351
|
+
fieldname: fieldName,
|
|
352
|
+
originalname: u.filename,
|
|
353
|
+
mimetype: u.contentType,
|
|
354
|
+
size: u.sizeBytes,
|
|
355
|
+
buffer: u.buffer,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
else if (isQaFileUrls(refsVal)) {
|
|
360
|
+
assertModeAllowsUrl(mode);
|
|
361
|
+
if (!fileUrl?.allowedHosts?.length) {
|
|
362
|
+
throw new Error('FILE_URL_MATERIALIZE_DISABLED: fileUrl.allowedHosts not configured');
|
|
363
|
+
}
|
|
364
|
+
for (const url of refsVal.__qaFileUrls) {
|
|
365
|
+
const fetched = await fetchFileUrl(url, fileUrl);
|
|
366
|
+
list.push({
|
|
367
|
+
fieldname: fieldName,
|
|
368
|
+
originalname: fetched.filename,
|
|
369
|
+
mimetype: fetched.contentType,
|
|
370
|
+
size: fetched.buffer.byteLength,
|
|
371
|
+
buffer: fetched.buffer,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
else if (Array.isArray(refsVal)) {
|
|
376
|
+
for (const r of refsVal) {
|
|
377
|
+
assertLeafRefUrlExclusivity(r);
|
|
378
|
+
if (isQaFileRef(r)) {
|
|
379
|
+
assertModeAllowsRef(mode);
|
|
380
|
+
if (!uploadStore)
|
|
381
|
+
throw new Error('FILE_REF_NOT_ALLOWED: upload store not configured');
|
|
382
|
+
const u = getUploadOrThrow(uploadStore, socketId, r.__qaFileRef);
|
|
383
|
+
list.push({
|
|
384
|
+
fieldname: fieldName,
|
|
385
|
+
originalname: u.filename,
|
|
386
|
+
mimetype: u.contentType,
|
|
387
|
+
size: u.sizeBytes,
|
|
388
|
+
buffer: u.buffer,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
else if (isQaFileUrl(r)) {
|
|
392
|
+
assertModeAllowsUrl(mode);
|
|
393
|
+
if (!fileUrl?.allowedHosts?.length) {
|
|
394
|
+
throw new Error('FILE_URL_MATERIALIZE_DISABLED: fileUrl.allowedHosts not configured');
|
|
395
|
+
}
|
|
396
|
+
const fetched = await fetchFileUrl(r.__qaFileUrl, fileUrl);
|
|
397
|
+
list.push({
|
|
398
|
+
fieldname: fieldName,
|
|
399
|
+
originalname: fetched.filename,
|
|
400
|
+
mimetype: fetched.contentType,
|
|
401
|
+
size: fetched.buffer.byteLength,
|
|
402
|
+
buffer: fetched.buffer,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (list.length)
|
|
408
|
+
filesByField[fieldName] = list;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
payload.file = single ?? payload.file;
|
|
412
|
+
const keys = Object.keys(filesByField);
|
|
413
|
+
if (keys.length === 1) {
|
|
414
|
+
payload.files = filesByField[keys[0]];
|
|
415
|
+
}
|
|
416
|
+
else if (keys.length > 1) {
|
|
417
|
+
payload.files = filesByField;
|
|
418
|
+
}
|
|
419
|
+
return payload;
|
|
420
|
+
}
|
|
421
|
+
/** Public slice of fileUrl options safe for BOOTSTRAP / catalog (no secrets in fileUrl itself). */
|
|
422
|
+
export function publicFileUrlCatalogSlice(opts) {
|
|
423
|
+
if (!opts)
|
|
424
|
+
return undefined;
|
|
425
|
+
return {
|
|
426
|
+
allowedHosts: [...opts.allowedHosts],
|
|
427
|
+
maxDownloadBytes: opts.maxDownloadBytes,
|
|
428
|
+
timeoutMs: opts.timeoutMs,
|
|
429
|
+
maxRedirects: opts.maxRedirects,
|
|
430
|
+
allowHttp: opts.allowHttp,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
export function buildCatalogWireExtras(options) {
|
|
434
|
+
const mode = options.qaFileWire?.mode ?? 'ref';
|
|
435
|
+
const out = { qaFileWire: { mode } };
|
|
436
|
+
if (options.qaMediaUpload)
|
|
437
|
+
out.qaMediaUpload = { ...options.qaMediaUpload };
|
|
438
|
+
const pub = publicFileUrlCatalogSlice(options.fileUrl ?? null);
|
|
439
|
+
if (pub)
|
|
440
|
+
out.fileUrl = pub;
|
|
441
|
+
if (typeof options.qaMediaUploadHostUploadUrl === 'string' && options.qaMediaUploadHostUploadUrl.trim()) {
|
|
442
|
+
out.qaMediaUploadHostUploadUrl = options.qaMediaUploadHostUploadUrl.trim();
|
|
443
|
+
}
|
|
444
|
+
return out;
|
|
445
|
+
}
|
|
446
|
+
export function enrichRegistryParamsWithWireHints(registry, extras) {
|
|
447
|
+
const mode = extras.qaFileWire.mode;
|
|
448
|
+
const target = extras.qaMediaUpload?.target;
|
|
449
|
+
return Object.fromEntries(Object.entries(registry).map(([k, entry]) => {
|
|
450
|
+
const params = entry.params.map((p) => {
|
|
451
|
+
const hasFile = p.kind === 'file' ||
|
|
452
|
+
p.kind === 'files' ||
|
|
453
|
+
(p.filePaths && p.filePaths.length > 0) ||
|
|
454
|
+
(p.fileArrayPaths && p.fileArrayPaths.length > 0);
|
|
455
|
+
if (!hasFile)
|
|
456
|
+
return { ...p };
|
|
457
|
+
return {
|
|
458
|
+
...p,
|
|
459
|
+
qaFileWire: mode,
|
|
460
|
+
...(target !== undefined ? { qaMediaUpload: target } : {}),
|
|
461
|
+
};
|
|
462
|
+
});
|
|
463
|
+
return [k, { ...entry, params }];
|
|
464
|
+
}));
|
|
465
|
+
}
|
|
466
|
+
//# sourceMappingURL=materialize.js.map
|