@milaboratories/pl-model-common 1.47.3 → 1.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bmodel/block_kind_ref.cjs +45 -0
- package/dist/bmodel/block_kind_ref.cjs.map +1 -0
- package/dist/bmodel/block_kind_ref.d.ts +57 -0
- package/dist/bmodel/block_kind_ref.d.ts.map +1 -0
- package/dist/bmodel/block_kind_ref.js +43 -0
- package/dist/bmodel/block_kind_ref.js.map +1 -0
- package/dist/bmodel/container.d.ts +8 -0
- package/dist/bmodel/container.d.ts.map +1 -1
- package/dist/bmodel/index.cjs +4 -0
- package/dist/bmodel/index.d.ts +2 -1
- package/dist/bmodel/index.js +2 -1
- package/dist/columns/dedup.cjs +1 -1
- package/dist/columns/dedup.cjs.map +1 -1
- package/dist/columns/dedup.d.ts +1 -1
- package/dist/columns/dedup.js +1 -1
- package/dist/columns/dedup.js.map +1 -1
- package/dist/columns/providers.cjs +1 -1
- package/dist/columns/providers.cjs.map +1 -1
- package/dist/columns/providers.d.ts +1 -1
- package/dist/columns/providers.js +1 -1
- package/dist/columns/providers.js.map +1 -1
- package/dist/drivers/index.cjs +4 -0
- package/dist/drivers/index.d.ts +2 -2
- package/dist/drivers/index.js +2 -2
- package/dist/drivers/pframe/index.cjs +4 -0
- package/dist/drivers/pframe/index.d.ts +2 -2
- package/dist/drivers/pframe/index.js +2 -2
- package/dist/drivers/pframe/spec/ids.cjs +151 -0
- package/dist/drivers/pframe/spec/ids.cjs.map +1 -1
- package/dist/drivers/pframe/spec/ids.d.ts +53 -1
- package/dist/drivers/pframe/spec/ids.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/ids.js +150 -3
- package/dist/drivers/pframe/spec/ids.js.map +1 -1
- package/dist/drivers/pframe/spec/index.cjs +4 -0
- package/dist/drivers/pframe/spec/index.d.ts +2 -2
- package/dist/drivers/pframe/spec/index.js +2 -2
- package/dist/index.cjs +29 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +8 -2
- package/dist/plid.cjs +1 -1
- package/dist/plid.cjs.map +1 -1
- package/dist/plid.d.ts +3 -2
- package/dist/plid.d.ts.map +1 -1
- package/dist/plid.js +1 -1
- package/dist/plid.js.map +1 -1
- package/dist/template/index.cjs +20 -0
- package/dist/template/index.d.ts +5 -0
- package/dist/template/index.js +5 -0
- package/dist/template/kind_selector.cjs +92 -0
- package/dist/template/kind_selector.cjs.map +1 -0
- package/dist/template/kind_selector.d.ts +80 -0
- package/dist/template/kind_selector.d.ts.map +1 -0
- package/dist/template/kind_selector.js +87 -0
- package/dist/template/kind_selector.js.map +1 -0
- package/dist/template/project_template_v1.cjs +231 -0
- package/dist/template/project_template_v1.cjs.map +1 -0
- package/dist/template/project_template_v1.d.ts +215 -0
- package/dist/template/project_template_v1.d.ts.map +1 -0
- package/dist/template/project_template_v1.js +225 -0
- package/dist/template/project_template_v1.js.map +1 -0
- package/dist/template/template_ref_form.cjs +73 -0
- package/dist/template/template_ref_form.cjs.map +1 -0
- package/dist/template/template_ref_form.d.ts +74 -0
- package/dist/template/template_ref_form.d.ts.map +1 -0
- package/dist/template/template_ref_form.js +72 -0
- package/dist/template/template_ref_form.js.map +1 -0
- package/dist/template/template_relocate.cjs +46 -0
- package/dist/template/template_relocate.cjs.map +1 -0
- package/dist/template/template_relocate.d.ts +32 -0
- package/dist/template/template_relocate.d.ts.map +1 -0
- package/dist/template/template_relocate.js +46 -0
- package/dist/template/template_relocate.js.map +1 -0
- package/package.json +5 -5
- package/src/bmodel/block_kind_ref.ts +59 -0
- package/src/bmodel/container.ts +9 -0
- package/src/bmodel/index.ts +1 -0
- package/src/columns/dedup.ts +1 -1
- package/src/columns/providers.ts +1 -1
- package/src/drivers/pframe/spec/ids.test.ts +90 -0
- package/src/drivers/pframe/spec/ids.ts +191 -1
- package/src/index.ts +1 -0
- package/src/plid.ts +5 -5
- package/src/template/index.ts +4 -0
- package/src/template/kind_selector.ts +126 -0
- package/src/template/project_template_v1.test.ts +315 -0
- package/src/template/project_template_v1.ts +444 -0
- package/src/template/template_ref_form.test.ts +86 -0
- package/src/template/template_ref_form.ts +108 -0
- package/src/template/template_relocate.test.ts +182 -0
- package/src/template/template_relocate.ts +61 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { describe, expect, expectTypeOf, test } from "vitest";
|
|
2
|
+
import type { BlockKindReference } from "../bmodel/block_kind_ref";
|
|
3
|
+
import { formatKindRef } from "../bmodel/block_kind_ref";
|
|
4
|
+
import {
|
|
5
|
+
formatKindSelector,
|
|
6
|
+
formatKindSelectorReference,
|
|
7
|
+
isBlockKindSelectorReference,
|
|
8
|
+
kindReferenceToSelectorReference,
|
|
9
|
+
parseKindSelector,
|
|
10
|
+
parseKindSelectorReference,
|
|
11
|
+
type BlockKindSelectorReference,
|
|
12
|
+
} from "./kind_selector";
|
|
13
|
+
import {
|
|
14
|
+
parseBlockPackLocation,
|
|
15
|
+
parseBlockPackReference,
|
|
16
|
+
parseProjectTemplateV1,
|
|
17
|
+
readProjectTemplateV1,
|
|
18
|
+
type BlockPackLocationReference,
|
|
19
|
+
type BlockPackReference,
|
|
20
|
+
type ProjectTemplateV1,
|
|
21
|
+
} from "./project_template_v1";
|
|
22
|
+
|
|
23
|
+
//
|
|
24
|
+
// The reference example for the format, as the value a YAML reader hands back:
|
|
25
|
+
//
|
|
26
|
+
// schema: template-v1
|
|
27
|
+
// blocks:
|
|
28
|
+
// - id: samples
|
|
29
|
+
// kind: "@platforma-open/milaboratories.samples-and-data.kind@^1.0.0"
|
|
30
|
+
// params:
|
|
31
|
+
// dataset: bulk-rna
|
|
32
|
+
// - id: mixcr
|
|
33
|
+
// kind: "@platforma-open/milaboratories.mixcr-clonotyping.kind@~1.2.0"
|
|
34
|
+
// params:
|
|
35
|
+
// input: { __isRef: true, blockId: samples, name: reads }
|
|
36
|
+
// species: human
|
|
37
|
+
// preset: milab-human-tcr-rna
|
|
38
|
+
// - id: browser
|
|
39
|
+
// kind: "@platforma-open/milaboratories.clonotype-browser.kind@1.0.0"
|
|
40
|
+
// block: "@platforma-open/milaboratories.clonotype-browser@2.4.1"
|
|
41
|
+
// params:
|
|
42
|
+
// clonotypes: { __isRef: true, blockId: mixcr, name: clonotypes }
|
|
43
|
+
//
|
|
44
|
+
// The YAML text <-> value step is not this package's job (no `yaml` dependency);
|
|
45
|
+
// byte-level round-tripping belongs to the middle-layer serializer.
|
|
46
|
+
//
|
|
47
|
+
const referenceExample = {
|
|
48
|
+
schema: "template-v1",
|
|
49
|
+
blocks: [
|
|
50
|
+
{
|
|
51
|
+
id: "samples",
|
|
52
|
+
kind: "@platforma-open/milaboratories.samples-and-data.kind@^1.0.0",
|
|
53
|
+
params: { dataset: "bulk-rna" },
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "mixcr",
|
|
57
|
+
kind: "@platforma-open/milaboratories.mixcr-clonotyping.kind@~1.2.0",
|
|
58
|
+
params: {
|
|
59
|
+
input: { __isRef: true, blockId: "samples", name: "reads" },
|
|
60
|
+
species: "human",
|
|
61
|
+
preset: "milab-human-tcr-rna",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "browser",
|
|
66
|
+
kind: "@platforma-open/milaboratories.clonotype-browser.kind@1.0.0",
|
|
67
|
+
block: "@platforma-open/milaboratories.clonotype-browser@2.4.1",
|
|
68
|
+
params: {
|
|
69
|
+
clonotypes: { __isRef: true, blockId: "mixcr", name: "clonotypes" },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
describe("kind selector grammar", () => {
|
|
76
|
+
test("the three tiers round-trip", () => {
|
|
77
|
+
expect(parseKindSelector("1.2.0")).toEqual({ op: "exact", version: "1.2.0" });
|
|
78
|
+
expect(parseKindSelector("~1.2.0")).toEqual({ op: "patch", version: "1.2.0" });
|
|
79
|
+
expect(parseKindSelector("^1.2.0")).toEqual({ op: "minor", version: "1.2.0" });
|
|
80
|
+
|
|
81
|
+
for (const raw of ["1.2.0", "~1.2.0", "^1.2.0"]) {
|
|
82
|
+
expect(formatKindSelector(parseKindSelector(raw))).toBe(raw);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("prerelease and build metadata survive", () => {
|
|
87
|
+
expect(parseKindSelector("~1.2.0-rc.1")).toEqual({ op: "patch", version: "1.2.0-rc.1" });
|
|
88
|
+
expect(parseKindSelector("1.2.0+build.5")).toEqual({ op: "exact", version: "1.2.0+build.5" });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("npm ranges outside the kind grammar are rejected", () => {
|
|
92
|
+
for (const raw of [">=1.0.0", "1.x", "1.2", "latest", "*", ""]) {
|
|
93
|
+
expect(() => parseKindSelector(raw)).toThrow(/Malformed kind version selector/);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("a scoped npm name keeps its leading @", () => {
|
|
98
|
+
const ref = "@platforma-open/milaboratories.mixcr-clonotyping.kind@~1.2.0";
|
|
99
|
+
expect(parseKindSelectorReference(ref as BlockKindSelectorReference)).toEqual({
|
|
100
|
+
name: "@platforma-open/milaboratories.mixcr-clonotyping.kind",
|
|
101
|
+
selector: { op: "patch", version: "1.2.0" },
|
|
102
|
+
});
|
|
103
|
+
expect(
|
|
104
|
+
formatKindSelectorReference({
|
|
105
|
+
name: "@platforma-open/milaboratories.mixcr-clonotyping.kind",
|
|
106
|
+
selector: { op: "patch", version: "1.2.0" },
|
|
107
|
+
}),
|
|
108
|
+
).toBe(ref);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("a reference with no version segment is malformed", () => {
|
|
112
|
+
expect(() =>
|
|
113
|
+
parseKindSelectorReference("@platforma-open/foo.kind" as BlockKindSelectorReference),
|
|
114
|
+
).toThrow(/Malformed kind selector reference/);
|
|
115
|
+
expect(isBlockKindSelectorReference("@platforma-open/foo.kind")).toBe(false);
|
|
116
|
+
expect(isBlockKindSelectorReference(42)).toBe(false);
|
|
117
|
+
expect(isBlockKindSelectorReference("@platforma-open/foo.kind@1.0.0")).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("an exact kind reference widens to the exact selector tier", () => {
|
|
121
|
+
// The export direction: a block implements exactly one version.
|
|
122
|
+
const resolved: BlockKindReference = formatKindRef({
|
|
123
|
+
name: "@platforma-open/milaboratories.clonotype-browser.kind",
|
|
124
|
+
version: "1.0.0",
|
|
125
|
+
});
|
|
126
|
+
const selector = kindReferenceToSelectorReference(resolved);
|
|
127
|
+
|
|
128
|
+
expect(selector).toBe("@platforma-open/milaboratories.clonotype-browser.kind@1.0.0");
|
|
129
|
+
expect(parseKindSelectorReference(selector).selector.op).toBe("exact");
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("block pack override", () => {
|
|
134
|
+
test("parses an exact reference", () => {
|
|
135
|
+
expect(
|
|
136
|
+
parseBlockPackReference(
|
|
137
|
+
"@platforma-open/milaboratories.clonotype-browser@2.4.1" as BlockPackReference,
|
|
138
|
+
),
|
|
139
|
+
).toEqual({
|
|
140
|
+
name: "@platforma-open/milaboratories.clonotype-browser",
|
|
141
|
+
version: "2.4.1",
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("rejects a range — an override must pin", () => {
|
|
146
|
+
expect(() =>
|
|
147
|
+
parseBlockPackReference("@platforma-open/foo@^2.4.1" as BlockPackReference),
|
|
148
|
+
).toThrow(/must pin an exact version/);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("block pack location", () => {
|
|
153
|
+
const location = (raw: string) => parseBlockPackLocation(raw as BlockPackLocationReference);
|
|
154
|
+
|
|
155
|
+
test("reads the scheme, which is all this layer needs to know", () => {
|
|
156
|
+
// Everything past the scheme belongs to whoever can reach it: this layer cannot
|
|
157
|
+
// know which schemes a given environment serves, and must not decide for it.
|
|
158
|
+
expect(location("file:///Users/dev/blocks/enter-numbers/block")).toEqual({ scheme: "file" });
|
|
159
|
+
expect(location("https://blocks.internal/enter-numbers")).toEqual({ scheme: "https" });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("the scheme is compared case-insensitively", () => {
|
|
163
|
+
expect(location("FILE:///Users/dev/blocks/x")).toEqual({ scheme: "file" });
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("a bare path is rejected, because it would be read against the wrong directory", () => {
|
|
167
|
+
// The whole point of a locator is removing the question "relative to what".
|
|
168
|
+
expect(() => location("/Users/dev/blocks/enter-numbers/block")).toThrow(
|
|
169
|
+
/absolute URI with a scheme/,
|
|
170
|
+
);
|
|
171
|
+
expect(() => location("./blocks/enter-numbers")).toThrow(/absolute URI with a scheme/);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("a Windows path is rejected rather than read as a one-letter scheme", () => {
|
|
175
|
+
// `C:\blocks\x` satisfies the URI scheme grammar with scheme `c`, so without the
|
|
176
|
+
// two-character floor it would be accepted here and fail somewhere unrelated.
|
|
177
|
+
expect(() => location("C:\\blocks\\enter-numbers")).toThrow(/absolute URI with a scheme/);
|
|
178
|
+
expect(location("file:///C:/blocks/enter-numbers")).toEqual({ scheme: "file" });
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("parseProjectTemplateV1", () => {
|
|
183
|
+
test("parses the reference example unchanged", () => {
|
|
184
|
+
const doc = parseProjectTemplateV1(referenceExample);
|
|
185
|
+
|
|
186
|
+
expect(doc).toEqual(referenceExample);
|
|
187
|
+
expect(doc.blocks.map((b) => b.id)).toEqual(["samples", "mixcr", "browser"]);
|
|
188
|
+
expect(doc.blocks[2].block).toBe("@platforma-open/milaboratories.clonotype-browser@2.4.1");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("params may be omitted in the file, and reads as `{}`", () => {
|
|
192
|
+
// The one field the parser fills in. Every reader past it gets a mapping, so none of
|
|
193
|
+
// them carries a `?? {}` that one of them would eventually forget.
|
|
194
|
+
const doc = parseProjectTemplateV1({
|
|
195
|
+
schema: "template-v1",
|
|
196
|
+
blocks: [{ id: "samples", kind: "@platforma-open/foo.kind@^1.0.0" }],
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
expect(doc.blocks[0].params).toEqual({});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("kind is required — it carries the params contract", () => {
|
|
203
|
+
expect(() =>
|
|
204
|
+
parseProjectTemplateV1({
|
|
205
|
+
schema: "template-v1",
|
|
206
|
+
blocks: [{ id: "samples", params: { dataset: "bulk-rna" } }],
|
|
207
|
+
}),
|
|
208
|
+
).toThrow(/kind/);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("an entry may pin where its implementation comes from", () => {
|
|
212
|
+
const doc = parseProjectTemplateV1({
|
|
213
|
+
schema: "template-v1",
|
|
214
|
+
blocks: [
|
|
215
|
+
{
|
|
216
|
+
id: "9f3c",
|
|
217
|
+
kind: "@milaboratories/milaboratories.test-enter-numbers.kind@^1.0.0",
|
|
218
|
+
location: "file:///Users/dev/blocks/enter-numbers/block",
|
|
219
|
+
params: { numbers: [1, 2, 3] },
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
expect(doc.blocks[0].location).toBe("file:///Users/dev/blocks/enter-numbers/block");
|
|
225
|
+
// The kind stays required alongside it: the locator says where to get the
|
|
226
|
+
// implementation, not what contract the params are written against.
|
|
227
|
+
expect(doc.blocks[0].kind).toBe(
|
|
228
|
+
"@milaboratories/milaboratories.test-enter-numbers.kind@^1.0.0",
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("an entry cannot pin both a version and a place", () => {
|
|
233
|
+
// Two different statements with nothing to reconcile them, so it is refused
|
|
234
|
+
// rather than settled by precedence.
|
|
235
|
+
const result = readProjectTemplateV1({
|
|
236
|
+
schema: "template-v1",
|
|
237
|
+
blocks: [
|
|
238
|
+
{
|
|
239
|
+
id: "a",
|
|
240
|
+
kind: "@o/a.kind@1.0.0",
|
|
241
|
+
block: "@o/a@1.0.0",
|
|
242
|
+
location: "file:///blocks/a",
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(result.ok).toBe(false);
|
|
248
|
+
if (result.ok) return;
|
|
249
|
+
expect(result.issues[0].path).toEqual(["blocks", 0, "location"]);
|
|
250
|
+
expect(result.issues[0].message).toMatch(/cannot carry both 'block' and 'location'/);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("a malformed location is reported on its own path", () => {
|
|
254
|
+
const result = readProjectTemplateV1({
|
|
255
|
+
schema: "template-v1",
|
|
256
|
+
blocks: [{ id: "a", kind: "@o/a.kind@1.0.0", location: "/blocks/a" }],
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(result.ok).toBe(false);
|
|
260
|
+
if (result.ok) return;
|
|
261
|
+
expect(result.issues[0].path).toEqual(["blocks", 0, "location"]);
|
|
262
|
+
// The grammar's own message, reported as it comes rather than restated.
|
|
263
|
+
expect(result.issues[0].message).toMatch(/absolute URI with a scheme/);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("the format marker is checked", () => {
|
|
267
|
+
expect(() => parseProjectTemplateV1({ schema: "template-v2", blocks: [] })).toThrow();
|
|
268
|
+
expect(() => parseProjectTemplateV1({ blocks: [] })).toThrow();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("unknown fields are rejected at both levels", () => {
|
|
272
|
+
// No `label` field: a template does not name block instances for display.
|
|
273
|
+
expect(() =>
|
|
274
|
+
parseProjectTemplateV1({
|
|
275
|
+
schema: "template-v1",
|
|
276
|
+
blocks: [{ id: "a", kind: "@o/a.kind@1.0.0", label: "Samples" }],
|
|
277
|
+
}),
|
|
278
|
+
).toThrow(/label/);
|
|
279
|
+
|
|
280
|
+
expect(() =>
|
|
281
|
+
parseProjectTemplateV1({ schema: "template-v1", blocks: [], metadata: {} }),
|
|
282
|
+
).toThrow(/metadata/);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("a malformed kind selector is reported on its own path", () => {
|
|
286
|
+
const result = readProjectTemplateV1({
|
|
287
|
+
schema: "template-v1",
|
|
288
|
+
blocks: [{ id: "a", kind: "@o/a.kind@>=1.0.0" }],
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
expect(result.ok).toBe(false);
|
|
292
|
+
if (!result.ok) {
|
|
293
|
+
expect(result.issues[0].path).toEqual(["blocks", 0, "kind"]);
|
|
294
|
+
expect(result.issues[0].message).toMatch(/Malformed kind version selector/);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("duplicate template-local ids are rejected", () => {
|
|
299
|
+
expect(() =>
|
|
300
|
+
parseProjectTemplateV1({
|
|
301
|
+
schema: "template-v1",
|
|
302
|
+
blocks: [
|
|
303
|
+
{ id: "a", kind: "@o/a.kind@1.0.0" },
|
|
304
|
+
{ id: "a", kind: "@o/b.kind@1.0.0" },
|
|
305
|
+
],
|
|
306
|
+
}),
|
|
307
|
+
).toThrow(/Duplicate template-local id: a/);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("the parser's output type is exactly ProjectTemplateV1", () => {
|
|
312
|
+
// NOTE: vitest runs without `--typecheck` and tsconfig excludes *.test.ts, so this
|
|
313
|
+
// assertion is authoring-time only; it is verified by running tsc over this file.
|
|
314
|
+
expectTypeOf<ReturnType<typeof parseProjectTemplateV1>>().toEqualTypeOf<ProjectTemplateV1>();
|
|
315
|
+
});
|