@milaboratories/pl-middle-layer 1.69.2 → 1.71.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 (46) hide show
  1. package/dist/index.cjs +0 -2
  2. package/dist/index.d.ts +1 -2
  3. package/dist/index.js +1 -2
  4. package/dist/middle_layer/build_stamp.cjs +1 -1
  5. package/dist/middle_layer/build_stamp.js +1 -1
  6. package/dist/middle_layer/middle_layer.cjs +14 -45
  7. package/dist/middle_layer/middle_layer.cjs.map +1 -1
  8. package/dist/middle_layer/middle_layer.d.ts +8 -15
  9. package/dist/middle_layer/middle_layer.d.ts.map +1 -1
  10. package/dist/middle_layer/middle_layer.js +14 -45
  11. package/dist/middle_layer/middle_layer.js.map +1 -1
  12. package/dist/middle_layer/template_list.cjs.map +1 -1
  13. package/dist/middle_layer/template_list.d.ts +1 -6
  14. package/dist/middle_layer/template_list.d.ts.map +1 -1
  15. package/dist/middle_layer/template_list.js.map +1 -1
  16. package/dist/model/index.cjs +0 -2
  17. package/dist/model/index.d.ts +1 -2
  18. package/dist/model/index.js +1 -2
  19. package/dist/model/template_export.cjs +5 -1
  20. package/dist/model/template_export.cjs.map +1 -1
  21. package/dist/model/template_export.d.ts +3 -0
  22. package/dist/model/template_export.d.ts.map +1 -1
  23. package/dist/model/template_export.js +5 -1
  24. package/dist/model/template_export.js.map +1 -1
  25. package/dist/model/template_serializer.cjs +2 -0
  26. package/dist/model/template_serializer.cjs.map +1 -1
  27. package/dist/model/template_serializer.d.ts.map +1 -1
  28. package/dist/model/template_serializer.js +2 -0
  29. package/dist/model/template_serializer.js.map +1 -1
  30. package/package.json +13 -13
  31. package/src/middle_layer/middle_layer.ts +21 -57
  32. package/src/middle_layer/template_list.ts +2 -5
  33. package/src/middle_layer/templates.test.ts +11 -20
  34. package/src/model/index.ts +0 -1
  35. package/src/model/template_export.test.ts +41 -5
  36. package/src/model/template_export.ts +11 -3
  37. package/src/model/template_serializer.test.ts +26 -3
  38. package/src/model/template_serializer.ts +2 -0
  39. package/dist/model/template_share.cjs +0 -42
  40. package/dist/model/template_share.cjs.map +0 -1
  41. package/dist/model/template_share.d.ts +0 -27
  42. package/dist/model/template_share.d.ts.map +0 -1
  43. package/dist/model/template_share.js +0 -42
  44. package/dist/model/template_share.js.map +0 -1
  45. package/src/model/template_share.test.ts +0 -78
  46. package/src/model/template_share.ts +0 -52
@@ -75,8 +75,6 @@ import {
75
75
  type ShareProjectsOptions,
76
76
  type ShareTemplateOptions,
77
77
  } from "../model/sharing_model";
