@platforma-sdk/model 1.77.20 → 1.78.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/package.cjs +1 -1
- package/dist/package.js +1 -1
- package/dist/render/accessor.cjs +38 -0
- package/dist/render/accessor.cjs.map +1 -1
- package/dist/render/accessor.d.ts +15 -0
- package/dist/render/accessor.d.ts.map +1 -1
- package/dist/render/accessor.js +38 -0
- package/dist/render/accessor.js.map +1 -1
- package/package.json +4 -4
- package/src/render/accessor.test.ts +106 -0
- package/src/render/accessor.ts +48 -0
package/dist/package.cjs
CHANGED
package/dist/package.js
CHANGED
package/dist/render/accessor.cjs
CHANGED
|
@@ -6,6 +6,19 @@ let _milaboratories_pl_model_common = require("@milaboratories/pl-model-common")
|
|
|
6
6
|
function ifDef(value, cb) {
|
|
7
7
|
return value === void 0 ? void 0 : cb(value);
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Decode an error node's content into a display message. The backend serializes
|
|
11
|
+
* a resource error as `{"message": "..."}` (`ResourceError`); unwrap that to the
|
|
12
|
+
* human-readable message. Falls back to the raw string when the content is not
|
|
13
|
+
* that envelope (e.g. plain text, or an unexpected shape).
|
|
14
|
+
*/
|
|
15
|
+
function decodeErrorMessage(raw) {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(raw);
|
|
18
|
+
if (typeof parsed?.message === "string") return parsed.message;
|
|
19
|
+
} catch {}
|
|
20
|
+
return raw;
|
|
21
|
+
}
|
|
9
22
|
/** Represent resource tree node accessor */
|
|
10
23
|
var TreeNodeAccessor = class TreeNodeAccessor {
|
|
11
24
|
constructor(handle, resolvePath) {
|
|
@@ -91,6 +104,31 @@ var TreeNodeAccessor = class TreeNodeAccessor {
|
|
|
91
104
|
return JSON.parse(content);
|
|
92
105
|
}
|
|
93
106
|
/**
|
|
107
|
+
* Like {@link getDataAsJson}, but does not throw while the resource is still
|
|
108
|
+
* computing. Three states:
|
|
109
|
+
*
|
|
110
|
+
* - **Not ready** → returns `undefined` (loading).
|
|
111
|
+
* - **Errored** → throws the resource's error.
|
|
112
|
+
* - **Ready** → returns the parsed content.
|
|
113
|
+
*
|
|
114
|
+
* Prefer this over {@link getDataAsJson} in reactive output lambdas: a field
|
|
115
|
+
* that resolves before its resource is ready (routine on remote backends)
|
|
116
|
+
* makes {@link getDataAsJson} throw "Resource has no content." mid-calculation,
|
|
117
|
+
* flashing a transient "Some outputs have errors" banner that clears when the
|
|
118
|
+
* resource finishes (MILAB-6318).
|
|
119
|
+
*/
|
|
120
|
+
getDataAsJsonOrUndefined() {
|
|
121
|
+
if (!this.getIsReadyOrError()) return void 0;
|
|
122
|
+
const error = this.getError();
|
|
123
|
+
if (error !== void 0) {
|
|
124
|
+
const raw = error.getDataAsString();
|
|
125
|
+
const message = raw === void 0 ? "Resource computation failed." : decodeErrorMessage(raw);
|
|
126
|
+
const path = this.resolvePath.join(".");
|
|
127
|
+
throw new Error(path ? `${message} (at ${path})` : message);
|
|
128
|
+
}
|
|
129
|
+
return this.getDataAsJson();
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
94
132
|
*
|
|
95
133
|
*/
|
|
96
134
|
getPColumns(errorOnUnknownField = false, prefix = "") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accessor.cjs","names":["getCfgRenderCtx","FutureRef"],"sources":["../../src/render/accessor.ts"],"sourcesContent":["import type {\n AnyLogHandle,\n ImportProgress,\n LocalBlobHandleAndSize,\n PColumn,\n PObject,\n RemoteBlobHandleAndSize,\n FolderURL,\n ArchiveFormat,\n ProgressLogWithInfo,\n RangeBytes,\n} from \"@milaboratories/pl-model-common\";\nimport { isPColumn, mapPObjectData } from \"@milaboratories/pl-model-common\";\nimport { getCfgRenderCtx } from \"../internal\";\nimport { FutureRef } from \"./future\";\nimport type { AccessorHandle } from \"./internal\";\nimport type { CommonFieldTraverseOps, FieldTraversalStep, ResourceType } from \"./traversal_ops\";\n\nexport function ifDef<T, R>(value: T | undefined, cb: (value: T) => R): R | undefined {\n return value === undefined ? undefined : cb(value);\n}\n\ntype FieldMapOps = {\n /**\n * Type of fields to iterate over.\n * (default 'Input')\n * */\n readonly fieldType?: \"Input\" | \"Output\" | \"Dynamic\";\n /**\n * If not locked, `undefined` value will be returned. Do nothing if mapping `Dynamic` fields.\n * (default true)\n * */\n readonly requireLocked?: boolean;\n /**\n * Will skip unresolved fields.\n * (default false)\n * */\n readonly skipUnresolved?: boolean;\n};\n\n/** Represent resource tree node accessor */\nexport class TreeNodeAccessor {\n constructor(\n public readonly handle: AccessorHandle,\n public readonly resolvePath: string[],\n ) {}\n\n /** Shortcut for {@link resolveInput} */\n public resolve(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n /** Shortcut for {@link resolveInput} */\n public resolve(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolve(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n const transformedSteps = steps.map(\n (s) =>\n ({\n assertFieldType: \"Input\",\n ...(typeof s === \"string\" ? { field: s } : s),\n }) satisfies FieldTraversalStep,\n );\n return this.resolveWithCommon({}, ...transformedSteps);\n }\n\n /** If field type assertion is not specified for the step, default is Output. */\n public resolveOutput(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n /** If field type assertion is not specified for the step, default is Output. */\n public resolveOutput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolveOutput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n const transformedSteps = steps.map(\n (s) =>\n ({\n assertFieldType: \"Output\",\n ...(typeof s === \"string\" ? { field: s } : s),\n }) satisfies FieldTraversalStep,\n );\n return this.resolveWithCommon({}, ...transformedSteps);\n }\n\n /** If field type assertion is not specified for the step, default is Input. */\n public resolveInput(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n /** If field type assertion is not specified for the step, default is Input. */\n public resolveInput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolveInput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n const transformedSteps = steps.map(\n (s) =>\n ({\n assertFieldType: \"Input\",\n ...(typeof s === \"string\" ? { field: s } : s),\n }) satisfies FieldTraversalStep,\n );\n return this.resolveWithCommon({}, ...transformedSteps);\n }\n\n public resolveAny(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n public resolveAny(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolveAny(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n return this.resolveWithCommon({}, ...steps);\n }\n\n public resolveWithCommon(\n commonOptions: CommonFieldTraverseOps,\n ...steps: (FieldTraversalStep | string)[]\n ): TreeNodeAccessor | undefined {\n const resolvePath = [\n ...this.resolvePath,\n ...steps.map((step) => (typeof step === \"string\" ? step : step.field)),\n ];\n return ifDef(\n getCfgRenderCtx().resolveWithCommon(this.handle, commonOptions, ...steps),\n (accessor) => new TreeNodeAccessor(accessor, resolvePath),\n );\n }\n\n public get resourceType(): ResourceType {\n return getCfgRenderCtx().getResourceType(this.handle);\n }\n\n public getInputsLocked(): boolean {\n return getCfgRenderCtx().getInputsLocked(this.handle);\n }\n\n public getOutputsLocked(): boolean {\n return getCfgRenderCtx().getOutputsLocked(this.handle);\n }\n\n public getIsReadyOrError(): boolean {\n return getCfgRenderCtx().getIsReadyOrError(this.handle);\n }\n\n public getIsFinal(): boolean {\n return getCfgRenderCtx().getIsFinal(this.handle);\n }\n\n public getError(): TreeNodeAccessor | undefined {\n const resolvePath = [...this.resolvePath, \"error\"];\n return ifDef(\n getCfgRenderCtx().getError(this.handle),\n (accsessor) => new TreeNodeAccessor(accsessor, resolvePath),\n );\n }\n\n public listInputFields(): string[] {\n return getCfgRenderCtx().listInputFields(this.handle);\n }\n\n public listOutputFields(): string[] {\n return getCfgRenderCtx().listOutputFields(this.handle);\n }\n\n public listDynamicFields(): string[] {\n return getCfgRenderCtx().listDynamicFields(this.handle);\n }\n\n public getKeyValueBase64(key: string): string | undefined {\n return getCfgRenderCtx().getKeyValueBase64(this.handle, key);\n }\n\n public getKeyValueAsString(key: string): string | undefined {\n return getCfgRenderCtx().getKeyValueAsString(this.handle, key);\n }\n\n public getKeyValueAsJson<T>(key: string): T {\n const content = this.getKeyValueAsString(key);\n if (content == undefined) throw new Error(\"Resource has no content.\");\n return JSON.parse(content);\n }\n\n public getDataBase64(): string | undefined {\n return getCfgRenderCtx().getDataBase64(this.handle);\n }\n\n public getDataAsString(): string | undefined {\n return getCfgRenderCtx().getDataAsString(this.handle);\n }\n\n public getDataAsJson<T>(): T {\n const content = this.getDataAsString();\n if (content == undefined) throw new Error(\"Resource has no content.\");\n return JSON.parse(content);\n }\n\n /**\n *\n */\n public getPColumns(\n errorOnUnknownField: boolean = false,\n prefix: string = \"\",\n ): PColumn<TreeNodeAccessor>[] | undefined {\n const result = this.parsePObjectCollection(errorOnUnknownField, prefix);\n if (result === undefined) return undefined;\n\n const pf = Object.entries(result).map(([, obj]) => {\n if (!isPColumn(obj)) throw new Error(`not a PColumn (kind = ${obj.spec.kind})`);\n return obj;\n });\n\n return pf;\n }\n\n /**\n *\n */\n public parsePObjectCollection(\n errorOnUnknownField: boolean = false,\n prefix: string = \"\",\n ): Record<string, PObject<TreeNodeAccessor>> | undefined {\n const pObjects = getCfgRenderCtx().parsePObjectCollection(\n this.handle,\n errorOnUnknownField,\n prefix,\n ...this.resolvePath,\n );\n if (pObjects === undefined) return undefined;\n const result: Record<string, PObject<TreeNodeAccessor>> = {};\n for (const [key, value] of Object.entries(pObjects)) {\n const resolvePath = [...this.resolvePath, key];\n result[key] = mapPObjectData(value, (c) => new TreeNodeAccessor(c, resolvePath));\n }\n return result;\n }\n\n public getFileContentAsBase64(range?: RangeBytes): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getBlobContentAsBase64(this.handle, range));\n }\n\n public getFileContentAsString(range?: RangeBytes): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getBlobContentAsString(this.handle, range));\n }\n\n public getFileContentAsJson<T>(range?: RangeBytes): FutureRef<T | undefined> {\n return new FutureRef<string | undefined>(\n getCfgRenderCtx().getBlobContentAsString(this.handle, range),\n ).mapDefined((v) => JSON.parse(v) as T);\n }\n\n /**\n * @deprecated use getFileContentAsBase64\n */\n public getBlobContentAsBase64(): FutureRef<string | undefined> {\n return this.getFileContentAsBase64();\n }\n\n /**\n * @deprecated use getFileContentAsString\n */\n public getBlobContentAsString(): FutureRef<string | undefined> {\n return this.getFileContentAsString();\n }\n\n /**\n * @returns downloaded file handle\n */\n public getFileHandle(): FutureRef<LocalBlobHandleAndSize | undefined> {\n return new FutureRef(getCfgRenderCtx().getDownloadedBlobContentHandle(this.handle));\n }\n\n /**\n * @deprecated use getFileHandle\n */\n public getDownloadedBlobHandle(): FutureRef<LocalBlobHandleAndSize | undefined> {\n return this.getFileHandle();\n }\n\n /**\n * @returns downloaded file handle\n */\n public getRemoteFileHandle(): FutureRef<RemoteBlobHandleAndSize | undefined> {\n return new FutureRef(getCfgRenderCtx().getOnDemandBlobContentHandle(this.handle));\n }\n\n /**\n * @deprecated use getRemoteFileHandle\n */\n public getOnDemandBlobHandle(): FutureRef<RemoteBlobHandleAndSize | undefined> {\n return this.getRemoteFileHandle();\n }\n\n /**\n * @returns the url to the extracted folder\n */\n public extractArchiveAndGetURL(format: ArchiveFormat): FutureRef<FolderURL | undefined> {\n return new FutureRef(getCfgRenderCtx().extractArchiveAndGetURL(this.handle, format));\n }\n\n public getImportProgress(): FutureRef<ImportProgress> {\n return new FutureRef(getCfgRenderCtx().getImportProgress(this.handle));\n }\n\n public getLastLogs(nLines: number): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getLastLogs(this.handle, nLines));\n }\n\n public getProgressLog(patternToSearch: string): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getProgressLog(this.handle, patternToSearch));\n }\n\n public getProgressLogWithInfo(\n patternToSearch: string,\n ): FutureRef<ProgressLogWithInfo | undefined> {\n return new FutureRef(getCfgRenderCtx().getProgressLogWithInfo(this.handle, patternToSearch));\n }\n\n public getLogHandle(): FutureRef<AnyLogHandle | undefined> {\n return new FutureRef(getCfgRenderCtx().getLogHandle(this.handle));\n }\n\n public allFieldsResolved(fieldType: \"Input\" | \"Output\" = \"Input\"): boolean {\n switch (fieldType) {\n case \"Input\":\n return (\n this.getInputsLocked() &&\n this.listInputFields().every(\n (field) => this.resolve({ field, assertFieldType: \"Input\" }) !== undefined,\n )\n );\n case \"Output\":\n return (\n this.getOutputsLocked() &&\n this.listOutputFields().every(\n (field) => this.resolve({ field, assertFieldType: \"Output\" }) !== undefined,\n )\n );\n }\n }\n\n public mapFields<T>(\n _mapping: (name: string, value: TreeNodeAccessor) => T,\n _ops: FieldMapOps & { skipUnresolved: true },\n ): T[] | undefined;\n public mapFields<T>(\n _mapping: (name: string, value: TreeNodeAccessor | undefined) => T,\n _ops?: FieldMapOps,\n ): T[] | undefined;\n public mapFields<T>(\n _mapping: (name: string, value: TreeNodeAccessor) => T,\n _ops?: FieldMapOps,\n ): T[] | undefined {\n const { fieldType, requireLocked, skipUnresolved } = {\n fieldType: \"Input\" as const,\n requireLocked: true,\n skipUnresolved: false,\n ..._ops,\n };\n const mapping = _mapping as (name: string, value: TreeNodeAccessor | undefined) => T;\n if (requireLocked) {\n if (fieldType === \"Input\" && !this.getInputsLocked()) return undefined;\n if (fieldType === \"Output\" && !this.getOutputsLocked()) return undefined;\n }\n const fieldList =\n fieldType === \"Input\"\n ? this.listInputFields()\n : fieldType === \"Output\"\n ? this.listOutputFields()\n : this.listDynamicFields();\n let fieldEntries = fieldList.map(\n (field) => [field, this.resolve({ field, assertFieldType: fieldType })] as const,\n );\n if (skipUnresolved) fieldEntries = fieldEntries.filter((e) => e[1] !== undefined);\n return fieldEntries.map(([name, value]) => mapping(name, value));\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"accessor.cjs","names":["getCfgRenderCtx","FutureRef"],"sources":["../../src/render/accessor.ts"],"sourcesContent":["import type {\n AnyLogHandle,\n ImportProgress,\n LocalBlobHandleAndSize,\n PColumn,\n PObject,\n RemoteBlobHandleAndSize,\n FolderURL,\n ArchiveFormat,\n ProgressLogWithInfo,\n RangeBytes,\n} from \"@milaboratories/pl-model-common\";\nimport { isPColumn, mapPObjectData } from \"@milaboratories/pl-model-common\";\nimport { getCfgRenderCtx } from \"../internal\";\nimport { FutureRef } from \"./future\";\nimport type { AccessorHandle } from \"./internal\";\nimport type { CommonFieldTraverseOps, FieldTraversalStep, ResourceType } from \"./traversal_ops\";\n\nexport function ifDef<T, R>(value: T | undefined, cb: (value: T) => R): R | undefined {\n return value === undefined ? undefined : cb(value);\n}\n\n/**\n * Decode an error node's content into a display message. The backend serializes\n * a resource error as `{\"message\": \"...\"}` (`ResourceError`); unwrap that to the\n * human-readable message. Falls back to the raw string when the content is not\n * that envelope (e.g. plain text, or an unexpected shape).\n */\nfunction decodeErrorMessage(raw: string): string {\n try {\n const parsed = JSON.parse(raw) as { message?: unknown };\n if (typeof parsed?.message === \"string\") return parsed.message;\n } catch {\n // Not JSON — surface the raw content.\n }\n return raw;\n}\n\ntype FieldMapOps = {\n /**\n * Type of fields to iterate over.\n * (default 'Input')\n * */\n readonly fieldType?: \"Input\" | \"Output\" | \"Dynamic\";\n /**\n * If not locked, `undefined` value will be returned. Do nothing if mapping `Dynamic` fields.\n * (default true)\n * */\n readonly requireLocked?: boolean;\n /**\n * Will skip unresolved fields.\n * (default false)\n * */\n readonly skipUnresolved?: boolean;\n};\n\n/** Represent resource tree node accessor */\nexport class TreeNodeAccessor {\n constructor(\n public readonly handle: AccessorHandle,\n public readonly resolvePath: string[],\n ) {}\n\n /** Shortcut for {@link resolveInput} */\n public resolve(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n /** Shortcut for {@link resolveInput} */\n public resolve(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolve(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n const transformedSteps = steps.map(\n (s) =>\n ({\n assertFieldType: \"Input\",\n ...(typeof s === \"string\" ? { field: s } : s),\n }) satisfies FieldTraversalStep,\n );\n return this.resolveWithCommon({}, ...transformedSteps);\n }\n\n /** If field type assertion is not specified for the step, default is Output. */\n public resolveOutput(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n /** If field type assertion is not specified for the step, default is Output. */\n public resolveOutput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolveOutput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n const transformedSteps = steps.map(\n (s) =>\n ({\n assertFieldType: \"Output\",\n ...(typeof s === \"string\" ? { field: s } : s),\n }) satisfies FieldTraversalStep,\n );\n return this.resolveWithCommon({}, ...transformedSteps);\n }\n\n /** If field type assertion is not specified for the step, default is Input. */\n public resolveInput(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n /** If field type assertion is not specified for the step, default is Input. */\n public resolveInput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolveInput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n const transformedSteps = steps.map(\n (s) =>\n ({\n assertFieldType: \"Input\",\n ...(typeof s === \"string\" ? { field: s } : s),\n }) satisfies FieldTraversalStep,\n );\n return this.resolveWithCommon({}, ...transformedSteps);\n }\n\n public resolveAny(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n public resolveAny(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolveAny(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n return this.resolveWithCommon({}, ...steps);\n }\n\n public resolveWithCommon(\n commonOptions: CommonFieldTraverseOps,\n ...steps: (FieldTraversalStep | string)[]\n ): TreeNodeAccessor | undefined {\n const resolvePath = [\n ...this.resolvePath,\n ...steps.map((step) => (typeof step === \"string\" ? step : step.field)),\n ];\n return ifDef(\n getCfgRenderCtx().resolveWithCommon(this.handle, commonOptions, ...steps),\n (accessor) => new TreeNodeAccessor(accessor, resolvePath),\n );\n }\n\n public get resourceType(): ResourceType {\n return getCfgRenderCtx().getResourceType(this.handle);\n }\n\n public getInputsLocked(): boolean {\n return getCfgRenderCtx().getInputsLocked(this.handle);\n }\n\n public getOutputsLocked(): boolean {\n return getCfgRenderCtx().getOutputsLocked(this.handle);\n }\n\n public getIsReadyOrError(): boolean {\n return getCfgRenderCtx().getIsReadyOrError(this.handle);\n }\n\n public getIsFinal(): boolean {\n return getCfgRenderCtx().getIsFinal(this.handle);\n }\n\n public getError(): TreeNodeAccessor | undefined {\n const resolvePath = [...this.resolvePath, \"error\"];\n return ifDef(\n getCfgRenderCtx().getError(this.handle),\n (accsessor) => new TreeNodeAccessor(accsessor, resolvePath),\n );\n }\n\n public listInputFields(): string[] {\n return getCfgRenderCtx().listInputFields(this.handle);\n }\n\n public listOutputFields(): string[] {\n return getCfgRenderCtx().listOutputFields(this.handle);\n }\n\n public listDynamicFields(): string[] {\n return getCfgRenderCtx().listDynamicFields(this.handle);\n }\n\n public getKeyValueBase64(key: string): string | undefined {\n return getCfgRenderCtx().getKeyValueBase64(this.handle, key);\n }\n\n public getKeyValueAsString(key: string): string | undefined {\n return getCfgRenderCtx().getKeyValueAsString(this.handle, key);\n }\n\n public getKeyValueAsJson<T>(key: string): T {\n const content = this.getKeyValueAsString(key);\n if (content == undefined) throw new Error(\"Resource has no content.\");\n return JSON.parse(content);\n }\n\n public getDataBase64(): string | undefined {\n return getCfgRenderCtx().getDataBase64(this.handle);\n }\n\n public getDataAsString(): string | undefined {\n return getCfgRenderCtx().getDataAsString(this.handle);\n }\n\n public getDataAsJson<T>(): T {\n const content = this.getDataAsString();\n if (content == undefined) throw new Error(\"Resource has no content.\");\n return JSON.parse(content);\n }\n\n /**\n * Like {@link getDataAsJson}, but does not throw while the resource is still\n * computing. Three states:\n *\n * - **Not ready** → returns `undefined` (loading).\n * - **Errored** → throws the resource's error.\n * - **Ready** → returns the parsed content.\n *\n * Prefer this over {@link getDataAsJson} in reactive output lambdas: a field\n * that resolves before its resource is ready (routine on remote backends)\n * makes {@link getDataAsJson} throw \"Resource has no content.\" mid-calculation,\n * flashing a transient \"Some outputs have errors\" banner that clears when the\n * resource finishes (MILAB-6318).\n */\n public getDataAsJsonOrUndefined<T>(): T | undefined {\n // Not ready → undefined (loading). getIsReadyOrError() registers the\n // readiness dependency, so the lambda re-runs once the resource finishes.\n if (!this.getIsReadyOrError()) return undefined;\n // Errored → throw the actual error, decoded from the error node and tagged\n // with the resolve path for debugging context. Beats getDataAsJson's\n // generic \"Resource has no content.\"\n const error = this.getError();\n if (error !== undefined) {\n const raw = error.getDataAsString();\n const message = raw === undefined ? \"Resource computation failed.\" : decodeErrorMessage(raw);\n const path = this.resolvePath.join(\".\");\n throw new Error(path ? `${message} (at ${path})` : message);\n }\n // Ready → parse the content.\n return this.getDataAsJson<T>();\n }\n\n /**\n *\n */\n public getPColumns(\n errorOnUnknownField: boolean = false,\n prefix: string = \"\",\n ): PColumn<TreeNodeAccessor>[] | undefined {\n const result = this.parsePObjectCollection(errorOnUnknownField, prefix);\n if (result === undefined) return undefined;\n\n const pf = Object.entries(result).map(([, obj]) => {\n if (!isPColumn(obj)) throw new Error(`not a PColumn (kind = ${obj.spec.kind})`);\n return obj;\n });\n\n return pf;\n }\n\n /**\n *\n */\n public parsePObjectCollection(\n errorOnUnknownField: boolean = false,\n prefix: string = \"\",\n ): Record<string, PObject<TreeNodeAccessor>> | undefined {\n const pObjects = getCfgRenderCtx().parsePObjectCollection(\n this.handle,\n errorOnUnknownField,\n prefix,\n ...this.resolvePath,\n );\n if (pObjects === undefined) return undefined;\n const result: Record<string, PObject<TreeNodeAccessor>> = {};\n for (const [key, value] of Object.entries(pObjects)) {\n const resolvePath = [...this.resolvePath, key];\n result[key] = mapPObjectData(value, (c) => new TreeNodeAccessor(c, resolvePath));\n }\n return result;\n }\n\n public getFileContentAsBase64(range?: RangeBytes): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getBlobContentAsBase64(this.handle, range));\n }\n\n public getFileContentAsString(range?: RangeBytes): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getBlobContentAsString(this.handle, range));\n }\n\n public getFileContentAsJson<T>(range?: RangeBytes): FutureRef<T | undefined> {\n return new FutureRef<string | undefined>(\n getCfgRenderCtx().getBlobContentAsString(this.handle, range),\n ).mapDefined((v) => JSON.parse(v) as T);\n }\n\n /**\n * @deprecated use getFileContentAsBase64\n */\n public getBlobContentAsBase64(): FutureRef<string | undefined> {\n return this.getFileContentAsBase64();\n }\n\n /**\n * @deprecated use getFileContentAsString\n */\n public getBlobContentAsString(): FutureRef<string | undefined> {\n return this.getFileContentAsString();\n }\n\n /**\n * @returns downloaded file handle\n */\n public getFileHandle(): FutureRef<LocalBlobHandleAndSize | undefined> {\n return new FutureRef(getCfgRenderCtx().getDownloadedBlobContentHandle(this.handle));\n }\n\n /**\n * @deprecated use getFileHandle\n */\n public getDownloadedBlobHandle(): FutureRef<LocalBlobHandleAndSize | undefined> {\n return this.getFileHandle();\n }\n\n /**\n * @returns downloaded file handle\n */\n public getRemoteFileHandle(): FutureRef<RemoteBlobHandleAndSize | undefined> {\n return new FutureRef(getCfgRenderCtx().getOnDemandBlobContentHandle(this.handle));\n }\n\n /**\n * @deprecated use getRemoteFileHandle\n */\n public getOnDemandBlobHandle(): FutureRef<RemoteBlobHandleAndSize | undefined> {\n return this.getRemoteFileHandle();\n }\n\n /**\n * @returns the url to the extracted folder\n */\n public extractArchiveAndGetURL(format: ArchiveFormat): FutureRef<FolderURL | undefined> {\n return new FutureRef(getCfgRenderCtx().extractArchiveAndGetURL(this.handle, format));\n }\n\n public getImportProgress(): FutureRef<ImportProgress> {\n return new FutureRef(getCfgRenderCtx().getImportProgress(this.handle));\n }\n\n public getLastLogs(nLines: number): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getLastLogs(this.handle, nLines));\n }\n\n public getProgressLog(patternToSearch: string): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getProgressLog(this.handle, patternToSearch));\n }\n\n public getProgressLogWithInfo(\n patternToSearch: string,\n ): FutureRef<ProgressLogWithInfo | undefined> {\n return new FutureRef(getCfgRenderCtx().getProgressLogWithInfo(this.handle, patternToSearch));\n }\n\n public getLogHandle(): FutureRef<AnyLogHandle | undefined> {\n return new FutureRef(getCfgRenderCtx().getLogHandle(this.handle));\n }\n\n public allFieldsResolved(fieldType: \"Input\" | \"Output\" = \"Input\"): boolean {\n switch (fieldType) {\n case \"Input\":\n return (\n this.getInputsLocked() &&\n this.listInputFields().every(\n (field) => this.resolve({ field, assertFieldType: \"Input\" }) !== undefined,\n )\n );\n case \"Output\":\n return (\n this.getOutputsLocked() &&\n this.listOutputFields().every(\n (field) => this.resolve({ field, assertFieldType: \"Output\" }) !== undefined,\n )\n );\n }\n }\n\n public mapFields<T>(\n _mapping: (name: string, value: TreeNodeAccessor) => T,\n _ops: FieldMapOps & { skipUnresolved: true },\n ): T[] | undefined;\n public mapFields<T>(\n _mapping: (name: string, value: TreeNodeAccessor | undefined) => T,\n _ops?: FieldMapOps,\n ): T[] | undefined;\n public mapFields<T>(\n _mapping: (name: string, value: TreeNodeAccessor) => T,\n _ops?: FieldMapOps,\n ): T[] | undefined {\n const { fieldType, requireLocked, skipUnresolved } = {\n fieldType: \"Input\" as const,\n requireLocked: true,\n skipUnresolved: false,\n ..._ops,\n };\n const mapping = _mapping as (name: string, value: TreeNodeAccessor | undefined) => T;\n if (requireLocked) {\n if (fieldType === \"Input\" && !this.getInputsLocked()) return undefined;\n if (fieldType === \"Output\" && !this.getOutputsLocked()) return undefined;\n }\n const fieldList =\n fieldType === \"Input\"\n ? this.listInputFields()\n : fieldType === \"Output\"\n ? this.listOutputFields()\n : this.listDynamicFields();\n let fieldEntries = fieldList.map(\n (field) => [field, this.resolve({ field, assertFieldType: fieldType })] as const,\n );\n if (skipUnresolved) fieldEntries = fieldEntries.filter((e) => e[1] !== undefined);\n return fieldEntries.map(([name, value]) => mapping(name, value));\n }\n}\n"],"mappings":"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"}
|
|
@@ -65,6 +65,21 @@ declare class TreeNodeAccessor {
|
|
|
65
65
|
getDataBase64(): string | undefined;
|
|
66
66
|
getDataAsString(): string | undefined;
|
|
67
67
|
getDataAsJson<T>(): T;
|
|
68
|
+
/**
|
|
69
|
+
* Like {@link getDataAsJson}, but does not throw while the resource is still
|
|
70
|
+
* computing. Three states:
|
|
71
|
+
*
|
|
72
|
+
* - **Not ready** → returns `undefined` (loading).
|
|
73
|
+
* - **Errored** → throws the resource's error.
|
|
74
|
+
* - **Ready** → returns the parsed content.
|
|
75
|
+
*
|
|
76
|
+
* Prefer this over {@link getDataAsJson} in reactive output lambdas: a field
|
|
77
|
+
* that resolves before its resource is ready (routine on remote backends)
|
|
78
|
+
* makes {@link getDataAsJson} throw "Resource has no content." mid-calculation,
|
|
79
|
+
* flashing a transient "Some outputs have errors" banner that clears when the
|
|
80
|
+
* resource finishes (MILAB-6318).
|
|
81
|
+
*/
|
|
82
|
+
getDataAsJsonOrUndefined<T>(): T | undefined;
|
|
68
83
|
/**
|
|
69
84
|
*
|
|
70
85
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accessor.d.ts","names":[],"sources":["../../src/render/accessor.ts"],"mappings":";;;;;;iBAkBgB,KAAA,MAAA,CAAY,KAAA,EAAO,CAAA,cAAe,EAAA,GAAK,KAAA,EAAO,CAAA,KAAM,CAAA,GAAI,CAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"accessor.d.ts","names":[],"sources":["../../src/render/accessor.ts"],"mappings":"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"}
|
package/dist/render/accessor.js
CHANGED
|
@@ -5,6 +5,19 @@ import { isPColumn, mapPObjectData } from "@milaboratories/pl-model-common";
|
|
|
5
5
|
function ifDef(value, cb) {
|
|
6
6
|
return value === void 0 ? void 0 : cb(value);
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Decode an error node's content into a display message. The backend serializes
|
|
10
|
+
* a resource error as `{"message": "..."}` (`ResourceError`); unwrap that to the
|
|
11
|
+
* human-readable message. Falls back to the raw string when the content is not
|
|
12
|
+
* that envelope (e.g. plain text, or an unexpected shape).
|
|
13
|
+
*/
|
|
14
|
+
function decodeErrorMessage(raw) {
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(raw);
|
|
17
|
+
if (typeof parsed?.message === "string") return parsed.message;
|
|
18
|
+
} catch {}
|
|
19
|
+
return raw;
|
|
20
|
+
}
|
|
8
21
|
/** Represent resource tree node accessor */
|
|
9
22
|
var TreeNodeAccessor = class TreeNodeAccessor {
|
|
10
23
|
constructor(handle, resolvePath) {
|
|
@@ -90,6 +103,31 @@ var TreeNodeAccessor = class TreeNodeAccessor {
|
|
|
90
103
|
return JSON.parse(content);
|
|
91
104
|
}
|
|
92
105
|
/**
|
|
106
|
+
* Like {@link getDataAsJson}, but does not throw while the resource is still
|
|
107
|
+
* computing. Three states:
|
|
108
|
+
*
|
|
109
|
+
* - **Not ready** → returns `undefined` (loading).
|
|
110
|
+
* - **Errored** → throws the resource's error.
|
|
111
|
+
* - **Ready** → returns the parsed content.
|
|
112
|
+
*
|
|
113
|
+
* Prefer this over {@link getDataAsJson} in reactive output lambdas: a field
|
|
114
|
+
* that resolves before its resource is ready (routine on remote backends)
|
|
115
|
+
* makes {@link getDataAsJson} throw "Resource has no content." mid-calculation,
|
|
116
|
+
* flashing a transient "Some outputs have errors" banner that clears when the
|
|
117
|
+
* resource finishes (MILAB-6318).
|
|
118
|
+
*/
|
|
119
|
+
getDataAsJsonOrUndefined() {
|
|
120
|
+
if (!this.getIsReadyOrError()) return void 0;
|
|
121
|
+
const error = this.getError();
|
|
122
|
+
if (error !== void 0) {
|
|
123
|
+
const raw = error.getDataAsString();
|
|
124
|
+
const message = raw === void 0 ? "Resource computation failed." : decodeErrorMessage(raw);
|
|
125
|
+
const path = this.resolvePath.join(".");
|
|
126
|
+
throw new Error(path ? `${message} (at ${path})` : message);
|
|
127
|
+
}
|
|
128
|
+
return this.getDataAsJson();
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
93
131
|
*
|
|
94
132
|
*/
|
|
95
133
|
getPColumns(errorOnUnknownField = false, prefix = "") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accessor.js","names":[],"sources":["../../src/render/accessor.ts"],"sourcesContent":["import type {\n AnyLogHandle,\n ImportProgress,\n LocalBlobHandleAndSize,\n PColumn,\n PObject,\n RemoteBlobHandleAndSize,\n FolderURL,\n ArchiveFormat,\n ProgressLogWithInfo,\n RangeBytes,\n} from \"@milaboratories/pl-model-common\";\nimport { isPColumn, mapPObjectData } from \"@milaboratories/pl-model-common\";\nimport { getCfgRenderCtx } from \"../internal\";\nimport { FutureRef } from \"./future\";\nimport type { AccessorHandle } from \"./internal\";\nimport type { CommonFieldTraverseOps, FieldTraversalStep, ResourceType } from \"./traversal_ops\";\n\nexport function ifDef<T, R>(value: T | undefined, cb: (value: T) => R): R | undefined {\n return value === undefined ? undefined : cb(value);\n}\n\ntype FieldMapOps = {\n /**\n * Type of fields to iterate over.\n * (default 'Input')\n * */\n readonly fieldType?: \"Input\" | \"Output\" | \"Dynamic\";\n /**\n * If not locked, `undefined` value will be returned. Do nothing if mapping `Dynamic` fields.\n * (default true)\n * */\n readonly requireLocked?: boolean;\n /**\n * Will skip unresolved fields.\n * (default false)\n * */\n readonly skipUnresolved?: boolean;\n};\n\n/** Represent resource tree node accessor */\nexport class TreeNodeAccessor {\n constructor(\n public readonly handle: AccessorHandle,\n public readonly resolvePath: string[],\n ) {}\n\n /** Shortcut for {@link resolveInput} */\n public resolve(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n /** Shortcut for {@link resolveInput} */\n public resolve(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolve(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n const transformedSteps = steps.map(\n (s) =>\n ({\n assertFieldType: \"Input\",\n ...(typeof s === \"string\" ? { field: s } : s),\n }) satisfies FieldTraversalStep,\n );\n return this.resolveWithCommon({}, ...transformedSteps);\n }\n\n /** If field type assertion is not specified for the step, default is Output. */\n public resolveOutput(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n /** If field type assertion is not specified for the step, default is Output. */\n public resolveOutput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolveOutput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n const transformedSteps = steps.map(\n (s) =>\n ({\n assertFieldType: \"Output\",\n ...(typeof s === \"string\" ? { field: s } : s),\n }) satisfies FieldTraversalStep,\n );\n return this.resolveWithCommon({}, ...transformedSteps);\n }\n\n /** If field type assertion is not specified for the step, default is Input. */\n public resolveInput(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n /** If field type assertion is not specified for the step, default is Input. */\n public resolveInput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolveInput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n const transformedSteps = steps.map(\n (s) =>\n ({\n assertFieldType: \"Input\",\n ...(typeof s === \"string\" ? { field: s } : s),\n }) satisfies FieldTraversalStep,\n );\n return this.resolveWithCommon({}, ...transformedSteps);\n }\n\n public resolveAny(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n public resolveAny(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolveAny(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n return this.resolveWithCommon({}, ...steps);\n }\n\n public resolveWithCommon(\n commonOptions: CommonFieldTraverseOps,\n ...steps: (FieldTraversalStep | string)[]\n ): TreeNodeAccessor | undefined {\n const resolvePath = [\n ...this.resolvePath,\n ...steps.map((step) => (typeof step === \"string\" ? step : step.field)),\n ];\n return ifDef(\n getCfgRenderCtx().resolveWithCommon(this.handle, commonOptions, ...steps),\n (accessor) => new TreeNodeAccessor(accessor, resolvePath),\n );\n }\n\n public get resourceType(): ResourceType {\n return getCfgRenderCtx().getResourceType(this.handle);\n }\n\n public getInputsLocked(): boolean {\n return getCfgRenderCtx().getInputsLocked(this.handle);\n }\n\n public getOutputsLocked(): boolean {\n return getCfgRenderCtx().getOutputsLocked(this.handle);\n }\n\n public getIsReadyOrError(): boolean {\n return getCfgRenderCtx().getIsReadyOrError(this.handle);\n }\n\n public getIsFinal(): boolean {\n return getCfgRenderCtx().getIsFinal(this.handle);\n }\n\n public getError(): TreeNodeAccessor | undefined {\n const resolvePath = [...this.resolvePath, \"error\"];\n return ifDef(\n getCfgRenderCtx().getError(this.handle),\n (accsessor) => new TreeNodeAccessor(accsessor, resolvePath),\n );\n }\n\n public listInputFields(): string[] {\n return getCfgRenderCtx().listInputFields(this.handle);\n }\n\n public listOutputFields(): string[] {\n return getCfgRenderCtx().listOutputFields(this.handle);\n }\n\n public listDynamicFields(): string[] {\n return getCfgRenderCtx().listDynamicFields(this.handle);\n }\n\n public getKeyValueBase64(key: string): string | undefined {\n return getCfgRenderCtx().getKeyValueBase64(this.handle, key);\n }\n\n public getKeyValueAsString(key: string): string | undefined {\n return getCfgRenderCtx().getKeyValueAsString(this.handle, key);\n }\n\n public getKeyValueAsJson<T>(key: string): T {\n const content = this.getKeyValueAsString(key);\n if (content == undefined) throw new Error(\"Resource has no content.\");\n return JSON.parse(content);\n }\n\n public getDataBase64(): string | undefined {\n return getCfgRenderCtx().getDataBase64(this.handle);\n }\n\n public getDataAsString(): string | undefined {\n return getCfgRenderCtx().getDataAsString(this.handle);\n }\n\n public getDataAsJson<T>(): T {\n const content = this.getDataAsString();\n if (content == undefined) throw new Error(\"Resource has no content.\");\n return JSON.parse(content);\n }\n\n /**\n *\n */\n public getPColumns(\n errorOnUnknownField: boolean = false,\n prefix: string = \"\",\n ): PColumn<TreeNodeAccessor>[] | undefined {\n const result = this.parsePObjectCollection(errorOnUnknownField, prefix);\n if (result === undefined) return undefined;\n\n const pf = Object.entries(result).map(([, obj]) => {\n if (!isPColumn(obj)) throw new Error(`not a PColumn (kind = ${obj.spec.kind})`);\n return obj;\n });\n\n return pf;\n }\n\n /**\n *\n */\n public parsePObjectCollection(\n errorOnUnknownField: boolean = false,\n prefix: string = \"\",\n ): Record<string, PObject<TreeNodeAccessor>> | undefined {\n const pObjects = getCfgRenderCtx().parsePObjectCollection(\n this.handle,\n errorOnUnknownField,\n prefix,\n ...this.resolvePath,\n );\n if (pObjects === undefined) return undefined;\n const result: Record<string, PObject<TreeNodeAccessor>> = {};\n for (const [key, value] of Object.entries(pObjects)) {\n const resolvePath = [...this.resolvePath, key];\n result[key] = mapPObjectData(value, (c) => new TreeNodeAccessor(c, resolvePath));\n }\n return result;\n }\n\n public getFileContentAsBase64(range?: RangeBytes): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getBlobContentAsBase64(this.handle, range));\n }\n\n public getFileContentAsString(range?: RangeBytes): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getBlobContentAsString(this.handle, range));\n }\n\n public getFileContentAsJson<T>(range?: RangeBytes): FutureRef<T | undefined> {\n return new FutureRef<string | undefined>(\n getCfgRenderCtx().getBlobContentAsString(this.handle, range),\n ).mapDefined((v) => JSON.parse(v) as T);\n }\n\n /**\n * @deprecated use getFileContentAsBase64\n */\n public getBlobContentAsBase64(): FutureRef<string | undefined> {\n return this.getFileContentAsBase64();\n }\n\n /**\n * @deprecated use getFileContentAsString\n */\n public getBlobContentAsString(): FutureRef<string | undefined> {\n return this.getFileContentAsString();\n }\n\n /**\n * @returns downloaded file handle\n */\n public getFileHandle(): FutureRef<LocalBlobHandleAndSize | undefined> {\n return new FutureRef(getCfgRenderCtx().getDownloadedBlobContentHandle(this.handle));\n }\n\n /**\n * @deprecated use getFileHandle\n */\n public getDownloadedBlobHandle(): FutureRef<LocalBlobHandleAndSize | undefined> {\n return this.getFileHandle();\n }\n\n /**\n * @returns downloaded file handle\n */\n public getRemoteFileHandle(): FutureRef<RemoteBlobHandleAndSize | undefined> {\n return new FutureRef(getCfgRenderCtx().getOnDemandBlobContentHandle(this.handle));\n }\n\n /**\n * @deprecated use getRemoteFileHandle\n */\n public getOnDemandBlobHandle(): FutureRef<RemoteBlobHandleAndSize | undefined> {\n return this.getRemoteFileHandle();\n }\n\n /**\n * @returns the url to the extracted folder\n */\n public extractArchiveAndGetURL(format: ArchiveFormat): FutureRef<FolderURL | undefined> {\n return new FutureRef(getCfgRenderCtx().extractArchiveAndGetURL(this.handle, format));\n }\n\n public getImportProgress(): FutureRef<ImportProgress> {\n return new FutureRef(getCfgRenderCtx().getImportProgress(this.handle));\n }\n\n public getLastLogs(nLines: number): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getLastLogs(this.handle, nLines));\n }\n\n public getProgressLog(patternToSearch: string): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getProgressLog(this.handle, patternToSearch));\n }\n\n public getProgressLogWithInfo(\n patternToSearch: string,\n ): FutureRef<ProgressLogWithInfo | undefined> {\n return new FutureRef(getCfgRenderCtx().getProgressLogWithInfo(this.handle, patternToSearch));\n }\n\n public getLogHandle(): FutureRef<AnyLogHandle | undefined> {\n return new FutureRef(getCfgRenderCtx().getLogHandle(this.handle));\n }\n\n public allFieldsResolved(fieldType: \"Input\" | \"Output\" = \"Input\"): boolean {\n switch (fieldType) {\n case \"Input\":\n return (\n this.getInputsLocked() &&\n this.listInputFields().every(\n (field) => this.resolve({ field, assertFieldType: \"Input\" }) !== undefined,\n )\n );\n case \"Output\":\n return (\n this.getOutputsLocked() &&\n this.listOutputFields().every(\n (field) => this.resolve({ field, assertFieldType: \"Output\" }) !== undefined,\n )\n );\n }\n }\n\n public mapFields<T>(\n _mapping: (name: string, value: TreeNodeAccessor) => T,\n _ops: FieldMapOps & { skipUnresolved: true },\n ): T[] | undefined;\n public mapFields<T>(\n _mapping: (name: string, value: TreeNodeAccessor | undefined) => T,\n _ops?: FieldMapOps,\n ): T[] | undefined;\n public mapFields<T>(\n _mapping: (name: string, value: TreeNodeAccessor) => T,\n _ops?: FieldMapOps,\n ): T[] | undefined {\n const { fieldType, requireLocked, skipUnresolved } = {\n fieldType: \"Input\" as const,\n requireLocked: true,\n skipUnresolved: false,\n ..._ops,\n };\n const mapping = _mapping as (name: string, value: TreeNodeAccessor | undefined) => T;\n if (requireLocked) {\n if (fieldType === \"Input\" && !this.getInputsLocked()) return undefined;\n if (fieldType === \"Output\" && !this.getOutputsLocked()) return undefined;\n }\n const fieldList =\n fieldType === \"Input\"\n ? this.listInputFields()\n : fieldType === \"Output\"\n ? this.listOutputFields()\n : this.listDynamicFields();\n let fieldEntries = fieldList.map(\n (field) => [field, this.resolve({ field, assertFieldType: fieldType })] as const,\n );\n if (skipUnresolved) fieldEntries = fieldEntries.filter((e) => e[1] !== undefined);\n return fieldEntries.map(([name, value]) => mapping(name, value));\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"accessor.js","names":[],"sources":["../../src/render/accessor.ts"],"sourcesContent":["import type {\n AnyLogHandle,\n ImportProgress,\n LocalBlobHandleAndSize,\n PColumn,\n PObject,\n RemoteBlobHandleAndSize,\n FolderURL,\n ArchiveFormat,\n ProgressLogWithInfo,\n RangeBytes,\n} from \"@milaboratories/pl-model-common\";\nimport { isPColumn, mapPObjectData } from \"@milaboratories/pl-model-common\";\nimport { getCfgRenderCtx } from \"../internal\";\nimport { FutureRef } from \"./future\";\nimport type { AccessorHandle } from \"./internal\";\nimport type { CommonFieldTraverseOps, FieldTraversalStep, ResourceType } from \"./traversal_ops\";\n\nexport function ifDef<T, R>(value: T | undefined, cb: (value: T) => R): R | undefined {\n return value === undefined ? undefined : cb(value);\n}\n\n/**\n * Decode an error node's content into a display message. The backend serializes\n * a resource error as `{\"message\": \"...\"}` (`ResourceError`); unwrap that to the\n * human-readable message. Falls back to the raw string when the content is not\n * that envelope (e.g. plain text, or an unexpected shape).\n */\nfunction decodeErrorMessage(raw: string): string {\n try {\n const parsed = JSON.parse(raw) as { message?: unknown };\n if (typeof parsed?.message === \"string\") return parsed.message;\n } catch {\n // Not JSON — surface the raw content.\n }\n return raw;\n}\n\ntype FieldMapOps = {\n /**\n * Type of fields to iterate over.\n * (default 'Input')\n * */\n readonly fieldType?: \"Input\" | \"Output\" | \"Dynamic\";\n /**\n * If not locked, `undefined` value will be returned. Do nothing if mapping `Dynamic` fields.\n * (default true)\n * */\n readonly requireLocked?: boolean;\n /**\n * Will skip unresolved fields.\n * (default false)\n * */\n readonly skipUnresolved?: boolean;\n};\n\n/** Represent resource tree node accessor */\nexport class TreeNodeAccessor {\n constructor(\n public readonly handle: AccessorHandle,\n public readonly resolvePath: string[],\n ) {}\n\n /** Shortcut for {@link resolveInput} */\n public resolve(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n /** Shortcut for {@link resolveInput} */\n public resolve(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolve(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n const transformedSteps = steps.map(\n (s) =>\n ({\n assertFieldType: \"Input\",\n ...(typeof s === \"string\" ? { field: s } : s),\n }) satisfies FieldTraversalStep,\n );\n return this.resolveWithCommon({}, ...transformedSteps);\n }\n\n /** If field type assertion is not specified for the step, default is Output. */\n public resolveOutput(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n /** If field type assertion is not specified for the step, default is Output. */\n public resolveOutput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolveOutput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n const transformedSteps = steps.map(\n (s) =>\n ({\n assertFieldType: \"Output\",\n ...(typeof s === \"string\" ? { field: s } : s),\n }) satisfies FieldTraversalStep,\n );\n return this.resolveWithCommon({}, ...transformedSteps);\n }\n\n /** If field type assertion is not specified for the step, default is Input. */\n public resolveInput(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n /** If field type assertion is not specified for the step, default is Input. */\n public resolveInput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolveInput(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n const transformedSteps = steps.map(\n (s) =>\n ({\n assertFieldType: \"Input\",\n ...(typeof s === \"string\" ? { field: s } : s),\n }) satisfies FieldTraversalStep,\n );\n return this.resolveWithCommon({}, ...transformedSteps);\n }\n\n public resolveAny(\n ...steps: [\n Omit<FieldTraversalStep, \"errorIfFieldNotSet\"> & {\n errorIfFieldNotAssigned: true;\n },\n ]\n ): TreeNodeAccessor;\n public resolveAny(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined;\n public resolveAny(...steps: (FieldTraversalStep | string)[]): TreeNodeAccessor | undefined {\n return this.resolveWithCommon({}, ...steps);\n }\n\n public resolveWithCommon(\n commonOptions: CommonFieldTraverseOps,\n ...steps: (FieldTraversalStep | string)[]\n ): TreeNodeAccessor | undefined {\n const resolvePath = [\n ...this.resolvePath,\n ...steps.map((step) => (typeof step === \"string\" ? step : step.field)),\n ];\n return ifDef(\n getCfgRenderCtx().resolveWithCommon(this.handle, commonOptions, ...steps),\n (accessor) => new TreeNodeAccessor(accessor, resolvePath),\n );\n }\n\n public get resourceType(): ResourceType {\n return getCfgRenderCtx().getResourceType(this.handle);\n }\n\n public getInputsLocked(): boolean {\n return getCfgRenderCtx().getInputsLocked(this.handle);\n }\n\n public getOutputsLocked(): boolean {\n return getCfgRenderCtx().getOutputsLocked(this.handle);\n }\n\n public getIsReadyOrError(): boolean {\n return getCfgRenderCtx().getIsReadyOrError(this.handle);\n }\n\n public getIsFinal(): boolean {\n return getCfgRenderCtx().getIsFinal(this.handle);\n }\n\n public getError(): TreeNodeAccessor | undefined {\n const resolvePath = [...this.resolvePath, \"error\"];\n return ifDef(\n getCfgRenderCtx().getError(this.handle),\n (accsessor) => new TreeNodeAccessor(accsessor, resolvePath),\n );\n }\n\n public listInputFields(): string[] {\n return getCfgRenderCtx().listInputFields(this.handle);\n }\n\n public listOutputFields(): string[] {\n return getCfgRenderCtx().listOutputFields(this.handle);\n }\n\n public listDynamicFields(): string[] {\n return getCfgRenderCtx().listDynamicFields(this.handle);\n }\n\n public getKeyValueBase64(key: string): string | undefined {\n return getCfgRenderCtx().getKeyValueBase64(this.handle, key);\n }\n\n public getKeyValueAsString(key: string): string | undefined {\n return getCfgRenderCtx().getKeyValueAsString(this.handle, key);\n }\n\n public getKeyValueAsJson<T>(key: string): T {\n const content = this.getKeyValueAsString(key);\n if (content == undefined) throw new Error(\"Resource has no content.\");\n return JSON.parse(content);\n }\n\n public getDataBase64(): string | undefined {\n return getCfgRenderCtx().getDataBase64(this.handle);\n }\n\n public getDataAsString(): string | undefined {\n return getCfgRenderCtx().getDataAsString(this.handle);\n }\n\n public getDataAsJson<T>(): T {\n const content = this.getDataAsString();\n if (content == undefined) throw new Error(\"Resource has no content.\");\n return JSON.parse(content);\n }\n\n /**\n * Like {@link getDataAsJson}, but does not throw while the resource is still\n * computing. Three states:\n *\n * - **Not ready** → returns `undefined` (loading).\n * - **Errored** → throws the resource's error.\n * - **Ready** → returns the parsed content.\n *\n * Prefer this over {@link getDataAsJson} in reactive output lambdas: a field\n * that resolves before its resource is ready (routine on remote backends)\n * makes {@link getDataAsJson} throw \"Resource has no content.\" mid-calculation,\n * flashing a transient \"Some outputs have errors\" banner that clears when the\n * resource finishes (MILAB-6318).\n */\n public getDataAsJsonOrUndefined<T>(): T | undefined {\n // Not ready → undefined (loading). getIsReadyOrError() registers the\n // readiness dependency, so the lambda re-runs once the resource finishes.\n if (!this.getIsReadyOrError()) return undefined;\n // Errored → throw the actual error, decoded from the error node and tagged\n // with the resolve path for debugging context. Beats getDataAsJson's\n // generic \"Resource has no content.\"\n const error = this.getError();\n if (error !== undefined) {\n const raw = error.getDataAsString();\n const message = raw === undefined ? \"Resource computation failed.\" : decodeErrorMessage(raw);\n const path = this.resolvePath.join(\".\");\n throw new Error(path ? `${message} (at ${path})` : message);\n }\n // Ready → parse the content.\n return this.getDataAsJson<T>();\n }\n\n /**\n *\n */\n public getPColumns(\n errorOnUnknownField: boolean = false,\n prefix: string = \"\",\n ): PColumn<TreeNodeAccessor>[] | undefined {\n const result = this.parsePObjectCollection(errorOnUnknownField, prefix);\n if (result === undefined) return undefined;\n\n const pf = Object.entries(result).map(([, obj]) => {\n if (!isPColumn(obj)) throw new Error(`not a PColumn (kind = ${obj.spec.kind})`);\n return obj;\n });\n\n return pf;\n }\n\n /**\n *\n */\n public parsePObjectCollection(\n errorOnUnknownField: boolean = false,\n prefix: string = \"\",\n ): Record<string, PObject<TreeNodeAccessor>> | undefined {\n const pObjects = getCfgRenderCtx().parsePObjectCollection(\n this.handle,\n errorOnUnknownField,\n prefix,\n ...this.resolvePath,\n );\n if (pObjects === undefined) return undefined;\n const result: Record<string, PObject<TreeNodeAccessor>> = {};\n for (const [key, value] of Object.entries(pObjects)) {\n const resolvePath = [...this.resolvePath, key];\n result[key] = mapPObjectData(value, (c) => new TreeNodeAccessor(c, resolvePath));\n }\n return result;\n }\n\n public getFileContentAsBase64(range?: RangeBytes): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getBlobContentAsBase64(this.handle, range));\n }\n\n public getFileContentAsString(range?: RangeBytes): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getBlobContentAsString(this.handle, range));\n }\n\n public getFileContentAsJson<T>(range?: RangeBytes): FutureRef<T | undefined> {\n return new FutureRef<string | undefined>(\n getCfgRenderCtx().getBlobContentAsString(this.handle, range),\n ).mapDefined((v) => JSON.parse(v) as T);\n }\n\n /**\n * @deprecated use getFileContentAsBase64\n */\n public getBlobContentAsBase64(): FutureRef<string | undefined> {\n return this.getFileContentAsBase64();\n }\n\n /**\n * @deprecated use getFileContentAsString\n */\n public getBlobContentAsString(): FutureRef<string | undefined> {\n return this.getFileContentAsString();\n }\n\n /**\n * @returns downloaded file handle\n */\n public getFileHandle(): FutureRef<LocalBlobHandleAndSize | undefined> {\n return new FutureRef(getCfgRenderCtx().getDownloadedBlobContentHandle(this.handle));\n }\n\n /**\n * @deprecated use getFileHandle\n */\n public getDownloadedBlobHandle(): FutureRef<LocalBlobHandleAndSize | undefined> {\n return this.getFileHandle();\n }\n\n /**\n * @returns downloaded file handle\n */\n public getRemoteFileHandle(): FutureRef<RemoteBlobHandleAndSize | undefined> {\n return new FutureRef(getCfgRenderCtx().getOnDemandBlobContentHandle(this.handle));\n }\n\n /**\n * @deprecated use getRemoteFileHandle\n */\n public getOnDemandBlobHandle(): FutureRef<RemoteBlobHandleAndSize | undefined> {\n return this.getRemoteFileHandle();\n }\n\n /**\n * @returns the url to the extracted folder\n */\n public extractArchiveAndGetURL(format: ArchiveFormat): FutureRef<FolderURL | undefined> {\n return new FutureRef(getCfgRenderCtx().extractArchiveAndGetURL(this.handle, format));\n }\n\n public getImportProgress(): FutureRef<ImportProgress> {\n return new FutureRef(getCfgRenderCtx().getImportProgress(this.handle));\n }\n\n public getLastLogs(nLines: number): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getLastLogs(this.handle, nLines));\n }\n\n public getProgressLog(patternToSearch: string): FutureRef<string | undefined> {\n return new FutureRef(getCfgRenderCtx().getProgressLog(this.handle, patternToSearch));\n }\n\n public getProgressLogWithInfo(\n patternToSearch: string,\n ): FutureRef<ProgressLogWithInfo | undefined> {\n return new FutureRef(getCfgRenderCtx().getProgressLogWithInfo(this.handle, patternToSearch));\n }\n\n public getLogHandle(): FutureRef<AnyLogHandle | undefined> {\n return new FutureRef(getCfgRenderCtx().getLogHandle(this.handle));\n }\n\n public allFieldsResolved(fieldType: \"Input\" | \"Output\" = \"Input\"): boolean {\n switch (fieldType) {\n case \"Input\":\n return (\n this.getInputsLocked() &&\n this.listInputFields().every(\n (field) => this.resolve({ field, assertFieldType: \"Input\" }) !== undefined,\n )\n );\n case \"Output\":\n return (\n this.getOutputsLocked() &&\n this.listOutputFields().every(\n (field) => this.resolve({ field, assertFieldType: \"Output\" }) !== undefined,\n )\n );\n }\n }\n\n public mapFields<T>(\n _mapping: (name: string, value: TreeNodeAccessor) => T,\n _ops: FieldMapOps & { skipUnresolved: true },\n ): T[] | undefined;\n public mapFields<T>(\n _mapping: (name: string, value: TreeNodeAccessor | undefined) => T,\n _ops?: FieldMapOps,\n ): T[] | undefined;\n public mapFields<T>(\n _mapping: (name: string, value: TreeNodeAccessor) => T,\n _ops?: FieldMapOps,\n ): T[] | undefined {\n const { fieldType, requireLocked, skipUnresolved } = {\n fieldType: \"Input\" as const,\n requireLocked: true,\n skipUnresolved: false,\n ..._ops,\n };\n const mapping = _mapping as (name: string, value: TreeNodeAccessor | undefined) => T;\n if (requireLocked) {\n if (fieldType === \"Input\" && !this.getInputsLocked()) return undefined;\n if (fieldType === \"Output\" && !this.getOutputsLocked()) return undefined;\n }\n const fieldList =\n fieldType === \"Input\"\n ? this.listInputFields()\n : fieldType === \"Output\"\n ? this.listOutputFields()\n : this.listDynamicFields();\n let fieldEntries = fieldList.map(\n (field) => [field, this.resolve({ field, assertFieldType: fieldType })] as const,\n );\n if (skipUnresolved) fieldEntries = fieldEntries.filter((e) => e[1] !== undefined);\n return fieldEntries.map(([name, value]) => mapping(name, value));\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platforma-sdk/model",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.78.1",
|
|
4
4
|
"description": "Platforma.bio SDK / Block Model",
|
|
5
5
|
"files": [
|
|
6
6
|
"./dist/**/*",
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"fast-json-patch": "^3.1.1",
|
|
31
31
|
"utility-types": "^3.11.0",
|
|
32
32
|
"zod": "~3.25.76",
|
|
33
|
-
"@milaboratories/pl-error-like": "1.12.10",
|
|
34
33
|
"@milaboratories/pl-model-middle-layer": "1.27.0",
|
|
35
|
-
"@milaboratories/helpers": "1.14.2",
|
|
36
34
|
"@milaboratories/pl-model-common": "1.43.0",
|
|
35
|
+
"@milaboratories/helpers": "1.14.2",
|
|
36
|
+
"@milaboratories/pl-error-like": "1.12.10",
|
|
37
37
|
"@milaboratories/ptabler-expression-js": "1.2.26"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"fast-json-patch": "^3.1.1",
|
|
42
42
|
"typescript": "~5.9.3",
|
|
43
43
|
"vitest": "^4.1.3",
|
|
44
|
-
"@milaboratories/pf-driver": "1.5.1",
|
|
45
44
|
"@milaboratories/build-configs": "2.0.0",
|
|
45
|
+
"@milaboratories/pf-driver": "1.5.2",
|
|
46
46
|
"@milaboratories/pf-spec-driver": "1.4.1",
|
|
47
47
|
"@milaboratories/ts-builder": "1.5.0",
|
|
48
48
|
"@milaboratories/ts-configs": "1.2.3"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { afterEach, expect, test, vi } from "vitest";
|
|
2
|
+
import { TreeNodeAccessor } from "./accessor";
|
|
3
|
+
import * as internal from "../internal";
|
|
4
|
+
import type { AccessorHandle } from "./internal";
|
|
5
|
+
|
|
6
|
+
const HANDLE = "test-handle" as AccessorHandle;
|
|
7
|
+
const ERROR_HANDLE = "error-handle" as AccessorHandle;
|
|
8
|
+
|
|
9
|
+
type RenderCtx = ReturnType<typeof internal.getCfgRenderCtx>;
|
|
10
|
+
|
|
11
|
+
function accessorWithCtx(ctx: Partial<RenderCtx>, resolvePath: string[] = []): TreeNodeAccessor {
|
|
12
|
+
vi.spyOn(internal, "getCfgRenderCtx").mockReturnValue(ctx as RenderCtx);
|
|
13
|
+
return new TreeNodeAccessor(HANDLE, resolvePath);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
vi.restoreAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("getDataAsJsonOrUndefined returns undefined while the resource is still computing", () => {
|
|
21
|
+
// Not ready → undefined, and crucially no throw (toBeUndefined fails on a throw).
|
|
22
|
+
// That is the MILAB-6318 fix.
|
|
23
|
+
const acc = accessorWithCtx({
|
|
24
|
+
getIsReadyOrError: () => false,
|
|
25
|
+
getError: () => undefined,
|
|
26
|
+
getDataAsString: () => undefined,
|
|
27
|
+
});
|
|
28
|
+
expect(acc.getDataAsJsonOrUndefined()).toBeUndefined();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("getDataAsJsonOrUndefined parses content once the resource is ready", () => {
|
|
32
|
+
const acc = accessorWithCtx({
|
|
33
|
+
getIsReadyOrError: () => true,
|
|
34
|
+
getError: () => undefined,
|
|
35
|
+
getDataAsString: () => JSON.stringify({ min_len: 7 }),
|
|
36
|
+
});
|
|
37
|
+
expect(acc.getDataAsJsonOrUndefined<{ min_len: number }>()).toEqual({ min_len: 7 });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("getDataAsJsonOrUndefined throws the error node's message when the resource errored", () => {
|
|
41
|
+
// The backend serializes a resource error as {"message": "..."}; the thrown
|
|
42
|
+
// string is the unwrapped message, not the raw JSON envelope.
|
|
43
|
+
const resourceError = JSON.stringify({ message: "upstream blew up" });
|
|
44
|
+
const acc = accessorWithCtx({
|
|
45
|
+
getIsReadyOrError: () => true,
|
|
46
|
+
getError: () => ERROR_HANDLE,
|
|
47
|
+
getDataAsString: (handle: AccessorHandle) =>
|
|
48
|
+
handle === ERROR_HANDLE ? resourceError : undefined,
|
|
49
|
+
});
|
|
50
|
+
expect(() => acc.getDataAsJsonOrUndefined()).toThrow(/^upstream blew up$/);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("getDataAsJsonOrUndefined tags the thrown error with the resolve path", () => {
|
|
54
|
+
// The path identifies which resolve failed when a lambda reads several fields.
|
|
55
|
+
const resourceError = JSON.stringify({ message: "boom" });
|
|
56
|
+
const acc = accessorWithCtx(
|
|
57
|
+
{
|
|
58
|
+
getIsReadyOrError: () => true,
|
|
59
|
+
getError: () => ERROR_HANDLE,
|
|
60
|
+
getDataAsString: (handle: AccessorHandle) =>
|
|
61
|
+
handle === ERROR_HANDLE ? resourceError : undefined,
|
|
62
|
+
},
|
|
63
|
+
["outputs", "isEmpty"],
|
|
64
|
+
);
|
|
65
|
+
expect(() => acc.getDataAsJsonOrUndefined()).toThrow(/^boom \(at outputs\.isEmpty\)$/);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("getDataAsJsonOrUndefined falls back to the raw content when it is not a message envelope", () => {
|
|
69
|
+
// Plain text (not JSON) → surfaced verbatim.
|
|
70
|
+
const acc = accessorWithCtx({
|
|
71
|
+
getIsReadyOrError: () => true,
|
|
72
|
+
getError: () => ERROR_HANDLE,
|
|
73
|
+
getDataAsString: (handle: AccessorHandle) => (handle === ERROR_HANDLE ? "not json" : undefined),
|
|
74
|
+
});
|
|
75
|
+
expect(() => acc.getDataAsJsonOrUndefined()).toThrow(/^not json$/);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("getDataAsJsonOrUndefined falls back to the raw content when JSON has no string message", () => {
|
|
79
|
+
// Valid JSON but not the {message} envelope → surfaced verbatim.
|
|
80
|
+
const raw = JSON.stringify({ code: 42 });
|
|
81
|
+
const acc = accessorWithCtx({
|
|
82
|
+
getIsReadyOrError: () => true,
|
|
83
|
+
getError: () => ERROR_HANDLE,
|
|
84
|
+
getDataAsString: (handle: AccessorHandle) => (handle === ERROR_HANDLE ? raw : undefined),
|
|
85
|
+
});
|
|
86
|
+
expect(() => acc.getDataAsJsonOrUndefined()).toThrow(raw);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("getDataAsJsonOrUndefined throws a generic message when the errored node has no content", () => {
|
|
90
|
+
// Errored, but the error node itself has nothing readable — generic fallback.
|
|
91
|
+
const acc = accessorWithCtx({
|
|
92
|
+
getIsReadyOrError: () => true,
|
|
93
|
+
getError: () => ERROR_HANDLE,
|
|
94
|
+
getDataAsString: () => undefined,
|
|
95
|
+
});
|
|
96
|
+
expect(() => acc.getDataAsJsonOrUndefined()).toThrow(/^Resource computation failed\.$/);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("getDataAsJsonOrUndefined throws when a ready resource has no error and no content", () => {
|
|
100
|
+
const acc = accessorWithCtx({
|
|
101
|
+
getIsReadyOrError: () => true,
|
|
102
|
+
getError: () => undefined,
|
|
103
|
+
getDataAsString: () => undefined,
|
|
104
|
+
});
|
|
105
|
+
expect(() => acc.getDataAsJsonOrUndefined()).toThrow("Resource has no content.");
|
|
106
|
+
});
|
package/src/render/accessor.ts
CHANGED
|
@@ -20,6 +20,22 @@ export function ifDef<T, R>(value: T | undefined, cb: (value: T) => R): R | unde
|
|
|
20
20
|
return value === undefined ? undefined : cb(value);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Decode an error node's content into a display message. The backend serializes
|
|
25
|
+
* a resource error as `{"message": "..."}` (`ResourceError`); unwrap that to the
|
|
26
|
+
* human-readable message. Falls back to the raw string when the content is not
|
|
27
|
+
* that envelope (e.g. plain text, or an unexpected shape).
|
|
28
|
+
*/
|
|
29
|
+
function decodeErrorMessage(raw: string): string {
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(raw) as { message?: unknown };
|
|
32
|
+
if (typeof parsed?.message === "string") return parsed.message;
|
|
33
|
+
} catch {
|
|
34
|
+
// Not JSON — surface the raw content.
|
|
35
|
+
}
|
|
36
|
+
return raw;
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
type FieldMapOps = {
|
|
24
40
|
/**
|
|
25
41
|
* Type of fields to iterate over.
|
|
@@ -202,6 +218,38 @@ export class TreeNodeAccessor {
|
|
|
202
218
|
return JSON.parse(content);
|
|
203
219
|
}
|
|
204
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Like {@link getDataAsJson}, but does not throw while the resource is still
|
|
223
|
+
* computing. Three states:
|
|
224
|
+
*
|
|
225
|
+
* - **Not ready** → returns `undefined` (loading).
|
|
226
|
+
* - **Errored** → throws the resource's error.
|
|
227
|
+
* - **Ready** → returns the parsed content.
|
|
228
|
+
*
|
|
229
|
+
* Prefer this over {@link getDataAsJson} in reactive output lambdas: a field
|
|
230
|
+
* that resolves before its resource is ready (routine on remote backends)
|
|
231
|
+
* makes {@link getDataAsJson} throw "Resource has no content." mid-calculation,
|
|
232
|
+
* flashing a transient "Some outputs have errors" banner that clears when the
|
|
233
|
+
* resource finishes (MILAB-6318).
|
|
234
|
+
*/
|
|
235
|
+
public getDataAsJsonOrUndefined<T>(): T | undefined {
|
|
236
|
+
// Not ready → undefined (loading). getIsReadyOrError() registers the
|
|
237
|
+
// readiness dependency, so the lambda re-runs once the resource finishes.
|
|
238
|
+
if (!this.getIsReadyOrError()) return undefined;
|
|
239
|
+
// Errored → throw the actual error, decoded from the error node and tagged
|
|
240
|
+
// with the resolve path for debugging context. Beats getDataAsJson's
|
|
241
|
+
// generic "Resource has no content."
|
|
242
|
+
const error = this.getError();
|
|
243
|
+
if (error !== undefined) {
|
|
244
|
+
const raw = error.getDataAsString();
|
|
245
|
+
const message = raw === undefined ? "Resource computation failed." : decodeErrorMessage(raw);
|
|
246
|
+
const path = this.resolvePath.join(".");
|
|
247
|
+
throw new Error(path ? `${message} (at ${path})` : message);
|
|
248
|
+
}
|
|
249
|
+
// Ready → parse the content.
|
|
250
|
+
return this.getDataAsJson<T>();
|
|
251
|
+
}
|
|
252
|
+
|
|
205
253
|
/**
|
|
206
254
|
*
|
|
207
255
|
*/
|