sqlstack 3.2.0 → 3.4.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 (70) hide show
  1. package/dist/cjs/adapters/sqlite.d.ts.map +1 -1
  2. package/dist/cjs/adapters/sqlite.js +4 -1
  3. package/dist/cjs/adapters/sqlite.js.map +1 -1
  4. package/dist/cjs/index.d.ts +1 -1
  5. package/dist/cjs/index.d.ts.map +1 -1
  6. package/dist/cjs/index.js +2 -1
  7. package/dist/cjs/index.js.map +1 -1
  8. package/dist/cjs/runtime.d.ts.map +1 -1
  9. package/dist/cjs/runtime.js +22 -19
  10. package/dist/cjs/runtime.js.map +1 -1
  11. package/dist/cjs/stack.d.ts +2 -0
  12. package/dist/cjs/stack.d.ts.map +1 -1
  13. package/dist/cjs/stack.js +40 -27
  14. package/dist/cjs/stack.js.map +1 -1
  15. package/dist/cjs/testing/builder.d.ts.map +1 -1
  16. package/dist/cjs/testing/builder.js +14 -5
  17. package/dist/cjs/testing/builder.js.map +1 -1
  18. package/dist/cjs/testing/fixture-provenance.d.ts +3 -0
  19. package/dist/cjs/testing/fixture-provenance.d.ts.map +1 -0
  20. package/dist/cjs/testing/fixture-provenance.js +21 -0
  21. package/dist/cjs/testing/fixture-provenance.js.map +1 -0
  22. package/dist/cjs/testing/index.d.ts +2 -0
  23. package/dist/cjs/testing/index.d.ts.map +1 -1
  24. package/dist/cjs/testing/index.js +5 -1
  25. package/dist/cjs/testing/index.js.map +1 -1
  26. package/dist/cjs/testing/postgres/database.d.ts +9 -1
  27. package/dist/cjs/testing/postgres/database.d.ts.map +1 -1
  28. package/dist/cjs/testing/postgres/database.js +17 -13
  29. package/dist/cjs/testing/postgres/database.js.map +1 -1
  30. package/dist/cjs/testing/resource.d.ts +76 -0
  31. package/dist/cjs/testing/resource.d.ts.map +1 -0
  32. package/dist/cjs/testing/resource.js +117 -0
  33. package/dist/cjs/testing/resource.js.map +1 -0
  34. package/dist/cjs/testing/sqlite/builder.d.ts.map +1 -1
  35. package/dist/cjs/testing/sqlite/builder.js +10 -2
  36. package/dist/cjs/testing/sqlite/builder.js.map +1 -1
  37. package/dist/cjs/testing/sqlite/database.d.ts +11 -1
  38. package/dist/cjs/testing/sqlite/database.d.ts.map +1 -1
  39. package/dist/cjs/testing/sqlite/database.js +32 -18
  40. package/dist/cjs/testing/sqlite/database.js.map +1 -1
  41. package/dist/cjs/transactions.d.ts +6 -0
  42. package/dist/cjs/transactions.d.ts.map +1 -1
  43. package/dist/cjs/transactions.js +9 -0
  44. package/dist/cjs/transactions.js.map +1 -1
  45. package/dist/esm/adapters/sqlite.js +4 -1
  46. package/dist/esm/adapters/sqlite.js.map +1 -1
  47. package/dist/esm/index.js +1 -1
  48. package/dist/esm/index.js.map +1 -1
  49. package/dist/esm/runtime.js +22 -19
  50. package/dist/esm/runtime.js.map +1 -1
  51. package/dist/esm/stack.js +40 -27
  52. package/dist/esm/stack.js.map +1 -1
  53. package/dist/esm/testing/builder.js +14 -5
  54. package/dist/esm/testing/builder.js.map +1 -1
  55. package/dist/esm/testing/fixture-provenance.js +15 -0
  56. package/dist/esm/testing/fixture-provenance.js.map +1 -0
  57. package/dist/esm/testing/index.js +1 -0
  58. package/dist/esm/testing/index.js.map +1 -1
  59. package/dist/esm/testing/postgres/database.js +17 -13
  60. package/dist/esm/testing/postgres/database.js.map +1 -1
  61. package/dist/esm/testing/resource.js +106 -0
  62. package/dist/esm/testing/resource.js.map +1 -0
  63. package/dist/esm/testing/sqlite/builder.js +10 -2
  64. package/dist/esm/testing/sqlite/builder.js.map +1 -1
  65. package/dist/esm/testing/sqlite/database.js +32 -18
  66. package/dist/esm/testing/sqlite/database.js.map +1 -1
  67. package/dist/esm/transactions.js +8 -0
  68. package/dist/esm/transactions.js.map +1 -1
  69. package/package.json +2 -2
  70. package/readme.md +3 -0
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.assertFixtureSqlFile = assertFixtureSqlFile;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const node_fs_1 = require("node:fs");
9
+ const errors_1 = require("../core/errors");
10
+ /** File provenance guard; inline SQL still requires architecture/source review. */
11
+ function assertFixtureSqlFile(file) {
12
+ const resolved = (0, node_fs_1.realpathSync)(file);
13
+ for (const candidate of [file, resolved]) {
14
+ const segments = candidate.split(/[\\/]/);
15
+ if (segments.some(segment => /^migrations?$/i.test(segment))
16
+ || /\.(up|down)\.(sql|[cm]?js|ts)$/i.test(node_path_1.default.basename(candidate))) {
17
+ throw new errors_1.SqlStackError(`Application fixtures cannot read migration file ${file}; use explicit schema/plain data and keep migration transitions in @noego/proper/testing.`);
18
+ }
19
+ }
20
+ }
21
+ //# sourceMappingURL=fixture-provenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture-provenance.js","sourceRoot":"","sources":["../../../src/testing/fixture-provenance.ts"],"names":[],"mappings":";;;;;AAKA,oDAWC;AAhBD,0DAA6B;AAC7B,qCAAuC;AACvC,2CAA+C;AAE/C,mFAAmF;AACnF,SAAgB,oBAAoB,CAAC,IAAY;IAC/C,MAAM,QAAQ,GAAG,IAAA,sBAAY,EAAC,IAAI,CAAC,CAAC;IACpC,KAAK,MAAM,SAAS,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;eACvD,iCAAiC,CAAC,IAAI,CAAC,mBAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;YACtE,MAAM,IAAI,sBAAa,CACrB,mDAAmD,IAAI,2FAA2F,CACnJ,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import path from 'node:path';\nimport { realpathSync } from 'node:fs';\nimport { SqlStackError } from '../core/errors';\n\n/** File provenance guard; inline SQL still requires architecture/source review. */\nexport function assertFixtureSqlFile(file: string): void {\n const resolved = realpathSync(file);\n for (const candidate of [file, resolved]) {\n const segments = candidate.split(/[\\\\/]/);\n if (segments.some(segment => /^migrations?$/i.test(segment))\n || /\\.(up|down)\\.(sql|[cm]?js|ts)$/i.test(path.basename(candidate))) {\n throw new SqlStackError(\n `Application fixtures cannot read migration file ${file}; use explicit schema/plain data and keep migration transitions in @noego/proper/testing.`,\n );\n }\n }\n}\n"]}
@@ -5,6 +5,8 @@ export { testSqlite } from './sqlite/builder';
5
5
  export type { TestSqliteBuilder, TestSqliteOptions } from './sqlite/builder';
6
6
  export { TestSqliteError } from './sqlite/database';
7
7
  export type { TestSqliteDatabase } from './sqlite/database';
8
+ export { isDisposableTestDatabase, TestDatabaseDisposeError, TestDatabaseBuildError } from './resource';
9
+ export type { DisposableTestDatabase } from './resource';
8
10
  export { parseTestSchema } from './schema/parser';
9
11
  export { normalizeTestSchema, TestSchemaError } from './schema/normalize';
