{"version":3,"file":"layer.mjs","names":[],"sources":["../../src/db/layer.ts"],"sourcesContent":["import type { PgDatabase, PgQueryResultHKT } from \"drizzle-orm/pg-core\";\nimport { type Context, Effect, Layer } from \"effect\";\nimport { getMigrationStorage, pluginMigrationSlug } from \"./core\";\nimport { createDatabaseDriver, pluginSchemaName } from \"./driver\";\nimport { DatabaseError } from \"./errors\";\nimport { detectDrift, type LoadedMigrations, runMigrations } from \"./run-migrations\";\n\n/**\n * The plugin author's whole database surface (ADR 0033): one call that\n * acquires the driver against the plugin's lifecycle scope, applies\n * migrations on boot (journal-scoped advisory lock, ADR 0019), detects\n * drift, and provides the typed drizzle database on the **plugin-owned tag**\n * — the swap seam. A different engine, a different drizzle setup, or a\n * totally different storage situation is the same interface: provide any\n * `Layer` for your own tag and nothing else in the plugin changes.\n *\n * `namespace: true` (the default) scopes the workspace into the\n * `plugin_<slug>` schema on the shared database; `namespace: false` keeps\n * the public topology (the api shell, or a dedicated database like auth's).\n * `migrations` is the workspace-bound loader Effect (`loadMigrations` from\n * this package, instantiated with the workspace's `import.meta.dirname` and\n * its bundle's `virtual:drizzle-migrations.sql` module); omit it to skip\n * boot migration entirely.\n */\nexport function databaseLayer<Identifier, TSchema extends Record<string, unknown>>(\n  tag: Context.Service<Identifier, PgDatabase<PgQueryResultHKT, TSchema>>,\n  url: string,\n  options: {\n    pluginId: string;\n    schema: TSchema;\n    migrations?: Effect.Effect<LoadedMigrations, DatabaseError>;\n    namespace?: boolean;\n  },\n): Layer.Layer<Identifier, DatabaseError> {\n  return Layer.effect(\n    tag,\n    Effect.gen(function* () {\n      const { pluginId, schema, migrations, namespace = true } = options;\n      const slug = pluginMigrationSlug(pluginId);\n      const schemaName = namespace ? pluginSchemaName(pluginId) : undefined;\n\n      const driver = yield* Effect.acquireRelease(\n        Effect.tryPromise({\n          try: () => createDatabaseDriver(url, schema, schemaName),\n          catch: (cause) => new DatabaseError({ stage: \"driver\", cause }),\n        }),\n        (driver) =>\n          Effect.tryPromise({\n            try: () => driver.close(),\n            catch: (cause) => new DatabaseError({ stage: \"close\", cause }),\n          }).pipe(Effect.ignore),\n      );\n\n      if (!migrations) {\n        return driver.db;\n      }\n\n      const storage = getMigrationStorage(slug);\n      const { migrations: loaded, source } = yield* migrations;\n\n      if (loaded.length === 0) {\n        yield* Effect.logWarning(\n          `[Database] No migrations found (source: ${source}) — schema may be missing`,\n        );\n      } else {\n        const report = yield* runMigrations(driver.db, loaded, {\n          journal: storage,\n          schemaName,\n        });\n        const applied = report.applied;\n\n        if (applied === 0) {\n          yield* Effect.logInfo(\n            `[Database] Schema up to date (0 migrations needed, source: ${source})`,\n          );\n        } else {\n          yield* Effect.logInfo(\n            `[Database] Applied ${applied}/${loaded.length} migration(s) (source: ${source}, journal: ${storage.schema}.${storage.table}, schema: ${schemaName ?? \"public\"})`,\n          );\n        }\n\n        const drift = yield* detectDrift(driver.db, loaded, storage, schemaName);\n        if (drift.status === \"healthy\" || drift.status === \"untracked-existing-schema\") {\n          yield* Effect.logInfo(`[Database] Ready`);\n        } else if (drift.status === \"drift-safe-repair\") {\n          yield* Effect.logWarning(\n            `[Database] ⚠️ Migration drift detected: ${drift.missingTables.length} expected table(s) missing: ${drift.missingTables.join(\", \")}`,\n          );\n          yield* Effect.logWarning(\n            `[Database] Run \\`bos db doctor ${storage.slug}\\` to diagnose and \\`bos db repair ${storage.slug}\\` to fix.`,\n          );\n          return yield* new DatabaseError({\n            stage: \"migration\",\n            migrationTag: \"drift-safe-repair\",\n            cause: new Error(\n              `Migration journal has ${drift.appliedHashes} applied hashes but all ${drift.expectedTables.length} expected table(s) are missing. ` +\n                `Run \\`bos db repair ${storage.slug}\\` to reset the migration history and reapply migrations. ` +\n                `Missing tables: ${drift.missingTables.join(\", \")}`,\n            ),\n          });\n        }\n        if (drift.status === \"drift-manual\") {\n          yield* Effect.logWarning(\n            `[Database] ⚠️ Partial migration drift detected: ${drift.missingTables.length}/${drift.expectedTables.length} expected table(s) missing.`,\n          );\n          return yield* new DatabaseError({\n            stage: \"migration\",\n            migrationTag: \"drift-manual\",\n            cause: new Error(\n              `Partial schema drift — ${drift.missingTables.length}/${drift.expectedTables.length} expected table(s) are missing. ` +\n                `Run \\`bos db doctor ${storage.slug}\\` for details. Manual intervention required. ` +\n                `Missing tables: ${drift.missingTables.join(\", \")}`,\n            ),\n          });\n        }\n      }\n\n      return driver.db;\n    }),\n  );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,cACd,KACA,KACA,SAMwC;CACxC,OAAO,MAAM,OACX,KACA,OAAO,IAAI,aAAa;EACtB,MAAM,EAAE,UAAU,QAAQ,YAAY,YAAY,SAAS;EAC3D,MAAM,OAAO,oBAAoB,QAAQ;EACzC,MAAM,aAAa,YAAY,iBAAiB,QAAQ,IAAI;EAE5D,MAAM,SAAS,OAAO,OAAO,eAC3B,OAAO,WAAW;GAChB,WAAW,qBAAqB,KAAK,QAAQ,UAAU;GACvD,QAAQ,UAAU,IAAI,cAAc;IAAE,OAAO;IAAU;GAAM,CAAC;EAChE,CAAC,IACA,WACC,OAAO,WAAW;GAChB,WAAW,OAAO,MAAM;GACxB,QAAQ,UAAU,IAAI,cAAc;IAAE,OAAO;IAAS;GAAM,CAAC;EAC/D,CAAC,CAAC,CAAC,KAAK,OAAO,MAAM,CACzB;EAEA,IAAI,CAAC,YACH,OAAO,OAAO;EAGhB,MAAM,UAAU,oBAAoB,IAAI;EACxC,MAAM,EAAE,YAAY,QAAQ,WAAW,OAAO;EAE9C,IAAI,OAAO,WAAW,GACpB,OAAO,OAAO,WACZ,2CAA2C,OAAO,0BACpD;OACK;GAKL,MAAM,WAAU,OAJM,cAAc,OAAO,IAAI,QAAQ;IACrD,SAAS;IACT;GACF,CAAC,EACqB,CAAC;GAEvB,IAAI,YAAY,GACd,OAAO,OAAO,QACZ,8DAA8D,OAAO,EACvE;QAEA,OAAO,OAAO,QACZ,sBAAsB,QAAQ,GAAG,OAAO,OAAO,yBAAyB,OAAO,aAAa,QAAQ,OAAO,GAAG,QAAQ,MAAM,YAAY,cAAc,SAAS,EACjK;GAGF,MAAM,QAAQ,OAAO,YAAY,OAAO,IAAI,QAAQ,SAAS,UAAU;GACvE,IAAI,MAAM,WAAW,aAAa,MAAM,WAAW,6BACjD,OAAO,OAAO,QAAQ,kBAAkB;QACnC,IAAI,MAAM,WAAW,qBAAqB;IAC/C,OAAO,OAAO,WACZ,2CAA2C,MAAM,cAAc,OAAO,8BAA8B,MAAM,cAAc,KAAK,IAAI,GACnI;IACA,OAAO,OAAO,WACZ,kCAAkC,QAAQ,KAAK,qCAAqC,QAAQ,KAAK,WACnG;IACA,OAAO,OAAO,IAAI,cAAc;KAC9B,OAAO;KACP,cAAc;KACd,uBAAO,IAAI,MACT,yBAAyB,MAAM,cAAc,0BAA0B,MAAM,eAAe,OAAO,sDAC1E,QAAQ,KAAK,4EACjB,MAAM,cAAc,KAAK,IAAI,GACpD;IACF,CAAC;GACH;GACA,IAAI,MAAM,WAAW,gBAAgB;IACnC,OAAO,OAAO,WACZ,mDAAmD,MAAM,cAAc,OAAO,GAAG,MAAM,eAAe,OAAO,4BAC/G;IACA,OAAO,OAAO,IAAI,cAAc;KAC9B,OAAO;KACP,cAAc;KACd,uBAAO,IAAI,MACT,0BAA0B,MAAM,cAAc,OAAO,GAAG,MAAM,eAAe,OAAO,sDAC3D,QAAQ,KAAK,gEACjB,MAAM,cAAc,KAAK,IAAI,GACpD;IACF,CAAC;GACH;EACF;EAEA,OAAO,OAAO;CAChB,CAAC,CACH;AACF"}