sqlstack 1.0.4 → 1.0.6
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/utils/dto.d.ts +1 -2
- package/dist/utils/dto.d.ts.map +1 -1
- package/dist/utils/dto.js +57 -27
- package/dist/utils/dto.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +9 -0
package/dist/utils/dto.d.ts
CHANGED
package/dist/utils/dto.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dto.d.ts","sourceRoot":"","sources":["../../src/utils/dto.ts"],"names":[],"mappings":"AAAA,KAAK,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE9B,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,
|
|
1
|
+
{"version":3,"file":"dto.d.ts","sourceRoot":"","sources":["../../src/utils/dto.ts"],"names":[],"mappings":"AAAA,KAAK,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE9B,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,CAAC,aAAa,EAAE,MAAM,GAAG,GAAG,CAAC;CAC9B,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,iGAAiG;IACjG,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AA6CF;;GAEG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAE,UAAe,IAkE7B,MAAM,EAAE,EAAE,sBAerC"}
|
package/dist/utils/dto.js
CHANGED
|
@@ -2,21 +2,24 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DTO = DTO;
|
|
4
4
|
const CHILD_INDEX = Symbol('childIndex');
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
function setDeep(obj, path, value) {
|
|
6
|
+
const parts = path.split('.');
|
|
7
|
+
let cur = obj;
|
|
8
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
9
|
+
const k = parts[i];
|
|
10
|
+
if (cur[k] == null || typeof cur[k] !== 'object')
|
|
11
|
+
cur[k] = {};
|
|
12
|
+
cur = cur[k];
|
|
13
|
+
}
|
|
14
|
+
cur[parts[parts.length - 1]] = value;
|
|
12
15
|
}
|
|
13
16
|
function childCollections(spec) {
|
|
14
17
|
const out = [];
|
|
15
18
|
for (const key of Object.keys(spec)) {
|
|
16
|
-
if (key === 'id' || key === 'name'
|
|
19
|
+
if (key === 'id' || key === 'name')
|
|
17
20
|
continue;
|
|
18
21
|
const val = spec[key];
|
|
19
|
-
if (val && typeof val === 'object' &&
|
|
22
|
+
if (val && typeof val === 'object' && !Array.isArray(val))
|
|
20
23
|
out.push([key, val]);
|
|
21
24
|
}
|
|
22
25
|
return out;
|
|
@@ -48,36 +51,63 @@ function stripMeta(node) {
|
|
|
48
51
|
*/
|
|
49
52
|
function DTO(spec, opts = {}) {
|
|
50
53
|
const single = opts.single ?? false;
|
|
54
|
+
const mappingsCache = new WeakMap();
|
|
55
|
+
function getMappings(levelSpec) {
|
|
56
|
+
const cached = mappingsCache.get(levelSpec);
|
|
57
|
+
if (cached)
|
|
58
|
+
return cached;
|
|
59
|
+
const out = [];
|
|
60
|
+
for (const [k, v] of Object.entries(levelSpec)) {
|
|
61
|
+
if (typeof v === 'string')
|
|
62
|
+
out.push([k, v]);
|
|
63
|
+
}
|
|
64
|
+
// Ensure stable ordering so identity is independent of spec key order
|
|
65
|
+
out.sort((a, b) => a[0].localeCompare(b[0]));
|
|
66
|
+
mappingsCache.set(levelSpec, out);
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
function computeKey(levelSpec, row) {
|
|
70
|
+
const mappings = getMappings(levelSpec);
|
|
71
|
+
if (mappings.length === 0)
|
|
72
|
+
return undefined;
|
|
73
|
+
const pairs = mappings.map(([, source]) => row[source]);
|
|
74
|
+
// If all mapping values are null/undefined, skip
|
|
75
|
+
if (pairs.every((v) => v === null || v === undefined))
|
|
76
|
+
return undefined;
|
|
77
|
+
// Stable composite key: include source keys with values
|
|
78
|
+
const composite = mappings.map(([, source], i) => [source, pairs[i]]);
|
|
79
|
+
return JSON.stringify(composite);
|
|
80
|
+
}
|
|
51
81
|
function upsertAtLevel(rowsIndex, rowsOut, levelSpec, row) {
|
|
52
|
-
const
|
|
53
|
-
if (
|
|
82
|
+
const nodeKey = computeKey(levelSpec, row);
|
|
83
|
+
if (nodeKey == null)
|
|
54
84
|
return null;
|
|
55
|
-
let node = rowsIndex.get(
|
|
85
|
+
let node = rowsIndex.get(nodeKey);
|
|
56
86
|
if (!node) {
|
|
57
|
-
node = {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
rowsIndex.set(
|
|
87
|
+
node = {};
|
|
88
|
+
for (const [target, source] of getMappings(levelSpec)) {
|
|
89
|
+
if (source in row)
|
|
90
|
+
setDeep(node, target, row[source]);
|
|
91
|
+
}
|
|
92
|
+
rowsIndex.set(nodeKey, node);
|
|
63
93
|
rowsOut.push(node);
|
|
64
94
|
}
|
|
65
95
|
return node;
|
|
66
96
|
}
|
|
67
97
|
function descend(parent, levelSpec, row) {
|
|
68
98
|
for (const [collectionKey, childSpec] of childCollections(levelSpec)) {
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
99
|
+
const childKey = computeKey(childSpec, row);
|
|
100
|
+
if (childKey == null)
|
|
71
101
|
continue; // skip this branch for this row
|
|
72
102
|
const { arr, index } = ensureChildInfra(parent, collectionKey);
|
|
73
|
-
let child = index.get(
|
|
103
|
+
let child = index.get(childKey);
|
|
74
104
|
if (!child) {
|
|
75
|
-
child = {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
index.set(
|
|
105
|
+
child = {};
|
|
106
|
+
for (const [target, source] of getMappings(childSpec)) {
|
|
107
|
+
if (source in row)
|
|
108
|
+
setDeep(child, target, row[source]);
|
|
109
|
+
}
|
|
110
|
+
index.set(childKey, child);
|
|
81
111
|
arr.push(child);
|
|
82
112
|
}
|
|
83
113
|
// Recurse further for this child (it may host its own collections)
|
package/dist/utils/dto.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dto.js","sourceRoot":"","sources":["../../src/utils/dto.ts"],"names":[],"mappings":"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","sourcesContent":["type KV = Record<string, any>;\n\nexport type NodeSpec = {\n id: string;\n name?: string;\n picks?: string[];\n // nested collections: { [collectionKey]: NodeSpec }\n [collectionKey: string]: any;\n};\n\ntype DTOOptions = {\n /** If true, return a single root (or null). If false, return an array of roots. Default: true */\n single?: boolean;\n};\n\nconst CHILD_INDEX = Symbol('childIndex');\n\nfunction copyPicks(row: KV, picks?: string[]) {\n if (!picks || picks.length === 0) return {};\n const out: KV = {};\n for (const k of picks) out[k] = row[k];\n return out;\n}\n\nfunction childCollections(spec: NodeSpec): Array<[string, NodeSpec]> {\n const out: Array<[string, NodeSpec]> = [];\n for (const key of Object.keys(spec)) {\n if (key === 'id' || key === 'name' || key === 'picks') continue;\n const val = (spec as any)[key];\n if (val && typeof val === 'object' && 'id' in val) out.push([key, val as NodeSpec]);\n }\n return out;\n}\n\nfunction ensureChildInfra(node: KV, collectionKey: string) {\n if (!node[collectionKey]) node[collectionKey] = [];\n if (!(node as any)[CHILD_INDEX]) (node as any)[CHILD_INDEX] = {} as Record<string, Map<any, KV>>;\n const idxBag = (node as any)[CHILD_INDEX] as Record<string, Map<any, KV>>;\n if (!idxBag[collectionKey]) idxBag[collectionKey] = new Map<any, KV>();\n return { arr: node[collectionKey] as KV[], index: idxBag[collectionKey] as Map<any, KV> };\n}\n\nfunction stripMeta(node: KV) {\n if (node && typeof node === 'object') {\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete (node as any)[CHILD_INDEX];\n for (const k of Object.keys(node)) {\n const v = node[k];\n if (Array.isArray(v)) v.forEach(stripMeta);\n }\n }\n return node;\n}\n\n/**\n * Build a rows→tree transformer from a nested spec.\n */\nexport function DTO(spec: NodeSpec, opts: DTOOptions = {}) {\n const single = opts.single ?? false;\n type IndexMap = Map<any, KV>;\n\n function upsertAtLevel(rowsIndex: IndexMap, rowsOut: KV[], levelSpec: NodeSpec, row: KV) {\n const nodeId = row[levelSpec.id];\n if (nodeId == null) return null;\n\n let node = rowsIndex.get(nodeId);\n if (!node) {\n node = {\n id: nodeId,\n ...(levelSpec.name ? { name: row[levelSpec.name] } : {}),\n ...copyPicks(row, levelSpec.picks),\n };\n rowsIndex.set(nodeId, node);\n rowsOut.push(node);\n }\n return node;\n }\n\n function descend(parent: KV, levelSpec: NodeSpec, row: KV) {\n for (const [collectionKey, childSpec] of childCollections(levelSpec)) {\n const childId = row[childSpec.id];\n if (childId == null) continue; // skip this branch for this row\n\n const { arr, index } = ensureChildInfra(parent, collectionKey);\n let child = index.get(childId);\n if (!child) {\n child = {\n id: childId,\n ...(childSpec.name ? { name: row[childSpec.name] } : {}),\n ...copyPicks(row, childSpec.picks),\n };\n index.set(childId, child);\n arr.push(child);\n }\n\n // Recurse further for this child (it may host its own collections)\n descend(child, childSpec, row);\n }\n }\n\n return function transform(rows: KV[]) {\n if (!rows || rows.length === 0) return single ? null : [];\n\n const roots: KV[] = [];\n const rootIndex: IndexMap = new Map();\n\n for (const row of rows) {\n const root = upsertAtLevel(rootIndex, roots, spec, row);\n if (!root) continue;\n descend(root, spec, row);\n }\n\n roots.forEach(stripMeta);\n return single ? roots[0] ?? null : roots;\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dto.js","sourceRoot":"","sources":["../../src/utils/dto.ts"],"names":[],"mappings":"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","sourcesContent":["type KV = Record<string, any>;\n\nexport type NodeSpec = {\n id?: string;\n name?: string;\n // nested collections: { [collectionKey]: NodeSpec }\n [collectionKey: string]: any;\n};\n\ntype DTOOptions = {\n /** If true, return a single root (or null). If false, return an array of roots. Default: true */\n single?: boolean;\n};\n\nconst CHILD_INDEX = Symbol('childIndex');\n\nfunction setDeep(obj: KV, path: string, value: any) {\n const parts = path.split('.');\n let cur: any = obj;\n for (let i = 0; i < parts.length - 1; i++) {\n const k = parts[i]!;\n if (cur[k] == null || typeof cur[k] !== 'object') cur[k] = {};\n cur = cur[k];\n }\n cur[parts[parts.length - 1]!] = value;\n}\n\nfunction childCollections(spec: NodeSpec): Array<[string, NodeSpec]> {\n const out: Array<[string, NodeSpec]> = [];\n for (const key of Object.keys(spec)) {\n if (key === 'id' || key === 'name') continue;\n const val = (spec as any)[key];\n if (val && typeof val === 'object' && !Array.isArray(val)) out.push([key, val as NodeSpec]);\n }\n return out;\n}\n\nfunction ensureChildInfra(node: KV, collectionKey: string) {\n if (!node[collectionKey]) node[collectionKey] = [];\n if (!(node as any)[CHILD_INDEX]) (node as any)[CHILD_INDEX] = {} as Record<string, Map<any, KV>>;\n const idxBag = (node as any)[CHILD_INDEX] as Record<string, Map<any, KV>>;\n if (!idxBag[collectionKey]) idxBag[collectionKey] = new Map<any, KV>();\n return { arr: node[collectionKey] as KV[], index: idxBag[collectionKey] as Map<any, KV> };\n}\n\nfunction stripMeta(node: KV) {\n if (node && typeof node === 'object') {\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete (node as any)[CHILD_INDEX];\n for (const k of Object.keys(node)) {\n const v = node[k];\n if (Array.isArray(v)) v.forEach(stripMeta);\n }\n }\n return node;\n}\n\n/**\n * Build a rows→tree transformer from a nested spec.\n */\nexport function DTO(spec: NodeSpec, opts: DTOOptions = {}) {\n const single = opts.single ?? false;\n type IndexMap = Map<any, KV>;\n const mappingsCache = new WeakMap<NodeSpec, Array<[string, string]>>();\n\n function getMappings(levelSpec: NodeSpec): Array<[string, string]> {\n const cached = mappingsCache.get(levelSpec);\n if (cached) return cached;\n const out: Array<[string, string]> = [];\n for (const [k, v] of Object.entries(levelSpec)) {\n if (typeof v === 'string') out.push([k, v]);\n }\n // Ensure stable ordering so identity is independent of spec key order\n out.sort((a, b) => (a[0] as string).localeCompare(b[0] as string));\n mappingsCache.set(levelSpec, out);\n return out;\n }\n\n function computeKey(levelSpec: NodeSpec, row: KV): any {\n const mappings = getMappings(levelSpec);\n if (mappings.length === 0) return undefined;\n const pairs = mappings.map(([, source]) => row[source]);\n // If all mapping values are null/undefined, skip\n if (pairs.every((v) => v === null || v === undefined)) return undefined;\n // Stable composite key: include source keys with values\n const composite = mappings.map(([, source], i) => [source, pairs[i]] as [string, any]);\n return JSON.stringify(composite);\n }\n\n function upsertAtLevel(rowsIndex: IndexMap, rowsOut: KV[], levelSpec: NodeSpec, row: KV) {\n const nodeKey = computeKey(levelSpec, row);\n if (nodeKey == null) return null;\n\n let node = rowsIndex.get(nodeKey);\n if (!node) {\n node = {};\n for (const [target, source] of getMappings(levelSpec)) {\n if (source in row) setDeep(node, target, (row as any)[source]);\n }\n rowsIndex.set(nodeKey, node);\n rowsOut.push(node);\n }\n return node;\n }\n\n function descend(parent: KV, levelSpec: NodeSpec, row: KV) {\n for (const [collectionKey, childSpec] of childCollections(levelSpec)) {\n const childKey = computeKey(childSpec, row);\n if (childKey == null) continue; // skip this branch for this row\n\n const { arr, index } = ensureChildInfra(parent, collectionKey);\n let child = index.get(childKey);\n if (!child) {\n child = {};\n for (const [target, source] of getMappings(childSpec)) {\n if (source in row) setDeep(child, target, (row as any)[source]);\n }\n index.set(childKey, child);\n arr.push(child);\n }\n\n // Recurse further for this child (it may host its own collections)\n descend(child, childSpec, row);\n }\n }\n\n return function transform(rows: KV[]) {\n if (!rows || rows.length === 0) return single ? null : [];\n\n const roots: KV[] = [];\n const rootIndex: IndexMap = new Map();\n\n for (const row of rows) {\n const root = upsertAtLevel(rootIndex, roots, spec, row);\n if (!root) continue;\n descend(root, spec, row);\n }\n\n roots.forEach(stripMeta);\n return single ? roots[0] ?? null : roots;\n };\n}\n"]}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -469,10 +469,19 @@ class Repo {
|
|
|
469
469
|
}
|
|
470
470
|
```
|
|
471
471
|
|
|
472
|
+
You can also project flat columns into nested properties directly in the spec:
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
// If a row has: { id: '1', muscle: 'biceps', group: 'arms' }
|
|
476
|
+
const toSimple = DTO({ id: 'id', name: 'muscle', 'group.name': 'group' } as any, { single: true });
|
|
477
|
+
// Result root: { name: 'biceps', group: { name: 'arms' } }
|
|
478
|
+
```
|
|
479
|
+
|
|
472
480
|
- Arbitrary depth: nest collections by adding objects under new keys.
|
|
473
481
|
- Dedupe: nodes are de-duplicated by their `id` at each level.
|
|
474
482
|
- Null-safe: branches with `NULL` child ids are skipped for that row.
|
|
475
483
|
- Order: sibling order follows your SQL `ORDER BY`.
|
|
484
|
+
- Mapping: put `'target.path': 'source_key'` entries directly in your spec to populate nested properties from flat columns.
|
|
476
485
|
|
|
477
486
|
## Underlying Connections
|
|
478
487
|
|