10
12
  export { generateDdl } from './schema/ddl';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAC1E,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,YAAY,EACV,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,sBAAsB,GACvB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAC1E,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACxG,YAAY,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,YAAY,EACV,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,sBAAsB,GACvB,MAAM,gBAAgB,CAAC"}
@@ -1,12 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_ADMIN_URL = exports.TestPostgresServerError = exports.TestDataError = exports.generateDdl = exports.TestSchemaError = exports.normalizeTestSchema = exports.parseTestSchema = exports.TestSqliteError = exports.testSqlite = exports.testPostgres = void 0;
3
+ exports.DEFAULT_ADMIN_URL = exports.TestPostgresServerError = exports.TestDataError = exports.generateDdl = exports.TestSchemaError = exports.normalizeTestSchema = exports.parseTestSchema = exports.TestDatabaseBuildError = exports.TestDatabaseDisposeError = exports.isDisposableTestDatabase = exports.TestSqliteError = exports.testSqlite = exports.testPostgres = void 0;
4
4
  var builder_1 = require("./builder");
5
5
  Object.defineProperty(exports, "testPostgres", { enumerable: true, get: function () { return builder_1.testPostgres; } });
6
6
  var builder_2 = require("./sqlite/builder");
7
7
  Object.defineProperty(exports, "testSqlite", { enumerable: true, get: function () { return builder_2.testSqlite; } });
8
8
  var database_1 = require("./sqlite/database");
9
9
  Object.defineProperty(exports, "TestSqliteError", { enumerable: true, get: function () { return database_1.TestSqliteError; } });
10
+ var resource_1 = require("./resource");
11
+ Object.defineProperty(exports, "isDisposableTestDatabase", { enumerable: true, get: function () { return resource_1.isDisposableTestDatabase; } });
12
+ Object.defineProperty(exports, "TestDatabaseDisposeError", { enumerable: true, get: function () { return resource_1.TestDatabaseDisposeError; } });
13
+ Object.defineProperty(exports, "TestDatabaseBuildError", { enumerable: true, get: function () { return resource_1.TestDatabaseBuildError; } });
10
14
  var parser_1 = require("./schema/parser");
11
15
  Object.defineProperty(exports, "parseTestSchema", { enumerable: true, get: function () { return parser_1.parseTestSchema; } });
12
16
  var normalize_1 = require("./schema/normalize");
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/testing/index.ts"],"names":[],"mappings":";;;AAAA,qCAAyC;AAAhC,uGAAA,YAAY,OAAA;AAGrB,4CAA8C;AAArC,qGAAA,UAAU,OAAA;AAEnB,8CAAoD;AAA3C,2GAAA,eAAe,OAAA;AAExB,0CAAkD;AAAzC,yGAAA,eAAe,OAAA;AACxB,gDAA0E;AAAjE,gHAAA,mBAAmB,OAAA;AAAE,4GAAA,eAAe,OAAA;AAC7C,oCAA2C;AAAlC,kGAAA,WAAW,OAAA;AACpB,gDAAoD;AAA3C,yGAAA,aAAa,OAAA;AACtB,4CAA+E;AAAtE,iHAAA,uBAAuB,OAAA;AAAE,2GAAA,iBAAiB,OAAA","sourcesContent":["export { testPostgres } from './builder';\nexport type { TestPostgresBuilder, TestPostgresOptions } from './builder';\nexport type { TestPostgresDatabase } from './postgres/database';\nexport { testSqlite } from './sqlite/builder';\nexport type { TestSqliteBuilder, TestSqliteOptions } from './sqlite/builder';\nexport { TestSqliteError } from './sqlite/database';\nexport type { TestSqliteDatabase } from './sqlite/database';\nexport { parseTestSchema } from './schema/parser';\nexport { normalizeTestSchema, TestSchemaError } from './schema/normalize';\nexport { generateDdl } from './schema/ddl';\nexport { TestDataError } from './postgres/loadData';\nexport { TestPostgresServerError, DEFAULT_ADMIN_URL } from './postgres/server';\nexport type {\n NormalizedTestSchema,\n NormalizedTable,\n NormalizedColumn,\n RawSqlEntry,\n ParseTestSchemaOptions,\n} from './schema/types';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/testing/index.ts"],"names":[],"mappings":";;;AAAA,qCAAyC;AAAhC,uGAAA,YAAY,OAAA;AAGrB,4CAA8C;AAArC,qGAAA,UAAU,OAAA;AAEnB,8CAAoD;AAA3C,2GAAA,eAAe,OAAA;AAExB,uCAAwG;AAA/F,oHAAA,wBAAwB,OAAA;AAAE,oHAAA,wBAAwB,OAAA;AAAE,kHAAA,sBAAsB,OAAA;AAEnF,0CAAkD;AAAzC,yGAAA,eAAe,OAAA;AACxB,gDAA0E;AAAjE,gHAAA,mBAAmB,OAAA;AAAE,4GAAA,eAAe,OAAA;AAC7C,oCAA2C;AAAlC,kGAAA,WAAW,OAAA;AACpB,gDAAoD;AAA3C,yGAAA,aAAa,OAAA;AACtB,4CAA+E;AAAtE,iHAAA,uBAAuB,OAAA;AAAE,2GAAA,iBAAiB,OAAA","sourcesContent":["export { testPostgres } from './builder';\nexport type { TestPostgresBuilder, TestPostgresOptions } from './builder';\nexport type { TestPostgresDatabase } from './postgres/database';\nexport { testSqlite } from './sqlite/builder';\nexport type { TestSqliteBuilder, TestSqliteOptions } from './sqlite/builder';\nexport { TestSqliteError } from './sqlite/database';\nexport type { TestSqliteDatabase } from './sqlite/database';\nexport { isDisposableTestDatabase, TestDatabaseDisposeError, TestDatabaseBuildError } from './resource';\nexport type { DisposableTestDatabase } from './resource';\nexport { parseTestSchema } from './schema/parser';\nexport { normalizeTestSchema, TestSchemaError } from './schema/normalize';\nexport { generateDdl } from './schema/ddl';\nexport { TestDataError } from './postgres/loadData';\nexport { TestPostgresServerError, DEFAULT_ADMIN_URL } from './postgres/server';\nexport type {\n NormalizedTestSchema,\n NormalizedTable,\n NormalizedColumn,\n RawSqlEntry,\n ParseTestSchemaOptions,\n} from './schema/types';\n"]}
@@ -1,10 +1,18 @@
1
1
  import { TestServerOptions } from './server';
2
2
  /** The built test database handle. */
3
3
  export interface TestPostgresDatabase {
4
+ /** Engine discriminator for SQLStack-minted disposable test databases. */
5
+ readonly engine: 'postgres';
6
+ /** Fresh per-allocation identifier; never reused within a process. */
7
+ readonly resourceId: string;
4
8
  /** Normal PostgreSQL connection string identifying only this database. */
5
9
  readonly url: string;
6
10
  query(sql: string, params?: readonly unknown[]): Promise<unknown>;
7
- /** Close helper connections and drop the owned database. Idempotent. */
11
+ /**
12
+ * Close helper connections and drop the owned database. Idempotent:
13
+ * concurrent and repeated calls share one outcome. Every cleanup step is
14
+ * attempted; failures are aggregated into a `TestDatabaseDisposeError`.
15
+ */
8
16
  dispose(): Promise<void>;
9
17
  }
10
18
  export interface OwnedDatabase {
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../../src/testing/postgres/database.ts"],"names":[],"mappings":"AACA,OAAO,EAAmC,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE9E,sCAAsC;AACtC,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,wEAAwE;IACxE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,aAAa,EACpB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,oBAAoB,GAAG;IAAE,KAAK,EAAE,GAAG,CAAA;CAAE,CAAC,CA4BhD"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../../src/testing/postgres/database.ts"],"names":[],"mappings":"AACA,OAAO,EAAmC,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG9E,sCAAsC;AACtC,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,sEAAsE;IACtE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,0EAA0E;IAC1E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE;;;;OAIG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,aAAa,EACpB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,oBAAoB,GAAG;IAAE,KAAK,EAAE,GAAG,CAAA;CAAE,CAAC,CAkChD"}
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createDatabaseHandle = createDatabaseHandle;
4
4
  const allocator_1 = require("./allocator");
