@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,182 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import type { ColumnUniversalId } from "../drivers";
|
|
3
|
+
import {
|
|
4
|
+
createColumnDiscoveredId,
|
|
5
|
+
createColumnFilteredId,
|
|
6
|
+
createColumnOverriddenId,
|
|
7
|
+
} from "../drivers";
|
|
8
|
+
import { canonicalizeJson } from "../json";
|
|
9
|
+
import { createGlobalPObjectId, createLocalPObjectId } from "../pool";
|
|
10
|
+
import { createPlRef } from "../ref";
|
|
11
|
+
import { relocateBlockIds } from "./template_relocate";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Pointing a block's params at the project being built.
|
|
15
|
+
*
|
|
16
|
+
* This is the whole of what a template does about references, so what these tests pin is not
|
|
17
|
+
* "the walk visits everything" but the two properties a textual rewrite could not have: a
|
|
18
|
+
* value that merely looks like an id is left alone, and an identifier comes back canonical.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const leafId = (blockId: string, name: string) =>
|
|
22
|
+
createGlobalPObjectId(blockId, name) as ColumnUniversalId;
|
|
23
|
+
|
|
24
|
+
const to = (from: string, into: string) => new Map([[from, into]]);
|
|
25
|
+
|
|
26
|
+
describe("relocateBlockIds", () => {
|
|
27
|
+
test("a PlRef is repointed where it sits", () => {
|
|
28
|
+
const params = { input: createPlRef("old", "reads") };
|
|
29
|
+
|
|
30
|
+
expect(relocateBlockIds(params, to("old", "new"))).toEqual({
|
|
31
|
+
input: createPlRef("new", "reads"),
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("an identifier held as a canonical string is repointed too", () => {
|
|
36
|
+
const params = { anchor: leafId("old", "reads") };
|
|
37
|
+
|
|
38
|
+
expect(relocateBlockIds(params, to("old", "new"))).toEqual({ anchor: leafId("new", "reads") });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("an id the map does not mention is left alone — that is the ordering rule", () => {
|
|
42
|
+
// A reference to an entry further down the file: the caller's map holds only entries
|
|
43
|
+
// already created, so this keeps its template-local id and the applied block reports
|
|
44
|
+
// itself as missing references instead of pointing at a block below it.
|
|
45
|
+
const params = { input: createPlRef("later", "reads") };
|
|
46
|
+
|
|
47
|
+
expect(relocateBlockIds(params, to("earlier", "new"))).toEqual(params);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("a local leaf has no block id, and comes back the same string", () => {
|
|
51
|
+
const local = createLocalPObjectId("some/path") as ColumnUniversalId;
|
|
52
|
+
|
|
53
|
+
expect(relocateBlockIds({ column: local }, to("old", "new"))).toEqual({ column: local });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("nothing to relocate means the very same params object", () => {
|
|
57
|
+
const params = { species: "hsa", numbers: [1, 2, 3], note: `see {"not":"an id"}` };
|
|
58
|
+
|
|
59
|
+
expect(relocateBlockIds(params, to("old", "new"))).toEqual(params);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("an empty map hands the params back untouched", () => {
|
|
63
|
+
const params = { input: createPlRef("old", "reads") };
|
|
64
|
+
|
|
65
|
+
expect(relocateBlockIds(params, new Map())).toBe(params);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("what a textual rewrite got wrong", () => {
|
|
69
|
+
test("a value that merely equals a block id is NOT rewritten", () => {
|
|
70
|
+
// The whole reason this is structural. `producedBy` is spec data that happens to carry
|
|
71
|
+
// the same string; a textual pass over the payload rewrote it along with the reference.
|
|
72
|
+
const id = createColumnOverriddenId({
|
|
73
|
+
source: leafId("old", "clonotypes"),
|
|
74
|
+
specOverrides: { domain: { producedBy: "old", chain: "old" } },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const moved = relocateBlockIds({ a: id }, to("old", "new")) as { a: string };
|
|
78
|
+
const key = JSON.parse(moved.a) as { source: string; specOverrides: { domain: unknown } };
|
|
79
|
+
|
|
80
|
+
expect(key.source).toContain('"blockId":"new"');
|
|
81
|
+
expect(key.specOverrides.domain).toEqual({ producedBy: "old", chain: "old" });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("an identifier in a map KEY is repointed, and the result is canonical again", () => {
|
|
85
|
+
// Rebuilding is what restores canonical order: the keys sort by their new values, so
|
|
86
|
+
// the result equals the id a fresh project would build for the same column.
|
|
87
|
+
const qual = [{ axis: { name: "sampleId" }, contextDomain: {} }];
|
|
88
|
+
const before = createColumnDiscoveredId({
|
|
89
|
+
column: leafId("src", "clonotypes"),
|
|
90
|
+
queriesQualifications: { [leafId("aaa", "x")]: qual, [leafId("zzz", "y")]: qual },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const moved = relocateBlockIds(
|
|
94
|
+
{ a: before },
|
|
95
|
+
new Map([
|
|
96
|
+
["src", "src2"],
|
|
97
|
+
["aaa", "zz9"],
|
|
98
|
+
["zzz", "aa1"],
|
|
99
|
+
]),
|
|
100
|
+
) as { a: string };
|
|
101
|
+
|
|
102
|
+
expect(moved.a).toBe(
|
|
103
|
+
createColumnDiscoveredId({
|
|
104
|
+
column: leafId("src2", "clonotypes"),
|
|
105
|
+
queriesQualifications: { [leafId("zz9", "x")]: qual, [leafId("aa1", "y")]: qual },
|
|
106
|
+
}),
|
|
107
|
+
);
|
|
108
|
+
expect(moved.a).toBe(canonicalizeJson(JSON.parse(moved.a)));
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("depth", () => {
|
|
113
|
+
test("three nested forms are repointed at the bottom and rebuilt on the way up", () => {
|
|
114
|
+
const deep = (block: string) =>
|
|
115
|
+
createColumnFilteredId({
|
|
116
|
+
source: createColumnOverriddenId({
|
|
117
|
+
source: leafId(block, "clonotypes"),
|
|
118
|
+
specOverrides: { domain: { species: "hsa" } },
|
|
119
|
+
}),
|
|
120
|
+
axisFilters: [[0, "IGH"]],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(relocateBlockIds({ anchor: deep("old") }, to("old", "new"))).toEqual({
|
|
124
|
+
anchor: deep("new"),
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("a discovered column's linker path names other blocks, and each is repointed", () => {
|
|
129
|
+
// The case one wrapper around the whole identifier had to cover: several ids, from
|
|
130
|
+
// several blocks, at several depths.
|
|
131
|
+
const disc = (a: string, b: string, c: string) =>
|
|
132
|
+
createColumnDiscoveredId({
|
|
133
|
+
column: leafId(a, "clonotypes"),
|
|
134
|
+
path: [
|
|
135
|
+
{ type: "linker", column: leafId(b, "cell-to-clone") },
|
|
136
|
+
{ type: "linker", column: leafId(c, "clone-to-gene") },
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const moved = relocateBlockIds(
|
|
141
|
+
{ anchor: disc("A", "B", "C") },
|
|
142
|
+
new Map([
|
|
143
|
+
["A", "A2"],
|
|
144
|
+
["B", "B2"],
|
|
145
|
+
["C", "C2"],
|
|
146
|
+
]),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(moved).toEqual({ anchor: disc("A2", "B2", "C2") });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("escape padding is peeled and put back as it was found", () => {
|
|
153
|
+
const padded = (block: string) => JSON.stringify(leafId(block, "reads"));
|
|
154
|
+
|
|
155
|
+
expect(relocateBlockIds({ hit: padded("old") }, to("old", "new"))).toEqual({
|
|
156
|
+
hit: padded("new"),
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("identifiers are reached in arrays and nested objects alike", () => {
|
|
161
|
+
const params = {
|
|
162
|
+
inputs: [createPlRef("old", "a"), { nested: leafId("old", "b") }],
|
|
163
|
+
deeper: { list: [[createPlRef("old", "c")]] },
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
expect(relocateBlockIds(params, to("old", "new"))).toEqual({
|
|
167
|
+
inputs: [createPlRef("new", "a"), { nested: leafId("new", "b") }],
|
|
168
|
+
deeper: { list: [[createPlRef("new", "c")]] },
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("params keyed BY a column id have the key repointed", () => {
|
|
173
|
+
// A block may key its params by column — per-column settings. A key carries a
|
|
174
|
+
// reference exactly as much as a value does.
|
|
175
|
+
const params = { [leafId("old", "reads")]: { visible: true } };
|
|
176
|
+
|
|
177
|
+
expect(relocateBlockIds(params, to("old", "new"))).toEqual({
|
|
178
|
+
[leafId("new", "reads")]: { visible: true },
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { isColumnUniversalKey, remapColumnIdBlockIds } from "../drivers";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Point every column identifier in a block's params at the blocks of the project being built.
|
|
5
|
+
*
|
|
6
|
+
* The whole of what a template does about references, and it lives here — in the package the
|
|
7
|
+
* block's own bundle imports — because knowing which values carry block ids is knowing the
|
|
8
|
+
* reference system. The engine carrying the params neither marks them, reads them, nor
|
|
9
|
+
* rewrites them: it hands the block its params and this map, and takes back what comes out.
|
|
10
|
+
*
|
|
11
|
+
* Params travel verbatim precisely so that this is possible. A file holds a `PlRef` as the
|
|
12
|
+
* object the block stored and a column id as the canonical string the block stored, with no
|
|
13
|
+
* marker of any kind, and the identifiers are found here by recognizing them — the same way
|
|
14
|
+
* the project's own dependency detector finds them in live args.
|
|
15
|
+
*
|
|
16
|
+
* Rewriting is structural, never textual: an identifier is taken apart, its `blockId` fields
|
|
17
|
+
* are replaced, and it is rebuilt canonically. That is what keeps a value that merely *looks*
|
|
18
|
+
* like an id — a `domain` entry, an axis filter — from being rewritten along with it, and
|
|
19
|
+
* what re-sorts a qualifications map whose keys are identifiers.
|
|
20
|
+
*
|
|
21
|
+
* An id the map does not mention is left as it is. That is the ordering rule doing its work:
|
|
22
|
+
* a caller building the map as it creates blocks passes only the entries already created, so
|
|
23
|
+
* a reference to an entry further down the file stays pointing at a block that does not
|
|
24
|
+
* exist, and the applied block reports itself as missing references rather than being wired
|
|
25
|
+
* to something below it.
|
|
26
|
+
*
|
|
27
|
+
* @param params Whatever the block projected, as the document stored it
|
|
28
|
+
* @param blockIds template-local entry id → the block id that entry was given
|
|
29
|
+
*/
|
|
30
|
+
export function relocateBlockIds<T>(params: T, blockIds: ReadonlyMap<string, string>): T {
|
|
31
|
+
if (blockIds.size === 0) return params;
|
|
32
|
+
const remapBlockId = (blockId: string) => blockIds.get(blockId) ?? blockId;
|
|
33
|
+
|
|
34
|
+
const walk = (node: unknown): unknown => {
|
|
35
|
+
// Any string may be an identifier under any amount of escaping; one that is not comes
|
|
36
|
+
// back as the very same string, so this needs no test of its own here.
|
|
37
|
+
if (typeof node === "string") return remapColumnIdBlockIds(node, remapBlockId);
|
|
38
|
+
|
|
39
|
+
// Before the generic object case: an identifier IS an object, and descending into one
|
|
40
|
+
// would rewrite the strings nested in it piecemeal instead of rebuilding the whole id —
|
|
41
|
+
// losing the bottom-up canonicalization that keeps the result a valid identifier.
|
|
42
|
+
if (isColumnUniversalKey(node)) return remapColumnIdBlockIds(node, remapBlockId);
|
|
43
|
+
|
|
44
|
+
if (Array.isArray(node)) return node.map(walk);
|
|
45
|
+
|
|
46
|
+
if (typeof node === "object" && node !== null) {
|
|
47
|
+
// Keys as well as values: params may be keyed by column id — per-column settings, say
|
|
48
|
+
// — and a key is exactly as much of a reference as a value is.
|
|
49
|
+
return Object.fromEntries(
|
|
50
|
+
Object.entries(node).map(([key, value]) => [
|
|
51
|
+
remapColumnIdBlockIds(key, remapBlockId),
|
|
52
|
+
walk(value),
|
|
53
|
+
]),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return node;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return walk(params) as T;
|
|
61
|
+
}
|