@milaboratories/pl-middle-layer 1.75.0 → 1.76.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.
Files changed (48) hide show
  1. package/dist/index.cjs +1 -0
  2. package/dist/index.d.ts +3 -2
  3. package/dist/index.js +2 -2
  4. package/dist/middle_layer/block_action_context.cjs +57 -0
  5. package/dist/middle_layer/block_action_context.cjs.map +1 -0
  6. package/dist/middle_layer/block_action_context.d.ts +47 -0
  7. package/dist/middle_layer/block_action_context.d.ts.map +1 -0
  8. package/dist/middle_layer/block_action_context.js +57 -0
  9. package/dist/middle_layer/block_action_context.js.map +1 -0
  10. package/dist/middle_layer/build_stamp.cjs +1 -1
  11. package/dist/middle_layer/build_stamp.js +1 -1
  12. package/dist/middle_layer/index.d.ts +2 -1
  13. package/dist/middle_layer/project.cjs +8 -0
  14. package/dist/middle_layer/project.cjs.map +1 -1
  15. package/dist/middle_layer/project.d.ts +6 -0
  16. package/dist/middle_layer/project.d.ts.map +1 -1
  17. package/dist/middle_layer/project.js +8 -0
  18. package/dist/middle_layer/project.js.map +1 -1
  19. package/dist/model/project_model.cjs +1 -1
  20. package/dist/model/project_model.cjs.map +1 -1
  21. package/dist/model/project_model.d.ts +1 -1
  22. package/dist/model/project_model.js +1 -1
  23. package/dist/model/project_model.js.map +1 -1
  24. package/dist/mutator/project.cjs +19 -4
  25. package/dist/mutator/project.cjs.map +1 -1
  26. package/dist/mutator/project.d.ts.map +1 -1
  27. package/dist/mutator/project.js +19 -4
  28. package/dist/mutator/project.js.map +1 -1
  29. package/dist/mutator/template/render_block.cjs +35 -2
  30. package/dist/mutator/template/render_block.cjs.map +1 -1
  31. package/dist/mutator/template/render_block.js +35 -4
  32. package/dist/mutator/template/render_block.js.map +1 -1
  33. package/dist/mutator/template/render_template.cjs +16 -1
  34. package/dist/mutator/template/render_template.cjs.map +1 -1
  35. package/dist/mutator/template/render_template.d.ts +12 -1
  36. package/dist/mutator/template/render_template.d.ts.map +1 -1
  37. package/dist/mutator/template/render_template.js +16 -2
  38. package/dist/mutator/template/render_template.js.map +1 -1
  39. package/package.json +7 -7
  40. package/src/middle_layer/block_action_context.test.ts +148 -0
  41. package/src/middle_layer/block_action_context.ts +162 -0
  42. package/src/middle_layer/index.ts +6 -0
  43. package/src/middle_layer/project.ts +13 -0
  44. package/src/model/project_model.ts +5 -2
  45. package/src/mutator/project.ts +47 -13
  46. package/src/mutator/project_action_context.test.ts +178 -0
  47. package/src/mutator/template/render_block.ts +70 -5
  48. package/src/mutator/template/render_template.ts +22 -2
@@ -12,8 +12,39 @@ const BContext = {
12
12
  };
13
13
  const BContextMultiParentPrefix = "parent/";
14
14
  const HeavyBlockOutputNames = ["context", "result"];