5
5
  const server_1 = require("./server");
6
+ const resource_1 = require("../resource");
6
7
  /**
7
8
  * Connect a helper pool to an allocated database and produce the handle.
8
9
  * The handle owns exactly this database and drops it on dispose.
@@ -11,29 +12,32 @@ async function createDatabaseHandle(owned, options = {}) {
11
12
  const pg = await (0, server_1.loadPg)();
12
13
  const Pool = pg.Pool ?? pg.default?.Pool;
13
14
  const pool = new Pool({ connectionString: owned.url, max: 4 });
14
- let disposed = false;
15
- return {
15
+ const resourceId = (0, resource_1.mintResourceId)('postgres');
16
+ let disposal;
17
+ const handle = {
18
+ engine: 'postgres',
19
+ resourceId,
16
20
  url: owned.url,
17
21
  _pool: pool,
18
22
  async query(sql, params = []) {
19
- if (disposed) {
23
+ if (disposal) {
20
24
  throw new server_1.TestPostgresServerError(`Test database "${owned.name}" has been disposed; no further queries are possible.`);
21
25
  }
22
26
  const res = await pool.query(sql, params);
23
27
  return res.rows;
24
28
  },
25
- async dispose() {
26
- if (disposed)
27
- return;
28
- disposed = true;
29
- try {
30
- await pool.end();
29
+ dispose() {
30
+ if (!disposal) {
31
+ (0, resource_1.retireResource)(handle);
32
+ disposal = Promise.resolve().then(() => (0, resource_1.runCleanupSteps)(`Test database "${owned.name}"`, [
33
+ () => pool.end(),
34
+ () => (0, allocator_1.dropDatabase)(owned.name, options),
35
+ ]));
31
36
  }
32
- catch {
33
- /* pool may already be closed */
34
- }
35
- await (0, allocator_1.dropDatabase)(owned.name, options);
37
+ return disposal;
36
38
  },
37
39
  };
40
+ (0, resource_1.registerResource)(handle, { engine: 'postgres', resourceId });
41
+ return handle;
38
42
  }
39
43
  //# sourceMappingURL=database.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"database.js","sourceRoot":"","sources":["../../../../src/testing/postgres/database.ts"],"names":[],"mappings":";;AAqBA,oDA+BC;AApDD,2CAA2C;AAC3C,qCAA8E;AAgB9E;;;GAGG;AACI,KAAK,UAAU,oBAAoB,CACxC,KAAoB,EACpB,UAA6B,EAAE;IAE/B,MAAM,EAAE,GAAG,MAAM,IAAA,eAAM,GAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,EAAE,gBAAgB,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/D,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,OAAO;QACL,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,KAAK,EAAE,IAAI;QACX,KAAK,CAAC,KAAK,CAAC,GAAW,EAAE,SAA6B,EAAE;YACtD,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,IAAI,gCAAuB,CAC/B,kBAAkB,KAAK,CAAC,IAAI,uDAAuD,CACpF,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,MAAmB,CAAC,CAAC;YACvD,OAAO,GAAG,CAAC,IAAI,CAAC;QAClB,CAAC;QACD,KAAK,CAAC,OAAO;YACX,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;YACnB,CAAC;YAAC,MAAM,CAAC;gBACP,gCAAgC;YAClC,CAAC;YACD,MAAM,IAAA,wBAAY,EAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC1C,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { dropDatabase } from './allocator';\nimport { loadPg, TestPostgresServerError, TestServerOptions } from './server';\n\n/** The built test database handle. */\nexport interface TestPostgresDatabase {\n /** Normal PostgreSQL connection string identifying only this database. */\n readonly url: string;\n query(sql: string, params?: readonly unknown[]): Promise<unknown>;\n /** Close helper connections and drop the owned database. Idempotent. */\n dispose(): Promise<void>;\n}\n\nexport interface OwnedDatabase {\n readonly name: string;\n readonly url: string;\n}\n\n/**\n * Connect a helper pool to an allocated database and produce the handle.\n * The handle owns exactly this database and drops it on dispose.\n */\nexport async function createDatabaseHandle(\n owned: OwnedDatabase,\n options: TestServerOptions = {}\n): Promise<TestPostgresDatabase & { _pool: any }> {\n const pg = await loadPg();\n const Pool = pg.Pool ?? pg.default?.Pool;\n const pool = new Pool({ connectionString: owned.url, max: 4 });\n let disposed = false;\n return {\n url: owned.url,\n _pool: pool,\n async query(sql: string, params: readonly unknown[] = []) {\n if (disposed) {\n throw new TestPostgresServerError(\n `Test database \"${owned.name}\" has been disposed; no further queries are possible.`\n );\n }\n const res = await pool.query(sql, params as unknown[]);\n return res.rows;\n },\n async dispose() {\n if (disposed) return;\n disposed = true;\n try {\n await pool.end();\n } catch {\n /* pool may already be closed */\n }\n await dropDatabase(owned.name, options);\n },\n };\n}\n"]}
