@milaboratories/pl-middle-layer 1.55.22 → 1.55.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,37 +17,67 @@ function outputRef(blockId, name, requireEnrichments) {
17
17
  function isBlockOutputReference(obj) {
18
18
  return typeof obj === "object" && obj !== null && "__isRef" in obj && obj.__isRef === true && "blockId" in obj && "name" in obj;
19
19
  }
20
+ /** Extracts all resource ids referenced by args object. */
21
+ function inferAllReferencedBlocks(args, allowed) {
22
+ const result = {
23
+ upstreams: /* @__PURE__ */ new Set(),
24
+ upstreamsRequiringEnrichments: /* @__PURE__ */ new Set(),
25
+ missingReferences: false
26
+ };
27
+ addAllReferencedBlocks(result, args, allowed);
28
+ return result;
29
+ }
20
30
  function addAllReferencedBlocks(result, node, allowed) {
21
31
  const type = typeof node;
22
32
  switch (type) {
23
33
  case "function":
24
34
  case "bigint":
25
35
  case "number":
26
- case "string":
27
36
  case "boolean":
28
37
  case "symbol":
29
38
  case "undefined": return;
39
+ case "string":
40
+ unwrapEmbeddedRef(node, (parsed) => addAllReferencedBlocks(result, parsed, allowed));
41
+ return;
30
42
  case "object":
31
43
  if (node === null) return;
32
- if (isBlockOutputReference(node)) if (allowed === void 0 || allowed.has(node.blockId)) {
33
- result.upstreams.add(node.blockId);
34
- if (node.requireEnrichments) result.upstreamsRequiringEnrichments.add(node.blockId);
35
- } else result.missingReferences = true;
44
+ if (isBlockOutputReference(node)) recordRef(result, node.blockId, node.requireEnrichments === true, allowed);
36
45
  else if (Array.isArray(node)) for (const child of node) addAllReferencedBlocks(result, child, allowed);
37
46
  else for (const [, child] of Object.entries(node)) addAllReferencedBlocks(result, child, allowed);
38
47
  return;
39
48
  default: (0, _milaboratories_ts_helpers.assertNever)(type);
40
49
  }
41
50
  }
42
- /** Extracts all resource ids referenced by args object. */
43
- function inferAllReferencedBlocks(args, allowed) {
44
- const result = {
45
- upstreams: /* @__PURE__ */ new Set(),
46
- upstreamsRequiringEnrichments: /* @__PURE__ */ new Set(),
47
- missingReferences: false
48
- };
49
- addAllReferencedBlocks(result, args, allowed);
50
- return result;
51
+ /**
52
+ * Detect a PlRef carried inside a string and hand the decoded value to `onParsed`.
53
+ *
54
+ * A PlRef-as-string is canonical `{...}` optionally wrapped by N `JSON.stringify`
55
+ * passes. Each pass adds a symmetric prefix/suffix made only of `"` and `\`
56
+ * chars (the escape padding) of equal length. The regex below is the strict
57
+ * shape gate — non-ref strings fail at the very first character, so we never
58
+ * scan their body. One pass is peeled per call; deeper nesting is unwrapped
59
+ * via recursion in the caller.
60
+ */
61
+ const EMBEDDED_REF_RE = /^(?<pre>[\\"]*)\{[\s\S]*?__isRef[\s\S]*\}(?<suf>[\\"]*)$/;
62
+ function unwrapEmbeddedRef(s, onParsed) {
63
+ const c0 = s.charCodeAt(0);
64
+ if (c0 !== 123 && c0 !== 34) return;
65
+ const m = EMBEDDED_REF_RE.exec(s);
66
+ if (m === null) return;
67
+ if (m.groups.pre.length !== m.groups.suf.length) return;
68
+ let parsed;
69
+ try {
70
+ parsed = JSON.parse(s);
71
+ } catch {
72
+ return;
73
+ }
74
+ if (parsed !== s) onParsed(parsed);
75
+ }
76
+ function recordRef(result, blockId, requireEnrichments, allowed) {
77
+ if (allowed === void 0 || allowed.has(blockId)) {
78
+ result.upstreams.add(blockId);
79
+ if (requireEnrichments) result.upstreamsRequiringEnrichments.add(blockId);
80
+ } else result.missingReferences = true;
51
81
  }
52
82
  //#endregion
53
83
  exports.inferAllReferencedBlocks = inferAllReferencedBlocks;
@@ -1 +1 @@
1
- {"version":3,"file":"args.cjs","names":[],"sources":["../../src/model/args.ts"],"sourcesContent":["import { assertNever } from \"@milaboratories/ts-helpers\";\nimport type { PlRef } from \"@platforma-sdk/model\";\n\nexport function outputRef(blockId: string, name: string, requireEnrichments?: boolean): PlRef {\n if (requireEnrichments) return { __isRef: true, blockId, name, requireEnrichments };\n else return { __isRef: true, blockId, name };\n}\n\nexport function isBlockOutputReference(obj: unknown): obj is PlRef {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"__isRef\" in obj &&\n obj.__isRef === true &&\n \"blockId\" in obj &&\n \"name\" in obj\n );\n}\n\nfunction addAllReferencedBlocks(result: BlockUpstreams, node: unknown, allowed?: Set<string>) {\n const type = typeof node;\n switch (type) {\n case \"function\":\n case \"bigint\":\n case \"number\":\n case \"string\":\n case \"boolean\":\n case \"symbol\":\n case \"undefined\":\n return;\n\n case \"object\":\n if (node === null) return;\n\n if (isBlockOutputReference(node)) {\n if (allowed === undefined || allowed.has(node.blockId)) {\n result.upstreams.add(node.blockId);\n if (node.requireEnrichments) result.upstreamsRequiringEnrichments.add(node.blockId);\n } else result.missingReferences = true;\n } else if (Array.isArray(node)) {\n for (const child of node) addAllReferencedBlocks(result, child, allowed);\n } else {\n for (const [, child] of Object.entries(node as object))\n addAllReferencedBlocks(result, child, allowed);\n }\n\n return;\n\n default:\n assertNever(type);\n }\n}\n\nexport interface BlockUpstreams {\n /** All direct block dependencies */\n upstreams: Set<string>;\n /** Direct block dependencies which enrichments are also required by current block */\n upstreamsRequiringEnrichments: Set<string>;\n /** True if not-allowed references was encountered */\n missingReferences: boolean;\n}\n\n/** Extracts all resource ids referenced by args object. */\nexport function inferAllReferencedBlocks(args: unknown, allowed?: Set<string>): BlockUpstreams {\n const result = {\n upstreams: new Set<string>(),\n upstreamsRequiringEnrichments: new Set<string>(),\n missingReferences: false,\n };\n addAllReferencedBlocks(result, args, allowed);\n return result;\n}\n"],"mappings":";;;AAGA,SAAgB,UAAU,SAAiB,MAAc,oBAAqC;AAC5F,KAAI,mBAAoB,QAAO;EAAE,SAAS;EAAM;EAAS;EAAM;EAAoB;KAC9E,QAAO;EAAE,SAAS;EAAM;EAAS;EAAM;;AAG9C,SAAgB,uBAAuB,KAA4B;AACjE,QACE,OAAO,QAAQ,YACf,QAAQ,QACR,aAAa,OACb,IAAI,YAAY,QAChB,aAAa,OACb,UAAU;;AAId,SAAS,uBAAuB,QAAwB,MAAe,SAAuB;CAC5F,MAAM,OAAO,OAAO;AACpB,SAAQ,MAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,YACH;EAEF,KAAK;AACH,OAAI,SAAS,KAAM;AAEnB,OAAI,uBAAuB,KAAK,CAC9B,KAAI,YAAY,KAAA,KAAa,QAAQ,IAAI,KAAK,QAAQ,EAAE;AACtD,WAAO,UAAU,IAAI,KAAK,QAAQ;AAClC,QAAI,KAAK,mBAAoB,QAAO,8BAA8B,IAAI,KAAK,QAAQ;SAC9E,QAAO,oBAAoB;YACzB,MAAM,QAAQ,KAAK,CAC5B,MAAK,MAAM,SAAS,KAAM,wBAAuB,QAAQ,OAAO,QAAQ;OAExE,MAAK,MAAM,GAAG,UAAU,OAAO,QAAQ,KAAe,CACpD,wBAAuB,QAAQ,OAAO,QAAQ;AAGlD;EAEF,QACE,EAAA,GAAA,2BAAA,aAAY,KAAK;;;;AAcvB,SAAgB,yBAAyB,MAAe,SAAuC;CAC7F,MAAM,SAAS;EACb,2BAAW,IAAI,KAAa;EAC5B,+CAA+B,IAAI,KAAa;EAChD,mBAAmB;EACpB;AACD,wBAAuB,QAAQ,MAAM,QAAQ;AAC7C,QAAO"}
1
+ {"version":3,"file":"args.cjs","names":[],"sources":["../../src/model/args.ts"],"sourcesContent":["import { assertNever } from \"@milaboratories/ts-helpers\";\nimport type { PlRef } from \"@platforma-sdk/model\";\n\nexport function outputRef(blockId: string, name: string, requireEnrichments?: boolean): PlRef {\n if (requireEnrichments) return { __isRef: true, blockId, name, requireEnrichments };\n else return { __isRef: true, blockId, name };\n}\n\nexport function isBlockOutputReference(obj: unknown): obj is PlRef {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"__isRef\" in obj &&\n obj.__isRef === true &&\n \"blockId\" in obj &&\n \"name\" in obj\n );\n}\n\nexport interface BlockUpstreams {\n /** All direct block dependencies */\n upstreams: Set<string>;\n /** Direct block dependencies which enrichments are also required by current block */\n upstreamsRequiringEnrichments: Set<string>;\n /** True if not-allowed references was encountered */\n missingReferences: boolean;\n}\n\n/** Extracts all resource ids referenced by args object. */\nexport function inferAllReferencedBlocks(args: unknown, allowed?: Set<string>): BlockUpstreams {\n const result = {\n upstreams: new Set<string>(),\n upstreamsRequiringEnrichments: new Set<string>(),\n missingReferences: false,\n };\n addAllReferencedBlocks(result, args, allowed);\n return result;\n}\n\nfunction addAllReferencedBlocks(result: BlockUpstreams, node: unknown, allowed?: Set<string>) {\n const type = typeof node;\n switch (type) {\n case \"function\":\n case \"bigint\":\n case \"number\":\n case \"boolean\":\n case \"symbol\":\n case \"undefined\":\n return;\n case \"string\": {\n unwrapEmbeddedRef(node as string, (parsed) =>\n addAllReferencedBlocks(result, parsed, allowed),\n );\n return;\n }\n case \"object\": {\n if (node === null) return;\n if (isBlockOutputReference(node)) {\n recordRef(result, node.blockId, node.requireEnrichments === true, allowed);\n } else if (Array.isArray(node)) {\n for (const child of node) addAllReferencedBlocks(result, child, allowed);\n } else {\n for (const [, child] of Object.entries(node as object))\n addAllReferencedBlocks(result, child, allowed);\n }\n\n return;\n }\n default:\n assertNever(type);\n }\n}\n\n/**\n * Detect a PlRef carried inside a string and hand the decoded value to `onParsed`.\n *\n * A PlRef-as-string is canonical `{...}` optionally wrapped by N `JSON.stringify`\n * passes. Each pass adds a symmetric prefix/suffix made only of `\"` and `\\`\n * chars (the escape padding) of equal length. The regex below is the strict\n * shape gate — non-ref strings fail at the very first character, so we never\n * scan their body. One pass is peeled per call; deeper nesting is unwrapped\n * via recursion in the caller.\n */\nconst EMBEDDED_REF_RE = /^(?<pre>[\\\\\"]*)\\{[\\s\\S]*?__isRef[\\s\\S]*\\}(?<suf>[\\\\\"]*)$/;\n\nfunction unwrapEmbeddedRef(s: string, onParsed: (value: unknown) => void) {\n const c0 = s.charCodeAt(0);\n if (c0 !== 0x7b /* { */ && c0 !== 0x22 /* \" */) return;\n const m = EMBEDDED_REF_RE.exec(s);\n if (m === null) return;\n if (m.groups!.pre.length !== m.groups!.suf.length) return;\n let parsed: unknown;\n try {\n parsed = JSON.parse(s);\n } catch {\n return;\n }\n if (parsed !== s) onParsed(parsed);\n}\n\nfunction recordRef(\n result: BlockUpstreams,\n blockId: string,\n requireEnrichments: boolean,\n allowed?: Set<string>,\n) {\n if (allowed === undefined || allowed.has(blockId)) {\n result.upstreams.add(blockId);\n if (requireEnrichments) result.upstreamsRequiringEnrichments.add(blockId);\n } else result.missingReferences = true;\n}\n"],"mappings":";;;AAGA,SAAgB,UAAU,SAAiB,MAAc,oBAAqC;AAC5F,KAAI,mBAAoB,QAAO;EAAE,SAAS;EAAM;EAAS;EAAM;EAAoB;KAC9E,QAAO;EAAE,SAAS;EAAM;EAAS;EAAM;;AAG9C,SAAgB,uBAAuB,KAA4B;AACjE,QACE,OAAO,QAAQ,YACf,QAAQ,QACR,aAAa,OACb,IAAI,YAAY,QAChB,aAAa,OACb,UAAU;;;AAcd,SAAgB,yBAAyB,MAAe,SAAuC;CAC7F,MAAM,SAAS;EACb,2BAAW,IAAI,KAAa;EAC5B,+CAA+B,IAAI,KAAa;EAChD,mBAAmB;EACpB;AACD,wBAAuB,QAAQ,MAAM,QAAQ;AAC7C,QAAO;;AAGT,SAAS,uBAAuB,QAAwB,MAAe,SAAuB;CAC5F,MAAM,OAAO,OAAO;AACpB,SAAQ,MAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,YACH;EACF,KAAK;AACH,qBAAkB,OAAiB,WACjC,uBAAuB,QAAQ,QAAQ,QAAQ,CAChD;AACD;EAEF,KAAK;AACH,OAAI,SAAS,KAAM;AACnB,OAAI,uBAAuB,KAAK,CAC9B,WAAU,QAAQ,KAAK,SAAS,KAAK,uBAAuB,MAAM,QAAQ;YACjE,MAAM,QAAQ,KAAK,CAC5B,MAAK,MAAM,SAAS,KAAM,wBAAuB,QAAQ,OAAO,QAAQ;OAExE,MAAK,MAAM,GAAG,UAAU,OAAO,QAAQ,KAAe,CACpD,wBAAuB,QAAQ,OAAO,QAAQ;AAGlD;EAEF,QACE,EAAA,GAAA,2BAAA,aAAY,KAAK;;;;;;;;;;;;;AAcvB,MAAM,kBAAkB;AAExB,SAAS,kBAAkB,GAAW,UAAoC;CACxE,MAAM,KAAK,EAAE,WAAW,EAAE;AAC1B,KAAI,OAAO,OAAgB,OAAO,GAAc;CAChD,MAAM,IAAI,gBAAgB,KAAK,EAAE;AACjC,KAAI,MAAM,KAAM;AAChB,KAAI,EAAE,OAAQ,IAAI,WAAW,EAAE,OAAQ,IAAI,OAAQ;CACnD,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,EAAE;SAChB;AACN;;AAEF,KAAI,WAAW,EAAG,UAAS,OAAO;;AAGpC,SAAS,UACP,QACA,SACA,oBACA,SACA;AACA,KAAI,YAAY,KAAA,KAAa,QAAQ,IAAI,QAAQ,EAAE;AACjD,SAAO,UAAU,IAAI,QAAQ;AAC7B,MAAI,mBAAoB,QAAO,8BAA8B,IAAI,QAAQ;OACpE,QAAO,oBAAoB"}
@@ -16,37 +16,67 @@ function outputRef(blockId, name, requireEnrichments) {
16
16
  function isBlockOutputReference(obj) {
17
17
  return typeof obj === "object" && obj !== null && "__isRef" in obj && obj.__isRef === true && "blockId" in obj && "name" in obj;
18
18
  }
19
+ /** Extracts all resource ids referenced by args object. */
20
+ function inferAllReferencedBlocks(args, allowed) {
21
+ const result = {
22
+ upstreams: /* @__PURE__ */ new Set(),
23
+ upstreamsRequiringEnrichments: /* @__PURE__ */ new Set(),
24
+ missingReferences: false
25
+ };
26
+ addAllReferencedBlocks(result, args, allowed);
27
+ return result;
28
+ }
19
29
  function addAllReferencedBlocks(result, node, allowed) {
20
30
  const type = typeof node;
21
31
  switch (type) {
22
32
  case "function":
23
33
  case "bigint":
24
34
  case "number":
25
- case "string":
26
35
  case "boolean":
27
36
  case "symbol":
28
37
  case "undefined": return;
38
+ case "string":
39
+ unwrapEmbeddedRef(node, (parsed) => addAllReferencedBlocks(result, parsed, allowed));
40
+ return;
29
41
  case "object":
30
42
  if (node === null) return;
31
- if (isBlockOutputReference(node)) if (allowed === void 0 || allowed.has(node.blockId)) {
32
- result.upstreams.add(node.blockId);
33
- if (node.requireEnrichments) result.upstreamsRequiringEnrichments.add(node.blockId);
34
- } else result.missingReferences = true;
43
+ if (isBlockOutputReference(node)) recordRef(result, node.blockId, node.requireEnrichments === true, allowed);
35
44
  else if (Array.isArray(node)) for (const child of node) addAllReferencedBlocks(result, child, allowed);
36
45
  else for (const [, child] of Object.entries(node)) addAllReferencedBlocks(result, child, allowed);
37
46
  return;
38
47
  default: assertNever(type);
39
48
  }
40
49
  }
41
- /** Extracts all resource ids referenced by args object. */
42
- function inferAllReferencedBlocks(args, allowed) {
43
- const result = {
44
- upstreams: /* @__PURE__ */ new Set(),
45
- upstreamsRequiringEnrichments: /* @__PURE__ */ new Set(),
46
- missingReferences: false
47
- };
48
- addAllReferencedBlocks(result, args, allowed);
49
- return result;
50
+ /**
51
+ * Detect a PlRef carried inside a string and hand the decoded value to `onParsed`.
52
+ *
53
+ * A PlRef-as-string is canonical `{...}` optionally wrapped by N `JSON.stringify`
54
+ * passes. Each pass adds a symmetric prefix/suffix made only of `"` and `\`
55
+ * chars (the escape padding) of equal length. The regex below is the strict
56
+ * shape gate — non-ref strings fail at the very first character, so we never
57
+ * scan their body. One pass is peeled per call; deeper nesting is unwrapped
58
+ * via recursion in the caller.
59
+ */
60
+ const EMBEDDED_REF_RE = /^(?<pre>[\\"]*)\{[\s\S]*?__isRef[\s\S]*\}(?<suf>[\\"]*)$/;
61
+ function unwrapEmbeddedRef(s, onParsed) {
62
+ const c0 = s.charCodeAt(0);
63
+ if (c0 !== 123 && c0 !== 34) return;
64
+ const m = EMBEDDED_REF_RE.exec(s);
65
+ if (m === null) return;
66
+ if (m.groups.pre.length !== m.groups.suf.length) return;
67
+ let parsed;
68
+ try {
69
+ parsed = JSON.parse(s);
70
+ } catch {
71
+ return;
72
+ }
73
+ if (parsed !== s) onParsed(parsed);
74
+ }
75
+ function recordRef(result, blockId, requireEnrichments, allowed) {
76
+ if (allowed === void 0 || allowed.has(blockId)) {
77
+ result.upstreams.add(blockId);
78
+ if (requireEnrichments) result.upstreamsRequiringEnrichments.add(blockId);
79
+ } else result.missingReferences = true;
50
80
  }
51
81
  //#endregion
52
82
  export { inferAllReferencedBlocks, outputRef };
@@ -1 +1 @@
1
- {"version":3,"file":"args.js","names":[],"sources":["../../src/model/args.ts"],"sourcesContent":["import { assertNever } from \"@milaboratories/ts-helpers\";\nimport type { PlRef } from \"@platforma-sdk/model\";\n\nexport function outputRef(blockId: string, name: string, requireEnrichments?: boolean): PlRef {\n if (requireEnrichments) return { __isRef: true, blockId, name, requireEnrichments };\n else return { __isRef: true, blockId, name };\n}\n\nexport function isBlockOutputReference(obj: unknown): obj is PlRef {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"__isRef\" in obj &&\n obj.__isRef === true &&\n \"blockId\" in obj &&\n \"name\" in obj\n );\n}\n\nfunction addAllReferencedBlocks(result: BlockUpstreams, node: unknown, allowed?: Set<string>) {\n const type = typeof node;\n switch (type) {\n case \"function\":\n case \"bigint\":\n case \"number\":\n case \"string\":\n case \"boolean\":\n case \"symbol\":\n case \"undefined\":\n return;\n\n case \"object\":\n if (node === null) return;\n\n if (isBlockOutputReference(node)) {\n if (allowed === undefined || allowed.has(node.blockId)) {\n result.upstreams.add(node.blockId);\n if (node.requireEnrichments) result.upstreamsRequiringEnrichments.add(node.blockId);\n } else result.missingReferences = true;\n } else if (Array.isArray(node)) {\n for (const child of node) addAllReferencedBlocks(result, child, allowed);\n } else {\n for (const [, child] of Object.entries(node as object))\n addAllReferencedBlocks(result, child, allowed);\n }\n\n return;\n\n default:\n assertNever(type);\n }\n}\n\nexport interface BlockUpstreams {\n /** All direct block dependencies */\n upstreams: Set<string>;\n /** Direct block dependencies which enrichments are also required by current block */\n upstreamsRequiringEnrichments: Set<string>;\n /** True if not-allowed references was encountered */\n missingReferences: boolean;\n}\n\n/** Extracts all resource ids referenced by args object. */\nexport function inferAllReferencedBlocks(args: unknown, allowed?: Set<string>): BlockUpstreams {\n const result = {\n upstreams: new Set<string>(),\n upstreamsRequiringEnrichments: new Set<string>(),\n missingReferences: false,\n };\n addAllReferencedBlocks(result, args, allowed);\n return result;\n}\n"],"mappings":";;AAGA,SAAgB,UAAU,SAAiB,MAAc,oBAAqC;AAC5F,KAAI,mBAAoB,QAAO;EAAE,SAAS;EAAM;EAAS;EAAM;EAAoB;KAC9E,QAAO;EAAE,SAAS;EAAM;EAAS;EAAM;;AAG9C,SAAgB,uBAAuB,KAA4B;AACjE,QACE,OAAO,QAAQ,YACf,QAAQ,QACR,aAAa,OACb,IAAI,YAAY,QAChB,aAAa,OACb,UAAU;;AAId,SAAS,uBAAuB,QAAwB,MAAe,SAAuB;CAC5F,MAAM,OAAO,OAAO;AACpB,SAAQ,MAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,YACH;EAEF,KAAK;AACH,OAAI,SAAS,KAAM;AAEnB,OAAI,uBAAuB,KAAK,CAC9B,KAAI,YAAY,KAAA,KAAa,QAAQ,IAAI,KAAK,QAAQ,EAAE;AACtD,WAAO,UAAU,IAAI,KAAK,QAAQ;AAClC,QAAI,KAAK,mBAAoB,QAAO,8BAA8B,IAAI,KAAK,QAAQ;SAC9E,QAAO,oBAAoB;YACzB,MAAM,QAAQ,KAAK,CAC5B,MAAK,MAAM,SAAS,KAAM,wBAAuB,QAAQ,OAAO,QAAQ;OAExE,MAAK,MAAM,GAAG,UAAU,OAAO,QAAQ,KAAe,CACpD,wBAAuB,QAAQ,OAAO,QAAQ;AAGlD;EAEF,QACE,aAAY,KAAK;;;;AAcvB,SAAgB,yBAAyB,MAAe,SAAuC;CAC7F,MAAM,SAAS;EACb,2BAAW,IAAI,KAAa;EAC5B,+CAA+B,IAAI,KAAa;EAChD,mBAAmB;EACpB;AACD,wBAAuB,QAAQ,MAAM,QAAQ;AAC7C,QAAO"}
1
+ {"version":3,"file":"args.js","names":[],"sources":["../../src/model/args.ts"],"sourcesContent":["import { assertNever } from \"@milaboratories/ts-helpers\";\nimport type { PlRef } from \"@platforma-sdk/model\";\n\nexport function outputRef(blockId: string, name: string, requireEnrichments?: boolean): PlRef {\n if (requireEnrichments) return { __isRef: true, blockId, name, requireEnrichments };\n else return { __isRef: true, blockId, name };\n}\n\nexport function isBlockOutputReference(obj: unknown): obj is PlRef {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"__isRef\" in obj &&\n obj.__isRef === true &&\n \"blockId\" in obj &&\n \"name\" in obj\n );\n}\n\nexport interface BlockUpstreams {\n /** All direct block dependencies */\n upstreams: Set<string>;\n /** Direct block dependencies which enrichments are also required by current block */\n upstreamsRequiringEnrichments: Set<string>;\n /** True if not-allowed references was encountered */\n missingReferences: boolean;\n}\n\n/** Extracts all resource ids referenced by args object. */\nexport function inferAllReferencedBlocks(args: unknown, allowed?: Set<string>): BlockUpstreams {\n const result = {\n upstreams: new Set<string>(),\n upstreamsRequiringEnrichments: new Set<string>(),\n missingReferences: false,\n };\n addAllReferencedBlocks(result, args, allowed);\n return result;\n}\n\nfunction addAllReferencedBlocks(result: BlockUpstreams, node: unknown, allowed?: Set<string>) {\n const type = typeof node;\n switch (type) {\n case \"function\":\n case \"bigint\":\n case \"number\":\n case \"boolean\":\n case \"symbol\":\n case \"undefined\":\n return;\n case \"string\": {\n unwrapEmbeddedRef(node as string, (parsed) =>\n addAllReferencedBlocks(result, parsed, allowed),\n );\n return;\n }\n case \"object\": {\n if (node === null) return;\n if (isBlockOutputReference(node)) {\n recordRef(result, node.blockId, node.requireEnrichments === true, allowed);\n } else if (Array.isArray(node)) {\n for (const child of node) addAllReferencedBlocks(result, child, allowed);\n } else {\n for (const [, child] of Object.entries(node as object))\n addAllReferencedBlocks(result, child, allowed);\n }\n\n return;\n }\n default:\n assertNever(type);\n }\n}\n\n/**\n * Detect a PlRef carried inside a string and hand the decoded value to `onParsed`.\n *\n * A PlRef-as-string is canonical `{...}` optionally wrapped by N `JSON.stringify`\n * passes. Each pass adds a symmetric prefix/suffix made only of `\"` and `\\`\n * chars (the escape padding) of equal length. The regex below is the strict\n * shape gate — non-ref strings fail at the very first character, so we never\n * scan their body. One pass is peeled per call; deeper nesting is unwrapped\n * via recursion in the caller.\n */\nconst EMBEDDED_REF_RE = /^(?<pre>[\\\\\"]*)\\{[\\s\\S]*?__isRef[\\s\\S]*\\}(?<suf>[\\\\\"]*)$/;\n\nfunction unwrapEmbeddedRef(s: string, onParsed: (value: unknown) => void) {\n const c0 = s.charCodeAt(0);\n if (c0 !== 0x7b /* { */ && c0 !== 0x22 /* \" */) return;\n const m = EMBEDDED_REF_RE.exec(s);\n if (m === null) return;\n if (m.groups!.pre.length !== m.groups!.suf.length) return;\n let parsed: unknown;\n try {\n parsed = JSON.parse(s);\n } catch {\n return;\n }\n if (parsed !== s) onParsed(parsed);\n}\n\nfunction recordRef(\n result: BlockUpstreams,\n blockId: string,\n requireEnrichments: boolean,\n allowed?: Set<string>,\n) {\n if (allowed === undefined || allowed.has(blockId)) {\n result.upstreams.add(blockId);\n if (requireEnrichments) result.upstreamsRequiringEnrichments.add(blockId);\n } else result.missingReferences = true;\n}\n"],"mappings":";;AAGA,SAAgB,UAAU,SAAiB,MAAc,oBAAqC;AAC5F,KAAI,mBAAoB,QAAO;EAAE,SAAS;EAAM;EAAS;EAAM;EAAoB;KAC9E,QAAO;EAAE,SAAS;EAAM;EAAS;EAAM;;AAG9C,SAAgB,uBAAuB,KAA4B;AACjE,QACE,OAAO,QAAQ,YACf,QAAQ,QACR,aAAa,OACb,IAAI,YAAY,QAChB,aAAa,OACb,UAAU;;;AAcd,SAAgB,yBAAyB,MAAe,SAAuC;CAC7F,MAAM,SAAS;EACb,2BAAW,IAAI,KAAa;EAC5B,+CAA+B,IAAI,KAAa;EAChD,mBAAmB;EACpB;AACD,wBAAuB,QAAQ,MAAM,QAAQ;AAC7C,QAAO;;AAGT,SAAS,uBAAuB,QAAwB,MAAe,SAAuB;CAC5F,MAAM,OAAO,OAAO;AACpB,SAAQ,MAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,YACH;EACF,KAAK;AACH,qBAAkB,OAAiB,WACjC,uBAAuB,QAAQ,QAAQ,QAAQ,CAChD;AACD;EAEF,KAAK;AACH,OAAI,SAAS,KAAM;AACnB,OAAI,uBAAuB,KAAK,CAC9B,WAAU,QAAQ,KAAK,SAAS,KAAK,uBAAuB,MAAM,QAAQ;YACjE,MAAM,QAAQ,KAAK,CAC5B,MAAK,MAAM,SAAS,KAAM,wBAAuB,QAAQ,OAAO,QAAQ;OAExE,MAAK,MAAM,GAAG,UAAU,OAAO,QAAQ,KAAe,CACpD,wBAAuB,QAAQ,OAAO,QAAQ;AAGlD;EAEF,QACE,aAAY,KAAK;;;;;;;;;;;;;AAcvB,MAAM,kBAAkB;AAExB,SAAS,kBAAkB,GAAW,UAAoC;CACxE,MAAM,KAAK,EAAE,WAAW,EAAE;AAC1B,KAAI,OAAO,OAAgB,OAAO,GAAc;CAChD,MAAM,IAAI,gBAAgB,KAAK,EAAE;AACjC,KAAI,MAAM,KAAM;AAChB,KAAI,EAAE,OAAQ,IAAI,WAAW,EAAE,OAAQ,IAAI,OAAQ;CACnD,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,EAAE;SAChB;AACN;;AAEF,KAAI,WAAW,EAAG,UAAS,OAAO;;AAGpC,SAAS,UACP,QACA,SACA,oBACA,SACA;AACA,KAAI,YAAY,KAAA,KAAa,QAAQ,IAAI,QAAQ,EAAE;AACjD,SAAO,UAAU,IAAI,QAAQ;AAC7B,MAAI,mBAAoB,QAAO,8BAA8B,IAAI,QAAQ;OACpE,QAAO,oBAAoB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-middle-layer",
3
- "version": "1.55.22",
3
+ "version": "1.55.24",
4
4
  "description": "Pl Middle Layer",
5
5
  "keywords": [],
6
6
  "license": "UNLICENSED",
@@ -30,24 +30,24 @@
30
30
  "utility-types": "^3.11.0",
31
31
  "yaml": "^2.8.0",
32
32
  "zod": "~3.25.76",
33
- "@milaboratories/pf-driver": "1.4.0",
34
33
  "@milaboratories/computable": "2.9.2",
35
- "@milaboratories/pl-client": "3.1.8",
36
- "@milaboratories/pf-spec-driver": "1.3.4",
37
34
  "@milaboratories/helpers": "1.14.1",
38
- "@milaboratories/pl-drivers": "1.12.19",
39
- "@milaboratories/pl-errors": "1.3.7",
35
+ "@milaboratories/pf-spec-driver": "1.3.4",
40
36
  "@milaboratories/pl-deployments": "2.17.7",
37
+ "@milaboratories/pl-errors": "1.3.8",
38
+ "@milaboratories/pf-driver": "1.4.0",
39
+ "@milaboratories/pl-client": "3.2.0",
40
+ "@milaboratories/pl-drivers": "1.12.20",
41
+ "@milaboratories/pl-model-backend": "1.2.16",
41
42
  "@milaboratories/pl-http": "1.2.4",
42
43
  "@milaboratories/pl-model-common": "1.36.0",
43
- "@milaboratories/pl-model-middle-layer": "1.18.5",
44
- "@milaboratories/pl-model-backend": "1.2.15",
45
44
  "@milaboratories/resolve-helper": "1.1.3",
45
+ "@milaboratories/pl-tree": "1.9.18",
46
+ "@milaboratories/pl-model-middle-layer": "1.18.5",
46
47
  "@milaboratories/ts-helpers": "1.8.1",
47
- "@platforma-sdk/block-tools": "2.7.14",
48
48
  "@platforma-sdk/model": "1.68.0",
49
- "@platforma-sdk/workflow-tengo": "5.15.0",
50
- "@milaboratories/pl-tree": "1.9.17"
49
+ "@platforma-sdk/block-tools": "2.7.14",
50
+ "@platforma-sdk/workflow-tengo": "5.15.1"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "~24.5.2",
@@ -1,4 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
+ import canonicalize from "canonicalize";
2
3
  import { inferAllReferencedBlocks, outputRef } from "./args";
3
4
 
4
5
  describe("inferAllReferencedBlocks", () => {
@@ -29,4 +30,106 @@ describe("inferAllReferencedBlocks", () => {
29
30
  expect(result.upstreams).toContain("block1");
30
31
  expect(result.upstreams.size).toBe(1);
31
32
  });
33
+
34
+ it("extracts PlRef embedded as canonicalized JSON string (global-form PObjectId)", () => {
35
+ const id = canonicalize(outputRef("blockA", "exportName"))!;
36
+
37
+ const result = inferAllReferencedBlocks({ columnId: id });
38
+
39
+ expect(result.upstreams).toContain("blockA");
40
+ expect(result.upstreams.size).toBe(1);
41
+ expect(result.upstreamsRequiringEnrichments.size).toBe(0);
42
+ expect(result.missingReferences).toBe(false);
43
+ });
44
+
45
+ it("propagates requireEnrichments from canonicalized PlRef string", () => {
46
+ const id = canonicalize(outputRef("blockA", "exportName", true))!;
47
+
48
+ const result = inferAllReferencedBlocks(id);
49
+
50
+ expect(result.upstreams).toContain("blockA");
51
+ expect(result.upstreamsRequiringEnrichments).toContain("blockA");
52
+ });
53
+
54
+ it("finds multiple PlRef strings inside an args object", () => {
55
+ const a = canonicalize(outputRef("blockA", "n1"))!;
56
+ const b = canonicalize(outputRef("blockB", "n2", true))!;
57
+
58
+ const result = inferAllReferencedBlocks({ first: a, second: b });
59
+
60
+ expect(result.upstreams).toEqual(new Set(["blockA", "blockB"]));
61
+ expect(result.upstreamsRequiringEnrichments).toEqual(new Set(["blockB"]));
62
+ });
63
+
64
+ it("respects allowed set for embedded refs", () => {
65
+ const id = canonicalize(outputRef("blockX", "n"))!;
66
+
67
+ const result = inferAllReferencedBlocks({ id }, new Set(["blockY"]));
68
+
69
+ expect(result.upstreams.size).toBe(0);
70
+ expect(result.missingReferences).toBe(true);
71
+ });
72
+
73
+ it("decodes JSON escapes inside blockId", () => {
74
+ const id = canonicalize(outputRef('weird"id\\with\nescapes', "n"))!;
75
+
76
+ const result = inferAllReferencedBlocks({ id });
77
+
78
+ expect(result.upstreams).toContain('weird"id\\with\nescapes');
79
+ });
80
+
81
+ it("ignores strings without __isRef marker", () => {
82
+ const result = inferAllReferencedBlocks({
83
+ a: "plain text",
84
+ b: '{"some":"json","without":"ref"}',
85
+ });
86
+
87
+ expect(result.upstreams.size).toBe(0);
88
+ expect(result.missingReferences).toBe(false);
89
+ });
90
+
91
+ it("detects PlRef string regardless of JSON.stringify nesting depth (depth 0..10)", () => {
92
+ const id = canonicalize(outputRef("blockA", "n", true))!;
93
+
94
+ for (let depth = 0; depth <= 10; depth++) {
95
+ let value: string = id;
96
+ for (let i = 0; i < depth; i++) value = JSON.stringify(value);
97
+
98
+ const result = inferAllReferencedBlocks({ raw: value });
99
+
100
+ expect(result.upstreams, `depth=${depth}`).toContain("blockA");
101
+ expect(result.upstreamsRequiringEnrichments, `depth=${depth}`).toContain("blockA");
102
+ }
103
+ });
104
+
105
+ it("detects PlRef when the entire args container is stringified multiple times", () => {
106
+ const ref = outputRef("blockZ", "n");
107
+ const container: { col: unknown } = { col: ref };
108
+
109
+ let value: unknown = container;
110
+ for (let i = 0; i < 5; i++) value = JSON.stringify(value);
111
+
112
+ const result = inferAllReferencedBlocks({ wrapper: value });
113
+
114
+ expect(result.upstreams).toContain("blockZ");
115
+ });
116
+
117
+ it("detects PlRef when args itself is the multi-stringified ref string", () => {
118
+ let value: string = canonicalize(outputRef("blockTop", "n"))!;
119
+ for (let i = 0; i < 4; i++) value = JSON.stringify(value);
120
+
121
+ const result = inferAllReferencedBlocks(value);
122
+
123
+ expect(result.upstreams).toContain("blockTop");
124
+ });
125
+
126
+ it("ignores malformed __isRef occurrences", () => {
127
+ const result = inferAllReferencedBlocks({
128
+ a: '{"__isRef":true,"blockId"', // truncated
129
+ b: '"__isRef":false', // wrong value
130
+ c: "__isRef without quotes",
131
+ });
132
+
133
+ expect(result.upstreams.size).toBe(0);
134
+ });
32
135
  });
package/src/model/args.ts CHANGED
@@ -17,26 +17,46 @@ export function isBlockOutputReference(obj: unknown): obj is PlRef {
17
17
  );
18
18
  }
19
19
 
20
+ export interface BlockUpstreams {
21
+ /** All direct block dependencies */
22
+ upstreams: Set<string>;
23
+ /** Direct block dependencies which enrichments are also required by current block */
24
+ upstreamsRequiringEnrichments: Set<string>;
25
+ /** True if not-allowed references was encountered */
26
+ missingReferences: boolean;
27
+ }
28
+
29
+ /** Extracts all resource ids referenced by args object. */
30
+ export function inferAllReferencedBlocks(args: unknown, allowed?: Set<string>): BlockUpstreams {
31
+ const result = {
32
+ upstreams: new Set<string>(),
33
+ upstreamsRequiringEnrichments: new Set<string>(),
34
+ missingReferences: false,
35
+ };
36
+ addAllReferencedBlocks(result, args, allowed);
37
+ return result;
38
+ }
39
+
20
40
  function addAllReferencedBlocks(result: BlockUpstreams, node: unknown, allowed?: Set<string>) {
21
41
  const type = typeof node;
22
42
  switch (type) {
23
43
  case "function":
24
44
  case "bigint":
25
45
  case "number":
26
- case "string":
27
46
  case "boolean":
28
47
  case "symbol":
29
48
  case "undefined":
30
49
  return;
31
-
32
- case "object":
50
+ case "string": {
51
+ unwrapEmbeddedRef(node as string, (parsed) =>
52
+ addAllReferencedBlocks(result, parsed, allowed),
53
+ );
54
+ return;
55
+ }
56
+ case "object": {
33
57
  if (node === null) return;
34
-
35
58
  if (isBlockOutputReference(node)) {
36
- if (allowed === undefined || allowed.has(node.blockId)) {
37
- result.upstreams.add(node.blockId);
38
- if (node.requireEnrichments) result.upstreamsRequiringEnrichments.add(node.blockId);
39
- } else result.missingReferences = true;
59
+ recordRef(result, node.blockId, node.requireEnrichments === true, allowed);
40
60
  } else if (Array.isArray(node)) {
41
61
  for (const child of node) addAllReferencedBlocks(result, child, allowed);
42
62
  } else {
@@ -45,28 +65,47 @@ function addAllReferencedBlocks(result: BlockUpstreams, node: unknown, allowed?:
45
65
  }
46
66
 
47
67
  return;
48
-
68
+ }
49
69
  default:
50
70
  assertNever(type);
51
71
  }
52
72
  }
53
73
 
54
- export interface BlockUpstreams {
55
- /** All direct block dependencies */
56
- upstreams: Set<string>;
57
- /** Direct block dependencies which enrichments are also required by current block */
58
- upstreamsRequiringEnrichments: Set<string>;
59
- /** True if not-allowed references was encountered */
60
- missingReferences: boolean;
74
+ /**
75
+ * Detect a PlRef carried inside a string and hand the decoded value to `onParsed`.
76
+ *
77
+ * A PlRef-as-string is canonical `{...}` optionally wrapped by N `JSON.stringify`
78
+ * passes. Each pass adds a symmetric prefix/suffix made only of `"` and `\`
79
+ * chars (the escape padding) of equal length. The regex below is the strict
80
+ * shape gate — non-ref strings fail at the very first character, so we never
81
+ * scan their body. One pass is peeled per call; deeper nesting is unwrapped
82
+ * via recursion in the caller.
83
+ */
84
+ const EMBEDDED_REF_RE = /^(?<pre>[\\"]*)\{[\s\S]*?__isRef[\s\S]*\}(?<suf>[\\"]*)$/;
85
+
86
+ function unwrapEmbeddedRef(s: string, onParsed: (value: unknown) => void) {
87
+ const c0 = s.charCodeAt(0);
88
+ if (c0 !== 0x7b /* { */ && c0 !== 0x22 /* " */) return;
89
+ const m = EMBEDDED_REF_RE.exec(s);
90
+ if (m === null) return;
91
+ if (m.groups!.pre.length !== m.groups!.suf.length) return;
92
+ let parsed: unknown;
93
+ try {
94
+ parsed = JSON.parse(s);
95
+ } catch {
96
+ return;
97
+ }
98
+ if (parsed !== s) onParsed(parsed);
61
99
  }
62
100
 
63
- /** Extracts all resource ids referenced by args object. */
64
- export function inferAllReferencedBlocks(args: unknown, allowed?: Set<string>): BlockUpstreams {
65
- const result = {
66
- upstreams: new Set<string>(),
67
- upstreamsRequiringEnrichments: new Set<string>(),
68
- missingReferences: false,
69
- };
70
- addAllReferencedBlocks(result, args, allowed);
71
- return result;
101
+ function recordRef(
102
+ result: BlockUpstreams,
103
+ blockId: string,
104
+ requireEnrichments: boolean,
105
+ allowed?: Set<string>,
106
+ ) {
107
+ if (allowed === undefined || allowed.has(blockId)) {
108
+ result.upstreams.add(blockId);
109
+ if (requireEnrichments) result.upstreamsRequiringEnrichments.add(blockId);
110
+ } else result.missingReferences = true;
72
111
  }