sqlstack 3.0.0 → 3.2.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.
Files changed (79) hide show
  1. package/dist/cjs/adapters/pg.d.ts.map +1 -1
  2. package/dist/cjs/adapters/pg.js +13 -9
  3. package/dist/cjs/adapters/pg.js.map +1 -1
  4. package/dist/cjs/adapters/sqlite.d.ts.map +1 -1
  5. package/dist/cjs/adapters/sqlite.js +15 -8
  6. package/dist/cjs/adapters/sqlite.js.map +1 -1
  7. package/dist/cjs/core/errors.d.ts +5 -0
  8. package/dist/cjs/core/errors.d.ts.map +1 -1
  9. package/dist/cjs/core/errors.js +15 -1
  10. package/dist/cjs/core/errors.js.map +1 -1
  11. package/dist/cjs/decorators/query.d.ts.map +1 -1
  12. package/dist/cjs/decorators/query.js +255 -241
  13. package/dist/cjs/decorators/query.js.map +1 -1
  14. package/dist/cjs/index.d.ts +1 -0
  15. package/dist/cjs/index.d.ts.map +1 -1
  16. package/dist/cjs/index.js +4 -1
  17. package/dist/cjs/index.js.map +1 -1
  18. package/dist/cjs/registry.d.ts +3 -4
  19. package/dist/cjs/registry.d.ts.map +1 -1
  20. package/dist/cjs/registry.js.map +1 -1
  21. package/dist/cjs/run.d.ts +29 -0
  22. package/dist/cjs/run.d.ts.map +1 -0
  23. package/dist/cjs/run.js +38 -0
  24. package/dist/cjs/run.js.map +1 -0
  25. package/dist/cjs/testing/index.d.ts +4 -0
  26. package/dist/cjs/testing/index.d.ts.map +1 -1
  27. package/dist/cjs/testing/index.js +5 -1
  28. package/dist/cjs/testing/index.js.map +1 -1
  29. package/dist/cjs/testing/schema/ddl.d.ts +1 -10
  30. package/dist/cjs/testing/schema/ddl.d.ts.map +1 -1
  31. package/dist/cjs/testing/schema/ddl.js +29 -13
  32. package/dist/cjs/testing/schema/ddl.js.map +1 -1
  33. package/dist/cjs/testing/schema/normalize.js +3 -3
  34. package/dist/cjs/testing/schema/normalize.js.map +1 -1
  35. package/dist/cjs/testing/schema/types.d.ts +1 -20
  36. package/dist/cjs/testing/schema/types.d.ts.map +1 -1
  37. package/dist/cjs/testing/schema/types.js +0 -4
  38. package/dist/cjs/testing/schema/types.js.map +1 -1
  39. package/dist/cjs/testing/sqlite/builder.d.ts +24 -0
  40. package/dist/cjs/testing/sqlite/builder.d.ts.map +1 -0
  41. package/dist/cjs/testing/sqlite/builder.js +134 -0
  42. package/dist/cjs/testing/sqlite/builder.js.map +1 -0
  43. package/dist/cjs/testing/sqlite/database.d.ts +18 -0
  44. package/dist/cjs/testing/sqlite/database.d.ts.map +1 -0
  45. package/dist/cjs/testing/sqlite/database.js +123 -0
  46. package/dist/cjs/testing/sqlite/database.js.map +1 -0
  47. package/dist/cjs/testing/sqlite/loadData.d.ts +7 -0
  48. package/dist/cjs/testing/sqlite/loadData.d.ts.map +1 -0
  49. package/dist/cjs/testing/sqlite/loadData.js +55 -0
  50. package/dist/cjs/testing/sqlite/loadData.js.map +1 -0
  51. package/dist/esm/adapters/pg.js +13 -9
  52. package/dist/esm/adapters/pg.js.map +1 -1
  53. package/dist/esm/adapters/sqlite.js +15 -8
  54. package/dist/esm/adapters/sqlite.js.map +1 -1
  55. package/dist/esm/core/errors.js +13 -0
  56. package/dist/esm/core/errors.js.map +1 -1
  57. package/dist/esm/decorators/query.js +256 -242
  58. package/dist/esm/decorators/query.js.map +1 -1
  59. package/dist/esm/index.js +2 -0
  60. package/dist/esm/index.js.map +1 -1
  61. package/dist/esm/registry.js.map +1 -1
  62. package/dist/esm/run.js +34 -0
  63. package/dist/esm/run.js.map +1 -0
  64. package/dist/esm/testing/index.js +2 -0
  65. package/dist/esm/testing/index.js.map +1 -1
  66. package/dist/esm/testing/schema/ddl.js +29 -13
  67. package/dist/esm/testing/schema/ddl.js.map +1 -1
  68. package/dist/esm/testing/schema/normalize.js +3 -3
  69. package/dist/esm/testing/schema/normalize.js.map +1 -1
  70. package/dist/esm/testing/schema/types.js +0 -4
  71. package/dist/esm/testing/schema/types.js.map +1 -1
  72. package/dist/esm/testing/sqlite/builder.js +98 -0
  73. package/dist/esm/testing/sqlite/builder.js.map +1 -0
  74. package/dist/esm/testing/sqlite/database.js +85 -0
  75. package/dist/esm/testing/sqlite/database.js.map +1 -0
  76. package/dist/esm/testing/sqlite/loadData.js +48 -0
  77. package/dist/esm/testing/sqlite/loadData.js.map +1 -0
  78. package/package.json +4 -3
  79. package/readme.md +6 -8
