@milaboratories/pl-middle-layer 1.66.19 → 1.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dist/block_registry/index.cjs +3 -0
  2. package/dist/block_registry/index.d.ts +2 -1
  3. package/dist/block_registry/index.js +2 -1
  4. package/dist/block_registry/location_provider.cjs +117 -0
  5. package/dist/block_registry/location_provider.cjs.map +1 -0
  6. package/dist/block_registry/location_provider.js +114 -0
  7. package/dist/block_registry/location_provider.js.map +1 -0
  8. package/dist/block_registry/registry.cjs +13 -0
  9. package/dist/block_registry/registry.cjs.map +1 -1
  10. package/dist/block_registry/registry.d.ts +14 -1
  11. package/dist/block_registry/registry.d.ts.map +1 -1
  12. package/dist/block_registry/registry.js +13 -0
  13. package/dist/block_registry/registry.js.map +1 -1
  14. package/dist/block_registry/template_provider.cjs +104 -0
  15. package/dist/block_registry/template_provider.cjs.map +1 -0
  16. package/dist/block_registry/template_provider.d.ts +62 -0
  17. package/dist/block_registry/template_provider.d.ts.map +1 -0
  18. package/dist/block_registry/template_provider.js +103 -0
  19. package/dist/block_registry/template_provider.js.map +1 -0
  20. package/dist/block_registry/watcher.cjs +3 -3
  21. package/dist/block_registry/watcher.js +2 -2
  22. package/dist/dev_env/util.js +2 -2
  23. package/dist/dev_env/util.js.map +1 -1
  24. package/dist/index.cjs +10 -0
  25. package/dist/index.d.ts +5 -1
  26. package/dist/index.js +5 -1
  27. package/dist/js_render/computable_context.cjs +1 -1
  28. package/dist/js_render/computable_context.js +1 -1
  29. package/dist/middle_layer/driver_kit.d.ts +1 -1
  30. package/dist/middle_layer/middle_layer.cjs +144 -2
  31. package/dist/middle_layer/middle_layer.cjs.map +1 -1
  32. package/dist/middle_layer/middle_layer.d.ts +67 -1
  33. package/dist/middle_layer/middle_layer.d.ts.map +1 -1
  34. package/dist/middle_layer/middle_layer.js +146 -4
  35. package/dist/middle_layer/middle_layer.js.map +1 -1
  36. package/dist/middle_layer/project.cjs +1 -0
  37. package/dist/middle_layer/project.cjs.map +1 -1
  38. package/dist/middle_layer/project.d.ts +2 -2
  39. package/dist/middle_layer/project.js +4 -4
  40. package/dist/middle_layer/project.js.map +1 -1
  41. package/dist/middle_layer/sharing_list.d.ts +1 -1
  42. package/dist/model/args.cjs +9 -28
  43. package/dist/model/args.cjs.map +1 -1
  44. package/dist/model/args.js +9 -28
  45. package/dist/model/args.js.map +1 -1
  46. package/dist/model/index.cjs +7 -0
  47. package/dist/model/index.d.ts +4 -1
  48. package/dist/model/index.js +4 -1
  49. package/dist/model/project_helper.cjs +111 -0
  50. package/dist/model/project_helper.cjs.map +1 -1
  51. package/dist/model/project_helper.d.ts +76 -1
  52. package/dist/model/project_helper.d.ts.map +1 -1
  53. package/dist/model/project_helper.js +111 -0
  54. package/dist/model/project_helper.js.map +1 -1
  55. package/dist/model/project_model.d.ts +1 -1
  56. package/dist/model/sharing_model.d.ts +1 -1
  57. package/dist/model/template_apply.cjs +67 -0
  58. package/dist/model/template_apply.cjs.map +1 -0
  59. package/dist/model/template_apply.d.ts +50 -0
  60. package/dist/model/template_apply.d.ts.map +1 -0
  61. package/dist/model/template_apply.js +64 -0
  62. package/dist/model/template_apply.js.map +1 -0
  63. package/dist/model/template_export.cjs +82 -0
  64. package/dist/model/template_export.cjs.map +1 -0
  65. package/dist/model/template_export.d.ts +9 -0
  66. package/dist/model/template_export.d.ts.map +1 -0
  67. package/dist/model/template_export.js +82 -0
  68. package/dist/model/template_export.js.map +1 -0
  69. package/dist/model/template_parser.cjs +74 -0
  70. package/dist/model/template_parser.cjs.map +1 -0
  71. package/dist/model/template_parser.d.ts +43 -0
  72. package/dist/model/template_parser.d.ts.map +1 -0
  73. package/dist/model/template_parser.js +72 -0
  74. package/dist/model/template_parser.js.map +1 -0
  75. package/dist/model/template_resolve.cjs +134 -0
  76. package/dist/model/template_resolve.cjs.map +1 -0
  77. package/dist/model/template_resolve.d.ts +193 -0
  78. package/dist/model/template_resolve.d.ts.map +1 -0
  79. package/dist/model/template_resolve.js +133 -0
  80. package/dist/model/template_resolve.js.map +1 -0
  81. package/dist/model/template_serializer.cjs +162 -0
  82. package/dist/model/template_serializer.cjs.map +1 -0
  83. package/dist/model/template_serializer.d.ts +17 -0
  84. package/dist/model/template_serializer.d.ts.map +1 -0
  85. package/dist/model/template_serializer.js +157 -0
  86. package/dist/model/template_serializer.js.map +1 -0
  87. package/dist/mutator/block-pack/block_pack.cjs +1 -1
  88. package/dist/mutator/block-pack/block_pack.js +1 -1
  89. package/dist/mutator/migration.cjs +1 -1
  90. package/dist/mutator/migration.js +1 -1
  91. package/dist/mutator/project.cjs +35 -5
  92. package/dist/mutator/project.cjs.map +1 -1
  93. package/dist/mutator/project.d.ts.map +1 -1
  94. package/dist/mutator/project.js +35 -5
  95. package/dist/mutator/project.js.map +1 -1
  96. package/dist/mutator/template_construct.cjs +77 -0
  97. package/dist/mutator/template_construct.cjs.map +1 -0
  98. package/dist/mutator/template_construct.js +77 -0
  99. package/dist/mutator/template_construct.js.map +1 -0
  100. package/dist/network_check/template.js +3 -3
  101. package/dist/network_check/template.js.map +1 -1
  102. package/package.json +18 -18
  103. package/src/block_registry/index.ts +1 -0
  104. package/src/block_registry/location_provider.test.ts +236 -0
  105. package/src/block_registry/location_provider.ts +124 -0
  106. package/src/block_registry/registry.ts +22 -0
  107. package/src/block_registry/template_provider.test.ts +275 -0
  108. package/src/block_registry/template_provider.ts +168 -0
  109. package/src/middle_layer/middle_layer.ts +192 -1
  110. package/src/middle_layer/project.ts +1 -1
  111. package/src/model/args.test.ts +26 -1
  112. package/src/model/args.ts +16 -31
  113. package/src/model/index.ts +21 -0
  114. package/src/model/project_helper.ts +238 -0
  115. package/src/model/project_helper_params_init.test.ts +249 -0
  116. package/src/model/template_apply.test.ts +65 -0
  117. package/src/model/template_apply.ts +128 -0
  118. package/src/model/template_export.test.ts +255 -0
  119. package/src/model/template_export.ts +136 -0
  120. package/src/model/template_parser.test.ts +264 -0
  121. package/src/model/template_parser.ts +108 -0
  122. package/src/model/template_resolve.test.ts +397 -0
  123. package/src/model/template_resolve.ts +346 -0
  124. package/src/model/template_serializer.test.ts +398 -0
  125. package/src/model/template_serializer.ts +232 -0
  126. package/src/model/template_serializer_fixtures.test.ts +274 -0
  127. package/src/mutator/project-v3.test.ts +11 -11
  128. package/src/mutator/project.ts +92 -5
  129. package/src/mutator/template_construct.test.ts +456 -0
  130. package/src/mutator/template_construct.ts +150 -0