1
+ {"version":3,"file":"database.js","sourceRoot":"","sources":["../../../../src/testing/postgres/database.ts"],"names":[],"mappings":";;AA8BA,oDAqCC;AAnED,2CAA2C;AAC3C,qCAA8E;AAC9E,0CAAgG;AAwBhG;;;GAGG;AACI,KAAK,UAAU,oBAAoB,CACxC,KAAoB,EACpB,UAA6B,EAAE;IAE/B,MAAM,EAAE,GAAG,MAAM,IAAA,eAAM,GAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,EAAE,gBAAgB,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/D,MAAM,UAAU,GAAG,IAAA,yBAAc,EAAC,UAAU,CAAC,CAAC;IAC9C,IAAI,QAAmC,CAAC;IAExC,MAAM,MAAM,GAA0C;QACpD,MAAM,EAAE,UAAU;QAClB,UAAU;QACV,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,KAAK,EAAE,IAAI;QACX,KAAK,CAAC,KAAK,CAAC,GAAW,EAAE,SAA6B,EAAE;YACtD,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,IAAI,gCAAuB,CAC/B,kBAAkB,KAAK,CAAC,IAAI,uDAAuD,CACpF,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,MAAmB,CAAC,CAAC;YACvD,OAAO,GAAG,CAAC,IAAI,CAAC;QAClB,CAAC;QACD,OAAO;YACL,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,IAAA,yBAAc,EAAC,MAAM,CAAC,CAAC;gBACvB,QAAQ,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAA,0BAAe,EAAC,kBAAkB,KAAK,CAAC,IAAI,GAAG,EAAE;oBACvF,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;oBAChB,GAAG,EAAE,CAAC,IAAA,wBAAY,EAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC;iBACxC,CAAC,CAAC,CAAC;YACN,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;KACF,CAAC;IACF,IAAA,2BAAgB,EAAC,MAAM,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IAC7D,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { dropDatabase } from './allocator';\nimport { loadPg, TestPostgresServerError, TestServerOptions } from './server';\nimport { mintResourceId, registerResource, retireResource, runCleanupSteps } from '../resource';\n\n/** The built test database handle. */\nexport interface TestPostgresDatabase {\n /** Engine discriminator for SQLStack-minted disposable test databases. */\n readonly engine: 'postgres';\n /** Fresh per-allocation identifier; never reused within a process. */\n readonly resourceId: string;\n /** Normal PostgreSQL connection string identifying only this database. */\n readonly url: string;\n query(sql: string, params?: readonly unknown[]): Promise<unknown>;\n /**\n * Close helper connections and drop the owned database. Idempotent:\n * concurrent and repeated calls share one outcome. Every cleanup step is\n * attempted; failures are aggregated into a `TestDatabaseDisposeError`.\n */\n dispose(): Promise<void>;\n}\n\nexport interface OwnedDatabase {\n readonly name: string;\n readonly url: string;\n}\n\n/**\n * Connect a helper pool to an allocated database and produce the handle.\n * The handle owns exactly this database and drops it on dispose.\n */\nexport async function createDatabaseHandle(\n owned: OwnedDatabase,\n options: TestServerOptions = {}\n): Promise<TestPostgresDatabase & { _pool: any }> {\n const pg = await loadPg();\n const Pool = pg.Pool ?? pg.default?.Pool;\n const pool = new Pool({ connectionString: owned.url, max: 4 });\n const resourceId = mintResourceId('postgres');\n let disposal: Promise<void> | undefined;\n\n const handle: TestPostgresDatabase & { _pool: any } = {\n engine: 'postgres',\n resourceId,\n url: owned.url,\n _pool: pool,\n async query(sql: string, params: readonly unknown[] = []) {\n if (disposal) {\n throw new TestPostgresServerError(\n `Test database \"${owned.name}\" has been disposed; no further queries are possible.`\n );\n }\n const res = await pool.query(sql, params as unknown[]);\n return res.rows;\n },\n dispose() {\n if (!disposal) {\n retireResource(handle);\n disposal = Promise.resolve().then(() => runCleanupSteps(`Test database \"${owned.name}\"`, [\n () => pool.end(),\n () => dropDatabase(owned.name, options),\n ]));\n }\n return disposal;\n },\n };\n registerResource(handle, { engine: 'postgres', resourceId });\n return handle;\n}\n"]}
@@ -0,0 +1,76 @@
1
+ import { SqlStackError } from '../core/errors';
2
+ import type { TestPostgresDatabase } from './postgres/database';
3
+ import type { TestSqliteDatabase } from './sqlite/database';
4
+ /**
5
+ * Internal provenance for SQLStack-minted disposable test databases.
6
+ *
7
+ * Every handle allocated by a SQLStack fixture builder is registered here at
8
+ * allocation time and retired on dispose. Membership is module-private:
9
+ * nothing outside this package can mint, register, or extend a handle's
10
+ * lifetime, and structural copies (spread, `{ ...handle }`, hand-built
11
+ * lookalikes) never validate because verification is by object identity.
12
+ */
13
+ /** A disposable test database minted by a SQLStack fixture allocation. */
14
+ export type DisposableTestDatabase = (TestPostgresDatabase & {
15
+ readonly engine: 'postgres';
16
+ readonly resourceId: string;
17
+ }) | (TestSqliteDatabase & {
18
+ readonly engine: 'sqlite';
19
+ readonly resourceId: string;
20
+ readonly storage: 'memory' | 'file';
21
+ });
22
+ export type DisposableEngine = DisposableTestDatabase['engine'];
23
+ interface ResourceRecord {
24
+ readonly engine: DisposableEngine;
25
+ readonly resourceId: string;
26
+ readonly storage?: 'memory' | 'file';
27
+ readonly url?: unknown;
28
+ readonly db?: unknown;
29
+ readonly conn?: unknown;
30
+ readonly path?: unknown;
31
+ readonly query?: unknown;
32
+ readonly dispose?: unknown;
33
+ }
34
+ /** Fresh, process-unique identifier for one allocation. */
35
+ export declare function mintResourceId(engine: DisposableEngine): string;
36
+ /**
37
+ * Register a freshly allocated handle as an active SQLStack resource.
38
+ * Only fixture allocation code paths call this; it is never exported from
39
+ * `sqlstack/testing`.
40
+ */
41
+ export declare function registerResource(handle: object, record: ResourceRecord): void;
42
+ /**
43
+ * Optional shared testing resource-scope ABI. SQLStack remains independently
44
+ * usable; it never creates a scope or imports a test runner.
45
+ * Called within the builder's rollback boundary before returning a fixture.
46
+ */
47
+ export declare function adoptFixtureResource<T extends {
48
+ dispose(): unknown;
49
+ }>(handle: T): T;
50
+ /** Mark a handle's owned resources as released. Verification fails from now on. */
51
+ export declare function retireResource(handle: object): void;
52
+ /**
53
+ * True only for a handle object that was minted by a SQLStack fixture
54
+ * allocation in this process and has not been disposed. Field checks are a
55
+ * consistency guard on top of identity membership, never a substitute for it.
56
+ */
57
+ export declare function isDisposableTestDatabase(value: unknown): value is DisposableTestDatabase;
58
+ /** Failed fixture construction plus failed rollback, preserving both causes. */
59
+ export declare class TestDatabaseBuildError extends SqlStackError {
60
+ readonly cause: unknown;
61
+ readonly errors: readonly unknown[];
62
+ constructor(message: string, cause: unknown, cleanup: unknown);
63
+ }
64
+ /** Raised by `dispose()` when one or more owned cleanup steps failed. */
65
+ export declare class TestDatabaseDisposeError extends SqlStackError {
66
+ /** Every cleanup failure, in attempt order. Nothing is swallowed. */
67
+ readonly errors: readonly unknown[];
68
+ constructor(message: string, errors: readonly unknown[]);
69
+ }
70
+ /**
71
+ * Run every cleanup step, retaining each failure, then either resolve or
72
+ * reject once with all retained errors.
73
+ */
74
+ export declare function runCleanupSteps(label: string, steps: readonly (() => Promise<void> | void)[]): Promise<void>;
75
+ export {};
76
+ //# sourceMappingURL=resource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../../src/testing/resource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;;;;;;GAQG;AAEH,0EAA0E;AAC1E,MAAM,MAAM,sBAAsB,GAC9B,CAAC,oBAAoB,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,GACrF,CAAC,kBAAkB,GAAG;IACpB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAC;CACrC,CAAC,CAAC;AAEP,MAAM,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC;AAEhE,UAAU,cAAc;IACtB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IACrC,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AASD,2DAA2D;AAC3D,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAG/D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAK7E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS;IAAE,OAAO,IAAI,OAAO,CAAA;CAAE,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAOnF;AAED,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,sBAAsB,CAcxF;AAED,gFAAgF;AAChF,qBAAa,sBAAuB,SAAQ,aAAa;IAE1B,QAAQ,CAAC,KAAK,EAAE,OAAO;IADpD,QAAQ,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;gBACxB,OAAO,EAAE,MAAM,EAAW,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;CAKvE;AAED,yEAAyE;AACzE,qBAAa,wBAAyB,SAAQ,aAAa;IACzD,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;gBAExB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,OAAO,EAAE;CAKxD;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,SAAS,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,GAC7C,OAAO,CAAC,IAAI,CAAC,CAiBf"}
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TestDatabaseDisposeError = exports.TestDatabaseBuildError = void 0;
4
+ exports.mintResourceId = mintResourceId;
5
+ exports.registerResource = registerResource;
6
+ exports.adoptFixtureResource = adoptFixtureResource;
7
+ exports.retireResource = retireResource;
8
+ exports.isDisposableTestDatabase = isDisposableTestDatabase;
9
+ exports.runCleanupSteps = runCleanupSteps;
10
+ const node_crypto_1 = require("node:crypto");
11
+ const errors_1 = require("../core/errors");
12
+ /** Handles whose owned resources are currently allocated (not yet disposed). */
13
+ const active = new WeakSet();
14
+ /** Provenance recorded at mint time, kept for the handle's whole lifetime. */
15
+ const provenance = new WeakMap();
16
+ let counter = 0;
17
+ /** Fresh, process-unique identifier for one allocation. */
18
+ function mintResourceId(engine) {
19
+ counter += 1;
20
+ return `${engine}-${(0, node_crypto_1.randomBytes)(6).toString('hex')}-${counter}`;
21
+ }
22
+ /**
23
+ * Register a freshly allocated handle as an active SQLStack resource.
24
+ * Only fixture allocation code paths call this; it is never exported from
25
+ * `sqlstack/testing`.
26
+ */
27
+ function registerResource(handle, record) {
28
+ const locator = handle;
29
+ provenance.set(handle, Object.freeze({ ...record, url: locator.url, db: locator.db,
30
+ conn: locator.conn, path: locator.path, query: locator.query, dispose: locator.dispose }));
31
+ active.add(handle);
32
+ }
33
+ /**
34
+ * Optional shared testing resource-scope ABI. SQLStack remains independently
35
+ * usable; it never creates a scope or imports a test runner.
36
+ * Called within the builder's rollback boundary before returning a fixture.
37
+ */
38
+ function adoptFixtureResource(handle) {
39
+ const channel = globalThis[Symbol.for('@noego/testing.resource-scope.v1')];
40
+ if (channel) {
41
+ if (typeof channel.getStore !== 'function')
42
+ throw new Error('Incompatible NoEgo resource-scope v1 channel');
43
+ channel.getStore()?.own(handle, 'sqlstack fixture');
44
+ }
45
+ return handle;
46
+ }
47
+ /** Mark a handle's owned resources as released. Verification fails from now on. */
48
+ function retireResource(handle) {
49
+ active.delete(handle);
50
+ }
51
+ /**
52
+ * True only for a handle object that was minted by a SQLStack fixture
53
+ * allocation in this process and has not been disposed. Field checks are a
54
+ * consistency guard on top of identity membership, never a substitute for it.
55
+ */
56
+ function isDisposableTestDatabase(value) {
57
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function'))
58
+ return false;
59
+ const handle = value;
60
+ if (!active.has(handle))
61
+ return false;
62
+ const record = provenance.get(handle);
63
+ if (!record)
64
+ return false;
65
+ const candidate = handle;
66
+ if (candidate.engine !== record.engine || candidate.resourceId !== record.resourceId)
67
+ return false;
68
+ if (record.engine === 'sqlite' && candidate.storage !== record.storage)
69
+ return false;
70
+ const locator = handle;
71
+ for (const key of ['url', 'db', 'conn', 'path', 'query', 'dispose']) {
72
+ if (locator[key] !== record[key])
73
+ return false;
74
+ }
75
+ return true;
76
+ }
77
+ /** Failed fixture construction plus failed rollback, preserving both causes. */
78
+ class TestDatabaseBuildError extends errors_1.SqlStackError {
79
+ constructor(message, cause, cleanup) {
80
+ super(message);
81
+ this.cause = cause;
82
+ this.name = 'TestDatabaseBuildError';
83
+ this.errors = Object.freeze([cause, cleanup]);
84
+ }
85
+ }
86
+ exports.TestDatabaseBuildError = TestDatabaseBuildError;
87
+ /** Raised by `dispose()` when one or more owned cleanup steps failed. */
88
+ class TestDatabaseDisposeError extends errors_1.SqlStackError {
89
+ constructor(message, errors) {
90
+ super(message);
91
+ this.name = 'TestDatabaseDisposeError';
92
+ this.errors = Object.freeze([...errors]);
93
+ }
94
+ }
95
+ exports.TestDatabaseDisposeError = TestDatabaseDisposeError;
96
+ /**
97
+ * Run every cleanup step, retaining each failure, then either resolve or
98
+ * reject once with all retained errors.
99
+ */
100
+ async function runCleanupSteps(label, steps) {
101
+ const errors = [];
102
+ for (const step of steps) {
103
+ try {
104
+ await step();
105
+ }
106
+ catch (err) {
107
+ errors.push(err);
108
+ }
109
+ }
110
+ if (errors.length === 0)
111
+ return;
112
+ const detail = errors
113
+ .map((err, index) => ` [${index + 1}] ${err instanceof Error ? err.message : String(err)}`)
114
+ .join('\n');
115
+ throw new TestDatabaseDisposeError(`${label}: ${errors.length} cleanup step(s) failed while disposing:\n${detail}`, errors);
116
+ }
117
+ //# sourceMappingURL=resource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource.js","sourceRoot":"","sources":["../../../src/testing/resource.ts"],"names":[],"mappings":";;;AA8CA,wCAGC;AAOD,4CAKC;AAOD,oDAOC;AAGD,wCAEC;AAOD,4DAcC;AA4BD,0CAoBC;AArJD,6CAA0C;AAC1C,2CAA+C;AAqC/C,gFAAgF;AAChF,MAAM,MAAM,GAAG,IAAI,OAAO,EAAU,CAAC;AACrC,8EAA8E;AAC9E,MAAM,UAAU,GAAG,IAAI,OAAO,EAA0B,CAAC;AAEzD,IAAI,OAAO,GAAG,CAAC,CAAC;AAEhB,2DAA2D;AAC3D,SAAgB,cAAc,CAAC,MAAwB;IACrD,OAAO,IAAI,CAAC,CAAC;IACb,OAAO,GAAG,MAAM,IAAI,IAAA,yBAAW,EAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;AAClE,CAAC;AAED;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,MAAc,EAAE,MAAsB;IACrE,MAAM,OAAO,GAAG,MAAiC,CAAC;IAClD,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE;QAChF,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC7F,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACrB,CAAC;AAED;;;;GAIG;AACH,SAAgB,oBAAoB,CAAmC,MAAS;IAC9E,MAAM,OAAO,GAAI,UAAuC,CAAC,MAAM,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC,CAAC;IACzG,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;QAC5G,OAAO,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,mFAAmF;AACnF,SAAgB,cAAc,CAAC,MAAc;IAC3C,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAED;;;;GAIG;AACH,SAAgB,wBAAwB,CAAC,KAAc;IACrD,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/F,MAAM,MAAM,GAAG,KAAe,CAAC;IAC/B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,MAAM,SAAS,GAAG,MAAuE,CAAC;IAC1F,IAAI,SAAS,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,IAAI,SAAS,CAAC,UAAU,KAAK,MAAM,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IACnG,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,SAAS,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IACrF,MAAM,OAAO,GAAG,MAAiC,CAAC;IAClD,KAAK,MAAM,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAU,EAAE,CAAC;QAC7E,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;IACjD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gFAAgF;AAChF,MAAa,sBAAuB,SAAQ,sBAAa;IAEvD,YAAY,OAAe,EAAW,KAAc,EAAE,OAAgB;QACpE,KAAK,CAAC,OAAO,CAAC,CAAC;QADqB,UAAK,GAAL,KAAK,CAAS;QAElD,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;IAChD,CAAC;CACF;AAPD,wDAOC;AAED,yEAAyE;AACzE,MAAa,wBAAyB,SAAQ,sBAAa;IAIzD,YAAY,OAAe,EAAE,MAA0B;QACrD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;IAC3C,CAAC;CACF;AATD,4DASC;AAED;;;GAGG;AACI,KAAK,UAAU,eAAe,CACnC,KAAa,EACb,KAA8C;IAE9C,MAAM,MAAM,GAAc,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,EAAE,CAAC;QACf,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAChC,MAAM,MAAM,GAAG,MAAM;SAClB,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,MAAM,KAAK,GAAG,CAAC,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;SAC3F,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,MAAM,IAAI,wBAAwB,CAChC,GAAG,KAAK,KAAK,MAAM,CAAC,MAAM,6CAA6C,MAAM,EAAE,EAC/E,MAAM,CACP,CAAC;AACJ,CAAC","sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { SqlStackError } from '../core/errors';\nimport type { TestPostgresDatabase } from './postgres/database';\nimport type { TestSqliteDatabase } from './sqlite/database';\n\n/**\n * Internal provenance for SQLStack-minted disposable test databases.\n *\n * Every handle allocated by a SQLStack fixture builder is registered here at\n * allocation time and retired on dispose. Membership is module-private:\n * nothing outside this package can mint, register, or extend a handle's\n * lifetime, and structural copies (spread, `{ ...handle }`, hand-built\n * lookalikes) never validate because verification is by object identity.\n */\n\n/** A disposable test database minted by a SQLStack fixture allocation. */\nexport type DisposableTestDatabase =\n | (TestPostgresDatabase & { readonly engine: 'postgres'; readonly resourceId: string })\n | (TestSqliteDatabase & {\n readonly engine: 'sqlite';\n readonly resourceId: string;\n readonly storage: 'memory' | 'file';\n });\n\nexport type DisposableEngine = DisposableTestDatabase['engine'];\n\ninterface ResourceRecord {\n readonly engine: DisposableEngine;\n readonly resourceId: string;\n readonly storage?: 'memory' | 'file';\n readonly url?: unknown;\n readonly db?: unknown;\n readonly conn?: unknown;\n readonly path?: unknown;\n readonly query?: unknown;\n readonly dispose?: unknown;\n}\n\n/** Handles whose owned resources are currently allocated (not yet disposed). */\nconst active = new WeakSet<object>();\n/** Provenance recorded at mint time, kept for the handle's whole lifetime. */\nconst provenance = new WeakMap<object, ResourceRecord>();\n\nlet counter = 0;\n\n/** Fresh, process-unique identifier for one allocation. */\nexport function mintResourceId(engine: DisposableEngine): string {\n counter += 1;\n return `${engine}-${randomBytes(6).toString('hex')}-${counter}`;\n}\n\n/**\n * Register a freshly allocated handle as an active SQLStack resource.\n * Only fixture allocation code paths call this; it is never exported from\n * `sqlstack/testing`.\n */\nexport function registerResource(handle: object, record: ResourceRecord): void {\n const locator = handle as Record<string, unknown>;\n provenance.set(handle, Object.freeze({ ...record, url: locator.url, db: locator.db,\n conn: locator.conn, path: locator.path, query: locator.query, dispose: locator.dispose }));\n active.add(handle);\n}\n\n/**\n * Optional shared testing resource-scope ABI. SQLStack remains independently\n * usable; it never creates a scope or imports a test runner.\n * Called within the builder's rollback boundary before returning a fixture.\n */\nexport function adoptFixtureResource<T extends { dispose(): unknown }>(handle: T): T {\n const channel = (globalThis as Record<PropertyKey, any>)[Symbol.for('@noego/testing.resource-scope.v1')];\n if (channel) {\n if (typeof channel.getStore !== 'function') throw new Error('Incompatible NoEgo resource-scope v1 channel');\n channel.getStore()?.own(handle, 'sqlstack fixture');\n }\n return handle;\n}\n\n/** Mark a handle's owned resources as released. Verification fails from now on. */\nexport function retireResource(handle: object): void {\n active.delete(handle);\n}\n\n/**\n * True only for a handle object that was minted by a SQLStack fixture\n * allocation in this process and has not been disposed. Field checks are a\n * consistency guard on top of identity membership, never a substitute for it.\n */\nexport function isDisposableTestDatabase(value: unknown): value is DisposableTestDatabase {\n if (value === null || (typeof value !== 'object' && typeof value !== 'function')) return false;\n const handle = value as object;\n if (!active.has(handle)) return false;\n const record = provenance.get(handle);\n if (!record) return false;\n const candidate = handle as { engine?: unknown; resourceId?: unknown; storage?: unknown };\n if (candidate.engine !== record.engine || candidate.resourceId !== record.resourceId) return false;\n if (record.engine === 'sqlite' && candidate.storage !== record.storage) return false;\n const locator = handle as Record<string, unknown>;\n for (const key of ['url', 'db', 'conn', 'path', 'query', 'dispose'] as const) {\n if (locator[key] !== record[key]) return false;\n }\n return true;\n}\n\n/** Failed fixture construction plus failed rollback, preserving both causes. */\nexport class TestDatabaseBuildError extends SqlStackError {\n readonly errors: readonly unknown[];\n constructor(message: string, readonly cause: unknown, cleanup: unknown) {\n super(message);\n this.name = 'TestDatabaseBuildError';\n this.errors = Object.freeze([cause, cleanup]);\n }\n}\n\n/** Raised by `dispose()` when one or more owned cleanup steps failed. */\nexport class TestDatabaseDisposeError extends SqlStackError {\n /** Every cleanup failure, in attempt order. Nothing is swallowed. */\n readonly errors: readonly unknown[];\n\n constructor(message: string, errors: readonly unknown[]) {\n super(message);\n this.name = 'TestDatabaseDisposeError';\n this.errors = Object.freeze([...errors]);\n }\n}\n\n/**\n * Run every cleanup step, retaining each failure, then either resolve or\n * reject once with all retained errors.\n */\nexport async function runCleanupSteps(\n label: string,\n steps: readonly (() => Promise<void> | void)[]\n): Promise<void> {\n const errors: unknown[] = [];\n for (const step of steps) {\n try {\n await step();\n } catch (err) {\n errors.push(err);\n }\n }\n if (errors.length === 0) return;\n const detail = errors\n .map((err, index) => ` [${index + 1}] ${err instanceof Error ? err.message : String(err)}`)\n .join('\\n');\n throw new TestDatabaseDisposeError(\n `${label}: ${errors.length} cleanup step(s) failed while disposing:\\n${detail}`,\n errors\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../../../src/testing/sqlite/builder.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACpF,OAAO,EAAwB,kBAAkB,EAAmB,MAAM,YAAY,CAAC;AACvF,OAAO,EAAe,aAAa,EAAc,GAAG,EAAgB,MAAM,YAAY,CAAC;AAEvF,MAAM,WAAW,iBAAkB,SAAQ,sBAAsB;IAC/D;;;;;OAKG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IACzB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,GAAG,EAAE,SAAS,aAAa,EAAE,CAAC;IACvC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,iBAAiB,CAAC;IACrD,KAAK,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACtC;AAoFD,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,OAAO,GAAE,iBAAsB,GAC9B,iBAAiB,CAcnB"}
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../../../src/testing/sqlite/builder.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACpF,OAAO,EAAwB,kBAAkB,EAAmB,MAAM,YAAY,CAAC;AACvF,OAAO,EAAe,aAAa,EAAc,GAAG,EAAgB,MAAM,YAAY,CAAC;AAEvF,MAAM,WAAW,iBAAkB,SAAQ,sBAAsB;IAC/D;;;;;OAKG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IACzB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,GAAG,EAAE,SAAS,aAAa,EAAE,CAAC;IACvC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,iBAAiB,CAAC;IACrD,KAAK,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACtC;AAsFD,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,OAAO,GAAE,iBAAsB,GAC9B,iBAAiB,CAcnB"}
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.testSqlite = testSqlite;
37
37
  const fs = __importStar(require("node:fs"));
38
+ const resource_1 = require("../resource");
39
+ const fixture_provenance_1 = require("../fixture-provenance");
38
40
  const ddl_1 = require("../schema/ddl");
39
41
  const parser_1 = require("../schema/parser");
40
42
  const database_1 = require("./database");
@@ -45,6 +47,7 @@ function readRawSql(schema) {
45
47
  return { label: `sql entry #${entry.index} (inline)`, sql: entry.sql };
46
48
  }
47
49
  try {
50
+ (0, fixture_provenance_1.assertFixtureSqlFile)(entry.resolvedPath);
48
51
  return {
49
52
  label: `sql entry #${entry.index} (${entry.sourcePath})`,
50
53
  sql: fs.readFileSync(entry.resolvedPath, 'utf8'),
@@ -102,10 +105,15 @@ function makeBuilder(state) {
102
105
  }
103
106
  }
104
107
  await (0, loadData_1.insertRows)((sql, params) => handle.db.query(sql, params), state.schema, effective);
105
- return handle;
108
+ return (0, resource_1.adoptFixtureResource)(handle);
106
109
  }
107
110
  catch (err) {
108
- await handle.dispose();
111
+ try {
112
+ await handle.dispose();
113
+ }
114
+ catch (cleanup) {
115
+ throw new resource_1.TestDatabaseBuildError('SQLite fixture build and cleanup failed', err, cleanup);
116
+ }
109
117
  throw err;
110
118
  }
111
119
  },
@@ -1 +1 @@
1
- {"version":3,"file":"builder.js","sourceRoot":"","sources":["../../../../src/testing/sqlite/builder.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport { generateDdl } from '../schema/ddl';\nimport { parseTestSchema } from '../schema/parser';\nimport type { NormalizedTestSchema, ParseTestSchemaOptions } from '../schema/types';\nimport { createDatabaseHandle, TestSqliteDatabase, TestSqliteError } from './database';\nimport { composeRows, DataOperation, insertRows, Row, toOperations } from './loadData';\n\nexport interface TestSqliteOptions extends ParseTestSchemaOptions {\n /**\n * Storage for the test database. Defaults to `'file'`: an owned temporary\n * file, removed on `dispose()`. Pass `'memory'` to opt back into an isolated\n * `:memory:` database — only worth it for a test that opens many databases\n * and never touches restart behavior.\n */\n mode?: 'memory' | 'file';\n /** Legacy alias for `mode`. `false` selects `:memory:`. */\n fileBacked?: boolean;\n /** Legacy alias for `mode`. `false` selects `:memory:`. */\n tempFile?: boolean;\n}\n\nexport interface TestSqliteBuilder {\n readonly schema: NormalizedTestSchema;\n readonly ops: readonly DataOperation[];\n data(rows: Record<string, Row[]>): TestSqliteBuilder;\n build(): Promise<TestSqliteDatabase>;\n}\n\ninterface BuilderState {\n readonly schema: NormalizedTestSchema;\n readonly ops: readonly DataOperation[];\n readonly options: Readonly<TestSqliteOptions>;\n}\n\nfunction readRawSql(schema: NormalizedTestSchema): { label: string; sql: string }[] {\n return schema.sql.map((entry) => {\n if (entry.kind === 'inline') {\n return { label: `sql entry #${entry.index} (inline)`, sql: entry.sql };\n }\n try {\n return {\n label: `sql entry #${entry.index} (${entry.sourcePath})`,\n sql: fs.readFileSync(entry.resolvedPath, 'utf8'),\n };\n } catch (err) {\n throw new TestSqliteError(\n `Cannot read raw SQL file for sql entry #${entry.index}: ${entry.resolvedPath} ` +\n `(authored as \"${entry.sourcePath}\" in ${schema.source}): ${(err as Error).message}`\n );\n }\n });\n}\n\nasync function executeRaw(handle: TestSqliteDatabase, sql: string): Promise<void> {\n const conn = handle.conn as { exec?: (sql: string) => unknown };\n if (typeof conn?.exec === 'function') {\n await conn.exec(sql);\n } else {\n await handle.db.query(sql, []);\n }\n}\n\nfunction makeBuilder(state: BuilderState): TestSqliteBuilder {\n return {\n schema: state.schema,\n ops: state.ops,\n data(rows: Record<string, Row[]>): TestSqliteBuilder {\n return makeBuilder({\n ...state,\n ops: [...state.ops, ...toOperations(state.schema, rows)],\n });\n },\n async build(): Promise<TestSqliteDatabase> {\n const rawSql = readRawSql(state.schema);\n const ddl = generateDdl(state.schema);\n const effective = composeRows(state.schema, state.ops);\n const fileBacked = state.options.mode\n ? state.options.mode === 'file'\n : state.options.fileBacked !== false && state.options.tempFile !== false;\n const handle = await createDatabaseHandle(fileBacked);\n try {\n for (const statement of ddl) {\n try {\n await handle.db.query(statement, []);\n } catch (err) {\n throw new TestSqliteError(\n `Failed to apply structured DDL from ${state.schema.source}: ${(err as Error).message}\\n` +\n `Statement:\\n${statement}`\n );\n }\n }\n for (const entry of rawSql) {\n try {\n await executeRaw(handle, entry.sql);\n } catch (err) {\n throw new TestSqliteError(\n `Failed to apply raw ${entry.label} from ${state.schema.source}: ${(err as Error).message}`\n );\n }\n }\n await insertRows((sql, params) => handle.db.query(sql, params), state.schema, effective);\n return handle;\n } catch (err) {\n await handle.dispose();\n throw err;\n }\n },\n };\n}\n\nexport function testSqlite(\n schema: string | object,\n options: TestSqliteOptions = {}\n): TestSqliteBuilder {\n const normalized = isNormalizedSchema(schema)\n ? schema\n : parseTestSchema(schema as string | object, options);\n if (normalized.dialect !== 'sqlite') {\n throw new TestSqliteError(\n `testSqlite requires a schema with dialect: sqlite (received: ${normalized.dialect})`\n );\n }\n return makeBuilder({\n schema: normalized,\n ops: [],\n options: Object.freeze({ ...options }),\n });\n}\n\nfunction isNormalizedSchema(input: unknown): input is NormalizedTestSchema {\n return (\n !!input &&\n typeof input === 'object' &&\n Array.isArray((input as NormalizedTestSchema).tables) &&\n Array.isArray((input as NormalizedTestSchema).sql) &&\n typeof (input as NormalizedTestSchema).source === 'string'\n );\n}\n"]}
1
+ {"version":3,"file":"builder.js","sourceRoot":"","sources":["../../../../src/testing/sqlite/builder.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport { adoptFixtureResource, TestDatabaseBuildError } from '../resource';\nimport { assertFixtureSqlFile } from '../fixture-provenance';\nimport { generateDdl } from '../schema/ddl';\nimport { parseTestSchema } from '../schema/parser';\nimport type { NormalizedTestSchema, ParseTestSchemaOptions } from '../schema/types';\nimport { createDatabaseHandle, TestSqliteDatabase, TestSqliteError } from './database';\nimport { composeRows, DataOperation, insertRows, Row, toOperations } from './loadData';\n\nexport interface TestSqliteOptions extends ParseTestSchemaOptions {\n /**\n * Storage for the test database. Defaults to `'file'`: an owned temporary\n * file, removed on `dispose()`. Pass `'memory'` to opt back into an isolated\n * `:memory:` database — only worth it for a test that opens many databases\n * and never touches restart behavior.\n */\n mode?: 'memory' | 'file';\n /** Legacy alias for `mode`. `false` selects `:memory:`. */\n fileBacked?: boolean;\n /** Legacy alias for `mode`. `false` selects `:memory:`. */\n tempFile?: boolean;\n}\n\nexport interface TestSqliteBuilder {\n readonly schema: NormalizedTestSchema;\n readonly ops: readonly DataOperation[];\n data(rows: Record<string, Row[]>): TestSqliteBuilder;\n build(): Promise<TestSqliteDatabase>;\n}\n\ninterface BuilderState {\n readonly schema: NormalizedTestSchema;\n readonly ops: readonly DataOperation[];\n readonly options: Readonly<TestSqliteOptions>;\n}\n\nfunction readRawSql(schema: NormalizedTestSchema): { label: string; sql: string }[] {\n return schema.sql.map((entry) => {\n if (entry.kind === 'inline') {\n return { label: `sql entry #${entry.index} (inline)`, sql: entry.sql };\n }\n try {\n assertFixtureSqlFile(entry.resolvedPath);\n return {\n label: `sql entry #${entry.index} (${entry.sourcePath})`,\n sql: fs.readFileSync(entry.resolvedPath, 'utf8'),\n };\n } catch (err) {\n throw new TestSqliteError(\n `Cannot read raw SQL file for sql entry #${entry.index}: ${entry.resolvedPath} ` +\n `(authored as \"${entry.sourcePath}\" in ${schema.source}): ${(err as Error).message}`\n );\n }\n });\n}\n\nasync function executeRaw(handle: TestSqliteDatabase, sql: string): Promise<void> {\n const conn = handle.conn as { exec?: (sql: string) => unknown };\n if (typeof conn?.exec === 'function') {\n await conn.exec(sql);\n } else {\n await handle.db.query(sql, []);\n }\n}\n\nfunction makeBuilder(state: BuilderState): TestSqliteBuilder {\n return {\n schema: state.schema,\n ops: state.ops,\n data(rows: Record<string, Row[]>): TestSqliteBuilder {\n return makeBuilder({\n ...state,\n ops: [...state.ops, ...toOperations(state.schema, rows)],\n });\n },\n async build(): Promise<TestSqliteDatabase> {\n const rawSql = readRawSql(state.schema);\n const ddl = generateDdl(state.schema);\n const effective = composeRows(state.schema, state.ops);\n const fileBacked = state.options.mode\n ? state.options.mode === 'file'\n : state.options.fileBacked !== false && state.options.tempFile !== false;\n const handle = await createDatabaseHandle(fileBacked);\n try {\n for (const statement of ddl) {\n try {\n await handle.db.query(statement, []);\n } catch (err) {\n throw new TestSqliteError(\n `Failed to apply structured DDL from ${state.schema.source}: ${(err as Error).message}\\n` +\n `Statement:\\n${statement}`\n );\n }\n }\n for (const entry of rawSql) {\n try {\n await executeRaw(handle, entry.sql);\n } catch (err) {\n throw new TestSqliteError(\n `Failed to apply raw ${entry.label} from ${state.schema.source}: ${(err as Error).message}`\n );\n }\n }\n await insertRows((sql, params) => handle.db.query(sql, params), state.schema, effective);\n return adoptFixtureResource(handle);\n } catch (err) {\n try { await handle.dispose(); }\n catch (cleanup) { throw new TestDatabaseBuildError('SQLite fixture build and cleanup failed', err, cleanup); }\n throw err;\n }\n },\n };\n}\n\nexport function testSqlite(\n schema: string | object,\n options: TestSqliteOptions = {}\n): TestSqliteBuilder {\n const normalized = isNormalizedSchema(schema)\n ? schema\n : parseTestSchema(schema as string | object, options);\n if (normalized.dialect !== 'sqlite') {\n throw new TestSqliteError(\n `testSqlite requires a schema with dialect: sqlite (received: ${normalized.dialect})`\n );\n }\n return makeBuilder({\n schema: normalized,\n ops: [],\n options: Object.freeze({ ...options }),\n });\n}\n\nfunction isNormalizedSchema(input: unknown): input is NormalizedTestSchema {\n return (\n !!input &&\n typeof input === 'object' &&\n Array.isArray((input as NormalizedTestSchema).tables) &&\n Array.isArray((input as NormalizedTestSchema).sql) &&\n typeof (input as NormalizedTestSchema).source === 'string'\n );\n}\n"]}
@@ -4,6 +4,12 @@ export declare class TestSqliteError extends SqlStackError {
4
4
  constructor(message: string);
5
5
  }
6
6
  export interface TestSqliteDatabase {
7
+ /** Engine discriminator for SQLStack-minted disposable test databases. */
8
+ readonly engine: 'sqlite';
9
+ /** Fresh per-allocation identifier; never reused within a process. */
10
+ readonly resourceId: string;
11
+ /** Explicit storage: an owned temporary file, or an isolated `:memory:` db. */
12
+ readonly storage: 'memory' | 'file';
7
13
  /** The live sqlstack SQLite adapter, suitable for direct injection. */
8
14
  readonly db: Database;
9
15
  /** The underlying better-sqlite3 (or sqlite3) connection. */
@@ -11,7 +17,11 @@ export interface TestSqliteDatabase {
11
17
  /** Absolute database path in file mode; undefined for :memory:. */
12
18
  readonly path?: string;
13
19
  query(sql: string, params?: readonly unknown[]): Promise<unknown>;
14
- /** Close the connection and remove owned temporary files. Idempotent. */
20
+ /**
21
+ * Close the connection and remove owned temporary files. Idempotent:
22
+ * concurrent and repeated calls share one outcome. Every cleanup step is
23
+ * attempted; failures are aggregated into a `TestDatabaseDisposeError`.
24
+ */
15
25
  dispose(): Promise<void>;
16
26
  }
17
27
  export declare function createDatabaseHandle(fileBacked: boolean): Promise<TestSqliteDatabase>;
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../../src/testing/sqlite/database.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,qBAAa,eAAgB,SAAQ,aAAa;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,yEAAyE;IACzE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAyBD,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,OAAO,GAClB,OAAO,CAAC,kBAAkB,CAAC,CA+C7B"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../../src/testing/sqlite/database.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG/C,qBAAa,eAAgB,SAAQ,aAAa;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,kBAAkB;IACjC,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,sEAAsE;IACtE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAC;IACpC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE;;;;OAIG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAgCD,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,OAAO,GAClB,OAAO,CAAC,kBAAkB,CAAC,CAsD7B"}
@@ -40,6 +40,7 @@ const os = __importStar(require("node:os"));
40
40
  const path = __importStar(require("node:path"));