@@ -0,0 +1,85 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { createSqliteDb } from '../../adapters/sqlite.js';
5
+ import { SqlStackError } from '../../core/errors.js';
6
+ export class TestSqliteError extends SqlStackError {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = 'TestSqliteError';
10
+ }
11
+ }
12
+ /**
13
+ * Temp directories for file-backed handles that were never disposed. Tests are
14
+ * expected to call `dispose()`, but a failed assertion can skip it; this keeps
15
+ * an abandoned directory from outliving the worker process.
16
+ */
17
+ const abandonedDirectories = new Set();
18
+ let exitCleanupRegistered = false;
19
+ function trackDirectory(directory) {
20
+ abandonedDirectories.add(directory);
21
+ if (exitCleanupRegistered)
22
+ return;
23
+ exitCleanupRegistered = true;
24
+ process.once('exit', () => {
25
+ for (const dir of abandonedDirectories) {
26
+ try {
27
+ fs.rmSync(dir, { recursive: true, force: true });
28
+ }
29
+ catch {
30
+ /* best-effort cleanup */
31
+ }
32
+ }
33
+ });
34
+ }
35
+ export async function createDatabaseHandle(fileBacked) {
36
+ let directory;
37
+ let filename = ':memory:';
38
+ if (fileBacked) {
39
+ directory = fs.mkdtempSync(path.join(os.tmpdir(), 'sqlstack-test-'));
40
+ filename = path.join(directory, 'database.sqlite');
41
+ trackDirectory(directory);
42
+ }
43
+ let db;
44
+ try {
45
+ db = await createSqliteDb(filename);
46
+ }
47
+ catch (err) {
48
+ if (directory) {
49
+ fs.rmSync(directory, { recursive: true, force: true });
50
+ abandonedDirectories.delete(directory);
51
+ }
52
+ const message = err.message;
53
+ if (message.includes('No SQLite driver available')) {
54
+ throw new TestSqliteError('testSqlite requires the optional peer "better-sqlite3".');
55
+ }
56
+ throw new TestSqliteError(`Failed to create SQLite test database: ${message}`);
57
+ }
58
+ let disposed = false;
59
+ return {
60
+ db,
61
+ conn: db.conn,
62
+ path: fileBacked ? filename : undefined,
63
+ async query(sql, params = []) {
64
+ if (disposed)
65
+ throw new TestSqliteError('Test SQLite database has been disposed.');
66
+ return db.query(sql, [...params]);
67
+ },
68
+ async dispose() {
69
+ if (disposed)
70
+ return;
71
+ disposed = true;
72
+ try {
73
+ await db.close();
74
+ }
75
+ catch {
76
+ /* connection may already be closed */
77
+ }
78
+ if (directory) {
79
+ fs.rmSync(directory, { recursive: true, force: true });
80
+ abandonedDirectories.delete(directory);
81
+ }
82
+ },
83
+ };
84
+ }
85
+ //# sourceMappingURL=database.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.js","sourceRoot":"","sources":["../../../../src/testing/sqlite/database.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { createSqliteDb } from '../../adapters/sqlite';\nimport { SqlStackError } from '../../core/errors';\nimport type { Database } from '../../registry';\n\nexport class TestSqliteError extends SqlStackError {\n constructor(message: string) {\n super(message);\n this.name = 'TestSqliteError';\n }\n}\n\nexport interface TestSqliteDatabase {\n /** The live sqlstack SQLite adapter, suitable for direct injection. */\n readonly db: Database;\n /** The underlying better-sqlite3 (or sqlite3) connection. */\n readonly conn: unknown;\n /** Absolute database path in file mode; undefined for :memory:. */\n readonly path?: string;\n query(sql: string, params?: readonly unknown[]): Promise<unknown>;\n /** Close the connection and remove owned temporary files. Idempotent. */\n dispose(): Promise<void>;\n}\n\n/**\n * Temp directories for file-backed handles that were never disposed. Tests are\n * expected to call `dispose()`, but a failed assertion can skip it; this keeps\n * an abandoned directory from outliving the worker process.\n */\nconst abandonedDirectories = new Set<string>();\nlet exitCleanupRegistered = false;\n\nfunction trackDirectory(directory: string): void {\n abandonedDirectories.add(directory);\n if (exitCleanupRegistered) return;\n exitCleanupRegistered = true;\n process.once('exit', () => {\n for (const dir of abandonedDirectories) {\n try {\n fs.rmSync(dir, { recursive: true, force: true });\n } catch {\n /* best-effort cleanup */\n }\n }\n });\n}\n\nexport async function createDatabaseHandle(\n fileBacked: boolean\n): Promise<TestSqliteDatabase> {\n let directory: string | undefined;\n let filename = ':memory:';\n if (fileBacked) {\n directory = fs.mkdtempSync(path.join(os.tmpdir(), 'sqlstack-test-'));\n filename = path.join(directory, 'database.sqlite');\n trackDirectory(directory);\n }\n\n let db: Database;\n try {\n db = await createSqliteDb(filename);\n } catch (err) {\n if (directory) {\n fs.rmSync(directory, { recursive: true, force: true });\n abandonedDirectories.delete(directory);\n }\n const message = (err as Error).message;\n if (message.includes('No SQLite driver available')) {\n throw new TestSqliteError('testSqlite requires the optional peer \"better-sqlite3\".');\n }\n throw new TestSqliteError(`Failed to create SQLite test database: ${message}`);\n }\n\n let disposed = false;\n return {\n db,\n conn: db.conn,\n path: fileBacked ? filename : undefined,\n async query(sql: string, params: readonly unknown[] = []) {\n if (disposed) throw new TestSqliteError('Test SQLite database has been disposed.');\n return db.query(sql, [...params]);\n },\n async dispose() {\n if (disposed) return;\n disposed = true;\n try {\n await db.close();\n } catch {\n /* connection may already be closed */\n }\n if (directory) {\n fs.rmSync(directory, { recursive: true, force: true });\n abandonedDirectories.delete(directory);\n }\n },\n };\n}\n"]}
@@ -0,0 +1,48 @@
1
+ import { quoteIdent } from '../schema/ddl.js';
2
+ import { TestDataError, composeRows, planInsertOrder, toOperations, } from '../postgres/loadData.js';
3
+ export { TestDataError, composeRows, planInsertOrder, toOperations };
4
+ export async function insertRows(query, schema, effective) {
5
+ const order = planInsertOrder(schema, [...effective.keys()]);
6
+ const declared = new Map(schema.tables.map((table) => [
7
+ table.name,
8
+ table.columns.map((column) => column.name),
9
+ ]));
10
+ for (const tableName of order) {
11
+ const rows = effective.get(tableName);
12
+ const columnOrder = declared.get(tableName);
13
+ const batches = new Map();
14
+ for (const row of rows) {
15
+ const columns = columnOrder.filter((column) => row[column] !== undefined);
16
+ const signature = columns.join(',');
17
+ const batch = batches.get(signature) ?? { columns, rows: [] };
18
+ batch.rows.push(row);
19
+ batches.set(signature, batch);
20
+ }
21
+ for (const { columns, rows: batchRows } of batches.values()) {
22
+ if (columns.length === 0) {
23
+ for (let i = 0; i < batchRows.length; i++) {
24
+ await run(query, tableName, `INSERT INTO ${quoteIdent(tableName)} DEFAULT VALUES`, []);
25
+ }
26
+ continue;
27
+ }
28
+ const params = [];
29
+ const tuples = batchRows.map((row) => {
30
+ const placeholders = columns.map((column) => {
31
+ params.push(row[column]);
32
+ return '?';
33
+ });
34
+ return `(${placeholders.join(', ')})`;
35
+ });
36
+ await run(query, tableName, `INSERT INTO ${quoteIdent(tableName)} (${columns.map(quoteIdent).join(', ')}) VALUES ${tuples.join(', ')}`, params);
37
+ }
38
+ }
39
+ }
40
+ async function run(query, tableName, sql, params) {
41
+ try {
42
+ await query(sql, params);
43
+ }
44
+ catch (err) {
45
+ throw new TestDataError(`Failed to insert scenario rows into table "${tableName}": ${err.message}`);
46
+ }
47
+ }
48
+ //# sourceMappingURL=loadData.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadData.js","sourceRoot":"","sources":["../../../../src/testing/sqlite/loadData.ts"],"names":[],"mappings":"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","sourcesContent":["import { quoteIdent } from '../schema/ddl';\nimport type { NormalizedTestSchema } from '../schema/types';\nimport {\n TestDataError,\n composeRows,\n planInsertOrder,\n toOperations,\n} from '../postgres/loadData';\nimport type { DataOperation, Row } from '../postgres/loadData';\n\nexport { TestDataError, composeRows, planInsertOrder, toOperations };\nexport type { DataOperation, Row };\n\nexport async function insertRows(\n query: (sql: string, params: unknown[]) => Promise<unknown>,\n schema: NormalizedTestSchema,\n effective: Map<string, Row[]>\n): Promise<void> {\n const order = planInsertOrder(schema, [...effective.keys()]);\n const declared = new Map(schema.tables.map((table) => [\n table.name,\n table.columns.map((column) => column.name),\n ]));\n for (const tableName of order) {\n const rows = effective.get(tableName)!;\n const columnOrder = declared.get(tableName)!;\n const batches = new Map<string, { columns: string[]; rows: Row[] }>();\n for (const row of rows) {\n const columns = columnOrder.filter((column) => row[column] !== undefined);\n const signature = columns.join(',');\n const batch = batches.get(signature) ?? { columns, rows: [] };\n batch.rows.push(row);\n batches.set(signature, batch);\n }\n for (const { columns, rows: batchRows } of batches.values()) {\n if (columns.length === 0) {\n for (let i = 0; i < batchRows.length; i++) {\n await run(query, tableName, `INSERT INTO ${quoteIdent(tableName)} DEFAULT VALUES`, []);\n }\n continue;\n }\n const params: unknown[] = [];\n const tuples = batchRows.map((row) => {\n const placeholders = columns.map((column) => {\n params.push(row[column]);\n return '?';\n });\n return `(${placeholders.join(', ')})`;\n });\n await run(\n query,\n tableName,\n `INSERT INTO ${quoteIdent(tableName)} (${columns.map(quoteIdent).join(', ')}) VALUES ${tuples.join(', ')}`,\n params\n );\n }\n }\n}\n\nasync function run(\n query: (sql: string, params: unknown[]) => Promise<unknown>,\n tableName: string,\n sql: string,\n params: unknown[]\n): Promise<void> {\n try {\n await query(sql, params);\n } catch (err) {\n throw new TestDataError(\n `Failed to insert scenario rows into table \"${tableName}\": ${(err as Error).message}`\n );\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqlstack",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -56,6 +56,7 @@
56
56
  "build:cjs": "tsc -p tsconfig.json",
