sqlstack 1.0.3 → 1.0.4
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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/dto.d.ts +17 -0
- package/dist/utils/dto.d.ts.map +1 -0
- package/dist/utils/dto.js +102 -0
- package/dist/utils/dto.js.map +1 -0
- package/package.json +1 -1
- package/readme.md +47 -0
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -25,4 +25,5 @@ __exportStar(require("./decorators/defaults"), exports);
|
|
|
25
25
|
__exportStar(require("./decorators/resultShape"), exports);
|
|
26
26
|
__exportStar(require("./decorators/validateResult"), exports);
|
|
27
27
|
__exportStar(require("./decorators/transform"), exports);
|
|
28
|
+
__exportStar(require("./utils/dto"), exports);
|
|
28
29
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,gDAA8B;AAC9B,mDAAiC;AACjC,6CAA2B;AAC3B,6CAA2B;AAC3B,2DAAyC;AACzC,qDAAmC;AACnC,wDAAsC;AACtC,2DAAyC;AACzC,8DAA4C;AAC5C,yDAAuC","sourcesContent":["export * from './binders/sqlBinder';\nexport * from './core/errors';\nexport * from './core/validator';\nexport * from './registry';\nexport * from './adapters';\nexport * from './decorators/queryBinder';\nexport * from './decorators/query';\nexport * from './decorators/defaults';\nexport * from './decorators/resultShape';\nexport * from './decorators/validateResult';\nexport * from './decorators/transform';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,gDAA8B;AAC9B,mDAAiC;AACjC,6CAA2B;AAC3B,6CAA2B;AAC3B,2DAAyC;AACzC,qDAAmC;AACnC,wDAAsC;AACtC,2DAAyC;AACzC,8DAA4C;AAC5C,yDAAuC;AACvC,8CAA4B","sourcesContent":["export * from './binders/sqlBinder';\nexport * from './core/errors';\nexport * from './core/validator';\nexport * from './registry';\nexport * from './adapters';\nexport * from './decorators/queryBinder';\nexport * from './decorators/query';\nexport * from './decorators/defaults';\nexport * from './decorators/resultShape';\nexport * from './decorators/validateResult';\nexport * from './decorators/transform';\nexport * from './utils/dto';\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type KV = Record<string, any>;
|
|
2
|
+
export type NodeSpec = {
|
|
3
|
+
id: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
picks?: string[];
|
|
6
|
+
[collectionKey: string]: any;
|
|
7
|
+
};
|
|
8
|
+
type DTOOptions = {
|
|
9
|
+
/** If true, return a single root (or null). If false, return an array of roots. Default: true */
|
|
10
|
+
single?: boolean;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Build a rows→tree transformer from a nested spec.
|
|
14
|
+
*/
|
|
15
|
+
export declare function DTO(spec: NodeSpec, opts?: DTOOptions): (rows: KV[]) => KV | KV[] | null;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=dto.d.ts.map
|
|
@@ -0,0 +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,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAEjB,CAAC,aAAa,EAAE,MAAM,GAAG,GAAG,CAAC;CAC9B,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,iGAAiG;IACjG,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAyCF;;GAEG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAE,UAAe,IA2C7B,MAAM,EAAE,EAAE,sBAerC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DTO = DTO;
|
|
4
|
+
const CHILD_INDEX = Symbol('childIndex');
|
|
5
|
+
function copyPicks(row, picks) {
|
|
6
|
+
if (!picks || picks.length === 0)
|
|
7
|
+
return {};
|
|
8
|
+
const out = {};
|
|
9
|
+
for (const k of picks)
|
|
10
|
+
out[k] = row[k];
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
function childCollections(spec) {
|
|
14
|
+
const out = [];
|
|
15
|
+
for (const key of Object.keys(spec)) {
|
|
16
|
+
if (key === 'id' || key === 'name' || key === 'picks')
|
|
17
|
+
continue;
|
|
18
|
+
const val = spec[key];
|
|
19
|
+
if (val && typeof val === 'object' && 'id' in val)
|
|
20
|
+
out.push([key, val]);
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
function ensureChildInfra(node, collectionKey) {
|
|
25
|
+
if (!node[collectionKey])
|
|
26
|
+
node[collectionKey] = [];
|
|
27
|
+
if (!node[CHILD_INDEX])
|
|
28
|
+
node[CHILD_INDEX] = {};
|
|
29
|
+
const idxBag = node[CHILD_INDEX];
|
|
30
|
+
if (!idxBag[collectionKey])
|
|
31
|
+
idxBag[collectionKey] = new Map();
|
|
32
|
+
return { arr: node[collectionKey], index: idxBag[collectionKey] };
|
|
33
|
+
}
|
|
34
|
+
function stripMeta(node) {
|
|
35
|
+
if (node && typeof node === 'object') {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
37
|
+
delete node[CHILD_INDEX];
|
|
38
|
+
for (const k of Object.keys(node)) {
|
|
39
|
+
const v = node[k];
|
|
40
|
+
if (Array.isArray(v))
|
|
41
|
+
v.forEach(stripMeta);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return node;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Build a rows→tree transformer from a nested spec.
|
|
48
|
+
*/
|
|
49
|
+
function DTO(spec, opts = {}) {
|
|
50
|
+
const single = opts.single ?? false;
|
|
51
|
+
function upsertAtLevel(rowsIndex, rowsOut, levelSpec, row) {
|
|
52
|
+
const nodeId = row[levelSpec.id];
|
|
53
|
+
if (nodeId == null)
|
|
54
|
+
return null;
|
|
55
|
+
let node = rowsIndex.get(nodeId);
|
|
56
|
+
if (!node) {
|
|
57
|
+
node = {
|
|
58
|
+
id: nodeId,
|
|
59
|
+
...(levelSpec.name ? { name: row[levelSpec.name] } : {}),
|
|
60
|
+
...copyPicks(row, levelSpec.picks),
|
|
61
|
+
};
|
|
62
|
+
rowsIndex.set(nodeId, node);
|
|
63
|
+
rowsOut.push(node);
|
|
64
|
+
}
|
|
65
|
+
return node;
|
|
66
|
+
}
|
|
67
|
+
function descend(parent, levelSpec, row) {
|
|
68
|
+
for (const [collectionKey, childSpec] of childCollections(levelSpec)) {
|
|
69
|
+
const childId = row[childSpec.id];
|
|
70
|
+
if (childId == null)
|
|
71
|
+
continue; // skip this branch for this row
|
|
72
|
+
const { arr, index } = ensureChildInfra(parent, collectionKey);
|
|
73
|
+
let child = index.get(childId);
|
|
74
|
+
if (!child) {
|
|
75
|
+
child = {
|
|
76
|
+
id: childId,
|
|
77
|
+
...(childSpec.name ? { name: row[childSpec.name] } : {}),
|
|
78
|
+
...copyPicks(row, childSpec.picks),
|
|
79
|
+
};
|
|
80
|
+
index.set(childId, child);
|
|
81
|
+
arr.push(child);
|
|
82
|
+
}
|
|
83
|
+
// Recurse further for this child (it may host its own collections)
|
|
84
|
+
descend(child, childSpec, row);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return function transform(rows) {
|
|
88
|
+
if (!rows || rows.length === 0)
|
|
89
|
+
return single ? null : [];
|
|
90
|
+
const roots = [];
|
|
91
|
+
const rootIndex = new Map();
|
|
92
|
+
for (const row of rows) {
|
|
93
|
+
const root = upsertAtLevel(rootIndex, roots, spec, row);
|
|
94
|
+
if (!root)
|
|
95
|
+
continue;
|
|
96
|
+
descend(root, spec, row);
|
|
97
|
+
}
|
|
98
|
+
roots.forEach(stripMeta);
|
|
99
|
+
return single ? roots[0] ?? null : roots;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=dto.js.map
|
|
@@ -0,0 +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"]}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -427,6 +427,53 @@ if (!Array.isArray(pgWrite) && pgWrite.returning) {
|
|
|
427
427
|
}
|
|
428
428
|
```
|
|
429
429
|
|
|
430
|
+
## DTO Helper
|
|
431
|
+
|
|
432
|
+
Turn flat SQL rows into nested objects using a simple spec and the `@Transform` decorator.
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
import { DTO, Transform, Query, QueryBinder } from 'sqlstack';
|
|
436
|
+
|
|
437
|
+
// Define a nesting spec using column names
|
|
438
|
+
const toMuscleGroup = DTO(
|
|
439
|
+
{
|
|
440
|
+
id: 'group_id',
|
|
441
|
+
name: 'group_name',
|
|
442
|
+
muscles: {
|
|
443
|
+
id: 'muscle_id',
|
|
444
|
+
name: 'muscle_name',
|
|
445
|
+
exercises: {
|
|
446
|
+
id: 'exercise_id',
|
|
447
|
+
name: 'exercise_name',
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
{ single: true } // default returns an array; set { single: true } to return a single root (or null)
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
@QueryBinder()
|
|
455
|
+
class Repo {
|
|
456
|
+
@Transform(toMuscleGroup)
|
|
457
|
+
@Query({ sql: `
|
|
458
|
+
SELECT
|
|
459
|
+
g.id AS group_id, g.name AS group_name,
|
|
460
|
+
m.id AS muscle_id, m.name AS muscle_name,
|
|
461
|
+
e.id AS exercise_id,e.name AS exercise_name
|
|
462
|
+
FROM groups g
|
|
463
|
+
LEFT JOIN muscles m ON m.group_id = g.id
|
|
464
|
+
LEFT JOIN exercises e ON e.muscle_id = m.id
|
|
465
|
+
WHERE g.id = :id
|
|
466
|
+
ORDER BY m.name, e.name
|
|
467
|
+
` })
|
|
468
|
+
async getGroup(_a: { id: string }): Promise<any | null> { throw new Error('replaced'); }
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
- Arbitrary depth: nest collections by adding objects under new keys.
|
|
473
|
+
- Dedupe: nodes are de-duplicated by their `id` at each level.
|
|
474
|
+
- Null-safe: branches with `NULL` child ids are skipped for that row.
|
|
475
|
+
- Order: sibling order follows your SQL `ORDER BY`.
|
|
476
|
+
|
|
430
477
|
## Underlying Connections
|
|
431
478
|
|
|
432
479
|
Every `Database` exposes its driver via `conn` so you can drop down to the raw client when needed:
|