@milaboratories/pl-middle-layer 1.60.5 → 1.61.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 (38) hide show
  1. package/dist/block_registry/registry.cjs +7 -1
  2. package/dist/block_registry/registry.cjs.map +1 -1
  3. package/dist/block_registry/registry.d.ts.map +1 -1
  4. package/dist/block_registry/registry.js +7 -1
  5. package/dist/block_registry/registry.js.map +1 -1
  6. package/dist/middle_layer/middle_layer.cjs +9 -0
  7. package/dist/middle_layer/middle_layer.cjs.map +1 -1
  8. package/dist/middle_layer/middle_layer.d.ts +7 -0
  9. package/dist/middle_layer/middle_layer.d.ts.map +1 -1
  10. package/dist/middle_layer/middle_layer.js +9 -0
  11. package/dist/middle_layer/middle_layer.js.map +1 -1
  12. package/dist/middle_layer/project.cjs +17 -0
  13. package/dist/middle_layer/project.cjs.map +1 -1
  14. package/dist/middle_layer/project.d.ts.map +1 -1
  15. package/dist/middle_layer/project.js +17 -0
  16. package/dist/middle_layer/project.js.map +1 -1
  17. package/dist/model/block_pack_spec.cjs.map +1 -1
  18. package/dist/model/block_pack_spec.d.ts +6 -0
  19. package/dist/model/block_pack_spec.d.ts.map +1 -1
  20. package/dist/model/block_pack_spec.js.map +1 -1
  21. package/dist/mutator/block-pack/block_pack.cjs +5 -3
  22. package/dist/mutator/block-pack/block_pack.cjs.map +1 -1
  23. package/dist/mutator/block-pack/block_pack.d.ts.map +1 -1
  24. package/dist/mutator/block-pack/block_pack.js +5 -3
  25. package/dist/mutator/block-pack/block_pack.js.map +1 -1
  26. package/dist/mutator/block-pack/required_capabilities.cjs +44 -0
  27. package/dist/mutator/block-pack/required_capabilities.cjs.map +1 -0
  28. package/dist/mutator/block-pack/required_capabilities.js +42 -0
  29. package/dist/mutator/block-pack/required_capabilities.js.map +1 -0
  30. package/dist/mutator/project.cjs +1 -1
  31. package/dist/mutator/project.js +1 -1
  32. package/package.json +15 -15
  33. package/src/block_registry/registry.ts +21 -1
  34. package/src/middle_layer/middle_layer.ts +10 -0
  35. package/src/middle_layer/project.ts +27 -0
  36. package/src/model/block_pack_spec.ts +6 -0
  37. package/src/mutator/block-pack/block_pack.ts +7 -2
  38. package/src/mutator/block-pack/required_capabilities.ts +44 -0
@@ -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("../mutator/block-pack/required_capabilities.cjs");
3
4
  let _milaboratories_pl_model_middle_layer = require("@milaboratories/pl-model-middle-layer");
4
5
  let undici = require("undici");
5
6
  let _platforma_sdk_block_tools = require("@platforma-sdk/block-tools");
@@ -147,9 +148,14 @@ var BlockPackRegistry = class {
147
148
  }