57
57
  "build:esm": "tsc -p tsconfig.esm.json && node scripts/fix-esm-extensions.js && node scripts/create-esm-package.cjs",
58
58
  "test": "jest",
59
+ "typecheck": "tsc -p tsconfig.json --noEmit",
59
60
  "test:watch": "jest --watch",
60
61
  "clean": "rimraf dist",
61
62
  "test:integration": "jest -c jest.integration.config.ts",
@@ -70,7 +71,7 @@
70
71
  "db:logs:mysql": "docker logs -f sqlstack-mysql"
71
72
  },
72
73
  "devDependencies": {
73
- "@noego/ioc": "file:../ioc",
74
+ "@noego/ioc": "^0.6.1",
74
75
  "@types/jest": "^29.5.13",
75
76
  "better-sqlite3": "^12.3.0",
76
77
  "jest": "^29.7.0",
@@ -88,7 +89,7 @@
88
89
  "yaml": "^2.9.0"
89
90
  },
90
91
  "peerDependencies": {
91
- "@noego/ioc": "*",
92
+ "@noego/ioc": ">=0.6.1",
92
93
  "better-sqlite3": "*",
93
94
  "mysql2": "*",
94
95
  "pg": "*",
package/readme.md CHANGED
@@ -828,16 +828,14 @@ const result: WriteResult = await db.query(
828
828
  console.log(result.rowsAffected); // number
829
829
  ```
830
830
 
831
- **Postgres with RETURNING:**
831
+ **With RETURNING (Postgres, SQLite ≥ 3.35):** the statement produces a result set, so it returns rows exactly like a `SELECT` — and `@Single` / `@Only` / `@Exist` / `@None` apply to it. The affected count is `rows.length`.
832
832
 
833
833
  ```ts
834
- const pgResult = await db.query(
834
+ const rows = await db.query(
835
835
  'INSERT INTO users(id, email) VALUES($1, $2) RETURNING id',
836
836
  ['u9', 'nine@example.com']
837
837
  );
838
- if (pgResult.returning) {
839
- console.log(pgResult.returning[0].id); // 'u9'
840
- }
838
+ console.log(rows[0].id); // 'u9'
841
839
  ```
842
840
 
843
841
  Type definition:
@@ -847,11 +845,11 @@ type WriteResult = {
847
845
  lastInsertId?: unknown;
848
846
  };
849
847
 
850
- type PostgresWriteResult = WriteResult & {
851
- returning?: Row[];
852
- };
848
+ type QueryResult = Row[] | WriteResult;
853
849
  ```
854
850
 
851
+ A result-shape decorator on a method whose statement produces no result set (e.g. `@Single` on a plain `UPDATE`) throws `ExpectedRowsError` rather than returning the write result.
852
+
855
853
  ### Evolving Queries Without Breaking Callers
856
854
 
857
855
  Your method signature is the contract; the SQL is an implementation detail.