@opengeni/db 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/chunk-57MLICFR.js +121 -0
  2. package/dist/chunk-57MLICFR.js.map +1 -0
  3. package/dist/chunk-OGCE6O2X.js +52 -0
  4. package/dist/chunk-OGCE6O2X.js.map +1 -0
  5. package/dist/chunk-PSX56ZTL.js +1093 -0
  6. package/dist/chunk-PSX56ZTL.js.map +1 -0
  7. package/dist/chunk-PZ5AY32C.js +10 -0
  8. package/dist/chunk-PZ5AY32C.js.map +1 -0
  9. package/dist/index.d.ts +8 -0
  10. package/dist/index.js +5165 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/migrate.d.ts +40 -0
  13. package/dist/migrate.js +10 -0
  14. package/dist/migrate.js.map +1 -0
  15. package/dist/provision-roles.d.ts +2063 -0
  16. package/dist/provision-roles.js +8 -0
  17. package/dist/provision-roles.js.map +1 -0
  18. package/dist/schema-CaeZQAJQ.d.ts +9705 -0
  19. package/dist/schema.d.ts +3 -0
  20. package/dist/schema.js +110 -0
  21. package/dist/schema.js.map +1 -0
  22. package/drizzle/0000_initial.sql +179 -0
  23. package/drizzle/0001_workspace_auth_billing.sql +590 -0
  24. package/drizzle/0002_packs_and_social.sql +99 -0
  25. package/drizzle/0003_capability_catalog.sql +73 -0
  26. package/drizzle/0004_workspace_environments.sql +65 -0
  27. package/drizzle/0005_session_goals.sql +45 -0
  28. package/drizzle/0006_workspace_packs.sql +31 -0
  29. package/drizzle/0007_session_history_items.sql +66 -0
  30. package/drizzle/0008_session_first_party_mcp_permissions.sql +5 -0
  31. package/drizzle/0009_goal_sessions_first_party_goals_manage.sql +34 -0
  32. package/drizzle/0010_session_parent_linkage.sql +30 -0
  33. package/drizzle/0011_context_compaction.sql +33 -0
  34. package/drizzle/0012_compaction_summary_fractional_position.sql +19 -0
  35. package/drizzle/0013_session_compact_requested.sql +16 -0
  36. package/drizzle/0014_repair_orphaned_function_call_results.sql +125 -0
  37. package/drizzle/0015_workspace_agent_instructions.sql +17 -0
  38. package/drizzle/0016_session_create_idempotency.sql +27 -0
  39. package/drizzle/0017_sandbox_leases.sql +313 -0
  40. package/drizzle/0018_sandbox_os.sql +89 -0
  41. package/drizzle/0019_session_stream_acknowledgments.sql +94 -0
  42. package/drizzle/0020_session_recordings.sql +88 -0
  43. package/drizzle/0021_sandbox_pty_sessions.sql +70 -0
  44. package/drizzle/0022_sandbox_lease_terminal_url.sql +32 -0
  45. package/drizzle/0023_session_title.sql +19 -0
  46. package/drizzle/0024_codex_subscription_credentials.sql +51 -0
  47. package/drizzle/0024_sandboxes_enrollments_metrics.sql +262 -0
  48. package/drizzle/0025_device_enrollment_requests.sql +142 -0
  49. package/drizzle/0026_device_enrollment_user_code_resolver.sql +47 -0
  50. package/drizzle/0027_session_working_dir.sql +24 -0
  51. package/drizzle/0028_codex_multi_account.sql +85 -0
  52. package/drizzle/0029_session_history_item_producer.sql +31 -0
  53. package/drizzle/0030_agent_run_state_frozen_codex.sql +35 -0
  54. package/drizzle/0031_codex_usage_cache.sql +21 -0
  55. package/drizzle/0032_codex_account_cooldown.sql +18 -0
  56. package/drizzle/0033_codex_connector_cache.sql +20 -0
  57. package/drizzle/0034_sandbox_lease_image.sql +21 -0
  58. package/drizzle/meta/_journal.json +167 -0
  59. package/package.json +66 -0
  60. package/src/codex-token-resolver.ts +247 -0
  61. package/src/environment-crypto.ts +51 -0
  62. package/src/event-payload-sanitizer.ts +89 -0
  63. package/src/index.ts +7776 -0
  64. package/src/migrate.ts +95 -0
  65. package/src/provision-roles.ts +198 -0
  66. package/src/schema.ts +1110 -0
