sqlstack 3.3.0 → 3.5.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.
- package/dist/cjs/adapters/sqlite.d.ts.map +1 -1
- package/dist/cjs/adapters/sqlite.js +4 -1
- package/dist/cjs/adapters/sqlite.js.map +1 -1
- package/dist/cjs/core/errors.d.ts +9 -0
- package/dist/cjs/core/errors.d.ts.map +1 -1
- package/dist/cjs/core/errors.js +10 -0
- package/dist/cjs/core/errors.js.map +1 -1
- package/dist/cjs/decorators/query.d.ts +14 -0
- package/dist/cjs/decorators/query.d.ts.map +1 -1
- package/dist/cjs/decorators/query.js +124 -17
- package/dist/cjs/decorators/query.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +4 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/run.d.ts +140 -6
- package/dist/cjs/run.d.ts.map +1 -1
- package/dist/cjs/run.js +127 -17
- package/dist/cjs/run.js.map +1 -1
- package/dist/cjs/runtime.d.ts +8 -0
- package/dist/cjs/runtime.d.ts.map +1 -1
- package/dist/cjs/runtime.js +12 -0
- package/dist/cjs/runtime.js.map +1 -1
- package/dist/cjs/sql.d.ts +19 -0
- package/dist/cjs/sql.d.ts.map +1 -1
- package/dist/cjs/sql.js +66 -28
- package/dist/cjs/sql.js.map +1 -1
- package/dist/cjs/stack.d.ts +2 -0
- package/dist/cjs/stack.d.ts.map +1 -1
- package/dist/cjs/stack.js +40 -27
- package/dist/cjs/stack.js.map +1 -1
- package/dist/cjs/testing/builder.d.ts.map +1 -1
- package/dist/cjs/testing/builder.js +14 -5
- package/dist/cjs/testing/builder.js.map +1 -1
- package/dist/cjs/testing/fixture-provenance.d.ts +3 -0
- package/dist/cjs/testing/fixture-provenance.d.ts.map +1 -0
- package/dist/cjs/testing/fixture-provenance.js +21 -0
- package/dist/cjs/testing/fixture-provenance.js.map +1 -0
- package/dist/cjs/testing/index.d.ts +2 -0
- package/dist/cjs/testing/index.d.ts.map +1 -1
- package/dist/cjs/testing/index.js +5 -1
- package/dist/cjs/testing/index.js.map +1 -1
- package/dist/cjs/testing/postgres/database.d.ts +9 -1
- package/dist/cjs/testing/postgres/database.d.ts.map +1 -1
- package/dist/cjs/testing/postgres/database.js +17 -13
- package/dist/cjs/testing/postgres/database.js.map +1 -1
- package/dist/cjs/testing/resource.d.ts +76 -0
- package/dist/cjs/testing/resource.d.ts.map +1 -0
- package/dist/cjs/testing/resource.js +117 -0
- package/dist/cjs/testing/resource.js.map +1 -0
- package/dist/cjs/testing/sqlite/builder.d.ts.map +1 -1
- package/dist/cjs/testing/sqlite/builder.js +10 -2
- package/dist/cjs/testing/sqlite/builder.js.map +1 -1
- package/dist/cjs/testing/sqlite/database.d.ts +11 -1
- package/dist/cjs/testing/sqlite/database.d.ts.map +1 -1
- package/dist/cjs/testing/sqlite/database.js +32 -18
- package/dist/cjs/testing/sqlite/database.js.map +1 -1
- package/dist/cjs/transactions.d.ts +14 -0
- package/dist/cjs/transactions.d.ts.map +1 -1
- package/dist/cjs/transactions.js +27 -0
- package/dist/cjs/transactions.js.map +1 -1
- package/dist/esm/adapters/sqlite.js +4 -1
- package/dist/esm/adapters/sqlite.js.map +1 -1
- package/dist/esm/core/errors.js +10 -0
- package/dist/esm/core/errors.js.map +1 -1
- package/dist/esm/decorators/query.js +127 -20
- package/dist/esm/decorators/query.js.map +1 -1
- package/dist/esm/index.js +3 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/run.js +123 -18
- package/dist/esm/run.js.map +1 -1
- package/dist/esm/runtime.js +11 -0
- package/dist/esm/runtime.js.map +1 -1
- package/dist/esm/sql.js +63 -27
- package/dist/esm/sql.js.map +1 -1
- package/dist/esm/stack.js +40 -27
- package/dist/esm/stack.js.map +1 -1
- package/dist/esm/testing/builder.js +14 -5
- package/dist/esm/testing/builder.js.map +1 -1
- package/dist/esm/testing/fixture-provenance.js +15 -0
- package/dist/esm/testing/fixture-provenance.js.map +1 -0
- package/dist/esm/testing/index.js +1 -0
- package/dist/esm/testing/index.js.map +1 -1
- package/dist/esm/testing/postgres/database.js +17 -13
- package/dist/esm/testing/postgres/database.js.map +1 -1
- package/dist/esm/testing/resource.js +106 -0
- package/dist/esm/testing/resource.js.map +1 -0
- package/dist/esm/testing/sqlite/builder.js +10 -2
- package/dist/esm/testing/sqlite/builder.js.map +1 -1
- package/dist/esm/testing/sqlite/database.js +32 -18
- package/dist/esm/testing/sqlite/database.js.map +1 -1
- package/dist/esm/transactions.js +25 -0
- package/dist/esm/transactions.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +128 -39
|
@@ -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
|
-
/**
|
|
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;
|
|
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
|
-
|
|
15
|
-
|
|
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 (
|
|
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
|
-
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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":";;
|
|
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":"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","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":"
|
|
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
|
-
|
|
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
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
|
-
/**
|
|
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;
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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 (
|
|
119
|
+
if (disposal)
|
|
103
120
|
throw new TestSqliteError('Test SQLite database has been disposed.');
|
|
104
121
|
return db.query(sql, [...params]);
|
|
105
122
|
},
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../../../src/testing/sqlite/database.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../../../src/testing/sqlite/database.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,oDAwDC;AA3HD,4CAA8B;AAC9B,4CAA8B;AAC9B,gDAAkC;AAClC,kDAAuD;AACvD,8CAAkD;AAElD,0CAA0H;AAE1H,MAAa,eAAgB,SAAQ,sBAAa;IAChD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AALD,0CAKC;AAwBD;;;;GAIG;AACH,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAU,CAAC;AAC/C,IAAI,qBAAqB,GAAG,KAAK,CAAC;AAElC,SAAS,cAAc,CAAC,SAAiB;IACvC,oBAAoB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACpC,IAAI,qBAAqB;QAAE,OAAO;IAClC,qBAAqB,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;QACxB,KAAK,MAAM,GAAG,IAAI,oBAAoB,EAAE,CAAC;YACvC,IAAI,CAAC;gBACH,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACnD,CAAC;YAAC,MAAM,CAAC;gBACP,yBAAyB;YAC3B,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,6EAA6E;AAC7E,SAAS,oBAAoB,CAAC,SAAiB;IAC7C,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,uEAAuE;IACvE,oBAAoB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AACzC,CAAC;AAEM,KAAK,UAAU,oBAAoB,CACxC,UAAmB;IAEnB,IAAI,SAA6B,CAAC;IAClC,IAAI,QAAQ,GAAG,UAAU,CAAC;IAC1B,IAAI,UAAU,EAAE,CAAC;QACf,SAAS,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;QACrE,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;QACnD,cAAc,CAAC,SAAS,CAAC,CAAC;IAC5B,CAAC;IAED,IAAI,EAAY,CAAC;IACjB,IAAI,CAAC;QACH,EAAE,GAAG,MAAM,IAAA,uBAAc,EAAC,QAAQ,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,CAAC;gBAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC;YAAC,CAAC;YACxC,OAAO,OAAO,EAAE,CAAC;gBACf,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,mCAAwB,CAAC,wDAAwD,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAC9I,CAAC;QACH,CAAC;QACD,MAAM,OAAO,GAAI,GAAa,CAAC,OAAO,CAAC;QACvC,IAAI,OAAO,CAAC,QAAQ,CAAC,4BAA4B,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,eAAe,CAAC,yDAAyD,CAAC,CAAC;QACvF,CAAC;QACD,MAAM,IAAI,eAAe,CAAC,0CAA0C,OAAO,EAAE,CAAC,CAAC;IACjF,CAAC;IAED,MAAM,OAAO,GAAsB,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClE,MAAM,UAAU,GAAG,IAAA,yBAAc,EAAC,QAAQ,CAAC,CAAC;IAC5C,MAAM,cAAc,GAAG,SAAS,CAAC;IACjC,IAAI,QAAmC,CAAC;IAExC,MAAM,MAAM,GAAuB;QACjC,MAAM,EAAE,QAAQ;QAChB,UAAU;QACV,OAAO;QACP,EAAE;QACF,IAAI,EAAE,EAAE,CAAC,IAAI;QACb,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;QACvC,KAAK,CAAC,KAAK,CAAC,GAAW,EAAE,SAA6B,EAAE;YACtD,IAAI,QAAQ;gBAAE,MAAM,IAAI,eAAe,CAAC,yCAAyC,CAAC,CAAC;YACnF,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;QACpC,CAAC;QACD,OAAO;YACL,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,IAAA,yBAAc,EAAC,MAAM,CAAC,CAAC;gBACvB,MAAM,KAAK,GAAmC,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;gBACjE,IAAI,cAAc;oBAAE,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,cAAc,CAAC,CAAC,CAAC;gBAC3E,QAAQ,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAA,0BAAe,EAAC,yBAAyB,OAAO,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;YACvG,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;KACF,CAAC;IACF,IAAA,2BAAgB,EAAC,MAAM,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;IACpE,OAAO,MAAM,CAAC;AAChB,CAAC","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';\nimport { mintResourceId, registerResource, retireResource, runCleanupSteps, TestDatabaseDisposeError } from '../resource';\n\nexport class TestSqliteError extends SqlStackError {\n constructor(message: string) {\n super(message);\n this.name = 'TestSqliteError';\n }\n}\n\nexport interface TestSqliteDatabase {\n /** Engine discriminator for SQLStack-minted disposable test databases. */\n readonly engine: 'sqlite';\n /** Fresh per-allocation identifier; never reused within a process. */\n readonly resourceId: string;\n /** Explicit storage: an owned temporary file, or an isolated `:memory:` db. */\n readonly storage: 'memory' | 'file';\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 /**\n * Close the connection and remove owned temporary files. 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\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\n/** Remove exactly the SQLStack-owned temp directory; never a caller path. */\nfunction removeOwnedDirectory(directory: string): void {\n fs.rmSync(directory, { recursive: true, force: true });\n // Keep failed paths in the owned inventory for the exit cleanup retry.\n abandonedDirectories.delete(directory);\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 try { removeOwnedDirectory(directory); }\n catch (cleanup) {\n throw Object.assign(new TestDatabaseDisposeError('SQLite test allocation failed and owned cleanup failed', [err, cleanup]), { cause: err });\n }\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 const storage: 'memory' | 'file' = fileBacked ? 'file' : 'memory';\n const resourceId = mintResourceId('sqlite');\n const ownedDirectory = directory;\n let disposal: Promise<void> | undefined;\n\n const handle: TestSqliteDatabase = {\n engine: 'sqlite',\n resourceId,\n storage,\n db,\n conn: db.conn,\n path: fileBacked ? filename : undefined,\n async query(sql: string, params: readonly unknown[] = []) {\n if (disposal) throw new TestSqliteError('Test SQLite database has been disposed.');\n return db.query(sql, [...params]);\n },\n dispose() {\n if (!disposal) {\n retireResource(handle);\n const steps: (() => Promise<void> | void)[] = [() => db.close()];\n if (ownedDirectory) steps.push(() => removeOwnedDirectory(ownedDirectory));\n disposal = Promise.resolve().then(() => runCleanupSteps(`Test SQLite database (${storage})`, steps));\n }\n return disposal;\n },\n };\n registerResource(handle, { engine: 'sqlite', resourceId, storage });\n return handle;\n}\n"]}
|
|
@@ -45,6 +45,12 @@ declare class TxState {
|
|
|
45
45
|
* from the root's SqlStack (`stack.getEntry(name)`).
|
|
46
46
|
*/
|
|
47
47
|
export declare function currentTransaction(db?: string): TransactionHandle | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Whether this async call carries any transaction records. This observation
|
|
50
|
+
* never resolves a database, opens a connection, or consults legacy defaults.
|
|
51
|
+
* It does not choose an owner: use currentTransactionFor(entry) for that.
|
|
52
|
+
*/
|
|
53
|
+
export declare function hasTransactionContext(): boolean;
|
|
48
54
|
/** Active transaction handle for a concrete database entry, if any. */
|
|
49
55
|
export declare function currentTransactionFor(entry: DatabaseEntry): TransactionHandle | undefined;
|
|
50
56
|
export declare function withTransaction<T>(fn: () => Promise<T>, options?: TransactionOptions): Promise<T>;
|
|
@@ -57,5 +63,13 @@ export type ActiveTransaction = {
|
|
|
57
63
|
query(sql: string, params: unknown[]): Promise<QueryResult>;
|
|
58
64
|
};
|
|
59
65
|
export declare function getActiveTransaction(entry: DatabaseEntry): ActiveTransaction | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Active transaction whose registered database is exactly `db` (object
|
|
68
|
+
* identity), if any. `run(db, ...)` uses this so a handle passed at call time
|
|
69
|
+
* joins a transaction opened on the same database (by `@transaction` or
|
|
70
|
+
* `withTransaction`) instead of bypassing it. A handle that was never
|
|
71
|
+
* registered with a SqlStack (or the legacy registry) never matches.
|
|
72
|
+
*/
|
|
73
|
+
export declare function getActiveTransactionForDatabase(db: object): ActiveTransaction | undefined;
|
|
60
74
|
export {};
|
|
61
75
|
//# sourceMappingURL=transactions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transactions.d.ts","sourceRoot":"","sources":["../../src/transactions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7C,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AA6BF,qBAAa,iBAAiB;;IAC5B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;gBAKlB,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO;IAMhE,IAAI,MAAM,IAAI,MAAM,CAEnB;IAEK,aAAa;IAInB,YAAY,CAAC,YAAY,CAAC,EAAE,OAAO;IAO7B,MAAM;IAKN,QAAQ;IAKR,OAAO;IAKP,eAAe;IAMf,aAAa,CAAC,GAAG,EAAE,OAAO;CAKjC;AAED,cAAM,OAAO;IACX,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;IAClG,KAAK,SAAK;IACV,OAAO,UAAS;IAChB,SAAS,UAAS;IAClB,YAAY,UAAS;IACrB,aAAa,EAAE,OAAO,GAAG,SAAS,CAAC;IAEnC,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,SAAS,CAAoC;gBAEzC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB,GAAG,SAAS;IASzE,IAAI,QAAQ,IAAI,QAAQ,CAEvB;IAEK,aAAa;IAuCb,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAK3D,eAAe;IAUf,iBAAiB;IAUjB,OAAO;IASP,eAAe;IAaf,aAAa,CAAC,GAAG,EAAE,OAAO;CAWjC;AAuBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAO7E;AAED,uEAAuE;AACvE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,aAAa,GAAG,iBAAiB,GAAG,SAAS,CAKzF;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC,CAkBvG;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACrE,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACxG,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,oBAYlB,CAAC;AAST,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CAC7D,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,iBAAiB,GAAG,SAAS,CAGxF"}
|
|
1
|
+
{"version":3,"file":"transactions.d.ts","sourceRoot":"","sources":["../../src/transactions.ts"],"names":[],"mappings":"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"}
|