@milaboratories/pl-model-common 1.47.3 → 1.49.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/author_marker.d.ts +1 -2
- package/dist/author_marker.d.ts.map +1 -1
- package/dist/base64.d.ts +4 -6
- package/dist/base64.d.ts.map +1 -1
- package/dist/block_state.d.ts +13 -9
- package/dist/block_state.d.ts.map +1 -1
- package/dist/bmodel/block_config.d.ts +51 -29
- package/dist/bmodel/block_config.d.ts.map +1 -1
- 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 +55 -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/code.d.ts +4 -5
- package/dist/bmodel/code.d.ts.map +1 -1
- package/dist/bmodel/container.d.ts +13 -5
- 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/bmodel/normalization.d.ts +1 -3
- package/dist/bmodel/normalization.d.ts.map +1 -1
- package/dist/bmodel/types.d.ts +1 -2
- package/dist/bmodel/types.d.ts.map +1 -1
- package/dist/branding.d.ts +2 -3
- package/dist/branding.d.ts.map +1 -1
- package/dist/columns/accessor_traversal.d.ts +6 -8
- package/dist/columns/accessor_traversal.d.ts.map +1 -1
- package/dist/columns/column_registry.d.ts +2 -3
- package/dist/columns/column_registry.d.ts.map +1 -1
- package/dist/columns/column_selector.d.ts +9 -9
- package/dist/columns/column_selector.d.ts.map +1 -1
- package/dist/columns/dedup.cjs +1 -1
- package/dist/columns/dedup.cjs.map +1 -1
- package/dist/columns/dedup.d.ts +2 -4
- package/dist/columns/dedup.d.ts.map +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 +4 -5
- package/dist/columns/providers.d.ts.map +1 -1
- package/dist/columns/providers.js +1 -1
- package/dist/columns/providers.js.map +1 -1
- package/dist/columns/types.d.ts +12 -9
- package/dist/columns/types.d.ts.map +1 -1
- package/dist/common_types.d.ts +4 -6
- package/dist/common_types.d.ts.map +1 -1
- package/dist/dialog/index.d.ts +4 -5
- package/dist/dialog/index.d.ts.map +1 -1
- package/dist/driver_kit.d.ts +2 -2
- package/dist/driver_kit.d.ts.map +1 -1
- package/dist/drivers/ChunkedStreamReader.d.ts +3 -5
- package/dist/drivers/ChunkedStreamReader.d.ts.map +1 -1
- package/dist/drivers/blob.d.ts +36 -25
- package/dist/drivers/blob.d.ts.map +1 -1
- package/dist/drivers/columns/columns_collection_driver.d.ts +6 -7
- package/dist/drivers/columns/columns_collection_driver.d.ts.map +1 -1
- package/dist/drivers/columns/discover_columns_options.d.ts +9 -8
- package/dist/drivers/columns/discover_columns_options.d.ts.map +1 -1
- package/dist/drivers/index.cjs +5 -0
- package/dist/drivers/index.d.ts +5 -3
- package/dist/drivers/index.js +3 -3
- package/dist/drivers/interfaces.d.ts +2 -3
- package/dist/drivers/interfaces.d.ts.map +1 -1
- package/dist/drivers/log.d.ts +26 -28
- package/dist/drivers/log.d.ts.map +1 -1
- package/dist/drivers/ls.d.ts +37 -27
- package/dist/drivers/ls.d.ts.map +1 -1
- package/dist/drivers/pframe/column_filter.d.ts +1 -3
- package/dist/drivers/pframe/column_filter.d.ts.map +1 -1
- package/dist/drivers/pframe/data_info.d.ts +77 -50
- package/dist/drivers/pframe/data_info.d.ts.map +1 -1
- package/dist/drivers/pframe/data_types.d.ts +50 -45
- package/dist/drivers/pframe/data_types.d.ts.map +1 -1
- package/dist/drivers/pframe/driver.d.ts +5 -5
- package/dist/drivers/pframe/driver.d.ts.map +1 -1
- package/dist/drivers/pframe/filter_spec.d.ts +10 -12
- package/dist/drivers/pframe/filter_spec.d.ts.map +1 -1
- package/dist/drivers/pframe/find_columns.d.ts +2 -4
- package/dist/drivers/pframe/find_columns.d.ts.map +1 -1
- package/dist/drivers/pframe/index.cjs +5 -0
- package/dist/drivers/pframe/index.d.ts +5 -3
- package/dist/drivers/pframe/index.js +3 -3
- package/dist/drivers/pframe/linker_columns.d.ts +4 -10
- package/dist/drivers/pframe/linker_columns.d.ts.map +1 -1
- package/dist/drivers/pframe/pframe.d.ts +3 -3
- package/dist/drivers/pframe/pframe.d.ts.map +1 -1
- package/dist/drivers/pframe/query/query_common.d.ts +52 -46
- package/dist/drivers/pframe/query/query_common.d.ts.map +1 -1
- package/dist/drivers/pframe/query/query_data.d.ts +26 -21
- package/dist/drivers/pframe/query/query_data.d.ts.map +1 -1
- package/dist/drivers/pframe/query/query_spec.d.ts +22 -19
- package/dist/drivers/pframe/query/query_spec.d.ts.map +1 -1
- package/dist/drivers/pframe/query/utils.d.ts +15 -11
- package/dist/drivers/pframe/query/utils.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/anchored.d.ts +3 -5
- package/dist/drivers/pframe/spec/anchored.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/discovered_column.d.ts +10 -11
- package/dist/drivers/pframe/spec/discovered_column.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/filtered_column.d.ts +17 -18
- package/dist/drivers/pframe/spec/filtered_column.d.ts.map +1 -1
- 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 +63 -11
- 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 +5 -0
- package/dist/drivers/pframe/spec/index.d.ts +3 -3
- package/dist/drivers/pframe/spec/index.js +3 -3
- package/dist/drivers/pframe/spec/native_id.d.ts +3 -3
- package/dist/drivers/pframe/spec/native_id.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/overridden.d.ts +14 -16
- package/dist/drivers/pframe/spec/overridden.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/selectors.d.ts +22 -22
- package/dist/drivers/pframe/spec/selectors.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/spec.cjs +31 -0
- package/dist/drivers/pframe/spec/spec.cjs.map +1 -1
- package/dist/drivers/pframe/spec/spec.d.ts +94 -70
- package/dist/drivers/pframe/spec/spec.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/spec.js +31 -1
- package/dist/drivers/pframe/spec/spec.js.map +1 -1
- package/dist/drivers/pframe/spec_driver.d.ts +30 -25
- package/dist/drivers/pframe/spec_driver.d.ts.map +1 -1
- package/dist/drivers/pframe/table.d.ts +1 -3
- package/dist/drivers/pframe/table.d.ts.map +1 -1
- package/dist/drivers/pframe/table_calculate.d.ts +55 -46
- package/dist/drivers/pframe/table_calculate.d.ts.map +1 -1
- package/dist/drivers/pframe/table_common.d.ts +9 -10
- package/dist/drivers/pframe/table_common.d.ts.map +1 -1
- package/dist/drivers/pframe/unique_values.d.ts +3 -3
- package/dist/drivers/pframe/unique_values.d.ts.map +1 -1
- package/dist/drivers/upload.d.ts +2 -3
- package/dist/drivers/upload.d.ts.map +1 -1
- package/dist/drivers/urls.d.ts +7 -8
- package/dist/drivers/urls.d.ts.map +1 -1
- package/dist/errors.d.ts +35 -36
- package/dist/errors.d.ts.map +1 -1
- package/dist/flags/block_flags.cjs.map +1 -1
- package/dist/flags/block_flags.d.ts +7 -7
- package/dist/flags/block_flags.d.ts.map +1 -1
- package/dist/flags/block_flags.js.map +1 -1
- package/dist/flags/flag_utils.d.ts +6 -8
- package/dist/flags/flag_utils.d.ts.map +1 -1
- package/dist/flags/type_utils.d.ts +6 -7
- package/dist/flags/type_utils.d.ts.map +1 -1
- package/dist/httpAuth.d.ts +4 -5
- package/dist/httpAuth.d.ts.map +1 -1
- package/dist/index.cjs +30 -0
- package/dist/index.d.ts +15 -3
- package/dist/index.js +9 -3
- package/dist/json.d.ts +11 -12
- package/dist/json.d.ts.map +1 -1
- package/dist/navigation.d.ts +16 -11
- package/dist/navigation.d.ts.map +1 -1
- package/dist/plid.cjs +1 -1
- package/dist/plid.cjs.map +1 -1
- package/dist/plid.d.ts +8 -9
- package/dist/plid.d.ts.map +1 -1
- package/dist/plid.js +1 -1
- package/dist/plid.js.map +1 -1
- package/dist/pool/entry.d.ts +8 -6
- package/dist/pool/entry.d.ts.map +1 -1
- package/dist/pool/query.d.ts +3 -4
- package/dist/pool/query.d.ts.map +1 -1
- package/dist/pool/spec.d.ts +33 -30
- package/dist/pool/spec.d.ts.map +1 -1
- package/dist/pool_entry.d.ts +2 -3
- package/dist/pool_entry.d.ts.map +1 -1
- package/dist/project_id.d.ts +1 -3
- package/dist/project_id.d.ts.map +1 -1
- package/dist/ref.d.ts +22 -23
- package/dist/ref.d.ts.map +1 -1
- package/dist/resource_types.d.ts +2 -3
- package/dist/resource_types.d.ts.map +1 -1
- package/dist/services/node_service_handlers.d.ts +1 -3
- package/dist/services/node_service_handlers.d.ts.map +1 -1
- package/dist/services/service_capabilities.d.ts +7 -9
- package/dist/services/service_capabilities.d.ts.map +1 -1
- package/dist/services/service_declarations.d.ts +2 -2
- package/dist/services/service_declarations.d.ts.map +1 -1
- package/dist/services/service_injectors.d.ts +7 -8
- package/dist/services/service_injectors.d.ts.map +1 -1
- package/dist/services/service_registry.d.ts +2 -4
- package/dist/services/service_registry.d.ts.map +1 -1
- package/dist/services/service_types.d.ts +24 -26
- package/dist/services/service_types.d.ts.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 +78 -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 +217 -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 +75 -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 +67 -0
- package/dist/template/template_relocate.cjs.map +1 -0
- package/dist/template/template_relocate.d.ts +36 -0
- package/dist/template/template_relocate.d.ts.map +1 -0
- package/dist/template/template_relocate.js +67 -0
- package/dist/template/template_relocate.js.map +1 -0
- package/dist/utag.d.ts +2 -4
- package/dist/utag.d.ts.map +1 -1
- package/dist/util.d.ts +2 -3
- package/dist/util.d.ts.map +1 -1
- package/dist/value_or_error.d.ts +2 -3
- package/dist/value_or_error.d.ts.map +1 -1
- 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/drivers/pframe/spec/spec.ts +31 -0
- package/src/flags/block_flags.ts +1 -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 +239 -0
- package/src/template/template_relocate.ts +89 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/template/template_relocate.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Point every column identifier in a block's params at the blocks of the project being built.
|
|
4
|
+
*
|
|
5
|
+
* The whole of what a template does about references, and it lives here — in the package the
|
|
6
|
+
* block's own bundle imports — because knowing which values carry block ids is knowing the
|
|
7
|
+
* reference system. The engine carrying the params neither marks them, reads them, nor
|
|
8
|
+
* rewrites them: it hands the block its params and this map, and takes back what comes out.
|
|
9
|
+
*
|
|
10
|
+
* Params travel verbatim precisely so that this is possible. A file holds a `PlRef` as the
|
|
11
|
+
* object the block stored and a column id as the canonical string the block stored, with no
|
|
12
|
+
* marker of any kind, and the identifiers are found here by recognizing them — the same way
|
|
13
|
+
* the project's own dependency detector finds them in live args.
|
|
14
|
+
*
|
|
15
|
+
* Rewriting is structural, never textual: an identifier is taken apart, its `blockId` fields
|
|
16
|
+
* are replaced, and it is rebuilt canonically. That is what keeps a value that merely *looks*
|
|
17
|
+
* like an id — an axis filter, a domain this package can see belongs to an identifier it
|
|
18
|
+
* knows — from being rewritten along with it, and what re-sorts a qualifications map whose
|
|
19
|
+
* keys are identifiers.
|
|
20
|
+
*
|
|
21
|
+
* A domain reached on its own is the exception, and `relocateDomain` below says why: an axis is
|
|
22
|
+
* qualified by the block that produced it. Only the domain keys known to name a block are
|
|
23
|
+
* repointed — matching an entry id is not on its own evidence of a reference.
|
|
24
|
+
*
|
|
25
|
+
* An id the map does not mention is left as it is. That is the ordering rule doing its work:
|
|
26
|
+
* a caller building the map as it creates blocks passes only the entries already created, so
|
|
27
|
+
* a reference to an entry further down the file stays pointing at a block that does not
|
|
28
|
+
* exist, and the applied block reports itself as missing references rather than being wired
|
|
29
|
+
* to something below it.
|
|
30
|
+
*
|
|
31
|
+
* @param params Whatever the block projected, as the document stored it
|
|
32
|
+
* @param blockIds template-local entry id → the block id that entry was given
|
|
33
|
+
*/
|
|
34
|
+
export declare function relocateBlockIds<T>(params: T, blockIds: ReadonlyMap<string, string>): T;
|
|
35
|
+
//#endregion
|
|
36
|
+
//# sourceMappingURL=template_relocate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template_relocate.d.ts","names":[],"sources":["../../src/template/template_relocate.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAoCgB,iBAAiB,GAAG,QAAQ,GAAG,UAAU,8BAA8B"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { BlockScopedDomain } from "../drivers/pframe/spec/spec.js";
|
|
2
|
+
import { isColumnUniversalKey, remapColumnIdBlockIds } from "../drivers/pframe/spec/ids.js";
|
|
3
|
+
import "../drivers/index.js";
|
|
4
|
+
import { isPlainObject } from "es-toolkit";
|
|
5
|
+
//#region src/template/template_relocate.ts
|
|
6
|
+
/**
|
|
7
|
+
* Point every column identifier in a block's params at the blocks of the project being built.
|
|
8
|
+
*
|
|
9
|
+
* The whole of what a template does about references, and it lives here — in the package the
|
|
10
|
+
* block's own bundle imports — because knowing which values carry block ids is knowing the
|
|
11
|
+
* reference system. The engine carrying the params neither marks them, reads them, nor
|
|
12
|
+
* rewrites them: it hands the block its params and this map, and takes back what comes out.
|
|
13
|
+
*
|
|
14
|
+
* Params travel verbatim precisely so that this is possible. A file holds a `PlRef` as the
|
|
15
|
+
* object the block stored and a column id as the canonical string the block stored, with no
|
|
16
|
+
* marker of any kind, and the identifiers are found here by recognizing them — the same way
|
|
17
|
+
* the project's own dependency detector finds them in live args.
|
|
18
|
+
*
|
|
19
|
+
* Rewriting is structural, never textual: an identifier is taken apart, its `blockId` fields
|
|
20
|
+
* are replaced, and it is rebuilt canonically. That is what keeps a value that merely *looks*
|
|
21
|
+
* like an id — an axis filter, a domain this package can see belongs to an identifier it
|
|
22
|
+
* knows — from being rewritten along with it, and what re-sorts a qualifications map whose
|
|
23
|
+
* keys are identifiers.
|
|
24
|
+
*
|
|
25
|
+
* A domain reached on its own is the exception, and `relocateDomain` below says why: an axis is
|
|
26
|
+
* qualified by the block that produced it. Only the domain keys known to name a block are
|
|
27
|
+
* repointed — matching an entry id is not on its own evidence of a reference.
|
|
28
|
+
*
|
|
29
|
+
* An id the map does not mention is left as it is. That is the ordering rule doing its work:
|
|
30
|
+
* a caller building the map as it creates blocks passes only the entries already created, so
|
|
31
|
+
* a reference to an entry further down the file stays pointing at a block that does not
|
|
32
|
+
* exist, and the applied block reports itself as missing references rather than being wired
|
|
33
|
+
* to something below it.
|
|
34
|
+
*
|
|
35
|
+
* @param params Whatever the block projected, as the document stored it
|
|
36
|
+
* @param blockIds template-local entry id → the block id that entry was given
|
|
37
|
+
*/
|
|
38
|
+
function relocateBlockIds(params, blockIds) {
|
|
39
|
+
if (blockIds.size === 0) return params;
|
|
40
|
+
const remapBlockId = (blockId) => blockIds.get(blockId) ?? blockId;
|
|
41
|
+
const walk = (node) => {
|
|
42
|
+
if (typeof node === "string") return remapColumnIdBlockIds(node, remapBlockId);
|
|
43
|
+
if (isColumnUniversalKey(node)) return remapColumnIdBlockIds(node, remapBlockId);
|
|
44
|
+
if (Array.isArray(node)) return node.map(walk);
|
|
45
|
+
if (typeof node === "object" && node !== null) return Object.fromEntries(Object.entries(node).map(([key, value]) => [remapColumnIdBlockIds(key, remapBlockId), key === "domain" && isPlainObject(value) ? relocateDomain(value) : walk(value)]));
|
|
46
|
+
return node;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* The block-naming entries of a domain, repointed at the project being built: an axis a block
|
|
50
|
+
* produced names that block in its domain, so those are references like any other.
|
|
51
|
+
*
|
|
52
|
+
* Only the keys {@link BlockScopedDomain} lists, because a template's entry ids are arbitrary
|
|
53
|
+
* non-empty strings — a hand-written template may name an entry `closest`, and a qualifier
|
|
54
|
+
* reading `closest` is not a reference to it. Matching the map is not on its own enough to
|
|
55
|
+
* tell one from the other; the key is.
|
|
56
|
+
*
|
|
57
|
+
* Reached only from the generic object case — inside an identifier this package recognizes a
|
|
58
|
+
* domain is spec data and stays as it is, which is what keeps an overridden column's
|
|
59
|
+
* `specOverrides.domain` untouched.
|
|
60
|
+
*/
|
|
61
|
+
const relocateDomain = (domain) => Object.fromEntries(Object.entries(domain).map(([key, value]) => [key, typeof value === "string" && BlockScopedDomain.has(key) ? remapBlockId(value) : walk(value)]));
|
|
62
|
+
return walk(params);
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { relocateBlockIds };
|
|
66
|
+
|
|
67
|
+
//# sourceMappingURL=template_relocate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template_relocate.js","names":[],"sources":["../../src/template/template_relocate.ts"],"sourcesContent":["import { isPlainObject } from \"es-toolkit\";\n\nimport { BlockScopedDomain, isColumnUniversalKey, remapColumnIdBlockIds } from \"../drivers\";\n\n/**\n * Point every column identifier in a block's params at the blocks of the project being built.\n *\n * The whole of what a template does about references, and it lives here — in the package the\n * block's own bundle imports — because knowing which values carry block ids is knowing the\n * reference system. The engine carrying the params neither marks them, reads them, nor\n * rewrites them: it hands the block its params and this map, and takes back what comes out.\n *\n * Params travel verbatim precisely so that this is possible. A file holds a `PlRef` as the\n * object the block stored and a column id as the canonical string the block stored, with no\n * marker of any kind, and the identifiers are found here by recognizing them — the same way\n * the project's own dependency detector finds them in live args.\n *\n * Rewriting is structural, never textual: an identifier is taken apart, its `blockId` fields\n * are replaced, and it is rebuilt canonically. That is what keeps a value that merely *looks*\n * like an id — an axis filter, a domain this package can see belongs to an identifier it\n * knows — from being rewritten along with it, and what re-sorts a qualifications map whose\n * keys are identifiers.\n *\n * A domain reached on its own is the exception, and `relocateDomain` below says why: an axis is\n * qualified by the block that produced it. Only the domain keys known to name a block are\n * repointed — matching an entry id is not on its own evidence of a reference.\n *\n * An id the map does not mention is left as it is. That is the ordering rule doing its work:\n * a caller building the map as it creates blocks passes only the entries already created, so\n * a reference to an entry further down the file stays pointing at a block that does not\n * exist, and the applied block reports itself as missing references rather than being wired\n * to something below it.\n *\n * @param params Whatever the block projected, as the document stored it\n * @param blockIds template-local entry id → the block id that entry was given\n */\nexport function relocateBlockIds<T>(params: T, blockIds: ReadonlyMap<string, string>): T {\n if (blockIds.size === 0) return params;\n const remapBlockId = (blockId: string) => blockIds.get(blockId) ?? blockId;\n\n const walk = (node: unknown): unknown => {\n // Any string may be an identifier under any amount of escaping; one that is not comes\n // back as the very same string, so this needs no test of its own here.\n if (typeof node === \"string\") return remapColumnIdBlockIds(node, remapBlockId);\n\n // Before the generic object case: an identifier IS an object, and descending into one\n // would rewrite the strings nested in it piecemeal instead of rebuilding the whole id —\n // losing the bottom-up canonicalization that keeps the result a valid identifier.\n if (isColumnUniversalKey(node)) return remapColumnIdBlockIds(node, remapBlockId);\n\n if (Array.isArray(node)) return node.map(walk);\n\n if (typeof node === \"object\" && node !== null) {\n // Keys as well as values: params may be keyed by column id — per-column settings, say\n // — and a key is exactly as much of a reference as a value is.\n return Object.fromEntries(\n Object.entries(node).map(([key, value]) => [\n remapColumnIdBlockIds(key, remapBlockId),\n key === \"domain\" && isPlainObject(value) ? relocateDomain(value) : walk(value),\n ]),\n );\n }\n\n return node;\n };\n\n /**\n * The block-naming entries of a domain, repointed at the project being built: an axis a block\n * produced names that block in its domain, so those are references like any other.\n *\n * Only the keys {@link BlockScopedDomain} lists, because a template's entry ids are arbitrary\n * non-empty strings — a hand-written template may name an entry `closest`, and a qualifier\n * reading `closest` is not a reference to it. Matching the map is not on its own enough to\n * tell one from the other; the key is.\n *\n * Reached only from the generic object case — inside an identifier this package recognizes a\n * domain is spec data and stays as it is, which is what keeps an overridden column's\n * `specOverrides.domain` untouched.\n */\n const relocateDomain = (domain: Record<string, unknown>): Record<string, unknown> =>\n Object.fromEntries(\n Object.entries(domain).map(([key, value]) => [\n key,\n typeof value === \"string\" && BlockScopedDomain.has(key) ? remapBlockId(value) : walk(value),\n ]),\n );\n\n return walk(params) as T;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,iBAAoB,QAAW,UAA0C;CACvF,IAAI,SAAS,SAAS,GAAG,OAAO;CAChC,MAAM,gBAAgB,YAAoB,SAAS,IAAI,OAAO,KAAK;CAEnE,MAAM,QAAQ,SAA2B;EAGvC,IAAI,OAAO,SAAS,UAAU,OAAO,sBAAsB,MAAM,YAAY;EAK7E,IAAI,qBAAqB,IAAI,GAAG,OAAO,sBAAsB,MAAM,YAAY;EAE/E,IAAI,MAAM,QAAQ,IAAI,GAAG,OAAO,KAAK,IAAI,IAAI;EAE7C,IAAI,OAAO,SAAS,YAAY,SAAS,MAGvC,OAAO,OAAO,YACZ,OAAO,QAAQ,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,CACzC,sBAAsB,KAAK,YAAY,GACvC,QAAQ,YAAY,cAAc,KAAK,IAAI,eAAe,KAAK,IAAI,KAAK,KAAK,CAC/E,CAAC,CACH;EAGF,OAAO;CACT;;;;;;;;;;;;;;CAeA,MAAM,kBAAkB,WACtB,OAAO,YACL,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,CAC3C,KACA,OAAO,UAAU,YAAY,kBAAkB,IAAI,GAAG,IAAI,aAAa,KAAK,IAAI,KAAK,KAAK,CAC5F,CAAC,CACH;CAEF,OAAO,KAAK,MAAM;AACpB"}
|
package/dist/utag.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { AuthorMarker } from "./author_marker.js";
|
|
2
|
-
|
|
3
2
|
//#region src/utag.d.ts
|
|
4
3
|
/** Value returned for changing states supporting reactive listening for changes */
|
|
5
|
-
interface ValueWithUTag<V> {
|
|
4
|
+
export interface ValueWithUTag<V> {
|
|
6
5
|
/** Value snapshot. */
|
|
7
6
|
readonly value: V;
|
|
8
7
|
/**
|
|
@@ -14,9 +13,8 @@ interface ValueWithUTag<V> {
|
|
|
14
13
|
* */
|
|
15
14
|
readonly uTag: string;
|
|
16
15
|
}
|
|
17
|
-
interface ValueWithUTagAndAuthor<V> extends ValueWithUTag<V> {
|
|
16
|
+
export interface ValueWithUTagAndAuthor<V> extends ValueWithUTag<V> {
|
|
18
17
|
readonly author?: AuthorMarker;
|
|
19
18
|
}
|
|
20
19
|
//#endregion
|
|
21
|
-
export { ValueWithUTag, ValueWithUTagAndAuthor };
|
|
22
20
|
//# sourceMappingURL=utag.d.ts.map
|
package/dist/utag.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utag.d.ts","names":[],"sources":["../src/utag.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"utag.d.ts","names":[],"sources":["../src/utag.ts"],"mappings":";;;iBAGiB,cAAc;;WAEpB,OAAO;;;;;;;;WASP;;iBAGM,uBAAuB,WAAW,cAAc;WACtD,SAAS"}
|
package/dist/util.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
//#region src/util.d.ts
|
|
2
|
-
declare function assertNever(x: never): never;
|
|
2
|
+
export declare function assertNever(x: never): never;
|
|
3
3
|
/**
|
|
4
4
|
* Return unique entries of the array by the provided id
|
|
5
5
|
* For each id, the last entry is kept
|
|
6
6
|
*/
|
|
7
|
-
declare function uniqueBy<T>(array: T[], makeId: (entry: T) => string): T[];
|
|
7
|
+
export declare function uniqueBy<T>(array: T[], makeId: (entry: T) => string): T[];
|
|
8
8
|
//#endregion
|
|
9
|
-
export { assertNever, uniqueBy };
|
|
10
9
|
//# sourceMappingURL=util.d.ts.map
|
package/dist/util.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","names":[],"sources":["../src/util.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"util.d.ts","names":[],"sources":["../src/util.ts"],"mappings":";wBAAgB,YAAY;;;;;wBAQZ,SAAS,GAAG,OAAO,KAAK,SAAS,OAAO,eAAe"}
|
package/dist/value_or_error.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
//#region src/value_or_error.d.ts
|
|
2
|
-
type ValueOrError<V, E> = {
|
|
2
|
+
export type ValueOrError<V, E> = {
|
|
3
3
|
ok: true;
|
|
4
4
|
value: V;
|
|
5
5
|
} | {
|
|
6
6
|
ok: false;
|
|
7
7
|
error: E;
|
|
8
8
|
};
|
|
9
|
-
declare function mapValueInVOE<V1, V2, E>(voe: ValueOrError<V1, E>, cb: (value: V1) => V2): ValueOrError<V2, E>;
|
|
9
|
+
export declare function mapValueInVOE<V1, V2, E>(voe: ValueOrError<V1, E>, cb: (value: V1) => V2): ValueOrError<V2, E>;
|
|
10
10
|
//#endregion
|
|
11
|
-
export { ValueOrError, mapValueInVOE };
|
|
12
11
|
//# sourceMappingURL=value_or_error.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"value_or_error.d.ts","names":[],"sources":["../src/value_or_error.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"value_or_error.d.ts","names":[],"sources":["../src/value_or_error.ts"],"mappings":";YAAY,aAAa,GAAG;EAEtB;EACA,OAAO;;EAGP;EACA,OAAO;;wBAGG,cAAc,IAAI,IAAI,GACpC,KAAK,aAAa,IAAI,IACtB,KAAK,OAAO,OAAO,KAClB,aAAa,IAAI"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/pl-model-common",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.49.0",
|
|
4
4
|
"description": "Platforma SDK Model",
|
|
5
5
|
"files": [
|
|
6
6
|
"./dist/**/*",
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@vitest/coverage-istanbul": "^4.1.3",
|
|
28
|
-
"typescript": "
|
|
28
|
+
"typescript": "7.0.2",
|
|
29
29
|
"vitest": "^4.1.3",
|
|
30
|
-
"@milaboratories/build-configs": "2.0.
|
|
31
|
-
"@milaboratories/ts-builder": "1.
|
|
32
|
-
"@milaboratories/ts-configs": "1.
|
|
30
|
+
"@milaboratories/build-configs": "2.0.1",
|
|
31
|
+
"@milaboratories/ts-builder": "1.7.2",
|
|
32
|
+
"@milaboratories/ts-configs": "1.4.0"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "ts-builder build --target node",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Branded } from "@milaboratories/helpers";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* On-wire reference to a block kind, canonically the string `{name}@{version}`.
|
|
5
|
+
*
|
|
6
|
+
* A branded string: readers overwhelmingly need identity equality ("does block
|
|
7
|
+
* X implement kind Y?"), for which an opaque canonical string is ideal. Any
|
|
8
|
+
* reader that needs the parts calls {@link parseKindRef}; any writer composes
|
|
9
|
+
* the reference through {@link formatKindRef}. Keeping composition in a single
|
|
10
|
+
* function localizes the one open decision — whether the name segment has to be
|
|
11
|
+
* org-qualified for global uniqueness — to one place.
|
|
12
|
+
*/
|
|
13
|
+
export type BlockKindReference = Branded<string, "BlockKindReference">;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Compose a {@link BlockKindReference} from a kind's `name`/`version`.
|
|
17
|
+
*
|
|
18
|
+
* The single place that decides how the reference is assembled. If global
|
|
19
|
+
* uniqueness later requires the name segment to be org-qualified, this is the
|
|
20
|
+
* one line that changes.
|
|
21
|
+
*/
|
|
22
|
+
export const formatKindRef = (k: { name: string; version: string }): BlockKindReference =>
|
|
23
|
+
`${k.name}@${k.version}` as BlockKindReference;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Split a `{name}@{version}` string on its version separator.
|
|
27
|
+
*
|
|
28
|
+
* The one place that decides where the name ends. Uses the LAST `@` so an
|
|
29
|
+
* org-qualified npm name that itself starts with `@` (e.g.
|
|
30
|
+
* `@platforma-open/pkg.kind`) keeps its whole name. A leading/absent separator
|
|
31
|
+
* (`lastIndexOf("@") <= 0`) means the string carries no version segment —
|
|
32
|
+
* malformed — so this throws rather than returning a silently version-less
|
|
33
|
+
* result.
|
|
34
|
+
*
|
|
35
|
+
* Shared with the template layer, whose `{name}@{selector}` references use the
|
|
36
|
+
* same split and differ only in how the right half is interpreted (see
|
|
37
|
+
* `parseKindSelectorReference`). `what` names the thing being parsed so the
|
|
38
|
+
* error message stays specific to the caller's reference type.
|
|
39
|
+
*/
|
|
40
|
+
export const splitVersionedName = (
|
|
41
|
+
ref: string,
|
|
42
|
+
what = "block kind reference",
|
|
43
|
+
expected = "{name}@{version}",
|
|
44
|
+
): { name: string; version: string } => {
|
|
45
|
+
const at = ref.lastIndexOf("@");
|
|
46
|
+
if (at <= 0) {
|
|
47
|
+
throw new Error(`Malformed ${what} (expected '${expected}'): ${ref}`);
|
|
48
|
+
}
|
|
49
|
+
return { name: ref.slice(0, at), version: ref.slice(at + 1) };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Split a {@link BlockKindReference} back into `{ name, version }`.
|
|
54
|
+
*
|
|
55
|
+
* Throws on a reference with no version segment — see
|
|
56
|
+
* {@link splitVersionedName}, which owns the split rule.
|
|
57
|
+
*/
|
|
58
|
+
export const parseKindRef = (ref: BlockKindReference): { name: string; version: string } =>
|
|
59
|
+
splitVersionedName(ref);
|
package/src/bmodel/container.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BlockConfigV3Generic, BlockConfigV4Generic } from "./block_config";
|
|
2
|
+
import type { BlockKindReference } from "./block_kind_ref";
|
|
2
3
|
import type { Code } from "./code";
|
|
3
4
|
import type { BlockRenderingMode } from "./types";
|
|
4
5
|
|
|
@@ -11,6 +12,14 @@ export type BlockConfigContainer = {
|
|
|
11
12
|
/** Config code bundle. Actually is required, but we keep it optional for backward compatibility */
|
|
12
13
|
readonly code?: Code;
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Reference to the block kind this config implements, in `{name}@{version}`
|
|
17
|
+
* form. Version-independent block identity — lives at the container level
|
|
18
|
+
* beside {@link code}, orthogonal to which render envelope (`v3`/`v4`)
|
|
19
|
+
* applies. Optional for backward compatibility with kind-less blocks.
|
|
20
|
+
*/
|
|
21
|
+
readonly kind?: BlockKindReference;
|
|
22
|
+
|
|
14
23
|
//
|
|
15
24
|
// Fields below are used to read previous config versions
|
|
16
25
|
//
|
package/src/bmodel/index.ts
CHANGED
package/src/columns/dedup.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { isPObjectId } from "../pool";
|
|
|
20
20
|
*
|
|
21
21
|
* Shared by sandbox-side `extractColumns` (column_providers) and host-side
|
|
22
22
|
* `ColumnsCollectionDriverImpl.getColumns` — both layers need identical
|
|
23
|
-
* dedup semantics, but operate on different concrete item types (
|
|
23
|
+
* dedup semantics, but operate on different concrete item types (DataColumnRecipe
|
|
24
24
|
* vs. raw ColumnUniversalId).
|
|
25
25
|
*/
|
|
26
26
|
export function dedupColumns<T>(
|
package/src/columns/providers.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { AccessorLike, ColumnEntriesProvider, LeafEntry, UpstreamBlockCtx }
|
|
|
8
8
|
* exposes `isFinal()` via the root's `getInputsLocked()`.
|
|
9
9
|
*
|
|
10
10
|
* Used directly on the host side; sandbox extends it with `getColumns()`
|
|
11
|
-
* returning {@link
|
|
11
|
+
* returning {@link DataColumnRecipe}s — see `AccessorColumnsProvider` in
|
|
12
12
|
* `@platforma-sdk/model`.
|
|
13
13
|
*/
|
|
14
14
|
export class AccessorEntriesProvider<
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { createGlobalPObjectId, createLocalPObjectId } from "../../../pool";
|
|
3
|
+
import { createColumnDiscoveredId } from "./discovered_column";
|
|
4
|
+
import { createColumnFilteredId } from "./filtered_column";
|
|
5
|
+
import { peelJsonLayers, type ColumnUniversalId } from "./ids";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The escape-layer peeler.
|
|
9
|
+
*
|
|
10
|
+
* It is the one definition of "how a value can be hiding inside a string", and the reference
|
|
11
|
+
* detector in `pl-middle-layer` (`inferAllReferencedBlocks`) is built on it — a block id can
|
|
12
|
+
* sit under any number of `JSON.stringify` passes, and a walk over object properties reaches
|
|
13
|
+
* none of them. It deliberately says nothing about which values count as identifiers; the
|
|
14
|
+
* cases below are identifiers only because that is what the callers care about.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const leaf = (blockId: string, name: string) =>
|
|
18
|
+
createGlobalPObjectId(blockId, name) as ColumnUniversalId;
|
|
19
|
+
|
|
20
|
+
describe("peelJsonLayers", () => {
|
|
21
|
+
test("a canonical id is layer zero — encoded once, and that once is the id itself", () => {
|
|
22
|
+
const id = leaf("samples", "reads");
|
|
23
|
+
|
|
24
|
+
expect(peelJsonLayers(id)).toEqual({
|
|
25
|
+
value: { __isRef: true, blockId: "samples", name: "reads" },
|
|
26
|
+
layers: 0,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("each extra stringify pass is one more layer", () => {
|
|
31
|
+
const id = leaf("samples", "reads");
|
|
32
|
+
|
|
33
|
+
expect(peelJsonLayers(JSON.stringify(id))?.layers).toBe(1);
|
|
34
|
+
expect(peelJsonLayers(JSON.stringify(JSON.stringify(id)))?.layers).toBe(2);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("the value at the bottom is the same however deep it was", () => {
|
|
38
|
+
const id = leaf("samples", "reads");
|
|
39
|
+
const bottom = { __isRef: true, blockId: "samples", name: "reads" };
|
|
40
|
+
|
|
41
|
+
expect(peelJsonLayers(JSON.stringify(JSON.stringify(id)))?.value).toEqual(bottom);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("a nested identifier peels to its own outer key, not to the leaf", () => {
|
|
45
|
+
// Wrapper forms nest by *string*, so peeling reaches the outermost key and stops. Walking
|
|
46
|
+
// further in is the caller's business, and no caller does — which is the point.
|
|
47
|
+
const filtered = createColumnFilteredId({
|
|
48
|
+
source: createColumnDiscoveredId({ column: leaf("samples", "clonotypes") }),
|
|
49
|
+
axisFilters: [[0, "IGH"]],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const peeled = peelJsonLayers(filtered);
|
|
53
|
+
|
|
54
|
+
expect(peeled?.layers).toBe(0);
|
|
55
|
+
expect(peeled?.value).toMatchObject({ __isFiltered: true });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("a local leaf peels too, though it carries no marker at all", () => {
|
|
59
|
+
// The gate must not demand `__isRef`: a filtered id whose innermost leaf is local has
|
|
60
|
+
// none, and a peeler that required one would miss the whole chain.
|
|
61
|
+
const id = createLocalPObjectId(["pf", "byChain"], "abundance");
|
|
62
|
+
|
|
63
|
+
expect(peelJsonLayers(id)?.value).toEqual({
|
|
64
|
+
resolvePath: ["pf", "byChain"],
|
|
65
|
+
name: "abundance",
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("an ordinary string is not JSON and stops at the first character", () => {
|
|
70
|
+
for (const value of ["samples", "", "not json", "1.0", "yes"]) {
|
|
71
|
+
expect(peelJsonLayers(value)).toBeUndefined();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("a quoted string that only ever yields strings is refused", () => {
|
|
76
|
+
// `"\"abc\""` peels to `abc`, which is not JSON — there is no encoded value in there, so
|
|
77
|
+
// there is nothing for a caller to look at.
|
|
78
|
+
expect(peelJsonLayers(JSON.stringify("abc"))).toBeUndefined();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("malformed JSON is refused rather than thrown", () => {
|
|
82
|
+
expect(peelJsonLayers('{"__isRef": true')).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("a JSON array or scalar is a value like any other", () => {
|
|
86
|
+
// Nothing here is identifier-specific: the peeler answers "what was encoded", full stop.
|
|
87
|
+
expect(peelJsonLayers("[1,2]")).toBeUndefined();
|
|
88
|
+
expect(peelJsonLayers('{"a":1}')).toEqual({ value: { a: 1 }, layers: 0 });
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
} from "./filtered_column";
|
|
9
9
|
import {
|
|
10
10
|
createPObjectId,
|
|
11
|
+
isGlobalPObjectKey,
|
|
12
|
+
isLocalPObjectKey,
|
|
11
13
|
isPObjectId,
|
|
12
14
|
isPObjectKey,
|
|
13
15
|
LocalPObjectKey,
|
|
@@ -28,7 +30,7 @@ import {
|
|
|
28
30
|
type ColumnOverriddenId,
|
|
29
31
|
type ColumnOverriddenKey,
|
|
30
32
|
} from "./overridden";
|
|
31
|
-
import { canonicalizeJson } from "../../../json";
|
|
33
|
+
import { canonicalizeJson, parseJsonSafely } from "../../../json";
|
|
32
34
|
import { AxisSpec, PColumnSpec } from "./spec";
|
|
33
35
|
import { isString } from "es-toolkit";
|
|
34
36
|
|
|
@@ -97,6 +99,194 @@ export function parseColumnIdSafely(
|
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
/** Whether `value` is any of the five key forms a {@link ColumnUniversalId} serializes. */
|
|
103
|
+
export function isColumnUniversalKey(value: unknown): value is ColumnUniversalKey {
|
|
104
|
+
return (
|
|
105
|
+
isPObjectKey(value) ||
|
|
106
|
+
isColumnFilteredKey(value) ||
|
|
107
|
+
isColumnDiscoveredKey(value) ||
|
|
108
|
+
isColumnOverriddenKey(value)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function isColumnUniversalId(value: unknown): value is ColumnUniversalId {
|
|
113
|
+
const key = isString(value) ? parseJsonSafely(value, false) : false;
|
|
114
|
+
return key === false ? false : isColumnUniversalKey(key);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* A JSON string with its escape padding taken off, and how many passes that took.
|
|
119
|
+
*
|
|
120
|
+
* `layers` counts the `JSON.stringify` passes *above* the encoded value: a canonical id
|
|
121
|
+
* is `layers: 0`, the same id run through `JSON.stringify` once more is `layers: 1`. Keep
|
|
122
|
+
* it to put the value back the way it was found.
|
|
123
|
+
*/
|
|
124
|
+
export type PeeledJsonLayers = {
|
|
125
|
+
readonly value: unknown;
|
|
126
|
+
readonly layers: number;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Take a value out of however many `JSON.stringify` passes wrapped it, or `undefined`
|
|
131
|
+
* when `s` is not JSON at all.
|
|
132
|
+
*
|
|
133
|
+
* The one definition of "how a value can be hiding inside a string" — a block id can sit
|
|
134
|
+
* under several layers of escaping, and a walk over object properties reaches none of
|
|
135
|
+
* them. Callers differ in what they do at the bottom (this deliberately says nothing
|
|
136
|
+
* about which values count as identifiers), but they must agree on the mechanics, or
|
|
137
|
+
* "what carries a block id" ends up with two answers that drift.
|
|
138
|
+
*
|
|
139
|
+
* The gate is cheap and does NOT require any marker in the body: a filtered id whose
|
|
140
|
+
* innermost leaf is a {@link LocalPObjectKey} carries no `__isRef`, so demanding one
|
|
141
|
+
* would miss it.
|
|
142
|
+
*/
|
|
143
|
+
export function peelJsonLayers(s: string): PeeledJsonLayers | undefined {
|
|
144
|
+
let current = s;
|
|
145
|
+
let layers = 0;
|
|
146
|
+
for (;;) {
|
|
147
|
+
const c0 = current.charCodeAt(0);
|
|
148
|
+
if (c0 !== 0x7b /* { */ && c0 !== 0x22 /* " */) return undefined;
|
|
149
|
+
let parsed: unknown;
|
|
150
|
+
try {
|
|
151
|
+
parsed = JSON.parse(current);
|
|
152
|
+
} catch {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
// A pass that yielded another string was escape padding, so peel again. The string
|
|
156
|
+
// is strictly shorter each time, which is what bounds the loop.
|
|
157
|
+
if (isString(parsed)) {
|
|
158
|
+
if (parsed.length >= current.length) return undefined;
|
|
159
|
+
current = parsed;
|
|
160
|
+
layers++;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
return { value: parsed, layers };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Rewrite every block id buried inside a column id.
|
|
169
|
+
*
|
|
170
|
+
* A {@link GlobalPObjectKey} leaf names its upstream by block id, and the wrapper key forms
|
|
171
|
+
* nest by *string* id rather than by object — so a block id can sit under several layers of
|
|
172
|
+
* JSON escaping, and `queriesQualifications` carries one in a map *key*. A caller that only
|
|
173
|
+
* walks object properties never reaches any of them, which is why moving a column id between
|
|
174
|
+
* projects needs this rather than a generic walk.
|
|
175
|
+
*
|
|
176
|
+
* Recursion re-canonicalizes bottom-up, so every level is canonical afterwards — including
|
|
177
|
+
* the rebuilt `queriesQualifications`, whose keys the canonical form sorts. That is the
|
|
178
|
+
* property a textual rewrite cannot have: redirecting an id that is a map key changes what
|
|
179
|
+
* the sorted order should be, and only rebuilding restores it.
|
|
180
|
+
*
|
|
181
|
+
* Returns the input itself when no block id changed, so a caller mapping ids to themselves
|
|
182
|
+
* gets its value back byte-for-byte and never re-serializes a stored id. Any `string` is
|
|
183
|
+
* accepted for the same reason: a caller sweeping a params object cannot know which of its
|
|
184
|
+
* strings are ids, and one that is not is returned as-is.
|
|
185
|
+
*
|
|
186
|
+
* @param remapBlockId old block id → new block id. Throw from it to reject an id that cannot
|
|
187
|
+
* be mapped.
|
|
188
|
+
*/
|
|
189
|
+
export function remapColumnIdBlockIds<T extends string | ColumnUniversalKey>(
|
|
190
|
+
id: T,
|
|
191
|
+
remapBlockId: (blockId: string) => string,
|
|
192
|
+
): T {
|
|
193
|
+
const remapped = isString(id) ? remapIdString(id, remapBlockId) : remapKey(id, remapBlockId);
|
|
194
|
+
return (remapped ?? id) as T;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* The string half of {@link remapColumnIdBlockIds}. `undefined` means "nothing to change",
|
|
199
|
+
* which is what keeps an unaffected id from being re-serialized.
|
|
200
|
+
*
|
|
201
|
+
* Escape padding is peeled and put back, so an id that reached params through an extra
|
|
202
|
+
* `JSON.stringify` is rewritten in place and comes back wrapped as it was found. A string
|
|
203
|
+
* that does not peel to a column key is left alone: params hold ordinary strings too.
|
|
204
|
+
*/
|
|
205
|
+
function remapIdString(id: string, remapBlockId: (blockId: string) => string): string | undefined {
|
|
206
|
+
const peeled = peelJsonLayers(id);
|
|
207
|
+
if (peeled === undefined || !isColumnUniversalKey(peeled.value)) return undefined;
|
|
208
|
+
|
|
209
|
+
const remappedKey = remapKey(peeled.value, remapBlockId);
|
|
210
|
+
if (remappedKey === undefined) return undefined;
|
|
211
|
+
|
|
212
|
+
let rebuilt: string = stringifyColumnId(remappedKey);
|
|
213
|
+
for (let layer = 0; layer < peeled.layers; layer++) rebuilt = JSON.stringify(rebuilt);
|
|
214
|
+
return rebuilt;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** The key half of {@link remapColumnIdBlockIds}. `undefined` means "nothing to change". */
|
|
218
|
+
function remapKey(
|
|
219
|
+
key: ColumnUniversalKey,
|
|
220
|
+
remapBlockId: (blockId: string) => string,
|
|
221
|
+
): ColumnUniversalKey | undefined {
|
|
222
|
+
if (isGlobalPObjectKey(key)) {
|
|
223
|
+
const blockId = remapBlockId(key.blockId);
|
|
224
|
+
return blockId === key.blockId ? undefined : { ...key, blockId };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// A local leaf names its column by a path inside its own block — no block id.
|
|
228
|
+
if (isLocalPObjectKey(key)) return undefined;
|
|
229
|
+
|
|
230
|
+
if (isColumnFilteredKey(key)) {
|
|
231
|
+
const source = remapIdString(key.source, remapBlockId);
|
|
232
|
+
return source === undefined ? undefined : { ...key, source: source as ColumnUniversalId };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (isColumnOverriddenKey(key)) {
|
|
236
|
+
const source = remapIdString(key.source, remapBlockId);
|
|
237
|
+
// Remapping preserves the id's shape, so `source` is still not an Overridden id.
|
|
238
|
+
return source === undefined
|
|
239
|
+
? undefined
|
|
240
|
+
: { ...key, source: source as ColumnOverriddenKey["source"] };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (isColumnDiscoveredKey(key)) return remapDiscoveredKey(key, remapBlockId);
|
|
244
|
+
|
|
245
|
+
throw new Error(
|
|
246
|
+
`remapColumnIdBlockIds: unrecognized column id structure: ${JSON.stringify(key)}`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Discovered is the only key form carrying more than one nested id: the column it
|
|
252
|
+
* discovered, one per linker hop, and one per entry in `queriesQualifications` — where the
|
|
253
|
+
* id is the map key, not the value.
|
|
254
|
+
*/
|
|
255
|
+
function remapDiscoveredKey(
|
|
256
|
+
key: ColumnDiscoveredKey,
|
|
257
|
+
remapBlockId: (blockId: string) => string,
|
|
258
|
+
): ColumnDiscoveredKey | undefined {
|
|
259
|
+
const column = remapIdString(key.column, remapBlockId);
|
|
260
|
+
|
|
261
|
+
let pathChanged = false;
|
|
262
|
+
const path = key.path?.map((item) => {
|
|
263
|
+
const itemColumn = remapIdString(item.column, remapBlockId);
|
|
264
|
+
if (itemColumn === undefined) return item;
|
|
265
|
+
pathChanged = true;
|
|
266
|
+
return { ...item, column: itemColumn as ColumnUniversalId };
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
let queriesChanged = false;
|
|
270
|
+
const queriesQualifications =
|
|
271
|
+
key.queriesQualifications &&
|
|
272
|
+
(Object.fromEntries(
|
|
273
|
+
Object.entries(key.queriesQualifications).map(([queryId, qualifications]) => {
|
|
274
|
+
const remappedId = remapIdString(queryId, remapBlockId);
|
|
275
|
+
if (remappedId === undefined) return [queryId, qualifications];
|
|
276
|
+
queriesChanged = true;
|
|
277
|
+
return [remappedId, qualifications];
|
|
278
|
+
}),
|
|
279
|
+
) as ColumnDiscoveredKey["queriesQualifications"]);
|
|
280
|
+
|
|
281
|
+
if (column === undefined && !pathChanged && !queriesChanged) return undefined;
|
|
282
|
+
return {
|
|
283
|
+
...key,
|
|
284
|
+
...(column !== undefined ? { column: column as ColumnUniversalId } : {}),
|
|
285
|
+
...(pathChanged ? { path } : {}),
|
|
286
|
+
...(queriesChanged ? { queriesQualifications } : {}),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
100
290
|
/**
|
|
101
291
|
* Walk a rich column id down to its terminal leaf {@link PObjectId}.
|
|
102
292
|
*/
|