78
- import type { TemplateShareProblem } from "../model/template_share";
79
- import { unshareableTemplateEntries } from "../model/template_share";
80
78
  import {
81
79
  buildShareEnvelope,
82
80
  buildTemplateShareEnvelope,
@@ -1002,9 +1000,10 @@ export class MiddleLayer {
1002
1000
  * The cost of that is the donor's receipt: nobody can write an acceptance onto a read-only
1003
1001
  * envelope, so a template share never reports who accepted it.
1004
1002
  *
1005
- * A template holding a block installed from a folder on this machine is refused rather than sent,
1006
- * with every offending entry named. {@link checkTemplateShareable} answers the same question
1007
- * without attempting the share, so a UI can state it on the template itself.
1003
+ * Nothing about the document is checked: a stored template is shareable by virtue of existing.
1004
+ * An entry the recipient cannot resolve a block installed from a folder on the sender's
1005
+ * machine, say is theirs to see when they preview or apply it, where every unresolvable entry
1006
+ * is named anyway.
1008
1007
  *
1009
1008
  * @param id template to share
1010
1009
  * @param options recipients XOR everyone, plus the title recipients see
@@ -1013,8 +1012,7 @@ export class MiddleLayer {
1013
1012
  id: TemplateId,
1014
1013
  options: ShareTemplateOptions,
1015
1014
  ): Promise<ShareTemplateOutcome> {
1016
- const loaded = await this.loadShareableTemplate(id);
1017
- if (!loaded.ok) return { ok: false, problems: loaded.problems };
1015
+ const template = await this.loadTemplateForShare(id);
1018
1016
 
1019
1017
  const everyone = "everyone" in options;
1020
1018
  const sender = this.currentUserLogin ?? "";
@@ -1026,7 +1024,7 @@ export class MiddleLayer {
1026
1024
  const { envelope, data } = buildTemplateShareEnvelope(
1027
1025
  tx,
1028
1026
  this.sharingOutboxResourceId,
1029
- loaded.template,
1027
+ template,
1030
1028
  { sender, title: options.title, expiresAt },
1031
1029
  );
1032
1030
  shareId = data.shareId;
@@ -1037,29 +1035,16 @@ export class MiddleLayer {
1037
1035
  });
1038
1036
 
1039
1037
  await this.sharingOutboxTree.refreshState();
1040
- return { ok: true, shareId: shareId! };
1038
+ return { shareId: shareId! };
1041
1039
  }
1042
1040
 
1043
- /**
1044
- * Every entry of a stored template that stands in the way of sharing it, empty for a template
1045
- * that can be shared. Reads the template and nothing else, so a UI can state the refusal on the
1046
- * template itself instead of only when the user tries to share it.
1047
- */
1048
- public async checkTemplateShareable(id: TemplateId): Promise<readonly TemplateShareProblem[]> {
1049
- const stored = await this.getTemplateData(id);
1050
- return unshareableTemplateEntries(stored.document);
1051
- }
1052
-
1053
- /** The document and the label of a template that may be shared, or every entry that stops it.
1054
- * The label is what the recipient's own list will show, so it travels with the document. */
1055
- private async loadShareableTemplate(
1041
+ /** The document and the label of a template about to be shared. The label is what the
1042
+ * recipient's own list will show, so it travels with the document. */
1043
+ private async loadTemplateForShare(
1056
1044
  id: TemplateId,
1057
- ): Promise<
1058
- | { ok: true; template: { document: ProjectTemplateV1; label: string } }
1059
- | { ok: false; problems: readonly TemplateShareProblem[] }
1060
- > {
1045
+ ): Promise<{ document: ProjectTemplateV1; label: string }> {
1061
1046
  const rid = await this.resolveTemplateId(id);
1062
- const template = await this.pl.withReadTx("MLReadTemplateForShare", async (tx) => {
1047
+ return await this.pl.withReadTx("MLReadTemplateForShare", async (tx) => {
1063
1048
  const rd = await tx.getResourceData(rid, false);
1064
1049
  if (rd.data === undefined) throw new Error(`Template ${id} carries no document.`);
1065
1050
  return {
@@ -1067,10 +1052,6 @@ export class MiddleLayer {
1067
1052
  label: await tx.getKValueJson<string>(rid, TemplateLabelKey),
1068
1053
  };
1069
1054
  });
1070
-
1071
- const problems = unshareableTemplateEntries(template.document);
1072
- if (problems.length > 0) return { ok: false, problems };
1073
- return { ok: true, template };
1074
1055
  }
1075
1056
 
1076
1057
  /**
@@ -1092,8 +1073,7 @@ export class MiddleLayer {
1092
1073
  * `opts.templateId` is required for, and only used by, a share that carries a template: a stored
1093
1074
  * template is immutable, so an improved one is a different template and the share cannot re-read
1094
1075
  * the one it started from — the caller names the new target. Every other option means the same
1095
- * thing for both kinds of share. Sharing the named template must be permitted (see
1096
- * {@link checkTemplateShareable}) or this throws.
1076
+ * thing for both kinds of share.
1097
1077
  */
1098
1078
  public async changeShare(
1099
1079
  shareId: ShareId,
@@ -1105,14 +1085,9 @@ export class MiddleLayer {
1105
1085
  templateId?: TemplateId;
1106
1086
  } = {},
1107
1087
  ): Promise<void> {
1108
- // Read outside the write tx: it is two round-trips of its own, and the refusal it can produce
1109
- // must be raised before anything is torn down.
1088
+ // Read outside the write tx: it is two round-trips of its own.
1110
1089
  const target =
1111
- opts.templateId === undefined ? undefined : await this.loadShareableTemplate(opts.templateId);
1112
- if (target !== undefined && !target.ok)
1113
- throw new Error(
1114
- `changeShare: template ${opts.templateId} cannot be shared: ${describeShareProblems(target.problems)}`,
1115
- );
1090
+ opts.templateId === undefined ? undefined : await this.loadTemplateForShare(opts.templateId);
1116
1091
 
1117
1092
  await this.pl.withWriteTx("MLChangeShare", async (tx) => {
1118
1093
  const old = await this.resolveOutboxEnvelope(tx, shareId);
@@ -1137,17 +1112,12 @@ export class MiddleLayer {
1137
1112
 
1138
1113
  // Same shareId, same outbox field name — detach the old field before rebuilding, or they collide.
1139
1114
  tx.removeField(field(this.sharingOutboxResourceId, old.fieldName));
1140
- const { envelope } = buildTemplateShareEnvelope(
1141
- tx,
1142
- this.sharingOutboxResourceId,
1143
- target.template,
1144
- {
1145
- sender: self,
1146
- title: opts.title === undefined ? old.data.title : opts.title.trim(),
1147
- expiresAt: everyone ? null : Date.now() + this.env.ops.envelopeTtlMs,
1148
- shareId, // SAME shareId — the essence of change
1149
- },
1150
- );
1115
+ const { envelope } = buildTemplateShareEnvelope(tx, this.sharingOutboxResourceId, target, {
1116
+ sender: self,
1117
+ title: opts.title === undefined ? old.data.title : opts.title.trim(),
1118
+ expiresAt: everyone ? null : Date.now() + this.env.ops.envelopeTtlMs,
1119
+ shareId, // SAME shareId — the essence of change
1120
+ });
1151
1121
 
1152
1122
  // Nothing to transfer: a read-only grant cannot write an acceptance, so a template share
1153
1123
  // never accumulated one.
@@ -1828,9 +1798,3 @@ export class MiddleLayer {
1828
1798
  //
1829
1799
  // Internals
1830
1800
  //
1831
-
1832
- /** Refusal reasons as one line, each naming the entry it belongs to, so a throw that escapes to a
1833
- * log still says which block stands in the way. */
1834
- function describeShareProblems(problems: readonly TemplateShareProblem[]): string {
1835
- return problems.map((p) => `${p.entryId}: ${p.error}`).join("; ");
1836
- }
@@ -20,7 +20,6 @@ import type { MiddleLayerEnvironment } from "./middle_layer";
20
20
  import { notEmpty } from "@milaboratories/ts-helpers";
21
21
  import type { Branded, ProjectTemplateV1 } from "@milaboratories/pl-model-common";
22
22
  import type { TemplateExportProblem } from "../model/template_export";
23
- import type { TemplateShareProblem } from "../model/template_share";
24
23
  import type { ShareId } from "../model/sharing_model";
25
24
  import type { AppliedEntry, TemplateApplyProblem } from "../model/template_apply";
26
25
  import type { ProjectId } from "../model/project_model";
@@ -80,10 +79,8 @@ export type SaveProjectAsTemplateOutcome =
80
79
  | { readonly ok: true; readonly templateId: TemplateId }
81
80
  | { readonly ok: false; readonly problems: readonly TemplateExportProblem[] };
82
81
 
83
- /** What sharing a stored template yields: the share's logical id, or every entry in the way. */
84
- export type ShareTemplateOutcome =
85
- | { readonly ok: true; readonly shareId: ShareId }
86
- | { readonly ok: false; readonly problems: readonly TemplateShareProblem[] };
82
+ /** What sharing a stored template yields: the share's logical id. */
83
+ export type ShareTemplateOutcome = { readonly shareId: ShareId };
87
84
 
88
85
  /**
89
86
  * What applying a stored template yields.
@@ -91,26 +91,19 @@ test("an entry nothing can resolve creates no project, and every entry is report
91
91
  });
92
92
  });
93
93
 
94
- test("a template holding a block from a folder on this machine is refused rather than shared", async () => {
94
+ test("a template holding a block from a folder on this machine is shared like any other", async () => {
95
95
  await withMl(async (ml) => {
96
- const stored = await storeTemplate(ml, "Built here", {
97
- schemaVersion: 1,
98
- document: documentOf(entry("a"), entry("b", LOCAL_FOLDER)),
99
- });
96
+ const document = documentOf(entry("a"), entry("b", LOCAL_FOLDER));
97
+ const stored = await storeTemplate(ml, "Built here", { schemaVersion: 1, document });
100
98
 
101
- // Asked of a template that is merely being displayed, so the refusal can be stated on the
102
- // template itself instead of only once the user has tried to send it.
103
- expect((await ml.checkTemplateShareable(stored.id)).map((p) => p.entryId)).toStrictEqual(["b"]);
104
-
105
- const outcome = await ml.shareTemplate(stored.id, {
106
- recipients: ["colleague"],
107
- title: "Built here",
108
- });
99
+ // A stored template is shareable by existing. Whether the recipient can resolve every entry
100
+ // is their question, answered where they preview or apply it not a gate on sending.
101
+ // With everyone, like the other share tests here: a named recipient must already exist on
102
+ // the backend, and the test server has no second user.
103
+ const shared = await ml.shareTemplate(stored.id, { everyone: true, title: "Built here" });
109
104
 
110
- if (outcome.ok) throw new Error("a template with a file: entry must not be shareable");
111
- expect(outcome.problems.map((p) => p.entryId)).toStrictEqual(["b"]);
112
- // Refused before anything was written: no envelope, so nothing to revoke.
113
- expect((await ml.outgoingShares.getValue()) ?? []).toStrictEqual([]);
105
+ const outgoing = await ml.outgoingShares.awaitStableValue();
106
+ expect((outgoing ?? []).map((s) => s.shareId)).toStrictEqual([shared.shareId]);
114
107
  });
115
108
  });
116
109
 
@@ -142,7 +135,6 @@ test("an accepted template share lands on the acceptor's shelf and builds nothin
142
135
  });
143
136
 
144
137
  const shared = await ml.shareTemplate(stored.id, { everyone: true, title: "A pipeline" });
145
- if (!shared.ok) throw new Error(`share refused: ${JSON.stringify(shared.problems)}`);
146
138
 
147
139
  const outcome = await ml.acceptShare([shared.shareId]);
148
140
 
@@ -175,7 +167,6 @@ test("a changed template share keeps its id, and whoever already responded is no
175
167
  });
176
168
 
177
169
  const shared = await ml.shareTemplate(first.id, { everyone: true, title: "First" });
178
- if (!shared.ok) throw new Error(`share refused: ${JSON.stringify(shared.problems)}`);
179
170
 
180
171
  // Someone responds to the share, which is what the replace below must not undo.
181
172
  const accept = await ml.acceptShare([shared.shareId]);
@@ -186,7 +177,7 @@ test("a changed template share keeps its id, and whoever already responded is no
186
177
  await ml.changeShare(shared.shareId, { templateId: second.id, title: "Second" });
187
178
 
188
179
  const outgoing = (await ml.outgoingShares.getValue()) ?? [];
189
- expect(outgoing.map((s) => s.shareId)).toStrictEqual([shared.shareId]);
180
+ expect((outgoing ?? []).map((s) => s.shareId)).toStrictEqual([shared.shareId]);
190
181
  expect(outgoing[0]).toMatchObject({
191
182
  payloadKind: "template",
192
183
  title: "Second",
@@ -48,4 +48,3 @@ export type { TemplateExportProblem } from "./template_export";
48
48
 
49
49
  // The template share path. Whether a template may be shared at all is a question a UI asks about
50
50
  // a template it is merely displaying, so the check and its problem type are public.
51
- export { unshareableTemplateEntries, type TemplateShareProblem } from "./template_share";
@@ -24,6 +24,35 @@ function providerFrom(params: Record<string, TemplateParamsResult>) {
24
24
 
25
25
  const ok = (value: unknown): TemplateParamsResult => ({ value });
26
26
 
27
+ describe("labels", () => {
28
+ test("an entry and a problem both carry the block's label, not its id", () => {
29
+ // `simpleStructure` labels every block after its id, which cannot tell a label read from
30
+ // the structure apart from an id copied into the field. Distinct labels here can.
31
+ const structure: ProjectStructure = {
32
+ groups: [
33
+ {
34
+ id: "g1",
35
+ label: "G1",
36
+ blocks: [
37
+ { id: "b1", label: "MiXCR Clonotyping", renderingMode: "Heavy" },
38
+ { id: "b2", label: "Clonotype Browser", renderingMode: "Heavy" },
39
+ ],
40
+ },
41
+ ],
42
+ };
43
+ const walk = walkProjectForTemplateExport(structure, providerFrom({ b1: ok({}) }));
44
+
45
+ expect(walk.entries).toEqual([{ blockId: "b1", blockLabel: "MiXCR Clonotyping", params: {} }]);
46
+ expect(walk.problems).toEqual([
47
+ {
48
+ blockId: "b2",
49
+ blockLabel: "Clonotype Browser",
50
+ error: "Block state is unavailable, so its template params could not be derived",
51
+ },
52
+ ]);
53
+ });
54
+ });
55
+
27
56
  describe("order", () => {
28
57
  test("entries come out in structure order — no sort, none needed", () => {
29
58
  // The structure IS the topological order: a block can only legally reference
@@ -101,7 +130,7 @@ describe("collecting each block's descriptor output", () => {
101
130
  providerFrom({ mixcr: ok(params) }),
102
131
  );
103
132
 
104
- expect(walk.entries).toEqual([{ blockId: "mixcr", params }]);
133
+ expect(walk.entries).toEqual([{ blockId: "mixcr", blockLabel: "mixcr", params }]);
105
134
  });
106
135
 
107
136
  test("a block with nothing to project yields empty params", () => {
@@ -112,7 +141,9 @@ describe("collecting each block's descriptor output", () => {
112
141
  providerFrom({ "pool-explorer": ok({}) }),
113
142
  );
114
143
 
115
- expect(walk.entries).toEqual([{ blockId: "pool-explorer", params: {} }]);
144
+ expect(walk.entries).toEqual([
145
+ { blockId: "pool-explorer", blockLabel: "pool-explorer", params: {} },
146
+ ]);
116
147
  expect(walk.problems).toEqual([]);
117
148
  });
118
149
  });
@@ -145,7 +176,7 @@ describe("what the walk does with params", () => {
145
176
  providerFrom({ block1: ok({}) }),
146
177
  );
147
178
 
148
- expect(walk.entries).toEqual([{ blockId: "block1", params: {} }]);
179
+ expect(walk.entries).toEqual([{ blockId: "block1", blockLabel: "block1", params: {} }]);
149
180
  });
150
181
 
151
182
  test("a wrapper's contents are never inspected, whatever they are", () => {
@@ -210,7 +241,7 @@ describe("problems", () => {
210
241
  // Every offending block is reported at once rather than aborting on the
211
242
  // first, so the user fixes them in one pass.
212
243
  expect(walk.problems).toEqual([
213
- { blockId: "b", error: "templateParams() threw: not exportable yet" },
244
+ { blockId: "b", blockLabel: "b", error: "templateParams() threw: not exportable yet" },
214
245
  ]);
215
246
  expect(walk.entries.map((e) => e.blockId)).toEqual(["a", "c"]);
216
247
  });
@@ -228,6 +259,7 @@ describe("problems", () => {
228
259
  expect(walk.problems).toEqual([
229
260
  {
230
261
  blockId: "ghost",
262
+ blockLabel: "ghost",
231
263
  error: "Block state is unavailable, so its template params could not be derived",
232
264
  },
233
265
  ]);
@@ -249,7 +281,11 @@ describe("problems", () => {
249
281
 
250
282
  expect(walk.entries).toEqual([]);
251
283
  expect(walk.problems).toEqual([
252
- { blockId: "odd", error: `templateParams() must return an object, got ${expected}` },
284
+ {
285
+ blockId: "odd",
286
+ blockLabel: "odd",
287
+ error: `templateParams() must return an object, got ${expected}`,
288
+ },
253
289
  ]);
254
290
  });
255
291
  });
@@ -20,6 +20,9 @@ export type TemplateExportEntry = {
20
20
  * already stored in params need no translation.
21
21
  */
22
22
  readonly blockId: string;
23
+ /** The block's label from the project structure, carried so a problem found downstream can
24
+ * name the block to a person. Not written to the template. */
25
+ readonly blockLabel: string;
23
26
  /**
24
27
  * The block's params exactly as it projected them.
25
28
  *
@@ -33,6 +36,9 @@ export type TemplateExportEntry = {
33
36
  /** Why one block could not be exported. */
34
37
  export type TemplateExportProblem = {
35
38
  readonly blockId: string;
39
+ /** The block's label as the project structure holds it. An id means nothing to the person who
40
+ * pressed Export; this is what a UI shows. */
41
+ readonly blockLabel: string;
36
42
  readonly error: string;
37
43
  };
38
44
 
@@ -91,19 +97,20 @@ export function walkProjectForTemplateExport(
91
97
  const entries: TemplateExportEntry[] = [];
92
98
  const problems: TemplateExportProblem[] = [];
93
99
 
94
- for (const { id } of allBlocks(structure)) {
100
+ for (const { id, label } of allBlocks(structure)) {
95
101
  const derived = paramsProvider(id);
96
102
 
97
103
  if (derived === undefined) {
98
104
  problems.push({
99
105
  blockId: id,
106
+ blockLabel: label,
100
107
  error: "Block state is unavailable, so its template params could not be derived",
101
108
  });
102
109
  continue;
103
110
  }
104
111
 
105
112
  if (derived.error !== undefined) {
106
- problems.push({ blockId: id, error: derived.error });
113
+ problems.push({ blockId: id, blockLabel: label, error: derived.error });
107
114
  continue;
108
115
  }
109
116
 
@@ -117,12 +124,13 @@ export function walkProjectForTemplateExport(
117
124
  if (typeof params !== "object" || params === null || Array.isArray(params)) {
118
125
  problems.push({
119
126
  blockId: id,
127
+ blockLabel: label,
120
128
  error: `templateParams() must return an object, got ${typeName(params)}`,
121
129
  });
122
130
  continue;
123
131
  }
124
132
 
125
- entries.push({ blockId: id, params: params as Record<string, unknown> });
133
+ entries.push({ blockId: id, blockLabel: label, params: params as Record<string, unknown> });
126
134
  }
127
135
 
128
136
  return { entries, problems };
@@ -198,6 +198,27 @@ describe("problems", () => {
198
198
  expect(result.problems[0].error).toContain("declares no kind");
199
199
  });
200
200
 
201
+ test("a problem raised here carries the block's label from the walk", () => {
202
+ // Labels distinct from ids, or the assertion could not tell a label carried through from
203
+ // an id copied into the field.
204
+ const structure: ProjectStructure = {
205
+ groups: [
206
+ {
207
+ id: "g1",
208
+ label: "G1",
209
+ blocks: [{ id: "legacy", label: "Old Aligner", renderingMode: "Heavy" }],
210
+ },
211
+ ],
212
+ };
213
+ const result = exportOf(structure, { legacy: ok({}) }, () => undefined);
214
+
215
+ expect(result.ok).toBe(false);
216
+ if (result.ok) return;
217
+ expect(result.problems).toEqual([
218
+ { blockId: "legacy", blockLabel: "Old Aligner", error: expect.stringContaining("no kind") },
219
+ ]);
220
+ });
221
+
201
222
  test("a malformed stored kind reference is a problem, not a throw", () => {
202
223
  const result = exportOf(
203
224
  simpleStructure("a"),
@@ -385,14 +406,16 @@ describe("assembleProjectTemplateV1", () => {
385
406
  test("carries the walk's problems through unchanged", () => {
386
407
  const { document, problems } = assembleProjectTemplateV1(
387
408
  {
388
- entries: [{ blockId: "a", params: {} }],
389
- problems: [{ blockId: "ghost", error: "state unavailable" }],
409
+ entries: [{ blockId: "a", blockLabel: "a", params: {} }],
410
+ problems: [{ blockId: "ghost", blockLabel: "ghost", error: "state unavailable" }],
390
411
  },
391
412
  kindPerBlock,
392
413
  () => registrySpec,
393
414
  );
394
415
 
395
- expect(problems).toEqual([{ blockId: "ghost", error: "state unavailable" }]);
416
+ expect(problems).toEqual([
417
+ { blockId: "ghost", blockLabel: "ghost", error: "state unavailable" },
418
+ ]);
396
419
  expect(document.blocks.map((b) => b.id)).toEqual(["a"]);
397
420
  });
398
421
  });
@@ -126,6 +126,7 @@ export function assembleProjectTemplateV1(
126
126
  if (kind === undefined) {
127
127
  problems.push({
128
128
  blockId: entry.blockId,
129
+ blockLabel: entry.blockLabel,
129
130
  error:
130
131
  "Block declares no kind, so it cannot be written to a template: an entry's kind " +
131
132
  "carries the params contract the entry is typed against",
@@ -143,6 +144,7 @@ export function assembleProjectTemplateV1(
143
144
  } catch (e) {
144
145
  problems.push({
145
146
  blockId: entry.blockId,
147
+ blockLabel: entry.blockLabel,
146
148
  error: `Block's stored kind reference is malformed: ${e instanceof Error ? e.message : String(e)}`,
147
149
  });
148
150
  continue;
@@ -1,42 +0,0 @@
1
- let _milaboratories_pl_model_common = require("@milaboratories/pl-model-common");
2
- //#region src/model/template_share.ts
3
- /**
4
- * Every entry of a template that cannot travel to another machine, or an empty list for a
5
- * template that can be shared.
6
- *
7
- * An entry's `location` names a place rather than a name, and a `file:` place is a folder on
8
- * the author's own disk: a recipient resolving it finds nothing, or worse finds something
9
- * else. Such a template stays perfectly usable where it was made, so it is stored and applied
10
- * as normal — only sharing it is refused.
11
- *
12
- * A location whose scheme cannot be read is refused for the same reason: nothing can resolve
13
- * it anywhere, here included.
14
- *
15
- * Every offending entry is reported, not only the first, so a UI can name each block instead
16
- * of sending its user round the loop once per entry.
17
- */
18
- function unshareableTemplateEntries(document) {
19
- const problems = [];
20
- for (const entry of document.blocks) {
21
- if (entry.location === void 0) continue;
22
- let scheme;
23
- try {
24
- scheme = (0, _milaboratories_pl_model_common.parseBlockPackLocation)(entry.location).scheme;
25
- } catch (e) {
26
- problems.push({
27
- entryId: entry.id,
28
- error: `Block is installed from a location nothing can resolve: ${e instanceof Error ? e.message : String(e)}`
29
- });
30
- continue;
31
- }
32
- if (scheme === "file") problems.push({
33
- entryId: entry.id,
34
- error: `Block is installed from ${entry.location}, a folder on this machine — it resolves to nothing on the recipient's, so this template cannot be shared`
35
- });
36
- }
37
- return problems;
38
- }
39
- //#endregion
40
- exports.unshareableTemplateEntries = unshareableTemplateEntries;
41
-
42
- //# sourceMappingURL=template_share.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"template_share.cjs","names":[],"sources":["../../src/model/template_share.ts"],"sourcesContent":["import type { ProjectTemplateV1 } from \"@milaboratories/pl-model-common\";\nimport { parseBlockPackLocation } from \"@milaboratories/pl-model-common\";\n\n/** One template entry standing in the way of sharing the template, and why. */\nexport type TemplateShareProblem = {\n /** The template-local id of the entry the problem belongs to; on an exported template it is\n * the block's project-local uuid. */\n readonly entryId: string;\n readonly error: string;\n};\n\n/**\n * Every entry of a template that cannot travel to another machine, or an empty list for a\n * template that can be shared.\n *\n * An entry's `location` names a place rather than a name, and a `file:` place is a folder on\n * the author's own disk: a recipient resolving it finds nothing, or worse finds something\n * else. Such a template stays perfectly usable where it was made, so it is stored and applied\n * as normal — only sharing it is refused.\n *\n * A location whose scheme cannot be read is refused for the same reason: nothing can resolve\n * it anywhere, here included.\n *\n * Every offending entry is reported, not only the first, so a UI can name each block instead\n * of sending its user round the loop once per entry.\n */\nexport function unshareableTemplateEntries(\n document: ProjectTemplateV1,\n): readonly TemplateShareProblem[] {\n const problems: TemplateShareProblem[] = [];\n for (const entry of document.blocks) {\n if (entry.location === undefined) continue;\n let scheme: string;\n try {\n scheme = parseBlockPackLocation(entry.location).scheme;\n } catch (e) {\n problems.push({\n entryId: entry.id,\n error: `Block is installed from a location nothing can resolve: ${e instanceof Error ? e.message : String(e)}`,\n });\n continue;\n }\n if (scheme === \"file\")\n problems.push({\n entryId: entry.id,\n error:\n `Block is installed from ${entry.location}, a folder on this machine — it resolves ` +\n \"to nothing on the recipient's, so this template cannot be shared\",\n });\n }\n return problems;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA0BA,SAAgB,2BACd,UACiC;CACjC,MAAM,WAAmC,CAAC;CAC1C,KAAK,MAAM,SAAS,SAAS,QAAQ;EACnC,IAAI,MAAM,aAAa,KAAA,GAAW;EAClC,IAAI;EACJ,IAAI;GACF,UAAA,GAAA,gCAAA,uBAAA,CAAgC,MAAM,QAAQ,CAAC,CAAC;EAClD,SAAS,GAAG;GACV,SAAS,KAAK;IACZ,SAAS,MAAM;IACf,OAAO,2DAA2D,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;GAC7G,CAAC;GACD;EACF;EACA,IAAI,WAAW,QACb,SAAS,KAAK;GACZ,SAAS,MAAM;GACf,OACE,2BAA2B,MAAM,SAAS;EAE9C,CAAC;CACL;CACA,OAAO;AACT"}
@@ -1,27 +0,0 @@
1
- import { ProjectTemplateV1 } from "@milaboratories/pl-model-common";
2
- //#region src/model/template_share.d.ts
3
- /** One template entry standing in the way of sharing the template, and why. */
4
- export type TemplateShareProblem = {
5
- /** The template-local id of the entry the problem belongs to; on an exported template it is
6
- * the block's project-local uuid. */
7
- readonly entryId: string;
8
- readonly error: string;
9
- };
10
- /**
11
- * Every entry of a template that cannot travel to another machine, or an empty list for a
12
- * template that can be shared.
13
- *
14
- * An entry's `location` names a place rather than a name, and a `file:` place is a folder on
15
- * the author's own disk: a recipient resolving it finds nothing, or worse finds something
16
- * else. Such a template stays perfectly usable where it was made, so it is stored and applied
17
- * as normal — only sharing it is refused.
18
- *
19
- * A location whose scheme cannot be read is refused for the same reason: nothing can resolve
20
- * it anywhere, here included.
21
- *
22
- * Every offending entry is reported, not only the first, so a UI can name each block instead
23
- * of sending its user round the loop once per entry.
24
- */
25
- export declare function unshareableTemplateEntries(document: ProjectTemplateV1): readonly TemplateShareProblem[];
26
- //#endregion
27
- //# sourceMappingURL=template_share.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"template_share.d.ts","names":[],"sources":["../../src/model/template_share.ts"],"mappings":";;;YAIY;;;WAGD;WACA;;;;;;;;;;;;;;;;;wBAkBK,2BACd,UAAU,6BACA"}
@@ -1,42 +0,0 @@
1
- import { parseBlockPackLocation } from "@milaboratories/pl-model-common";
2
- //#region src/model/template_share.ts
3
- /**
4
- * Every entry of a template that cannot travel to another machine, or an empty list for a
5
- * template that can be shared.
6
- *
7
- * An entry's `location` names a place rather than a name, and a `file:` place is a folder on
8
- * the author's own disk: a recipient resolving it finds nothing, or worse finds something
9
- * else. Such a template stays perfectly usable where it was made, so it is stored and applied
10
- * as normal — only sharing it is refused.
11
- *
12
- * A location whose scheme cannot be read is refused for the same reason: nothing can resolve
13
- * it anywhere, here included.
14
- *
15
- * Every offending entry is reported, not only the first, so a UI can name each block instead
16
- * of sending its user round the loop once per entry.
17
- */
18
- function unshareableTemplateEntries(document) {
19
- const problems = [];
20
- for (const entry of document.blocks) {
21
- if (entry.location === void 0) continue;
22
- let scheme;
23
- try {
24
- scheme = parseBlockPackLocation(entry.location).scheme;
25
- } catch (e) {
26
- problems.push({
27
- entryId: entry.id,
28
- error: `Block is installed from a location nothing can resolve: ${e instanceof Error ? e.message : String(e)}`
29
- });
30
- continue;
31
- }
32
- if (scheme === "file") problems.push({
33
- entryId: entry.id,
34
- error: `Block is installed from ${entry.location}, a folder on this machine — it resolves to nothing on the recipient's, so this template cannot be shared`
35
- });
36
- }
37
- return problems;
38
- }
39
- //#endregion
40
- export { unshareableTemplateEntries };
41
-
42
- //# sourceMappingURL=template_share.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"template_share.js","names":[],"sources":["../../src/model/template_share.ts"],"sourcesContent":["import type { ProjectTemplateV1 } from \"@milaboratories/pl-model-common\";\nimport { parseBlockPackLocation } from \"@milaboratories/pl-model-common\";\n\n/** One template entry standing in the way of sharing the template, and why. */\nexport type TemplateShareProblem = {\n /** The template-local id of the entry the problem belongs to; on an exported template it is\n * the block's project-local uuid. */\n readonly entryId: string;\n readonly error: string;\n};\n\n/**\n * Every entry of a template that cannot travel to another machine, or an empty list for a\n * template that can be shared.\n *\n * An entry's `location` names a place rather than a name, and a `file:` place is a folder on\n * the author's own disk: a recipient resolving it finds nothing, or worse finds something\n * else. Such a template stays perfectly usable where it was made, so it is stored and applied\n * as normal — only sharing it is refused.\n *\n * A location whose scheme cannot be read is refused for the same reason: nothing can resolve\n * it anywhere, here included.\n *\n * Every offending entry is reported, not only the first, so a UI can name each block instead\n * of sending its user round the loop once per entry.\n */\nexport function unshareableTemplateEntries(\n document: ProjectTemplateV1,\n): readonly TemplateShareProblem[] {\n const problems: TemplateShareProblem[] = [];\n for (const entry of document.blocks) {\n if (entry.location === undefined) continue;\n let scheme: string;\n try {\n scheme = parseBlockPackLocation(entry.location).scheme;\n } catch (e) {\n problems.push({\n entryId: entry.id,\n error: `Block is installed from a location nothing can resolve: ${e instanceof Error ? e.message : String(e)}`,\n });\n continue;\n }\n if (scheme === \"file\")\n problems.push({\n entryId: entry.id,\n error:\n `Block is installed from ${entry.location}, a folder on this machine — it resolves ` +\n \"to nothing on the recipient's, so this template cannot be shared\",\n });\n }\n return problems;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA0BA,SAAgB,2BACd,UACiC;CACjC,MAAM,WAAmC,CAAC;CAC1C,KAAK,MAAM,SAAS,SAAS,QAAQ;EACnC,IAAI,MAAM,aAAa,KAAA,GAAW;EAClC,IAAI;EACJ,IAAI;GACF,SAAS,uBAAuB,MAAM,QAAQ,CAAC,CAAC;EAClD,SAAS,GAAG;GACV,SAAS,KAAK;IACZ,SAAS,MAAM;IACf,OAAO,2DAA2D,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;GAC7G,CAAC;GACD;EACF;EACA,IAAI,WAAW,QACb,SAAS,KAAK;GACZ,SAAS,MAAM;GACf,OACE,2BAA2B,MAAM,SAAS;EAE9C,CAAC;CACL;CACA,OAAO;AACT"}