@@ -0,0 +1,82 @@
1
+ import { allBlocks } from "./project_model_util.js";
2
+ //#region src/model/template_export.ts
3
+ /**
4
+ * Walk a project's blocks in dependency order, collecting each one's
5
+ * template-descriptor output.
6
+ *
7
+ * **No topological sort is performed, because none is needed.** The project
8
+ * structure is already stored in topological order, and that is enforced rather
9
+ * than assumed: `productionGraph` traverses `allBlocks(structure)` and passes the
10
+ * set of blocks seen *so far* as the allowed set to `inferAllReferencedBlocks`, so
11
+ * a reference to a block that is not already above is recorded as a missing
12
+ * reference instead of an upstream. A block can therefore only legally reference
13
+ * blocks earlier in this sequence — which is exactly what a template file needs,
14
+ * since its block order is the instantiation order and the engine creates blocks
15
+ * upstream-first. Emitting entries in structure order satisfies that for free.
16
+ *
17
+ * Groups are flattened in order, so cross-group ordering is the structure's too.
18
+ *
19
+ * A structure that violates the ordering rule is reported as-is, not repaired:
20
+ * reordering would change which references are legal in the first place.
21
+ *
22
+ * Params are written exactly as the block projected them. The walk parses nothing, rewrites
23
+ * nothing and inspects nothing inside them, and neither does anything else between here and the
24
+ * file — which values carry block ids is knowledge of the reference system, and a template
25
+ * engine holds none of it. The block that receives these params on the way back in is what
26
+ * recognizes them; a block that projects the wrong fields produces a template that does not
27
+ * work, the same way one whose `templateParams` returns the wrong shape does.
28
+ *
29
+ * @param structure The project structure — the source of both membership and order
30
+ * @param paramsProvider Yields a block's derived template params. Return
31
+ * `undefined` for a block whose state cannot be read at all; such a block is
32
+ * recorded as a problem rather than skipped, because a template that quietly
33
+ * omits a block does not describe the project it was exported from, and the
34
+ * surviving entries may still reference the omitted one.
35
+ */
36
+ function walkProjectForTemplateExport(structure, paramsProvider) {
37
+ const entries = [];
38
+ const problems = [];
39
+ for (const { id } of allBlocks(structure)) {
40
+ const derived = paramsProvider(id);
41
+ if (derived === void 0) {
42
+ problems.push({
43
+ blockId: id,
44
+ error: "Block state is unavailable, so its template params could not be derived"
45
+ });
46
+ continue;
47
+ }
48
+ if (derived.error !== void 0) {
49
+ problems.push({
50
+ blockId: id,
51
+ error: derived.error
52
+ });
53
+ continue;
54
+ }
55
+ const params = derived.value;
56
+ if (typeof params !== "object" || params === null || Array.isArray(params)) {
57
+ problems.push({
58
+ blockId: id,
59
+ error: `templateParams() must return an object, got ${typeName(params)}`
60
+ });
61
+ continue;
62
+ }
63
+ entries.push({
64
+ blockId: id,
65
+ params
66
+ });
67
+ }
68
+ return {
69
+ entries,
70
+ problems
71
+ };
72
+ }
73
+ /** Name the offending value's type for an error message, without printing the value. */
74
+ function typeName(value) {
75
+ if (value === null) return "null";
76
+ if (Array.isArray(value)) return "an array";
77
+ return `a ${typeof value}`;
78
+ }
79
+ //#endregion
80
+ export { walkProjectForTemplateExport };
81
+
82
+ //# sourceMappingURL=template_export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template_export.js","names":[],"sources":["../../src/model/template_export.ts"],"sourcesContent":["import type { ProjectStructure } from \"./project_model\";\nimport { allBlocks } from \"./project_model_util\";\n\n/**\n * One block's template-descriptor output as the walk receives it.\n *\n * Deliberately the same shape the `__pl_initializationParams_derive` facade callback\n * returns, so a provider can hand the VM's result straight through without\n * reshaping it.\n */\nexport type TemplateParamsResult =\n | { readonly error: string }\n | { readonly error?: undefined; readonly value: unknown };\n\n/** One block's contribution to the template being exported. */\nexport type TemplateExportEntry = {\n /**\n * The block's project-local id, which is also its template-local id: a template\n * has no id namespace of its own, so the id is reused verbatim and references\n * already stored in params need no translation.\n */\n readonly blockId: string;\n /**\n * The block's params exactly as it projected them.\n *\n * Always a mapping: a block that declared no `templateParams`, or whose lambda returned\n * something else, is reported as a problem rather than carried here — see the object check\n * in the walk. So nothing downstream has an absent case to decide.\n */\n readonly params: Record<string, unknown>;\n};\n\n/** Why one block could not be exported. */\nexport type TemplateExportProblem = {\n readonly blockId: string;\n readonly error: string;\n};\n\n/**\n * Outcome of the walk: the blocks that can be written, and the ones that cannot.\n *\n * Both lists are returned rather than throwing on the first failure, so the\n * caller can report every offending block at once instead of making the user fix\n * them one export at a time. Whether a non-empty `problems` aborts the export is\n * the caller's policy, not the walk's — but note that emitting `entries` while\n * ignoring `problems` can produce a file whose surviving entries reference a\n * dropped block, which is an unusable template.\n */\nexport type TemplateExportWalk = {\n readonly entries: readonly TemplateExportEntry[];\n readonly problems: readonly TemplateExportProblem[];\n};\n\n/**\n * Walk a project's blocks in dependency order, collecting each one's\n * template-descriptor output.\n *\n * **No topological sort is performed, because none is needed.** The project\n * structure is already stored in topological order, and that is enforced rather\n * than assumed: `productionGraph` traverses `allBlocks(structure)` and passes the\n * set of blocks seen *so far* as the allowed set to `inferAllReferencedBlocks`, so\n * a reference to a block that is not already above is recorded as a missing\n * reference instead of an upstream. A block can therefore only legally reference\n * blocks earlier in this sequence — which is exactly what a template file needs,\n * since its block order is the instantiation order and the engine creates blocks\n * upstream-first. Emitting entries in structure order satisfies that for free.\n *\n * Groups are flattened in order, so cross-group ordering is the structure's too.\n *\n * A structure that violates the ordering rule is reported as-is, not repaired:\n * reordering would change which references are legal in the first place.\n *\n * Params are written exactly as the block projected them. The walk parses nothing, rewrites\n * nothing and inspects nothing inside them, and neither does anything else between here and the\n * file — which values carry block ids is knowledge of the reference system, and a template\n * engine holds none of it. The block that receives these params on the way back in is what\n * recognizes them; a block that projects the wrong fields produces a template that does not\n * work, the same way one whose `templateParams` returns the wrong shape does.\n *\n * @param structure The project structure — the source of both membership and order\n * @param paramsProvider Yields a block's derived template params. Return\n * `undefined` for a block whose state cannot be read at all; such a block is\n * recorded as a problem rather than skipped, because a template that quietly\n * omits a block does not describe the project it was exported from, and the\n * surviving entries may still reference the omitted one.\n */\nexport function walkProjectForTemplateExport(\n structure: ProjectStructure,\n paramsProvider: (blockId: string) => TemplateParamsResult | undefined,\n): TemplateExportWalk {\n const entries: TemplateExportEntry[] = [];\n const problems: TemplateExportProblem[] = [];\n\n for (const { id } of allBlocks(structure)) {\n const derived = paramsProvider(id);\n\n if (derived === undefined) {\n problems.push({\n blockId: id,\n error: \"Block state is unavailable, so its template params could not be derived\",\n });\n continue;\n }\n\n if (derived.error !== undefined) {\n problems.push({ blockId: id, error: derived.error });\n continue;\n }\n\n const params = derived.value;\n\n // An entry's `params` must be a mapping. The lambda's declared return type is the\n // block kind's params type, and the kind's parser checks values coming IN, but\n // nothing checks what the lambda hands back on the way out — so a block whose\n // params type is a primitive or a tuple compiles fine and would produce an\n // unwritable entry. This is the only place that can catch it.\n if (typeof params !== \"object\" || params === null || Array.isArray(params)) {\n problems.push({\n blockId: id,\n error: `templateParams() must return an object, got ${typeName(params)}`,\n });\n continue;\n }\n\n entries.push({ blockId: id, params: params as Record<string, unknown> });\n }\n\n return { entries, problems };\n}\n\n/** Name the offending value's type for an error message, without printing the value. */\nfunction typeName(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"an array\";\n return `a ${typeof value}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsFA,SAAgB,6BACd,WACA,gBACoB;CACpB,MAAM,UAAiC,CAAC;CACxC,MAAM,WAAoC,CAAC;CAE3C,KAAK,MAAM,EAAE,QAAQ,UAAU,SAAS,GAAG;EACzC,MAAM,UAAU,eAAe,EAAE;EAEjC,IAAI,YAAY,KAAA,GAAW;GACzB,SAAS,KAAK;IACZ,SAAS;IACT,OAAO;GACT,CAAC;GACD;EACF;EAEA,IAAI,QAAQ,UAAU,KAAA,GAAW;GAC/B,SAAS,KAAK;IAAE,SAAS;IAAI,OAAO,QAAQ;GAAM,CAAC;GACnD;EACF;EAEA,MAAM,SAAS,QAAQ;EAOvB,IAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;GAC1E,SAAS,KAAK;IACZ,SAAS;IACT,OAAO,+CAA+C,SAAS,MAAM;GACvE,CAAC;GACD;EACF;EAEA,QAAQ,KAAK;GAAE,SAAS;GAAY;EAAkC,CAAC;CACzE;CAEA,OAAO;EAAE;EAAS;CAAS;AAC7B;;AAGA,SAAS,SAAS,OAAwB;CACxC,IAAI,UAAU,MAAM,OAAO;CAC3B,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO;CACjC,OAAO,KAAK,OAAO;AACrB"}
@@ -0,0 +1,74 @@
1
+ const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
2
+ let yaml = require("yaml");
3
+ yaml = require_runtime.__toESM(yaml, 1);
4
+ let _milaboratories_pl_model_common = require("@milaboratories/pl-model-common");
5
+ //#region src/model/template_parser.ts
6
+ /**
7
+ * Read `template-v1` YAML text into a document.
8
+ *
9
+ * The text half of the import direction, mirroring `stringifyProjectTemplateV1` on the
10
+ * export side — and, like it, living here rather than in `pl-model-common`, which ships
11
+ * in every block-model and UI bundle and takes no `yaml` dependency. The document half
12
+ * is `parseProjectTemplateV1`, shared with export so that what export writes is by
13
+ * construction what import reads.
14
+ *
15
+ * JSON is accepted for free: every JSON document is also YAML.
16
+ *
17
+ * **Read as YAML 1.2**, while the emitter quotes as if for 1.1. That asymmetry is
18
+ * deliberate on both ends: quoting against the stricter ruleset makes a file we write
19
+ * mean the same thing to any reader, and reading with the looser one means a bare
20
+ * `yes` or `1:30` in a hand-written file stays the string it looks like instead of
21
+ * silently becoming `true` or `90`.
22
+ *
23
+ * Nothing here checks that references point anywhere or that a kind can be resolved —
24
+ * those are later stages, and both need more than the file to answer.
25
+ */
26
+ function parseProjectTemplateV1Yaml(text) {
27
+ let value;
28
+ try {
29
+ value = yaml.default.parse(text);
30
+ } catch (e) {
31
+ return {
32
+ ok: false,
33
+ error: `The file is not valid YAML: ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`
34
+ };
35
+ }
36
+ if (value === null || value === void 0) return {
37
+ ok: false,
38
+ error: "The file is empty."
39
+ };
40
+ if (typeof value !== "object" || Array.isArray(value)) return {
41
+ ok: false,
42
+ error: "The file does not describe a template: expected a mapping with 'schema' and 'blocks' at the top level."
43
+ };
44
+ const marker = value.schema;
45
+ if (marker !== _milaboratories_pl_model_common.PROJECT_TEMPLATE_SCHEMA_V1) return {
46
+ ok: false,
47
+ error: marker === void 0 ? `This file has no 'schema' field, so it is not a ${_milaboratories_pl_model_common.PROJECT_TEMPLATE_SCHEMA_V1} template.` : `This file says it is '${String(marker)}', not ${_milaboratories_pl_model_common.PROJECT_TEMPLATE_SCHEMA_V1}.`
48
+ };
49
+ const outcome = (0, _milaboratories_pl_model_common.readProjectTemplateV1)(value);
50
+ return outcome.ok ? {
51
+ ok: true,
52
+ document: outcome.document
53
+ } : {
54
+ ok: false,
55
+ error: describeIssues(outcome.issues)
56
+ };
57
+ }
58
+ /**
59
+ * Turn the reader's issues into something a person can act on, all of them at once.
60
+ *
61
+ * The per-issue line comes from `formatTemplateParseIssue`, shared with the error the reader
62
+ * throws, so a problem reads the same whether it reached a person through this outcome or
63
+ * through a log. What is added here is the headline, which is the one part that belongs to
64
+ * this stage: it says the FILE is at fault, which a caller that has one can say and the
65
+ * document reader cannot.
66
+ */
67
+ function describeIssues(issues) {
68
+ const lines = issues.map((issue) => `- ${(0, _milaboratories_pl_model_common.formatTemplateParseIssue)(issue)}`);
69
+ return [lines.length === 1 ? "The template file has a problem:" : `The template file has ${lines.length} problems:`, ...lines].join("\n");
70
+ }
71
+ //#endregion
72
+ exports.parseProjectTemplateV1Yaml = parseProjectTemplateV1Yaml;
73
+
74
+ //# sourceMappingURL=template_parser.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template_parser.cjs","names":["YAML","PROJECT_TEMPLATE_SCHEMA_V1"],"sources":["../../src/model/template_parser.ts"],"sourcesContent":["import YAML from \"yaml\";\nimport type { ProjectTemplateV1, TemplateParseIssue } from \"@milaboratories/pl-model-common\";\nimport {\n PROJECT_TEMPLATE_SCHEMA_V1,\n formatTemplateParseIssue,\n readProjectTemplateV1,\n} from \"@milaboratories/pl-model-common\";\n\n/**\n * A template file read, or why it could not be.\n *\n * A single message rather than a list of per-entry problems: until the document\n * parses there are no entries to attach anything to, and a file that does not parse\n * has one problem — it is not a template. Per-entry reporting starts at the stage\n * after this one. The message may span several lines when the file has several\n * fixable issues, so that fixing it takes one pass.\n */\nexport type TemplateParseOutcome =\n | { readonly ok: true; readonly document: ProjectTemplateV1 }\n | { readonly ok: false; readonly error: string };\n\n/**\n * Read `template-v1` YAML text into a document.\n *\n * The text half of the import direction, mirroring `stringifyProjectTemplateV1` on the\n * export side — and, like it, living here rather than in `pl-model-common`, which ships\n * in every block-model and UI bundle and takes no `yaml` dependency. The document half\n * is `parseProjectTemplateV1`, shared with export so that what export writes is by\n * construction what import reads.\n *\n * JSON is accepted for free: every JSON document is also YAML.\n *\n * **Read as YAML 1.2**, while the emitter quotes as if for 1.1. That asymmetry is\n * deliberate on both ends: quoting against the stricter ruleset makes a file we write\n * mean the same thing to any reader, and reading with the looser one means a bare\n * `yes` or `1:30` in a hand-written file stays the string it looks like instead of\n * silently becoming `true` or `90`.\n *\n * Nothing here checks that references point anywhere or that a kind can be resolved —\n * those are later stages, and both need more than the file to answer.\n */\nexport function parseProjectTemplateV1Yaml(text: string): TemplateParseOutcome {\n let value: unknown;\n try {\n value = YAML.parse(text);\n } catch (e) {\n // The library's message already carries \"at line L, column C\"; what follows it is\n // a code frame of the offending lines, dropped here because whoever is fixing the\n // file has it open and the position is what they need.\n const first = e instanceof Error ? e.message.split(\"\\n\")[0] : String(e);\n return { ok: false, error: `The file is not valid YAML: ${first}` };\n }\n\n // Empty input, blank lines and a file of nothing but comments all parse to null.\n // Left to the schema, this reads as \"expected object, received null\", which says\n // nothing about the actual mistake.\n if (value === null || value === undefined) {\n return { ok: false, error: \"The file is empty.\" };\n }\n\n if (typeof value !== \"object\" || Array.isArray(value)) {\n return {\n ok: false,\n error:\n \"The file does not describe a template: expected a mapping with 'schema' and \" +\n \"'blocks' at the top level.\",\n };\n }\n\n // Checked ahead of the document reader because it is the likeliest mistake by far — the\n // wrong file was picked — and deserves a sentence about that rather than one about a field\n // whose value is not what was expected.\n const marker = (value as { schema?: unknown }).schema;\n if (marker !== PROJECT_TEMPLATE_SCHEMA_V1) {\n return {\n ok: false,\n error:\n marker === undefined\n ? `This file has no 'schema' field, so it is not a ${PROJECT_TEMPLATE_SCHEMA_V1} template.`\n : `This file says it is '${String(marker)}', not ${PROJECT_TEMPLATE_SCHEMA_V1}.`,\n };\n }\n\n const outcome = readProjectTemplateV1(value);\n return outcome.ok\n ? { ok: true, document: outcome.document }\n : { ok: false, error: describeIssues(outcome.issues) };\n}\n\n/**\n * Turn the reader's issues into something a person can act on, all of them at once.\n *\n * The per-issue line comes from `formatTemplateParseIssue`, shared with the error the reader\n * throws, so a problem reads the same whether it reached a person through this outcome or\n * through a log. What is added here is the headline, which is the one part that belongs to\n * this stage: it says the FILE is at fault, which a caller that has one can say and the\n * document reader cannot.\n */\nfunction describeIssues(issues: readonly TemplateParseIssue[]): string {\n const lines = issues.map((issue) => `- ${formatTemplateParseIssue(issue)}`);\n\n const headline =\n lines.length === 1\n ? \"The template file has a problem:\"\n : `The template file has ${lines.length} problems:`;\n\n return [headline, ...lines].join(\"\\n\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,2BAA2B,MAAoC;CAC7E,IAAI;CACJ,IAAI;EACF,QAAQA,KAAAA,QAAK,MAAM,IAAI;CACzB,SAAS,GAAG;EAKV,OAAO;GAAE,IAAI;GAAO,OAAO,+BADb,aAAa,QAAQ,EAAE,QAAQ,MAAM,IAAI,CAAC,CAAC,KAAK,OAAO,CAAC;EACJ;CACpE;CAKA,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,OAAO;EAAE,IAAI;EAAO,OAAO;CAAqB;CAGlD,IAAI,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAClD,OAAO;EACL,IAAI;EACJ,OACE;CAEJ;CAMF,MAAM,SAAU,MAA+B;CAC/C,IAAI,WAAWC,gCAAAA,4BACb,OAAO;EACL,IAAI;EACJ,OACE,WAAW,KAAA,IACP,mDAAmDA,gCAAAA,2BAA2B,cAC9E,yBAAyB,OAAO,MAAM,EAAE,SAASA,gCAAAA,2BAA2B;CACpF;CAGF,MAAM,WAAA,GAAA,gCAAA,sBAAA,CAAgC,KAAK;CAC3C,OAAO,QAAQ,KACX;EAAE,IAAI;EAAM,UAAU,QAAQ;CAAS,IACvC;EAAE,IAAI;EAAO,OAAO,eAAe,QAAQ,MAAM;CAAE;AACzD;;;;;;;;;;AAWA,SAAS,eAAe,QAA+C;CACrE,MAAM,QAAQ,OAAO,KAAK,UAAU,MAAA,GAAA,gCAAA,yBAAA,CAA8B,KAAK,GAAG;CAO1E,OAAO,CAJL,MAAM,WAAW,IACb,qCACA,yBAAyB,MAAM,OAAO,aAE1B,GAAG,KAAK,CAAC,CAAC,KAAK,IAAI;AACvC"}
@@ -0,0 +1,43 @@
1
+ import { ProjectTemplateV1 } from "@milaboratories/pl-model-common";
2
+
3
+ //#region src/model/template_parser.d.ts
4
+ /**
5
+ * A template file read, or why it could not be.
6
+ *
7
+ * A single message rather than a list of per-entry problems: until the document
8
+ * parses there are no entries to attach anything to, and a file that does not parse
9
+ * has one problem — it is not a template. Per-entry reporting starts at the stage
10
+ * after this one. The message may span several lines when the file has several
11
+ * fixable issues, so that fixing it takes one pass.
12
+ */
13
+ type TemplateParseOutcome = {
14
+ readonly ok: true;
15
+ readonly document: ProjectTemplateV1;
16
+ } | {
17
+ readonly ok: false;
18
+ readonly error: string;
19
+ };
20
+ /**
21
+ * Read `template-v1` YAML text into a document.
22
+ *
23
+ * The text half of the import direction, mirroring `stringifyProjectTemplateV1` on the
24
+ * export side — and, like it, living here rather than in `pl-model-common`, which ships
25
+ * in every block-model and UI bundle and takes no `yaml` dependency. The document half
26
+ * is `parseProjectTemplateV1`, shared with export so that what export writes is by
27
+ * construction what import reads.
28
+ *
29
+ * JSON is accepted for free: every JSON document is also YAML.
30
+ *
31
+ * **Read as YAML 1.2**, while the emitter quotes as if for 1.1. That asymmetry is
32
+ * deliberate on both ends: quoting against the stricter ruleset makes a file we write
33
+ * mean the same thing to any reader, and reading with the looser one means a bare
34
+ * `yes` or `1:30` in a hand-written file stays the string it looks like instead of
35
+ * silently becoming `true` or `90`.
36
+ *
37
+ * Nothing here checks that references point anywhere or that a kind can be resolved —
38
+ * those are later stages, and both need more than the file to answer.
39
+ */
40
+ declare function parseProjectTemplateV1Yaml(text: string): TemplateParseOutcome;
41
+ //#endregion
42
+ export { TemplateParseOutcome, parseProjectTemplateV1Yaml };
43
+ //# sourceMappingURL=template_parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template_parser.d.ts","names":[],"sources":["../../src/model/template_parser.ts"],"mappings":";;;;;AAiBA;;;;;;;KAAY,oBAAA;EAAA,SACG,EAAA;EAAA,SAAmB,QAAA,EAAU,iBAAiB;AAAA;EAAA,SAC9C,EAAA;EAAA,SAAoB,KAAA;AAAA;;AAsB2C;;;;;;;;;;;;;;;;;;;iBAA9D,0BAAA,CAA2B,IAAA,WAAe,oBAAoB"}
@@ -0,0 +1,72 @@
1
+ import YAML from "yaml";
2
+ import { PROJECT_TEMPLATE_SCHEMA_V1, formatTemplateParseIssue, readProjectTemplateV1 } from "@milaboratories/pl-model-common";
3
+ //#region src/model/template_parser.ts
4
+ /**
5
+ * Read `template-v1` YAML text into a document.
6
+ *
7
+ * The text half of the import direction, mirroring `stringifyProjectTemplateV1` on the
8
+ * export side — and, like it, living here rather than in `pl-model-common`, which ships
9
+ * in every block-model and UI bundle and takes no `yaml` dependency. The document half
10
+ * is `parseProjectTemplateV1`, shared with export so that what export writes is by
11
+ * construction what import reads.
12
+ *
13
+ * JSON is accepted for free: every JSON document is also YAML.
14
+ *
15
+ * **Read as YAML 1.2**, while the emitter quotes as if for 1.1. That asymmetry is
16
+ * deliberate on both ends: quoting against the stricter ruleset makes a file we write
17
+ * mean the same thing to any reader, and reading with the looser one means a bare
18
+ * `yes` or `1:30` in a hand-written file stays the string it looks like instead of
19
+ * silently becoming `true` or `90`.
20
+ *
21
+ * Nothing here checks that references point anywhere or that a kind can be resolved —
22
+ * those are later stages, and both need more than the file to answer.
23
+ */
24
+ function parseProjectTemplateV1Yaml(text) {
25
+ let value;
26
+ try {
27
+ value = YAML.parse(text);
28
+ } catch (e) {
29
+ return {
30
+ ok: false,
31
+ error: `The file is not valid YAML: ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`
32
+ };
33
+ }
34
+ if (value === null || value === void 0) return {
35
+ ok: false,
36
+ error: "The file is empty."
37
+ };
38
+ if (typeof value !== "object" || Array.isArray(value)) return {
39
+ ok: false,
40
+ error: "The file does not describe a template: expected a mapping with 'schema' and 'blocks' at the top level."
41
+ };
42
+ const marker = value.schema;
43
+ if (marker !== PROJECT_TEMPLATE_SCHEMA_V1) return {
44
+ ok: false,
45
+ error: marker === void 0 ? `This file has no 'schema' field, so it is not a ${PROJECT_TEMPLATE_SCHEMA_V1} template.` : `This file says it is '${String(marker)}', not ${PROJECT_TEMPLATE_SCHEMA_V1}.`
46
+ };
47
+ const outcome = readProjectTemplateV1(value);
48
+ return outcome.ok ? {
49
+ ok: true,
50
+ document: outcome.document
51
+ } : {
52
+ ok: false,
53
+ error: describeIssues(outcome.issues)
54
+ };
55
+ }
56
+ /**
57
+ * Turn the reader's issues into something a person can act on, all of them at once.
58
+ *
59
+ * The per-issue line comes from `formatTemplateParseIssue`, shared with the error the reader
60
+ * throws, so a problem reads the same whether it reached a person through this outcome or
61
+ * through a log. What is added here is the headline, which is the one part that belongs to
62
+ * this stage: it says the FILE is at fault, which a caller that has one can say and the
63
+ * document reader cannot.
64
+ */
65
+ function describeIssues(issues) {
66
+ const lines = issues.map((issue) => `- ${formatTemplateParseIssue(issue)}`);
67
+ return [lines.length === 1 ? "The template file has a problem:" : `The template file has ${lines.length} problems:`, ...lines].join("\n");
68
+ }
69
+ //#endregion
70
+ export { parseProjectTemplateV1Yaml };
71
+
72
+ //# sourceMappingURL=template_parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template_parser.js","names":[],"sources":["../../src/model/template_parser.ts"],"sourcesContent":["import YAML from \"yaml\";\nimport type { ProjectTemplateV1, TemplateParseIssue } from \"@milaboratories/pl-model-common\";\nimport {\n PROJECT_TEMPLATE_SCHEMA_V1,\n formatTemplateParseIssue,\n readProjectTemplateV1,\n} from \"@milaboratories/pl-model-common\";\n\n/**\n * A template file read, or why it could not be.\n *\n * A single message rather than a list of per-entry problems: until the document\n * parses there are no entries to attach anything to, and a file that does not parse\n * has one problem — it is not a template. Per-entry reporting starts at the stage\n * after this one. The message may span several lines when the file has several\n * fixable issues, so that fixing it takes one pass.\n */\nexport type TemplateParseOutcome =\n | { readonly ok: true; readonly document: ProjectTemplateV1 }\n | { readonly ok: false; readonly error: string };\n\n/**\n * Read `template-v1` YAML text into a document.\n *\n * The text half of the import direction, mirroring `stringifyProjectTemplateV1` on the\n * export side — and, like it, living here rather than in `pl-model-common`, which ships\n * in every block-model and UI bundle and takes no `yaml` dependency. The document half\n * is `parseProjectTemplateV1`, shared with export so that what export writes is by\n * construction what import reads.\n *\n * JSON is accepted for free: every JSON document is also YAML.\n *\n * **Read as YAML 1.2**, while the emitter quotes as if for 1.1. That asymmetry is\n * deliberate on both ends: quoting against the stricter ruleset makes a file we write\n * mean the same thing to any reader, and reading with the looser one means a bare\n * `yes` or `1:30` in a hand-written file stays the string it looks like instead of\n * silently becoming `true` or `90`.\n *\n * Nothing here checks that references point anywhere or that a kind can be resolved —\n * those are later stages, and both need more than the file to answer.\n */\nexport function parseProjectTemplateV1Yaml(text: string): TemplateParseOutcome {\n let value: unknown;\n try {\n value = YAML.parse(text);\n } catch (e) {\n // The library's message already carries \"at line L, column C\"; what follows it is\n // a code frame of the offending lines, dropped here because whoever is fixing the\n // file has it open and the position is what they need.\n const first = e instanceof Error ? e.message.split(\"\\n\")[0] : String(e);\n return { ok: false, error: `The file is not valid YAML: ${first}` };\n }\n\n // Empty input, blank lines and a file of nothing but comments all parse to null.\n // Left to the schema, this reads as \"expected object, received null\", which says\n // nothing about the actual mistake.\n if (value === null || value === undefined) {\n return { ok: false, error: \"The file is empty.\" };\n }\n\n if (typeof value !== \"object\" || Array.isArray(value)) {\n return {\n ok: false,\n error:\n \"The file does not describe a template: expected a mapping with 'schema' and \" +\n \"'blocks' at the top level.\",\n };\n }\n\n // Checked ahead of the document reader because it is the likeliest mistake by far — the\n // wrong file was picked — and deserves a sentence about that rather than one about a field\n // whose value is not what was expected.\n const marker = (value as { schema?: unknown }).schema;\n if (marker !== PROJECT_TEMPLATE_SCHEMA_V1) {\n return {\n ok: false,\n error:\n marker === undefined\n ? `This file has no 'schema' field, so it is not a ${PROJECT_TEMPLATE_SCHEMA_V1} template.`\n : `This file says it is '${String(marker)}', not ${PROJECT_TEMPLATE_SCHEMA_V1}.`,\n };\n }\n\n const outcome = readProjectTemplateV1(value);\n return outcome.ok\n ? { ok: true, document: outcome.document }\n : { ok: false, error: describeIssues(outcome.issues) };\n}\n\n/**\n * Turn the reader's issues into something a person can act on, all of them at once.\n *\n * The per-issue line comes from `formatTemplateParseIssue`, shared with the error the reader\n * throws, so a problem reads the same whether it reached a person through this outcome or\n * through a log. What is added here is the headline, which is the one part that belongs to\n * this stage: it says the FILE is at fault, which a caller that has one can say and the\n * document reader cannot.\n */\nfunction describeIssues(issues: readonly TemplateParseIssue[]): string {\n const lines = issues.map((issue) => `- ${formatTemplateParseIssue(issue)}`);\n\n const headline =\n lines.length === 1\n ? \"The template file has a problem:\"\n : `The template file has ${lines.length} problems:`;\n\n return [headline, ...lines].join(\"\\n\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,2BAA2B,MAAoC;CAC7E,IAAI;CACJ,IAAI;EACF,QAAQ,KAAK,MAAM,IAAI;CACzB,SAAS,GAAG;EAKV,OAAO;GAAE,IAAI;GAAO,OAAO,+BADb,aAAa,QAAQ,EAAE,QAAQ,MAAM,IAAI,CAAC,CAAC,KAAK,OAAO,CAAC;EACJ;CACpE;CAKA,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,OAAO;EAAE,IAAI;EAAO,OAAO;CAAqB;CAGlD,IAAI,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAClD,OAAO;EACL,IAAI;EACJ,OACE;CAEJ;CAMF,MAAM,SAAU,MAA+B;CAC/C,IAAI,WAAW,4BACb,OAAO;EACL,IAAI;EACJ,OACE,WAAW,KAAA,IACP,mDAAmD,2BAA2B,cAC9E,yBAAyB,OAAO,MAAM,EAAE,SAAS,2BAA2B;CACpF;CAGF,MAAM,UAAU,sBAAsB,KAAK;CAC3C,OAAO,QAAQ,KACX;EAAE,IAAI;EAAM,UAAU,QAAQ;CAAS,IACvC;EAAE,IAAI;EAAO,OAAO,eAAe,QAAQ,MAAM;CAAE;AACzD;;;;;;;;;;AAWA,SAAS,eAAe,QAA+C;CACrE,MAAM,QAAQ,OAAO,KAAK,UAAU,KAAK,yBAAyB,KAAK,GAAG;CAO1E,OAAO,CAJL,MAAM,WAAW,IACb,qCACA,yBAAyB,MAAM,OAAO,aAE1B,GAAG,KAAK,CAAC,CAAC,KAAK,IAAI;AACvC"}
@@ -0,0 +1,134 @@
1
+ let _milaboratories_pl_model_common = require("@milaboratories/pl-model-common");
2
+ //#region src/model/template_resolve.ts
3
+ /**
4
+ * Resolve every entry in a template document to a concrete block pack.
5
+ *
6
+ * The first stage of an apply, and the only one that touches the network. It runs
7
+ * before the project exists, which is what makes "no block for this entry" a message
8
+ * about a file rather than a half-built project — and it is also why the whole
9
+ * construction API downstream can be synchronous.
10
+ *
11
+ * Every entry is attempted, and every failure collected: an unapplicable file should
12
+ * take one pass to fix, not one pass per bad entry. The one exception is that a
13
+ * problem short-circuits nothing, so `resolved` may be shorter than the document.
14
+ *
15
+ * @param document A parsed template document
16
+ * @param provider Where blocks come from
17
+ * @param options `allowUnstable` applies to the whole document, not per entry
18
+ */
19
+ async function resolveTemplateEntries(document, provider, options) {
20
+ const resolved = [];
21
+ const problems = [];
22
+ for (const entry of document.blocks) {
23
+ const outcome = await resolveEntry(entry, provider, options);
24
+ if (outcome.ok) resolved.push(outcome.entry);
25
+ else problems.push(outcome.problem);
26
+ }
27
+ return {
28
+ resolved,
29
+ problems
30
+ };
31
+ }
32
+ async function resolveEntry(entry, provider, options) {
33
+ const problem = (error) => ({
34
+ ok: false,
35
+ problem: {
36
+ entryId: entry.id,
37
+ error
38
+ }
39
+ });
40
+ if (entry.location !== void 0) {
41
+ const outcome = await provider.byLocation(entry.location);
42
+ if (!outcome.ok) switch (outcome.reason) {
43
+ case "unsupported-scheme": return problem(`This entry says its block is at '${entry.location}', which this application cannot read. Only a local path ('file:') is supported here.`);
44
+ case "not-found": return problem(`Nothing is at '${entry.location}', where this entry says its block is. A template that pins a folder only works on the machine that folder is on.`);
45
+ case "not-a-block": return problem(`'${entry.location}' is not a block: it holds neither a built block package nor a block's manifest. Point the entry at the folder holding the block's package.json.`);
46
+ }
47
+ return {
48
+ ok: true,
49
+ entry: {
50
+ entryId: entry.id,
51
+ spec: outcome.spec,
52
+ title: outcome.title,
53
+ pinned: true
54
+ }
55
+ };
56
+ }
57
+ if (entry.block !== void 0) {
58
+ let id;
59
+ try {
60
+ const { name, version } = (0, _milaboratories_pl_model_common.parseBlockPackReference)(entry.block);
61
+ id = {
62
+ ...parseBlockPackName(name),
63
+ version
64
+ };
65
+ } catch (e) {
66
+ return problem(`Pinned block version is not readable: ${messageOf(e)}`);
67
+ }
68
+ const outcome = await provider.byExactVersion(id);
69
+ if (outcome.ok) return {
70
+ ok: true,
71
+ entry: {
72
+ entryId: entry.id,
73
+ spec: outcome.spec,
74
+ title: outcome.title,
75
+ pinned: true
76
+ }
77
+ };
78
+ return problem(`Block '${id.organization}/${id.name}' version ${id.version} was not found. Correct the pinned version in this entry, or remove it so a version is chosen automatically.`);
79
+ }
80
+ let kindName;
81
+ try {
82
+ kindName = (0, _milaboratories_pl_model_common.parseKindSelectorReference)(entry.kind).name;
83
+ } catch (e) {
84
+ return problem(`Block kind is not readable: ${messageOf(e)}`);
85
+ }
86
+ const outcome = await provider.byKind(entry.kind, options);
87
+ if (outcome.ok) return {
88
+ ok: true,
89
+ entry: {
90
+ entryId: entry.id,
91
+ spec: outcome.spec,
92
+ title: outcome.title,
93
+ pinned: false
94
+ }
95
+ };
96
+ switch (outcome.reason) {
97
+ case "no-matching-kind-version": return problem(`No published version of '${kindName}' matches what this entry asks for (${entry.kind}). Check the version, or update the block registry.`);
98
+ case "no-implementation": return problem(`'${kindName}' has no block implementing the version this entry asks for (${entry.kind}). Nothing can be installed for it yet.`);
99
+ case "no-stable-implementation": return problem(`The only blocks implementing '${kindName}' for this entry (${entry.kind}) are pre-release versions. Import again with unstable versions allowed to use them.`);
100
+ }
101
+ }
102
+ /**
103
+ * Split a block package's npm name into the organization and name a registry knows
104
+ * it by: `@npm-scope/organization.name` → `{ organization, name }`.
105
+ *
106
+ * The npm scope and the organization are different things, and only the second one
107
+ * reaches the registry — `@milaboratories/milaboratories.test-download-file` is
108
+ * published as organization `milaboratories`, name `test-download-file`. The split is
109
+ * on the FIRST dot, so a name may contain further dots.
110
+ *
111
+ * Kind names follow the same convention, which is why a template can carry both in
112
+ * the same npm-ish form.
113
+ *
114
+ * @throws if the name has no npm scope or no organization segment
115
+ */
116
+ function parseBlockPackName(npmName) {
117
+ const slash = npmName.indexOf("/");
118
+ if (!npmName.startsWith("@") || slash < 0) throw new Error(`expected '@npm-scope/organization.name', got '${npmName}'`);
119
+ const base = npmName.slice(slash + 1);
120
+ const dot = base.indexOf(".");
121
+ if (dot <= 0 || dot === base.length - 1) throw new Error(`'${base}' does not separate an organization from a name with '.' in '${npmName}'`);
122
+ return {
123
+ organization: base.slice(0, dot),
124
+ name: base.slice(dot + 1)
125
+ };
126
+ }
127
+ function messageOf(e) {
128
+ return e instanceof Error ? e.message : String(e);
129
+ }
130
+ //#endregion
131
+ exports.parseBlockPackName = parseBlockPackName;
132
+ exports.resolveTemplateEntries = resolveTemplateEntries;
133
+
134
+ //# sourceMappingURL=template_resolve.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template_resolve.cjs","names":[],"sources":["../../src/model/template_resolve.ts"],"sourcesContent":["import type {\n BlockKindSelectorReference,\n BlockPackLocationReference,\n ProjectTemplateV1,\n ProjectTemplateV1Entry,\n} from \"@milaboratories/pl-model-common\";\nimport {\n parseBlockPackReference,\n parseKindSelectorReference,\n} from \"@milaboratories/pl-model-common\";\nimport type { BlockPackId, BlockPackSpec } from \"@milaboratories/pl-model-middle-layer\";\nimport type { TemplateApplyProblem } from \"./template_apply\";\n\n/**\n * Where a template entry's block comes from.\n *\n * All three of an entry's paths to an implementation go through this one interface, and\n * all three come back as the same `BlockPackSpec` — the shape the existing add-block\n * path already consumes — plus the block's published title. That convergence is the\n * point: everything downstream of resolution treats a kind-resolved entry, a\n * version-pinned one and a filesystem-located one identically, which is why a locally\n * built block travels the same preparation and construction path as a published one.\n *\n * Registry knowledge sits deliberately on the far side of this interface. A template\n * file names no registry, so choosing which one (or ones) to consult is a property\n * of the environment applying the file, not of the file — and keeping it out here is\n * what lets resolution be tested with no registry at all.\n *\n * The title is required rather than optional for the same reason: an implementation\n * always has one to hand — a registry adapter reads it off the block's manifest, and\n * anything else names the blocks it serves — while a caller downstream has no source\n * for it at all, and the fallbacks it could invent are all wrong. See\n * {@link ResolvedEntry}'s `title`.\n */\nexport type BlockPackProvider = {\n /**\n * Find the block implementing a kind selector.\n *\n * @param kind The entry's `{name}@{selector}` reference\n * @param options `allowUnstable` widens the search from stable implementations to\n * every published one, for the whole apply\n */\n byKind: (\n kind: BlockKindSelectorReference,\n options: { allowUnstable: boolean },\n ) => Promise<KindResolution>;\n\n /**\n * Find one exact block package version, named directly.\n *\n * This is the `block` override's path, and it bypasses kinds entirely — the\n * override exists precisely to pin an implementation that resolution would not\n * have chosen.\n */\n byExactVersion: (id: BlockPackId) => Promise<ExactResolution>;\n\n /**\n * Read the implementation sitting at one location.\n *\n * The `location` override's path, for a block that no registry knows because it was\n * built and never published. Nothing is searched: the entry says where, so either\n * that place holds a block or the entry is wrong.\n *\n * Answers \"is there a block here\", not \"is it the right one\". Whether the block found\n * implements the kind the entry asks for is settled once it has been prepared, from the\n * compiled model — the one place all three routes have it. This reader could answer it\n * too, since it reads the block's description anyway, and answering it here as well would\n * make two homes for one invariant while leaving the pinned-version route uncovered.\n */\n byLocation: (location: BlockPackLocationReference) => Promise<LocationResolution>;\n};\n\n/**\n * The outcome of resolving one kind selector.\n *\n * The three failure reasons are distinct because each has a different way out, and\n * a caller that collapsed them would leave the reader guessing which:\n *\n * - `no-matching-kind-version` — the selector matches no published version of the\n * kind at all. The file, or the registry, is wrong.\n * - `no-implementation` — the kind version exists, but nothing implements it. The\n * kind was published ahead of any block.\n * - `no-stable-implementation` — implementations exist but none is stable, and the\n * apply did not allow unstable ones. This is the only reason the user can act on\n * without editing anything.\n */\nexport type KindResolution =\n | { readonly ok: true; readonly spec: BlockPackSpec; readonly title: string }\n | {\n readonly ok: false;\n readonly reason:\n | \"no-matching-kind-version\"\n | \"no-implementation\"\n | \"no-stable-implementation\";\n };\n\n/** The outcome of locating one exact block version. */\nexport type ExactResolution =\n | { readonly ok: true; readonly spec: BlockPackSpec; readonly title: string }\n | { readonly ok: false; readonly reason: \"no-such-block-version\" };\n\n/**\n * The outcome of reading the implementation at one location.\n *\n * The three failure reasons separate three different mistakes, and each is fixed\n * differently:\n *\n * - `unsupported-scheme` — the URI is well-formed but names a way of fetching that\n * this environment has no reader for. A property of the environment, not the file.\n * - `not-found` — nothing is at that location. Usually a template carried to another\n * machine, or a folder that moved.\n * - `not-a-block` — something is there, but it is not a block. Usually a path pointing\n * one directory off, which is why the message has to say what was looked for.\n */\nexport type LocationResolution =\n | {\n readonly ok: true;\n readonly spec: BlockPackSpec;\n readonly title: string;\n }\n | {\n readonly ok: false;\n readonly reason: \"unsupported-scheme\" | \"not-found\" | \"not-a-block\";\n };\n\n/** Where one entry's block will come from. */\nexport type ResolvedEntry = {\n /** The entry's template-local id. */\n readonly entryId: string;\n readonly spec: BlockPackSpec;\n /**\n * The block package's own title, as its author published it — `meta.title` for a\n * registry block.\n *\n * Carried here because resolution is the only stage that talks to a registry, and a\n * registry is the only thing that knows it. Nothing downstream can recover it: a\n * prepared block pack holds the model, the workflow and the frontend, none of which\n * names the block.\n *\n * It becomes the created block's label, which is what the user sees for any block\n * whose model derives no title of its own — `graph-maker`, `table` and seven other\n * shipped blocks. Deriving it from the entry instead is specifically wrong: an\n * exported template names its entries by the source project's block ids, so a\n * round-tripped project would show UUIDs in the sidebar.\n */\n readonly title: string;\n /**\n * True when the entry named its own implementation — an exact version or a location —\n * rather than letting its kind choose one.\n *\n * Both overrides count, because what this records is that the choice was the file's\n * and not the registry's, which is the same fact for either of them.\n */\n readonly pinned: boolean;\n};\n\n/**\n * What resolution found for a whole document.\n *\n * `resolved` is in file order and holds only the entries that resolved, so\n * `problems.length > 0` means it is incomplete. A caller must not apply a partial\n * resolution: the point of resolving before construction is that nothing is created\n * until every entry has an implementation.\n */\nexport type TemplateResolveOutcome = {\n readonly resolved: readonly ResolvedEntry[];\n readonly problems: readonly TemplateApplyProblem[];\n};\n\n/**\n * Resolve every entry in a template document to a concrete block pack.\n *\n * The first stage of an apply, and the only one that touches the network. It runs\n * before the project exists, which is what makes \"no block for this entry\" a message\n * about a file rather than a half-built project — and it is also why the whole\n * construction API downstream can be synchronous.\n *\n * Every entry is attempted, and every failure collected: an unapplicable file should\n * take one pass to fix, not one pass per bad entry. The one exception is that a\n * problem short-circuits nothing, so `resolved` may be shorter than the document.\n *\n * @param document A parsed template document\n * @param provider Where blocks come from\n * @param options `allowUnstable` applies to the whole document, not per entry\n */\nexport async function resolveTemplateEntries(\n document: ProjectTemplateV1,\n provider: BlockPackProvider,\n options: { allowUnstable: boolean },\n): Promise<TemplateResolveOutcome> {\n const resolved: ResolvedEntry[] = [];\n const problems: TemplateApplyProblem[] = [];\n\n // Sequential, not concurrent. Entries commonly share a kind, and a provider\n // caching its reads can only dedupe them if the second request happens after the\n // first has landed. A template holds a handful of entries, so there is nothing to\n // win by overlapping the reads.\n for (const entry of document.blocks) {\n const outcome = await resolveEntry(entry, provider, options);\n if (outcome.ok) resolved.push(outcome.entry);\n else problems.push(outcome.problem);\n }\n\n return { resolved, problems };\n}\n\ntype EntryOutcome =\n | { ok: true; entry: ResolvedEntry }\n | { ok: false; problem: TemplateApplyProblem };\n\nasync function resolveEntry(\n entry: ProjectTemplateV1Entry,\n provider: BlockPackProvider,\n options: { allowUnstable: boolean },\n): Promise<EntryOutcome> {\n const problem = (error: string): EntryOutcome => ({\n ok: false,\n problem: { entryId: entry.id, error },\n });\n\n if (entry.location !== undefined) {\n const outcome = await provider.byLocation(entry.location);\n\n if (!outcome.ok) {\n switch (outcome.reason) {\n case \"unsupported-scheme\":\n return problem(\n `This entry says its block is at '${entry.location}', which this application ` +\n `cannot read. Only a local path ('file:') is supported here.`,\n );\n case \"not-found\":\n return problem(\n `Nothing is at '${entry.location}', where this entry says its block is. A ` +\n `template that pins a folder only works on the machine that folder is on.`,\n );\n case \"not-a-block\":\n return problem(\n `'${entry.location}' is not a block: it holds neither a built block package ` +\n `nor a block's manifest. Point the entry at the folder holding the block's ` +\n `package.json.`,\n );\n }\n }\n\n return {\n ok: true,\n entry: { entryId: entry.id, spec: outcome.spec, title: outcome.title, pinned: true },\n };\n }\n\n if (entry.block !== undefined) {\n let id: BlockPackId;\n try {\n const { name, version } = parseBlockPackReference(entry.block);\n id = { ...parseBlockPackName(name), version };\n } catch (e) {\n // The document's parser already checked the `{name}@X.Y.Z` grammar, but not\n // that the name carries an organization, so this is reachable from a\n // hand-written file. Reported rather than thrown, so one malformed entry cannot\n // hide the state of the rest.\n return problem(`Pinned block version is not readable: ${messageOf(e)}`);\n }\n\n const outcome = await provider.byExactVersion(id);\n if (outcome.ok)\n return {\n ok: true,\n entry: { entryId: entry.id, spec: outcome.spec, title: outcome.title, pinned: true },\n };\n\n return problem(\n `Block '${id.organization}/${id.name}' version ${id.version} was not found. Correct ` +\n `the pinned version in this entry, or remove it so a version is chosen ` +\n `automatically.`,\n );\n }\n\n let kindName: string;\n try {\n kindName = parseKindSelectorReference(entry.kind).name;\n } catch (e) {\n return problem(`Block kind is not readable: ${messageOf(e)}`);\n }\n\n const outcome = await provider.byKind(entry.kind, options);\n if (outcome.ok)\n return {\n ok: true,\n entry: { entryId: entry.id, spec: outcome.spec, title: outcome.title, pinned: false },\n };\n\n switch (outcome.reason) {\n case \"no-matching-kind-version\":\n return problem(\n `No published version of '${kindName}' matches what this entry asks for ` +\n `(${entry.kind}). Check the version, or update the block registry.`,\n );\n case \"no-implementation\":\n return problem(\n `'${kindName}' has no block implementing the version this entry asks for ` +\n `(${entry.kind}). Nothing can be installed for it yet.`,\n );\n case \"no-stable-implementation\":\n // The one failure with a way out that changes nothing in the file, so the\n // message names it.\n return problem(\n `The only blocks implementing '${kindName}' for this entry (${entry.kind}) are ` +\n `pre-release versions. Import again with unstable versions allowed to use them.`,\n );\n }\n}\n\n/**\n * Split a block package's npm name into the organization and name a registry knows\n * it by: `@npm-scope/organization.name` → `{ organization, name }`.\n *\n * The npm scope and the organization are different things, and only the second one\n * reaches the registry — `@milaboratories/milaboratories.test-download-file` is\n * published as organization `milaboratories`, name `test-download-file`. The split is\n * on the FIRST dot, so a name may contain further dots.\n *\n * Kind names follow the same convention, which is why a template can carry both in\n * the same npm-ish form.\n *\n * @throws if the name has no npm scope or no organization segment\n */\nexport function parseBlockPackName(npmName: string): { organization: string; name: string } {\n const slash = npmName.indexOf(\"/\");\n if (!npmName.startsWith(\"@\") || slash < 0) {\n throw new Error(`expected '@npm-scope/organization.name', got '${npmName}'`);\n }\n\n const base = npmName.slice(slash + 1);\n const dot = base.indexOf(\".\");\n if (dot <= 0 || dot === base.length - 1) {\n throw new Error(\n `'${base}' does not separate an organization from a name with '.' in '${npmName}'`,\n );\n }\n\n return { organization: base.slice(0, dot), name: base.slice(dot + 1) };\n}\n\nfunction messageOf(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAyLA,eAAsB,uBACpB,UACA,UACA,SACiC;CACjC,MAAM,WAA4B,CAAC;CACnC,MAAM,WAAmC,CAAC;CAM1C,KAAK,MAAM,SAAS,SAAS,QAAQ;EACnC,MAAM,UAAU,MAAM,aAAa,OAAO,UAAU,OAAO;EAC3D,IAAI,QAAQ,IAAI,SAAS,KAAK,QAAQ,KAAK;OACtC,SAAS,KAAK,QAAQ,OAAO;CACpC;CAEA,OAAO;EAAE;EAAU;CAAS;AAC9B;AAMA,eAAe,aACb,OACA,UACA,SACuB;CACvB,MAAM,WAAW,WAAiC;EAChD,IAAI;EACJ,SAAS;GAAE,SAAS,MAAM;GAAI;EAAM;CACtC;CAEA,IAAI,MAAM,aAAa,KAAA,GAAW;EAChC,MAAM,UAAU,MAAM,SAAS,WAAW,MAAM,QAAQ;EAExD,IAAI,CAAC,QAAQ,IACX,QAAQ,QAAQ,QAAhB;GACE,KAAK,sBACH,OAAO,QACL,oCAAoC,MAAM,SAAS,sFAErD;GACF,KAAK,aACH,OAAO,QACL,kBAAkB,MAAM,SAAS,kHAEnC;GACF,KAAK,eACH,OAAO,QACL,IAAI,MAAM,SAAS,iJAGrB;EACJ;EAGF,OAAO;GACL,IAAI;GACJ,OAAO;IAAE,SAAS,MAAM;IAAI,MAAM,QAAQ;IAAM,OAAO,QAAQ;IAAO,QAAQ;GAAK;EACrF;CACF;CAEA,IAAI,MAAM,UAAU,KAAA,GAAW;EAC7B,IAAI;EACJ,IAAI;GACF,MAAM,EAAE,MAAM,aAAA,GAAA,gCAAA,wBAAA,CAAoC,MAAM,KAAK;GAC7D,KAAK;IAAE,GAAG,mBAAmB,IAAI;IAAG;GAAQ;EAC9C,SAAS,GAAG;GAKV,OAAO,QAAQ,yCAAyC,UAAU,CAAC,GAAG;EACxE;EAEA,MAAM,UAAU,MAAM,SAAS,eAAe,EAAE;EAChD,IAAI,QAAQ,IACV,OAAO;GACL,IAAI;GACJ,OAAO;IAAE,SAAS,MAAM;IAAI,MAAM,QAAQ;IAAM,OAAO,QAAQ;IAAO,QAAQ;GAAK;EACrF;EAEF,OAAO,QACL,UAAU,GAAG,aAAa,GAAG,GAAG,KAAK,YAAY,GAAG,QAAQ,6GAG9D;CACF;CAEA,IAAI;CACJ,IAAI;EACF,YAAA,GAAA,gCAAA,2BAAA,CAAsC,MAAM,IAAI,CAAC,CAAC;CACpD,SAAS,GAAG;EACV,OAAO,QAAQ,+BAA+B,UAAU,CAAC,GAAG;CAC9D;CAEA,MAAM,UAAU,MAAM,SAAS,OAAO,MAAM,MAAM,OAAO;CACzD,IAAI,QAAQ,IACV,OAAO;EACL,IAAI;EACJ,OAAO;GAAE,SAAS,MAAM;GAAI,MAAM,QAAQ;GAAM,OAAO,QAAQ;GAAO,QAAQ;EAAM;CACtF;CAEF,QAAQ,QAAQ,QAAhB;EACE,KAAK,4BACH,OAAO,QACL,4BAA4B,SAAS,sCAC/B,MAAM,KAAK,oDACnB;EACF,KAAK,qBACH,OAAO,QACL,IAAI,SAAS,+DACP,MAAM,KAAK,wCACnB;EACF,KAAK,4BAGH,OAAO,QACL,iCAAiC,SAAS,oBAAoB,MAAM,KAAK,qFAE3E;CACJ;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,mBAAmB,SAAyD;CAC1F,MAAM,QAAQ,QAAQ,QAAQ,GAAG;CACjC,IAAI,CAAC,QAAQ,WAAW,GAAG,KAAK,QAAQ,GACtC,MAAM,IAAI,MAAM,iDAAiD,QAAQ,EAAE;CAG7E,MAAM,OAAO,QAAQ,MAAM,QAAQ,CAAC;CACpC,MAAM,MAAM,KAAK,QAAQ,GAAG;CAC5B,IAAI,OAAO,KAAK,QAAQ,KAAK,SAAS,GACpC,MAAM,IAAI,MACR,IAAI,KAAK,+DAA+D,QAAQ,EAClF;CAGF,OAAO;EAAE,cAAc,KAAK,MAAM,GAAG,GAAG;EAAG,MAAM,KAAK,MAAM,MAAM,CAAC;CAAE;AACvE;AAEA,SAAS,UAAU,GAAoB;CACrC,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAClD"}