sqlstack 1.0.2 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/decorators/query.ts"],"names":[],"mappings":"AAOA,KAAK,YAAY,GAAG;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;CAC3C,CAAC;AAEF,wBAAgB,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,YAAY,IAC9B,SAAS,GAAG,EAAE,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,kBAAkB,wBAiF/E"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/decorators/query.ts"],"names":[],"mappings":"AAOA,KAAK,YAAY,GAAG;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;CAC3C,CAAC;AAEF,wBAAgB,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,YAAY,IAC9B,SAAS,GAAG,EAAE,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,kBAAkB,wBA2E/E"}
@@ -57,13 +57,7 @@ function Query(arg) {
57
57
  // 6) If rows were returned (SELECT/CTE), shape + transform + validate
58
58
  if (Array.isArray(out)) {
59
59
  const shaped = applyShape(out, labels.shape);
60
- const transformed = labels.transform
61
- ? Array.isArray(shaped)
62
- ? shaped.map((r) => labels.transform(r))
63
- : shaped !== null && shaped !== undefined
64
- ? labels.transform(shaped)
65
- : shaped
66
- : shaped;
60
+ const transformed = labels.transform ? labels.transform(shaped) : shaped;
67
61
  // 7) validate result via JSON Schema, if provided
68
62
  if (labels.validateSchema && !(0, validator_1.isValidationDisabled)()) {
69
63
  if (Array.isArray(transformed)) {
@@ -1 +1 @@
1
- {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/decorators/query.ts"],"names":[],"mappings":"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","sourcesContent":["import { getClassLabels, getMethodLabels, setMethodLabels } from '../core/metadata';\nimport { SqlStackError, ExactlyOneRowError, NoRowsError, RowsExistError } from '../core/errors';\nimport { isValidationDisabled, validateOutput } from '../core/validator';\nimport { SqlStackDB } from '../registry';\nimport { createSqliteBinder, createMysqlBinder, createPostgresBinder } from '../binders/sqlBinder';\nimport { coerceRootPath, guessDecoratorDir, resolveSqlFromFs } from '../core/fileResolution';\n\ntype QueryOptions = {\n sql?: string; // optional inline SQL for testing; otherwise, future: load from file\n db?: string; // optional override DB name\n dialect?: 'sqlite' | 'mysql' | 'postgres';\n};\n\nexport function Query(arg?: string | QueryOptions) {\n return function (_target: any, prop: string | symbol, desc: PropertyDescriptor) {\n const options: QueryOptions | undefined = typeof arg === 'string' ? undefined : arg;\n const rootArg: string | undefined = typeof arg === 'string' ? arg : undefined;\n // Store method-level db override if provided\n if (options?.db) {\n setMethodLabels(_target, prop, { db: options.db });\n }\n // Store inline SQL if provided\n if (options?.sql) {\n setMethodLabels(_target, prop, { source: { kind: 'inline', sql: options.sql } });\n } else {\n // FS-based resolution\n const { rootDir } = coerceRootPath(rootArg);\n const dir = rootDir ?? guessDecoratorDir();\n if (dir) {\n setMethodLabels(_target, prop, { source: { kind: 'fs', rootDir: dir } });\n }\n }\n\n const original = desc.value;\n desc.value = async function (...callArgs: any[]) {\n const labels = getMethodLabels(this, prop);\n const classLabels = getClassLabels(this);\n\n // 1) prepare args with defaults\n const rawInput = callArgs.length === 1 ? callArgs[0] : callArgs; // object or positional array\n\n // 2) choose database and dialect\n const dbName = labels.db ?? options?.db ?? classLabels?.db;\n const db = SqlStackDB.get(dbName);\n const dialect = options?.dialect ?? db.dialect ?? 'sqlite';\n\n // 3) load SQL source (inline or fs)\n const sql = labels?.source?.kind === 'inline'\n ? labels.source.sql\n : labels?.source?.kind === 'fs'\n ? resolveSqlFromFs(labels.source.rootDir, String(prop), dialect).sql\n : undefined;\n if (!sql) {\n throw new SqlStackError(`No SQL source provided for method ${String(prop)}. Use @Query() with a file/folder path or inline sql.`);\n }\n\n // 4) bind placeholders\n // Decide argument mode: positional if SQL contains :argN\n const hasPositional = /:arg[1-9][0-9]*/.test(sql);\n const args = hasPositional ? callArgs : applyDefaults(labels.defaults, rawInput);\n const binder =\n dialect === 'postgres' ? createPostgresBinder() : dialect === 'mysql' ? createMysqlBinder() : createSqliteBinder();\n const { sql: boundSql, params } = binder.bind(sql, args as any);\n\n // 5) execute query via registry DB\n const out = await db.query(boundSql, params);\n\n // 6) If rows were returned (SELECT/CTE), shape + transform + validate\n if (Array.isArray(out)) {\n const shaped = applyShape(out, labels.shape);\n const transformed = labels.transform\n ? Array.isArray(shaped)\n ? shaped.map((r: any) => labels.transform!(r))\n : shaped !== null && shaped !== undefined\n ? labels.transform!(shaped)\n : shaped\n : shaped;\n\n // 7) validate result via JSON Schema, if provided\n if (labels.validateSchema && !isValidationDisabled()) {\n if (Array.isArray(transformed)) {\n for (const row of transformed) validateOutput(labels.validateSchema, row);\n } else if (transformed !== null && transformed !== undefined) {\n validateOutput(labels.validateSchema, transformed);\n }\n }\n return transformed;\n }\n\n // Non-SELECT: return the write result as-is\n return out;\n };\n\n return desc;\n };\n}\n\nfunction applyDefaults(defaults: Record<string, unknown> | undefined, input: any) {\n if (!defaults) return input;\n if (Array.isArray(input)) return input; // defaults only apply to named-object args\n return { ...defaults, ...(input || {}) };\n}\n\nfunction applyShape<T>(rows: T[], shape?: 'single' | 'only' | 'exist' | 'none'): any {\n switch (shape) {\n case 'single':\n return rows.length > 0 ? rows[0] : null;\n case 'only':\n if (rows.length !== 1) throw new ExactlyOneRowError(rows.length);\n return rows[0];\n case 'exist':\n if (rows.length < 1) throw new NoRowsError();\n return rows[0];\n case 'none':\n if (rows.length > 0) throw new RowsExistError(rows.length);\n return null;\n default:\n return rows;\n }\n}\n"]}
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/decorators/query.ts"],"names":[],"mappings":"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","sourcesContent":["import { getClassLabels, getMethodLabels, setMethodLabels } from '../core/metadata';\nimport { SqlStackError, ExactlyOneRowError, NoRowsError, RowsExistError } from '../core/errors';\nimport { isValidationDisabled, validateOutput } from '../core/validator';\nimport { SqlStackDB } from '../registry';\nimport { createSqliteBinder, createMysqlBinder, createPostgresBinder } from '../binders/sqlBinder';\nimport { coerceRootPath, guessDecoratorDir, resolveSqlFromFs } from '../core/fileResolution';\n\ntype QueryOptions = {\n sql?: string; // optional inline SQL for testing; otherwise, future: load from file\n db?: string; // optional override DB name\n dialect?: 'sqlite' | 'mysql' | 'postgres';\n};\n\nexport function Query(arg?: string | QueryOptions) {\n return function (_target: any, prop: string | symbol, desc: PropertyDescriptor) {\n const options: QueryOptions | undefined = typeof arg === 'string' ? undefined : arg;\n const rootArg: string | undefined = typeof arg === 'string' ? arg : undefined;\n // Store method-level db override if provided\n if (options?.db) {\n setMethodLabels(_target, prop, { db: options.db });\n }\n // Store inline SQL if provided\n if (options?.sql) {\n setMethodLabels(_target, prop, { source: { kind: 'inline', sql: options.sql } });\n } else {\n // FS-based resolution\n const { rootDir } = coerceRootPath(rootArg);\n const dir = rootDir ?? guessDecoratorDir();\n if (dir) {\n setMethodLabels(_target, prop, { source: { kind: 'fs', rootDir: dir } });\n }\n }\n\n const original = desc.value;\n desc.value = async function (...callArgs: any[]) {\n const labels = getMethodLabels(this, prop);\n const classLabels = getClassLabels(this);\n\n // 1) prepare args with defaults\n const rawInput = callArgs.length === 1 ? callArgs[0] : callArgs; // object or positional array\n\n // 2) choose database and dialect\n const dbName = labels.db ?? options?.db ?? classLabels?.db;\n const db = SqlStackDB.get(dbName);\n const dialect = options?.dialect ?? db.dialect ?? 'sqlite';\n\n // 3) load SQL source (inline or fs)\n const sql = labels?.source?.kind === 'inline'\n ? labels.source.sql\n : labels?.source?.kind === 'fs'\n ? resolveSqlFromFs(labels.source.rootDir, String(prop), dialect).sql\n : undefined;\n if (!sql) {\n throw new SqlStackError(`No SQL source provided for method ${String(prop)}. Use @Query() with a file/folder path or inline sql.`);\n }\n\n // 4) bind placeholders\n // Decide argument mode: positional if SQL contains :argN\n const hasPositional = /:arg[1-9][0-9]*/.test(sql);\n const args = hasPositional ? callArgs : applyDefaults(labels.defaults, rawInput);\n const binder =\n dialect === 'postgres' ? createPostgresBinder() : dialect === 'mysql' ? createMysqlBinder() : createSqliteBinder();\n const { sql: boundSql, params } = binder.bind(sql, args as any);\n\n // 5) execute query via registry DB\n const out = await db.query(boundSql, params);\n\n // 6) If rows were returned (SELECT/CTE), shape + transform + validate\n if (Array.isArray(out)) {\n const shaped = applyShape(out, labels.shape);\n const transformed = labels.transform ? labels.transform(shaped) : shaped;\n\n // 7) validate result via JSON Schema, if provided\n if (labels.validateSchema && !isValidationDisabled()) {\n if (Array.isArray(transformed)) {\n for (const row of transformed) validateOutput(labels.validateSchema, row);\n } else if (transformed !== null && transformed !== undefined) {\n validateOutput(labels.validateSchema, transformed);\n }\n }\n return transformed;\n }\n\n // Non-SELECT: return the write result as-is\n return out;\n };\n\n return desc;\n };\n}\n\nfunction applyDefaults(defaults: Record<string, unknown> | undefined, input: any) {\n if (!defaults) return input;\n if (Array.isArray(input)) return input; // defaults only apply to named-object args\n return { ...defaults, ...(input || {}) };\n}\n\nfunction applyShape<T>(rows: T[], shape?: 'single' | 'only' | 'exist' | 'none'): any {\n switch (shape) {\n case 'single':\n return rows.length > 0 ? rows[0] : null;\n case 'only':\n if (rows.length !== 1) throw new ExactlyOneRowError(rows.length);\n return rows[0];\n case 'exist':\n if (rows.length < 1) throw new NoRowsError();\n return rows[0];\n case 'none':\n if (rows.length > 0) throw new RowsExistError(rows.length);\n return null;\n default:\n return rows;\n }\n}\n"]}
@@ -1,2 +1,2 @@
1
- export declare function Transform<TIn = any, TOut = any>(fn: (row: TIn) => TOut): (target: any, prop: string | symbol) => void;
1
+ export declare function Transform<TIn = any, TOut = any>(fn: (value: TIn) => TOut): (target: any, prop: string | symbol) => void;
2
2
  //# sourceMappingURL=transform.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/decorators/transform.ts"],"names":[],"mappings":"AAEA,wBAAgB,SAAS,CAAC,GAAG,GAAG,GAAG,EAAE,IAAI,GAAG,GAAG,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,IACpD,QAAQ,GAAG,EAAE,MAAM,MAAM,GAAG,MAAM,UAGpD"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/decorators/transform.ts"],"names":[],"mappings":"AAGA,wBAAgB,SAAS,CAAC,GAAG,GAAG,GAAG,EAAE,IAAI,GAAG,GAAG,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,IACtD,QAAQ,GAAG,EAAE,MAAM,MAAM,GAAG,MAAM,UAGpD"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Transform = Transform;
4
4
  const metadata_1 = require("../core/metadata");
5
+ // Transform operates on the shaped value (array of rows, single row, or null)
5
6
  function Transform(fn) {
6
7
  return function (target, prop) {
7
8
  (0, metadata_1.setMethodLabels)(target, prop, { transform: fn });
@@ -1 +1 @@
1
- {"version":3,"file":"transform.js","sourceRoot":"","sources":["../../src/decorators/transform.ts"],"names":[],"mappings":";;AAEA,8BAIC;AAND,+CAAmD;AAEnD,SAAgB,SAAS,CAAwB,EAAsB;IACrE,OAAO,UAAU,MAAW,EAAE,IAAqB;QACjD,IAAA,0BAAe,EAAC,MAAM,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,EAAS,EAAE,CAAC,CAAC;IAC1D,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { setMethodLabels } from '../core/metadata';\n\nexport function Transform<TIn = any, TOut = any>(fn: (row: TIn) => TOut) {\n return function (target: any, prop: string | symbol) {\n setMethodLabels(target, prop, { transform: fn as any });\n };\n}\n\n"]}
1
+ {"version":3,"file":"transform.js","sourceRoot":"","sources":["../../src/decorators/transform.ts"],"names":[],"mappings":";;AAGA,8BAIC;AAPD,+CAAmD;AAEnD,8EAA8E;AAC9E,SAAgB,SAAS,CAAwB,EAAwB;IACvE,OAAO,UAAU,MAAW,EAAE,IAAqB;QACjD,IAAA,0BAAe,EAAC,MAAM,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,EAAS,EAAE,CAAC,CAAC;IAC1D,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { setMethodLabels } from '../core/metadata';\n\n// Transform operates on the shaped value (array of rows, single row, or null)\nexport function Transform<TIn = any, TOut = any>(fn: (value: TIn) => TOut) {\n return function (target: any, prop: string | symbol) {\n setMethodLabels(target, prop, { transform: fn as any });\n };\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -9,4 +9,5 @@ export * from './decorators/defaults';
9
9
  export * from './decorators/resultShape';
10
10
  export * from './decorators/validateResult';
11
11
  export * from './decorators/transform';
12
+ export * from './utils/dto';
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -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":";;AAyDA,kBA0DC;AApGD,MAAM,WAAW,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;AAEzC,SAAS,SAAS,CAAC,GAAO,EAAE,KAAgB;IAC1C,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC5C,MAAM,GAAG,GAAO,EAAE,CAAC;IACnB,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACvC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAc;IACtC,MAAM,GAAG,GAA8B,EAAE,CAAC;IAC1C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,OAAO;YAAE,SAAS;QAChE,MAAM,GAAG,GAAI,IAAY,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,IAAI,IAAI,GAAG;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,GAAe,CAAC,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAQ,EAAE,aAAqB;IACvD,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC;QAAE,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC;IACnD,IAAI,CAAE,IAAY,CAAC,WAAW,CAAC;QAAG,IAAY,CAAC,WAAW,CAAC,GAAG,EAAkC,CAAC;IACjG,MAAM,MAAM,GAAI,IAAY,CAAC,WAAW,CAAiC,CAAC;IAC1E,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC;QAAE,MAAM,CAAC,aAAa,CAAC,GAAG,IAAI,GAAG,EAAW,CAAC;IACvE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,aAAa,CAAS,EAAE,KAAK,EAAE,MAAM,CAAC,aAAa,CAAiB,EAAE,CAAC;AAC5F,CAAC;AAED,SAAS,SAAS,CAAC,IAAQ;IACzB,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACrC,gEAAgE;QAChE,OAAQ,IAAY,CAAC,WAAW,CAAC,CAAC;QAClC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;gBAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,SAAgB,GAAG,CAAC,IAAc,EAAE,OAAmB,EAAE;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC;IAGpC,SAAS,aAAa,CAAC,SAAmB,EAAE,OAAa,EAAE,SAAmB,EAAE,GAAO;QACrF,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACjC,IAAI,MAAM,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC;QAEhC,IAAI,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,GAAG;gBACL,EAAE,EAAE,MAAM;gBACV,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACxD,GAAG,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC,KAAK,CAAC;aACnC,CAAC;YACF,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAC5B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,SAAS,OAAO,CAAC,MAAU,EAAE,SAAmB,EAAE,GAAO;QACvD,KAAK,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC,IAAI,gBAAgB,CAAC,SAAS,CAAC,EAAE,CAAC;YACrE,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YAClC,IAAI,OAAO,IAAI,IAAI;gBAAE,SAAS,CAAC,gCAAgC;YAE/D,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,gBAAgB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;YAC/D,IAAI,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,KAAK,GAAG;oBACN,EAAE,EAAE,OAAO;oBACX,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACxD,GAAG,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC,KAAK,CAAC;iBACnC,CAAC;gBACF,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;gBAC1B,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;YAED,mEAAmE;YACnE,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,OAAO,SAAS,SAAS,CAAC,IAAU;QAClC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAE1D,MAAM,KAAK,GAAS,EAAE,CAAC;QACvB,MAAM,SAAS,GAAa,IAAI,GAAG,EAAE,CAAC;QAEtC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,aAAa,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YACxD,IAAI,CAAC,IAAI;gBAAE,SAAS;YACpB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QAC3B,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACzB,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IAC3C,CAAC,CAAC;AACJ,CAAC","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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqlstack",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "",
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: