@stackbone/sdk 0.1.0-alpha.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/db/index.cjs ADDED
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ var drizzleOrm = require('drizzle-orm');
4
+ var pgCore = require('drizzle-orm/pg-core');
5
+
6
+
7
+
8
+ Object.keys(drizzleOrm).forEach(function (k) {
9
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
10
+ enumerable: true,
11
+ get: function () { return drizzleOrm[k]; }
12
+ });
13
+ });
14
+ Object.keys(pgCore).forEach(function (k) {
15
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
16
+ enumerable: true,
17
+ get: function () { return pgCore[k]; }
18
+ });
19
+ });
20
+ //# sourceMappingURL=index.cjs.map
21
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.cjs","sourcesContent":[]}
package/db/index.d.cts ADDED
@@ -0,0 +1,3 @@
1
+ export * from 'drizzle-orm';
2
+ export * from 'drizzle-orm/pg-core';
3
+ export { ColumnsWithTable, SelectedFields, SelectedFieldsFlat, SelectedFieldsOrdered, TableConfig } from 'drizzle-orm/pg-core';
package/db/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from 'drizzle-orm';
2
+ export * from 'drizzle-orm/pg-core';
3
+ export { ColumnsWithTable, SelectedFields, SelectedFieldsFlat, SelectedFieldsOrdered, TableConfig } from 'drizzle-orm/pg-core';
package/db/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from 'drizzle-orm';
2
+ export * from 'drizzle-orm/pg-core';
3
+ //# sourceMappingURL=index.js.map
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourcesContent":[]}
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+ var postgresJs = require('drizzle-orm/postgres-js');
5
+ var migrator = require('drizzle-orm/postgres-js/migrator');
6
+ var postgres = require('postgres');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var postgres__default = /*#__PURE__*/_interopDefault(postgres);
11
+
12
+ var __defProp = Object.defineProperty;
13
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
14
+ var resolveAdminUrl = /* @__PURE__ */ __name((override) => override ?? process.env["STACKBONE_TEST_POSTGRES_URL"] ?? process.env["STACKBONE_POSTGRES_URL"] ?? process.env["DATABASE_URL"] ?? "postgres://stackbone:stackbone@localhost:5432/stackbone", "resolveAdminUrl");
15
+ var generateSchemaName = /* @__PURE__ */ __name(() => `stackbone_test_${crypto.randomBytes(8).toString("hex")}`, "generateSchemaName");
16
+ var tagConnectionStringWithSchema = /* @__PURE__ */ __name((url, schemaName) => {
17
+ const u = new URL(url);
18
+ u.searchParams.set("schema", schemaName);
19
+ return u.toString();
20
+ }, "tagConnectionStringWithSchema");
21
+ var dropSchema = /* @__PURE__ */ __name(async (adminUrl, schemaName) => {
22
+ const admin = postgres__default.default(adminUrl, {
23
+ max: 1,
24
+ onnotice: /* @__PURE__ */ __name(() => void 0, "onnotice")
25
+ });
26
+ try {
27
+ await admin.unsafe(`DROP SCHEMA "${schemaName.replace(/"/g, '""')}" CASCADE`);
28
+ } finally {
29
+ await admin.end({
30
+ timeout: 5
31
+ });
32
+ }
33
+ }, "dropSchema");
34
+ var createTestDatabase = /* @__PURE__ */ __name(async (options) => {
35
+ const adminUrl = resolveAdminUrl(options.connectionString);
36
+ const schemaName = generateSchemaName();
37
+ const admin = postgres__default.default(adminUrl, {
38
+ max: 1,
39
+ onnotice: /* @__PURE__ */ __name(() => void 0, "onnotice")
40
+ });
41
+ try {
42
+ await admin`CREATE SCHEMA ${admin(schemaName)}`;
43
+ } finally {
44
+ await admin.end({
45
+ timeout: 5
46
+ });
47
+ }
48
+ const client = postgres__default.default(adminUrl, {
49
+ max: 5,
50
+ onnotice: /* @__PURE__ */ __name(() => void 0, "onnotice"),
51
+ connection: {
52
+ search_path: schemaName
53
+ }
54
+ });
55
+ const db = postgresJs.drizzle(client);
56
+ let disposed = false;
57
+ const dispose = /* @__PURE__ */ __name(async () => {
58
+ if (disposed) return;
59
+ disposed = true;
60
+ await client.end({
61
+ timeout: 5
62
+ }).catch(() => void 0);
63
+ await dropSchema(adminUrl, schemaName).catch(() => void 0);
64
+ }, "dispose");
65
+ try {
66
+ await migrator.migrate(db, {
67
+ migrationsFolder: options.migrationsDir,
68
+ migrationsSchema: schemaName
69
+ });
70
+ } catch (err) {
71
+ await dispose();
72
+ throw err;
73
+ }
74
+ return {
75
+ connectionString: tagConnectionStringWithSchema(adminUrl, schemaName),
76
+ drizzle: db,
77
+ dispose
78
+ };
79
+ }, "createTestDatabase");
80
+
81
+ exports.createTestDatabase = createTestDatabase;
82
+ //# sourceMappingURL=index.cjs.map
83
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../libs/sdk/src/db/testing/index.ts"],"names":["resolveAdminUrl","override","process","env","generateSchemaName","randomBytes","toString","tagConnectionStringWithSchema","url","schemaName","u","URL","searchParams","set","dropSchema","adminUrl","admin","postgres","max","onnotice","undefined","unsafe","replace","end","timeout","createTestDatabase","options","connectionString","client","connection","search_path","db","drizzle","disposed","dispose","catch","migrate","migrationsFolder","migrationsDir","migrationsSchema","err"],"mappings":"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","file":"index.cjs","sourcesContent":["// Public API for `@stackbone/sdk/db/testing`.\n//\n// `createTestDatabase` provisions an ephemeral, randomly-named Postgres schema\n// inside a shared developer database (the docker-compose `stackbone-postgres`\n// container in dev, or whatever Postgres the consumer points at via env or\n// `connectionString`) and applies a drizzle-kit-shaped migrations folder\n// against it. Returns a ready-to-query Drizzle instance plus a `dispose()`\n// that drops the schema and closes the pool — guaranteeing that two parallel\n// tests neither bleed rows nor leak schemas after the suite ends.\n//\n// Why a schema instead of a fresh database or a testcontainer:\n// - Schema creation is ~milliseconds (CREATE DATABASE on Postgres takes a\n// real lock and copies template1).\n// - One pre-pulled Postgres image (the dev compose container) avoids the\n// per-suite testcontainer boot cost (~3-5s) on every `nx test`.\n// - `search_path` isolation keeps every creator's migration SQL — which\n// references unqualified table names — applying to the throwaway schema\n// without rewriting their `.sql` files.\n//\n// Why not reuse `apps/cli/src/dev/db-migrator.ts`:\n// - That module owns the platform's advisory-lock + `__stackbone_migrations__`\n// journal table, which is shared across the whole DB and explicitly NOT\n// scoped to a schema. Tests run inside their own schema, so the standard\n// drizzle migrator (`drizzle-orm/postgres-js/migrator`) with\n// `migrationsSchema` pointed at the throwaway schema is the right primitive.\n\nimport { randomBytes } from 'node:crypto';\nimport { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js';\nimport { migrate } from 'drizzle-orm/postgres-js/migrator';\nimport postgres, { type Sql } from 'postgres';\n\n/** Inputs for `createTestDatabase`. */\nexport interface CreateTestDatabaseOptions {\n /**\n * Drizzle-kit-shaped migrations directory: `meta/_journal.json` plus one\n * `<tag>.sql` file per entry. Required — the helper applies the migrations\n * before returning so the first query a test runs already sees the schema.\n */\n migrationsDir: string;\n /**\n * Optional Postgres URL pointing at the shared dev DB. Falls back, in order,\n * to:\n * 1. `STACKBONE_TEST_POSTGRES_URL` (preferred — lets CI override\n * independently of the agent's runtime URL).\n * 2. `STACKBONE_POSTGRES_URL` (the env var the rest of the SDK reads).\n * 3. `DATABASE_URL` (the env var loaded by `node --env-file=.env`).\n * 4. `postgres://stackbone:stackbone@localhost:5432/stackbone` (the\n * docker-compose default — the same DB `pnpm db:migrate` hits).\n */\n connectionString?: string;\n}\n\n/** Result of `createTestDatabase`. */\nexport interface TestDatabase {\n /**\n * Postgres URL pinned to the throwaway schema via the `?schema=<name>` query\n * parameter. Useful for sub-processes (drizzle-kit studio, a worker) that\n * need to share the same isolation.\n */\n connectionString: string;\n /** Drizzle handle bound to the throwaway schema, ready to query. */\n drizzle: PostgresJsDatabase<Record<string, never>>;\n /**\n * Tears the test DB down: closes the pool, then drops the schema with\n * CASCADE so dependent tables/indexes/sequences vanish in one statement.\n * Idempotent — calling it twice is a no-op.\n */\n dispose: () => Promise<void>;\n}\n\n/**\n * Resolves the admin Postgres URL. See `connectionString` on\n * `CreateTestDatabaseOptions` for the env-fallback chain.\n */\nconst resolveAdminUrl = (override?: string): string =>\n override ??\n process.env['STACKBONE_TEST_POSTGRES_URL'] ??\n process.env['STACKBONE_POSTGRES_URL'] ??\n process.env['DATABASE_URL'] ??\n 'postgres://stackbone:stackbone@localhost:5432/stackbone';\n\n/**\n * Generates a random schema name short enough to fit Postgres's 63-char\n * identifier limit while still being unique across parallel workers.\n */\nconst generateSchemaName = (): string => `stackbone_test_${randomBytes(8).toString('hex')}`;\n\n/**\n * Encodes a `?schema=<name>` query parameter into the URL so callers (and\n * sub-processes) can opt into the same isolation by reading their own URL.\n * Metadata for the caller — libpq itself does not honour it.\n */\nconst tagConnectionStringWithSchema = (url: string, schemaName: string): string => {\n const u = new URL(url);\n u.searchParams.set('schema', schemaName);\n return u.toString();\n};\n\n/** Drops the throwaway schema via a one-shot admin connection. */\nconst dropSchema = async (adminUrl: string, schemaName: string): Promise<void> => {\n const admin = postgres(adminUrl, { max: 1, onnotice: () => undefined });\n try {\n await admin.unsafe(`DROP SCHEMA \"${schemaName.replace(/\"/g, '\"\"')}\" CASCADE`);\n } finally {\n await admin.end({ timeout: 5 });\n }\n};\n\n/**\n * Provisions an ephemeral, randomly-named schema inside the shared dev\n * Postgres, applies the supplied migrations directory against it, and hands\n * back a Drizzle instance + `dispose()` for tear-down. See module header for\n * design rationale.\n */\nexport const createTestDatabase = async (\n options: CreateTestDatabaseOptions,\n): Promise<TestDatabase> => {\n const adminUrl = resolveAdminUrl(options.connectionString);\n const schemaName = generateSchemaName();\n\n // Step 1 — admin client to bootstrap the schema. Tiny pool because we only\n // run one DDL statement before tearing the connection down.\n const admin = postgres(adminUrl, { max: 1, onnotice: () => undefined });\n try {\n await admin`CREATE SCHEMA ${admin(schemaName)}`;\n } finally {\n await admin.end({ timeout: 5 });\n }\n\n // Step 2 — scoped client. `search_path` is set at connection time so every\n // statement (DDL from the migrations and the consumer's own queries) lands\n // inside the throwaway schema without qualifying table names.\n const client: Sql = postgres(adminUrl, {\n max: 5,\n onnotice: () => undefined,\n connection: { search_path: schemaName },\n });\n const db = drizzle(client);\n\n let disposed = false;\n const dispose = async (): Promise<void> => {\n if (disposed) return;\n disposed = true;\n await client.end({ timeout: 5 }).catch(() => undefined);\n await dropSchema(adminUrl, schemaName).catch(() => undefined);\n };\n\n // Step 3 — apply migrations. `migrationsSchema` keeps Drizzle's journal\n // table inside the throwaway schema too, so the schema-drop in `dispose`\n // wipes both creator-owned tables and Drizzle's own bookkeeping in one\n // statement. On failure, dispose so a broken fixture does not leak a\n // half-bootstrapped schema for the rest of the suite.\n try {\n await migrate(db, {\n migrationsFolder: options.migrationsDir,\n migrationsSchema: schemaName,\n });\n } catch (err) {\n await dispose();\n throw err;\n }\n\n return {\n connectionString: tagConnectionStringWithSchema(adminUrl, schemaName),\n drizzle: db,\n dispose,\n };\n};\n"]}
@@ -0,0 +1,48 @@
1
+ import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
2
+
3
+ /** Inputs for `createTestDatabase`. */
4
+ interface CreateTestDatabaseOptions {
5
+ /**
6
+ * Drizzle-kit-shaped migrations directory: `meta/_journal.json` plus one
7
+ * `<tag>.sql` file per entry. Required — the helper applies the migrations
8
+ * before returning so the first query a test runs already sees the schema.
9
+ */
10
+ migrationsDir: string;
11
+ /**
12
+ * Optional Postgres URL pointing at the shared dev DB. Falls back, in order,
13
+ * to:
14
+ * 1. `STACKBONE_TEST_POSTGRES_URL` (preferred — lets CI override
15
+ * independently of the agent's runtime URL).
16
+ * 2. `STACKBONE_POSTGRES_URL` (the env var the rest of the SDK reads).
17
+ * 3. `DATABASE_URL` (the env var loaded by `node --env-file=.env`).
18
+ * 4. `postgres://stackbone:stackbone@localhost:5432/stackbone` (the
19
+ * docker-compose default — the same DB `pnpm db:migrate` hits).
20
+ */
21
+ connectionString?: string;
22
+ }
23
+ /** Result of `createTestDatabase`. */
24
+ interface TestDatabase {
25
+ /**
26
+ * Postgres URL pinned to the throwaway schema via the `?schema=<name>` query
27
+ * parameter. Useful for sub-processes (drizzle-kit studio, a worker) that
28
+ * need to share the same isolation.
29
+ */
30
+ connectionString: string;
31
+ /** Drizzle handle bound to the throwaway schema, ready to query. */
32
+ drizzle: PostgresJsDatabase<Record<string, never>>;
33
+ /**
34
+ * Tears the test DB down: closes the pool, then drops the schema with
35
+ * CASCADE so dependent tables/indexes/sequences vanish in one statement.
36
+ * Idempotent — calling it twice is a no-op.
37
+ */
38
+ dispose: () => Promise<void>;
39
+ }
40
+ /**
41
+ * Provisions an ephemeral, randomly-named schema inside the shared dev
42
+ * Postgres, applies the supplied migrations directory against it, and hands
43
+ * back a Drizzle instance + `dispose()` for tear-down. See module header for
44
+ * design rationale.
45
+ */
46
+ declare const createTestDatabase: (options: CreateTestDatabaseOptions) => Promise<TestDatabase>;
47
+
48
+ export { type CreateTestDatabaseOptions, type TestDatabase, createTestDatabase };
@@ -0,0 +1,48 @@
1
+ import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
2
+
3
+ /** Inputs for `createTestDatabase`. */
4
+ interface CreateTestDatabaseOptions {
5
+ /**
6
+ * Drizzle-kit-shaped migrations directory: `meta/_journal.json` plus one
7
+ * `<tag>.sql` file per entry. Required — the helper applies the migrations
8
+ * before returning so the first query a test runs already sees the schema.
9
+ */
10
+ migrationsDir: string;
11
+ /**
12
+ * Optional Postgres URL pointing at the shared dev DB. Falls back, in order,
13
+ * to:
14
+ * 1. `STACKBONE_TEST_POSTGRES_URL` (preferred — lets CI override
15
+ * independently of the agent's runtime URL).
16
+ * 2. `STACKBONE_POSTGRES_URL` (the env var the rest of the SDK reads).
17
+ * 3. `DATABASE_URL` (the env var loaded by `node --env-file=.env`).
18
+ * 4. `postgres://stackbone:stackbone@localhost:5432/stackbone` (the
19
+ * docker-compose default — the same DB `pnpm db:migrate` hits).
20
+ */
21
+ connectionString?: string;
22
+ }
23
+ /** Result of `createTestDatabase`. */
24
+ interface TestDatabase {
25
+ /**
26
+ * Postgres URL pinned to the throwaway schema via the `?schema=<name>` query
27
+ * parameter. Useful for sub-processes (drizzle-kit studio, a worker) that
28
+ * need to share the same isolation.
29
+ */
30
+ connectionString: string;
31
+ /** Drizzle handle bound to the throwaway schema, ready to query. */
32
+ drizzle: PostgresJsDatabase<Record<string, never>>;
33
+ /**
34
+ * Tears the test DB down: closes the pool, then drops the schema with
35
+ * CASCADE so dependent tables/indexes/sequences vanish in one statement.
36
+ * Idempotent — calling it twice is a no-op.
37
+ */
38
+ dispose: () => Promise<void>;
39
+ }
40
+ /**
41
+ * Provisions an ephemeral, randomly-named schema inside the shared dev
42
+ * Postgres, applies the supplied migrations directory against it, and hands
43
+ * back a Drizzle instance + `dispose()` for tear-down. See module header for
44
+ * design rationale.
45
+ */
46
+ declare const createTestDatabase: (options: CreateTestDatabaseOptions) => Promise<TestDatabase>;
47
+
48
+ export { type CreateTestDatabaseOptions, type TestDatabase, createTestDatabase };
@@ -0,0 +1,77 @@
1
+ import { randomBytes } from 'crypto';
2
+ import { drizzle } from 'drizzle-orm/postgres-js';
3
+ import { migrate } from 'drizzle-orm/postgres-js/migrator';
4
+ import postgres from 'postgres';
5
+
6
+ var __defProp = Object.defineProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+ var resolveAdminUrl = /* @__PURE__ */ __name((override) => override ?? process.env["STACKBONE_TEST_POSTGRES_URL"] ?? process.env["STACKBONE_POSTGRES_URL"] ?? process.env["DATABASE_URL"] ?? "postgres://stackbone:stackbone@localhost:5432/stackbone", "resolveAdminUrl");
9
+ var generateSchemaName = /* @__PURE__ */ __name(() => `stackbone_test_${randomBytes(8).toString("hex")}`, "generateSchemaName");
10
+ var tagConnectionStringWithSchema = /* @__PURE__ */ __name((url, schemaName) => {
11
+ const u = new URL(url);
12
+ u.searchParams.set("schema", schemaName);
13
+ return u.toString();
14
+ }, "tagConnectionStringWithSchema");
15
+ var dropSchema = /* @__PURE__ */ __name(async (adminUrl, schemaName) => {
16
+ const admin = postgres(adminUrl, {
17
+ max: 1,
18
+ onnotice: /* @__PURE__ */ __name(() => void 0, "onnotice")
19
+ });
20
+ try {
21
+ await admin.unsafe(`DROP SCHEMA "${schemaName.replace(/"/g, '""')}" CASCADE`);
22
+ } finally {
23
+ await admin.end({
24
+ timeout: 5
25
+ });
26
+ }
27
+ }, "dropSchema");
28
+ var createTestDatabase = /* @__PURE__ */ __name(async (options) => {
29
+ const adminUrl = resolveAdminUrl(options.connectionString);
30
+ const schemaName = generateSchemaName();
31
+ const admin = postgres(adminUrl, {
32
+ max: 1,
33
+ onnotice: /* @__PURE__ */ __name(() => void 0, "onnotice")
34
+ });
35
+ try {
36
+ await admin`CREATE SCHEMA ${admin(schemaName)}`;
37
+ } finally {
38
+ await admin.end({
39
+ timeout: 5
40
+ });
41
+ }
42
+ const client = postgres(adminUrl, {
43
+ max: 5,
44
+ onnotice: /* @__PURE__ */ __name(() => void 0, "onnotice"),
45
+ connection: {
46
+ search_path: schemaName
47
+ }
48
+ });
49
+ const db = drizzle(client);
50
+ let disposed = false;
51
+ const dispose = /* @__PURE__ */ __name(async () => {
52
+ if (disposed) return;
53
+ disposed = true;
54
+ await client.end({
55
+ timeout: 5
56
+ }).catch(() => void 0);
57
+ await dropSchema(adminUrl, schemaName).catch(() => void 0);
58
+ }, "dispose");
59
+ try {
60
+ await migrate(db, {
61
+ migrationsFolder: options.migrationsDir,
62
+ migrationsSchema: schemaName
63
+ });
64
+ } catch (err) {
65
+ await dispose();
66
+ throw err;
67
+ }
68
+ return {
69
+ connectionString: tagConnectionStringWithSchema(adminUrl, schemaName),
70
+ drizzle: db,
71
+ dispose
72
+ };
73
+ }, "createTestDatabase");
74
+
75
+ export { createTestDatabase };
76
+ //# sourceMappingURL=index.js.map
77
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../libs/sdk/src/db/testing/index.ts"],"names":["resolveAdminUrl","override","process","env","generateSchemaName","randomBytes","toString","tagConnectionStringWithSchema","url","schemaName","u","URL","searchParams","set","dropSchema","adminUrl","admin","postgres","max","onnotice","undefined","unsafe","replace","end","timeout","createTestDatabase","options","connectionString","client","connection","search_path","db","drizzle","disposed","dispose","catch","migrate","migrationsFolder","migrationsDir","migrationsSchema","err"],"mappings":"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","file":"index.js","sourcesContent":["// Public API for `@stackbone/sdk/db/testing`.\n//\n// `createTestDatabase` provisions an ephemeral, randomly-named Postgres schema\n// inside a shared developer database (the docker-compose `stackbone-postgres`\n// container in dev, or whatever Postgres the consumer points at via env or\n// `connectionString`) and applies a drizzle-kit-shaped migrations folder\n// against it. Returns a ready-to-query Drizzle instance plus a `dispose()`\n// that drops the schema and closes the pool — guaranteeing that two parallel\n// tests neither bleed rows nor leak schemas after the suite ends.\n//\n// Why a schema instead of a fresh database or a testcontainer:\n// - Schema creation is ~milliseconds (CREATE DATABASE on Postgres takes a\n// real lock and copies template1).\n// - One pre-pulled Postgres image (the dev compose container) avoids the\n// per-suite testcontainer boot cost (~3-5s) on every `nx test`.\n// - `search_path` isolation keeps every creator's migration SQL — which\n// references unqualified table names — applying to the throwaway schema\n// without rewriting their `.sql` files.\n//\n// Why not reuse `apps/cli/src/dev/db-migrator.ts`:\n// - That module owns the platform's advisory-lock + `__stackbone_migrations__`\n// journal table, which is shared across the whole DB and explicitly NOT\n// scoped to a schema. Tests run inside their own schema, so the standard\n// drizzle migrator (`drizzle-orm/postgres-js/migrator`) with\n// `migrationsSchema` pointed at the throwaway schema is the right primitive.\n\nimport { randomBytes } from 'node:crypto';\nimport { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js';\nimport { migrate } from 'drizzle-orm/postgres-js/migrator';\nimport postgres, { type Sql } from 'postgres';\n\n/** Inputs for `createTestDatabase`. */\nexport interface CreateTestDatabaseOptions {\n /**\n * Drizzle-kit-shaped migrations directory: `meta/_journal.json` plus one\n * `<tag>.sql` file per entry. Required — the helper applies the migrations\n * before returning so the first query a test runs already sees the schema.\n */\n migrationsDir: string;\n /**\n * Optional Postgres URL pointing at the shared dev DB. Falls back, in order,\n * to:\n * 1. `STACKBONE_TEST_POSTGRES_URL` (preferred — lets CI override\n * independently of the agent's runtime URL).\n * 2. `STACKBONE_POSTGRES_URL` (the env var the rest of the SDK reads).\n * 3. `DATABASE_URL` (the env var loaded by `node --env-file=.env`).\n * 4. `postgres://stackbone:stackbone@localhost:5432/stackbone` (the\n * docker-compose default — the same DB `pnpm db:migrate` hits).\n */\n connectionString?: string;\n}\n\n/** Result of `createTestDatabase`. */\nexport interface TestDatabase {\n /**\n * Postgres URL pinned to the throwaway schema via the `?schema=<name>` query\n * parameter. Useful for sub-processes (drizzle-kit studio, a worker) that\n * need to share the same isolation.\n */\n connectionString: string;\n /** Drizzle handle bound to the throwaway schema, ready to query. */\n drizzle: PostgresJsDatabase<Record<string, never>>;\n /**\n * Tears the test DB down: closes the pool, then drops the schema with\n * CASCADE so dependent tables/indexes/sequences vanish in one statement.\n * Idempotent — calling it twice is a no-op.\n */\n dispose: () => Promise<void>;\n}\n\n/**\n * Resolves the admin Postgres URL. See `connectionString` on\n * `CreateTestDatabaseOptions` for the env-fallback chain.\n */\nconst resolveAdminUrl = (override?: string): string =>\n override ??\n process.env['STACKBONE_TEST_POSTGRES_URL'] ??\n process.env['STACKBONE_POSTGRES_URL'] ??\n process.env['DATABASE_URL'] ??\n 'postgres://stackbone:stackbone@localhost:5432/stackbone';\n\n/**\n * Generates a random schema name short enough to fit Postgres's 63-char\n * identifier limit while still being unique across parallel workers.\n */\nconst generateSchemaName = (): string => `stackbone_test_${randomBytes(8).toString('hex')}`;\n\n/**\n * Encodes a `?schema=<name>` query parameter into the URL so callers (and\n * sub-processes) can opt into the same isolation by reading their own URL.\n * Metadata for the caller — libpq itself does not honour it.\n */\nconst tagConnectionStringWithSchema = (url: string, schemaName: string): string => {\n const u = new URL(url);\n u.searchParams.set('schema', schemaName);\n return u.toString();\n};\n\n/** Drops the throwaway schema via a one-shot admin connection. */\nconst dropSchema = async (adminUrl: string, schemaName: string): Promise<void> => {\n const admin = postgres(adminUrl, { max: 1, onnotice: () => undefined });\n try {\n await admin.unsafe(`DROP SCHEMA \"${schemaName.replace(/\"/g, '\"\"')}\" CASCADE`);\n } finally {\n await admin.end({ timeout: 5 });\n }\n};\n\n/**\n * Provisions an ephemeral, randomly-named schema inside the shared dev\n * Postgres, applies the supplied migrations directory against it, and hands\n * back a Drizzle instance + `dispose()` for tear-down. See module header for\n * design rationale.\n */\nexport const createTestDatabase = async (\n options: CreateTestDatabaseOptions,\n): Promise<TestDatabase> => {\n const adminUrl = resolveAdminUrl(options.connectionString);\n const schemaName = generateSchemaName();\n\n // Step 1 — admin client to bootstrap the schema. Tiny pool because we only\n // run one DDL statement before tearing the connection down.\n const admin = postgres(adminUrl, { max: 1, onnotice: () => undefined });\n try {\n await admin`CREATE SCHEMA ${admin(schemaName)}`;\n } finally {\n await admin.end({ timeout: 5 });\n }\n\n // Step 2 — scoped client. `search_path` is set at connection time so every\n // statement (DDL from the migrations and the consumer's own queries) lands\n // inside the throwaway schema without qualifying table names.\n const client: Sql = postgres(adminUrl, {\n max: 5,\n onnotice: () => undefined,\n connection: { search_path: schemaName },\n });\n const db = drizzle(client);\n\n let disposed = false;\n const dispose = async (): Promise<void> => {\n if (disposed) return;\n disposed = true;\n await client.end({ timeout: 5 }).catch(() => undefined);\n await dropSchema(adminUrl, schemaName).catch(() => undefined);\n };\n\n // Step 3 — apply migrations. `migrationsSchema` keeps Drizzle's journal\n // table inside the throwaway schema too, so the schema-drop in `dispose`\n // wipes both creator-owned tables and Drizzle's own bookkeeping in one\n // statement. On failure, dispose so a broken fixture does not leak a\n // half-bootstrapped schema for the rest of the suite.\n try {\n await migrate(db, {\n migrationsFolder: options.migrationsDir,\n migrationsSchema: schemaName,\n });\n } catch (err) {\n await dispose();\n throw err;\n }\n\n return {\n connectionString: tagConnectionStringWithSchema(adminUrl, schemaName),\n drizzle: db,\n dispose,\n };\n};\n"]}