15
- function createRenderHeavyBlock(tx, tpl, inputs) {
16
- return require_render_template.createRenderTemplate(tx, tpl, true, inputs, HeavyBlockOutputNames);
15
+ /** Resource type of a block action context. See ActionContextV1Type in the backend. */
16
+ const ActionContextType = {
17
+ name: "ActionContext",
18
+ version: "1"
19
+ };
20
+ /** The capability under which the backend accepts the "actionContext" link on a render. */
21
+ const ActionContextLinkCapability = "optionalFieldsInSchemas:v1";
22
+ /**
23
+ * Creates the root action context of a block render: an ActionContext resource carrying the
24
+ * baggage, with a new random `run-id`, since each render is one run of the block and the contexts
25
+ * the templates create copy it. Undefined without baggage, or on a backend that cannot take the link.
26
+ */
27
+ function createBlockActionContext(tx, baggage) {
28
+ if (baggage === void 0 || !tx.hasCapability("optionalFieldsInSchemas:v1")) return void 0;
29
+ return tx.createEphemeral(ActionContextType, JSON.stringify({
30
+ name: baggage.name,
31
+ "block-pack": baggage.blockPack,
32
+ "block-id": baggage.blockId,
33
+ "run-id": (0, node_crypto.randomUUID)()
34
+ }));
35
+ }
36
+ /**
37
+ * Creates the heavy block render, and with `baggage` its root action context in the same
38
+ * transaction. Returns `actionContext`: the render is garbage collected after its outputs resolve,
39
+ * so the caller must hold the context to keep it.
40
+ */
41
+ function createRenderHeavyBlock(tx, tpl, inputs, baggage) {
42
+ const actionContext = createBlockActionContext(tx, baggage);
43
+ const { outputs } = require_render_template.createRenderTemplateWithResource(tx, tpl, true, inputs, HeavyBlockOutputNames, actionContext);
44
+ return actionContext === void 0 ? outputs : {
45
+ ...outputs,
46
+ actionContext
47
+ };
17
48
  }
18
49
  function createBContextEnd(tx) {
19
50
  const ctx = tx.createEphemeral(BContextEnd);
@@ -29,6 +60,8 @@ function createBContextFromUpstreams(tx, upstreamCtxs) {
29
60
  tx.lock(ctx);
30
61
  return ctx;
31
62
  }
63
+ exports.ActionContextLinkCapability = ActionContextLinkCapability;
64
+ exports.ActionContextType = ActionContextType;
32
65
  exports.BContext = BContext;
33
66
  exports.BContextEnd = BContextEnd;
34
67
  exports.BContextMultiParentPrefix = BContextMultiParentPrefix;
@@ -1 +1 @@
1
- {"version":3,"file":"render_block.cjs","names":["createRenderTemplate","field","Pl","randomUUID"],"sources":["../../../src/mutator/template/render_block.ts"],"sourcesContent":["import type { AnyRef, PlTransaction, ResourceRef, ResourceType } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport { randomUUID } from \"node:crypto\";\nimport { createRenderTemplate } from \"./render_template\";\n\nexport const BContextEnd: ResourceType = { name: \"BContextEnd\", version: \"1\" };\nexport const BContext: ResourceType = { name: \"BContext\", version: \"1\" };\nexport const BContextId = \"id\";\nexport const BContextParent = \"parent\";\nexport const BContextMultiParentPrefix = \"parent/\";\n\n// TODO: add implementation for dual context heavy block.\n// export type BlockType =\n// | 'LightBlock'\n// | 'HeavyBlock'\n// | 'DualContextHeavyBlock';\n// moved to project model ==>>>\n\nexport type HeavyBlockInputs = {\n args: AnyRef;\n blockId: AnyRef;\n isProduction: AnyRef;\n context: AnyRef;\n};\n\nexport type HeavyBlockOutputs = {\n context: AnyRef;\n result: AnyRef;\n};\n\nexport const HeavyBlockOutputNames: (keyof HeavyBlockOutputs)[] = [\"context\", \"result\"];\n\nexport function createRenderHeavyBlock(\n tx: PlTransaction,\n tpl: AnyRef,\n inputs: HeavyBlockInputs,\n): HeavyBlockOutputs {\n return createRenderTemplate(tx, tpl, true, inputs, HeavyBlockOutputNames);\n}\n\nexport type LightBlockInputs = {\n args: AnyRef;\n blockId: AnyRef;\n stagingContext: AnyRef;\n productionContext: AnyRef;\n};\n\nexport type LightBlockOutput = {\n result: AnyRef;\n};\n\nexport const LightBlockOutputNames: (keyof LightBlockOutput)[] = [\"result\"];\n\nexport function createRenderLightBlock(\n tx: PlTransaction,\n tpl: ResourceRef,\n inputs: LightBlockInputs,\n): LightBlockOutput {\n return createRenderTemplate(tx, tpl, true, inputs, LightBlockOutputNames);\n}\n\nexport function createBContextEnd(tx: PlTransaction): ResourceRef {\n const ctx = tx.createEphemeral(BContextEnd);\n tx.lock(ctx);\n return ctx;\n}\n\nexport function createBContextFromUpstreams(tx: PlTransaction, upstreamCtxs: AnyRef[]): AnyRef {\n if (upstreamCtxs.length === 0) return createBContextEnd(tx);\n\n if (upstreamCtxs.length === 1) return upstreamCtxs[0];\n\n const ctx = tx.createEphemeral(BContext);\n\n // setting id\n tx.createField(field(ctx, BContextId), \"Input\", Pl.createPlString(tx, randomUUID()));\n\n // setting parents\n for (let i = 0; i < upstreamCtxs.length; i++)\n tx.createField(field(ctx, `${BContextMultiParentPrefix}${i}`), \"Input\", upstreamCtxs[i]);\n\n tx.lock(ctx);\n\n return ctx;\n}\n"],"mappings":";;;;AAKA,MAAa,cAA4B;CAAE,MAAM;CAAe,SAAS;AAAI;AAC7E,MAAa,WAAyB;CAAE,MAAM;CAAY,SAAS;AAAI;AAGvE,MAAa,4BAA4B;AAqBzC,MAAa,wBAAqD,CAAC,WAAW,QAAQ;AAEtF,SAAgB,uBACd,IACA,KACA,QACmB;CACnB,OAAOA,wBAAAA,qBAAqB,IAAI,KAAK,MAAM,QAAQ,qBAAqB;AAC1E;AAuBA,SAAgB,kBAAkB,IAAgC;CAChE,MAAM,MAAM,GAAG,gBAAgB,WAAW;CAC1C,GAAG,KAAK,GAAG;CACX,OAAO;AACT;AAEA,SAAgB,4BAA4B,IAAmB,cAAgC;CAC7F,IAAI,aAAa,WAAW,GAAG,OAAO,kBAAkB,EAAE;CAE1D,IAAI,aAAa,WAAW,GAAG,OAAO,aAAa;CAEnD,MAAM,MAAM,GAAG,gBAAgB,QAAQ;CAGvC,GAAG,aAAA,GAAYC,0BAAAA,MAAAA,CAAM,KAAA,IAAe,GAAG,SAASC,0BAAAA,GAAG,eAAe,KAAA,GAAIC,YAAAA,WAAAA,CAAW,CAAC,CAAC;CAGnF,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KACvC,GAAG,aAAA,GAAYF,0BAAAA,MAAAA,CAAM,KAAK,GAAG,4BAA4B,GAAG,GAAG,SAAS,aAAa,EAAE;CAEzF,GAAG,KAAK,GAAG;CAEX,OAAO;AACT"}
1
+ {"version":3,"file":"render_block.cjs","names":["randomUUID","createRenderTemplateWithResource","field","Pl"],"sources":["../../../src/mutator/template/render_block.ts"],"sourcesContent":["import type { AnyRef, PlTransaction, ResourceRef, ResourceType } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport { randomUUID } from \"node:crypto\";\nimport { createRenderTemplateWithResource } from \"./render_template\";\n\nexport const BContextEnd: ResourceType = { name: \"BContextEnd\", version: \"1\" };\nexport const BContext: ResourceType = { name: \"BContext\", version: \"1\" };\nexport const BContextId = \"id\";\nexport const BContextParent = \"parent\";\nexport const BContextMultiParentPrefix = \"parent/\";\n\n// TODO: add implementation for dual context heavy block.\n// export type BlockType =\n// | 'LightBlock'\n// | 'HeavyBlock'\n// | 'DualContextHeavyBlock';\n// moved to project model ==>>>\n\nexport type HeavyBlockInputs = {\n args: AnyRef;\n blockId: AnyRef;\n isProduction: AnyRef;\n context: AnyRef;\n};\n\nexport type HeavyBlockOutputs = {\n context: AnyRef;\n result: AnyRef;\n};\n\nexport const HeavyBlockOutputNames: (keyof HeavyBlockOutputs)[] = [\"context\", \"result\"];\n\n/** Baggage of the root action context of a block render. See the block action context spec. */\nexport type BlockActionContextBaggage = {\n /** Display name of the block. */\n name: string;\n /** JSON of the block pack spec. */\n blockPack: string;\n blockId: string;\n};\n\n/** Resource type of a block action context. See ActionContextV1Type in the backend. */\nexport const ActionContextType: ResourceType = { name: \"ActionContext\", version: \"1\" };\n\n/** The capability under which the backend accepts the \"actionContext\" link on a render. */\nexport const ActionContextLinkCapability = \"optionalFieldsInSchemas:v1\";\n\n/**\n * Creates the root action context of a block render: an ActionContext resource carrying the\n * baggage, with a new random `run-id`, since each render is one run of the block and the contexts\n * the templates create copy it. Undefined without baggage, or on a backend that cannot take the link.\n */\nfunction createBlockActionContext(\n tx: PlTransaction,\n baggage: BlockActionContextBaggage | undefined,\n): ResourceRef | undefined {\n if (baggage === undefined || !tx.hasCapability(ActionContextLinkCapability)) return undefined;\n return tx.createEphemeral(\n ActionContextType,\n JSON.stringify({\n name: baggage.name,\n \"block-pack\": baggage.blockPack,\n \"block-id\": baggage.blockId,\n \"run-id\": randomUUID(),\n }),\n );\n}\n\n/**\n * Creates the heavy block render, and with `baggage` its root action context in the same\n * transaction. Returns `actionContext`: the render is garbage collected after its outputs resolve,\n * so the caller must hold the context to keep it.\n */\nexport function createRenderHeavyBlock(\n tx: PlTransaction,\n tpl: AnyRef,\n inputs: HeavyBlockInputs,\n baggage?: BlockActionContextBaggage,\n): HeavyBlockOutputs & { actionContext?: ResourceRef } {\n const actionContext = createBlockActionContext(tx, baggage);\n const { outputs } = createRenderTemplateWithResource(\n tx,\n tpl,\n true,\n inputs,\n HeavyBlockOutputNames,\n actionContext,\n );\n return actionContext === undefined ? outputs : { ...outputs, actionContext };\n}\n\nexport type LightBlockInputs = {\n args: AnyRef;\n blockId: AnyRef;\n stagingContext: AnyRef;\n productionContext: AnyRef;\n};\n\nexport type LightBlockOutput = {\n result: AnyRef;\n};\n\nexport const LightBlockOutputNames: (keyof LightBlockOutput)[] = [\"result\"];\n\n/**\n * Creates the light block render, with the same action context as a heavy one, so template code\n * of either kind knows its block and run.\n */\nexport function createRenderLightBlock(\n tx: PlTransaction,\n tpl: ResourceRef,\n inputs: LightBlockInputs,\n baggage?: BlockActionContextBaggage,\n): LightBlockOutput & { actionContext?: ResourceRef } {\n const actionContext = createBlockActionContext(tx, baggage);\n const { outputs } = createRenderTemplateWithResource(\n tx,\n tpl,\n true,\n inputs,\n LightBlockOutputNames,\n actionContext,\n );\n return actionContext === undefined ? outputs : { ...outputs, actionContext };\n}\n\nexport function createBContextEnd(tx: PlTransaction): ResourceRef {\n const ctx = tx.createEphemeral(BContextEnd);\n tx.lock(ctx);\n return ctx;\n}\n\nexport function createBContextFromUpstreams(tx: PlTransaction, upstreamCtxs: AnyRef[]): AnyRef {\n if (upstreamCtxs.length === 0) return createBContextEnd(tx);\n\n if (upstreamCtxs.length === 1) return upstreamCtxs[0];\n\n const ctx = tx.createEphemeral(BContext);\n\n // setting id\n tx.createField(field(ctx, BContextId), \"Input\", Pl.createPlString(tx, randomUUID()));\n\n // setting parents\n for (let i = 0; i < upstreamCtxs.length; i++)\n tx.createField(field(ctx, `${BContextMultiParentPrefix}${i}`), \"Input\", upstreamCtxs[i]);\n\n tx.lock(ctx);\n\n return ctx;\n}\n"],"mappings":";;;;AAKA,MAAa,cAA4B;CAAE,MAAM;CAAe,SAAS;AAAI;AAC7E,MAAa,WAAyB;CAAE,MAAM;CAAY,SAAS;AAAI;AAGvE,MAAa,4BAA4B;AAqBzC,MAAa,wBAAqD,CAAC,WAAW,QAAQ;;AAYtF,MAAa,oBAAkC;CAAE,MAAM;CAAiB,SAAS;AAAI;;AAGrF,MAAa,8BAA8B;;;;;;AAO3C,SAAS,yBACP,IACA,SACyB;CACzB,IAAI,YAAY,KAAA,KAAa,CAAC,GAAG,cAAA,4BAAyC,GAAG,OAAO,KAAA;CACpF,OAAO,GAAG,gBACR,mBACA,KAAK,UAAU;EACb,MAAM,QAAQ;EACd,cAAc,QAAQ;EACtB,YAAY,QAAQ;EACpB,WAAA,GAAUA,YAAAA,WAAAA,CAAW;CACvB,CAAC,CACH;AACF;;;;;;AAOA,SAAgB,uBACd,IACA,KACA,QACA,SACqD;CACrD,MAAM,gBAAgB,yBAAyB,IAAI,OAAO;CAC1D,MAAM,EAAE,YAAYC,wBAAAA,iCAClB,IACA,KACA,MACA,QACA,uBACA,aACF;CACA,OAAO,kBAAkB,KAAA,IAAY,UAAU;EAAE,GAAG;EAAS;CAAc;AAC7E;AAqCA,SAAgB,kBAAkB,IAAgC;CAChE,MAAM,MAAM,GAAG,gBAAgB,WAAW;CAC1C,GAAG,KAAK,GAAG;CACX,OAAO;AACT;AAEA,SAAgB,4BAA4B,IAAmB,cAAgC;CAC7F,IAAI,aAAa,WAAW,GAAG,OAAO,kBAAkB,EAAE;CAE1D,IAAI,aAAa,WAAW,GAAG,OAAO,aAAa;CAEnD,MAAM,MAAM,GAAG,gBAAgB,QAAQ;CAGvC,GAAG,aAAA,GAAYC,0BAAAA,MAAAA,CAAM,KAAA,IAAe,GAAG,SAASC,0BAAAA,GAAG,eAAe,KAAA,GAAIH,YAAAA,WAAAA,CAAW,CAAC,CAAC;CAGnF,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KACvC,GAAG,aAAA,GAAYE,0BAAAA,MAAAA,CAAM,KAAK,GAAG,4BAA4B,GAAG,GAAG,SAAS,aAAa,EAAE;CAEzF,GAAG,KAAK,GAAG;CAEX,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- import { createRenderTemplate } from "./render_template.js";
1
+ import { createRenderTemplateWithResource } from "./render_template.js";
2
2
  import { Pl, field } from "@milaboratories/pl-client";
3
3
  import { randomUUID } from "node:crypto";
4
4
  // src/mutator/template/render_block.ts
@@ -12,8 +12,39 @@ const BContext = {
12
12
  };
13
13
  const BContextMultiParentPrefix = "parent/";
14
14
  const HeavyBlockOutputNames = ["context", "result"];
15
- function createRenderHeavyBlock(tx, tpl, inputs) {
16
- return createRenderTemplate(tx, tpl, true, inputs, HeavyBlockOutputNames);
15
+ /** Resource type of a block action context. See ActionContextV1Type in the backend. */
16
+ const ActionContextType = {
17
+ name: "ActionContext",
18
+ version: "1"
19
+ };
20
+ /** The capability under which the backend accepts the "actionContext" link on a render. */
21
+ const ActionContextLinkCapability = "optionalFieldsInSchemas:v1";
22
+ /**
23
+ * Creates the root action context of a block render: an ActionContext resource carrying the
24
+ * baggage, with a new random `run-id`, since each render is one run of the block and the contexts
25
+ * the templates create copy it. Undefined without baggage, or on a backend that cannot take the link.
26
+ */
27
+ function createBlockActionContext(tx, baggage) {
28
+ if (baggage === void 0 || !tx.hasCapability("optionalFieldsInSchemas:v1")) return void 0;
29
+ return tx.createEphemeral(ActionContextType, JSON.stringify({
30
+ name: baggage.name,
31
+ "block-pack": baggage.blockPack,
32
+ "block-id": baggage.blockId,
33
+ "run-id": randomUUID()
34
+ }));
35
+ }
36
+ /**
37
+ * Creates the heavy block render, and with `baggage` its root action context in the same
38
+ * transaction. Returns `actionContext`: the render is garbage collected after its outputs resolve,
39
+ * so the caller must hold the context to keep it.
40
+ */
41
+ function createRenderHeavyBlock(tx, tpl, inputs, baggage) {
42
+ const actionContext = createBlockActionContext(tx, baggage);
43
+ const { outputs } = createRenderTemplateWithResource(tx, tpl, true, inputs, HeavyBlockOutputNames, actionContext);
44
+ return actionContext === void 0 ? outputs : {
45
+ ...outputs,
46
+ actionContext
47
+ };
17
48
  }
18
49
  function createBContextEnd(tx) {
19
50
  const ctx = tx.createEphemeral(BContextEnd);
@@ -29,6 +60,6 @@ function createBContextFromUpstreams(tx, upstreamCtxs) {
29
60
  tx.lock(ctx);
30
61
  return ctx;
31
62
  }
32
- export { BContext, BContextEnd, BContextMultiParentPrefix, HeavyBlockOutputNames, createBContextEnd, createBContextFromUpstreams, createRenderHeavyBlock };
63
+ export { ActionContextLinkCapability, ActionContextType, BContext, BContextEnd, BContextMultiParentPrefix, HeavyBlockOutputNames, createBContextEnd, createBContextFromUpstreams, createRenderHeavyBlock };
33
64
 
34
65
  //# sourceMappingURL=render_block.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"render_block.js","names":[],"sources":["../../../src/mutator/template/render_block.ts"],"sourcesContent":["import type { AnyRef, PlTransaction, ResourceRef, ResourceType } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport { randomUUID } from \"node:crypto\";\nimport { createRenderTemplate } from \"./render_template\";\n\nexport const BContextEnd: ResourceType = { name: \"BContextEnd\", version: \"1\" };\nexport const BContext: ResourceType = { name: \"BContext\", version: \"1\" };\nexport const BContextId = \"id\";\nexport const BContextParent = \"parent\";\nexport const BContextMultiParentPrefix = \"parent/\";\n\n// TODO: add implementation for dual context heavy block.\n// export type BlockType =\n// | 'LightBlock'\n// | 'HeavyBlock'\n// | 'DualContextHeavyBlock';\n// moved to project model ==>>>\n\nexport type HeavyBlockInputs = {\n args: AnyRef;\n blockId: AnyRef;\n isProduction: AnyRef;\n context: AnyRef;\n};\n\nexport type HeavyBlockOutputs = {\n context: AnyRef;\n result: AnyRef;\n};\n\nexport const HeavyBlockOutputNames: (keyof HeavyBlockOutputs)[] = [\"context\", \"result\"];\n\nexport function createRenderHeavyBlock(\n tx: PlTransaction,\n tpl: AnyRef,\n inputs: HeavyBlockInputs,\n): HeavyBlockOutputs {\n return createRenderTemplate(tx, tpl, true, inputs, HeavyBlockOutputNames);\n}\n\nexport type LightBlockInputs = {\n args: AnyRef;\n blockId: AnyRef;\n stagingContext: AnyRef;\n productionContext: AnyRef;\n};\n\nexport type LightBlockOutput = {\n result: AnyRef;\n};\n\nexport const LightBlockOutputNames: (keyof LightBlockOutput)[] = [\"result\"];\n\nexport function createRenderLightBlock(\n tx: PlTransaction,\n tpl: ResourceRef,\n inputs: LightBlockInputs,\n): LightBlockOutput {\n return createRenderTemplate(tx, tpl, true, inputs, LightBlockOutputNames);\n}\n\nexport function createBContextEnd(tx: PlTransaction): ResourceRef {\n const ctx = tx.createEphemeral(BContextEnd);\n tx.lock(ctx);\n return ctx;\n}\n\nexport function createBContextFromUpstreams(tx: PlTransaction, upstreamCtxs: AnyRef[]): AnyRef {\n if (upstreamCtxs.length === 0) return createBContextEnd(tx);\n\n if (upstreamCtxs.length === 1) return upstreamCtxs[0];\n\n const ctx = tx.createEphemeral(BContext);\n\n // setting id\n tx.createField(field(ctx, BContextId), \"Input\", Pl.createPlString(tx, randomUUID()));\n\n // setting parents\n for (let i = 0; i < upstreamCtxs.length; i++)\n tx.createField(field(ctx, `${BContextMultiParentPrefix}${i}`), \"Input\", upstreamCtxs[i]);\n\n tx.lock(ctx);\n\n return ctx;\n}\n"],"mappings":";;;;AAKA,MAAa,cAA4B;CAAE,MAAM;CAAe,SAAS;AAAI;AAC7E,MAAa,WAAyB;CAAE,MAAM;CAAY,SAAS;AAAI;AAGvE,MAAa,4BAA4B;AAqBzC,MAAa,wBAAqD,CAAC,WAAW,QAAQ;AAEtF,SAAgB,uBACd,IACA,KACA,QACmB;CACnB,OAAO,qBAAqB,IAAI,KAAK,MAAM,QAAQ,qBAAqB;AAC1E;AAuBA,SAAgB,kBAAkB,IAAgC;CAChE,MAAM,MAAM,GAAG,gBAAgB,WAAW;CAC1C,GAAG,KAAK,GAAG;CACX,OAAO;AACT;AAEA,SAAgB,4BAA4B,IAAmB,cAAgC;CAC7F,IAAI,aAAa,WAAW,GAAG,OAAO,kBAAkB,EAAE;CAE1D,IAAI,aAAa,WAAW,GAAG,OAAO,aAAa;CAEnD,MAAM,MAAM,GAAG,gBAAgB,QAAQ;CAGvC,GAAG,YAAY,MAAM,KAAA,IAAe,GAAG,SAAS,GAAG,eAAe,IAAI,WAAW,CAAC,CAAC;CAGnF,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KACvC,GAAG,YAAY,MAAM,KAAK,GAAG,4BAA4B,GAAG,GAAG,SAAS,aAAa,EAAE;CAEzF,GAAG,KAAK,GAAG;CAEX,OAAO;AACT"}
1
+ {"version":3,"file":"render_block.js","names":[],"sources":["../../../src/mutator/template/render_block.ts"],"sourcesContent":["import type { AnyRef, PlTransaction, ResourceRef, ResourceType } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport { randomUUID } from \"node:crypto\";\nimport { createRenderTemplateWithResource } from \"./render_template\";\n\nexport const BContextEnd: ResourceType = { name: \"BContextEnd\", version: \"1\" };\nexport const BContext: ResourceType = { name: \"BContext\", version: \"1\" };\nexport const BContextId = \"id\";\nexport const BContextParent = \"parent\";\nexport const BContextMultiParentPrefix = \"parent/\";\n\n// TODO: add implementation for dual context heavy block.\n// export type BlockType =\n// | 'LightBlock'\n// | 'HeavyBlock'\n// | 'DualContextHeavyBlock';\n// moved to project model ==>>>\n\nexport type HeavyBlockInputs = {\n args: AnyRef;\n blockId: AnyRef;\n isProduction: AnyRef;\n context: AnyRef;\n};\n\nexport type HeavyBlockOutputs = {\n context: AnyRef;\n result: AnyRef;\n};\n\nexport const HeavyBlockOutputNames: (keyof HeavyBlockOutputs)[] = [\"context\", \"result\"];\n\n/** Baggage of the root action context of a block render. See the block action context spec. */\nexport type BlockActionContextBaggage = {\n /** Display name of the block. */\n name: string;\n /** JSON of the block pack spec. */\n blockPack: string;\n blockId: string;\n};\n\n/** Resource type of a block action context. See ActionContextV1Type in the backend. */\nexport const ActionContextType: ResourceType = { name: \"ActionContext\", version: \"1\" };\n\n/** The capability under which the backend accepts the \"actionContext\" link on a render. */\nexport const ActionContextLinkCapability = \"optionalFieldsInSchemas:v1\";\n\n/**\n * Creates the root action context of a block render: an ActionContext resource carrying the\n * baggage, with a new random `run-id`, since each render is one run of the block and the contexts\n * the templates create copy it. Undefined without baggage, or on a backend that cannot take the link.\n */\nfunction createBlockActionContext(\n tx: PlTransaction,\n baggage: BlockActionContextBaggage | undefined,\n): ResourceRef | undefined {\n if (baggage === undefined || !tx.hasCapability(ActionContextLinkCapability)) return undefined;\n return tx.createEphemeral(\n ActionContextType,\n JSON.stringify({\n name: baggage.name,\n \"block-pack\": baggage.blockPack,\n \"block-id\": baggage.blockId,\n \"run-id\": randomUUID(),\n }),\n );\n}\n\n/**\n * Creates the heavy block render, and with `baggage` its root action context in the same\n * transaction. Returns `actionContext`: the render is garbage collected after its outputs resolve,\n * so the caller must hold the context to keep it.\n */\nexport function createRenderHeavyBlock(\n tx: PlTransaction,\n tpl: AnyRef,\n inputs: HeavyBlockInputs,\n baggage?: BlockActionContextBaggage,\n): HeavyBlockOutputs & { actionContext?: ResourceRef } {\n const actionContext = createBlockActionContext(tx, baggage);\n const { outputs } = createRenderTemplateWithResource(\n tx,\n tpl,\n true,\n inputs,\n HeavyBlockOutputNames,\n actionContext,\n );\n return actionContext === undefined ? outputs : { ...outputs, actionContext };\n}\n\nexport type LightBlockInputs = {\n args: AnyRef;\n blockId: AnyRef;\n stagingContext: AnyRef;\n productionContext: AnyRef;\n};\n\nexport type LightBlockOutput = {\n result: AnyRef;\n};\n\nexport const LightBlockOutputNames: (keyof LightBlockOutput)[] = [\"result\"];\n\n/**\n * Creates the light block render, with the same action context as a heavy one, so template code\n * of either kind knows its block and run.\n */\nexport function createRenderLightBlock(\n tx: PlTransaction,\n tpl: ResourceRef,\n inputs: LightBlockInputs,\n baggage?: BlockActionContextBaggage,\n): LightBlockOutput & { actionContext?: ResourceRef } {\n const actionContext = createBlockActionContext(tx, baggage);\n const { outputs } = createRenderTemplateWithResource(\n tx,\n tpl,\n true,\n inputs,\n LightBlockOutputNames,\n actionContext,\n );\n return actionContext === undefined ? outputs : { ...outputs, actionContext };\n}\n\nexport function createBContextEnd(tx: PlTransaction): ResourceRef {\n const ctx = tx.createEphemeral(BContextEnd);\n tx.lock(ctx);\n return ctx;\n}\n\nexport function createBContextFromUpstreams(tx: PlTransaction, upstreamCtxs: AnyRef[]): AnyRef {\n if (upstreamCtxs.length === 0) return createBContextEnd(tx);\n\n if (upstreamCtxs.length === 1) return upstreamCtxs[0];\n\n const ctx = tx.createEphemeral(BContext);\n\n // setting id\n tx.createField(field(ctx, BContextId), \"Input\", Pl.createPlString(tx, randomUUID()));\n\n // setting parents\n for (let i = 0; i < upstreamCtxs.length; i++)\n tx.createField(field(ctx, `${BContextMultiParentPrefix}${i}`), \"Input\", upstreamCtxs[i]);\n\n tx.lock(ctx);\n\n return ctx;\n}\n"],"mappings":";;;;AAKA,MAAa,cAA4B;CAAE,MAAM;CAAe,SAAS;AAAI;AAC7E,MAAa,WAAyB;CAAE,MAAM;CAAY,SAAS;AAAI;AAGvE,MAAa,4BAA4B;AAqBzC,MAAa,wBAAqD,CAAC,WAAW,QAAQ;;AAYtF,MAAa,oBAAkC;CAAE,MAAM;CAAiB,SAAS;AAAI;;AAGrF,MAAa,8BAA8B;;;;;;AAO3C,SAAS,yBACP,IACA,SACyB;CACzB,IAAI,YAAY,KAAA,KAAa,CAAC,GAAG,cAAA,4BAAyC,GAAG,OAAO,KAAA;CACpF,OAAO,GAAG,gBACR,mBACA,KAAK,UAAU;EACb,MAAM,QAAQ;EACd,cAAc,QAAQ;EACtB,YAAY,QAAQ;EACpB,UAAU,WAAW;CACvB,CAAC,CACH;AACF;;;;;;AAOA,SAAgB,uBACd,IACA,KACA,QACA,SACqD;CACrD,MAAM,gBAAgB,yBAAyB,IAAI,OAAO;CAC1D,MAAM,EAAE,YAAY,iCAClB,IACA,KACA,MACA,QACA,uBACA,aACF;CACA,OAAO,kBAAkB,KAAA,IAAY,UAAU;EAAE,GAAG;EAAS;CAAc;AAC7E;AAqCA,SAAgB,kBAAkB,IAAgC;CAChE,MAAM,MAAM,GAAG,gBAAgB,WAAW;CAC1C,GAAG,KAAK,GAAG;CACX,OAAO;AACT;AAEA,SAAgB,4BAA4B,IAAmB,cAAgC;CAC7F,IAAI,aAAa,WAAW,GAAG,OAAO,kBAAkB,EAAE;CAE1D,IAAI,aAAa,WAAW,GAAG,OAAO,aAAa;CAEnD,MAAM,MAAM,GAAG,gBAAgB,QAAQ;CAGvC,GAAG,YAAY,MAAM,KAAA,IAAe,GAAG,SAAS,GAAG,eAAe,IAAI,WAAW,CAAC,CAAC;CAGnF,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KACvC,GAAG,YAAY,MAAM,KAAK,GAAG,4BAA4B,GAAG,GAAG,SAAS,aAAa,EAAE;CAEzF,GAAG,KAAK,GAAG;CAEX,OAAO;AACT"}
@@ -18,15 +18,30 @@ const RenderTemplate = {
18
18
  * @param outputNames names of the outputs to collect and return from this method
19
19
  */
20
20
  function createRenderTemplate(tx, tpl, ephemeral, inputs, outputNames) {
21
+ return createRenderTemplateWithResource(tx, tpl, ephemeral, inputs, outputNames).outputs;
22
+ }
23
+ /**
24
+ * Same as {@link createRenderTemplate}, and also returns the render resource itself.
25
+ *
26
+ * With `actionContext`, the render's "actionContext" service field points to it. The link is created
27
+ * before the inputs are locked: the backend accepts it only in the creating transaction, ahead of
28
+ * any explicit input lock.
29
+ */
30
+ function createRenderTemplateWithResource(tx, tpl, ephemeral, inputs, outputNames, actionContext) {
21
31
  if (outputNames.length === 0) throw new Error("Zero output names provided");
22
32
  const rId = ephemeral ? tx.createEphemeral(EphRenderTemplate) : tx.createStruct(RenderTemplate);
23
33
  const tplField = (0, _milaboratories_pl_client.field)(rId, "template");
24
34
  const inputsField = (0, _milaboratories_pl_client.field)(rId, "inputs");
25
35
  tx.createField(tplField, "Input", tpl);
26
36
  tx.createField(inputsField, "Input", _milaboratories_pl_client.Pl.createPlMap(tx, inputs, ephemeral));
37
+ if (actionContext !== void 0) tx.createField((0, _milaboratories_pl_client.field)(rId, "actionContext"), "Service", actionContext);
27
38
  tx.lockInputs(rId);
28
- return _milaboratories_pl_client.Pl.futureRecord(tx, rId, outputNames, "Output", "outputs/");
39
+ return {
40
+ render: rId,
41
+ outputs: _milaboratories_pl_client.Pl.futureRecord(tx, rId, outputNames, "Output", "outputs/")
42
+ };
29
43
  }
30
44
  exports.createRenderTemplate = createRenderTemplate;
45
+ exports.createRenderTemplateWithResource = createRenderTemplateWithResource;
31
46
 
32
47
  //# sourceMappingURL=render_template.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"render_template.cjs","names":["field","Pl"],"sources":["../../../src/mutator/template/render_template.ts"],"sourcesContent":["import type { AnyRef, FieldRef, PlTransaction } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport type { ResourceType } from \"@platforma-sdk/model\";\n\nconst EphRenderTemplate: ResourceType = {\n name: \"EphRenderTemplate\",\n version: \"1\",\n};\nconst RenderTemplate: ResourceType = {\n name: \"RenderTemplate\",\n version: \"1\",\n};\n\n/**\n * Creates render template operation, for a given remplate and inputs.\n *\n * @param tx transaction to use during rendering creation\n * @param tpl reference to the template resource (see {@link loadTemplate})\n * @param ephemeral true for ephemeral templates, false for pure templates\n * @param inputs map of inputs pointing to other resources or fields\n * @param outputNames names of the outputs to collect and return from this method\n */\nexport function createRenderTemplate<O extends string>(\n tx: PlTransaction,\n tpl: AnyRef,\n ephemeral: boolean,\n inputs: Pl.PlRecord,\n outputNames: O[],\n): Record<O, FieldRef> {\n if (outputNames.length === 0) throw new Error(\"Zero output names provided\");\n const rId = ephemeral ? tx.createEphemeral(EphRenderTemplate) : tx.createStruct(RenderTemplate);\n\n const tplField = field(rId, \"template\");\n const inputsField = field(rId, \"inputs\");\n\n tx.createField(tplField, \"Input\", tpl);\n tx.createField(inputsField, \"Input\", Pl.createPlMap(tx, inputs, ephemeral));\n tx.lockInputs(rId);\n\n return Pl.futureRecord(tx, rId, outputNames, \"Output\", \"outputs/\");\n}\n"],"mappings":";;AAIA,MAAM,oBAAkC;CACtC,MAAM;CACN,SAAS;AACX;AACA,MAAM,iBAA+B;CACnC,MAAM;CACN,SAAS;AACX;;;;;;;;;;AAWA,SAAgB,qBACd,IACA,KACA,WACA,QACA,aACqB;CACrB,IAAI,YAAY,WAAW,GAAG,MAAM,IAAI,MAAM,4BAA4B;CAC1E,MAAM,MAAM,YAAY,GAAG,gBAAgB,iBAAiB,IAAI,GAAG,aAAa,cAAc;CAE9F,MAAM,YAAA,GAAWA,0BAAAA,MAAAA,CAAM,KAAK,UAAU;CACtC,MAAM,eAAA,GAAcA,0BAAAA,MAAAA,CAAM,KAAK,QAAQ;CAEvC,GAAG,YAAY,UAAU,SAAS,GAAG;CACrC,GAAG,YAAY,aAAa,SAASC,0BAAAA,GAAG,YAAY,IAAI,QAAQ,SAAS,CAAC;CAC1E,GAAG,WAAW,GAAG;CAEjB,OAAOA,0BAAAA,GAAG,aAAa,IAAI,KAAK,aAAa,UAAU,UAAU;AACnE"}
1
+ {"version":3,"file":"render_template.cjs","names":["field","Pl"],"sources":["../../../src/mutator/template/render_template.ts"],"sourcesContent":["import type { AnyRef, FieldRef, PlTransaction, ResourceRef } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport type { ResourceType } from \"@platforma-sdk/model\";\n\nconst EphRenderTemplate: ResourceType = {\n name: \"EphRenderTemplate\",\n version: \"1\",\n};\nconst RenderTemplate: ResourceType = {\n name: \"RenderTemplate\",\n version: \"1\",\n};\n\n/**\n * Creates render template operation, for a given remplate and inputs.\n *\n * @param tx transaction to use during rendering creation\n * @param tpl reference to the template resource (see {@link loadTemplate})\n * @param ephemeral true for ephemeral templates, false for pure templates\n * @param inputs map of inputs pointing to other resources or fields\n * @param outputNames names of the outputs to collect and return from this method\n */\nexport function createRenderTemplate<O extends string>(\n tx: PlTransaction,\n tpl: AnyRef,\n ephemeral: boolean,\n inputs: Pl.PlRecord,\n outputNames: O[],\n): Record<O, FieldRef> {\n return createRenderTemplateWithResource(tx, tpl, ephemeral, inputs, outputNames).outputs;\n}\n\n/**\n * Same as {@link createRenderTemplate}, and also returns the render resource itself.\n *\n * With `actionContext`, the render's \"actionContext\" service field points to it. The link is created\n * before the inputs are locked: the backend accepts it only in the creating transaction, ahead of\n * any explicit input lock.\n */\nexport function createRenderTemplateWithResource<O extends string>(\n tx: PlTransaction,\n tpl: AnyRef,\n ephemeral: boolean,\n inputs: Pl.PlRecord,\n outputNames: O[],\n actionContext?: AnyRef,\n): { render: ResourceRef; outputs: Record<O, FieldRef> } {\n if (outputNames.length === 0) throw new Error(\"Zero output names provided\");\n const rId = ephemeral ? tx.createEphemeral(EphRenderTemplate) : tx.createStruct(RenderTemplate);\n\n const tplField = field(rId, \"template\");\n const inputsField = field(rId, \"inputs\");\n\n tx.createField(tplField, \"Input\", tpl);\n tx.createField(inputsField, \"Input\", Pl.createPlMap(tx, inputs, ephemeral));\n if (actionContext !== undefined)\n tx.createField(field(rId, \"actionContext\"), \"Service\", actionContext);\n tx.lockInputs(rId);\n\n return { render: rId, outputs: Pl.futureRecord(tx, rId, outputNames, \"Output\", \"outputs/\") };\n}\n"],"mappings":";;AAIA,MAAM,oBAAkC;CACtC,MAAM;CACN,SAAS;AACX;AACA,MAAM,iBAA+B;CACnC,MAAM;CACN,SAAS;AACX;;;;;;;;;;AAWA,SAAgB,qBACd,IACA,KACA,WACA,QACA,aACqB;CACrB,OAAO,iCAAiC,IAAI,KAAK,WAAW,QAAQ,WAAW,CAAC,CAAC;AACnF;;;;;;;;AASA,SAAgB,iCACd,IACA,KACA,WACA,QACA,aACA,eACuD;CACvD,IAAI,YAAY,WAAW,GAAG,MAAM,IAAI,MAAM,4BAA4B;CAC1E,MAAM,MAAM,YAAY,GAAG,gBAAgB,iBAAiB,IAAI,GAAG,aAAa,cAAc;CAE9F,MAAM,YAAA,GAAWA,0BAAAA,MAAAA,CAAM,KAAK,UAAU;CACtC,MAAM,eAAA,GAAcA,0BAAAA,MAAAA,CAAM,KAAK,QAAQ;CAEvC,GAAG,YAAY,UAAU,SAAS,GAAG;CACrC,GAAG,YAAY,aAAa,SAASC,0BAAAA,GAAG,YAAY,IAAI,QAAQ,SAAS,CAAC;CAC1E,IAAI,kBAAkB,KAAA,GACpB,GAAG,aAAA,GAAYD,0BAAAA,MAAAA,CAAM,KAAK,eAAe,GAAG,WAAW,aAAa;CACtE,GAAG,WAAW,GAAG;CAEjB,OAAO;EAAE,QAAQ;EAAK,SAASC,0BAAAA,GAAG,aAAa,IAAI,KAAK,aAAa,UAAU,UAAU;CAAE;AAC7F"}
@@ -1,4 +1,4 @@
1
- import { AnyRef, FieldRef, Pl, PlTransaction } from "@milaboratories/pl-client";
1
+ import { AnyRef, FieldRef, Pl, PlTransaction, ResourceRef } from "@milaboratories/pl-client";
2
2
  /**
3
3
  * Creates render template operation, for a given remplate and inputs.
4
4
  *
@@ -9,4 +9,15 @@ import { AnyRef, FieldRef, Pl, PlTransaction } from "@milaboratories/pl-client";
9
9
  * @param outputNames names of the outputs to collect and return from this method
10
10
  */
11
11
  export declare function createRenderTemplate<O extends string>(tx: PlTransaction, tpl: AnyRef, ephemeral: boolean, inputs: Pl.PlRecord, outputNames: O[]): Record<O, FieldRef>;
12
+ /**
13
+ * Same as {@link createRenderTemplate}, and also returns the render resource itself.
14
+ *
15
+ * With `actionContext`, the render's "actionContext" service field points to it. The link is created
16
+ * before the inputs are locked: the backend accepts it only in the creating transaction, ahead of
17
+ * any explicit input lock.
18
+ */
19
+ export declare function createRenderTemplateWithResource<O extends string>(tx: PlTransaction, tpl: AnyRef, ephemeral: boolean, inputs: Pl.PlRecord, outputNames: O[], actionContext?: AnyRef): {
20
+ render: ResourceRef;
21
+ outputs: Record<O, FieldRef>;
22
+ };
12
23
  //# sourceMappingURL=render_template.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"render_template.d.ts","names":[],"sources":["../../../src/mutator/template/render_template.ts"],"mappings":";;;;;;;;;;wBAsBgB,qBAAqB,kBACnC,IAAI,eACJ,KAAK,QACL,oBACA,QAAQ,GAAG,UACX,aAAa,MACZ,OAAO,GAAG"}
1
+ {"version":3,"file":"render_template.d.ts","names":[],"sources":["../../../src/mutator/template/render_template.ts"],"mappings":";;;;;;;;;;wBAsBgB,qBAAqB,kBACnC,IAAI,eACJ,KAAK,QACL,oBACA,QAAQ,GAAG,UACX,aAAa,MACZ,OAAO,GAAG;;;;;;;;wBAWG,iCAAiC,kBAC/C,IAAI,eACJ,KAAK,QACL,oBACA,QAAQ,GAAG,UACX,aAAa,KACb,gBAAgB;EACb,QAAQ;EAAa,SAAS,OAAO,GAAG"}
@@ -18,15 +18,29 @@ const RenderTemplate = {
18
18
  * @param outputNames names of the outputs to collect and return from this method
19
19
  */
20
20
  function createRenderTemplate(tx, tpl, ephemeral, inputs, outputNames) {
21
+ return createRenderTemplateWithResource(tx, tpl, ephemeral, inputs, outputNames).outputs;
22
+ }
23
+ /**
24
+ * Same as {@link createRenderTemplate}, and also returns the render resource itself.
25
+ *
26
+ * With `actionContext`, the render's "actionContext" service field points to it. The link is created
27
+ * before the inputs are locked: the backend accepts it only in the creating transaction, ahead of
28
+ * any explicit input lock.
29
+ */
30
+ function createRenderTemplateWithResource(tx, tpl, ephemeral, inputs, outputNames, actionContext) {
21
31
  if (outputNames.length === 0) throw new Error("Zero output names provided");
22
32
  const rId = ephemeral ? tx.createEphemeral(EphRenderTemplate) : tx.createStruct(RenderTemplate);
23
33
  const tplField = field(rId, "template");
24
34
  const inputsField = field(rId, "inputs");
25
35
  tx.createField(tplField, "Input", tpl);
26
36
  tx.createField(inputsField, "Input", Pl.createPlMap(tx, inputs, ephemeral));
37
+ if (actionContext !== void 0) tx.createField(field(rId, "actionContext"), "Service", actionContext);
27
38
  tx.lockInputs(rId);
28
- return Pl.futureRecord(tx, rId, outputNames, "Output", "outputs/");
39
+ return {
40
+ render: rId,
41
+ outputs: Pl.futureRecord(tx, rId, outputNames, "Output", "outputs/")
42
+ };
29
43
  }
30
- export { createRenderTemplate };
44
+ export { createRenderTemplate, createRenderTemplateWithResource };
31
45
 
32
46
  //# sourceMappingURL=render_template.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"render_template.js","names":[],"sources":["../../../src/mutator/template/render_template.ts"],"sourcesContent":["import type { AnyRef, FieldRef, PlTransaction } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport type { ResourceType } from \"@platforma-sdk/model\";\n\nconst EphRenderTemplate: ResourceType = {\n name: \"EphRenderTemplate\",\n version: \"1\",\n};\nconst RenderTemplate: ResourceType = {\n name: \"RenderTemplate\",\n version: \"1\",\n};\n\n/**\n * Creates render template operation, for a given remplate and inputs.\n *\n * @param tx transaction to use during rendering creation\n * @param tpl reference to the template resource (see {@link loadTemplate})\n * @param ephemeral true for ephemeral templates, false for pure templates\n * @param inputs map of inputs pointing to other resources or fields\n * @param outputNames names of the outputs to collect and return from this method\n */\nexport function createRenderTemplate<O extends string>(\n tx: PlTransaction,\n tpl: AnyRef,\n ephemeral: boolean,\n inputs: Pl.PlRecord,\n outputNames: O[],\n): Record<O, FieldRef> {\n if (outputNames.length === 0) throw new Error(\"Zero output names provided\");\n const rId = ephemeral ? tx.createEphemeral(EphRenderTemplate) : tx.createStruct(RenderTemplate);\n\n const tplField = field(rId, \"template\");\n const inputsField = field(rId, \"inputs\");\n\n tx.createField(tplField, \"Input\", tpl);\n tx.createField(inputsField, \"Input\", Pl.createPlMap(tx, inputs, ephemeral));\n tx.lockInputs(rId);\n\n return Pl.futureRecord(tx, rId, outputNames, \"Output\", \"outputs/\");\n}\n"],"mappings":";;AAIA,MAAM,oBAAkC;CACtC,MAAM;CACN,SAAS;AACX;AACA,MAAM,iBAA+B;CACnC,MAAM;CACN,SAAS;AACX;;;;;;;;;;AAWA,SAAgB,qBACd,IACA,KACA,WACA,QACA,aACqB;CACrB,IAAI,YAAY,WAAW,GAAG,MAAM,IAAI,MAAM,4BAA4B;CAC1E,MAAM,MAAM,YAAY,GAAG,gBAAgB,iBAAiB,IAAI,GAAG,aAAa,cAAc;CAE9F,MAAM,WAAW,MAAM,KAAK,UAAU;CACtC,MAAM,cAAc,MAAM,KAAK,QAAQ;CAEvC,GAAG,YAAY,UAAU,SAAS,GAAG;CACrC,GAAG,YAAY,aAAa,SAAS,GAAG,YAAY,IAAI,QAAQ,SAAS,CAAC;CAC1E,GAAG,WAAW,GAAG;CAEjB,OAAO,GAAG,aAAa,IAAI,KAAK,aAAa,UAAU,UAAU;AACnE"}
1
+ {"version":3,"file":"render_template.js","names":[],"sources":["../../../src/mutator/template/render_template.ts"],"sourcesContent":["import type { AnyRef, FieldRef, PlTransaction, ResourceRef } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport type { ResourceType } from \"@platforma-sdk/model\";\n\nconst EphRenderTemplate: ResourceType = {\n name: \"EphRenderTemplate\",\n version: \"1\",\n};\nconst RenderTemplate: ResourceType = {\n name: \"RenderTemplate\",\n version: \"1\",\n};\n\n/**\n * Creates render template operation, for a given remplate and inputs.\n *\n * @param tx transaction to use during rendering creation\n * @param tpl reference to the template resource (see {@link loadTemplate})\n * @param ephemeral true for ephemeral templates, false for pure templates\n * @param inputs map of inputs pointing to other resources or fields\n * @param outputNames names of the outputs to collect and return from this method\n */\nexport function createRenderTemplate<O extends string>(\n tx: PlTransaction,\n tpl: AnyRef,\n ephemeral: boolean,\n inputs: Pl.PlRecord,\n outputNames: O[],\n): Record<O, FieldRef> {\n return createRenderTemplateWithResource(tx, tpl, ephemeral, inputs, outputNames).outputs;\n}\n\n/**\n * Same as {@link createRenderTemplate}, and also returns the render resource itself.\n *\n * With `actionContext`, the render's \"actionContext\" service field points to it. The link is created\n * before the inputs are locked: the backend accepts it only in the creating transaction, ahead of\n * any explicit input lock.\n */\nexport function createRenderTemplateWithResource<O extends string>(\n tx: PlTransaction,\n tpl: AnyRef,\n ephemeral: boolean,\n inputs: Pl.PlRecord,\n outputNames: O[],\n actionContext?: AnyRef,\n): { render: ResourceRef; outputs: Record<O, FieldRef> } {\n if (outputNames.length === 0) throw new Error(\"Zero output names provided\");\n const rId = ephemeral ? tx.createEphemeral(EphRenderTemplate) : tx.createStruct(RenderTemplate);\n\n const tplField = field(rId, \"template\");\n const inputsField = field(rId, \"inputs\");\n\n tx.createField(tplField, \"Input\", tpl);\n tx.createField(inputsField, \"Input\", Pl.createPlMap(tx, inputs, ephemeral));\n if (actionContext !== undefined)\n tx.createField(field(rId, \"actionContext\"), \"Service\", actionContext);\n tx.lockInputs(rId);\n\n return { render: rId, outputs: Pl.futureRecord(tx, rId, outputNames, \"Output\", \"outputs/\") };\n}\n"],"mappings":";;AAIA,MAAM,oBAAkC;CACtC,MAAM;CACN,SAAS;AACX;AACA,MAAM,iBAA+B;CACnC,MAAM;CACN,SAAS;AACX;;;;;;;;;;AAWA,SAAgB,qBACd,IACA,KACA,WACA,QACA,aACqB;CACrB,OAAO,iCAAiC,IAAI,KAAK,WAAW,QAAQ,WAAW,CAAC,CAAC;AACnF;;;;;;;;AASA,SAAgB,iCACd,IACA,KACA,WACA,QACA,aACA,eACuD;CACvD,IAAI,YAAY,WAAW,GAAG,MAAM,IAAI,MAAM,4BAA4B;CAC1E,MAAM,MAAM,YAAY,GAAG,gBAAgB,iBAAiB,IAAI,GAAG,aAAa,cAAc;CAE9F,MAAM,WAAW,MAAM,KAAK,UAAU;CACtC,MAAM,cAAc,MAAM,KAAK,QAAQ;CAEvC,GAAG,YAAY,UAAU,SAAS,GAAG;CACrC,GAAG,YAAY,aAAa,SAAS,GAAG,YAAY,IAAI,QAAQ,SAAS,CAAC;CAC1E,IAAI,kBAAkB,KAAA,GACpB,GAAG,YAAY,MAAM,KAAK,eAAe,GAAG,WAAW,aAAa;CACtE,GAAG,WAAW,GAAG;CAEjB,OAAO;EAAE,QAAQ;EAAK,SAAS,GAAG,aAAa,IAAI,KAAK,aAAa,UAAU,UAAU;CAAE;AAC7F"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-middle-layer",
3
- "version": "1.75.0",
3
+ "version": "1.76.1",
4
4
  "description": "Pl Middle Layer",
5
5
  "keywords": [],
6
6
  "license": "UNLICENSED",
@@ -25,19 +25,19 @@
25
25
  "@milaboratories/pf-driver": "1.9.13",
26
26
  "@milaboratories/pf-spec-driver": "1.5.13",
27
27
  "@milaboratories/pframes-rs-node": "1.2.0",
28
- "@milaboratories/pl-client": "3.19.4",
28
+ "@milaboratories/pl-client": "3.20.0",
29
29
  "@milaboratories/pl-crash-recorder": "0.3.9",
30
30
  "@milaboratories/pl-deployments": "3.0.25",
31
- "@milaboratories/pl-drivers": "1.16.39",
32
- "@milaboratories/pl-errors": "1.4.56",
31
+ "@milaboratories/pl-drivers": "1.16.40",
32
+ "@milaboratories/pl-errors": "1.4.57",
33
33
  "@milaboratories/pl-http": "1.2.5",
34
- "@milaboratories/pl-model-backend": "1.4.41",
34
+ "@milaboratories/pl-model-backend": "1.4.42",
35
35
  "@milaboratories/pl-model-common": "1.55.0",
36
36
  "@milaboratories/pl-model-middle-layer": "1.35.0",
37
- "@milaboratories/pl-tree": "1.16.8",
37
+ "@milaboratories/pl-tree": "1.16.9",
38
38
  "@milaboratories/resolve-helper": "1.1.3",
39
39
  "@milaboratories/ts-helpers": "1.9.0",
40
- "@platforma-sdk/block-tools": "2.17.3",
40
+ "@platforma-sdk/block-tools": "2.17.5",
41
41
  "@platforma-sdk/model": "1.88.0",
42
42
  "@platforma-sdk/workflow-tengo": "6.20.0",
43
43
  "canonicalize": "~2.1.0",
@@ -0,0 +1,148 @@
1
+ import type { SignedResourceId } from "@milaboratories/pl-client";
2
+ import { createSignedResourceId, parseSignedResourceId } from "@milaboratories/pl-client";
3
+ import { describe, expect, test } from "vitest";
4
+ import { projectFieldName } from "../model/project_model";
5
+ import type { ActionContextReader, ActionContextReadTx } from "./block_action_context";
6
+ import { readBlockActionContextTree } from "./block_action_context";
7
+
8
+ type FakeResource = {
9
+ data?: Record<string, string>;
10
+ fields: { name: string; value: SignedResourceId }[];
11
+ kvs: { key: string; value: string }[];
12
+ };
13
+
14
+ function signedId(globalId: number): SignedResourceId {
15
+ return createSignedResourceId(BigInt(globalId));
16
+ }
17
+
18
+ /** A client whose read transaction serves the given resources by global ID. */
19
+ function fakeClient(resources: Map<string, FakeResource>): ActionContextReader {
20
+ const lookup = (ref: SignedResourceId) => {
21
+ const resource = resources.get(String(parseSignedResourceId(ref).globalId));
22
+ if (resource === undefined) throw new Error(`no resource ${ref}`);
23
+ return resource;
24
+ };
25
+ const tx: ActionContextReadTx = {
26
+ getResourceData: async (ref: SignedResourceId) => {
27
+ const resource = lookup(ref);
28
+ return {
29
+ data: resource.data === undefined ? undefined : Buffer.from(JSON.stringify(resource.data)),
30
+ fields: resource.fields,
31
+ };
32
+ },
33
+ listKeyValuesString: async (ref: SignedResourceId) => lookup(ref).kvs,
34
+ };
35
+
36
+ return {
37
+ withReadTx: async <T>(_name: string, body: (tx: ActionContextReadTx) => Promise<T>) =>
38
+ await body(tx),
39
+ };
40
+ }
41
+
42
+ const project = signedId(1);
43
+ const prodRoot = signedId(10);
44
+ const stagingRoot = signedId(11);
45
+ const child = signedId(20);
46
+ const grandchild = signedId(30);
47
+
48
+ function resources(): Map<string, FakeResource> {
49
+ return new Map<string, FakeResource>([
50
+ [
51
+ "1",
52
+ {
53
+ fields: [
54
+ { name: projectFieldName("block1", "prodActionContext"), value: prodRoot },
55
+ { name: projectFieldName("block1", "stagingActionContext"), value: stagingRoot },
56
+ ],
57
+ kvs: [],
58
+ },
59
+ ],
60
+ [
61
+ "10",
62
+ {
63
+ data: { name: "prod render" },
64
+ fields: [
65
+ { name: "child/32", value: child },
66
+ { name: "other", value: signedId(99) },
67
+ ],
68
+ kvs: [
69
+ // timestamps of different lengths: string order differs from time order
70
+ { key: "transition/render/16/1000", value: '{"state":"Running"}' },
71
+ { key: "transition/render/16/999", value: '{"state":"WaitingForInputs"}' },
72
+ { key: "reading/exec/16/progress", value: "40" },
73
+ { key: "reading/exec/16/stage", value: "align" },
74
+ { key: "unrelated", value: "x" },
75
+ ],
76
+ },
77
+ ],
78
+ ["11", { data: { name: "staging render" }, fields: [], kvs: [] }],
79
+ [
80
+ "20",
81
+ {
82
+ data: { name: "child render" },
83
+ fields: [{ name: "child/48", value: grandchild }],
84
+ kvs: [{ key: "transition/render/32/5", value: '{"state":"Completed","detail":"ok"}' }],
85
+ },
86
+ ],
87
+ ["30", { data: { name: "grandchild render" }, fields: [], kvs: [] }],
88
+ ]);
89
+ }
90
+
91
+ describe("readBlockActionContextTree", () => {
92
+ test("reads the prod tree with children, ordered transitions and readings", async () => {
93
+ const tree = (await readBlockActionContextTree(
94
+ fakeClient(resources()),
95
+ project,
96
+ "block1",
97
+ "prod",
98
+ ))!;
99
+
100
+ expect(tree.contextId).toEqual("10");
101
+ expect(tree.resource).toEqual("16");
102
+ expect(tree.baggage).toEqual({ name: "prod render" });
103
+ expect(tree.transitions.map((t) => [t.timestampNs, t.state])).toEqual([
104
+ ["999", "WaitingForInputs"],
105
+ ["1000", "Running"],
106
+ ]);
107
+ expect(tree.readings).toEqual([
108
+ { namespace: "exec", resource: "16", key: "progress", value: "40" },
109
+ { namespace: "exec", resource: "16", key: "stage", value: "align" },
110
+ ]);
111
+
112
+ expect(tree.children.map((c) => c.baggage["name"])).toEqual(["child render"]);
113
+ const childNode = tree.children[0];
114
+ expect(childNode.transitions).toEqual([
115
+ {
116
+ namespace: "render",
117
+ resource: "32",
118
+ timestampNs: "5",
119
+ state: "Completed",
120
+ detail: "ok",
121
+ reason: "",
122
+ },
123
+ ]);
124
+ expect(childNode.resource).toEqual("32");
125
+ expect(childNode.children.map((c) => c.contextId)).toEqual(["30"]);
126
+ expect(childNode.children[0].resource).toBeUndefined();
127
+ expect(childNode.children[0].children).toEqual([]);
128
+ });
129
+
130
+ test("reads the staging tree for staging mode", async () => {
131
+ const tree = (await readBlockActionContextTree(
132
+ fakeClient(resources()),
133
+ project,
134
+ "block1",
135
+ "staging",
136
+ ))!;
137
+
138
+ expect(tree.contextId).toEqual("11");
139
+ expect(tree.baggage).toEqual({ name: "staging render" });
140
+ expect(tree.children).toEqual([]);
141
+ });
142
+
143
+ test("returns undefined for a block without an action context field", async () => {
144
+ expect(
145
+ await readBlockActionContextTree(fakeClient(resources()), project, "block2", "prod"),
146
+ ).toBeUndefined();
147
+ });
148
+ });
@@ -0,0 +1,162 @@
1
+ import type {
2
+ KeyValueString,
3
+ OptionalSignedResourceId,
4
+ SignedResourceId,
5
+ } from "@milaboratories/pl-client";
6
+ import { isNullSignedResourceId, parseSignedResourceId } from "@milaboratories/pl-client";
7
+ import { projectFieldName } from "../model/project_model";
8
+
9
+ /** Which render of a block the action context tree belongs to. */
10
+ export type BlockActionContextMode = "prod" | "staging";
11
+
12
+ /** An action transition: transition/<namespace>/<resource>/<ns> in an action context. */
13
+ export type BlockActionTransition = {
14
+ namespace: string;
15
+ /** The resource that reported, e.g. the render or a command it created. */
16
+ resource: string;
17
+ /** Server clock in nanoseconds when the transition was recorded, as a decimal string. */
18
+ timestampNs: string;
19
+ state: string;
20
+ detail: string;
21
+ reason: string;
22
+ };
23
+
24
+ /**
25
+ * A reading: reading/<namespace>/<resource>/<key> in an action context. The backend sets it asynchronously:
26
+ * of two readings of one key, the one from the transaction opened later stays.
27
+ */
28
+ export type BlockActionReading = {
29
+ namespace: string;
30
+ resource: string;
31
+ key: string;
32
+ /** Opaque bytes as UTF-8 text; the reporter owns the format. */
33
+ value: string;
34
+ };
35
+
36
+ /** One action context: the action context of one render and the renders it created. */
37
+ export type BlockActionContextNode = {
38
+ /**
39
+ * Global resource ID of this ActionContext resource itself, as a decimal string. Not the ID of the
40
+ * render it describes: that is {@link BlockActionContextNode.resource}. Pass it back to the MCP
41
+ * tool to fetch this node's subtree.
42
+ */
43
+ contextId: string;
44
+ /**
45
+ * The render the context belongs to: the resource of its first transition under namespace "render",
46
+ * which the workflow controller records when it evaluates the render.
47
+ * Undefined until that transition is applied, or on a backend without block action context reports.
48
+ */
49
+ resource?: string;
50
+ /** Context baggage: name, block-pack, block-id, run-id and so on. */
51
+ baggage: Record<string, string>;
52
+ /** In time order. */
53
+ transitions: BlockActionTransition[];
54
+ readings: BlockActionReading[];
55
+ children: BlockActionContextNode[];
56
+ };
57
+
58
+ const childFieldPrefix = "child/";
59
+
60
+ /**
61
+ * Reads the action context tree of a block render: the root action context the project holds in
62
+ * <blockId>-prodActionContext or <blockId>-stagingActionContext, and every child context under it.
63
+ * Reads the current state in one transaction; it does not watch for changes.
64
+ *
65
+ * @returns undefined when the block has no action context: it was not rendered yet, or the backend
66
+ * does not support block action context.
67
+ */
68
+ /**
69
+ * The part of a read transaction the reader uses: a {@link PlTransaction} satisfies it, and a test
70
+ * can serve it without a backend.
71
+ */
72
+ export interface ActionContextReadTx {
73
+ getResourceData(
74
+ rId: SignedResourceId,
75
+ loadFields: true,
76
+ ): Promise<{
77
+ readonly data?: Uint8Array;
78
+ readonly fields: readonly { readonly name: string; readonly value: OptionalSignedResourceId }[];
79
+ }>;
80
+ listKeyValuesString(rId: SignedResourceId): Promise<KeyValueString[]>;
81
+ }
82
+
83
+ /** The part of a client the reader uses: a {@link PlClient} satisfies it. */
84
+ export interface ActionContextReader {
85
+ withReadTx<T>(name: string, body: (tx: ActionContextReadTx) => Promise<T>): Promise<T>;
86
+ }
87
+
88
+ export async function readBlockActionContextTree(
89
+ pl: ActionContextReader,
90
+ projectRid: SignedResourceId,
91
+ blockId: string,
92
+ mode: BlockActionContextMode,
93
+ ): Promise<BlockActionContextNode | undefined> {
94
+ return await pl.withReadTx("readBlockActionContextTree", async (tx) => {
95
+ const project = await tx.getResourceData(projectRid, true);
96
+ const fieldName = projectFieldName(
97
+ blockId,
98
+ mode === "prod" ? "prodActionContext" : "stagingActionContext",
99
+ );
100
+ const actionContextField = project.fields.find((f) => f.name === fieldName);
101
+ if (actionContextField === undefined || isNullSignedResourceId(actionContextField.value))
102
+ return undefined;
103
+
104
+ return await readActionContextNode(tx, actionContextField.value);
105
+ });
106
+ }
107
+
108
+ async function readActionContextNode(
109
+ tx: ActionContextReadTx,
110
+ contextRef: SignedResourceId,
111
+ ): Promise<BlockActionContextNode> {
112
+ const [context, kvs] = await Promise.all([
113
+ tx.getResourceData(contextRef, true),
114
+ tx.listKeyValuesString(contextRef),
115
+ ]);
116
+
117
+ const baggage =
118
+ context.data === undefined || context.data.length === 0
119
+ ? {}
120
+ : (JSON.parse(Buffer.from(context.data).toString()) as Record<string, string>);
121
+
122
+ const transitions: BlockActionTransition[] = [];
123
+ const readings: BlockActionReading[] = [];
124
+ for (const { key, value } of kvs) {
125
+ const parts = key.split("/");
126
+ if (parts[0] === "transition" && parts.length === 4) {
127
+ const parsed = JSON.parse(value) as { state?: string; detail?: string; reason?: string };
128
+ transitions.push({
129
+ namespace: parts[1],
130
+ resource: parts[2],
131
+ timestampNs: parts[3],
132
+ state: parsed.state ?? "",
133
+ detail: parsed.detail ?? "",
134
+ reason: parsed.reason ?? "",
135
+ });
136
+ } else if (parts[0] === "reading" && parts.length === 4) {
137
+ readings.push({ namespace: parts[1], resource: parts[2], key: parts[3], value });
138
+ }
139
+ }
140
+ transitions.sort((a, b) => compareDecimal(a.timestampNs, b.timestampNs));
141
+
142
+ const childRefs: SignedResourceId[] = [];
143
+ for (const f of context.fields) {
144
+ if (f.name.startsWith(childFieldPrefix) && !isNullSignedResourceId(f.value))
145
+ childRefs.push(f.value);
146
+ }
147
+ const children = await Promise.all(childRefs.map((ref) => readActionContextNode(tx, ref)));
148
+
149
+ return {
150
+ contextId: String(parseSignedResourceId(contextRef).globalId),
151
+ resource: transitions.find((t) => t.namespace === "render")?.resource,
152
+ baggage,
153
+ transitions,
154
+ readings,
155
+ children,
156
+ };
157
+ }
158
+
159
+ /** Compares non-negative decimal integers given as strings. */
160
+ function compareDecimal(a: string, b: string): number {
161
+ return a.length !== b.length ? a.length - b.length : a < b ? -1 : a > b ? 1 : 0;
162
+ }
@@ -15,3 +15,9 @@ export type {
15
15
  TemplateId,
16
16
  TemplateListEntry,
17
17
  } from "./template_list";
18
+ export type {
19
+ BlockActionReading,
20
+ BlockActionContextMode,
21
+ BlockActionContextNode,
22
+ BlockActionTransition,
23
+ } from "./block_action_context";