41
41
  const sqlite_1 = require("../../adapters/sqlite");
42
42
  const errors_1 = require("../../core/errors");
43
+ const resource_1 = require("../resource");
43
44
  class TestSqliteError extends errors_1.SqlStackError {
44
45
  constructor(message) {
45
46
  super(message);
@@ -70,6 +71,12 @@ function trackDirectory(directory) {
70
71
  }
71
72
  });
72
73
  }
74
+ /** Remove exactly the SQLStack-owned temp directory; never a caller path. */
75
+ function removeOwnedDirectory(directory) {
76
+ fs.rmSync(directory, { recursive: true, force: true });
77
+ // Keep failed paths in the owned inventory for the exit cleanup retry.
78
+ abandonedDirectories.delete(directory);
79
+ }
73
80
  async function createDatabaseHandle(fileBacked) {
74
81
  let directory;
75
82
  let filename = ':memory:';
@@ -84,8 +91,12 @@ async function createDatabaseHandle(fileBacked) {
84
91
  }
85
92
  catch (err) {
86
93
  if (directory) {
87
- fs.rmSync(directory, { recursive: true, force: true });
88
- abandonedDirectories.delete(directory);
94
+ try {
95
+ removeOwnedDirectory(directory);
96
+ }
97
+ catch (cleanup) {
98
+ throw Object.assign(new resource_1.TestDatabaseDisposeError('SQLite test allocation failed and owned cleanup failed', [err, cleanup]), { cause: err });
99
+ }
89
100
  }
90
101
  const message = err.message;
91
102
  if (message.includes('No SQLite driver available')) {
@@ -93,31 +104,34 @@ async function createDatabaseHandle(fileBacked) {
93
104
  }
94
105
  throw new TestSqliteError(`Failed to create SQLite test database: ${message}`);
95
106
  }
96
- let disposed = false;
97
- return {
107
+ const storage = fileBacked ? 'file' : 'memory';
108
+ const resourceId = (0, resource_1.mintResourceId)('sqlite');
109
+ const ownedDirectory = directory;
110
+ let disposal;
111
+ const handle = {
112
+ engine: 'sqlite',
113
+ resourceId,
114
+ storage,
98
115
  db,
99
116
  conn: db.conn,
100
117
  path: fileBacked ? filename : undefined,
101
118
  async query(sql, params = []) {
102
- if (disposed)
119
+ if (disposal)
103
120
  throw new TestSqliteError('Test SQLite database has been disposed.');
104
121
  return db.query(sql, [...params]);
105
122
  },
106
- async dispose() {
107
- if (disposed)
108
- return;
109
- disposed = true;
110
- try {
111
- await db.close();
112
- }
113
- catch {
114
- /* connection may already be closed */
115
- }
116
- if (directory) {
117
- fs.rmSync(directory, { recursive: true, force: true });
118
- abandonedDirectories.delete(directory);
123
+ dispose() {
124
+ if (!disposal) {
125
+ (0, resource_1.retireResource)(handle);
126
+ const steps = [() => db.close()];
127
+ if (ownedDirectory)
128
+ steps.push(() => removeOwnedDirectory(ownedDirectory));
129
+ disposal = Promise.resolve().then(() => (0, resource_1.runCleanupSteps)(`Test SQLite database (${storage})`, steps));
119
130
  }
131
+ return disposal;
120
132
  },
121
133
  };
134
+ (0, resource_1.registerResource)(handle, { engine: 'sqlite', resourceId, storage });
135
+ return handle;
122
136
  }
123
137
  //# sourceMappingURL=database.js.map