@@ -0,0 +1,121 @@
1
+ // src/provision-roles.ts
2
+ import postgres from "postgres";
3
+ async function provisionRoles(adminConnection, options = {}) {
4
+ const schema = validateIdentifier("targetSchema", options.targetSchema ?? "public");
5
+ const rlsStrategy = options.rlsStrategy ?? "force";
6
+ const appRole = validateIdentifier("appRole", options.appRole ?? (process.env.OPENGENI_APP_DATABASE_USER?.trim() || "opengeni_app"));
7
+ const appPassword = options.appPassword ?? process.env.OPENGENI_APP_DATABASE_PASSWORD;
8
+ const temporalRole = validateIdentifier("temporalRole", options.temporalRole ?? (process.env.OPENGENI_TEMPORAL_DATABASE_USER?.trim() || "opengeni_temporal"));
9
+ const temporalPassword = options.temporalPassword ?? process.env.OPENGENI_TEMPORAL_DATABASE_PASSWORD;
10
+ const temporalDatabases = (options.temporalDatabases ?? commaSeparated(process.env.OPENGENI_TEMPORAL_DATABASES ?? "temporal,temporal_visibility")).map((name) => validateIdentifier("temporalDatabases", name));
11
+ const sql = postgres(adminConnection, { max: 1 });
12
+ try {
13
+ let provisionedAppRole = null;
14
+ if (rlsStrategy === "force") {
15
+ if (!appPassword) {
16
+ throw new Error("OPENGENI_APP_DATABASE_PASSWORD (or appPassword) is required for rlsStrategy 'force'");
17
+ }
18
+ await ensureLoginRole(sql, appRole, appPassword);
19
+ provisionedAppRole = appRole;
20
+ }
21
+ if (temporalPassword) {
22
+ await ensureLoginRole(sql, temporalRole, temporalPassword);
23
+ for (const database of temporalDatabases) {
24
+ await ensureDatabase(sql, database, temporalRole);
25
+ await grantTemporalRoleInDatabase(adminConnection, database, temporalRole);
26
+ }
27
+ }
28
+ if (rlsStrategy === "force") {
29
+ await grantAppRoleIfSchemaExists(sql, appRole, schema);
30
+ }
31
+ return {
32
+ appRole: provisionedAppRole,
33
+ temporalRole: temporalPassword ? temporalRole : null,
34
+ temporalDatabases: temporalPassword ? temporalDatabases : [],
35
+ schema,
36
+ rlsStrategy
37
+ };
38
+ } finally {
39
+ await sql.end();
40
+ }
41
+ }
42
+ async function ensureLoginRole(sql, role, password) {
43
+ await sql.unsafe(`
44
+ DO $$
45
+ BEGIN
46
+ IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = ${literal(role)}) THEN
47
+ EXECUTE format('CREATE ROLE %I LOGIN PASSWORD %L', ${literal(role)}, ${literal(password)});
48
+ ELSE
49
+ EXECUTE format('ALTER ROLE %I LOGIN PASSWORD %L', ${literal(role)}, ${literal(password)});
50
+ END IF;
51
+ END $$;
52
+ `);
53
+ }
54
+ async function ensureDatabase(sql, database, owner) {
55
+ const existing = await sql`
56
+ select exists(select 1 from pg_database where datname = ${database}) as exists
57
+ `;
58
+ if (!existing[0]?.exists) {
59
+ await sql.unsafe(`CREATE DATABASE ${identifier(database)} OWNER ${identifier(owner)}`);
60
+ }
61
+ await sql.unsafe(`GRANT ALL PRIVILEGES ON DATABASE ${identifier(database)} TO ${identifier(owner)}`);
62
+ }
63
+ async function grantTemporalRoleInDatabase(adminConnection, database, role) {
64
+ const databaseUrl = databaseUrlFor(adminConnection, database);
65
+ const databaseSql = postgres(databaseUrl, { max: 1 });
66
+ try {
67
+ await databaseSql.unsafe(`GRANT USAGE, CREATE ON SCHEMA public TO ${identifier(role)}`);
68
+ } finally {
69
+ await databaseSql.end();
70
+ }
71
+ }
72
+ async function grantAppRoleIfSchemaExists(sql, role, schema) {
73
+ await sql.unsafe(`
74
+ DO $$
75
+ BEGIN
76
+ IF EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = ${literal(schema)}) THEN
77
+ EXECUTE format('GRANT USAGE ON SCHEMA %I TO %I', ${literal(schema)}, ${literal(role)});
78
+ EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO %I', ${literal(schema)}, ${literal(role)});
79
+ END IF;
80
+ IF EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'opengeni_private') THEN
81
+ EXECUTE format('GRANT USAGE ON SCHEMA opengeni_private TO %I', ${literal(role)});
82
+ EXECUTE format('GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA opengeni_private TO %I', ${literal(role)});
83
+ END IF;
84
+ END $$;
85
+ `);
86
+ }
87
+ function commaSeparated(value) {
88
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
89
+ }
90
+ function validateIdentifier(name, value) {
91
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
92
+ throw new Error(`${name} contains an invalid Postgres identifier: ${value}`);
93
+ }
94
+ return value;
95
+ }
96
+ function identifier(value) {
97
+ return `"${value.replace(/"/g, '""')}"`;
98
+ }
99
+ function literal(value) {
100
+ return `'${value.replace(/'/g, "''")}'`;
101
+ }
102
+ function databaseUrlFor(value, database) {
103
+ const url = new URL(value);
104
+ url.pathname = `/${database}`;
105
+ return url.toString();
106
+ }
107
+ if (import.meta.main) {
108
+ const adminUrl = process.env.OPENGENI_MIGRATIONS_DATABASE_URL ?? process.env.OPENGENI_DATABASE_ADMIN_URL ?? process.env.OPENGENI_DATABASE_URL;
109
+ if (!adminUrl) {
110
+ throw new Error("OPENGENI_MIGRATIONS_DATABASE_URL, OPENGENI_DATABASE_ADMIN_URL, or OPENGENI_DATABASE_URL is required");
111
+ }
112
+ const result = await provisionRoles(adminUrl, {
113
+ ...process.env.OPENGENI_DB_SCHEMA?.trim() ? { targetSchema: process.env.OPENGENI_DB_SCHEMA.trim() } : {}
114
+ });
115
+ console.log(JSON.stringify(result, null, 2));
116
+ }
117
+
118
+ export {
119
+ provisionRoles
120
+ };
121
+ //# sourceMappingURL=chunk-57MLICFR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provision-roles.ts"],"sourcesContent":["import postgres from \"postgres\";\nimport type { RlsStrategy } from \"./index\";\n\nexport type ProvisionResult = {\n appRole: string | null;\n temporalRole: string | null;\n temporalDatabases: string[];\n schema: string;\n rlsStrategy: RlsStrategy;\n};\n\nexport type ProvisionRolesOptions = {\n /**\n * The schema OpenGeni's tables live in. The app-role GRANTs target this\n * schema + `opengeni_private`. Defaults to `public` (standalone).\n */\n targetSchema?: string;\n /**\n * RLS posture (Step I). `\"force\"` (default) provisions the non-owner\n * `opengeni_app` login role and GRANTs it table DML in the target schema —\n * the role OpenGeni connects as under FORCE-RLS. `\"scoped\"` SKIPS the app-role\n * provisioning entirely: the embedded host runs OpenGeni's queries over a role\n * IT owns/manages (typically the schema owner), so OpenGeni neither creates\n * nor grants the `opengeni_app` role. Temporal-role provisioning is unaffected\n * by strategy.\n */\n rlsStrategy?: RlsStrategy;\n appRole?: string;\n appPassword?: string;\n temporalRole?: string;\n temporalPassword?: string;\n temporalDatabases?: string[];\n};\n\n/**\n * SDK entry point (Step I): provision the OpenGeni database roles + grants over\n * a host-supplied admin connection. This is the named, parameterized form of the\n * historical env-driven `provision-roles` script (which still works as a CLI via\n * the `import.meta.main` block at the bottom — it just reads env into these\n * options).\n *\n * STANDALONE (default): `provisionRoles(adminConnection)` with no options →\n * `targetSchema: \"public\"`, `rlsStrategy: \"force\"`, reads `opengeni_app` creds\n * from env. Byte-for-byte the historical script behavior.\n *\n * EMBEDDED: `provisionRoles(adminConnection, { targetSchema, rlsStrategy })` lets\n * a host provision the app role over a dedicated schema (force) OR skip the\n * app role entirely and own the connection role itself (scoped).\n */\nexport async function provisionRoles(\n adminConnection: string,\n options: ProvisionRolesOptions = {},\n): Promise<ProvisionResult> {\n const schema = validateIdentifier(\"targetSchema\", options.targetSchema ?? \"public\");\n const rlsStrategy: RlsStrategy = options.rlsStrategy ?? \"force\";\n\n const appRole = validateIdentifier(\"appRole\", options.appRole ?? (process.env.OPENGENI_APP_DATABASE_USER?.trim() || \"opengeni_app\"));\n const appPassword = options.appPassword ?? process.env.OPENGENI_APP_DATABASE_PASSWORD;\n const temporalRole = validateIdentifier(\"temporalRole\", options.temporalRole ?? (process.env.OPENGENI_TEMPORAL_DATABASE_USER?.trim() || \"opengeni_temporal\"));\n const temporalPassword = options.temporalPassword ?? process.env.OPENGENI_TEMPORAL_DATABASE_PASSWORD;\n const temporalDatabases = (options.temporalDatabases\n ?? commaSeparated(process.env.OPENGENI_TEMPORAL_DATABASES ?? \"temporal,temporal_visibility\"))\n .map((name) => validateIdentifier(\"temporalDatabases\", name));\n\n const sql = postgres(adminConnection, { max: 1 });\n try {\n // FORCE strategy provisions the non-owner app role OpenGeni connects as.\n // SCOPED strategy: the host owns the connection role; OpenGeni provisions no\n // app role (skipped here), only the optional Temporal role.\n let provisionedAppRole: string | null = null;\n if (rlsStrategy === \"force\") {\n if (!appPassword) {\n throw new Error(\"OPENGENI_APP_DATABASE_PASSWORD (or appPassword) is required for rlsStrategy 'force'\");\n }\n await ensureLoginRole(sql, appRole, appPassword);\n provisionedAppRole = appRole;\n }\n\n if (temporalPassword) {\n await ensureLoginRole(sql, temporalRole, temporalPassword);\n for (const database of temporalDatabases) {\n await ensureDatabase(sql, database, temporalRole);\n await grantTemporalRoleInDatabase(adminConnection, database, temporalRole);\n }\n }\n\n if (rlsStrategy === \"force\") {\n await grantAppRoleIfSchemaExists(sql, appRole, schema);\n }\n\n return {\n appRole: provisionedAppRole,\n temporalRole: temporalPassword ? temporalRole : null,\n temporalDatabases: temporalPassword ? temporalDatabases : [],\n schema,\n rlsStrategy,\n };\n } finally {\n await sql.end();\n }\n}\n\nasync function ensureLoginRole(sql: postgres.Sql, role: string, password: string): Promise<void> {\n await sql.unsafe(`\nDO $$\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = ${literal(role)}) THEN\n EXECUTE format('CREATE ROLE %I LOGIN PASSWORD %L', ${literal(role)}, ${literal(password)});\n ELSE\n EXECUTE format('ALTER ROLE %I LOGIN PASSWORD %L', ${literal(role)}, ${literal(password)});\n END IF;\nEND $$;\n`);\n}\n\nasync function ensureDatabase(sql: postgres.Sql, database: string, owner: string): Promise<void> {\n const existing = await sql<{ exists: boolean }[]>`\n select exists(select 1 from pg_database where datname = ${database}) as exists\n `;\n if (!existing[0]?.exists) {\n await sql.unsafe(`CREATE DATABASE ${identifier(database)} OWNER ${identifier(owner)}`);\n }\n await sql.unsafe(`GRANT ALL PRIVILEGES ON DATABASE ${identifier(database)} TO ${identifier(owner)}`);\n}\n\nasync function grantTemporalRoleInDatabase(adminConnection: string, database: string, role: string): Promise<void> {\n const databaseUrl = databaseUrlFor(adminConnection, database);\n const databaseSql = postgres(databaseUrl, { max: 1 });\n try {\n await databaseSql.unsafe(`GRANT USAGE, CREATE ON SCHEMA public TO ${identifier(role)}`);\n } finally {\n await databaseSql.end();\n }\n}\n\n/**\n * Grant the app role table DML in the OpenGeni data schema + EXECUTE on the\n * `opengeni_private` helper functions. Schema-parameterized (Step I): standalone\n * passes `public`; embedded passes the dedicated schema. The grants are guarded\n * on schema existence so provisioning before migrate is a safe no-op.\n */\nasync function grantAppRoleIfSchemaExists(sql: postgres.Sql, role: string, schema: string): Promise<void> {\n await sql.unsafe(`\nDO $$\nBEGIN\n IF EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = ${literal(schema)}) THEN\n EXECUTE format('GRANT USAGE ON SCHEMA %I TO %I', ${literal(schema)}, ${literal(role)});\n EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO %I', ${literal(schema)}, ${literal(role)});\n END IF;\n IF EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'opengeni_private') THEN\n EXECUTE format('GRANT USAGE ON SCHEMA opengeni_private TO %I', ${literal(role)});\n EXECUTE format('GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA opengeni_private TO %I', ${literal(role)});\n END IF;\nEND $$;\n`);\n}\n\nfunction commaSeparated(value: string): string[] {\n return value.split(\",\").map((item) => item.trim()).filter(Boolean);\n}\n\nfunction validateIdentifier(name: string, value: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {\n throw new Error(`${name} contains an invalid Postgres identifier: ${value}`);\n }\n return value;\n}\n\nfunction identifier(value: string): string {\n return `\"${value.replace(/\"/g, \"\\\"\\\"\")}\"`;\n}\n\nfunction literal(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`;\n}\n\nfunction databaseUrlFor(value: string, database: string): string {\n const url = new URL(value);\n url.pathname = `/${database}`;\n return url.toString();\n}\n\n// CLI form (unchanged behavior): read env into the SDK options and run. This is\n// what `bun src/provision-roles.ts` / the `provision-roles` package script\n// invokes — standalone byte-for-byte the historical script (public schema,\n// force strategy, env-driven creds).\nif (import.meta.main) {\n const adminUrl = process.env.OPENGENI_MIGRATIONS_DATABASE_URL\n ?? process.env.OPENGENI_DATABASE_ADMIN_URL\n ?? process.env.OPENGENI_DATABASE_URL;\n if (!adminUrl) {\n throw new Error(\"OPENGENI_MIGRATIONS_DATABASE_URL, OPENGENI_DATABASE_ADMIN_URL, or OPENGENI_DATABASE_URL is required\");\n }\n const result = await provisionRoles(adminUrl, {\n ...(process.env.OPENGENI_DB_SCHEMA?.trim() ? { targetSchema: process.env.OPENGENI_DB_SCHEMA.trim() } : {}),\n });\n console.log(JSON.stringify(result, null, 2));\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,52 @@
1
+ // src/migrate.ts
2
+ import { readdir, readFile } from "fs/promises";
3
+ import { dirname, join } from "path";
4
+ import { fileURLToPath } from "url";
5
+ import postgres from "postgres";
6
+ var DEFAULT_DATABASE_URL = "postgres://opengeni:opengeni@127.0.0.1:5432/opengeni";
7
+ function assertIdentifier(name, value) {
8
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
9
+ throw new Error(`${name} is not a valid Postgres identifier: ${value}`);
10
+ }
11
+ return value;
12
+ }
13
+ async function migrate(databaseUrl = process.env.OPENGENI_MIGRATIONS_DATABASE_URL ?? process.env.OPENGENI_DATABASE_URL ?? DEFAULT_DATABASE_URL, schema = process.env.OPENGENI_DB_SCHEMA?.trim() || void 0) {
14
+ const migrationsDir = join(dirname(fileURLToPath(import.meta.url)), "../drizzle");
15
+ const files = (await readdir(migrationsDir)).filter((file) => file.endsWith(".sql")).sort();
16
+ const sql = postgres(databaseUrl, { max: 1 });
17
+ try {
18
+ await sql`SELECT pg_advisory_lock(727458)`;
19
+ if (schema) {
20
+ assertIdentifier("OPENGENI_DB_SCHEMA", schema);
21
+ await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS "${schema}"`);
22
+ await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS "opengeni_private"`);
23
+ await sql.unsafe(`SET search_path = "${schema}", "opengeni_private", "public"`);
24
+ }
25
+ await sql.unsafe(`CREATE TABLE IF NOT EXISTS "schema_migrations" ("name" text PRIMARY KEY, "applied_at" timestamptz NOT NULL DEFAULT now())`);
26
+ const appliedRows = await sql`SELECT "name" FROM "schema_migrations"`;
27
+ const applied = new Set(appliedRows.map((row) => row.name));
28
+ for (const file of files) {
29
+ if (applied.has(file)) {
30
+ continue;
31
+ }
32
+ const sqlText = await readFile(join(migrationsDir, file), "utf8");
33
+ await sql.unsafe(sqlText);
34
+ await sql`INSERT INTO "schema_migrations" ("name") VALUES (${file}) ON CONFLICT DO NOTHING`;
35
+ }
36
+ } finally {
37
+ await sql.end();
38
+ }
39
+ }
40
+ async function runMigrations(adminConnection, targetSchema) {
41
+ await migrate(adminConnection, targetSchema);
42
+ }
43
+ if (import.meta.main) {
44
+ await migrate();
45
+ console.log("Applied Drizzle SQL migrations.");
46
+ }
47
+
48
+ export {
49
+ migrate,
50
+ runMigrations
51
+ };
52
+ //# sourceMappingURL=chunk-OGCE6O2X.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/migrate.ts"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport postgres from \"postgres\";\n\nconst DEFAULT_DATABASE_URL = \"postgres://opengeni:opengeni@127.0.0.1:5432/opengeni\";\n\n/** A bare Postgres identifier (schema/role name) safe to interpolate into DDL. */\nfunction assertIdentifier(name: string, value: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {\n throw new Error(`${name} is not a valid Postgres identifier: ${value}`);\n }\n return value;\n}\n\n/**\n * Apply the OpenGeni SQL migration chain.\n *\n * STANDALONE (default, unchanged): `migrate()` / `migrate(databaseUrl)` runs the\n * whole chain with NO search_path manipulation, so every unqualified\n * table/index/policy lands in the server default schema (`public`). This is the\n * byte-for-byte historical behavior — the migration test suite calls\n * `migrate(DB_URL)` and is unaffected.\n *\n * EMBEDDED (Step I, §7.8 runtime/SDK half): pass a `schema` (or set\n * `OPENGENI_DB_SCHEMA`). The migrate session then `CREATE SCHEMA IF NOT EXISTS`\n * for both `<schema>` and `opengeni_private`, and sets\n * `search_path = \"<schema>\", \"opengeni_private\", \"public\"`, so EVERY unqualified\n * DDL statement lands in the dedicated schema with NO per-statement SQL rewrite\n * (the SPIKE-1 F1 result). Two things make this work and stay idempotent:\n * 1. The policy-existence guards in the migration SQL use `current_schema()`\n * (not a hardcoded `'public'`) — so a re-run finds the policy it already\n * created in `<schema>` and DROP/CREATEs idempotently instead of failing\n * with \"policy already exists\". (This guard substitution is the migrate-\n * time enabler for the runtime search_path approach; without it the SDK\n * entry point silently fails on re-run — the Fork-6 hazard.)\n * 2. `public` stays LAST on the path so `gen_random_uuid()` (pgcrypto) and the\n * `vector` type — both installed into `public` by 0000 — still resolve. The\n * `opengeni_private.*` helpers are always called with an absolute prefix.\n *\n * `OPENGENI_DB_SCHEMA` defaults UNSET → `public` → standalone, so the default\n * binding never regresses.\n */\nexport async function migrate(\n databaseUrl = process.env.OPENGENI_MIGRATIONS_DATABASE_URL ?? process.env.OPENGENI_DATABASE_URL ?? DEFAULT_DATABASE_URL,\n schema: string | undefined = process.env.OPENGENI_DB_SCHEMA?.trim() || undefined,\n): Promise<void> {\n const migrationsDir = join(dirname(fileURLToPath(import.meta.url)), \"../drizzle\");\n const files = (await readdir(migrationsDir)).filter((file) => file.endsWith(\".sql\")).sort();\n const sql = postgres(databaseUrl, { max: 1 });\n try {\n // Serialize concurrent migrate() runs; the session-level lock is released\n // when the connection closes.\n await sql`SELECT pg_advisory_lock(727458)`;\n if (schema) {\n assertIdentifier(\"OPENGENI_DB_SCHEMA\", schema);\n await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS \"${schema}\"`);\n // opengeni_private is also created by 0001 with an absolute prefix, but the\n // session search_path must already resolve it for the policy predicates\n // and the SECURITY DEFINER functions that inherit the caller's path.\n await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS \"opengeni_private\"`);\n await sql.unsafe(`SET search_path = \"${schema}\", \"opengeni_private\", \"public\"`);\n }\n await sql.unsafe(`CREATE TABLE IF NOT EXISTS \"schema_migrations\" (\"name\" text PRIMARY KEY, \"applied_at\" timestamptz NOT NULL DEFAULT now())`);\n const appliedRows = await sql`SELECT \"name\" FROM \"schema_migrations\"`;\n const applied = new Set(appliedRows.map((row) => row.name as string));\n for (const file of files) {\n if (applied.has(file)) {\n continue;\n }\n const sqlText = await readFile(join(migrationsDir, file), \"utf8\");\n await sql.unsafe(sqlText);\n await sql`INSERT INTO \"schema_migrations\" (\"name\") VALUES (${file}) ON CONFLICT DO NOTHING`;\n }\n } finally {\n await sql.end();\n }\n}\n\n/**\n * SDK entry point (Step I): run the migration chain over a host-supplied admin\n * connection string against an explicit target schema. This is the embedded\n * topology's named entry — a host calls `runMigrations(adminConnection,\n * targetSchema)` from its own provisioning code instead of relying on env vars.\n * `targetSchema` undefined → `public` → standalone behavior. Thin wrapper over\n * `migrate` so there is one migration engine.\n */\nexport async function runMigrations(adminConnection: string, targetSchema?: string): Promise<void> {\n await migrate(adminConnection, targetSchema);\n}\n\nif (import.meta.main) {\n await migrate();\n console.log(\"Applied Drizzle SQL migrations.\");\n}\n"],"mappings":";AAAA,SAAS,SAAS,gBAAgB;AAClC,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAC9B,OAAO,cAAc;AAErB,IAAM,uBAAuB;AAG7B,SAAS,iBAAiB,MAAc,OAAuB;AAC7D,MAAI,CAAC,2BAA2B,KAAK,KAAK,GAAG;AAC3C,UAAM,IAAI,MAAM,GAAG,IAAI,wCAAwC,KAAK,EAAE;AAAA,EACxE;AACA,SAAO;AACT;AA8BA,eAAsB,QACpB,cAAc,QAAQ,IAAI,oCAAoC,QAAQ,IAAI,yBAAyB,sBACnG,SAA6B,QAAQ,IAAI,oBAAoB,KAAK,KAAK,QACxD;AACf,QAAM,gBAAgB,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC,GAAG,YAAY;AAChF,QAAM,SAAS,MAAM,QAAQ,aAAa,GAAG,OAAO,CAAC,SAAS,KAAK,SAAS,MAAM,CAAC,EAAE,KAAK;AAC1F,QAAM,MAAM,SAAS,aAAa,EAAE,KAAK,EAAE,CAAC;AAC5C,MAAI;AAGF,UAAM;AACN,QAAI,QAAQ;AACV,uBAAiB,sBAAsB,MAAM;AAC7C,YAAM,IAAI,OAAO,gCAAgC,MAAM,GAAG;AAI1D,YAAM,IAAI,OAAO,gDAAgD;AACjE,YAAM,IAAI,OAAO,sBAAsB,MAAM,iCAAiC;AAAA,IAChF;AACA,UAAM,IAAI,OAAO,2HAA2H;AAC5I,UAAM,cAAc,MAAM;AAC1B,UAAM,UAAU,IAAI,IAAI,YAAY,IAAI,CAAC,QAAQ,IAAI,IAAc,CAAC;AACpE,eAAW,QAAQ,OAAO;AACxB,UAAI,QAAQ,IAAI,IAAI,GAAG;AACrB;AAAA,MACF;AACA,YAAM,UAAU,MAAM,SAAS,KAAK,eAAe,IAAI,GAAG,MAAM;AAChE,YAAM,IAAI,OAAO,OAAO;AACxB,YAAM,uDAAuD,IAAI;AAAA,IACnE;AAAA,EACF,UAAE;AACA,UAAM,IAAI,IAAI;AAAA,EAChB;AACF;AAUA,eAAsB,cAAc,iBAAyB,cAAsC;AACjG,QAAM,QAAQ,iBAAiB,YAAY;AAC7C;AAEA,IAAI,YAAY,MAAM;AACpB,QAAM,QAAQ;AACd,UAAQ,IAAI,iCAAiC;AAC/C;","names":[]}