@milaboratories/pl-middle-layer 1.60.5 → 1.61.0
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/block_registry/registry.cjs +7 -1
- package/dist/block_registry/registry.cjs.map +1 -1
- package/dist/block_registry/registry.d.ts.map +1 -1
- package/dist/block_registry/registry.js +7 -1
- package/dist/block_registry/registry.js.map +1 -1
- package/dist/middle_layer/middle_layer.cjs +9 -0
- package/dist/middle_layer/middle_layer.cjs.map +1 -1
- package/dist/middle_layer/middle_layer.d.ts +7 -0
- package/dist/middle_layer/middle_layer.d.ts.map +1 -1
- package/dist/middle_layer/middle_layer.js +9 -0
- package/dist/middle_layer/middle_layer.js.map +1 -1
- package/dist/middle_layer/project.cjs +17 -0
- package/dist/middle_layer/project.cjs.map +1 -1
- package/dist/middle_layer/project.d.ts.map +1 -1
- package/dist/middle_layer/project.js +17 -0
- package/dist/middle_layer/project.js.map +1 -1
- package/dist/model/block_pack_spec.cjs.map +1 -1
- package/dist/model/block_pack_spec.d.ts +6 -0
- package/dist/model/block_pack_spec.d.ts.map +1 -1
- package/dist/model/block_pack_spec.js.map +1 -1
- package/dist/mutator/block-pack/block_pack.cjs +5 -3
- package/dist/mutator/block-pack/block_pack.cjs.map +1 -1
- package/dist/mutator/block-pack/block_pack.d.ts.map +1 -1
- package/dist/mutator/block-pack/block_pack.js +5 -3
- package/dist/mutator/block-pack/block_pack.js.map +1 -1
- package/dist/mutator/block-pack/required_capabilities.cjs +44 -0
- package/dist/mutator/block-pack/required_capabilities.cjs.map +1 -0
- package/dist/mutator/block-pack/required_capabilities.js +42 -0
- package/dist/mutator/block-pack/required_capabilities.js.map +1 -0
- package/dist/mutator/project.cjs +1 -1
- package/dist/mutator/project.js +1 -1
- package/package.json +14 -14
- package/src/block_registry/registry.ts +21 -1
- package/src/middle_layer/middle_layer.ts +10 -0
- package/src/middle_layer/project.ts +27 -0
- package/src/model/block_pack_spec.ts +6 -0
- package/src/mutator/block-pack/block_pack.ts +7 -2
- package/src/mutator/block-pack/required_capabilities.ts +44 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block_pack_spec.js","names":[],"sources":["../../src/model/block_pack_spec.ts"],"sourcesContent":["import type { CachedTemplate, ExplicitTemplate, PreparedTemplate } from \"./template_spec\";\nimport type { ResourceType } from \"@milaboratories/pl-client\";\nimport type { BlockConfigContainer } from \"@platforma-sdk/model\";\nimport type { BlockPackSpec } from \"@milaboratories/pl-model-middle-layer\";\n\nexport type FrontendSpec = FrontendFromUrl | FrontendFromFolder;\n\nexport interface FrontendFromUrlData {\n url: string;\n}\n\nexport const FrontendFromUrlResourceType: ResourceType = { name: \"Frontend/FromUrl\", version: \"1\" };\n\n/** Directs user of the block pack to download the contents from the URL\n * outside the pl infrastructure. */\nexport interface FrontendFromUrl extends FrontendFromUrlData {\n type: \"url\";\n}\n\nexport interface FrontendFromFolderData {\n path: string;\n /** HMAC signature of the path using local secret encoded as hex. */\n signature: string;\n}\n\nexport const FrontendFromFolderResourceType: ResourceType = {\n name: \"Frontend/FromFolder\",\n version: \"1\",\n};\n\n/** Directs user of the block pack to load frontend from specific local\n * folder. Signature allows to confirm that this is the same client who\n * added the resource. */\nexport interface FrontendFromFolder extends FrontendFromFolderData {\n type: \"local\";\n}\n\n/** Direct instructions to create block-pack from client. Currently, this\n * is the only block-pack spec that can be directly materialized. */\nexport interface BlockPackExplicit {\n type: \"explicit\";\n template: ExplicitTemplate;\n config: BlockConfigContainer;\n frontend: FrontendSpec;\n source: BlockPackSpec;\n}\n\n/** Block-pack spec that can be materialized in pl. */\nexport type BlockPackSpecPrepared = {\n type: \"prepared\";\n template: PreparedTemplate | CachedTemplate;\n config: BlockConfigContainer;\n frontend: FrontendSpec;\n source: BlockPackSpec;\n};\n\n/** All block-pack specs. */\nexport type BlockPackSpecAny = BlockPackSpecPrepared | BlockPackExplicit | BlockPackSpec;\n"],"mappings":";AAWA,MAAa,8BAA4C;CAAE,MAAM;CAAoB,SAAS;CAAK;AAcnG,MAAa,iCAA+C;CAC1D,MAAM;CACN,SAAS;CACV"}
|
|
1
|
+
{"version":3,"file":"block_pack_spec.js","names":[],"sources":["../../src/model/block_pack_spec.ts"],"sourcesContent":["import type { CachedTemplate, ExplicitTemplate, PreparedTemplate } from \"./template_spec\";\nimport type { ResourceType } from \"@milaboratories/pl-client\";\nimport type { BlockConfigContainer } from \"@platforma-sdk/model\";\nimport type { BlockPackSpec } from \"@milaboratories/pl-model-middle-layer\";\n\nexport type FrontendSpec = FrontendFromUrl | FrontendFromFolder;\n\nexport interface FrontendFromUrlData {\n url: string;\n}\n\nexport const FrontendFromUrlResourceType: ResourceType = { name: \"Frontend/FromUrl\", version: \"1\" };\n\n/** Directs user of the block pack to download the contents from the URL\n * outside the pl infrastructure. */\nexport interface FrontendFromUrl extends FrontendFromUrlData {\n type: \"url\";\n}\n\nexport interface FrontendFromFolderData {\n path: string;\n /** HMAC signature of the path using local secret encoded as hex. */\n signature: string;\n}\n\nexport const FrontendFromFolderResourceType: ResourceType = {\n name: \"Frontend/FromFolder\",\n version: \"1\",\n};\n\n/** Directs user of the block pack to load frontend from specific local\n * folder. Signature allows to confirm that this is the same client who\n * added the resource. */\nexport interface FrontendFromFolder extends FrontendFromFolderData {\n type: \"local\";\n}\n\n/** Direct instructions to create block-pack from client. Currently, this\n * is the only block-pack spec that can be directly materialized. */\nexport interface BlockPackExplicit {\n type: \"explicit\";\n template: ExplicitTemplate;\n config: BlockConfigContainer;\n frontend: FrontendSpec;\n source: BlockPackSpec;\n}\n\n/** Block-pack spec that can be materialized in pl. */\nexport type BlockPackSpecPrepared = {\n type: \"prepared\";\n template: PreparedTemplate | CachedTemplate;\n config: BlockConfigContainer;\n frontend: FrontendSpec;\n source: BlockPackSpec;\n /** Backend-side runtime capabilities the block requires at install time.\n * Derived from the parsed workflow template by\n * `requiredCapabilitiesFromTemplate` during `BlockPackPreparer.prepare`.\n * `Project.addBlock` matches this list against `pl.serverInfo.capabilities`\n * and refuses to install when anything is missing. */\n requiredCapabilities?: string[];\n};\n\n/** All block-pack specs. */\nexport type BlockPackSpecAny = BlockPackSpecPrepared | BlockPackExplicit | BlockPackSpec;\n"],"mappings":";AAWA,MAAa,8BAA4C;CAAE,MAAM;CAAoB,SAAS;CAAK;AAcnG,MAAa,iCAA+C;CAC1D,MAAM;CACN,SAAS;CACV"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const require_runtime = require("../../_virtual/_rolldown/runtime.cjs");
|
|
2
2
|
const require_index = require("../../dev_env/index.cjs");
|
|
3
|
+
const require_required_capabilities = require("./required_capabilities.cjs");
|
|
3
4
|
const require_registry = require("../../block_registry/registry.cjs");
|
|
4
5
|
require("../../block_registry/index.cjs");
|
|
5
6
|
const require_template_loading = require("../template/template_loading.cjs");
|
|
@@ -101,14 +102,15 @@ var BlockPackPreparer = class {
|
|
|
101
102
|
if (cached) return cached;
|
|
102
103
|
}
|
|
103
104
|
const explicit = await this.prepareWithoutUnpacking(spec);
|
|
104
|
-
const
|
|
105
|
+
const parsed = await _usingCtx$1.a(new require_WorkerManager.WorkerManager()).process("parseTemplate", explicit.template.content);
|
|
105
106
|
const result = {
|
|
106
107
|
...explicit,
|
|
107
108
|
type: "prepared",
|
|
108
109
|
template: {
|
|
109
110
|
type: "prepared",
|
|
110
|
-
data:
|
|
111
|
-
}
|
|
111
|
+
data: parsed
|
|
112
|
+
},
|
|
113
|
+
requiredCapabilities: require_required_capabilities.requiredCapabilitiesFromTemplate(parsed)
|
|
112
114
|
};
|
|
113
115
|
if (key) this.preparedCache.set(key, result);
|
|
114
116
|
return result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block_pack.cjs","names":["z","Code","LRUCache","resolveDevPacket","fs","RegistryV1","WorkerManager","getDevV2PacketMtime","loadTemplate","createFrontend"],"sources":["../../../src/mutator/block-pack/block_pack.ts"],"sourcesContent":["import type { AnyResourceRef, PlTransaction, ResourceType } from \"@milaboratories/pl-client\";\nimport { field } from \"@milaboratories/pl-client\";\nimport { loadTemplate } from \"../template/template_loading\";\nimport type { BlockPackExplicit, BlockPackSpecAny, BlockPackSpecPrepared } from \"../../model\";\nimport type { Signer } from \"@milaboratories/ts-helpers\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport type { Branded } from \"@milaboratories/pl-model-common\";\nimport fs from \"node:fs\";\nimport type { Dispatcher } from \"undici\";\nimport { request } from \"undici\";\nimport { createFrontend } from \"./frontend\";\nimport type { BlockConfigContainer } from \"@platforma-sdk/model\";\nimport { Code } from \"@platforma-sdk/model\";\nimport { loadPackDescription, RegistryV1 } from \"@platforma-sdk/block-tools\";\nimport type { BlockPackInfo } from \"../../model/block_pack\";\nimport { resolveDevPacket } from \"../../dev_env\";\nimport { getDevV2PacketMtime } from \"../../block_registry\";\nimport type { V2RegistryProvider } from \"../../block_registry/registry-v2-provider\";\nimport { LRUCache } from \"lru-cache\";\nimport canonicalize from \"canonicalize\";\nimport type { BlockPackSpec } from \"@milaboratories/pl-model-middle-layer\";\nimport { WorkerManager } from \"../../worker/WorkerManager\";\nimport { z } from \"zod\";\n\ntype PreparedCacheKey = Branded<string, \"PreparedCacheKey\">;\n\nexport const BlockPackCustomType: ResourceType = { name: \"BlockPackCustom\", version: \"1\" };\nexport const BlockPackTemplateField = \"template\";\nexport const BlockPackFrontendField = \"frontend\";\n\n/** Ensure trailing slash */\nfunction tSlash(str: string): string {\n if (str.endsWith(\"/\")) return str;\n else return `${str}/`;\n}\n\nfunction parseStringConfig(configContent: string): BlockConfigContainer {\n const res = z.record(z.string(), z.unknown()).safeParse(JSON.parse(configContent));\n\n if (!res.success) {\n throw new Error(\"Invalid config content\");\n }\n\n if (!Code.safeParse(res.data.code).success) {\n throw new Error(\"parseStringConfig:No code bundle\");\n }\n\n return res.data as BlockConfigContainer;\n}\n\nfunction parseBufferConfig(buffer: ArrayBuffer): BlockConfigContainer {\n return parseStringConfig(Buffer.from(buffer).toString(\"utf8\"));\n}\n\nexport class BlockPackPreparer {\n constructor(\n private readonly v2RegistryProvider: V2RegistryProvider,\n private readonly signer: Signer,\n private readonly http?: Dispatcher,\n ) {}\n\n private readonly remoteContentCache = new LRUCache<string, ArrayBuffer>({\n max: 500,\n maxSize: 128 * 1024 * 1024,\n fetchMethod: async (key) => {\n const httpOptions = this.http !== undefined ? { dispatcher: this.http } : {};\n return await (await request(key, httpOptions)).body.arrayBuffer();\n },\n sizeCalculation: (value) => value.byteLength,\n });\n\n /** Cache of prepared block packs for registry specs (immutable by version). */\n private readonly preparedCache = new LRUCache<PreparedCacheKey, BlockPackSpecPrepared>({\n max: 50,\n });\n\n public async getBlockConfigContainer(spec: BlockPackSpecAny): Promise<BlockConfigContainer> {\n switch (spec.type) {\n case \"explicit\":\n return spec.config;\n\n case \"prepared\":\n return spec.config;\n\n case \"dev-v1\": {\n const devPaths = await resolveDevPacket(spec.folder, false);\n const configContent = await fs.promises.readFile(devPaths.config, { encoding: \"utf-8\" });\n return JSON.parse(configContent);\n }\n\n case \"dev-v2\": {\n const description = await loadPackDescription(spec.folder);\n const configContent = await fs.promises.readFile(description.components.model.file, {\n encoding: \"utf-8\",\n });\n return parseStringConfig(configContent);\n }\n\n case \"from-registry-v1\": {\n const urlPrefix = `${tSlash(spec.registryUrl)}${RegistryV1.packageContentPrefix({ organization: spec.id.organization, package: spec.id.name, version: spec.id.version })}`;\n\n const configResponse = await this.remoteContentCache.forceFetch(`${urlPrefix}/config.json`);\n return JSON.parse(Buffer.from(configResponse).toString(\"utf8\"));\n }\n\n case \"from-registry-v2\": {\n const registry = this.v2RegistryProvider.getRegistry(spec.registryUrl);\n const components = await registry.getComponents(spec.id);\n const configResponse = await this.remoteContentCache.forceFetch(components.model.url);\n return parseBufferConfig(configResponse);\n }\n\n default:\n return assertNever(spec);\n }\n }\n\n /** Returns a stable cache key for registry specs (immutable by version). Dev specs return undefined. */\n private specKey(spec: BlockPackSpecAny): PreparedCacheKey | undefined {\n switch (spec.type) {\n case \"from-registry-v1\":\n return `v1:${spec.registryUrl}:${spec.id.organization}:${spec.id.name}:${spec.id.version}` as PreparedCacheKey;\n case \"from-registry-v2\":\n return `v2:${spec.registryUrl}:${canonicalize(spec.id)}` as PreparedCacheKey;\n default:\n return undefined; // dev, explicit, prepared — not cacheable\n }\n }\n\n public async prepare(spec: BlockPackSpecAny): Promise<BlockPackSpecPrepared> {\n if (spec.type === \"prepared\") {\n return spec;\n }\n\n // Check prepare cache for registry specs\n const key = this.specKey(spec);\n if (key) {\n const cached = this.preparedCache.get(key);\n if (cached) return cached;\n }\n\n const explicit = await this.prepareWithoutUnpacking(spec);\n\n await using workerManager = new WorkerManager();\n\n const result: BlockPackSpecPrepared = {\n ...explicit,\n type: \"prepared\",\n template: {\n type: \"prepared\",\n data: await workerManager.process(\"parseTemplate\", explicit.template.content),\n },\n };\n\n if (key) {\n this.preparedCache.set(key, result);\n }\n\n return result;\n }\n\n private async prepareWithoutUnpacking(\n spec: BlockPackExplicit | BlockPackSpec,\n ): Promise<BlockPackExplicit> {\n switch (spec.type) {\n case \"explicit\":\n return spec;\n\n case \"dev-v1\": {\n const devPaths = await resolveDevPacket(spec.folder, false);\n\n // template\n const templateContent = await fs.promises.readFile(devPaths.workflow);\n\n // config\n const config = JSON.parse(await fs.promises.readFile(devPaths.config, \"utf-8\"));\n\n // frontend\n const frontendPath = devPaths.ui;\n\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: templateContent,\n },\n config,\n frontend: {\n type: \"local\",\n path: frontendPath,\n signature: this.signer.sign(frontendPath),\n },\n source: spec,\n };\n }\n\n case \"dev-v2\": {\n const description = await loadPackDescription(spec.folder);\n const config = parseStringConfig(\n await fs.promises.readFile(description.components.model.file, {\n encoding: \"utf-8\",\n }),\n );\n const workflowContent = await fs.promises.readFile(\n description.components.workflow.main.file,\n );\n const frontendPath = description.components.ui.folder;\n const source = { ...spec };\n if (spec.mtime === undefined)\n // if absent, calculating the mtime here, so the block will correctly show whether it can be updated\n source.mtime = await getDevV2PacketMtime(description);\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: workflowContent,\n },\n config,\n frontend: {\n type: \"local\",\n path: frontendPath,\n signature: this.signer.sign(frontendPath),\n },\n source,\n };\n }\n\n case \"from-registry-v1\": {\n const urlPrefix = `${tSlash(spec.registryUrl)}${RegistryV1.packageContentPrefix({ organization: spec.id.organization, package: spec.id.name, version: spec.id.version })}`;\n\n const templateUrl = `${urlPrefix}/template.plj.gz`;\n // template\n const templateResponse = await this.remoteContentCache.forceFetch(templateUrl);\n const templateContent = new Uint8Array(templateResponse);\n\n // config\n const configResponse = await this.remoteContentCache.forceFetch(`${urlPrefix}/config.json`);\n const config = JSON.parse(\n Buffer.from(configResponse).toString(\"utf8\"),\n ) as BlockConfigContainer;\n\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: templateContent,\n },\n config,\n frontend: {\n type: \"url\",\n url: `${urlPrefix}/frontend.tgz`,\n },\n source: spec,\n };\n }\n\n case \"from-registry-v2\": {\n const registry = this.v2RegistryProvider.getRegistry(spec.registryUrl);\n const components = await registry.getComponents(spec.id);\n const getModel = async () =>\n parseBufferConfig(await this.remoteContentCache.forceFetch(components.model.url));\n const getWorkflow = async () =>\n await this.remoteContentCache.forceFetch(components.workflow.main.url);\n\n const [model, workflow] = await Promise.all([getModel(), getWorkflow()]);\n\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: Buffer.from(workflow),\n },\n config: model,\n frontend: {\n type: \"url\",\n url: components.ui.url,\n },\n source: spec,\n };\n }\n\n default:\n return assertNever(spec);\n }\n }\n}\n\nfunction createCustomBlockPack(tx: PlTransaction, spec: BlockPackSpecPrepared): AnyResourceRef {\n const blockPackInfo: BlockPackInfo = { config: spec.config, source: spec.source };\n const bp = tx.createStruct(BlockPackCustomType, JSON.stringify(blockPackInfo));\n tx.createField(field(bp, BlockPackTemplateField), \"Input\", loadTemplate(tx, spec.template));\n tx.createField(field(bp, BlockPackFrontendField), \"Input\", createFrontend(tx, spec.frontend));\n tx.lock(bp);\n\n return bp;\n}\n\nexport function createBlockPack(tx: PlTransaction, spec: BlockPackSpecPrepared): AnyResourceRef {\n switch (spec.type) {\n case \"prepared\":\n return createCustomBlockPack(tx, spec);\n default:\n return assertNever(spec.type);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"block_pack.cjs","names":["z","Code","LRUCache","resolveDevPacket","fs","RegistryV1","WorkerManager","requiredCapabilitiesFromTemplate","getDevV2PacketMtime","loadTemplate","createFrontend"],"sources":["../../../src/mutator/block-pack/block_pack.ts"],"sourcesContent":["import type { AnyResourceRef, PlTransaction, ResourceType } from \"@milaboratories/pl-client\";\nimport { field } from \"@milaboratories/pl-client\";\nimport { loadTemplate } from \"../template/template_loading\";\nimport type { BlockPackExplicit, BlockPackSpecAny, BlockPackSpecPrepared } from \"../../model\";\nimport type { Signer } from \"@milaboratories/ts-helpers\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport type { Branded } from \"@milaboratories/pl-model-common\";\nimport fs from \"node:fs\";\nimport type { Dispatcher } from \"undici\";\nimport { request } from \"undici\";\nimport { createFrontend } from \"./frontend\";\nimport { requiredCapabilitiesFromTemplate } from \"./required_capabilities\";\nimport type { BlockConfigContainer } from \"@platforma-sdk/model\";\nimport { Code } from \"@platforma-sdk/model\";\nimport { loadPackDescription, RegistryV1 } from \"@platforma-sdk/block-tools\";\nimport type { BlockPackInfo } from \"../../model/block_pack\";\nimport { resolveDevPacket } from \"../../dev_env\";\nimport { getDevV2PacketMtime } from \"../../block_registry\";\nimport type { V2RegistryProvider } from \"../../block_registry/registry-v2-provider\";\nimport { LRUCache } from \"lru-cache\";\nimport canonicalize from \"canonicalize\";\nimport type { BlockPackSpec } from \"@milaboratories/pl-model-middle-layer\";\nimport { WorkerManager } from \"../../worker/WorkerManager\";\nimport { z } from \"zod\";\n\ntype PreparedCacheKey = Branded<string, \"PreparedCacheKey\">;\n\nexport const BlockPackCustomType: ResourceType = { name: \"BlockPackCustom\", version: \"1\" };\nexport const BlockPackTemplateField = \"template\";\nexport const BlockPackFrontendField = \"frontend\";\n\n/** Ensure trailing slash */\nfunction tSlash(str: string): string {\n if (str.endsWith(\"/\")) return str;\n else return `${str}/`;\n}\n\nfunction parseStringConfig(configContent: string): BlockConfigContainer {\n const res = z.record(z.string(), z.unknown()).safeParse(JSON.parse(configContent));\n\n if (!res.success) {\n throw new Error(\"Invalid config content\");\n }\n\n if (!Code.safeParse(res.data.code).success) {\n throw new Error(\"parseStringConfig:No code bundle\");\n }\n\n return res.data as BlockConfigContainer;\n}\n\nfunction parseBufferConfig(buffer: ArrayBuffer): BlockConfigContainer {\n return parseStringConfig(Buffer.from(buffer).toString(\"utf8\"));\n}\n\nexport class BlockPackPreparer {\n constructor(\n private readonly v2RegistryProvider: V2RegistryProvider,\n private readonly signer: Signer,\n private readonly http?: Dispatcher,\n ) {}\n\n private readonly remoteContentCache = new LRUCache<string, ArrayBuffer>({\n max: 500,\n maxSize: 128 * 1024 * 1024,\n fetchMethod: async (key) => {\n const httpOptions = this.http !== undefined ? { dispatcher: this.http } : {};\n return await (await request(key, httpOptions)).body.arrayBuffer();\n },\n sizeCalculation: (value) => value.byteLength,\n });\n\n /** Cache of prepared block packs for registry specs (immutable by version). */\n private readonly preparedCache = new LRUCache<PreparedCacheKey, BlockPackSpecPrepared>({\n max: 50,\n });\n\n public async getBlockConfigContainer(spec: BlockPackSpecAny): Promise<BlockConfigContainer> {\n switch (spec.type) {\n case \"explicit\":\n return spec.config;\n\n case \"prepared\":\n return spec.config;\n\n case \"dev-v1\": {\n const devPaths = await resolveDevPacket(spec.folder, false);\n const configContent = await fs.promises.readFile(devPaths.config, { encoding: \"utf-8\" });\n return JSON.parse(configContent);\n }\n\n case \"dev-v2\": {\n const description = await loadPackDescription(spec.folder);\n const configContent = await fs.promises.readFile(description.components.model.file, {\n encoding: \"utf-8\",\n });\n return parseStringConfig(configContent);\n }\n\n case \"from-registry-v1\": {\n const urlPrefix = `${tSlash(spec.registryUrl)}${RegistryV1.packageContentPrefix({ organization: spec.id.organization, package: spec.id.name, version: spec.id.version })}`;\n\n const configResponse = await this.remoteContentCache.forceFetch(`${urlPrefix}/config.json`);\n return JSON.parse(Buffer.from(configResponse).toString(\"utf8\"));\n }\n\n case \"from-registry-v2\": {\n const registry = this.v2RegistryProvider.getRegistry(spec.registryUrl);\n const components = await registry.getComponents(spec.id);\n const configResponse = await this.remoteContentCache.forceFetch(components.model.url);\n return parseBufferConfig(configResponse);\n }\n\n default:\n return assertNever(spec);\n }\n }\n\n /** Returns a stable cache key for registry specs (immutable by version). Dev specs return undefined. */\n private specKey(spec: BlockPackSpecAny): PreparedCacheKey | undefined {\n switch (spec.type) {\n case \"from-registry-v1\":\n return `v1:${spec.registryUrl}:${spec.id.organization}:${spec.id.name}:${spec.id.version}` as PreparedCacheKey;\n case \"from-registry-v2\":\n return `v2:${spec.registryUrl}:${canonicalize(spec.id)}` as PreparedCacheKey;\n default:\n return undefined; // dev, explicit, prepared — not cacheable\n }\n }\n\n public async prepare(spec: BlockPackSpecAny): Promise<BlockPackSpecPrepared> {\n if (spec.type === \"prepared\") {\n return spec;\n }\n\n // Check prepare cache for registry specs\n const key = this.specKey(spec);\n if (key) {\n const cached = this.preparedCache.get(key);\n if (cached) return cached;\n }\n\n const explicit = await this.prepareWithoutUnpacking(spec);\n\n await using workerManager = new WorkerManager();\n\n const parsed = await workerManager.process(\"parseTemplate\", explicit.template.content);\n\n const result: BlockPackSpecPrepared = {\n ...explicit,\n type: \"prepared\",\n template: {\n type: \"prepared\",\n data: parsed,\n },\n requiredCapabilities: requiredCapabilitiesFromTemplate(parsed),\n };\n\n if (key) {\n this.preparedCache.set(key, result);\n }\n\n return result;\n }\n\n private async prepareWithoutUnpacking(\n spec: BlockPackExplicit | BlockPackSpec,\n ): Promise<BlockPackExplicit> {\n switch (spec.type) {\n case \"explicit\":\n return spec;\n\n case \"dev-v1\": {\n const devPaths = await resolveDevPacket(spec.folder, false);\n\n // template\n const templateContent = await fs.promises.readFile(devPaths.workflow);\n\n // config\n const config = JSON.parse(await fs.promises.readFile(devPaths.config, \"utf-8\"));\n\n // frontend\n const frontendPath = devPaths.ui;\n\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: templateContent,\n },\n config,\n frontend: {\n type: \"local\",\n path: frontendPath,\n signature: this.signer.sign(frontendPath),\n },\n source: spec,\n };\n }\n\n case \"dev-v2\": {\n const description = await loadPackDescription(spec.folder);\n const config = parseStringConfig(\n await fs.promises.readFile(description.components.model.file, {\n encoding: \"utf-8\",\n }),\n );\n const workflowContent = await fs.promises.readFile(\n description.components.workflow.main.file,\n );\n const frontendPath = description.components.ui.folder;\n const source = { ...spec };\n if (spec.mtime === undefined)\n // if absent, calculating the mtime here, so the block will correctly show whether it can be updated\n source.mtime = await getDevV2PacketMtime(description);\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: workflowContent,\n },\n config,\n frontend: {\n type: \"local\",\n path: frontendPath,\n signature: this.signer.sign(frontendPath),\n },\n source,\n };\n }\n\n case \"from-registry-v1\": {\n const urlPrefix = `${tSlash(spec.registryUrl)}${RegistryV1.packageContentPrefix({ organization: spec.id.organization, package: spec.id.name, version: spec.id.version })}`;\n\n const templateUrl = `${urlPrefix}/template.plj.gz`;\n // template\n const templateResponse = await this.remoteContentCache.forceFetch(templateUrl);\n const templateContent = new Uint8Array(templateResponse);\n\n // config\n const configResponse = await this.remoteContentCache.forceFetch(`${urlPrefix}/config.json`);\n const config = JSON.parse(\n Buffer.from(configResponse).toString(\"utf8\"),\n ) as BlockConfigContainer;\n\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: templateContent,\n },\n config,\n frontend: {\n type: \"url\",\n url: `${urlPrefix}/frontend.tgz`,\n },\n source: spec,\n };\n }\n\n case \"from-registry-v2\": {\n const registry = this.v2RegistryProvider.getRegistry(spec.registryUrl);\n const components = await registry.getComponents(spec.id);\n const getModel = async () =>\n parseBufferConfig(await this.remoteContentCache.forceFetch(components.model.url));\n const getWorkflow = async () =>\n await this.remoteContentCache.forceFetch(components.workflow.main.url);\n\n const [model, workflow] = await Promise.all([getModel(), getWorkflow()]);\n const workflowContent = Buffer.from(workflow);\n\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: workflowContent,\n },\n config: model,\n frontend: {\n type: \"url\",\n url: components.ui.url,\n },\n source: spec,\n };\n }\n\n default:\n return assertNever(spec);\n }\n }\n}\n\nfunction createCustomBlockPack(tx: PlTransaction, spec: BlockPackSpecPrepared): AnyResourceRef {\n const blockPackInfo: BlockPackInfo = { config: spec.config, source: spec.source };\n const bp = tx.createStruct(BlockPackCustomType, JSON.stringify(blockPackInfo));\n tx.createField(field(bp, BlockPackTemplateField), \"Input\", loadTemplate(tx, spec.template));\n tx.createField(field(bp, BlockPackFrontendField), \"Input\", createFrontend(tx, spec.frontend));\n tx.lock(bp);\n\n return bp;\n}\n\nexport function createBlockPack(tx: PlTransaction, spec: BlockPackSpecPrepared): AnyResourceRef {\n switch (spec.type) {\n case \"prepared\":\n return createCustomBlockPack(tx, spec);\n default:\n return assertNever(spec.type);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block_pack.d.ts","names":[],"sources":["../../../src/mutator/block-pack/block_pack.ts"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"block_pack.d.ts","names":[],"sources":["../../../src/mutator/block-pack/block_pack.ts"],"mappings":";;;;;;;;cAuDa,iBAAA;EAAA,iBAEQ,kBAAA;EAAA,iBACA,MAAA;EAAA,iBACA,IAAA;cAFA,kBAAA,EAAoB,kBAAA,EACpB,MAAA,EAAQ,MAAA,EACR,IAAA,GAAO,UAAA;EAAA,iBAGT,kBAAA;EAe0B;EAAA,iBAJ1B,aAAA;EAIJ,uBAAA,CAAwB,IAAA,EAAM,gBAAA,GAAmB,OAAA,CAAQ,oBAAA;EAqD3C;EAAA,QAXnB,OAAA;EAWK,OAAA,CAAQ,IAAA,EAAM,gBAAA,GAAmB,OAAA,CAAQ,qBAAA;EAAA,QAmCxC,uBAAA;AAAA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveDevPacket } from "../../dev_env/index.js";
|
|
2
|
+
import { requiredCapabilitiesFromTemplate } from "./required_capabilities.js";
|
|
2
3
|
import { getDevV2PacketMtime } from "../../block_registry/registry.js";
|
|
3
4
|
import "../../block_registry/index.js";
|
|
4
5
|
import { loadTemplate } from "../template/template_loading.js";
|
|
@@ -98,14 +99,15 @@ var BlockPackPreparer = class {
|
|
|
98
99
|
if (cached) return cached;
|
|
99
100
|
}
|
|
100
101
|
const explicit = await this.prepareWithoutUnpacking(spec);
|
|
101
|
-
const
|
|
102
|
+
const parsed = await _usingCtx$1.a(new WorkerManager()).process("parseTemplate", explicit.template.content);
|
|
102
103
|
const result = {
|
|
103
104
|
...explicit,
|
|
104
105
|
type: "prepared",
|
|
105
106
|
template: {
|
|
106
107
|
type: "prepared",
|
|
107
|
-
data:
|
|
108
|
-
}
|
|
108
|
+
data: parsed
|
|
109
|
+
},
|
|
110
|
+
requiredCapabilities: requiredCapabilitiesFromTemplate(parsed)
|
|
109
111
|
};
|
|
110
112
|
if (key) this.preparedCache.set(key, result);
|
|
111
113
|
return result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block_pack.js","names":[],"sources":["../../../src/mutator/block-pack/block_pack.ts"],"sourcesContent":["import type { AnyResourceRef, PlTransaction, ResourceType } from \"@milaboratories/pl-client\";\nimport { field } from \"@milaboratories/pl-client\";\nimport { loadTemplate } from \"../template/template_loading\";\nimport type { BlockPackExplicit, BlockPackSpecAny, BlockPackSpecPrepared } from \"../../model\";\nimport type { Signer } from \"@milaboratories/ts-helpers\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport type { Branded } from \"@milaboratories/pl-model-common\";\nimport fs from \"node:fs\";\nimport type { Dispatcher } from \"undici\";\nimport { request } from \"undici\";\nimport { createFrontend } from \"./frontend\";\nimport type { BlockConfigContainer } from \"@platforma-sdk/model\";\nimport { Code } from \"@platforma-sdk/model\";\nimport { loadPackDescription, RegistryV1 } from \"@platforma-sdk/block-tools\";\nimport type { BlockPackInfo } from \"../../model/block_pack\";\nimport { resolveDevPacket } from \"../../dev_env\";\nimport { getDevV2PacketMtime } from \"../../block_registry\";\nimport type { V2RegistryProvider } from \"../../block_registry/registry-v2-provider\";\nimport { LRUCache } from \"lru-cache\";\nimport canonicalize from \"canonicalize\";\nimport type { BlockPackSpec } from \"@milaboratories/pl-model-middle-layer\";\nimport { WorkerManager } from \"../../worker/WorkerManager\";\nimport { z } from \"zod\";\n\ntype PreparedCacheKey = Branded<string, \"PreparedCacheKey\">;\n\nexport const BlockPackCustomType: ResourceType = { name: \"BlockPackCustom\", version: \"1\" };\nexport const BlockPackTemplateField = \"template\";\nexport const BlockPackFrontendField = \"frontend\";\n\n/** Ensure trailing slash */\nfunction tSlash(str: string): string {\n if (str.endsWith(\"/\")) return str;\n else return `${str}/`;\n}\n\nfunction parseStringConfig(configContent: string): BlockConfigContainer {\n const res = z.record(z.string(), z.unknown()).safeParse(JSON.parse(configContent));\n\n if (!res.success) {\n throw new Error(\"Invalid config content\");\n }\n\n if (!Code.safeParse(res.data.code).success) {\n throw new Error(\"parseStringConfig:No code bundle\");\n }\n\n return res.data as BlockConfigContainer;\n}\n\nfunction parseBufferConfig(buffer: ArrayBuffer): BlockConfigContainer {\n return parseStringConfig(Buffer.from(buffer).toString(\"utf8\"));\n}\n\nexport class BlockPackPreparer {\n constructor(\n private readonly v2RegistryProvider: V2RegistryProvider,\n private readonly signer: Signer,\n private readonly http?: Dispatcher,\n ) {}\n\n private readonly remoteContentCache = new LRUCache<string, ArrayBuffer>({\n max: 500,\n maxSize: 128 * 1024 * 1024,\n fetchMethod: async (key) => {\n const httpOptions = this.http !== undefined ? { dispatcher: this.http } : {};\n return await (await request(key, httpOptions)).body.arrayBuffer();\n },\n sizeCalculation: (value) => value.byteLength,\n });\n\n /** Cache of prepared block packs for registry specs (immutable by version). */\n private readonly preparedCache = new LRUCache<PreparedCacheKey, BlockPackSpecPrepared>({\n max: 50,\n });\n\n public async getBlockConfigContainer(spec: BlockPackSpecAny): Promise<BlockConfigContainer> {\n switch (spec.type) {\n case \"explicit\":\n return spec.config;\n\n case \"prepared\":\n return spec.config;\n\n case \"dev-v1\": {\n const devPaths = await resolveDevPacket(spec.folder, false);\n const configContent = await fs.promises.readFile(devPaths.config, { encoding: \"utf-8\" });\n return JSON.parse(configContent);\n }\n\n case \"dev-v2\": {\n const description = await loadPackDescription(spec.folder);\n const configContent = await fs.promises.readFile(description.components.model.file, {\n encoding: \"utf-8\",\n });\n return parseStringConfig(configContent);\n }\n\n case \"from-registry-v1\": {\n const urlPrefix = `${tSlash(spec.registryUrl)}${RegistryV1.packageContentPrefix({ organization: spec.id.organization, package: spec.id.name, version: spec.id.version })}`;\n\n const configResponse = await this.remoteContentCache.forceFetch(`${urlPrefix}/config.json`);\n return JSON.parse(Buffer.from(configResponse).toString(\"utf8\"));\n }\n\n case \"from-registry-v2\": {\n const registry = this.v2RegistryProvider.getRegistry(spec.registryUrl);\n const components = await registry.getComponents(spec.id);\n const configResponse = await this.remoteContentCache.forceFetch(components.model.url);\n return parseBufferConfig(configResponse);\n }\n\n default:\n return assertNever(spec);\n }\n }\n\n /** Returns a stable cache key for registry specs (immutable by version). Dev specs return undefined. */\n private specKey(spec: BlockPackSpecAny): PreparedCacheKey | undefined {\n switch (spec.type) {\n case \"from-registry-v1\":\n return `v1:${spec.registryUrl}:${spec.id.organization}:${spec.id.name}:${spec.id.version}` as PreparedCacheKey;\n case \"from-registry-v2\":\n return `v2:${spec.registryUrl}:${canonicalize(spec.id)}` as PreparedCacheKey;\n default:\n return undefined; // dev, explicit, prepared — not cacheable\n }\n }\n\n public async prepare(spec: BlockPackSpecAny): Promise<BlockPackSpecPrepared> {\n if (spec.type === \"prepared\") {\n return spec;\n }\n\n // Check prepare cache for registry specs\n const key = this.specKey(spec);\n if (key) {\n const cached = this.preparedCache.get(key);\n if (cached) return cached;\n }\n\n const explicit = await this.prepareWithoutUnpacking(spec);\n\n await using workerManager = new WorkerManager();\n\n const result: BlockPackSpecPrepared = {\n ...explicit,\n type: \"prepared\",\n template: {\n type: \"prepared\",\n data: await workerManager.process(\"parseTemplate\", explicit.template.content),\n },\n };\n\n if (key) {\n this.preparedCache.set(key, result);\n }\n\n return result;\n }\n\n private async prepareWithoutUnpacking(\n spec: BlockPackExplicit | BlockPackSpec,\n ): Promise<BlockPackExplicit> {\n switch (spec.type) {\n case \"explicit\":\n return spec;\n\n case \"dev-v1\": {\n const devPaths = await resolveDevPacket(spec.folder, false);\n\n // template\n const templateContent = await fs.promises.readFile(devPaths.workflow);\n\n // config\n const config = JSON.parse(await fs.promises.readFile(devPaths.config, \"utf-8\"));\n\n // frontend\n const frontendPath = devPaths.ui;\n\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: templateContent,\n },\n config,\n frontend: {\n type: \"local\",\n path: frontendPath,\n signature: this.signer.sign(frontendPath),\n },\n source: spec,\n };\n }\n\n case \"dev-v2\": {\n const description = await loadPackDescription(spec.folder);\n const config = parseStringConfig(\n await fs.promises.readFile(description.components.model.file, {\n encoding: \"utf-8\",\n }),\n );\n const workflowContent = await fs.promises.readFile(\n description.components.workflow.main.file,\n );\n const frontendPath = description.components.ui.folder;\n const source = { ...spec };\n if (spec.mtime === undefined)\n // if absent, calculating the mtime here, so the block will correctly show whether it can be updated\n source.mtime = await getDevV2PacketMtime(description);\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: workflowContent,\n },\n config,\n frontend: {\n type: \"local\",\n path: frontendPath,\n signature: this.signer.sign(frontendPath),\n },\n source,\n };\n }\n\n case \"from-registry-v1\": {\n const urlPrefix = `${tSlash(spec.registryUrl)}${RegistryV1.packageContentPrefix({ organization: spec.id.organization, package: spec.id.name, version: spec.id.version })}`;\n\n const templateUrl = `${urlPrefix}/template.plj.gz`;\n // template\n const templateResponse = await this.remoteContentCache.forceFetch(templateUrl);\n const templateContent = new Uint8Array(templateResponse);\n\n // config\n const configResponse = await this.remoteContentCache.forceFetch(`${urlPrefix}/config.json`);\n const config = JSON.parse(\n Buffer.from(configResponse).toString(\"utf8\"),\n ) as BlockConfigContainer;\n\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: templateContent,\n },\n config,\n frontend: {\n type: \"url\",\n url: `${urlPrefix}/frontend.tgz`,\n },\n source: spec,\n };\n }\n\n case \"from-registry-v2\": {\n const registry = this.v2RegistryProvider.getRegistry(spec.registryUrl);\n const components = await registry.getComponents(spec.id);\n const getModel = async () =>\n parseBufferConfig(await this.remoteContentCache.forceFetch(components.model.url));\n const getWorkflow = async () =>\n await this.remoteContentCache.forceFetch(components.workflow.main.url);\n\n const [model, workflow] = await Promise.all([getModel(), getWorkflow()]);\n\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: Buffer.from(workflow),\n },\n config: model,\n frontend: {\n type: \"url\",\n url: components.ui.url,\n },\n source: spec,\n };\n }\n\n default:\n return assertNever(spec);\n }\n }\n}\n\nfunction createCustomBlockPack(tx: PlTransaction, spec: BlockPackSpecPrepared): AnyResourceRef {\n const blockPackInfo: BlockPackInfo = { config: spec.config, source: spec.source };\n const bp = tx.createStruct(BlockPackCustomType, JSON.stringify(blockPackInfo));\n tx.createField(field(bp, BlockPackTemplateField), \"Input\", loadTemplate(tx, spec.template));\n tx.createField(field(bp, BlockPackFrontendField), \"Input\", createFrontend(tx, spec.frontend));\n tx.lock(bp);\n\n return bp;\n}\n\nexport function createBlockPack(tx: PlTransaction, spec: BlockPackSpecPrepared): AnyResourceRef {\n switch (spec.type) {\n case \"prepared\":\n return createCustomBlockPack(tx, spec);\n default:\n return assertNever(spec.type);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"block_pack.js","names":[],"sources":["../../../src/mutator/block-pack/block_pack.ts"],"sourcesContent":["import type { AnyResourceRef, PlTransaction, ResourceType } from \"@milaboratories/pl-client\";\nimport { field } from \"@milaboratories/pl-client\";\nimport { loadTemplate } from \"../template/template_loading\";\nimport type { BlockPackExplicit, BlockPackSpecAny, BlockPackSpecPrepared } from \"../../model\";\nimport type { Signer } from \"@milaboratories/ts-helpers\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport type { Branded } from \"@milaboratories/pl-model-common\";\nimport fs from \"node:fs\";\nimport type { Dispatcher } from \"undici\";\nimport { request } from \"undici\";\nimport { createFrontend } from \"./frontend\";\nimport { requiredCapabilitiesFromTemplate } from \"./required_capabilities\";\nimport type { BlockConfigContainer } from \"@platforma-sdk/model\";\nimport { Code } from \"@platforma-sdk/model\";\nimport { loadPackDescription, RegistryV1 } from \"@platforma-sdk/block-tools\";\nimport type { BlockPackInfo } from \"../../model/block_pack\";\nimport { resolveDevPacket } from \"../../dev_env\";\nimport { getDevV2PacketMtime } from \"../../block_registry\";\nimport type { V2RegistryProvider } from \"../../block_registry/registry-v2-provider\";\nimport { LRUCache } from \"lru-cache\";\nimport canonicalize from \"canonicalize\";\nimport type { BlockPackSpec } from \"@milaboratories/pl-model-middle-layer\";\nimport { WorkerManager } from \"../../worker/WorkerManager\";\nimport { z } from \"zod\";\n\ntype PreparedCacheKey = Branded<string, \"PreparedCacheKey\">;\n\nexport const BlockPackCustomType: ResourceType = { name: \"BlockPackCustom\", version: \"1\" };\nexport const BlockPackTemplateField = \"template\";\nexport const BlockPackFrontendField = \"frontend\";\n\n/** Ensure trailing slash */\nfunction tSlash(str: string): string {\n if (str.endsWith(\"/\")) return str;\n else return `${str}/`;\n}\n\nfunction parseStringConfig(configContent: string): BlockConfigContainer {\n const res = z.record(z.string(), z.unknown()).safeParse(JSON.parse(configContent));\n\n if (!res.success) {\n throw new Error(\"Invalid config content\");\n }\n\n if (!Code.safeParse(res.data.code).success) {\n throw new Error(\"parseStringConfig:No code bundle\");\n }\n\n return res.data as BlockConfigContainer;\n}\n\nfunction parseBufferConfig(buffer: ArrayBuffer): BlockConfigContainer {\n return parseStringConfig(Buffer.from(buffer).toString(\"utf8\"));\n}\n\nexport class BlockPackPreparer {\n constructor(\n private readonly v2RegistryProvider: V2RegistryProvider,\n private readonly signer: Signer,\n private readonly http?: Dispatcher,\n ) {}\n\n private readonly remoteContentCache = new LRUCache<string, ArrayBuffer>({\n max: 500,\n maxSize: 128 * 1024 * 1024,\n fetchMethod: async (key) => {\n const httpOptions = this.http !== undefined ? { dispatcher: this.http } : {};\n return await (await request(key, httpOptions)).body.arrayBuffer();\n },\n sizeCalculation: (value) => value.byteLength,\n });\n\n /** Cache of prepared block packs for registry specs (immutable by version). */\n private readonly preparedCache = new LRUCache<PreparedCacheKey, BlockPackSpecPrepared>({\n max: 50,\n });\n\n public async getBlockConfigContainer(spec: BlockPackSpecAny): Promise<BlockConfigContainer> {\n switch (spec.type) {\n case \"explicit\":\n return spec.config;\n\n case \"prepared\":\n return spec.config;\n\n case \"dev-v1\": {\n const devPaths = await resolveDevPacket(spec.folder, false);\n const configContent = await fs.promises.readFile(devPaths.config, { encoding: \"utf-8\" });\n return JSON.parse(configContent);\n }\n\n case \"dev-v2\": {\n const description = await loadPackDescription(spec.folder);\n const configContent = await fs.promises.readFile(description.components.model.file, {\n encoding: \"utf-8\",\n });\n return parseStringConfig(configContent);\n }\n\n case \"from-registry-v1\": {\n const urlPrefix = `${tSlash(spec.registryUrl)}${RegistryV1.packageContentPrefix({ organization: spec.id.organization, package: spec.id.name, version: spec.id.version })}`;\n\n const configResponse = await this.remoteContentCache.forceFetch(`${urlPrefix}/config.json`);\n return JSON.parse(Buffer.from(configResponse).toString(\"utf8\"));\n }\n\n case \"from-registry-v2\": {\n const registry = this.v2RegistryProvider.getRegistry(spec.registryUrl);\n const components = await registry.getComponents(spec.id);\n const configResponse = await this.remoteContentCache.forceFetch(components.model.url);\n return parseBufferConfig(configResponse);\n }\n\n default:\n return assertNever(spec);\n }\n }\n\n /** Returns a stable cache key for registry specs (immutable by version). Dev specs return undefined. */\n private specKey(spec: BlockPackSpecAny): PreparedCacheKey | undefined {\n switch (spec.type) {\n case \"from-registry-v1\":\n return `v1:${spec.registryUrl}:${spec.id.organization}:${spec.id.name}:${spec.id.version}` as PreparedCacheKey;\n case \"from-registry-v2\":\n return `v2:${spec.registryUrl}:${canonicalize(spec.id)}` as PreparedCacheKey;\n default:\n return undefined; // dev, explicit, prepared — not cacheable\n }\n }\n\n public async prepare(spec: BlockPackSpecAny): Promise<BlockPackSpecPrepared> {\n if (spec.type === \"prepared\") {\n return spec;\n }\n\n // Check prepare cache for registry specs\n const key = this.specKey(spec);\n if (key) {\n const cached = this.preparedCache.get(key);\n if (cached) return cached;\n }\n\n const explicit = await this.prepareWithoutUnpacking(spec);\n\n await using workerManager = new WorkerManager();\n\n const parsed = await workerManager.process(\"parseTemplate\", explicit.template.content);\n\n const result: BlockPackSpecPrepared = {\n ...explicit,\n type: \"prepared\",\n template: {\n type: \"prepared\",\n data: parsed,\n },\n requiredCapabilities: requiredCapabilitiesFromTemplate(parsed),\n };\n\n if (key) {\n this.preparedCache.set(key, result);\n }\n\n return result;\n }\n\n private async prepareWithoutUnpacking(\n spec: BlockPackExplicit | BlockPackSpec,\n ): Promise<BlockPackExplicit> {\n switch (spec.type) {\n case \"explicit\":\n return spec;\n\n case \"dev-v1\": {\n const devPaths = await resolveDevPacket(spec.folder, false);\n\n // template\n const templateContent = await fs.promises.readFile(devPaths.workflow);\n\n // config\n const config = JSON.parse(await fs.promises.readFile(devPaths.config, \"utf-8\"));\n\n // frontend\n const frontendPath = devPaths.ui;\n\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: templateContent,\n },\n config,\n frontend: {\n type: \"local\",\n path: frontendPath,\n signature: this.signer.sign(frontendPath),\n },\n source: spec,\n };\n }\n\n case \"dev-v2\": {\n const description = await loadPackDescription(spec.folder);\n const config = parseStringConfig(\n await fs.promises.readFile(description.components.model.file, {\n encoding: \"utf-8\",\n }),\n );\n const workflowContent = await fs.promises.readFile(\n description.components.workflow.main.file,\n );\n const frontendPath = description.components.ui.folder;\n const source = { ...spec };\n if (spec.mtime === undefined)\n // if absent, calculating the mtime here, so the block will correctly show whether it can be updated\n source.mtime = await getDevV2PacketMtime(description);\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: workflowContent,\n },\n config,\n frontend: {\n type: \"local\",\n path: frontendPath,\n signature: this.signer.sign(frontendPath),\n },\n source,\n };\n }\n\n case \"from-registry-v1\": {\n const urlPrefix = `${tSlash(spec.registryUrl)}${RegistryV1.packageContentPrefix({ organization: spec.id.organization, package: spec.id.name, version: spec.id.version })}`;\n\n const templateUrl = `${urlPrefix}/template.plj.gz`;\n // template\n const templateResponse = await this.remoteContentCache.forceFetch(templateUrl);\n const templateContent = new Uint8Array(templateResponse);\n\n // config\n const configResponse = await this.remoteContentCache.forceFetch(`${urlPrefix}/config.json`);\n const config = JSON.parse(\n Buffer.from(configResponse).toString(\"utf8\"),\n ) as BlockConfigContainer;\n\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: templateContent,\n },\n config,\n frontend: {\n type: \"url\",\n url: `${urlPrefix}/frontend.tgz`,\n },\n source: spec,\n };\n }\n\n case \"from-registry-v2\": {\n const registry = this.v2RegistryProvider.getRegistry(spec.registryUrl);\n const components = await registry.getComponents(spec.id);\n const getModel = async () =>\n parseBufferConfig(await this.remoteContentCache.forceFetch(components.model.url));\n const getWorkflow = async () =>\n await this.remoteContentCache.forceFetch(components.workflow.main.url);\n\n const [model, workflow] = await Promise.all([getModel(), getWorkflow()]);\n const workflowContent = Buffer.from(workflow);\n\n return {\n type: \"explicit\",\n template: {\n type: \"explicit\",\n content: workflowContent,\n },\n config: model,\n frontend: {\n type: \"url\",\n url: components.ui.url,\n },\n source: spec,\n };\n }\n\n default:\n return assertNever(spec);\n }\n }\n}\n\nfunction createCustomBlockPack(tx: PlTransaction, spec: BlockPackSpecPrepared): AnyResourceRef {\n const blockPackInfo: BlockPackInfo = { config: spec.config, source: spec.source };\n const bp = tx.createStruct(BlockPackCustomType, JSON.stringify(blockPackInfo));\n tx.createField(field(bp, BlockPackTemplateField), \"Input\", loadTemplate(tx, spec.template));\n tx.createField(field(bp, BlockPackFrontendField), \"Input\", createFrontend(tx, spec.frontend));\n tx.lock(bp);\n\n return bp;\n}\n\nexport function createBlockPack(tx: PlTransaction, spec: BlockPackSpecPrepared): AnyResourceRef {\n switch (spec.type) {\n case \"prepared\":\n return createCustomBlockPack(tx, spec);\n default:\n return assertNever(spec.type);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA2BA,MAAa,sBAAoC;CAAE,MAAM;CAAmB,SAAS;CAAK;AAC1F,MAAa,yBAAyB;AACtC,MAAa,yBAAyB;;AAGtC,SAAS,OAAO,KAAqB;AACnC,KAAI,IAAI,SAAS,IAAI,CAAE,QAAO;KACzB,QAAO,GAAG,IAAI;;AAGrB,SAAS,kBAAkB,eAA6C;CACtE,MAAM,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,SAAS,CAAC,CAAC,UAAU,KAAK,MAAM,cAAc,CAAC;AAElF,KAAI,CAAC,IAAI,QACP,OAAM,IAAI,MAAM,yBAAyB;AAG3C,KAAI,CAAC,KAAK,UAAU,IAAI,KAAK,KAAK,CAAC,QACjC,OAAM,IAAI,MAAM,mCAAmC;AAGrD,QAAO,IAAI;;AAGb,SAAS,kBAAkB,QAA2C;AACpE,QAAO,kBAAkB,OAAO,KAAK,OAAO,CAAC,SAAS,OAAO,CAAC;;AAGhE,IAAa,oBAAb,MAA+B;CAC7B,YACE,oBACA,QACA,MACA;AAHiB,OAAA,qBAAA;AACA,OAAA,SAAA;AACA,OAAA,OAAA;;CAGnB,qBAAsC,IAAI,SAA8B;EACtE,KAAK;EACL,SAAS,MAAM,OAAO;EACtB,aAAa,OAAO,QAAQ;AAE1B,UAAO,OAAO,MAAM,QAAQ,KADR,KAAK,SAAS,KAAA,IAAY,EAAE,YAAY,KAAK,MAAM,GAAG,EAAE,CAC/B,EAAE,KAAK,aAAa;;EAEnE,kBAAkB,UAAU,MAAM;EACnC,CAAC;;CAGF,gBAAiC,IAAI,SAAkD,EACrF,KAAK,IACN,CAAC;CAEF,MAAa,wBAAwB,MAAuD;AAC1F,UAAQ,KAAK,MAAb;GACE,KAAK,WACH,QAAO,KAAK;GAEd,KAAK,WACH,QAAO,KAAK;GAEd,KAAK,UAAU;IACb,MAAM,WAAW,MAAM,iBAAiB,KAAK,QAAQ,MAAM;IAC3D,MAAM,gBAAgB,MAAM,GAAG,SAAS,SAAS,SAAS,QAAQ,EAAE,UAAU,SAAS,CAAC;AACxF,WAAO,KAAK,MAAM,cAAc;;GAGlC,KAAK,UAAU;IACb,MAAM,cAAc,MAAM,oBAAoB,KAAK,OAAO;AAI1D,WAAO,kBAHe,MAAM,GAAG,SAAS,SAAS,YAAY,WAAW,MAAM,MAAM,EAClF,UAAU,SACX,CAAC,CACqC;;GAGzC,KAAK,oBAAoB;IACvB,MAAM,YAAY,GAAG,OAAO,KAAK,YAAY,GAAG,WAAW,qBAAqB;KAAE,cAAc,KAAK,GAAG;KAAc,SAAS,KAAK,GAAG;KAAM,SAAS,KAAK,GAAG;KAAS,CAAC;IAExK,MAAM,iBAAiB,MAAM,KAAK,mBAAmB,WAAW,GAAG,UAAU,cAAc;AAC3F,WAAO,KAAK,MAAM,OAAO,KAAK,eAAe,CAAC,SAAS,OAAO,CAAC;;GAGjE,KAAK,oBAAoB;IAEvB,MAAM,aAAa,MADF,KAAK,mBAAmB,YAAY,KAAK,YAAY,CACpC,cAAc,KAAK,GAAG;AAExD,WAAO,kBADgB,MAAM,KAAK,mBAAmB,WAAW,WAAW,MAAM,IAAI,CAC7C;;GAG1C,QACE,QAAO,YAAY,KAAK;;;;CAK9B,QAAgB,MAAsD;AACpE,UAAQ,KAAK,MAAb;GACE,KAAK,mBACH,QAAO,MAAM,KAAK,YAAY,GAAG,KAAK,GAAG,aAAa,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG;GACnF,KAAK,mBACH,QAAO,MAAM,KAAK,YAAY,GAAG,aAAa,KAAK,GAAG;GACxD,QACE;;;CAIN,MAAa,QAAQ,MAAwD;;;AAC3E,OAAI,KAAK,SAAS,WAChB,QAAO;GAIT,MAAM,MAAM,KAAK,QAAQ,KAAK;AAC9B,OAAI,KAAK;IACP,MAAM,SAAS,KAAK,cAAc,IAAI,IAAI;AAC1C,QAAI,OAAQ,QAAO;;GAGrB,MAAM,WAAW,MAAM,KAAK,wBAAwB,KAAK;GAIzD,MAAM,SAAS,MAAA,YAAA,EAFa,IAAI,eAAe,CAAA,CAEZ,QAAQ,iBAAiB,SAAS,SAAS,QAAQ;GAEtF,MAAM,SAAgC;IACpC,GAAG;IACH,MAAM;IACN,UAAU;KACR,MAAM;KACN,MAAM;KACP;IACD,sBAAsB,iCAAiC,OAAO;IAC/D;AAED,OAAI,IACF,MAAK,cAAc,IAAI,KAAK,OAAO;AAGrC,UAAO;;;;;;;CAGT,MAAc,wBACZ,MAC4B;AAC5B,UAAQ,KAAK,MAAb;GACE,KAAK,WACH,QAAO;GAET,KAAK,UAAU;IACb,MAAM,WAAW,MAAM,iBAAiB,KAAK,QAAQ,MAAM;IAG3D,MAAM,kBAAkB,MAAM,GAAG,SAAS,SAAS,SAAS,SAAS;IAGrE,MAAM,SAAS,KAAK,MAAM,MAAM,GAAG,SAAS,SAAS,SAAS,QAAQ,QAAQ,CAAC;IAG/E,MAAM,eAAe,SAAS;AAE9B,WAAO;KACL,MAAM;KACN,UAAU;MACR,MAAM;MACN,SAAS;MACV;KACD;KACA,UAAU;MACR,MAAM;MACN,MAAM;MACN,WAAW,KAAK,OAAO,KAAK,aAAa;MAC1C;KACD,QAAQ;KACT;;GAGH,KAAK,UAAU;IACb,MAAM,cAAc,MAAM,oBAAoB,KAAK,OAAO;IAC1D,MAAM,SAAS,kBACb,MAAM,GAAG,SAAS,SAAS,YAAY,WAAW,MAAM,MAAM,EAC5D,UAAU,SACX,CAAC,CACH;IACD,MAAM,kBAAkB,MAAM,GAAG,SAAS,SACxC,YAAY,WAAW,SAAS,KAAK,KACtC;IACD,MAAM,eAAe,YAAY,WAAW,GAAG;IAC/C,MAAM,SAAS,EAAE,GAAG,MAAM;AAC1B,QAAI,KAAK,UAAU,KAAA,EAEjB,QAAO,QAAQ,MAAM,oBAAoB,YAAY;AACvD,WAAO;KACL,MAAM;KACN,UAAU;MACR,MAAM;MACN,SAAS;MACV;KACD;KACA,UAAU;MACR,MAAM;MACN,MAAM;MACN,WAAW,KAAK,OAAO,KAAK,aAAa;MAC1C;KACD;KACD;;GAGH,KAAK,oBAAoB;IACvB,MAAM,YAAY,GAAG,OAAO,KAAK,YAAY,GAAG,WAAW,qBAAqB;KAAE,cAAc,KAAK,GAAG;KAAc,SAAS,KAAK,GAAG;KAAM,SAAS,KAAK,GAAG;KAAS,CAAC;IAExK,MAAM,cAAc,GAAG,UAAU;IAEjC,MAAM,mBAAmB,MAAM,KAAK,mBAAmB,WAAW,YAAY;IAC9E,MAAM,kBAAkB,IAAI,WAAW,iBAAiB;IAGxD,MAAM,iBAAiB,MAAM,KAAK,mBAAmB,WAAW,GAAG,UAAU,cAAc;IAC3F,MAAM,SAAS,KAAK,MAClB,OAAO,KAAK,eAAe,CAAC,SAAS,OAAO,CAC7C;AAED,WAAO;KACL,MAAM;KACN,UAAU;MACR,MAAM;MACN,SAAS;MACV;KACD;KACA,UAAU;MACR,MAAM;MACN,KAAK,GAAG,UAAU;MACnB;KACD,QAAQ;KACT;;GAGH,KAAK,oBAAoB;IAEvB,MAAM,aAAa,MADF,KAAK,mBAAmB,YAAY,KAAK,YAAY,CACpC,cAAc,KAAK,GAAG;IACxD,MAAM,WAAW,YACf,kBAAkB,MAAM,KAAK,mBAAmB,WAAW,WAAW,MAAM,IAAI,CAAC;IACnF,MAAM,cAAc,YAClB,MAAM,KAAK,mBAAmB,WAAW,WAAW,SAAS,KAAK,IAAI;IAExE,MAAM,CAAC,OAAO,YAAY,MAAM,QAAQ,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAGxE,WAAO;KACL,MAAM;KACN,UAAU;MACR,MAAM;MACN,SANoB,OAAO,KAAK,SAAS;MAO1C;KACD,QAAQ;KACR,UAAU;MACR,MAAM;MACN,KAAK,WAAW,GAAG;MACpB;KACD,QAAQ;KACT;;GAGH,QACE,QAAO,YAAY,KAAK;;;;AAKhC,SAAS,sBAAsB,IAAmB,MAA6C;CAC7F,MAAM,gBAA+B;EAAE,QAAQ,KAAK;EAAQ,QAAQ,KAAK;EAAQ;CACjF,MAAM,KAAK,GAAG,aAAa,qBAAqB,KAAK,UAAU,cAAc,CAAC;AAC9E,IAAG,YAAY,MAAM,IAAI,uBAAuB,EAAE,SAAS,aAAa,IAAI,KAAK,SAAS,CAAC;AAC3F,IAAG,YAAY,MAAM,IAAI,uBAAuB,EAAE,SAAS,eAAe,IAAI,KAAK,SAAS,CAAC;AAC7F,IAAG,KAAK,GAAG;AAEX,QAAO;;AAGT,SAAgB,gBAAgB,IAAmB,MAA6C;AAC9F,SAAQ,KAAK,MAAb;EACE,KAAK,WACH,QAAO,sBAAsB,IAAI,KAAK;EACxC,QACE,QAAO,YAAY,KAAK,KAAK"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require("../../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
let node_zlib = require("node:zlib");
|
|
3
|
+
//#region src/mutator/block-pack/required_capabilities.ts
|
|
4
|
+
/**
|
|
5
|
+
* Reads the capability tokens a template declares it requires via
|
|
6
|
+
* `TemplateDataV3.requiredCapabilities` (populated by `tengo-builder` at
|
|
7
|
+
* compile time).
|
|
8
|
+
*
|
|
9
|
+
* Use this on the install path: `BlockPackPreparer.prepare` parses the
|
|
10
|
+
* workflow off-thread via the worker, and the parsed result feeds
|
|
11
|
+
* straight into this function — no second gunzip+JSON.parse on the main
|
|
12
|
+
* thread, no recursive walk to re-derive what the compiler already wrote
|
|
13
|
+
* down.
|
|
14
|
+
*
|
|
15
|
+
* Returns `undefined` for v2 templates (no compile-time capability
|
|
16
|
+
* field) and for v3 templates that declare no requirements; otherwise
|
|
17
|
+
* the array as the compiler wrote it.
|
|
18
|
+
*/
|
|
19
|
+
function requiredCapabilitiesFromTemplate(parsed) {
|
|
20
|
+
if (parsed.type !== "pl.tengo-template.v3") return void 0;
|
|
21
|
+
return parsed.template.requiredCapabilities;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Same lookup, starting from raw `main.plj.gz` bytes. Used only at
|
|
25
|
+
* catalog-listing time for local-dev blocks where the worker pipeline
|
|
26
|
+
* isn't in play (`block_registry/registry.ts`). Install paths go through
|
|
27
|
+
* `requiredCapabilitiesFromTemplate` instead to avoid parsing the
|
|
28
|
+
* workflow twice.
|
|
29
|
+
*/
|
|
30
|
+
function deriveRequiredCapabilities(workflowContent) {
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
const json = (0, node_zlib.gunzipSync)(workflowContent).toString("utf-8");
|
|
34
|
+
parsed = JSON.parse(json);
|
|
35
|
+
} catch {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
return requiredCapabilitiesFromTemplate(parsed);
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
exports.deriveRequiredCapabilities = deriveRequiredCapabilities;
|
|
42
|
+
exports.requiredCapabilitiesFromTemplate = requiredCapabilitiesFromTemplate;
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=required_capabilities.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"required_capabilities.cjs","names":[],"sources":["../../../src/mutator/block-pack/required_capabilities.ts"],"sourcesContent":["import { gunzipSync } from \"node:zlib\";\nimport type { CompiledTemplateV3, TemplateData } from \"@milaboratories/pl-model-backend\";\n\n/**\n * Reads the capability tokens a template declares it requires via\n * `TemplateDataV3.requiredCapabilities` (populated by `tengo-builder` at\n * compile time).\n *\n * Use this on the install path: `BlockPackPreparer.prepare` parses the\n * workflow off-thread via the worker, and the parsed result feeds\n * straight into this function — no second gunzip+JSON.parse on the main\n * thread, no recursive walk to re-derive what the compiler already wrote\n * down.\n *\n * Returns `undefined` for v2 templates (no compile-time capability\n * field) and for v3 templates that declare no requirements; otherwise\n * the array as the compiler wrote it.\n */\nexport function requiredCapabilitiesFromTemplate(\n parsed: TemplateData | CompiledTemplateV3,\n): string[] | undefined {\n if (parsed.type !== \"pl.tengo-template.v3\") return undefined;\n return parsed.template.requiredCapabilities;\n}\n\n/**\n * Same lookup, starting from raw `main.plj.gz` bytes. Used only at\n * catalog-listing time for local-dev blocks where the worker pipeline\n * isn't in play (`block_registry/registry.ts`). Install paths go through\n * `requiredCapabilitiesFromTemplate` instead to avoid parsing the\n * workflow twice.\n */\nexport function deriveRequiredCapabilities(\n workflowContent: Uint8Array | Buffer,\n): string[] | undefined {\n let parsed: unknown;\n try {\n const json = gunzipSync(workflowContent).toString(\"utf-8\");\n parsed = JSON.parse(json);\n } catch {\n return undefined;\n }\n return requiredCapabilitiesFromTemplate(parsed as TemplateData | CompiledTemplateV3);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAAgB,iCACd,QACsB;AACtB,KAAI,OAAO,SAAS,uBAAwB,QAAO,KAAA;AACnD,QAAO,OAAO,SAAS;;;;;;;;;AAUzB,SAAgB,2BACd,iBACsB;CACtB,IAAI;AACJ,KAAI;EACF,MAAM,QAAA,GAAA,UAAA,YAAkB,gBAAgB,CAAC,SAAS,QAAQ;AAC1D,WAAS,KAAK,MAAM,KAAK;SACnB;AACN;;AAEF,QAAO,iCAAiC,OAA4C"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { gunzipSync } from "node:zlib";
|
|
2
|
+
//#region src/mutator/block-pack/required_capabilities.ts
|
|
3
|
+
/**
|
|
4
|
+
* Reads the capability tokens a template declares it requires via
|
|
5
|
+
* `TemplateDataV3.requiredCapabilities` (populated by `tengo-builder` at
|
|
6
|
+
* compile time).
|
|
7
|
+
*
|
|
8
|
+
* Use this on the install path: `BlockPackPreparer.prepare` parses the
|
|
9
|
+
* workflow off-thread via the worker, and the parsed result feeds
|
|
10
|
+
* straight into this function — no second gunzip+JSON.parse on the main
|
|
11
|
+
* thread, no recursive walk to re-derive what the compiler already wrote
|
|
12
|
+
* down.
|
|
13
|
+
*
|
|
14
|
+
* Returns `undefined` for v2 templates (no compile-time capability
|
|
15
|
+
* field) and for v3 templates that declare no requirements; otherwise
|
|
16
|
+
* the array as the compiler wrote it.
|
|
17
|
+
*/
|
|
18
|
+
function requiredCapabilitiesFromTemplate(parsed) {
|
|
19
|
+
if (parsed.type !== "pl.tengo-template.v3") return void 0;
|
|
20
|
+
return parsed.template.requiredCapabilities;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Same lookup, starting from raw `main.plj.gz` bytes. Used only at
|
|
24
|
+
* catalog-listing time for local-dev blocks where the worker pipeline
|
|
25
|
+
* isn't in play (`block_registry/registry.ts`). Install paths go through
|
|
26
|
+
* `requiredCapabilitiesFromTemplate` instead to avoid parsing the
|
|
27
|
+
* workflow twice.
|
|
28
|
+
*/
|
|
29
|
+
function deriveRequiredCapabilities(workflowContent) {
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
const json = gunzipSync(workflowContent).toString("utf-8");
|
|
33
|
+
parsed = JSON.parse(json);
|
|
34
|
+
} catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
return requiredCapabilitiesFromTemplate(parsed);
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { deriveRequiredCapabilities, requiredCapabilitiesFromTemplate };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=required_capabilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"required_capabilities.js","names":[],"sources":["../../../src/mutator/block-pack/required_capabilities.ts"],"sourcesContent":["import { gunzipSync } from \"node:zlib\";\nimport type { CompiledTemplateV3, TemplateData } from \"@milaboratories/pl-model-backend\";\n\n/**\n * Reads the capability tokens a template declares it requires via\n * `TemplateDataV3.requiredCapabilities` (populated by `tengo-builder` at\n * compile time).\n *\n * Use this on the install path: `BlockPackPreparer.prepare` parses the\n * workflow off-thread via the worker, and the parsed result feeds\n * straight into this function — no second gunzip+JSON.parse on the main\n * thread, no recursive walk to re-derive what the compiler already wrote\n * down.\n *\n * Returns `undefined` for v2 templates (no compile-time capability\n * field) and for v3 templates that declare no requirements; otherwise\n * the array as the compiler wrote it.\n */\nexport function requiredCapabilitiesFromTemplate(\n parsed: TemplateData | CompiledTemplateV3,\n): string[] | undefined {\n if (parsed.type !== \"pl.tengo-template.v3\") return undefined;\n return parsed.template.requiredCapabilities;\n}\n\n/**\n * Same lookup, starting from raw `main.plj.gz` bytes. Used only at\n * catalog-listing time for local-dev blocks where the worker pipeline\n * isn't in play (`block_registry/registry.ts`). Install paths go through\n * `requiredCapabilitiesFromTemplate` instead to avoid parsing the\n * workflow twice.\n */\nexport function deriveRequiredCapabilities(\n workflowContent: Uint8Array | Buffer,\n): string[] | undefined {\n let parsed: unknown;\n try {\n const json = gunzipSync(workflowContent).toString(\"utf-8\");\n parsed = JSON.parse(json);\n } catch {\n return undefined;\n }\n return requiredCapabilitiesFromTemplate(parsed as TemplateData | CompiledTemplateV3);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAkBA,SAAgB,iCACd,QACsB;AACtB,KAAI,OAAO,SAAS,uBAAwB,QAAO,KAAA;AACnD,QAAO,OAAO,SAAS;;;;;;;;;AAUzB,SAAgB,2BACd,iBACsB;CACtB,IAAI;AACJ,KAAI;EACF,MAAM,OAAO,WAAW,gBAAgB,CAAC,SAAS,QAAQ;AAC1D,WAAS,KAAK,MAAM,KAAK;SACnB;AACN;;AAEF,QAAO,iCAAiC,OAA4C"}
|
package/dist/mutator/project.cjs
CHANGED
|
@@ -9,11 +9,11 @@ const require_index = require("../debug/index.cjs");
|
|
|
9
9
|
let _platforma_sdk_model = require("@platforma-sdk/model");
|
|
10
10
|
let _milaboratories_pl_model_middle_layer = require("@milaboratories/pl-model-middle-layer");
|
|
11
11
|
let _milaboratories_ts_helpers = require("@milaboratories/ts-helpers");
|
|
12
|
+
let node_zlib = require("node:zlib");
|
|
12
13
|
let _milaboratories_pl_client = require("@milaboratories/pl-client");
|
|
13
14
|
let _milaboratories_pl_errors = require("@milaboratories/pl-errors");
|
|
14
15
|
let denque = require("denque");
|
|
15
16
|
denque = require_runtime.__toESM(denque);
|
|
16
|
-
let node_zlib = require("node:zlib");
|
|
17
17
|
//#region src/mutator/project.ts
|
|
18
18
|
function cached(modIdCb, valueCb) {
|
|
19
19
|
let initialized = false;
|
package/dist/mutator/project.js
CHANGED
|
@@ -8,10 +8,10 @@ import { getDebugFlags } from "../debug/index.js";
|
|
|
8
8
|
import { BLOCK_STORAGE_FACADE_VERSION, UiError, extractConfig } from "@platforma-sdk/model";
|
|
9
9
|
import { InitialBlockSettings } from "@milaboratories/pl-model-middle-layer";
|
|
10
10
|
import { ConsoleLoggerAdapter, cachedDecode, cachedDeserialize, canonicalJsonBytes, notEmpty } from "@milaboratories/ts-helpers";
|
|
11
|
+
import { gzipSync } from "node:zlib";
|
|
11
12
|
import { Pl, PlClient, ensureSignedResourceIdNotNull, field, isNotNullSignedResourceId, isNullSignedResourceId, isResource, isResourceId, isResourceRef } from "@milaboratories/pl-client";
|
|
12
13
|
import { ModelAPIVersionMismatchError } from "@milaboratories/pl-errors";
|
|
13
14
|
import Denque from "denque";
|
|
14
|
-
import { gzipSync } from "node:zlib";
|
|
15
15
|
//#region src/mutator/project.ts
|
|
16
16
|
function cached(modIdCb, valueCb) {
|
|
17
17
|
let initialized = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/pl-middle-layer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.61.0",
|
|
4
4
|
"description": "Pl Middle Layer",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -31,23 +31,23 @@
|
|
|
31
31
|
"yaml": "^2.8.0",
|
|
32
32
|
"zod": "~3.25.76",
|
|
33
33
|
"@milaboratories/computable": "2.9.4",
|
|
34
|
-
"@milaboratories/pf-spec-driver": "1.3.16",
|
|
35
|
-
"@milaboratories/pl-drivers": "1.14.8",
|
|
36
|
-
"@milaboratories/pl-deployments": "2.17.18",
|
|
37
|
-
"@milaboratories/pf-driver": "1.4.11",
|
|
38
34
|
"@milaboratories/helpers": "1.14.2",
|
|
39
|
-
"@milaboratories/
|
|
40
|
-
"@milaboratories/pl-
|
|
35
|
+
"@milaboratories/pf-spec-driver": "1.3.17",
|
|
36
|
+
"@milaboratories/pl-deployments": "2.17.18",
|
|
37
|
+
"@milaboratories/pl-drivers": "1.14.9",
|
|
38
|
+
"@milaboratories/pf-driver": "1.4.12",
|
|
39
|
+
"@milaboratories/pl-client": "3.7.0",
|
|
40
|
+
"@milaboratories/pl-errors": "1.4.9",
|
|
41
41
|
"@milaboratories/pl-http": "1.2.4",
|
|
42
|
+
"@milaboratories/pl-model-backend": "1.3.0",
|
|
42
43
|
"@milaboratories/pl-model-common": "1.42.0",
|
|
43
|
-
"@milaboratories/pl-
|
|
44
|
+
"@milaboratories/pl-tree": "1.11.2",
|
|
44
45
|
"@milaboratories/resolve-helper": "1.1.3",
|
|
45
|
-
"@milaboratories/pl-model-backend": "1.2.30",
|
|
46
|
-
"@milaboratories/pl-tree": "1.11.1",
|
|
47
46
|
"@milaboratories/ts-helpers": "1.8.2",
|
|
48
|
-
"@platforma-sdk/
|
|
49
|
-
"@platforma-sdk/workflow-tengo": "5.
|
|
50
|
-
"@platforma-sdk/
|
|
47
|
+
"@platforma-sdk/model": "1.77.4",
|
|
48
|
+
"@platforma-sdk/workflow-tengo": "5.25.0",
|
|
49
|
+
"@platforma-sdk/block-tools": "2.8.0",
|
|
50
|
+
"@milaboratories/pl-model-middle-layer": "1.20.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/node": "~24.5.2",
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"semver": "^7.7.2",
|
|
57
57
|
"typescript": "~5.9.3",
|
|
58
58
|
"vitest": "^4.1.3",
|
|
59
|
-
"@milaboratories/ts-configs": "1.2.3",
|
|
60
59
|
"@milaboratories/ts-builder": "1.5.0",
|
|
60
|
+
"@milaboratories/ts-configs": "1.2.3",
|
|
61
61
|
"@milaboratories/build-configs": "2.0.0"
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|
|
@@ -8,6 +8,7 @@ import YAML from "yaml";
|
|
|
8
8
|
import { assertNever } from "@milaboratories/ts-helpers";
|
|
9
9
|
import { LegacyDevBlockPackFiles } from "../dev_env";
|
|
10
10
|
import { tryLoadPackDescription } from "@platforma-sdk/block-tools";
|
|
11
|
+
import { deriveRequiredCapabilities } from "../mutator/block-pack/required_capabilities";
|
|
11
12
|
import type { V2RegistryProvider } from "./registry-v2-provider";
|
|
12
13
|
import type {
|
|
13
14
|
BlockPackId,
|
|
@@ -207,9 +208,28 @@ export class BlockPackRegistry {
|
|
|
207
208
|
if (v2Description !== undefined) {
|
|
208
209
|
const mtime = await getDevV2PacketMtime(v2Description);
|
|
209
210
|
|
|
211
|
+
const meta = await BlockPackMetaEmbedAbsoluteBytes.parseAsync(v2Description.meta);
|
|
212
|
+
|
|
213
|
+
// `tryLoadPackDescription` reads `package.json#block.meta` —
|
|
214
|
+
// the dev SOURCE meta the developer wrote.
|
|
215
|
+
// `requiredCapabilities` is a PACK-TIME artifact written by
|
|
216
|
+
// `block-tools pack` into `block-pack/manifest.json`.
|
|
217
|
+
// So for any dev block, the source meta never carries the field.
|
|
218
|
+
//
|
|
219
|
+
// To surface the gate at listing time without requiring the
|
|
220
|
+
// developer to re-pack on every workflow change, derive from
|
|
221
|
+
// the compiled workflow bytes directly.
|
|
222
|
+
if (meta.requiredCapabilities === undefined) {
|
|
223
|
+
const workflowBytes = await fs.promises.readFile(
|
|
224
|
+
v2Description.components.workflow.main.file,
|
|
225
|
+
);
|
|
226
|
+
const derived = deriveRequiredCapabilities(workflowBytes);
|
|
227
|
+
if (derived !== undefined) meta.requiredCapabilities = derived;
|
|
228
|
+
}
|
|
229
|
+
|
|
210
230
|
const latestOverview: SingleBlockPackOverview = {
|
|
211
231
|
id: v2Description.id,
|
|
212
|
-
meta
|
|
232
|
+
meta,
|
|
213
233
|
featureFlags: v2Description.featureFlags,
|
|
214
234
|
spec: {
|
|
215
235
|
type: "dev-v2",
|
|
@@ -106,6 +106,16 @@ export class MiddleLayer {
|
|
|
106
106
|
return this.pl.serverInfo.platform as BlockPlatform | undefined;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Runtime capabilities advertised by the connected backend (tokens of
|
|
111
|
+
* the form `<feature>:<version>`, e.g. "wasm:v1"). Empty list if the
|
|
112
|
+
* backend predates the capability mechanism — that's the desired
|
|
113
|
+
* fail-closed behaviour for blocks declaring any `requiredCapabilities`.
|
|
114
|
+
*/
|
|
115
|
+
public get serverCapabilities(): string[] {
|
|
116
|
+
return this.pl.serverInfo.capabilities ?? [];
|
|
117
|
+
}
|
|
118
|
+
|
|
109
119
|
/** Adds a runtime capability to the middle layer. */
|
|
110
120
|
public addRuntimeCapability(
|
|
111
121
|
requirement: SupportedRequirement,
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
FieldData,
|
|
4
4
|
Filter,
|
|
5
5
|
OptionalAnyResourceId,
|
|
6
|
+
PlClient,
|
|
6
7
|
SignedResourceId,
|
|
7
8
|
} from "@milaboratories/pl-client";
|
|
8
9
|
import {
|
|
@@ -219,6 +220,7 @@ export class Project {
|
|
|
219
220
|
const blockCfg = extractConfig(preparedBp.config);
|
|
220
221
|
|
|
221
222
|
this.env.runtimeCapabilities.throwIfIncompatible(blockCfg.featureFlags);
|
|
223
|
+
throwIfMissingServerCapabilities(this.env.pl, preparedBp.requiredCapabilities);
|
|
222
224
|
|
|
223
225
|
// Pre-materialize template via cache (separate transaction(s))
|
|
224
226
|
const cachedBp = await cacheBlockPackTemplate(this.env.pl, preparedBp);
|
|
@@ -312,6 +314,7 @@ export class Project {
|
|
|
312
314
|
const blockCfg = extractConfig(preparedBp.config);
|
|
313
315
|
|
|
314
316
|
this.env.runtimeCapabilities.throwIfIncompatible(blockCfg.featureFlags);
|
|
317
|
+
throwIfMissingServerCapabilities(this.env.pl, preparedBp.requiredCapabilities);
|
|
315
318
|
|
|
316
319
|
// Pre-materialize template via cache (separate transaction(s))
|
|
317
320
|
const cachedBp = await cacheBlockPackTemplate(this.env.pl, preparedBp);
|
|
@@ -985,3 +988,27 @@ function convertErrorsToStrings(
|
|
|
985
988
|
|
|
986
989
|
return result;
|
|
987
990
|
}
|
|
991
|
+
|
|
992
|
+
/** Throws when the connected backend doesn't advertise every capability the
|
|
993
|
+
* block-pack requires (as declared in `meta.requiredCapabilities`).
|
|
994
|
+
*
|
|
995
|
+
* The matching set is the desktop's install gate moved server-side so both
|
|
996
|
+
* the UI (`AddBlockModal`) and direct middle-layer callers (the MCP server's
|
|
997
|
+
* `add_block` tool, programmatic block installs, tests) reject incompatible
|
|
998
|
+
* blocks consistently. */
|
|
999
|
+
function throwIfMissingServerCapabilities(
|
|
1000
|
+
pl: PlClient,
|
|
1001
|
+
requiredCapabilities: readonly string[] | undefined,
|
|
1002
|
+
): void {
|
|
1003
|
+
if (!requiredCapabilities || requiredCapabilities.length === 0) return;
|
|
1004
|
+
const advertised = (pl.serverInfo.capabilities ?? []) as readonly string[];
|
|
1005
|
+
const filterFn = (c: string) => !advertised.includes(c);
|
|
1006
|
+
if (!requiredCapabilities.some(filterFn)) return;
|
|
1007
|
+
const missing = requiredCapabilities.filter(filterFn);
|
|
1008
|
+
throw new Error(
|
|
1009
|
+
`Block cannot be added: connected backend does not advertise capabilities ` +
|
|
1010
|
+
`${JSON.stringify(missing)}. ` +
|
|
1011
|
+
`Backend advertises ${JSON.stringify(advertised)}. ` +
|
|
1012
|
+
`Upgrade the backend or remove the requirement from the block manifest.`,
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
@@ -52,6 +52,12 @@ export type BlockPackSpecPrepared = {
|
|
|
52
52
|
config: BlockConfigContainer;
|
|
53
53
|
frontend: FrontendSpec;
|
|
54
54
|
source: BlockPackSpec;
|
|
55
|
+
/** Backend-side runtime capabilities the block requires at install time.
|
|
56
|
+
* Derived from the parsed workflow template by
|
|
57
|
+
* `requiredCapabilitiesFromTemplate` during `BlockPackPreparer.prepare`.
|
|
58
|
+
* `Project.addBlock` matches this list against `pl.serverInfo.capabilities`
|
|
59
|
+
* and refuses to install when anything is missing. */
|
|
60
|
+
requiredCapabilities?: string[];
|
|
55
61
|
};
|
|
56
62
|
|
|
57
63
|
/** All block-pack specs. */
|
|
@@ -9,6 +9,7 @@ import fs from "node:fs";
|
|
|
9
9
|
import type { Dispatcher } from "undici";
|
|
10
10
|
import { request } from "undici";
|
|
11
11
|
import { createFrontend } from "./frontend";
|
|
12
|
+
import { requiredCapabilitiesFromTemplate } from "./required_capabilities";
|
|
12
13
|
import type { BlockConfigContainer } from "@platforma-sdk/model";
|
|
13
14
|
import { Code } from "@platforma-sdk/model";
|
|
14
15
|
import { loadPackDescription, RegistryV1 } from "@platforma-sdk/block-tools";
|
|
@@ -143,13 +144,16 @@ export class BlockPackPreparer {
|
|
|
143
144
|
|
|
144
145
|
await using workerManager = new WorkerManager();
|
|
145
146
|
|
|
147
|
+
const parsed = await workerManager.process("parseTemplate", explicit.template.content);
|
|
148
|
+
|
|
146
149
|
const result: BlockPackSpecPrepared = {
|
|
147
150
|
...explicit,
|
|
148
151
|
type: "prepared",
|
|
149
152
|
template: {
|
|
150
153
|
type: "prepared",
|
|
151
|
-
data:
|
|
154
|
+
data: parsed,
|
|
152
155
|
},
|
|
156
|
+
requiredCapabilities: requiredCapabilitiesFromTemplate(parsed),
|
|
153
157
|
};
|
|
154
158
|
|
|
155
159
|
if (key) {
|
|
@@ -263,12 +267,13 @@ export class BlockPackPreparer {
|
|
|
263
267
|
await this.remoteContentCache.forceFetch(components.workflow.main.url);
|
|
264
268
|
|
|
265
269
|
const [model, workflow] = await Promise.all([getModel(), getWorkflow()]);
|
|
270
|
+
const workflowContent = Buffer.from(workflow);
|
|
266
271
|
|
|
267
272
|
return {
|
|
268
273
|
type: "explicit",
|
|
269
274
|
template: {
|
|
270
275
|
type: "explicit",
|
|
271
|
-
content:
|
|
276
|
+
content: workflowContent,
|
|
272
277
|
},
|
|
273
278
|
config: model,
|
|
274
279
|
frontend: {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { gunzipSync } from "node:zlib";
|
|
2
|
+
import type { CompiledTemplateV3, TemplateData } from "@milaboratories/pl-model-backend";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Reads the capability tokens a template declares it requires via
|
|
6
|
+
* `TemplateDataV3.requiredCapabilities` (populated by `tengo-builder` at
|
|
7
|
+
* compile time).
|
|
8
|
+
*
|
|
9
|
+
* Use this on the install path: `BlockPackPreparer.prepare` parses the
|
|
10
|
+
* workflow off-thread via the worker, and the parsed result feeds
|
|
11
|
+
* straight into this function — no second gunzip+JSON.parse on the main
|
|
12
|
+
* thread, no recursive walk to re-derive what the compiler already wrote
|
|
13
|
+
* down.
|
|
14
|
+
*
|
|
15
|
+
* Returns `undefined` for v2 templates (no compile-time capability
|
|
16
|
+
* field) and for v3 templates that declare no requirements; otherwise
|
|
17
|
+
* the array as the compiler wrote it.
|
|
18
|
+
*/
|
|
19
|
+
export function requiredCapabilitiesFromTemplate(
|
|
20
|
+
parsed: TemplateData | CompiledTemplateV3,
|
|
21
|
+
): string[] | undefined {
|
|
22
|
+
if (parsed.type !== "pl.tengo-template.v3") return undefined;
|
|
23
|
+
return parsed.template.requiredCapabilities;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Same lookup, starting from raw `main.plj.gz` bytes. Used only at
|
|
28
|
+
* catalog-listing time for local-dev blocks where the worker pipeline
|
|
29
|
+
* isn't in play (`block_registry/registry.ts`). Install paths go through
|
|
30
|
+
* `requiredCapabilitiesFromTemplate` instead to avoid parsing the
|
|
31
|
+
* workflow twice.
|
|
32
|
+
*/
|
|
33
|
+
export function deriveRequiredCapabilities(
|
|
34
|
+
workflowContent: Uint8Array | Buffer,
|
|
35
|
+
): string[] | undefined {
|
|
36
|
+
let parsed: unknown;
|
|
37
|
+
try {
|
|
38
|
+
const json = gunzipSync(workflowContent).toString("utf-8");
|
|
39
|
+
parsed = JSON.parse(json);
|
|
40
|
+
} catch {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return requiredCapabilitiesFromTemplate(parsed as TemplateData | CompiledTemplateV3);
|
|
44
|
+
}
|