148
149
  if (v2Description !== void 0) {
149
150
  const mtime = await getDevV2PacketMtime(v2Description);
151
+ const meta = await _platforma_sdk_block_tools.BlockPackMetaEmbedAbsoluteBytes.parseAsync(v2Description.meta);
152
+ if (meta.requiredCapabilities === void 0) {
153
+ const derived = require_required_capabilities.deriveRequiredCapabilities(await node_fs.default.promises.readFile(v2Description.components.workflow.main.file));
154
+ if (derived !== void 0) meta.requiredCapabilities = derived;
155
+ }
150
156
  const latestOverview = {
151
157
  id: v2Description.id,
152
- meta: await _platforma_sdk_block_tools.BlockPackMetaEmbedAbsoluteBytes.parseAsync(v2Description.meta),
158
+ meta,
153
159
  featureFlags: v2Description.featureFlags,
154
160
  spec: {
155
161
  type: "dev-v2",
@@ -1 +1 @@
1
- {"version":3,"file":"registry.cjs","names":["fs","LegacyDevBlockPackFiles","path","RegistryV1","AnyChannel","StableChannel","YAML","BlockPackMetaEmbedAbsoluteBytes"],"sources":["../../src/block_registry/registry.ts"],"sourcesContent":["import type { Dispatcher } from \"undici\";\nimport { request } from \"undici\";\nimport type { BlockPackDescriptionAbsolute } from \"@platforma-sdk/block-tools\";\nimport { BlockPackMetaEmbedAbsoluteBytes, RegistryV1 } from \"@platforma-sdk/block-tools\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport YAML from \"yaml\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport { LegacyDevBlockPackFiles } from \"../dev_env\";\nimport { tryLoadPackDescription } from \"@platforma-sdk/block-tools\";\nimport type { V2RegistryProvider } from \"./registry-v2-provider\";\nimport type {\n BlockPackId,\n BlockPackListing,\n BlockPackOverview,\n RegistryEntry,\n RegistryStatus,\n SingleBlockPackOverview,\n} from \"@milaboratories/pl-model-middle-layer\";\nimport { AnyChannel, StableChannel } from \"@milaboratories/pl-model-middle-layer\";\n\nasync function getFileContent(path: string) {\n try {\n return await fs.promises.readFile(path, \"utf8\");\n } catch (error: unknown) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n }\n}\n\nasync function getFileStat(path: string) {\n try {\n return await fs.promises.stat(path, { bigint: true });\n } catch (error: unknown) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n }\n}\n\nexport async function getDevV1PacketMtime(devPath: string): Promise<string> {\n let mtime = 0n;\n for (const f of LegacyDevBlockPackFiles) {\n const fullPath = path.join(devPath, ...f);\n const stat = await getFileStat(fullPath);\n if (stat === undefined) continue;\n if (mtime < stat.mtimeNs) mtime = stat.mtimeNs;\n }\n return mtime.toString();\n}\n\nexport async function getDevV2PacketMtime(\n description: BlockPackDescriptionAbsolute,\n): Promise<string> {\n const wfStats = await fs.promises.stat(description.components.workflow.main.file, {\n bigint: true,\n });\n const modelStats = await fs.promises.stat(description.components.model.file, { bigint: true });\n return (wfStats.mtimeNs > modelStats.mtimeNs ? wfStats.mtimeNs : modelStats.mtimeNs).toString();\n}\n\nexport class BlockPackRegistry {\n constructor(\n private readonly v2Provider: V2RegistryProvider,\n private readonly registries: RegistryEntry[],\n private readonly http?: Dispatcher,\n ) {}\n\n private async getPackagesForRoot(\n regEntry: RegistryEntry,\n options?: { signal?: AbortSignal },\n ): Promise<BlockPackOverview[]> {\n const result: BlockPackOverview[] = [];\n const regSpec = regEntry.spec;\n switch (regSpec.type) {\n case \"remote-v1\": {\n const httpOptions = this.http !== undefined ? { dispatcher: this.http } : {};\n\n const overviewResponse = await request(`${regSpec.url}/${RegistryV1.GlobalOverviewPath}`, {\n ...httpOptions,\n signal: options?.signal,\n });\n const overview = (await overviewResponse.body.json()) as RegistryV1.GlobalOverview;\n for (const overviewEntry of overview) {\n const { organization, package: pkg, latestMeta, latestVersion } = overviewEntry;\n const id = {\n organization,\n name: pkg,\n version: latestVersion,\n };\n const latestOverview: SingleBlockPackOverview = {\n id,\n meta: {\n title: (latestMeta[\"title\"] as string | undefined) ?? \"No title\",\n description: (latestMeta[\"description\"] as string | undefined) ?? \"No Description\",\n organization: {\n name: organization,\n url: \"https://unknown.com\",\n },\n },\n spec: {\n type: \"from-registry-v1\",\n id,\n registryUrl: regSpec.url,\n },\n };\n result.push({\n registryId: regEntry.id,\n id,\n latestByChannel: {\n [AnyChannel]: latestOverview,\n [StableChannel]: latestOverview,\n },\n allVersions: overviewEntry.allVersions.map((v) => ({ version: v, channels: [] })),\n });\n }\n return result;\n }\n\n case \"remote-v2\":\n return (\n await this.v2Provider.getRegistry(regSpec.url).listBlockPacks({ signal: options?.signal })\n ).map((e) => ({\n ...e,\n registryId: regEntry.id,\n }));\n // e.latestByChannel[StableChannel]\n // ? {\n // ...e,\n // registryId: regEntry.id,\n // }\n // : {\n // ...e,\n // latestByChannel: {\n // ...e.latestByChannel,\n // [StableChannel]: ((s: SingleBlockPackOverview) => {\n // if (s.spec.type === 'from-registry-v2') {\n // return { ...s, spec: { ...s.spec, channel: StableChannel } };\n // }\n\n // return s;\n // })(e.latestByChannel[AnyChannel]),\n // },\n // registryId: regEntry.id,\n // },\n\n case \"local-dev\":\n for (const entry of await fs.promises.readdir(regSpec.path, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const devPath = path.join(regSpec.path, entry.name);\n\n const legacyYamlContent = await getFileContent(\n path.join(devPath, RegistryV1.PlPackageYamlConfigFile),\n );\n if (legacyYamlContent !== undefined) {\n const config = RegistryV1.PlPackageConfigData.parse(YAML.parse(legacyYamlContent));\n\n const mtime = await getDevV1PacketMtime(devPath);\n\n const id = {\n organization: config.organization,\n name: config.package,\n version: \"DEV\",\n };\n\n const latestOverview: SingleBlockPackOverview = {\n id,\n meta: {\n title: (config.meta[\"title\"] as string) ?? \"No title\",\n description: (config.meta[\"description\"] as string) ?? \"No Description\",\n organization: {\n name: config.organization,\n url: \"https://unknown.com\",\n },\n },\n spec: {\n type: \"dev-v2\",\n folder: devPath,\n mtime,\n },\n };\n\n result.push({\n registryId: regEntry.id,\n id,\n latestByChannel: {\n [AnyChannel]: latestOverview,\n [StableChannel]: latestOverview,\n },\n allVersions: [],\n });\n } else {\n let actualDevPath = devPath;\n let v2Description = await tryLoadPackDescription(actualDevPath);\n\n if (v2Description === undefined)\n // iterating over expected subfolder names where block developer may put root block-pack package\n for (const bpSubfolder of [\"block\", \"meta\"]) {\n actualDevPath = path.join(devPath, bpSubfolder);\n v2Description = await tryLoadPackDescription(actualDevPath);\n if (v2Description !== undefined) break;\n }\n\n if (v2Description !== undefined) {\n const mtime = await getDevV2PacketMtime(v2Description);\n\n const latestOverview: SingleBlockPackOverview = {\n id: v2Description.id,\n meta: await BlockPackMetaEmbedAbsoluteBytes.parseAsync(v2Description.meta),\n featureFlags: v2Description.featureFlags,\n spec: {\n type: \"dev-v2\",\n folder: actualDevPath,\n mtime,\n },\n };\n\n result.push({\n registryId: regEntry.id,\n id: v2Description.id,\n latestByChannel: {\n [AnyChannel]: latestOverview,\n [StableChannel]: latestOverview,\n },\n allVersions: [],\n });\n }\n }\n\n continue;\n }\n return result;\n default:\n return assertNever(regSpec);\n }\n }\n\n public async listBlockPacks(options?: { signal?: AbortSignal }): Promise<BlockPackListing> {\n const blockPacks: BlockPackOverview[] = [];\n const registries: RegistryStatus[] = [];\n for (const regSpecs of this.registries) {\n registries.push({ ...regSpecs, status: \"online\" });\n blockPacks.push(...(await this.getPackagesForRoot(regSpecs, options)));\n }\n return { registries, blockPacks };\n }\n\n public async getOverview(\n registryId: string,\n blockId: BlockPackId,\n channel: string,\n ): Promise<SingleBlockPackOverview> {\n const regSpec = this.registries.find((reg) => reg.id === registryId)?.spec;\n if (!regSpec) throw new Error(`Registry with id \"${registryId}\" not found`);\n if (regSpec.type !== \"remote-v2\")\n throw new Error(\n `Only \"remote-v2\" registries support specific package version overview retrieval.`,\n );\n const reg = this.v2Provider.getRegistry(regSpec.url);\n return await reg.getSpecificOverview(blockId, channel);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"registry.cjs","names":["fs","LegacyDevBlockPackFiles","path","RegistryV1","AnyChannel","StableChannel","YAML","BlockPackMetaEmbedAbsoluteBytes","deriveRequiredCapabilities"],"sources":["../../src/block_registry/registry.ts"],"sourcesContent":["import type { Dispatcher } from \"undici\";\nimport { request } from \"undici\";\nimport type { BlockPackDescriptionAbsolute } from \"@platforma-sdk/block-tools\";\nimport { BlockPackMetaEmbedAbsoluteBytes, RegistryV1 } from \"@platforma-sdk/block-tools\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport YAML from \"yaml\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport { LegacyDevBlockPackFiles } from \"../dev_env\";\nimport { tryLoadPackDescription } from \"@platforma-sdk/block-tools\";\nimport { deriveRequiredCapabilities } from \"../mutator/block-pack/required_capabilities\";\nimport type { V2RegistryProvider } from \"./registry-v2-provider\";\nimport type {\n BlockPackId,\n BlockPackListing,\n BlockPackOverview,\n RegistryEntry,\n RegistryStatus,\n SingleBlockPackOverview,\n} from \"@milaboratories/pl-model-middle-layer\";\nimport { AnyChannel, StableChannel } from \"@milaboratories/pl-model-middle-layer\";\n\nasync function getFileContent(path: string) {\n try {\n return await fs.promises.readFile(path, \"utf8\");\n } catch (error: unknown) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n }\n}\n\nasync function getFileStat(path: string) {\n try {\n return await fs.promises.stat(path, { bigint: true });\n } catch (error: unknown) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n }\n}\n\nexport async function getDevV1PacketMtime(devPath: string): Promise<string> {\n let mtime = 0n;\n for (const f of LegacyDevBlockPackFiles) {\n const fullPath = path.join(devPath, ...f);\n const stat = await getFileStat(fullPath);\n if (stat === undefined) continue;\n if (mtime < stat.mtimeNs) mtime = stat.mtimeNs;\n }\n return mtime.toString();\n}\n\nexport async function getDevV2PacketMtime(\n description: BlockPackDescriptionAbsolute,\n): Promise<string> {\n const wfStats = await fs.promises.stat(description.components.workflow.main.file, {\n bigint: true,\n });\n const modelStats = await fs.promises.stat(description.components.model.file, { bigint: true });\n return (wfStats.mtimeNs > modelStats.mtimeNs ? wfStats.mtimeNs : modelStats.mtimeNs).toString();\n}\n\nexport class BlockPackRegistry {\n constructor(\n private readonly v2Provider: V2RegistryProvider,\n private readonly registries: RegistryEntry[],\n private readonly http?: Dispatcher,\n ) {}\n\n private async getPackagesForRoot(\n regEntry: RegistryEntry,\n options?: { signal?: AbortSignal },\n ): Promise<BlockPackOverview[]> {\n const result: BlockPackOverview[] = [];\n const regSpec = regEntry.spec;\n switch (regSpec.type) {\n case \"remote-v1\": {\n const httpOptions = this.http !== undefined ? { dispatcher: this.http } : {};\n\n const overviewResponse = await request(`${regSpec.url}/${RegistryV1.GlobalOverviewPath}`, {\n ...httpOptions,\n signal: options?.signal,\n });\n const overview = (await overviewResponse.body.json()) as RegistryV1.GlobalOverview;\n for (const overviewEntry of overview) {\n const { organization, package: pkg, latestMeta, latestVersion } = overviewEntry;\n const id = {\n organization,\n name: pkg,\n version: latestVersion,\n };\n const latestOverview: SingleBlockPackOverview = {\n id,\n meta: {\n title: (latestMeta[\"title\"] as string | undefined) ?? \"No title\",\n description: (latestMeta[\"description\"] as string | undefined) ?? \"No Description\",\n organization: {\n name: organization,\n url: \"https://unknown.com\",\n },\n },\n spec: {\n type: \"from-registry-v1\",\n id,\n registryUrl: regSpec.url,\n },\n };\n result.push({\n registryId: regEntry.id,\n id,\n latestByChannel: {\n [AnyChannel]: latestOverview,\n [StableChannel]: latestOverview,\n },\n allVersions: overviewEntry.allVersions.map((v) => ({ version: v, channels: [] })),\n });\n }\n return result;\n }\n\n case \"remote-v2\":\n return (\n await this.v2Provider.getRegistry(regSpec.url).listBlockPacks({ signal: options?.signal })\n ).map((e) => ({\n ...e,\n registryId: regEntry.id,\n }));\n // e.latestByChannel[StableChannel]\n // ? {\n // ...e,\n // registryId: regEntry.id,\n // }\n // : {\n // ...e,\n // latestByChannel: {\n // ...e.latestByChannel,\n // [StableChannel]: ((s: SingleBlockPackOverview) => {\n // if (s.spec.type === 'from-registry-v2') {\n // return { ...s, spec: { ...s.spec, channel: StableChannel } };\n // }\n\n // return s;\n // })(e.latestByChannel[AnyChannel]),\n // },\n // registryId: regEntry.id,\n // },\n\n case \"local-dev\":\n for (const entry of await fs.promises.readdir(regSpec.path, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const devPath = path.join(regSpec.path, entry.name);\n\n const legacyYamlContent = await getFileContent(\n path.join(devPath, RegistryV1.PlPackageYamlConfigFile),\n );\n if (legacyYamlContent !== undefined) {\n const config = RegistryV1.PlPackageConfigData.parse(YAML.parse(legacyYamlContent));\n\n const mtime = await getDevV1PacketMtime(devPath);\n\n const id = {\n organization: config.organization,\n name: config.package,\n version: \"DEV\",\n };\n\n const latestOverview: SingleBlockPackOverview = {\n id,\n meta: {\n title: (config.meta[\"title\"] as string) ?? \"No title\",\n description: (config.meta[\"description\"] as string) ?? \"No Description\",\n organization: {\n name: config.organization,\n url: \"https://unknown.com\",\n },\n },\n spec: {\n type: \"dev-v2\",\n folder: devPath,\n mtime,\n },\n };\n\n result.push({\n registryId: regEntry.id,\n id,\n latestByChannel: {\n [AnyChannel]: latestOverview,\n [StableChannel]: latestOverview,\n },\n allVersions: [],\n });\n } else {\n let actualDevPath = devPath;\n let v2Description = await tryLoadPackDescription(actualDevPath);\n\n if (v2Description === undefined)\n // iterating over expected subfolder names where block developer may put root block-pack package\n for (const bpSubfolder of [\"block\", \"meta\"]) {\n actualDevPath = path.join(devPath, bpSubfolder);\n v2Description = await tryLoadPackDescription(actualDevPath);\n if (v2Description !== undefined) break;\n }\n\n if (v2Description !== undefined) {\n const mtime = await getDevV2PacketMtime(v2Description);\n\n const meta = await BlockPackMetaEmbedAbsoluteBytes.parseAsync(v2Description.meta);\n\n // `tryLoadPackDescription` reads `package.json#block.meta` —\n // the dev SOURCE meta the developer wrote.\n // `requiredCapabilities` is a PACK-TIME artifact written by\n // `block-tools pack` into `block-pack/manifest.json`.\n // So for any dev block, the source meta never carries the field.\n //\n // To surface the gate at listing time without requiring the\n // developer to re-pack on every workflow change, derive from\n // the compiled workflow bytes directly.\n if (meta.requiredCapabilities === undefined) {\n const workflowBytes = await fs.promises.readFile(\n v2Description.components.workflow.main.file,\n );\n const derived = deriveRequiredCapabilities(workflowBytes);\n if (derived !== undefined) meta.requiredCapabilities = derived;\n }\n\n const latestOverview: SingleBlockPackOverview = {\n id: v2Description.id,\n meta,\n featureFlags: v2Description.featureFlags,\n spec: {\n type: \"dev-v2\",\n folder: actualDevPath,\n mtime,\n },\n };\n\n result.push({\n registryId: regEntry.id,\n id: v2Description.id,\n latestByChannel: {\n [AnyChannel]: latestOverview,\n [StableChannel]: latestOverview,\n },\n allVersions: [],\n });\n }\n }\n\n continue;\n }\n return result;\n default:\n return assertNever(regSpec);\n }\n }\n\n public async listBlockPacks(options?: { signal?: AbortSignal }): Promise<BlockPackListing> {\n const blockPacks: BlockPackOverview[] = [];\n const registries: RegistryStatus[] = [];\n for (const regSpecs of this.registries) {\n registries.push({ ...regSpecs, status: \"online\" });\n blockPacks.push(...(await this.getPackagesForRoot(regSpecs, options)));\n }\n return { registries, blockPacks };\n }\n\n public async getOverview(\n registryId: string,\n blockId: BlockPackId,\n channel: string,\n ): Promise<SingleBlockPackOverview> {\n const regSpec = this.registries.find((reg) => reg.id === registryId)?.spec;\n if (!regSpec) throw new Error(`Registry with id \"${registryId}\" not found`);\n if (regSpec.type !== \"remote-v2\")\n throw new Error(\n `Only \"remote-v2\" registries support specific package version overview retrieval.`,\n );\n const reg = this.v2Provider.getRegistry(regSpec.url);\n return await reg.getSpecificOverview(blockId, channel);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAsBA,eAAe,eAAe,MAAc;AAC1C,KAAI;AACF,SAAO,MAAMA,QAAAA,QAAG,SAAS,SAAS,MAAM,OAAO;UACxC,OAAgB;AACvB,MAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,SAC9D;AAEF,QAAM;;;AAIV,eAAe,YAAY,MAAc;AACvC,KAAI;AACF,SAAO,MAAMA,QAAAA,QAAG,SAAS,KAAK,MAAM,EAAE,QAAQ,MAAM,CAAC;UAC9C,OAAgB;AACvB,MAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,SAC9D;AAEF,QAAM;;;AAIV,eAAsB,oBAAoB,SAAkC;CAC1E,IAAI,QAAQ;AACZ,MAAK,MAAM,KAAKC,cAAAA,yBAAyB;EAEvC,MAAM,OAAO,MAAM,YADFC,UAAAA,QAAK,KAAK,SAAS,GAAG,EAAE,CACD;AACxC,MAAI,SAAS,KAAA,EAAW;AACxB,MAAI,QAAQ,KAAK,QAAS,SAAQ,KAAK;;AAEzC,QAAO,MAAM,UAAU;;AAGzB,eAAsB,oBACpB,aACiB;CACjB,MAAM,UAAU,MAAMF,QAAAA,QAAG,SAAS,KAAK,YAAY,WAAW,SAAS,KAAK,MAAM,EAChF,QAAQ,MACT,CAAC;CACF,MAAM,aAAa,MAAMA,QAAAA,QAAG,SAAS,KAAK,YAAY,WAAW,MAAM,MAAM,EAAE,QAAQ,MAAM,CAAC;AAC9F,SAAQ,QAAQ,UAAU,WAAW,UAAU,QAAQ,UAAU,WAAW,SAAS,UAAU;;AAGjG,IAAa,oBAAb,MAA+B;CAC7B,YACE,YACA,YACA,MACA;AAHiB,OAAA,aAAA;AACA,OAAA,aAAA;AACA,OAAA,OAAA;;CAGnB,MAAc,mBACZ,UACA,SAC8B;EAC9B,MAAM,SAA8B,EAAE;EACtC,MAAM,UAAU,SAAS;AACzB,UAAQ,QAAQ,MAAhB;GACE,KAAK,aAAa;IAChB,MAAM,cAAc,KAAK,SAAS,KAAA,IAAY,EAAE,YAAY,KAAK,MAAM,GAAG,EAAE;IAM5E,MAAM,WAAY,OAJO,OAAA,GAAA,OAAA,SAAc,GAAG,QAAQ,IAAI,GAAGG,2BAAAA,WAAW,sBAAsB;KACxF,GAAG;KACH,QAAQ,SAAS;KAClB,CAAC,EACuC,KAAK,MAAM;AACpD,SAAK,MAAM,iBAAiB,UAAU;KACpC,MAAM,EAAE,cAAc,SAAS,KAAK,YAAY,kBAAkB;KAClE,MAAM,KAAK;MACT;MACA,MAAM;MACN,SAAS;MACV;KACD,MAAM,iBAA0C;MAC9C;MACA,MAAM;OACJ,OAAQ,WAAW,YAAmC;OACtD,aAAc,WAAW,kBAAyC;OAClE,cAAc;QACZ,MAAM;QACN,KAAK;QACN;OACF;MACD,MAAM;OACJ,MAAM;OACN;OACA,aAAa,QAAQ;OACtB;MACF;AACD,YAAO,KAAK;MACV,YAAY,SAAS;MACrB;MACA,iBAAiB;QACdC,sCAAAA,aAAa;QACbC,sCAAAA,gBAAgB;OAClB;MACD,aAAa,cAAc,YAAY,KAAK,OAAO;OAAE,SAAS;OAAG,UAAU,EAAE;OAAE,EAAE;MAClF,CAAC;;AAEJ,WAAO;;GAGT,KAAK,YACH,SACE,MAAM,KAAK,WAAW,YAAY,QAAQ,IAAI,CAAC,eAAe,EAAE,QAAQ,SAAS,QAAQ,CAAC,EAC1F,KAAK,OAAO;IACZ,GAAG;IACH,YAAY,SAAS;IACtB,EAAE;GAqBL,KAAK;AACH,SAAK,MAAM,SAAS,MAAML,QAAAA,QAAG,SAAS,QAAQ,QAAQ,MAAM,EAAE,eAAe,MAAM,CAAC,EAAE;AACpF,SAAI,CAAC,MAAM,aAAa,CAAE;KAC1B,MAAM,UAAUE,UAAAA,QAAK,KAAK,QAAQ,MAAM,MAAM,KAAK;KAEnD,MAAM,oBAAoB,MAAM,eAC9BA,UAAAA,QAAK,KAAK,SAASC,2BAAAA,WAAW,wBAAwB,CACvD;AACD,SAAI,sBAAsB,KAAA,GAAW;MACnC,MAAM,SAASA,2BAAAA,WAAW,oBAAoB,MAAMG,KAAAA,QAAK,MAAM,kBAAkB,CAAC;MAElF,MAAM,QAAQ,MAAM,oBAAoB,QAAQ;MAEhD,MAAM,KAAK;OACT,cAAc,OAAO;OACrB,MAAM,OAAO;OACb,SAAS;OACV;MAED,MAAM,iBAA0C;OAC9C;OACA,MAAM;QACJ,OAAQ,OAAO,KAAK,YAAuB;QAC3C,aAAc,OAAO,KAAK,kBAA6B;QACvD,cAAc;SACZ,MAAM,OAAO;SACb,KAAK;SACN;QACF;OACD,MAAM;QACJ,MAAM;QACN,QAAQ;QACR;QACD;OACF;AAED,aAAO,KAAK;OACV,YAAY,SAAS;OACrB;OACA,iBAAiB;SACdF,sCAAAA,aAAa;SACbC,sCAAAA,gBAAgB;QAClB;OACD,aAAa,EAAE;OAChB,CAAC;YACG;MACL,IAAI,gBAAgB;MACpB,IAAI,gBAAgB,OAAA,GAAA,2BAAA,wBAA6B,cAAc;AAE/D,UAAI,kBAAkB,KAAA,EAEpB,MAAK,MAAM,eAAe,CAAC,SAAS,OAAO,EAAE;AAC3C,uBAAgBH,UAAAA,QAAK,KAAK,SAAS,YAAY;AAC/C,uBAAgB,OAAA,GAAA,2BAAA,wBAA6B,cAAc;AAC3D,WAAI,kBAAkB,KAAA,EAAW;;AAGrC,UAAI,kBAAkB,KAAA,GAAW;OAC/B,MAAM,QAAQ,MAAM,oBAAoB,cAAc;OAEtD,MAAM,OAAO,MAAMK,2BAAAA,gCAAgC,WAAW,cAAc,KAAK;AAWjF,WAAI,KAAK,yBAAyB,KAAA,GAAW;QAI3C,MAAM,UAAUC,8BAAAA,2BAHM,MAAMR,QAAAA,QAAG,SAAS,SACtC,cAAc,WAAW,SAAS,KAAK,KACxC,CACwD;AACzD,YAAI,YAAY,KAAA,EAAW,MAAK,uBAAuB;;OAGzD,MAAM,iBAA0C;QAC9C,IAAI,cAAc;QAClB;QACA,cAAc,cAAc;QAC5B,MAAM;SACJ,MAAM;SACN,QAAQ;SACR;SACD;QACF;AAED,cAAO,KAAK;QACV,YAAY,SAAS;QACrB,IAAI,cAAc;QAClB,iBAAiB;UACdI,sCAAAA,aAAa;UACbC,sCAAAA,gBAAgB;SAClB;QACD,aAAa,EAAE;QAChB,CAAC;;;;AAMR,WAAO;GACT,QACE,SAAA,GAAA,2BAAA,aAAmB,QAAQ;;;CAIjC,MAAa,eAAe,SAA+D;EACzF,MAAM,aAAkC,EAAE;EAC1C,MAAM,aAA+B,EAAE;AACvC,OAAK,MAAM,YAAY,KAAK,YAAY;AACtC,cAAW,KAAK;IAAE,GAAG;IAAU,QAAQ;IAAU,CAAC;AAClD,cAAW,KAAK,GAAI,MAAM,KAAK,mBAAmB,UAAU,QAAQ,CAAE;;AAExE,SAAO;GAAE;GAAY;GAAY;;CAGnC,MAAa,YACX,YACA,SACA,SACkC;EAClC,MAAM,UAAU,KAAK,WAAW,MAAM,QAAQ,IAAI,OAAO,WAAW,EAAE;AACtE,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,qBAAqB,WAAW,aAAa;AAC3E,MAAI,QAAQ,SAAS,YACnB,OAAM,IAAI,MACR,mFACD;AAEH,SAAO,MADK,KAAK,WAAW,YAAY,QAAQ,IAAI,CACnC,oBAAoB,SAAS,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","names":[],"sources":["../../src/block_registry/registry.ts"],"mappings":";;;;;;iBA2CsB,mBAAA,CAAoB,OAAA,WAAkB,OAAA;AAAA,iBAWtC,mBAAA,CACpB,WAAA,EAAa,4BAAA,GACZ,OAAA;AAAA,cAQU,iBAAA;EAAA,iBAEQ,UAAA;EAAA,iBACA,UAAA;EAAA,iBACA,IAAA;cAFA,UAAA,EAAY,kBAAA,EACZ,UAAA,EAAY,aAAA,IACZ,IAAA,GAAO,UAAA;EAAA,QAGZ,kBAAA;EAyKD,cAAA,CAAe,OAAA;IAAY,MAAA,GAAS,WAAA;EAAA,IAAgB,OAAA,CAAQ,gBAAA;EAU5D,WAAA,CACX,UAAA,UACA,OAAA,EAAS,WAAA,EACT,OAAA,WACC,OAAA,CAAQ,uBAAA;AAAA"}
1
+ {"version":3,"file":"registry.d.ts","names":[],"sources":["../../src/block_registry/registry.ts"],"mappings":";;;;;;iBA4CsB,mBAAA,CAAoB,OAAA,WAAkB,OAAA;AAAA,iBAWtC,mBAAA,CACpB,WAAA,EAAa,4BAAA,GACZ,OAAA;AAAA,cAQU,iBAAA;EAAA,iBAEQ,UAAA;EAAA,iBACA,UAAA;EAAA,iBACA,IAAA;cAFA,UAAA,EAAY,kBAAA,EACZ,UAAA,EAAY,aAAA,IACZ,IAAA,GAAO,UAAA;EAAA,QAGZ,kBAAA;EA4LD,cAAA,CAAe,OAAA;IAAY,MAAA,GAAS,WAAA;EAAA,IAAgB,OAAA,CAAQ,gBAAA;EAU5D,WAAA,CACX,UAAA,UACA,OAAA,EAAS,WAAA,EACT,OAAA,WACC,OAAA,CAAQ,uBAAA;AAAA"}
@@ -1,4 +1,5 @@
1
1
  import { LegacyDevBlockPackFiles } from "../dev_env/index.js";
2
+ import { deriveRequiredCapabilities } from "../mutator/block-pack/required_capabilities.js";
2
3
  import { AnyChannel, StableChannel } from "@milaboratories/pl-model-middle-layer";
3
4
  import { request } from "undici";
4
5
  import { BlockPackMetaEmbedAbsoluteBytes, RegistryV1, tryLoadPackDescription } from "@platforma-sdk/block-tools";
@@ -143,9 +144,14 @@ var BlockPackRegistry = class {
143
144
  }
144
145
  if (v2Description !== void 0) {
145
146
  const mtime = await getDevV2PacketMtime(v2Description);
147
+ const meta = await BlockPackMetaEmbedAbsoluteBytes.parseAsync(v2Description.meta);
148
+ if (meta.requiredCapabilities === void 0) {
149
+ const derived = deriveRequiredCapabilities(await fs.promises.readFile(v2Description.components.workflow.main.file));
150
+ if (derived !== void 0) meta.requiredCapabilities = derived;
151
+ }
146
152
  const latestOverview = {
147
153
  id: v2Description.id,
148
- meta: await BlockPackMetaEmbedAbsoluteBytes.parseAsync(v2Description.meta),
154
+ meta,
149
155
  featureFlags: v2Description.featureFlags,
150
156
  spec: {
151
157
  type: "dev-v2",
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","names":[],"sources":["../../src/block_registry/registry.ts"],"sourcesContent":["import type { Dispatcher } from \"undici\";\nimport { request } from \"undici\";\nimport type { BlockPackDescriptionAbsolute } from \"@platforma-sdk/block-tools\";\nimport { BlockPackMetaEmbedAbsoluteBytes, RegistryV1 } from \"@platforma-sdk/block-tools\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport YAML from \"yaml\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport { LegacyDevBlockPackFiles } from \"../dev_env\";\nimport { tryLoadPackDescription } from \"@platforma-sdk/block-tools\";\nimport type { V2RegistryProvider } from \"./registry-v2-provider\";\nimport type {\n BlockPackId,\n BlockPackListing,\n BlockPackOverview,\n RegistryEntry,\n RegistryStatus,\n SingleBlockPackOverview,\n} from \"@milaboratories/pl-model-middle-layer\";\nimport { AnyChannel, StableChannel } from \"@milaboratories/pl-model-middle-layer\";\n\nasync function getFileContent(path: string) {\n try {\n return await fs.promises.readFile(path, \"utf8\");\n } catch (error: unknown) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n }\n}\n\nasync function getFileStat(path: string) {\n try {\n return await fs.promises.stat(path, { bigint: true });\n } catch (error: unknown) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n }\n}\n\nexport async function getDevV1PacketMtime(devPath: string): Promise<string> {\n let mtime = 0n;\n for (const f of LegacyDevBlockPackFiles) {\n const fullPath = path.join(devPath, ...f);\n const stat = await getFileStat(fullPath);\n if (stat === undefined) continue;\n if (mtime < stat.mtimeNs) mtime = stat.mtimeNs;\n }\n return mtime.toString();\n}\n\nexport async function getDevV2PacketMtime(\n description: BlockPackDescriptionAbsolute,\n): Promise<string> {\n const wfStats = await fs.promises.stat(description.components.workflow.main.file, {\n bigint: true,\n });\n const modelStats = await fs.promises.stat(description.components.model.file, { bigint: true });\n return (wfStats.mtimeNs > modelStats.mtimeNs ? wfStats.mtimeNs : modelStats.mtimeNs).toString();\n}\n\nexport class BlockPackRegistry {\n constructor(\n private readonly v2Provider: V2RegistryProvider,\n private readonly registries: RegistryEntry[],\n private readonly http?: Dispatcher,\n ) {}\n\n private async getPackagesForRoot(\n regEntry: RegistryEntry,\n options?: { signal?: AbortSignal },\n ): Promise<BlockPackOverview[]> {\n const result: BlockPackOverview[] = [];\n const regSpec = regEntry.spec;\n switch (regSpec.type) {\n case \"remote-v1\": {\n const httpOptions = this.http !== undefined ? { dispatcher: this.http } : {};\n\n const overviewResponse = await request(`${regSpec.url}/${RegistryV1.GlobalOverviewPath}`, {\n ...httpOptions,\n signal: options?.signal,\n });\n const overview = (await overviewResponse.body.json()) as RegistryV1.GlobalOverview;\n for (const overviewEntry of overview) {\n const { organization, package: pkg, latestMeta, latestVersion } = overviewEntry;\n const id = {\n organization,\n name: pkg,\n version: latestVersion,\n };\n const latestOverview: SingleBlockPackOverview = {\n id,\n meta: {\n title: (latestMeta[\"title\"] as string | undefined) ?? \"No title\",\n description: (latestMeta[\"description\"] as string | undefined) ?? \"No Description\",\n organization: {\n name: organization,\n url: \"https://unknown.com\",\n },\n },\n spec: {\n type: \"from-registry-v1\",\n id,\n registryUrl: regSpec.url,\n },\n };\n result.push({\n registryId: regEntry.id,\n id,\n latestByChannel: {\n [AnyChannel]: latestOverview,\n [StableChannel]: latestOverview,\n },\n allVersions: overviewEntry.allVersions.map((v) => ({ version: v, channels: [] })),\n });\n }\n return result;\n }\n\n case \"remote-v2\":\n return (\n await this.v2Provider.getRegistry(regSpec.url).listBlockPacks({ signal: options?.signal })\n ).map((e) => ({\n ...e,\n registryId: regEntry.id,\n }));\n // e.latestByChannel[StableChannel]\n // ? {\n // ...e,\n // registryId: regEntry.id,\n // }\n // : {\n // ...e,\n // latestByChannel: {\n // ...e.latestByChannel,\n // [StableChannel]: ((s: SingleBlockPackOverview) => {\n // if (s.spec.type === 'from-registry-v2') {\n // return { ...s, spec: { ...s.spec, channel: StableChannel } };\n // }\n\n // return s;\n // })(e.latestByChannel[AnyChannel]),\n // },\n // registryId: regEntry.id,\n // },\n\n case \"local-dev\":\n for (const entry of await fs.promises.readdir(regSpec.path, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const devPath = path.join(regSpec.path, entry.name);\n\n const legacyYamlContent = await getFileContent(\n path.join(devPath, RegistryV1.PlPackageYamlConfigFile),\n );\n if (legacyYamlContent !== undefined) {\n const config = RegistryV1.PlPackageConfigData.parse(YAML.parse(legacyYamlContent));\n\n const mtime = await getDevV1PacketMtime(devPath);\n\n const id = {\n organization: config.organization,\n name: config.package,\n version: \"DEV\",\n };\n\n const latestOverview: SingleBlockPackOverview = {\n id,\n meta: {\n title: (config.meta[\"title\"] as string) ?? \"No title\",\n description: (config.meta[\"description\"] as string) ?? \"No Description\",\n organization: {\n name: config.organization,\n url: \"https://unknown.com\",\n },\n },\n spec: {\n type: \"dev-v2\",\n folder: devPath,\n mtime,\n },\n };\n\n result.push({\n registryId: regEntry.id,\n id,\n latestByChannel: {\n [AnyChannel]: latestOverview,\n [StableChannel]: latestOverview,\n },\n allVersions: [],\n });\n } else {\n let actualDevPath = devPath;\n let v2Description = await tryLoadPackDescription(actualDevPath);\n\n if (v2Description === undefined)\n // iterating over expected subfolder names where block developer may put root block-pack package\n for (const bpSubfolder of [\"block\", \"meta\"]) {\n actualDevPath = path.join(devPath, bpSubfolder);\n v2Description = await tryLoadPackDescription(actualDevPath);\n if (v2Description !== undefined) break;\n }\n\n if (v2Description !== undefined) {\n const mtime = await getDevV2PacketMtime(v2Description);\n\n const latestOverview: SingleBlockPackOverview = {\n id: v2Description.id,\n meta: await BlockPackMetaEmbedAbsoluteBytes.parseAsync(v2Description.meta),\n featureFlags: v2Description.featureFlags,\n spec: {\n type: \"dev-v2\",\n folder: actualDevPath,\n mtime,\n },\n };\n\n result.push({\n registryId: regEntry.id,\n id: v2Description.id,\n latestByChannel: {\n [AnyChannel]: latestOverview,\n [StableChannel]: latestOverview,\n },\n allVersions: [],\n });\n }\n }\n\n continue;\n }\n return result;\n default:\n return assertNever(regSpec);\n }\n }\n\n public async listBlockPacks(options?: { signal?: AbortSignal }): Promise<BlockPackListing> {\n const blockPacks: BlockPackOverview[] = [];\n const registries: RegistryStatus[] = [];\n for (const regSpecs of this.registries) {\n registries.push({ ...regSpecs, status: \"online\" });\n blockPacks.push(...(await this.getPackagesForRoot(regSpecs, options)));\n }\n return { registries, blockPacks };\n }\n\n public async getOverview(\n registryId: string,\n blockId: BlockPackId,\n channel: string,\n ): Promise<SingleBlockPackOverview> {\n const regSpec = this.registries.find((reg) => reg.id === registryId)?.spec;\n if (!regSpec) throw new Error(`Registry with id \"${registryId}\" not found`);\n if (regSpec.type !== \"remote-v2\")\n throw new Error(\n `Only \"remote-v2\" registries support specific package version overview retrieval.`,\n );\n const reg = this.v2Provider.getRegistry(regSpec.url);\n return await reg.getSpecificOverview(blockId, channel);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"registry.js","names":[],"sources":["../../src/block_registry/registry.ts"],"sourcesContent":["import type { Dispatcher } from \"undici\";\nimport { request } from \"undici\";\nimport type { BlockPackDescriptionAbsolute } from \"@platforma-sdk/block-tools\";\nimport { BlockPackMetaEmbedAbsoluteBytes, RegistryV1 } from \"@platforma-sdk/block-tools\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport YAML from \"yaml\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport { LegacyDevBlockPackFiles } from \"../dev_env\";\nimport { tryLoadPackDescription } from \"@platforma-sdk/block-tools\";\nimport { deriveRequiredCapabilities } from \"../mutator/block-pack/required_capabilities\";\nimport type { V2RegistryProvider } from \"./registry-v2-provider\";\nimport type {\n BlockPackId,\n BlockPackListing,\n BlockPackOverview,\n RegistryEntry,\n RegistryStatus,\n SingleBlockPackOverview,\n} from \"@milaboratories/pl-model-middle-layer\";\nimport { AnyChannel, StableChannel } from \"@milaboratories/pl-model-middle-layer\";\n\nasync function getFileContent(path: string) {\n try {\n return await fs.promises.readFile(path, \"utf8\");\n } catch (error: unknown) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n }\n}\n\nasync function getFileStat(path: string) {\n try {\n return await fs.promises.stat(path, { bigint: true });\n } catch (error: unknown) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n }\n}\n\nexport async function getDevV1PacketMtime(devPath: string): Promise<string> {\n let mtime = 0n;\n for (const f of LegacyDevBlockPackFiles) {\n const fullPath = path.join(devPath, ...f);\n const stat = await getFileStat(fullPath);\n if (stat === undefined) continue;\n if (mtime < stat.mtimeNs) mtime = stat.mtimeNs;\n }\n return mtime.toString();\n}\n\nexport async function getDevV2PacketMtime(\n description: BlockPackDescriptionAbsolute,\n): Promise<string> {\n const wfStats = await fs.promises.stat(description.components.workflow.main.file, {\n bigint: true,\n });\n const modelStats = await fs.promises.stat(description.components.model.file, { bigint: true });\n return (wfStats.mtimeNs > modelStats.mtimeNs ? wfStats.mtimeNs : modelStats.mtimeNs).toString();\n}\n\nexport class BlockPackRegistry {\n constructor(\n private readonly v2Provider: V2RegistryProvider,\n private readonly registries: RegistryEntry[],\n private readonly http?: Dispatcher,\n ) {}\n\n private async getPackagesForRoot(\n regEntry: RegistryEntry,\n options?: { signal?: AbortSignal },\n ): Promise<BlockPackOverview[]> {\n const result: BlockPackOverview[] = [];\n const regSpec = regEntry.spec;\n switch (regSpec.type) {\n case \"remote-v1\": {\n const httpOptions = this.http !== undefined ? { dispatcher: this.http } : {};\n\n const overviewResponse = await request(`${regSpec.url}/${RegistryV1.GlobalOverviewPath}`, {\n ...httpOptions,\n signal: options?.signal,\n });\n const overview = (await overviewResponse.body.json()) as RegistryV1.GlobalOverview;\n for (const overviewEntry of overview) {\n const { organization, package: pkg, latestMeta, latestVersion } = overviewEntry;\n const id = {\n organization,\n name: pkg,\n version: latestVersion,\n };\n const latestOverview: SingleBlockPackOverview = {\n id,\n meta: {\n title: (latestMeta[\"title\"] as string | undefined) ?? \"No title\",\n description: (latestMeta[\"description\"] as string | undefined) ?? \"No Description\",\n organization: {\n name: organization,\n url: \"https://unknown.com\",\n },\n },\n spec: {\n type: \"from-registry-v1\",\n id,\n registryUrl: regSpec.url,\n },\n };\n result.push({\n registryId: regEntry.id,\n id,\n latestByChannel: {\n [AnyChannel]: latestOverview,\n [StableChannel]: latestOverview,\n },\n allVersions: overviewEntry.allVersions.map((v) => ({ version: v, channels: [] })),\n });\n }\n return result;\n }\n\n case \"remote-v2\":\n return (\n await this.v2Provider.getRegistry(regSpec.url).listBlockPacks({ signal: options?.signal })\n ).map((e) => ({\n ...e,\n registryId: regEntry.id,\n }));\n // e.latestByChannel[StableChannel]\n // ? {\n // ...e,\n // registryId: regEntry.id,\n // }\n // : {\n // ...e,\n // latestByChannel: {\n // ...e.latestByChannel,\n // [StableChannel]: ((s: SingleBlockPackOverview) => {\n // if (s.spec.type === 'from-registry-v2') {\n // return { ...s, spec: { ...s.spec, channel: StableChannel } };\n // }\n\n // return s;\n // })(e.latestByChannel[AnyChannel]),\n // },\n // registryId: regEntry.id,\n // },\n\n case \"local-dev\":\n for (const entry of await fs.promises.readdir(regSpec.path, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const devPath = path.join(regSpec.path, entry.name);\n\n const legacyYamlContent = await getFileContent(\n path.join(devPath, RegistryV1.PlPackageYamlConfigFile),\n );\n if (legacyYamlContent !== undefined) {\n const config = RegistryV1.PlPackageConfigData.parse(YAML.parse(legacyYamlContent));\n\n const mtime = await getDevV1PacketMtime(devPath);\n\n const id = {\n organization: config.organization,\n name: config.package,\n version: \"DEV\",\n };\n\n const latestOverview: SingleBlockPackOverview = {\n id,\n meta: {\n title: (config.meta[\"title\"] as string) ?? \"No title\",\n description: (config.meta[\"description\"] as string) ?? \"No Description\",\n organization: {\n name: config.organization,\n url: \"https://unknown.com\",\n },\n },\n spec: {\n type: \"dev-v2\",\n folder: devPath,\n mtime,\n },\n };\n\n result.push({\n registryId: regEntry.id,\n id,\n latestByChannel: {\n [AnyChannel]: latestOverview,\n [StableChannel]: latestOverview,\n },\n allVersions: [],\n });\n } else {\n let actualDevPath = devPath;\n let v2Description = await tryLoadPackDescription(actualDevPath);\n\n if (v2Description === undefined)\n // iterating over expected subfolder names where block developer may put root block-pack package\n for (const bpSubfolder of [\"block\", \"meta\"]) {\n actualDevPath = path.join(devPath, bpSubfolder);\n v2Description = await tryLoadPackDescription(actualDevPath);\n if (v2Description !== undefined) break;\n }\n\n if (v2Description !== undefined) {\n const mtime = await getDevV2PacketMtime(v2Description);\n\n const meta = await BlockPackMetaEmbedAbsoluteBytes.parseAsync(v2Description.meta);\n\n // `tryLoadPackDescription` reads `package.json#block.meta` —\n // the dev SOURCE meta the developer wrote.\n // `requiredCapabilities` is a PACK-TIME artifact written by\n // `block-tools pack` into `block-pack/manifest.json`.\n // So for any dev block, the source meta never carries the field.\n //\n // To surface the gate at listing time without requiring the\n // developer to re-pack on every workflow change, derive from\n // the compiled workflow bytes directly.\n if (meta.requiredCapabilities === undefined) {\n const workflowBytes = await fs.promises.readFile(\n v2Description.components.workflow.main.file,\n );\n const derived = deriveRequiredCapabilities(workflowBytes);\n if (derived !== undefined) meta.requiredCapabilities = derived;\n }\n\n const latestOverview: SingleBlockPackOverview = {\n id: v2Description.id,\n meta,\n featureFlags: v2Description.featureFlags,\n spec: {\n type: \"dev-v2\",\n folder: actualDevPath,\n mtime,\n },\n };\n\n result.push({\n registryId: regEntry.id,\n id: v2Description.id,\n latestByChannel: {\n [AnyChannel]: latestOverview,\n [StableChannel]: latestOverview,\n },\n allVersions: [],\n });\n }\n }\n\n continue;\n }\n return result;\n default:\n return assertNever(regSpec);\n }\n }\n\n public async listBlockPacks(options?: { signal?: AbortSignal }): Promise<BlockPackListing> {\n const blockPacks: BlockPackOverview[] = [];\n const registries: RegistryStatus[] = [];\n for (const regSpecs of this.registries) {\n registries.push({ ...regSpecs, status: \"online\" });\n blockPacks.push(...(await this.getPackagesForRoot(regSpecs, options)));\n }\n return { registries, blockPacks };\n }\n\n public async getOverview(\n registryId: string,\n blockId: BlockPackId,\n channel: string,\n ): Promise<SingleBlockPackOverview> {\n const regSpec = this.registries.find((reg) => reg.id === registryId)?.spec;\n if (!regSpec) throw new Error(`Registry with id \"${registryId}\" not found`);\n if (regSpec.type !== \"remote-v2\")\n throw new Error(\n `Only \"remote-v2\" registries support specific package version overview retrieval.`,\n );\n const reg = this.v2Provider.getRegistry(regSpec.url);\n return await reg.getSpecificOverview(blockId, channel);\n }\n}\n"],"mappings":"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"}
@@ -56,6 +56,15 @@ var MiddleLayer = class MiddleLayer {
56
56
  get serverPlatform() {
57
57
  return this.pl.serverInfo.platform;
58
58
  }
59
+ /**
60
+ * Runtime capabilities advertised by the connected backend (tokens of
61
+ * the form `<feature>:<version>`, e.g. "wasm:v1"). Empty list if the
62
+ * backend predates the capability mechanism — that's the desired
63
+ * fail-closed behaviour for blocks declaring any `requiredCapabilities`.
64
+ */
65
+ get serverCapabilities() {
66
+ return this.pl.serverInfo.capabilities ?? [];
67
+ }
59
68
  /** Adds a runtime capability to the middle layer. */
60
69
  addRuntimeCapability(requirement, value = true) {
61
70
  this.env.runtimeCapabilities.addSupportedRequirement(requirement, value);
@@ -1 +1 @@
1
- {"version":3,"file":"middle_layer.cjs","names":["LRUCache","createProject","withProjectAuthored","ProjectMetaKey","isNotNullSignedResourceId","duplicateProject","Project","HmacSha256Signer","DefaultMiddleLayerOpsSettings","DefaultMiddleLayerOpsPaths","getDebugFlags","ProjectsField","ProjectsResourceType","initDriverKit","RetryAgent","V2RegistryProvider","BlockPackPreparer","RuntimeCapabilities","REQUIRES_PFRAMES_VERSION","createModelServiceRegistry","BlockEventDispatcher","BlockUpdateWatcher","ProjectHelper","WatchableValue","createProjectList"],"sources":["../../src/middle_layer/middle_layer.ts"],"sourcesContent":["import type { PlClient, SignedResourceId, ResourceRef } from \"@milaboratories/pl-client\";\nimport {\n field,\n isNotNullSignedResourceId,\n isNullSignedResourceId,\n resourceIdToString,\n} from \"@milaboratories/pl-client\";\nimport { LRUCache } from \"lru-cache\";\nimport { createProjectList, ProjectsField, ProjectsResourceType } from \"./project_list\";\nimport { createProject, duplicateProject, withProjectAuthored } from \"../mutator/project\";\nimport { ProjectMetaKey } from \"../model/project_model\";\nimport type { ProjectId } from \"../model/project_model\";\nimport type { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport { BlockPackPreparer } from \"../mutator/block-pack/block_pack\";\nimport type { MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { BlockEventDispatcher } from \"@milaboratories/ts-helpers\";\nimport { HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport type { ComputableStableDefined } from \"@milaboratories/computable\";\nimport { WatchableValue } from \"@milaboratories/computable\";\nimport { Project } from \"./project\";\nimport type { MiddleLayerOps, MiddleLayerOpsConstructor } from \"./ops\";\nimport { DefaultMiddleLayerOpsPaths, DefaultMiddleLayerOpsSettings } from \"./ops\";\nimport { randomUUID } from \"node:crypto\";\nimport type { ProjectListEntry } from \"../model\";\nimport type {\n AuthorMarker,\n ProjectMeta,\n BlockPlatform,\n} from \"@milaboratories/pl-model-middle-layer\";\nimport { BlockUpdateWatcher } from \"../block_registry/watcher\";\nimport type { QuickJSWASMModule } from \"quickjs-emscripten\";\nimport { getQuickJS } from \"quickjs-emscripten\";\nimport type { MiddleLayerDriverKit } from \"./driver_kit\";\nimport { initDriverKit } from \"./driver_kit\";\nimport type { BlockCodeFeatureFlags, DriverKit, SupportedRequirement } from \"@platforma-sdk/model\";\nimport { RuntimeCapabilities } from \"@platforma-sdk/model\";\nimport {\n type ModelServiceRegistry,\n registerServiceCapabilities,\n REQUIRES_PFRAMES_VERSION,\n} from \"@milaboratories/pl-model-common\";\nimport { createModelServiceRegistry } from \"../service_factories\";\nimport type { DownloadUrlDriver } from \"@milaboratories/pl-drivers\";\nimport { V2RegistryProvider } from \"../block_registry\";\nimport type { Dispatcher } from \"undici\";\nimport { RetryAgent } from \"undici\";\nimport { getDebugFlags } from \"../debug\";\nimport { ProjectHelper } from \"../model/project_helper\";\n\nexport interface MiddleLayerEnvironment {\n dispose(): Promise<void>;\n readonly pl: PlClient;\n readonly runtimeCapabilities: RuntimeCapabilities;\n readonly logger: MiLogger;\n readonly blockEventDispatcher: BlockEventDispatcher;\n readonly httpDispatcher: Dispatcher;\n readonly retryHttpDispatcher: Dispatcher;\n readonly signer: Signer;\n readonly ops: MiddleLayerOps;\n readonly bpPreparer: BlockPackPreparer;\n readonly frontendDownloadDriver: DownloadUrlDriver;\n readonly blockUpdateWatcher: BlockUpdateWatcher;\n readonly quickJs: QuickJSWASMModule;\n readonly driverKit: MiddleLayerDriverKit;\n readonly serviceRegistry: ModelServiceRegistry;\n readonly projectHelper: ProjectHelper;\n}\n\n/**\n * Main access object to work with pl from UI.\n *\n * It implements an abstraction layer of projects and blocks.\n *\n * As a main entry point inside the pl, this object uses a resource attached\n * via the {@link ProjectsField} to the pl client's root, this resource\n * contains project list.\n *\n * Read about alternative roots, if isolated project lists (working environments)\n * are required.\n * */\nexport class MiddleLayer {\n public readonly pl: PlClient;\n\n /** Contains a reactive list of projects along with their meta information. */\n public readonly projectList: ComputableStableDefined<ProjectListEntry[]>;\n\n private constructor(\n private readonly env: MiddleLayerEnvironment,\n public readonly driverKit: DriverKit,\n public readonly signer: Signer,\n private readonly projectListResourceId: SignedResourceId,\n private readonly openedProjectsList: WatchableValue<ProjectId[]>,\n private readonly projectListTree: SynchronizedTreeState,\n public readonly blockRegistryProvider: V2RegistryProvider,\n projectList: ComputableStableDefined<ProjectListEntry[]>,\n ) {\n this.projectList = projectList;\n this.pl = this.env.pl;\n }\n\n /**\n * Get the OS where backend is running.\n * For old backend versions returns undefined.\n */\n public get serverPlatform(): BlockPlatform | undefined {\n return this.pl.serverInfo.platform as BlockPlatform | undefined;\n }\n\n /** Adds a runtime capability to the middle layer. */\n public addRuntimeCapability(\n requirement: SupportedRequirement,\n value: number | boolean = true,\n ): void {\n this.env.runtimeCapabilities.addSupportedRequirement(requirement, value);\n }\n\n /** Checks if the given block feature flags are compatible with the runtime capabilities. */\n public checkBlockCompatibility(featureFlags: BlockCodeFeatureFlags | undefined): boolean {\n return this.env.runtimeCapabilities.checkCompatibility(featureFlags);\n }\n\n /** Returns extended API driver kit used internally by middle layer. */\n public get internalDriverKit(): MiddleLayerDriverKit {\n return this.env.driverKit;\n }\n\n /** Returns the service registry for service introspection. */\n public get serviceRegistry(): ModelServiceRegistry {\n return this.env.serviceRegistry;\n }\n\n //\n // ProjectId ↔ SignedResourceId resolution\n //\n\n private readonly projectIdCache = new LRUCache<ProjectId, SignedResourceId>({ max: 1024 });\n\n /** Resolves a ProjectId to a signed SignedResourceId.\n * Uses LRU cache with TX-scan fallback. */\n private async resolveProjectId(projectId: ProjectId): Promise<SignedResourceId> {\n const cached = this.projectIdCache.get(projectId);\n if (cached !== undefined) return cached;\n\n // Cache miss — scan project list fields to find the matching resource\n const rid = await this.pl.withReadTx(\"ResolveProjectId\", async (tx) => {\n const data = await tx.getResourceData(this.projectListResourceId, true);\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (resourceIdToString(f.value) === (projectId as string)) return f.value;\n }\n throw new Error(`Project ${projectId} not found in project list.`);\n });\n\n this.projectIdCache.set(projectId, rid);\n return rid;\n }\n\n //\n // Project List Manipulation\n //\n\n /** Creates a project with initial state and adds it to project list. */\n public async createProject(meta: ProjectMeta): Promise<ProjectId> {\n let prj: ResourceRef;\n await this.pl.withWriteTx(\"MLCreateProject\", async (tx) => {\n prj = await createProject(tx, meta);\n tx.createField(field(this.projectListResourceId, randomUUID()), \"Dynamic\", prj);\n await tx.commit();\n });\n await this.projectListTree.refreshState();\n\n const signedRid = await prj!.globalId;\n const projectId = resourceIdToString(signedRid) as ProjectId;\n this.projectIdCache.set(projectId, signedRid);\n return projectId;\n }\n\n /** Updates project metadata */\n public async setProjectMeta(\n id: ProjectId,\n meta: ProjectMeta,\n author?: AuthorMarker,\n ): Promise<void> {\n const rid = await this.resolveProjectId(id);\n await withProjectAuthored(\n this.env.projectHelper,\n this.pl,\n rid,\n author,\n (prj) => {\n prj.setMeta(meta);\n },\n { name: \"setProjectMeta\" },\n );\n await this.projectListTree.refreshState();\n }\n\n /** Permanently deletes project from the project list, this will result in\n * destruction of all attached objects, like files, analysis results etc. */\n public async deleteProject(id: ProjectId): Promise<void> {\n await this.pl.withWriteTx(\"MLRemoveProject\", async (tx) => {\n const data = await tx.getResourceData(this.projectListResourceId, true);\n let fieldName: string | undefined;\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (resourceIdToString(f.value) === (id as string)) {\n fieldName = f.name;\n break;\n }\n }\n if (fieldName === undefined) throw new Error(`Project ${id} not found in project list.`);\n tx.removeField(field(this.projectListResourceId, fieldName));\n await tx.commit();\n });\n this.projectIdCache.delete(id);\n await this.projectListTree.refreshState();\n }\n\n /**\n * Duplicates an existing project and adds the copy to this user's project list.\n *\n * @param srcProjectId - project id of the project to duplicate\n * @param rename - optional function that receives the source label and all existing\n * project labels (read within the same transaction), and returns the label for the copy\n */\n public async duplicateProject(\n srcProjectId: ProjectId,\n rename?: (previousLabel: string, existingLabels: string[]) => string,\n ): Promise<ProjectId> {\n const sourceRid = await this.resolveProjectId(srcProjectId);\n\n const newPrj: ResourceRef = await this.pl.withWriteTx(\"MLDuplicateProject\", async (tx) => {\n // Read source project meta\n const sourceMeta = await tx.getKValueJson<ProjectMeta>(sourceRid, ProjectMetaKey);\n\n // Read all existing project labels from the project list (parallel reads)\n const projectListData = await tx.getResourceData(this.projectListResourceId, true);\n const projectRids = projectListData.fields\n .map((f) => f.value)\n .filter(isNotNullSignedResourceId);\n const existingLabels = (\n await Promise.all(\n projectRids.map((rid) => tx.getKValueJson<ProjectMeta>(rid, ProjectMetaKey)),\n )\n ).map((m) => m.label);\n\n // Compute new label\n const newLabel = rename ? rename(sourceMeta.label, existingLabels) : sourceMeta.label;\n\n // Create the duplicate\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n\n // Attach to project list with a random UUID field name\n tx.createField(field(this.projectListResourceId, randomUUID()), \"Dynamic\", newPrj);\n await tx.commit();\n\n return newPrj;\n });\n\n await this.projectListTree.refreshState();\n\n const signedRid = await newPrj.globalId;\n const newProjectId = resourceIdToString(signedRid) as ProjectId;\n this.projectIdCache.set(newProjectId, signedRid);\n return newProjectId;\n }\n\n //\n // Projects\n //\n\n private readonly openedProjects = new Map<ProjectId, Project>();\n\n /** Opens a project, and starts corresponding project maintenance loop. */\n public async openProject(id: ProjectId): Promise<void> {\n if (this.openedProjects.has(id)) throw new Error(`Project ${id} already opened`);\n const rid = await this.resolveProjectId(id);\n this.openedProjects.set(id, await Project.init(this.env, id, rid));\n this.openedProjectsList.setValue([...this.openedProjects.keys()]);\n }\n\n /** Closes the project, and deallocate all corresponding resources. */\n public async closeProject(id: ProjectId): Promise<void> {\n const prj = this.openedProjects.get(id);\n if (prj === undefined) throw new Error(`Project ${id} not found among opened projects`);\n this.openedProjects.delete(id);\n await prj.destroy();\n this.openedProjectsList.setValue([...this.openedProjects.keys()]);\n }\n\n /** Returns a project access object for an opened project. */\n public getOpenedProject(id: ProjectId): Project {\n const prj = this.openedProjects.get(id);\n if (prj === undefined) throw new Error(`Project ${id} not found among opened projects`);\n return prj;\n }\n\n /** Returns true if project with given id is currently opened. */\n public isProjectOpened(id: ProjectId): boolean {\n return this.openedProjects.has(id);\n }\n\n /**\n * Deallocates all runtime resources consumed by this object and awaits\n * actual termination of event loops and other processes associated with\n * them.\n */\n public async close() {\n await Promise.all([...this.openedProjects.values()].map((prj) => prj.destroy()));\n // this.env.quickJs;\n await this.projectListTree.terminate();\n await this.env.dispose();\n await this.pl.close();\n }\n\n /** @deprecated */\n public async closeAndAwaitTermination() {\n await this.close();\n }\n\n /** Generates sufficiently random string to be used as local secret for the\n * middle layer */\n public static generateLocalSecret(): string {\n return HmacSha256Signer.generateSecret();\n }\n\n /** Returns a block event dispatcher, which can be used to listen to block events. */\n public get blockEventDispatcher(): BlockEventDispatcher {\n return this.env.blockEventDispatcher;\n }\n\n /** Initialize middle layer */\n public static async init(\n pl: PlClient,\n workdir: string,\n _ops: MiddleLayerOpsConstructor,\n ): Promise<MiddleLayer> {\n const ops: MiddleLayerOps = {\n ...DefaultMiddleLayerOpsSettings,\n ...DefaultMiddleLayerOpsPaths(workdir),\n ..._ops,\n };\n\n // overriding debug options from environment variables\n ops.defaultTreeOptions.logStat = getDebugFlags().logTreeStats;\n ops.debugOps.dumpInitialTreeState = getDebugFlags().dumpInitialTreeState;\n // apply MI_TREE_TRAVERSAL only when the embedder hasn't set an explicit mode\n if (\n ops.defaultTreeOptions.traversalMode === undefined &&\n getDebugFlags().treeTraversalMode !== undefined\n )\n ops.defaultTreeOptions.traversalMode = getDebugFlags().treeTraversalMode;\n\n const projects = await pl.withWriteTx(\"MLInitialization\", async (tx) => {\n const projectsField = field(tx.clientRoot, ProjectsField);\n tx.createField(projectsField, \"Dynamic\");\n const projectsFieldData = await tx.getField(projectsField);\n if (isNullSignedResourceId(projectsFieldData.value)) {\n const projects = tx.createEphemeral(ProjectsResourceType);\n tx.lock(projects);\n\n tx.setField(projectsField, projects);\n\n await tx.commit();\n\n return await projects.globalId;\n } else {\n return projectsFieldData.value;\n }\n });\n\n const logger = ops.logger;\n\n const driverKit = await initDriverKit(pl, workdir, ops.frontendDownloadPath, ops);\n\n // passed to components having no own retry logic\n const retryHttpDispatcher = new RetryAgent(pl.httpDispatcher);\n\n const v2RegistryProvider = new V2RegistryProvider(retryHttpDispatcher);\n\n const bpPreparer = new BlockPackPreparer(\n v2RegistryProvider,\n driverKit.signer,\n retryHttpDispatcher,\n );\n\n const quickJs = await getQuickJS();\n\n const runtimeCapabilities = new RuntimeCapabilities();\n // add runtime capabilities of model here\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 1);\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresCreatePTable\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresPFramesVersion\", REQUIRES_PFRAMES_VERSION);\n registerServiceCapabilities((flag, value) =>\n runtimeCapabilities.addSupportedRequirement(flag, value),\n );\n // runtime capabilities of the desktop are to be added by the desktop app / test framework\n\n const serviceRegistry = createModelServiceRegistry({ logger });\n\n const env: MiddleLayerEnvironment = {\n pl,\n blockEventDispatcher: new BlockEventDispatcher(),\n signer: driverKit.signer,\n logger,\n httpDispatcher: pl.httpDispatcher,\n retryHttpDispatcher,\n ops,\n bpPreparer,\n frontendDownloadDriver: driverKit.frontendDriver,\n driverKit,\n blockUpdateWatcher: new BlockUpdateWatcher(v2RegistryProvider, logger, {\n minDelay: ops.devBlockUpdateRecheckInterval,\n http: retryHttpDispatcher,\n preferredUpdateChannel: ops.preferredUpdateChannel,\n }),\n runtimeCapabilities,\n serviceRegistry,\n quickJs,\n projectHelper: new ProjectHelper(quickJs, logger),\n dispose: async () => {\n await serviceRegistry.dispose();\n await retryHttpDispatcher.destroy();\n await driverKit.dispose();\n },\n };\n\n const openedProjects = new WatchableValue<ProjectId[]>([]);\n const projectListTC = await createProjectList(pl, projects, openedProjects, env);\n\n return new MiddleLayer(\n env,\n driverKit,\n driverKit.signer,\n projects,\n openedProjects,\n projectListTC.tree,\n v2RegistryProvider,\n projectListTC.computable,\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"middle_layer.cjs","names":["LRUCache","createProject","withProjectAuthored","ProjectMetaKey","isNotNullSignedResourceId","duplicateProject","Project","HmacSha256Signer","DefaultMiddleLayerOpsSettings","DefaultMiddleLayerOpsPaths","getDebugFlags","ProjectsField","ProjectsResourceType","initDriverKit","RetryAgent","V2RegistryProvider","BlockPackPreparer","RuntimeCapabilities","REQUIRES_PFRAMES_VERSION","createModelServiceRegistry","BlockEventDispatcher","BlockUpdateWatcher","ProjectHelper","WatchableValue","createProjectList"],"sources":["../../src/middle_layer/middle_layer.ts"],"sourcesContent":["import type { PlClient, SignedResourceId, ResourceRef } from \"@milaboratories/pl-client\";\nimport {\n field,\n isNotNullSignedResourceId,\n isNullSignedResourceId,\n resourceIdToString,\n} from \"@milaboratories/pl-client\";\nimport { LRUCache } from \"lru-cache\";\nimport { createProjectList, ProjectsField, ProjectsResourceType } from \"./project_list\";\nimport { createProject, duplicateProject, withProjectAuthored } from \"../mutator/project\";\nimport { ProjectMetaKey } from \"../model/project_model\";\nimport type { ProjectId } from \"../model/project_model\";\nimport type { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport { BlockPackPreparer } from \"../mutator/block-pack/block_pack\";\nimport type { MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { BlockEventDispatcher } from \"@milaboratories/ts-helpers\";\nimport { HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport type { ComputableStableDefined } from \"@milaboratories/computable\";\nimport { WatchableValue } from \"@milaboratories/computable\";\nimport { Project } from \"./project\";\nimport type { MiddleLayerOps, MiddleLayerOpsConstructor } from \"./ops\";\nimport { DefaultMiddleLayerOpsPaths, DefaultMiddleLayerOpsSettings } from \"./ops\";\nimport { randomUUID } from \"node:crypto\";\nimport type { ProjectListEntry } from \"../model\";\nimport type {\n AuthorMarker,\n ProjectMeta,\n BlockPlatform,\n} from \"@milaboratories/pl-model-middle-layer\";\nimport { BlockUpdateWatcher } from \"../block_registry/watcher\";\nimport type { QuickJSWASMModule } from \"quickjs-emscripten\";\nimport { getQuickJS } from \"quickjs-emscripten\";\nimport type { MiddleLayerDriverKit } from \"./driver_kit\";\nimport { initDriverKit } from \"./driver_kit\";\nimport type { BlockCodeFeatureFlags, DriverKit, SupportedRequirement } from \"@platforma-sdk/model\";\nimport { RuntimeCapabilities } from \"@platforma-sdk/model\";\nimport {\n type ModelServiceRegistry,\n registerServiceCapabilities,\n REQUIRES_PFRAMES_VERSION,\n} from \"@milaboratories/pl-model-common\";\nimport { createModelServiceRegistry } from \"../service_factories\";\nimport type { DownloadUrlDriver } from \"@milaboratories/pl-drivers\";\nimport { V2RegistryProvider } from \"../block_registry\";\nimport type { Dispatcher } from \"undici\";\nimport { RetryAgent } from \"undici\";\nimport { getDebugFlags } from \"../debug\";\nimport { ProjectHelper } from \"../model/project_helper\";\n\nexport interface MiddleLayerEnvironment {\n dispose(): Promise<void>;\n readonly pl: PlClient;\n readonly runtimeCapabilities: RuntimeCapabilities;\n readonly logger: MiLogger;\n readonly blockEventDispatcher: BlockEventDispatcher;\n readonly httpDispatcher: Dispatcher;\n readonly retryHttpDispatcher: Dispatcher;\n readonly signer: Signer;\n readonly ops: MiddleLayerOps;\n readonly bpPreparer: BlockPackPreparer;\n readonly frontendDownloadDriver: DownloadUrlDriver;\n readonly blockUpdateWatcher: BlockUpdateWatcher;\n readonly quickJs: QuickJSWASMModule;\n readonly driverKit: MiddleLayerDriverKit;\n readonly serviceRegistry: ModelServiceRegistry;\n readonly projectHelper: ProjectHelper;\n}\n\n/**\n * Main access object to work with pl from UI.\n *\n * It implements an abstraction layer of projects and blocks.\n *\n * As a main entry point inside the pl, this object uses a resource attached\n * via the {@link ProjectsField} to the pl client's root, this resource\n * contains project list.\n *\n * Read about alternative roots, if isolated project lists (working environments)\n * are required.\n * */\nexport class MiddleLayer {\n public readonly pl: PlClient;\n\n /** Contains a reactive list of projects along with their meta information. */\n public readonly projectList: ComputableStableDefined<ProjectListEntry[]>;\n\n private constructor(\n private readonly env: MiddleLayerEnvironment,\n public readonly driverKit: DriverKit,\n public readonly signer: Signer,\n private readonly projectListResourceId: SignedResourceId,\n private readonly openedProjectsList: WatchableValue<ProjectId[]>,\n private readonly projectListTree: SynchronizedTreeState,\n public readonly blockRegistryProvider: V2RegistryProvider,\n projectList: ComputableStableDefined<ProjectListEntry[]>,\n ) {\n this.projectList = projectList;\n this.pl = this.env.pl;\n }\n\n /**\n * Get the OS where backend is running.\n * For old backend versions returns undefined.\n */\n public get serverPlatform(): BlockPlatform | undefined {\n return this.pl.serverInfo.platform as BlockPlatform | undefined;\n }\n\n /**\n * Runtime capabilities advertised by the connected backend (tokens of\n * the form `<feature>:<version>`, e.g. \"wasm:v1\"). Empty list if the\n * backend predates the capability mechanism — that's the desired\n * fail-closed behaviour for blocks declaring any `requiredCapabilities`.\n */\n public get serverCapabilities(): string[] {\n return this.pl.serverInfo.capabilities ?? [];\n }\n\n /** Adds a runtime capability to the middle layer. */\n public addRuntimeCapability(\n requirement: SupportedRequirement,\n value: number | boolean = true,\n ): void {\n this.env.runtimeCapabilities.addSupportedRequirement(requirement, value);\n }\n\n /** Checks if the given block feature flags are compatible with the runtime capabilities. */\n public checkBlockCompatibility(featureFlags: BlockCodeFeatureFlags | undefined): boolean {\n return this.env.runtimeCapabilities.checkCompatibility(featureFlags);\n }\n\n /** Returns extended API driver kit used internally by middle layer. */\n public get internalDriverKit(): MiddleLayerDriverKit {\n return this.env.driverKit;\n }\n\n /** Returns the service registry for service introspection. */\n public get serviceRegistry(): ModelServiceRegistry {\n return this.env.serviceRegistry;\n }\n\n //\n // ProjectId ↔ SignedResourceId resolution\n //\n\n private readonly projectIdCache = new LRUCache<ProjectId, SignedResourceId>({ max: 1024 });\n\n /** Resolves a ProjectId to a signed SignedResourceId.\n * Uses LRU cache with TX-scan fallback. */\n private async resolveProjectId(projectId: ProjectId): Promise<SignedResourceId> {\n const cached = this.projectIdCache.get(projectId);\n if (cached !== undefined) return cached;\n\n // Cache miss — scan project list fields to find the matching resource\n const rid = await this.pl.withReadTx(\"ResolveProjectId\", async (tx) => {\n const data = await tx.getResourceData(this.projectListResourceId, true);\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (resourceIdToString(f.value) === (projectId as string)) return f.value;\n }\n throw new Error(`Project ${projectId} not found in project list.`);\n });\n\n this.projectIdCache.set(projectId, rid);\n return rid;\n }\n\n //\n // Project List Manipulation\n //\n\n /** Creates a project with initial state and adds it to project list. */\n public async createProject(meta: ProjectMeta): Promise<ProjectId> {\n let prj: ResourceRef;\n await this.pl.withWriteTx(\"MLCreateProject\", async (tx) => {\n prj = await createProject(tx, meta);\n tx.createField(field(this.projectListResourceId, randomUUID()), \"Dynamic\", prj);\n await tx.commit();\n });\n await this.projectListTree.refreshState();\n\n const signedRid = await prj!.globalId;\n const projectId = resourceIdToString(signedRid) as ProjectId;\n this.projectIdCache.set(projectId, signedRid);\n return projectId;\n }\n\n /** Updates project metadata */\n public async setProjectMeta(\n id: ProjectId,\n meta: ProjectMeta,\n author?: AuthorMarker,\n ): Promise<void> {\n const rid = await this.resolveProjectId(id);\n await withProjectAuthored(\n this.env.projectHelper,\n this.pl,\n rid,\n author,\n (prj) => {\n prj.setMeta(meta);\n },\n { name: \"setProjectMeta\" },\n );\n await this.projectListTree.refreshState();\n }\n\n /** Permanently deletes project from the project list, this will result in\n * destruction of all attached objects, like files, analysis results etc. */\n public async deleteProject(id: ProjectId): Promise<void> {\n await this.pl.withWriteTx(\"MLRemoveProject\", async (tx) => {\n const data = await tx.getResourceData(this.projectListResourceId, true);\n let fieldName: string | undefined;\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (resourceIdToString(f.value) === (id as string)) {\n fieldName = f.name;\n break;\n }\n }\n if (fieldName === undefined) throw new Error(`Project ${id} not found in project list.`);\n tx.removeField(field(this.projectListResourceId, fieldName));\n await tx.commit();\n });\n this.projectIdCache.delete(id);\n await this.projectListTree.refreshState();\n }\n\n /**\n * Duplicates an existing project and adds the copy to this user's project list.\n *\n * @param srcProjectId - project id of the project to duplicate\n * @param rename - optional function that receives the source label and all existing\n * project labels (read within the same transaction), and returns the label for the copy\n */\n public async duplicateProject(\n srcProjectId: ProjectId,\n rename?: (previousLabel: string, existingLabels: string[]) => string,\n ): Promise<ProjectId> {\n const sourceRid = await this.resolveProjectId(srcProjectId);\n\n const newPrj: ResourceRef = await this.pl.withWriteTx(\"MLDuplicateProject\", async (tx) => {\n // Read source project meta\n const sourceMeta = await tx.getKValueJson<ProjectMeta>(sourceRid, ProjectMetaKey);\n\n // Read all existing project labels from the project list (parallel reads)\n const projectListData = await tx.getResourceData(this.projectListResourceId, true);\n const projectRids = projectListData.fields\n .map((f) => f.value)\n .filter(isNotNullSignedResourceId);\n const existingLabels = (\n await Promise.all(\n projectRids.map((rid) => tx.getKValueJson<ProjectMeta>(rid, ProjectMetaKey)),\n )\n ).map((m) => m.label);\n\n // Compute new label\n const newLabel = rename ? rename(sourceMeta.label, existingLabels) : sourceMeta.label;\n\n // Create the duplicate\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n\n // Attach to project list with a random UUID field name\n tx.createField(field(this.projectListResourceId, randomUUID()), \"Dynamic\", newPrj);\n await tx.commit();\n\n return newPrj;\n });\n\n await this.projectListTree.refreshState();\n\n const signedRid = await newPrj.globalId;\n const newProjectId = resourceIdToString(signedRid) as ProjectId;\n this.projectIdCache.set(newProjectId, signedRid);\n return newProjectId;\n }\n\n //\n // Projects\n //\n\n private readonly openedProjects = new Map<ProjectId, Project>();\n\n /** Opens a project, and starts corresponding project maintenance loop. */\n public async openProject(id: ProjectId): Promise<void> {\n if (this.openedProjects.has(id)) throw new Error(`Project ${id} already opened`);\n const rid = await this.resolveProjectId(id);\n this.openedProjects.set(id, await Project.init(this.env, id, rid));\n this.openedProjectsList.setValue([...this.openedProjects.keys()]);\n }\n\n /** Closes the project, and deallocate all corresponding resources. */\n public async closeProject(id: ProjectId): Promise<void> {\n const prj = this.openedProjects.get(id);\n if (prj === undefined) throw new Error(`Project ${id} not found among opened projects`);\n this.openedProjects.delete(id);\n await prj.destroy();\n this.openedProjectsList.setValue([...this.openedProjects.keys()]);\n }\n\n /** Returns a project access object for an opened project. */\n public getOpenedProject(id: ProjectId): Project {\n const prj = this.openedProjects.get(id);\n if (prj === undefined) throw new Error(`Project ${id} not found among opened projects`);\n return prj;\n }\n\n /** Returns true if project with given id is currently opened. */\n public isProjectOpened(id: ProjectId): boolean {\n return this.openedProjects.has(id);\n }\n\n /**\n * Deallocates all runtime resources consumed by this object and awaits\n * actual termination of event loops and other processes associated with\n * them.\n */\n public async close() {\n await Promise.all([...this.openedProjects.values()].map((prj) => prj.destroy()));\n // this.env.quickJs;\n await this.projectListTree.terminate();\n await this.env.dispose();\n await this.pl.close();\n }\n\n /** @deprecated */\n public async closeAndAwaitTermination() {\n await this.close();\n }\n\n /** Generates sufficiently random string to be used as local secret for the\n * middle layer */\n public static generateLocalSecret(): string {\n return HmacSha256Signer.generateSecret();\n }\n\n /** Returns a block event dispatcher, which can be used to listen to block events. */\n public get blockEventDispatcher(): BlockEventDispatcher {\n return this.env.blockEventDispatcher;\n }\n\n /** Initialize middle layer */\n public static async init(\n pl: PlClient,\n workdir: string,\n _ops: MiddleLayerOpsConstructor,\n ): Promise<MiddleLayer> {\n const ops: MiddleLayerOps = {\n ...DefaultMiddleLayerOpsSettings,\n ...DefaultMiddleLayerOpsPaths(workdir),\n ..._ops,\n };\n\n // overriding debug options from environment variables\n ops.defaultTreeOptions.logStat = getDebugFlags().logTreeStats;\n ops.debugOps.dumpInitialTreeState = getDebugFlags().dumpInitialTreeState;\n // apply MI_TREE_TRAVERSAL only when the embedder hasn't set an explicit mode\n if (\n ops.defaultTreeOptions.traversalMode === undefined &&\n getDebugFlags().treeTraversalMode !== undefined\n )\n ops.defaultTreeOptions.traversalMode = getDebugFlags().treeTraversalMode;\n\n const projects = await pl.withWriteTx(\"MLInitialization\", async (tx) => {\n const projectsField = field(tx.clientRoot, ProjectsField);\n tx.createField(projectsField, \"Dynamic\");\n const projectsFieldData = await tx.getField(projectsField);\n if (isNullSignedResourceId(projectsFieldData.value)) {\n const projects = tx.createEphemeral(ProjectsResourceType);\n tx.lock(projects);\n\n tx.setField(projectsField, projects);\n\n await tx.commit();\n\n return await projects.globalId;\n } else {\n return projectsFieldData.value;\n }\n });\n\n const logger = ops.logger;\n\n const driverKit = await initDriverKit(pl, workdir, ops.frontendDownloadPath, ops);\n\n // passed to components having no own retry logic\n const retryHttpDispatcher = new RetryAgent(pl.httpDispatcher);\n\n const v2RegistryProvider = new V2RegistryProvider(retryHttpDispatcher);\n\n const bpPreparer = new BlockPackPreparer(\n v2RegistryProvider,\n driverKit.signer,\n retryHttpDispatcher,\n );\n\n const quickJs = await getQuickJS();\n\n const runtimeCapabilities = new RuntimeCapabilities();\n // add runtime capabilities of model here\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 1);\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresCreatePTable\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresPFramesVersion\", REQUIRES_PFRAMES_VERSION);\n registerServiceCapabilities((flag, value) =>\n runtimeCapabilities.addSupportedRequirement(flag, value),\n );\n // runtime capabilities of the desktop are to be added by the desktop app / test framework\n\n const serviceRegistry = createModelServiceRegistry({ logger });\n\n const env: MiddleLayerEnvironment = {\n pl,\n blockEventDispatcher: new BlockEventDispatcher(),\n signer: driverKit.signer,\n logger,\n httpDispatcher: pl.httpDispatcher,\n retryHttpDispatcher,\n ops,\n bpPreparer,\n frontendDownloadDriver: driverKit.frontendDriver,\n driverKit,\n blockUpdateWatcher: new BlockUpdateWatcher(v2RegistryProvider, logger, {\n minDelay: ops.devBlockUpdateRecheckInterval,\n http: retryHttpDispatcher,\n preferredUpdateChannel: ops.preferredUpdateChannel,\n }),\n runtimeCapabilities,\n serviceRegistry,\n quickJs,\n projectHelper: new ProjectHelper(quickJs, logger),\n dispose: async () => {\n await serviceRegistry.dispose();\n await retryHttpDispatcher.destroy();\n await driverKit.dispose();\n },\n };\n\n const openedProjects = new WatchableValue<ProjectId[]>([]);\n const projectListTC = await createProjectList(pl, projects, openedProjects, env);\n\n return new MiddleLayer(\n env,\n driverKit,\n driverKit.signer,\n projects,\n openedProjects,\n projectListTC.tree,\n v2RegistryProvider,\n projectListTC.computable,\n );\n }\n}\n"],"mappings":"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"}
@@ -64,6 +64,13 @@ declare class MiddleLayer {
64
64
  * For old backend versions returns undefined.
65
65
  */
66
66
  get serverPlatform(): BlockPlatform | undefined;
67
+ /**
68
+ * Runtime capabilities advertised by the connected backend (tokens of
69
+ * the form `<feature>:<version>`, e.g. "wasm:v1"). Empty list if the
70
+ * backend predates the capability mechanism — that's the desired
71
+ * fail-closed behaviour for blocks declaring any `requiredCapabilities`.
72
+ */
73
+ get serverCapabilities(): string[];
67
74
  /** Adds a runtime capability to the middle layer. */
68
75
  addRuntimeCapability(requirement: SupportedRequirement, value?: number | boolean): void;
69
76
  /** Checks if the given block feature flags are compatible with the runtime capabilities. */
@@ -1 +1 @@
1
- {"version":3,"file":"middle_layer.d.ts","names":[],"sources":["../../src/middle_layer/middle_layer.ts"],"mappings":";;;;;;;;;;;;;;;;;;;UAiDiB,sBAAA;EACf,OAAA,IAAW,OAAA;EAAA,SACF,EAAA,EAAI,QAAA;EAAA,SACJ,mBAAA,EAAqB,mBAAA;EAAA,SACrB,MAAA,EAAQ,QAAA;EAAA,SACR,oBAAA,EAAsB,oBAAA;EAAA,SACtB,cAAA,EAAgB,UAAA;EAAA,SAChB,mBAAA,EAAqB,UAAA;EAAA,SACrB,MAAA,EAAQ,MAAA;EAAA,SACR,GAAA,EAAK,cAAA;EAAA,SACL,UAAA,EAAY,iBAAA;EAAA,SACZ,sBAAA,EAAwB,iBAAA;EAAA,SACxB,kBAAA,EAAoB,kBAAA;EAAA,SACpB,OAAA,EAAS,iBAAA;EAAA,SACT,SAAA,EAAW,oBAAA;EAAA,SACX,eAAA,EAAiB,oBAAA;EAAA,SACjB,aAAA,EAAe,aAAA;AAAA;;;;;;;;;;;;;cAeb,WAAA;EAAA,iBAOQ,GAAA;EAAA,SACD,SAAA,EAAW,SAAA;EAAA,SACX,MAAA,EAAQ,MAAA;EAAA,iBACP,qBAAA;EAAA,iBACA,kBAAA;EAAA,iBACA,eAAA;EAAA,SACD,qBAAA,EAAuB,kBAAA;EAAA,SAZzB,EAAA,EAAI,QAAA;EAvBN;EAAA,SA0BE,WAAA,EAAa,uBAAA,CAAwB,kBAAA;EAAA,QAE9C,WAAA,CAAA;EA1BE;;;;EAAA,IA4CE,cAAA,CAAA,GAAkB,aAAA;EA1CX;EA+CX,oBAAA,CACL,WAAA,EAAa,oBAAA,EACb,KAAA;EAhDkB;EAsDb,uBAAA,CAAwB,YAAA,EAAc,qBAAA;EArDnB;EAAA,IA0Df,iBAAA,CAAA,GAAqB,oBAAA;EAzDR;EAAA,IA8Db,eAAA,CAAA,GAAmB,oBAAA;EAAA,iBAQb,cAAA;EAvDN;;EAAA,QA2DG,gBAAA;EAnDe;EA0EhB,aAAA,CAAc,IAAA,EAAM,WAAA,GAAc,OAAA,CAAQ,SAAA;EArEd;EAqF5B,cAAA,CACX,EAAA,EAAI,SAAA,EACJ,IAAA,EAAM,WAAA,EACN,MAAA,GAAS,YAAA,GACR,OAAA;EAlGkD;;EAmHxC,aAAA,CAAc,EAAA,EAAI,SAAA,GAAY,OAAA;EAzF5B;;;;;;;EAmHF,gBAAA,CACX,YAAA,EAAc,SAAA,EACd,MAAA,IAAU,aAAA,UAAuB,cAAA,wBAChC,OAAA,CAAQ,SAAA;EAAA,iBA2CM,cAAA;EA1FN;EA6FE,WAAA,CAAY,EAAA,EAAI,SAAA,GAAY,OAAA;EA3EV;EAmFlB,YAAA,CAAa,EAAA,EAAI,SAAA,GAAY,OAAA;EAxD1B;EAiET,gBAAA,CAAiB,EAAA,EAAI,SAAA,GAAY,OAAA;EA/DrC;EAsEI,eAAA,CAAgB,EAAA,EAAI,SAAA;EAxBc;;;;;EAiC5B,KAAA,CAAA,GAAK,OAAA;EAAA;EASL,wBAAA,CAAA,GAAwB,OAAA;EAWF;;EAAA,OALrB,mBAAA,CAAA;EAcH;EAAA,IATA,oBAAA,CAAA,GAAwB,oBAAA;EASzB;EAAA,OAJU,IAAA,CAClB,EAAA,EAAI,QAAA,EACJ,OAAA,UACA,IAAA,EAAM,yBAAA,GACL,OAAA,CAAQ,WAAA;AAAA"}
1
+ {"version":3,"file":"middle_layer.d.ts","names":[],"sources":["../../src/middle_layer/middle_layer.ts"],"mappings":";;;;;;;;;;;;;;;;;;;UAiDiB,sBAAA;EACf,OAAA,IAAW,OAAA;EAAA,SACF,EAAA,EAAI,QAAA;EAAA,SACJ,mBAAA,EAAqB,mBAAA;EAAA,SACrB,MAAA,EAAQ,QAAA;EAAA,SACR,oBAAA,EAAsB,oBAAA;EAAA,SACtB,cAAA,EAAgB,UAAA;EAAA,SAChB,mBAAA,EAAqB,UAAA;EAAA,SACrB,MAAA,EAAQ,MAAA;EAAA,SACR,GAAA,EAAK,cAAA;EAAA,SACL,UAAA,EAAY,iBAAA;EAAA,SACZ,sBAAA,EAAwB,iBAAA;EAAA,SACxB,kBAAA,EAAoB,kBAAA;EAAA,SACpB,OAAA,EAAS,iBAAA;EAAA,SACT,SAAA,EAAW,oBAAA;EAAA,SACX,eAAA,EAAiB,oBAAA;EAAA,SACjB,aAAA,EAAe,aAAA;AAAA;;;;;;;;;;;;;cAeb,WAAA;EAAA,iBAOQ,GAAA;EAAA,SACD,SAAA,EAAW,SAAA;EAAA,SACX,MAAA,EAAQ,MAAA;EAAA,iBACP,qBAAA;EAAA,iBACA,kBAAA;EAAA,iBACA,eAAA;EAAA,SACD,qBAAA,EAAuB,kBAAA;EAAA,SAZzB,EAAA,EAAI,QAAA;EAvBN;EAAA,SA0BE,WAAA,EAAa,uBAAA,CAAwB,kBAAA;EAAA,QAE9C,WAAA,CAAA;EA1BE;;;;EAAA,IA4CE,cAAA,CAAA,GAAkB,aAAA;EA1CX;;;;;;EAAA,IAoDP,kBAAA,CAAA;EAjD0B;EAsD9B,oBAAA,CACL,WAAA,EAAa,oBAAA,EACb,KAAA;EAzCS;EA+CJ,uBAAA,CAAwB,YAAA,EAAc,qBAAA;;MAKlC,iBAAA,CAAA,GAAqB,oBAAA;EA3CN;EAAA,IAgDf,eAAA,CAAA,GAAmB,oBAAA;EAAA,iBAQb,cAAA;EA7DoC;;EAAA,QAiEvC,gBAAA;EA7BC;EAoDF,aAAA,CAAc,IAAA,EAAM,WAAA,GAAc,OAAA,CAAQ,SAAA;EAxCvB;EAwDnB,cAAA,CACX,EAAA,EAAI,SAAA,EACJ,IAAA,EAAM,WAAA,EACN,MAAA,GAAS,YAAA,GACR,OAAA;EApB8B;;EAqCpB,aAAA,CAAc,EAAA,EAAI,SAAA,GAAY,OAAA;EApBrC;;;;;;;EA8CO,gBAAA,CACX,YAAA,EAAc,SAAA,EACd,MAAA,IAAU,aAAA,UAAuB,cAAA,wBAChC,OAAA,CAAQ,SAAA;EAAA,iBA2CM,cAAA;EAGY;EAAhB,WAAA,CAAY,EAAA,EAAI,SAAA,GAAY,OAAA;EAQX;EAAjB,YAAA,CAAa,EAAA,EAAI,SAAA,GAAY,OAAA;EASd;EAArB,gBAAA,CAAiB,EAAA,EAAI,SAAA,GAAY,OAAA;EAOb;EAApB,eAAA,CAAgB,EAAA,EAAI,SAAA;EAkBU;;;;;EATxB,KAAA,CAAA,GAAK,OAAA;EA6BR;EApBG,wBAAA,CAAA,GAAwB,OAAA;EA/OlB;;EAAA,OAqPL,mBAAA,CAAA;EAnPI;EAAA,IAwPP,oBAAA,CAAA,GAAwB,oBAAA;EAvPhB;EAAA,OA4PC,IAAA,CAClB,EAAA,EAAI,QAAA,EACJ,OAAA,UACA,IAAA,EAAM,yBAAA,GACL,OAAA,CAAQ,WAAA;AAAA"}
@@ -55,6 +55,15 @@ var MiddleLayer = class MiddleLayer {
55
55
  get serverPlatform() {
56
56
  return this.pl.serverInfo.platform;
57
57
  }
58
+ /**
59
+ * Runtime capabilities advertised by the connected backend (tokens of
60
+ * the form `<feature>:<version>`, e.g. "wasm:v1"). Empty list if the
61
+ * backend predates the capability mechanism — that's the desired
62
+ * fail-closed behaviour for blocks declaring any `requiredCapabilities`.
63
+ */
64
+ get serverCapabilities() {
65
+ return this.pl.serverInfo.capabilities ?? [];
66
+ }
58
67
  /** Adds a runtime capability to the middle layer. */
59
68
  addRuntimeCapability(requirement, value = true) {
60
69
  this.env.runtimeCapabilities.addSupportedRequirement(requirement, value);
@@ -1 +1 @@
1
- {"version":3,"file":"middle_layer.js","names":[],"sources":["../../src/middle_layer/middle_layer.ts"],"sourcesContent":["import type { PlClient, SignedResourceId, ResourceRef } from \"@milaboratories/pl-client\";\nimport {\n field,\n isNotNullSignedResourceId,\n isNullSignedResourceId,\n resourceIdToString,\n} from \"@milaboratories/pl-client\";\nimport { LRUCache } from \"lru-cache\";\nimport { createProjectList, ProjectsField, ProjectsResourceType } from \"./project_list\";\nimport { createProject, duplicateProject, withProjectAuthored } from \"../mutator/project\";\nimport { ProjectMetaKey } from \"../model/project_model\";\nimport type { ProjectId } from \"../model/project_model\";\nimport type { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport { BlockPackPreparer } from \"../mutator/block-pack/block_pack\";\nimport type { MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { BlockEventDispatcher } from \"@milaboratories/ts-helpers\";\nimport { HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport type { ComputableStableDefined } from \"@milaboratories/computable\";\nimport { WatchableValue } from \"@milaboratories/computable\";\nimport { Project } from \"./project\";\nimport type { MiddleLayerOps, MiddleLayerOpsConstructor } from \"./ops\";\nimport { DefaultMiddleLayerOpsPaths, DefaultMiddleLayerOpsSettings } from \"./ops\";\nimport { randomUUID } from \"node:crypto\";\nimport type { ProjectListEntry } from \"../model\";\nimport type {\n AuthorMarker,\n ProjectMeta,\n BlockPlatform,\n} from \"@milaboratories/pl-model-middle-layer\";\nimport { BlockUpdateWatcher } from \"../block_registry/watcher\";\nimport type { QuickJSWASMModule } from \"quickjs-emscripten\";\nimport { getQuickJS } from \"quickjs-emscripten\";\nimport type { MiddleLayerDriverKit } from \"./driver_kit\";\nimport { initDriverKit } from \"./driver_kit\";\nimport type { BlockCodeFeatureFlags, DriverKit, SupportedRequirement } from \"@platforma-sdk/model\";\nimport { RuntimeCapabilities } from \"@platforma-sdk/model\";\nimport {\n type ModelServiceRegistry,\n registerServiceCapabilities,\n REQUIRES_PFRAMES_VERSION,\n} from \"@milaboratories/pl-model-common\";\nimport { createModelServiceRegistry } from \"../service_factories\";\nimport type { DownloadUrlDriver } from \"@milaboratories/pl-drivers\";\nimport { V2RegistryProvider } from \"../block_registry\";\nimport type { Dispatcher } from \"undici\";\nimport { RetryAgent } from \"undici\";\nimport { getDebugFlags } from \"../debug\";\nimport { ProjectHelper } from \"../model/project_helper\";\n\nexport interface MiddleLayerEnvironment {\n dispose(): Promise<void>;\n readonly pl: PlClient;\n readonly runtimeCapabilities: RuntimeCapabilities;\n readonly logger: MiLogger;\n readonly blockEventDispatcher: BlockEventDispatcher;\n readonly httpDispatcher: Dispatcher;\n readonly retryHttpDispatcher: Dispatcher;\n readonly signer: Signer;\n readonly ops: MiddleLayerOps;\n readonly bpPreparer: BlockPackPreparer;\n readonly frontendDownloadDriver: DownloadUrlDriver;\n readonly blockUpdateWatcher: BlockUpdateWatcher;\n readonly quickJs: QuickJSWASMModule;\n readonly driverKit: MiddleLayerDriverKit;\n readonly serviceRegistry: ModelServiceRegistry;\n readonly projectHelper: ProjectHelper;\n}\n\n/**\n * Main access object to work with pl from UI.\n *\n * It implements an abstraction layer of projects and blocks.\n *\n * As a main entry point inside the pl, this object uses a resource attached\n * via the {@link ProjectsField} to the pl client's root, this resource\n * contains project list.\n *\n * Read about alternative roots, if isolated project lists (working environments)\n * are required.\n * */\nexport class MiddleLayer {\n public readonly pl: PlClient;\n\n /** Contains a reactive list of projects along with their meta information. */\n public readonly projectList: ComputableStableDefined<ProjectListEntry[]>;\n\n private constructor(\n private readonly env: MiddleLayerEnvironment,\n public readonly driverKit: DriverKit,\n public readonly signer: Signer,\n private readonly projectListResourceId: SignedResourceId,\n private readonly openedProjectsList: WatchableValue<ProjectId[]>,\n private readonly projectListTree: SynchronizedTreeState,\n public readonly blockRegistryProvider: V2RegistryProvider,\n projectList: ComputableStableDefined<ProjectListEntry[]>,\n ) {\n this.projectList = projectList;\n this.pl = this.env.pl;\n }\n\n /**\n * Get the OS where backend is running.\n * For old backend versions returns undefined.\n */\n public get serverPlatform(): BlockPlatform | undefined {\n return this.pl.serverInfo.platform as BlockPlatform | undefined;\n }\n\n /** Adds a runtime capability to the middle layer. */\n public addRuntimeCapability(\n requirement: SupportedRequirement,\n value: number | boolean = true,\n ): void {\n this.env.runtimeCapabilities.addSupportedRequirement(requirement, value);\n }\n\n /** Checks if the given block feature flags are compatible with the runtime capabilities. */\n public checkBlockCompatibility(featureFlags: BlockCodeFeatureFlags | undefined): boolean {\n return this.env.runtimeCapabilities.checkCompatibility(featureFlags);\n }\n\n /** Returns extended API driver kit used internally by middle layer. */\n public get internalDriverKit(): MiddleLayerDriverKit {\n return this.env.driverKit;\n }\n\n /** Returns the service registry for service introspection. */\n public get serviceRegistry(): ModelServiceRegistry {\n return this.env.serviceRegistry;\n }\n\n //\n // ProjectId ↔ SignedResourceId resolution\n //\n\n private readonly projectIdCache = new LRUCache<ProjectId, SignedResourceId>({ max: 1024 });\n\n /** Resolves a ProjectId to a signed SignedResourceId.\n * Uses LRU cache with TX-scan fallback. */\n private async resolveProjectId(projectId: ProjectId): Promise<SignedResourceId> {\n const cached = this.projectIdCache.get(projectId);\n if (cached !== undefined) return cached;\n\n // Cache miss — scan project list fields to find the matching resource\n const rid = await this.pl.withReadTx(\"ResolveProjectId\", async (tx) => {\n const data = await tx.getResourceData(this.projectListResourceId, true);\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (resourceIdToString(f.value) === (projectId as string)) return f.value;\n }\n throw new Error(`Project ${projectId} not found in project list.`);\n });\n\n this.projectIdCache.set(projectId, rid);\n return rid;\n }\n\n //\n // Project List Manipulation\n //\n\n /** Creates a project with initial state and adds it to project list. */\n public async createProject(meta: ProjectMeta): Promise<ProjectId> {\n let prj: ResourceRef;\n await this.pl.withWriteTx(\"MLCreateProject\", async (tx) => {\n prj = await createProject(tx, meta);\n tx.createField(field(this.projectListResourceId, randomUUID()), \"Dynamic\", prj);\n await tx.commit();\n });\n await this.projectListTree.refreshState();\n\n const signedRid = await prj!.globalId;\n const projectId = resourceIdToString(signedRid) as ProjectId;\n this.projectIdCache.set(projectId, signedRid);\n return projectId;\n }\n\n /** Updates project metadata */\n public async setProjectMeta(\n id: ProjectId,\n meta: ProjectMeta,\n author?: AuthorMarker,\n ): Promise<void> {\n const rid = await this.resolveProjectId(id);\n await withProjectAuthored(\n this.env.projectHelper,\n this.pl,\n rid,\n author,\n (prj) => {\n prj.setMeta(meta);\n },\n { name: \"setProjectMeta\" },\n );\n await this.projectListTree.refreshState();\n }\n\n /** Permanently deletes project from the project list, this will result in\n * destruction of all attached objects, like files, analysis results etc. */\n public async deleteProject(id: ProjectId): Promise<void> {\n await this.pl.withWriteTx(\"MLRemoveProject\", async (tx) => {\n const data = await tx.getResourceData(this.projectListResourceId, true);\n let fieldName: string | undefined;\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (resourceIdToString(f.value) === (id as string)) {\n fieldName = f.name;\n break;\n }\n }\n if (fieldName === undefined) throw new Error(`Project ${id} not found in project list.`);\n tx.removeField(field(this.projectListResourceId, fieldName));\n await tx.commit();\n });\n this.projectIdCache.delete(id);\n await this.projectListTree.refreshState();\n }\n\n /**\n * Duplicates an existing project and adds the copy to this user's project list.\n *\n * @param srcProjectId - project id of the project to duplicate\n * @param rename - optional function that receives the source label and all existing\n * project labels (read within the same transaction), and returns the label for the copy\n */\n public async duplicateProject(\n srcProjectId: ProjectId,\n rename?: (previousLabel: string, existingLabels: string[]) => string,\n ): Promise<ProjectId> {\n const sourceRid = await this.resolveProjectId(srcProjectId);\n\n const newPrj: ResourceRef = await this.pl.withWriteTx(\"MLDuplicateProject\", async (tx) => {\n // Read source project meta\n const sourceMeta = await tx.getKValueJson<ProjectMeta>(sourceRid, ProjectMetaKey);\n\n // Read all existing project labels from the project list (parallel reads)\n const projectListData = await tx.getResourceData(this.projectListResourceId, true);\n const projectRids = projectListData.fields\n .map((f) => f.value)\n .filter(isNotNullSignedResourceId);\n const existingLabels = (\n await Promise.all(\n projectRids.map((rid) => tx.getKValueJson<ProjectMeta>(rid, ProjectMetaKey)),\n )\n ).map((m) => m.label);\n\n // Compute new label\n const newLabel = rename ? rename(sourceMeta.label, existingLabels) : sourceMeta.label;\n\n // Create the duplicate\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n\n // Attach to project list with a random UUID field name\n tx.createField(field(this.projectListResourceId, randomUUID()), \"Dynamic\", newPrj);\n await tx.commit();\n\n return newPrj;\n });\n\n await this.projectListTree.refreshState();\n\n const signedRid = await newPrj.globalId;\n const newProjectId = resourceIdToString(signedRid) as ProjectId;\n this.projectIdCache.set(newProjectId, signedRid);\n return newProjectId;\n }\n\n //\n // Projects\n //\n\n private readonly openedProjects = new Map<ProjectId, Project>();\n\n /** Opens a project, and starts corresponding project maintenance loop. */\n public async openProject(id: ProjectId): Promise<void> {\n if (this.openedProjects.has(id)) throw new Error(`Project ${id} already opened`);\n const rid = await this.resolveProjectId(id);\n this.openedProjects.set(id, await Project.init(this.env, id, rid));\n this.openedProjectsList.setValue([...this.openedProjects.keys()]);\n }\n\n /** Closes the project, and deallocate all corresponding resources. */\n public async closeProject(id: ProjectId): Promise<void> {\n const prj = this.openedProjects.get(id);\n if (prj === undefined) throw new Error(`Project ${id} not found among opened projects`);\n this.openedProjects.delete(id);\n await prj.destroy();\n this.openedProjectsList.setValue([...this.openedProjects.keys()]);\n }\n\n /** Returns a project access object for an opened project. */\n public getOpenedProject(id: ProjectId): Project {\n const prj = this.openedProjects.get(id);\n if (prj === undefined) throw new Error(`Project ${id} not found among opened projects`);\n return prj;\n }\n\n /** Returns true if project with given id is currently opened. */\n public isProjectOpened(id: ProjectId): boolean {\n return this.openedProjects.has(id);\n }\n\n /**\n * Deallocates all runtime resources consumed by this object and awaits\n * actual termination of event loops and other processes associated with\n * them.\n */\n public async close() {\n await Promise.all([...this.openedProjects.values()].map((prj) => prj.destroy()));\n // this.env.quickJs;\n await this.projectListTree.terminate();\n await this.env.dispose();\n await this.pl.close();\n }\n\n /** @deprecated */\n public async closeAndAwaitTermination() {\n await this.close();\n }\n\n /** Generates sufficiently random string to be used as local secret for the\n * middle layer */\n public static generateLocalSecret(): string {\n return HmacSha256Signer.generateSecret();\n }\n\n /** Returns a block event dispatcher, which can be used to listen to block events. */\n public get blockEventDispatcher(): BlockEventDispatcher {\n return this.env.blockEventDispatcher;\n }\n\n /** Initialize middle layer */\n public static async init(\n pl: PlClient,\n workdir: string,\n _ops: MiddleLayerOpsConstructor,\n ): Promise<MiddleLayer> {\n const ops: MiddleLayerOps = {\n ...DefaultMiddleLayerOpsSettings,\n ...DefaultMiddleLayerOpsPaths(workdir),\n ..._ops,\n };\n\n // overriding debug options from environment variables\n ops.defaultTreeOptions.logStat = getDebugFlags().logTreeStats;\n ops.debugOps.dumpInitialTreeState = getDebugFlags().dumpInitialTreeState;\n // apply MI_TREE_TRAVERSAL only when the embedder hasn't set an explicit mode\n if (\n ops.defaultTreeOptions.traversalMode === undefined &&\n getDebugFlags().treeTraversalMode !== undefined\n )\n ops.defaultTreeOptions.traversalMode = getDebugFlags().treeTraversalMode;\n\n const projects = await pl.withWriteTx(\"MLInitialization\", async (tx) => {\n const projectsField = field(tx.clientRoot, ProjectsField);\n tx.createField(projectsField, \"Dynamic\");\n const projectsFieldData = await tx.getField(projectsField);\n if (isNullSignedResourceId(projectsFieldData.value)) {\n const projects = tx.createEphemeral(ProjectsResourceType);\n tx.lock(projects);\n\n tx.setField(projectsField, projects);\n\n await tx.commit();\n\n return await projects.globalId;\n } else {\n return projectsFieldData.value;\n }\n });\n\n const logger = ops.logger;\n\n const driverKit = await initDriverKit(pl, workdir, ops.frontendDownloadPath, ops);\n\n // passed to components having no own retry logic\n const retryHttpDispatcher = new RetryAgent(pl.httpDispatcher);\n\n const v2RegistryProvider = new V2RegistryProvider(retryHttpDispatcher);\n\n const bpPreparer = new BlockPackPreparer(\n v2RegistryProvider,\n driverKit.signer,\n retryHttpDispatcher,\n );\n\n const quickJs = await getQuickJS();\n\n const runtimeCapabilities = new RuntimeCapabilities();\n // add runtime capabilities of model here\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 1);\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresCreatePTable\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresPFramesVersion\", REQUIRES_PFRAMES_VERSION);\n registerServiceCapabilities((flag, value) =>\n runtimeCapabilities.addSupportedRequirement(flag, value),\n );\n // runtime capabilities of the desktop are to be added by the desktop app / test framework\n\n const serviceRegistry = createModelServiceRegistry({ logger });\n\n const env: MiddleLayerEnvironment = {\n pl,\n blockEventDispatcher: new BlockEventDispatcher(),\n signer: driverKit.signer,\n logger,\n httpDispatcher: pl.httpDispatcher,\n retryHttpDispatcher,\n ops,\n bpPreparer,\n frontendDownloadDriver: driverKit.frontendDriver,\n driverKit,\n blockUpdateWatcher: new BlockUpdateWatcher(v2RegistryProvider, logger, {\n minDelay: ops.devBlockUpdateRecheckInterval,\n http: retryHttpDispatcher,\n preferredUpdateChannel: ops.preferredUpdateChannel,\n }),\n runtimeCapabilities,\n serviceRegistry,\n quickJs,\n projectHelper: new ProjectHelper(quickJs, logger),\n dispose: async () => {\n await serviceRegistry.dispose();\n await retryHttpDispatcher.destroy();\n await driverKit.dispose();\n },\n };\n\n const openedProjects = new WatchableValue<ProjectId[]>([]);\n const projectListTC = await createProjectList(pl, projects, openedProjects, env);\n\n return new MiddleLayer(\n env,\n driverKit,\n driverKit.signer,\n projects,\n openedProjects,\n projectListTC.tree,\n v2RegistryProvider,\n projectListTC.computable,\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"middle_layer.js","names":[],"sources":["../../src/middle_layer/middle_layer.ts"],"sourcesContent":["import type { PlClient, SignedResourceId, ResourceRef } from \"@milaboratories/pl-client\";\nimport {\n field,\n isNotNullSignedResourceId,\n isNullSignedResourceId,\n resourceIdToString,\n} from \"@milaboratories/pl-client\";\nimport { LRUCache } from \"lru-cache\";\nimport { createProjectList, ProjectsField, ProjectsResourceType } from \"./project_list\";\nimport { createProject, duplicateProject, withProjectAuthored } from \"../mutator/project\";\nimport { ProjectMetaKey } from \"../model/project_model\";\nimport type { ProjectId } from \"../model/project_model\";\nimport type { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport { BlockPackPreparer } from \"../mutator/block-pack/block_pack\";\nimport type { MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { BlockEventDispatcher } from \"@milaboratories/ts-helpers\";\nimport { HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport type { ComputableStableDefined } from \"@milaboratories/computable\";\nimport { WatchableValue } from \"@milaboratories/computable\";\nimport { Project } from \"./project\";\nimport type { MiddleLayerOps, MiddleLayerOpsConstructor } from \"./ops\";\nimport { DefaultMiddleLayerOpsPaths, DefaultMiddleLayerOpsSettings } from \"./ops\";\nimport { randomUUID } from \"node:crypto\";\nimport type { ProjectListEntry } from \"../model\";\nimport type {\n AuthorMarker,\n ProjectMeta,\n BlockPlatform,\n} from \"@milaboratories/pl-model-middle-layer\";\nimport { BlockUpdateWatcher } from \"../block_registry/watcher\";\nimport type { QuickJSWASMModule } from \"quickjs-emscripten\";\nimport { getQuickJS } from \"quickjs-emscripten\";\nimport type { MiddleLayerDriverKit } from \"./driver_kit\";\nimport { initDriverKit } from \"./driver_kit\";\nimport type { BlockCodeFeatureFlags, DriverKit, SupportedRequirement } from \"@platforma-sdk/model\";\nimport { RuntimeCapabilities } from \"@platforma-sdk/model\";\nimport {\n type ModelServiceRegistry,\n registerServiceCapabilities,\n REQUIRES_PFRAMES_VERSION,\n} from \"@milaboratories/pl-model-common\";\nimport { createModelServiceRegistry } from \"../service_factories\";\nimport type { DownloadUrlDriver } from \"@milaboratories/pl-drivers\";\nimport { V2RegistryProvider } from \"../block_registry\";\nimport type { Dispatcher } from \"undici\";\nimport { RetryAgent } from \"undici\";\nimport { getDebugFlags } from \"../debug\";\nimport { ProjectHelper } from \"../model/project_helper\";\n\nexport interface MiddleLayerEnvironment {\n dispose(): Promise<void>;\n readonly pl: PlClient;\n readonly runtimeCapabilities: RuntimeCapabilities;\n readonly logger: MiLogger;\n readonly blockEventDispatcher: BlockEventDispatcher;\n readonly httpDispatcher: Dispatcher;\n readonly retryHttpDispatcher: Dispatcher;\n readonly signer: Signer;\n readonly ops: MiddleLayerOps;\n readonly bpPreparer: BlockPackPreparer;\n readonly frontendDownloadDriver: DownloadUrlDriver;\n readonly blockUpdateWatcher: BlockUpdateWatcher;\n readonly quickJs: QuickJSWASMModule;\n readonly driverKit: MiddleLayerDriverKit;\n readonly serviceRegistry: ModelServiceRegistry;\n readonly projectHelper: ProjectHelper;\n}\n\n/**\n * Main access object to work with pl from UI.\n *\n * It implements an abstraction layer of projects and blocks.\n *\n * As a main entry point inside the pl, this object uses a resource attached\n * via the {@link ProjectsField} to the pl client's root, this resource\n * contains project list.\n *\n * Read about alternative roots, if isolated project lists (working environments)\n * are required.\n * */\nexport class MiddleLayer {\n public readonly pl: PlClient;\n\n /** Contains a reactive list of projects along with their meta information. */\n public readonly projectList: ComputableStableDefined<ProjectListEntry[]>;\n\n private constructor(\n private readonly env: MiddleLayerEnvironment,\n public readonly driverKit: DriverKit,\n public readonly signer: Signer,\n private readonly projectListResourceId: SignedResourceId,\n private readonly openedProjectsList: WatchableValue<ProjectId[]>,\n private readonly projectListTree: SynchronizedTreeState,\n public readonly blockRegistryProvider: V2RegistryProvider,\n projectList: ComputableStableDefined<ProjectListEntry[]>,\n ) {\n this.projectList = projectList;\n this.pl = this.env.pl;\n }\n\n /**\n * Get the OS where backend is running.\n * For old backend versions returns undefined.\n */\n public get serverPlatform(): BlockPlatform | undefined {\n return this.pl.serverInfo.platform as BlockPlatform | undefined;\n }\n\n /**\n * Runtime capabilities advertised by the connected backend (tokens of\n * the form `<feature>:<version>`, e.g. \"wasm:v1\"). Empty list if the\n * backend predates the capability mechanism — that's the desired\n * fail-closed behaviour for blocks declaring any `requiredCapabilities`.\n */\n public get serverCapabilities(): string[] {\n return this.pl.serverInfo.capabilities ?? [];\n }\n\n /** Adds a runtime capability to the middle layer. */\n public addRuntimeCapability(\n requirement: SupportedRequirement,\n value: number | boolean = true,\n ): void {\n this.env.runtimeCapabilities.addSupportedRequirement(requirement, value);\n }\n\n /** Checks if the given block feature flags are compatible with the runtime capabilities. */\n public checkBlockCompatibility(featureFlags: BlockCodeFeatureFlags | undefined): boolean {\n return this.env.runtimeCapabilities.checkCompatibility(featureFlags);\n }\n\n /** Returns extended API driver kit used internally by middle layer. */\n public get internalDriverKit(): MiddleLayerDriverKit {\n return this.env.driverKit;\n }\n\n /** Returns the service registry for service introspection. */\n public get serviceRegistry(): ModelServiceRegistry {\n return this.env.serviceRegistry;\n }\n\n //\n // ProjectId ↔ SignedResourceId resolution\n //\n\n private readonly projectIdCache = new LRUCache<ProjectId, SignedResourceId>({ max: 1024 });\n\n /** Resolves a ProjectId to a signed SignedResourceId.\n * Uses LRU cache with TX-scan fallback. */\n private async resolveProjectId(projectId: ProjectId): Promise<SignedResourceId> {\n const cached = this.projectIdCache.get(projectId);\n if (cached !== undefined) return cached;\n\n // Cache miss — scan project list fields to find the matching resource\n const rid = await this.pl.withReadTx(\"ResolveProjectId\", async (tx) => {\n const data = await tx.getResourceData(this.projectListResourceId, true);\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (resourceIdToString(f.value) === (projectId as string)) return f.value;\n }\n throw new Error(`Project ${projectId} not found in project list.`);\n });\n\n this.projectIdCache.set(projectId, rid);\n return rid;\n }\n\n //\n // Project List Manipulation\n //\n\n /** Creates a project with initial state and adds it to project list. */\n public async createProject(meta: ProjectMeta): Promise<ProjectId> {\n let prj: ResourceRef;\n await this.pl.withWriteTx(\"MLCreateProject\", async (tx) => {\n prj = await createProject(tx, meta);\n tx.createField(field(this.projectListResourceId, randomUUID()), \"Dynamic\", prj);\n await tx.commit();\n });\n await this.projectListTree.refreshState();\n\n const signedRid = await prj!.globalId;\n const projectId = resourceIdToString(signedRid) as ProjectId;\n this.projectIdCache.set(projectId, signedRid);\n return projectId;\n }\n\n /** Updates project metadata */\n public async setProjectMeta(\n id: ProjectId,\n meta: ProjectMeta,\n author?: AuthorMarker,\n ): Promise<void> {\n const rid = await this.resolveProjectId(id);\n await withProjectAuthored(\n this.env.projectHelper,\n this.pl,\n rid,\n author,\n (prj) => {\n prj.setMeta(meta);\n },\n { name: \"setProjectMeta\" },\n );\n await this.projectListTree.refreshState();\n }\n\n /** Permanently deletes project from the project list, this will result in\n * destruction of all attached objects, like files, analysis results etc. */\n public async deleteProject(id: ProjectId): Promise<void> {\n await this.pl.withWriteTx(\"MLRemoveProject\", async (tx) => {\n const data = await tx.getResourceData(this.projectListResourceId, true);\n let fieldName: string | undefined;\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (resourceIdToString(f.value) === (id as string)) {\n fieldName = f.name;\n break;\n }\n }\n if (fieldName === undefined) throw new Error(`Project ${id} not found in project list.`);\n tx.removeField(field(this.projectListResourceId, fieldName));\n await tx.commit();\n });\n this.projectIdCache.delete(id);\n await this.projectListTree.refreshState();\n }\n\n /**\n * Duplicates an existing project and adds the copy to this user's project list.\n *\n * @param srcProjectId - project id of the project to duplicate\n * @param rename - optional function that receives the source label and all existing\n * project labels (read within the same transaction), and returns the label for the copy\n */\n public async duplicateProject(\n srcProjectId: ProjectId,\n rename?: (previousLabel: string, existingLabels: string[]) => string,\n ): Promise<ProjectId> {\n const sourceRid = await this.resolveProjectId(srcProjectId);\n\n const newPrj: ResourceRef = await this.pl.withWriteTx(\"MLDuplicateProject\", async (tx) => {\n // Read source project meta\n const sourceMeta = await tx.getKValueJson<ProjectMeta>(sourceRid, ProjectMetaKey);\n\n // Read all existing project labels from the project list (parallel reads)\n const projectListData = await tx.getResourceData(this.projectListResourceId, true);\n const projectRids = projectListData.fields\n .map((f) => f.value)\n .filter(isNotNullSignedResourceId);\n const existingLabels = (\n await Promise.all(\n projectRids.map((rid) => tx.getKValueJson<ProjectMeta>(rid, ProjectMetaKey)),\n )\n ).map((m) => m.label);\n\n // Compute new label\n const newLabel = rename ? rename(sourceMeta.label, existingLabels) : sourceMeta.label;\n\n // Create the duplicate\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n\n // Attach to project list with a random UUID field name\n tx.createField(field(this.projectListResourceId, randomUUID()), \"Dynamic\", newPrj);\n await tx.commit();\n\n return newPrj;\n });\n\n await this.projectListTree.refreshState();\n\n const signedRid = await newPrj.globalId;\n const newProjectId = resourceIdToString(signedRid) as ProjectId;\n this.projectIdCache.set(newProjectId, signedRid);\n return newProjectId;\n }\n\n //\n // Projects\n //\n\n private readonly openedProjects = new Map<ProjectId, Project>();\n\n /** Opens a project, and starts corresponding project maintenance loop. */\n public async openProject(id: ProjectId): Promise<void> {\n if (this.openedProjects.has(id)) throw new Error(`Project ${id} already opened`);\n const rid = await this.resolveProjectId(id);\n this.openedProjects.set(id, await Project.init(this.env, id, rid));\n this.openedProjectsList.setValue([...this.openedProjects.keys()]);\n }\n\n /** Closes the project, and deallocate all corresponding resources. */\n public async closeProject(id: ProjectId): Promise<void> {\n const prj = this.openedProjects.get(id);\n if (prj === undefined) throw new Error(`Project ${id} not found among opened projects`);\n this.openedProjects.delete(id);\n await prj.destroy();\n this.openedProjectsList.setValue([...this.openedProjects.keys()]);\n }\n\n /** Returns a project access object for an opened project. */\n public getOpenedProject(id: ProjectId): Project {\n const prj = this.openedProjects.get(id);\n if (prj === undefined) throw new Error(`Project ${id} not found among opened projects`);\n return prj;\n }\n\n /** Returns true if project with given id is currently opened. */\n public isProjectOpened(id: ProjectId): boolean {\n return this.openedProjects.has(id);\n }\n\n /**\n * Deallocates all runtime resources consumed by this object and awaits\n * actual termination of event loops and other processes associated with\n * them.\n */\n public async close() {\n await Promise.all([...this.openedProjects.values()].map((prj) => prj.destroy()));\n // this.env.quickJs;\n await this.projectListTree.terminate();\n await this.env.dispose();\n await this.pl.close();\n }\n\n /** @deprecated */\n public async closeAndAwaitTermination() {\n await this.close();\n }\n\n /** Generates sufficiently random string to be used as local secret for the\n * middle layer */\n public static generateLocalSecret(): string {\n return HmacSha256Signer.generateSecret();\n }\n\n /** Returns a block event dispatcher, which can be used to listen to block events. */\n public get blockEventDispatcher(): BlockEventDispatcher {\n return this.env.blockEventDispatcher;\n }\n\n /** Initialize middle layer */\n public static async init(\n pl: PlClient,\n workdir: string,\n _ops: MiddleLayerOpsConstructor,\n ): Promise<MiddleLayer> {\n const ops: MiddleLayerOps = {\n ...DefaultMiddleLayerOpsSettings,\n ...DefaultMiddleLayerOpsPaths(workdir),\n ..._ops,\n };\n\n // overriding debug options from environment variables\n ops.defaultTreeOptions.logStat = getDebugFlags().logTreeStats;\n ops.debugOps.dumpInitialTreeState = getDebugFlags().dumpInitialTreeState;\n // apply MI_TREE_TRAVERSAL only when the embedder hasn't set an explicit mode\n if (\n ops.defaultTreeOptions.traversalMode === undefined &&\n getDebugFlags().treeTraversalMode !== undefined\n )\n ops.defaultTreeOptions.traversalMode = getDebugFlags().treeTraversalMode;\n\n const projects = await pl.withWriteTx(\"MLInitialization\", async (tx) => {\n const projectsField = field(tx.clientRoot, ProjectsField);\n tx.createField(projectsField, \"Dynamic\");\n const projectsFieldData = await tx.getField(projectsField);\n if (isNullSignedResourceId(projectsFieldData.value)) {\n const projects = tx.createEphemeral(ProjectsResourceType);\n tx.lock(projects);\n\n tx.setField(projectsField, projects);\n\n await tx.commit();\n\n return await projects.globalId;\n } else {\n return projectsFieldData.value;\n }\n });\n\n const logger = ops.logger;\n\n const driverKit = await initDriverKit(pl, workdir, ops.frontendDownloadPath, ops);\n\n // passed to components having no own retry logic\n const retryHttpDispatcher = new RetryAgent(pl.httpDispatcher);\n\n const v2RegistryProvider = new V2RegistryProvider(retryHttpDispatcher);\n\n const bpPreparer = new BlockPackPreparer(\n v2RegistryProvider,\n driverKit.signer,\n retryHttpDispatcher,\n );\n\n const quickJs = await getQuickJS();\n\n const runtimeCapabilities = new RuntimeCapabilities();\n // add runtime capabilities of model here\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 1);\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresCreatePTable\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresPFramesVersion\", REQUIRES_PFRAMES_VERSION);\n registerServiceCapabilities((flag, value) =>\n runtimeCapabilities.addSupportedRequirement(flag, value),\n );\n // runtime capabilities of the desktop are to be added by the desktop app / test framework\n\n const serviceRegistry = createModelServiceRegistry({ logger });\n\n const env: MiddleLayerEnvironment = {\n pl,\n blockEventDispatcher: new BlockEventDispatcher(),\n signer: driverKit.signer,\n logger,\n httpDispatcher: pl.httpDispatcher,\n retryHttpDispatcher,\n ops,\n bpPreparer,\n frontendDownloadDriver: driverKit.frontendDriver,\n driverKit,\n blockUpdateWatcher: new BlockUpdateWatcher(v2RegistryProvider, logger, {\n minDelay: ops.devBlockUpdateRecheckInterval,\n http: retryHttpDispatcher,\n preferredUpdateChannel: ops.preferredUpdateChannel,\n }),\n runtimeCapabilities,\n serviceRegistry,\n quickJs,\n projectHelper: new ProjectHelper(quickJs, logger),\n dispose: async () => {\n await serviceRegistry.dispose();\n await retryHttpDispatcher.destroy();\n await driverKit.dispose();\n },\n };\n\n const openedProjects = new WatchableValue<ProjectId[]>([]);\n const projectListTC = await createProjectList(pl, projects, openedProjects, env);\n\n return new MiddleLayer(\n env,\n driverKit,\n driverKit.signer,\n projects,\n openedProjects,\n projectListTC.tree,\n v2RegistryProvider,\n projectListTC.computable,\n );\n }\n}\n"],"mappings":"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"}
@@ -115,6 +115,7 @@ var Project = class Project {
115
115
  const preparedBp = await this.env.bpPreparer.prepare(blockPackSpec);
116
116
  const blockCfg = (0, _platforma_sdk_model.extractConfig)(preparedBp.config);
117
117
  this.env.runtimeCapabilities.throwIfIncompatible(blockCfg.featureFlags);
118
+ throwIfMissingServerCapabilities(this.env.pl, preparedBp.requiredCapabilities);
118
119
  const cachedBp = await require_template_cache.cacheBlockPackTemplate(this.env.pl, preparedBp);
119
120
  const newBlockSpec = blockCfg.modelAPIVersion === _platforma_sdk_model.BLOCK_STORAGE_FACADE_VERSION ? {
120
121
  storageMode: "fromModel",
@@ -172,6 +173,7 @@ var Project = class Project {
172
173
  const preparedBp = await this.env.bpPreparer.prepare(blockPackSpec);
173
174
  const blockCfg = (0, _platforma_sdk_model.extractConfig)(preparedBp.config);
174
175
  this.env.runtimeCapabilities.throwIfIncompatible(blockCfg.featureFlags);
176
+ throwIfMissingServerCapabilities(this.env.pl, preparedBp.requiredCapabilities);
175
177
  const cachedBp = await require_template_cache.cacheBlockPackTemplate(this.env.pl, preparedBp);
176
178
  const resetState = resetArgs ? { state: blockCfg.modelAPIVersion === 1 ? {
177
179
  args: blockCfg.initialArgs,
@@ -550,6 +552,21 @@ function convertErrorsToStrings(outputs) {
550
552
  }
551
553
  return result;
552
554
  }
555
+ /** Throws when the connected backend doesn't advertise every capability the
556
+ * block-pack requires (as declared in `meta.requiredCapabilities`).
557
+ *
558
+ * The matching set is the desktop's install gate moved server-side so both
559
+ * the UI (`AddBlockModal`) and direct middle-layer callers (the MCP server's
560
+ * `add_block` tool, programmatic block installs, tests) reject incompatible
561
+ * blocks consistently. */
562
+ function throwIfMissingServerCapabilities(pl, requiredCapabilities) {
563
+ if (!requiredCapabilities || requiredCapabilities.length === 0) return;
564
+ const advertised = pl.serverInfo.capabilities ?? [];
565
+ const filterFn = (c) => !advertised.includes(c);
566
+ if (!requiredCapabilities.some(filterFn)) return;
567
+ const missing = requiredCapabilities.filter(filterFn);
568
+ throw new Error(`Block cannot be added: connected backend does not advertise capabilities ${JSON.stringify(missing)}. Backend advertises ${JSON.stringify(advertised)}. Upgrade the backend or remove the requirement from the block manifest.`);
569
+ }
553
570
  //#endregion
554
571
  exports.Project = Project;
555
572