@elysiumjs/mnemosyne-drizzle 0.1.3 → 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 (45) hide show
  1. package/.omc/state/agent-replay-3920b579-6d8e-4f5f-924a-319202936685.jsonl +7 -0
  2. package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/last-tool-error-state.json +7 -0
  3. package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/mission-state.json +93 -0
  4. package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/pre-tool-advisory-throttle.json +18 -0
  5. package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/subagent-tracking-state.json +61 -0
  6. package/.turbo/turbo-build.log +3 -0
  7. package/CHANGELOG.md +40 -0
  8. package/README.md +51 -0
  9. package/dist/commands/migration_generate.command.d.ts +11 -0
  10. package/dist/commands/migration_generate.command.js +1 -0
  11. package/dist/commands/migration_run.command.d.ts +13 -0
  12. package/dist/commands/migration_run.command.js +15 -0
  13. package/dist/database.d.ts +79 -0
  14. package/dist/database.js +1 -0
  15. package/dist/drizzle.module.d.ts +40 -0
  16. package/dist/drizzle.module.js +1 -0
  17. package/dist/index.d.ts +10 -0
  18. package/dist/index.js +1 -0
  19. package/dist/migrations.d.ts +33 -0
  20. package/dist/migrations.js +1 -0
  21. package/dist/model.d.ts +61 -0
  22. package/dist/model.js +1 -0
  23. package/dist/repository.d.ts +35 -0
  24. package/dist/repository.js +1 -0
  25. package/dist/table.d.ts +69 -0
  26. package/dist/table.js +1 -0
  27. package/dist/tenancy.d.ts +88 -0
  28. package/dist/tenancy.js +1 -0
  29. package/package.json +11 -9
  30. package/scripts/minify.ts +10 -0
  31. package/src/commands/migration_generate.command.ts +101 -0
  32. package/src/commands/migration_run.command.ts +160 -0
  33. package/src/database.ts +37 -7
  34. package/src/drizzle.module.ts +97 -0
  35. package/src/index.ts +8 -30
  36. package/src/migrations.ts +100 -0
  37. package/src/model.ts +18 -11
  38. package/src/table.ts +4 -4
  39. package/src/tenancy.ts +5 -5
  40. package/tests/drizzle.module.test.ts +201 -0
  41. package/tests/migrations.test.ts +123 -0
  42. package/tests/setup.ts +0 -10
  43. package/tsconfig.json +2 -2
  44. package/bunfig.toml +0 -11
  45. package/tests/database.test.ts +0 -198
@@ -0,0 +1,7 @@
1
+ {"t":0,"agent":"aac206b","agent_type":"executor","event":"agent_start","parent_mode":"none"}
2
+ {"t":0,"agent":"a914068","agent_type":"executor","event":"agent_stop","success":true,"duration_ms":31478}
3
+ {"t":0,"agent":"aa0cc16","agent_type":"untracked-native-fork","event":"agent_stop","success":true,"synthetic":true,"telemetry_status":"unmatched_stop","reason":"SubagentStop arrived without a matching SubagentStart; native Agent/Task start telemetry was not observed."}
4
+ {"t":0,"agent":"a55f688","agent_type":"untracked-native-fork","event":"agent_stop","success":true,"synthetic":true,"telemetry_status":"unmatched_stop","reason":"SubagentStop arrived without a matching SubagentStart; native Agent/Task start telemetry was not observed."}
5
+ {"t":0,"agent":"a0ba584","agent_type":"untracked-native-fork","event":"agent_stop","success":true,"synthetic":true,"telemetry_status":"unmatched_stop","reason":"SubagentStop arrived without a matching SubagentStart; native Agent/Task start telemetry was not observed."}
6
+ {"t":0,"agent":"a6e8774","agent_type":"untracked-native-fork","event":"agent_stop","success":true,"synthetic":true,"telemetry_status":"unmatched_stop","reason":"SubagentStop arrived without a matching SubagentStart; native Agent/Task start telemetry was not observed."}
7
+ {"t":0,"agent":"aac206b","agent_type":"executor","event":"agent_stop","success":true,"duration_ms":168112}
@@ -0,0 +1,7 @@
1
+ {
2
+ "tool_name": "Bash",
3
+ "tool_input_preview": "{\"command\":\"perl -0pi -e 's/\\\\t\\\\t\\\\tconst migrations: \\\\{ name: string \\\\}\\\\[\\\\] = await sql`SELECT name FROM elysium_migrations;`;\\\\n\\\\t\\\\t\\\\tconst completedMigrations = migrations\\\\.map\\\\(\\\\(row\\\\)...",
4
+ "error": "Exit code 1\n\t\t\t}\n\n\t\t\tconst rows: { name: string; applied_at: Date }[] =\n\t\t\t\tawait sql`SELECT name, applied_at FROM elysium_migrations;`;\n\t\t\tconst completedMigrations = rows.map((row) => ({ name: row.name, appliedAt: row.applied_at }));\n\n\t\t\tconst filesIt = new Bun.Glob(`./src/database/migrations/**/*.sql`).scan({\n\t\t\t\tabsolute: true,\n\t\t\t\tonlyFiles: true\n\t\t\t});\n\n\t\t\tconst files = await Array.fromAsync(filesIt);\n\t\t\tconst { pending: sqlFiles, renames } = planMigrations(files, completedMigrations);\nChe...",
5
+ "timestamp": "2026-10-26T02:29:07.154Z",
6
+ "retry_count": 1
7
+ }
@@ -0,0 +1,93 @@
1
+ {
2
+ "updatedAt": "2026-10-24T23:32:53.900Z",
3
+ "missions": [
4
+ {
5
+ "id": "session:3920b579-6d8e-4f5f-924a-319202936685:none",
6
+ "source": "session",
7
+ "name": "none",
8
+ "objective": "Session mission",
9
+ "createdAt": "2026-10-24T23:30:05.788Z",
10
+ "updatedAt": "2026-10-24T23:32:53.900Z",
11
+ "status": "done",
12
+ "workerCount": 1,
13
+ "taskCounts": {
14
+ "total": 1,
15
+ "pending": 0,
16
+ "blocked": 0,
17
+ "inProgress": 0,
18
+ "completed": 1,
19
+ "failed": 0
20
+ },
21
+ "agents": [
22
+ {
23
+ "name": "executor:aac206b",
24
+ "role": "executor",
25
+ "ownership": "aac206b9ab7956eb4",
26
+ "status": "done",
27
+ "currentStep": null,
28
+ "latestUpdate": "completed",
29
+ "completedSummary": null,
30
+ "updatedAt": "2026-10-24T23:32:53.900Z"
31
+ }
32
+ ],
33
+ "timeline": [
34
+ {
35
+ "id": "session-start:aac206b9ab7956eb4:2026-10-24T23:30:05.788Z",
36
+ "at": "2026-10-24T23:30:05.788Z",
37
+ "kind": "update",
38
+ "agent": "executor:aac206b",
39
+ "detail": "started executor:aac206b",
40
+ "sourceKey": "session-start:aac206b9ab7956eb4"
41
+ },
42
+ {
43
+ "id": "session-stop:a914068b2d3b26e56:2026-10-24T23:30:37.266Z",
44
+ "at": "2026-10-24T23:30:37.266Z",
45
+ "kind": "completion",
46
+ "agent": "executor:aac206b",
47
+ "detail": "completed",
48
+ "sourceKey": "session-stop:a914068b2d3b26e56"
49
+ },
50
+ {
51
+ "id": "session-stop:aa0cc16535a1e4c83:2026-10-24T23:31:08.989Z",
52
+ "at": "2026-10-24T23:31:08.989Z",
53
+ "kind": "completion",
54
+ "agent": "executor:aac206b",
55
+ "detail": "completed",
56
+ "sourceKey": "session-stop:aa0cc16535a1e4c83"
57
+ },
58
+ {
59
+ "id": "session-stop:a55f6887513d75db7:2026-10-24T23:31:40.515Z",
60
+ "at": "2026-10-24T23:31:40.515Z",
61
+ "kind": "completion",
62
+ "agent": "executor:aac206b",
63
+ "detail": "completed",
64
+ "sourceKey": "session-stop:a55f6887513d75db7"
65
+ },
66
+ {
67
+ "id": "session-stop:a0ba584b3d851e834:2026-10-24T23:32:12.704Z",
68
+ "at": "2026-10-24T23:32:12.704Z",
69
+ "kind": "completion",
70
+ "agent": "executor:aac206b",
71
+ "detail": "completed",
72
+ "sourceKey": "session-stop:a0ba584b3d851e834"
73
+ },
74
+ {
75
+ "id": "session-stop:a6e8774450f70f52b:2026-10-24T23:32:44.262Z",
76
+ "at": "2026-10-24T23:32:44.262Z",
77
+ "kind": "completion",
78
+ "agent": "executor:aac206b",
79
+ "detail": "completed",
80
+ "sourceKey": "session-stop:a6e8774450f70f52b"
81
+ },
82
+ {
83
+ "id": "session-stop:aac206b9ab7956eb4:2026-10-24T23:32:53.900Z",
84
+ "at": "2026-10-24T23:32:53.900Z",
85
+ "kind": "completion",
86
+ "agent": "executor:aac206b",
87
+ "detail": "completed",
88
+ "sourceKey": "session-stop:aac206b9ab7956eb4"
89
+ }
90
+ ]
91
+ }
92
+ ]
93
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "version": 1,
3
+ "entries": {
4
+ "445ed27a3872b681d98190bae61ccb84954e1bc4e140df5370be958dee776b3a": {
5
+ "last_emitted_at_ms": 1792981759903,
6
+ "message": "Verify changes work after editing. Test functionality before marking complete."
7
+ },
8
+ "79a93d4a2f8f50b95f852280616242fee1855dc99a3c75211917f55e72e95fae": {
9
+ "last_emitted_at_ms": 1792981745167,
10
+ "message": "Use parallel execution for independent tasks. Use run_in_background for long operations (npm install, builds, tests)."
11
+ },
12
+ "466399dafa2d20f60587180bad0a07358eb7f2bce724df0ff682f038ad33f5ad": {
13
+ "last_emitted_at_ms": 1792981448165,
14
+ "message": "Read multiple files in parallel when possible for faster analysis."
15
+ }
16
+ },
17
+ "updated_at": "2026-10-26T02:29:19.903Z"
18
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "agents": [
3
+ {
4
+ "agent_id": "aac206b9ab7956eb4",
5
+ "agent_type": "oh-my-claudecode:executor",
6
+ "started_at": "2026-10-24T23:30:05.788Z",
7
+ "parent_mode": "none",
8
+ "status": "completed",
9
+ "completed_at": "2026-10-24T23:32:53.900Z",
10
+ "duration_ms": 168112
11
+ },
12
+ {
13
+ "agent_id": "aa0cc16535a1e4c83",
14
+ "agent_type": "untracked-native-fork",
15
+ "started_at": "2026-10-24T23:31:08.989Z",
16
+ "parent_mode": "none",
17
+ "status": "completed",
18
+ "completed_at": "2026-10-24T23:31:08.989Z",
19
+ "synthetic": true,
20
+ "telemetry_status": "unmatched_stop",
21
+ "telemetry_note": "SubagentStop arrived without a matching SubagentStart; native Agent/Task start telemetry was not observed."
22
+ },
23
+ {
24
+ "agent_id": "a55f6887513d75db7",
25
+ "agent_type": "untracked-native-fork",
26
+ "started_at": "2026-10-24T23:31:40.515Z",
27
+ "parent_mode": "none",
28
+ "status": "completed",
29
+ "completed_at": "2026-10-24T23:31:40.515Z",
30
+ "synthetic": true,
31
+ "telemetry_status": "unmatched_stop",
32
+ "telemetry_note": "SubagentStop arrived without a matching SubagentStart; native Agent/Task start telemetry was not observed."
33
+ },
34
+ {
35
+ "agent_id": "a0ba584b3d851e834",
36
+ "agent_type": "untracked-native-fork",
37
+ "started_at": "2026-10-24T23:32:12.704Z",
38
+ "parent_mode": "none",
39
+ "status": "completed",
40
+ "completed_at": "2026-10-24T23:32:12.704Z",
41
+ "synthetic": true,
42
+ "telemetry_status": "unmatched_stop",
43
+ "telemetry_note": "SubagentStop arrived without a matching SubagentStart; native Agent/Task start telemetry was not observed."
44
+ },
45
+ {
46
+ "agent_id": "a6e8774450f70f52b",
47
+ "agent_type": "untracked-native-fork",
48
+ "started_at": "2026-10-24T23:32:44.262Z",
49
+ "parent_mode": "none",
50
+ "status": "completed",
51
+ "completed_at": "2026-10-24T23:32:44.262Z",
52
+ "synthetic": true,
53
+ "telemetry_status": "unmatched_stop",
54
+ "telemetry_note": "SubagentStop arrived without a matching SubagentStart; native Agent/Task start telemetry was not observed."
55
+ }
56
+ ],
57
+ "total_spawned": 1,
58
+ "total_completed": 6,
59
+ "total_failed": 0,
60
+ "last_updated": "2026-10-24T23:32:53.907Z"
61
+ }
@@ -0,0 +1,3 @@
1
+ $ bun run clean && tsc && bun run build:minify
2
+ $ rimraf dist
3
+ $ bun scripts/minify.ts
package/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # @elysiumjs/mnemosyne-drizzle
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`8ac91ab`](https://github.com/na2axl/elysiumjs/commit/8ac91ab676b1119b4eecf0ddb3fd0a3e84017cac) Thanks [@na2axl](https://github.com/na2axl)! - Upgrade dependencies to their latest major versions: wampy 8, zod 4, commander 15, @casl/ability 7, OpenTelemetry SDK 2 (sdk-node 0.300, resources 3), @sentry/bun 11. Generated projects now use TypeScript 7.
8
+
9
+ Breaking in `@elysiumjs/mnemosyne-drizzle`: it now requires drizzle-orm 1.0 (release candidate) and generated projects use drizzle-kit 1.0. `ColumnsWithTable` is no longer re-exported. `migration:generate` writes 1.0 migration folders (`<timestamp>_<name>/migration.sql`), and `migration:run` tracks those by folder name while still running flat `*.sql` files. Existing projects can upgrade their migrations folder with `drizzle-kit up`: `migration:run` recognises the moved migrations as already applied and renames their records. The `make:model` stub now generates a `pgTable` and a `DrizzleModel` built from it.
10
+
11
+ Breaking in `@elysiumjs/core`: the deprecated `@elysiajs/swagger` plugin is replaced by `@elysiajs/openapi`, and the app option `swagger` is renamed `openapi` (typed `ElysiaOpenAPIConfig | false`). Without a `path`, the docs now live at `/openapi` instead of `/swagger`. The UI options `scalarConfig` and `swaggerOptions` are now `scalar` and `swagger`. Elysia stays on 1.4 (now `^1.4.30`) until Elysia 2 and its OpenAPI plugin are stable.
12
+
13
+ Also in `@elysiumjs/core`: the WAMP `onChallenge` callback must return a string (or a promise of one); `null` is no longer accepted. `onError` now receives the error and `onReconnectSuccess` receives wampy's details object. `WampClient` is now wampy's own `Wampy` type. `onOpen` is called by Elysium once the connection is established, since wampy 8 no longer has that callback.
14
+
15
+ - [`07236b3`](https://github.com/na2axl/elysiumjs/commit/07236b386c2506590d74e8753ce847c1e184b3ee) Thanks [@na2axl](https://github.com/na2axl)! - Introduce the module system. Modules are now the only way to register anything in an Elysium.js app. This is a breaking change with no compatibility layer.
16
+
17
+ Breaking changes:
18
+
19
+ - `@Module.register()` takes `imports`, `services`, `exports`, `controllers`, `commands`, `jobs`, `prefix` and `env`. Services are private to their module unless exported (or global). The `beforeRegister` and `afterRegister` hooks are replaced by `onInit`, `onServe` and `onDestroy`.
20
+ - Configurable modules are bound with `with()`, for example `HermesModule.with({ level: 'debug' })`. `with()` accepts an object or a function.
21
+ - Decorators only record metadata. Importing a file no longer registers commands, services or event wiring, and the side-effect imports of the packages are removed. List commands, jobs and services in the module that owns them.
22
+ - `@Application.register()` no longer takes `database`, `redis`, `wamp` or `'elysium:hermes'`/`'elysium:cerberus'` keys. Use `DrizzleModule`, `RedisModule`, `WampModule`, `HermesModule` and `CerberusModule`. Package plugins are replaced by modules: add `SentryModule` instead of the `plugin()` function. `plugins` still accepts plain Elysia plugin factories.
23
+ - Constructing an `Application` no longer runs it. Use `await App.start()`; `Application.testing()` boots a module graph for tests. The `styx` binary loads `src/app.ts` (or `index.ts`) and starts the exported application, so a project entry no longer needs to import commands.
24
+ - The job system of core (`Job`, `Worker`, `WorkerPool`, `commandWork`) is removed. `@elysiumjs/heracles` is the only job system: extend its `Job`, list jobs in a module's `jobs`, declare queues with `HeraclesModule.with({ queues })`, and dispatch with `Heracles.dispatch()`.
25
+ - `@elysiumjs/styx` ships `StyxModule`, active in development only. The `migration:generate` and `migration:run` commands move to `DrizzleModule` and are available in production.
26
+ - `@elysiumjs/mnemosyne` ships `MnemosyneModule` for its caches (`redis: false` or `memory: false` opts out). `DrizzleModule` imports it.
27
+ - New modules: `HephaestusModule`, `ArgusModule` (`serviceName` is required), `SentryModule` (needs a `dsn` or `SENTRY_DSN`) and `CerberusModule` (`getSubject` is required).
28
+ - Module and service code throws typed errors (for example `InvalidModuleConfigError`) instead of exiting the process. `Application.run()` prints them and exits; command `run()` methods may still exit, and package plugin factories are unchanged.
29
+ - `Application.register` merges subclass props over the parent's (own props, then own metadata, then parent, then defaults), and concatenates `modules` parent first. Previously defaults and parent values won, so `openapi` could not be enabled.
30
+ - `Heracles.dispatch` starts a stopped queue on first use, so producers no longer start queues. `Queue.start()` is idempotent.
31
+ - `create-elysium` generates apps that use modules, and adds Sentry and Hephaestus to the `modules` list.
32
+ - `@Event.on`/`@Event.once` method listeners on classes in a loaded module's `services` or `controllers` are wired by the kernel and run in their module's context. Jobs, commands and middlewares are not wired, and an instance listener on a class that is not a service throws `EventListenerNotSupportedError`. Listeners on classes not listed in a loaded module no longer fire.
33
+ - New typed errors: `CircularDependencyError` (services that depend on each other, in sync and async resolution), `DuplicateModuleNameError` (two different module classes sharing a name; module scopes and config tokens are name based) and `EventListenerNotSupportedError`.
34
+ - Lazy boot contract: a CLI command initialises only its own module and that module's imports. Sync helpers such as `Database.getConnection()` work when the owning module's `onInit` is synchronous; otherwise they throw `ServiceNotReadyError` naming the module. Use `await Service.resolve()` or set `static boot = true` on the command.
35
+
36
+ ### Patch Changes
37
+
38
+ - Updated dependencies [[`8ac91ab`](https://github.com/na2axl/elysiumjs/commit/8ac91ab676b1119b4eecf0ddb3fd0a3e84017cac), [`3625883`](https://github.com/na2axl/elysiumjs/commit/3625883dba92f1596e7444ee52f9399d8e9b049f), [`07236b3`](https://github.com/na2axl/elysiumjs/commit/07236b386c2506590d74e8753ce847c1e184b3ee)]:
39
+ - @elysiumjs/core@0.8.0
40
+ - @elysiumjs/mnemosyne@0.2.0
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # @elysiumjs/mnemosyne-drizzle
2
+
3
+ Drizzle ORM driver for `@elysiumjs/mnemosyne`.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @elysiumjs/mnemosyne-drizzle
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Add `DrizzleModule` to your application. It declares the connections as global services, registers the tenancy strategy and provides the `migration:generate` and `migration:run` commands.
14
+
15
+ ```ts
16
+ import { Application } from '@elysiumjs/core';
17
+ import { DrizzleModule } from '@elysiumjs/mnemosyne-drizzle';
18
+
19
+ @Application.register({
20
+ modules: [
21
+ DrizzleModule.with({
22
+ default: 'main',
23
+ connections: {
24
+ main: { connection: process.env.DATABASE_URL! }
25
+ }
26
+ })
27
+ ]
28
+ })
29
+ export class App extends Application {}
30
+ ```
31
+
32
+ `with()` also accepts a function, which is called when the module is booted: `DrizzleModule.with(() => ({ ... }))`. Set `tenancy` to switch between schema (default) and RLS tenancy.
33
+
34
+ Define models with the enhanced `pgTable`:
35
+
36
+ ```ts
37
+ import { DrizzleModel, DrizzleRepository, pgTable } from '@elysiumjs/mnemosyne-drizzle';
38
+ import { uuid, varchar } from 'drizzle-orm/pg-core';
39
+
40
+ const users = pgTable('users', {
41
+ id: uuid().primaryKey().defaultRandom(),
42
+ name: varchar({ length: 255 }).notNull()
43
+ });
44
+
45
+ export class UserModel extends DrizzleModel(users, { supportTenancy: true }) {}
46
+ export class UserRepository extends DrizzleRepository(UserModel) {}
47
+ ```
48
+
49
+ ## License
50
+
51
+ This project is licensed under the Apache License, Version 2.0. See the [LICENSE](../../LICENSE) file for more info.
@@ -0,0 +1,11 @@
1
+ import { Command } from '@elysiumjs/core';
2
+ /**
3
+ * Generates migration files.
4
+ * @author Axel Nana <axel.nana@workbud.com>
5
+ */
6
+ export declare class MigrationGenerateCommand extends Command {
7
+ static readonly command: string;
8
+ static readonly description: string;
9
+ private name?;
10
+ run(): Promise<void>;
11
+ }
@@ -0,0 +1 @@
1
+ var __decorate=function(decorators,target,key,desc){var c=arguments.length,r=c<3?target:desc===null?desc=Object.getOwnPropertyDescriptor(target,key):desc,d;if(typeof Reflect==="object"&&typeof Reflect.decorate==="function")r=Reflect.decorate(decorators,target,key,desc);else for(var i=decorators.length-1;i>=0;i--)if(d=decorators[i])r=(c<3?d(r):c>3?d(target,key,r):d(target,key))||r;return c>3&&r&&Object.defineProperty(target,key,r),r};var __metadata=function(k,v){if(typeof Reflect==="object"&&typeof Reflect.metadata==="function")return Reflect.metadata(k,v)};import{existsSync}from"node:fs";import{exists}from"node:fs/promises";import{dirname,join}from"node:path";import{Command,CommandArgumentType}from"@elysiumjs/core";import prompts from"prompts";const getProjectPath=()=>{const startPath=process.cwd();let dir=startPath;while(true){if(existsSync(join(dir,".elysiumrc")))return dir;const parentDir=dirname(dir);if(dir===parentDir)break;dir=parentDir}return startPath};let MigrationGenerateCommand=class MigrationGenerateCommand extends Command{static command="migration:generate";static description="Generates migration files.";name;async run(){if(!this.name){const answers=await prompts([{type:"text",name:"name",message:"Migration Name:",initial:"create_users_table",validate(value){if(value.length<1){return"Migration name cannot be empty"}return true}}],{onCancel:()=>{this.error("Operation cancelled.");process.exit(0)}});this.name=answers.name}const projectRoot=getProjectPath();let schemaPath=`${projectRoot}/src/database/schemas`;if(await exists(`${projectRoot}/src/database/schema.ts`)){schemaPath=`${projectRoot}/src/database/schema.ts`}await Bun.$`bunx --bun drizzle-kit generate --dialect postgresql --schema ${schemaPath} --out ${projectRoot}/src/database/migrations --name ${this.name}`;const sqlFiles=new Bun.Glob(`${projectRoot}/src/database/migrations/**/*.sql`).scan();for await(const path of sqlFiles){const file=Bun.file(path);const content=await file.text();await file.write(content.replace(/"public"\./gm,""))}}};__decorate([Command.arg({description:"The name of the migration to create",type:CommandArgumentType.STRING}),__metadata("design:type",String)],MigrationGenerateCommand.prototype,"name",undefined);MigrationGenerateCommand=__decorate([Command.register()],MigrationGenerateCommand);export{MigrationGenerateCommand};
@@ -0,0 +1,13 @@
1
+ import { Command } from '@elysiumjs/core';
2
+ /**
3
+ * Execute migration files.
4
+ * @author Axel Nana <axel.nana@workbud.com>
5
+ */
6
+ export declare class MigrationRunCommand extends Command {
7
+ static readonly command: string;
8
+ static readonly description: string;
9
+ private tenant;
10
+ private connection;
11
+ private fresh;
12
+ run(): Promise<void>;
13
+ }
@@ -0,0 +1,15 @@
1
+ var __decorate=function(decorators,target,key,desc){var c=arguments.length,r=c<3?target:desc===null?desc=Object.getOwnPropertyDescriptor(target,key):desc,d;if(typeof Reflect==="object"&&typeof Reflect.decorate==="function")r=Reflect.decorate(decorators,target,key,desc);else for(var i=decorators.length-1;i>=0;i--)if(d=decorators[i])r=(c<3?d(r):c>3?d(target,key,r):d(target,key))||r;return c>3&&r&&Object.defineProperty(target,key,r),r};var __metadata=function(k,v){if(typeof Reflect==="object"&&typeof Reflect.metadata==="function")return Reflect.metadata(k,v)};import{Command,CommandArgumentType}from"@elysiumjs/core";import prompts from"prompts";import{Database}from"../database";import{getMigrationName,planMigrations}from"../migrations";let MigrationRunCommand=class MigrationRunCommand extends Command{static command="migration:run";static description="Runs pending migrations.";tenant="";connection="default";fresh=false;async run(){if(!Database.connectionExists(this.connection)){this.error(`Connection ${this.bold(this.connection)} does not exist.`);return process.exit(1)}const sql=Database.getConnection(this.connection).$client;await sql.connect();await sql.begin(async(sql)=>{if(this.fresh){await sql`DROP SCHEMA IF EXISTS ${sql(this.tenant)} CASCADE;`;await sql`CREATE SCHEMA ${sql(this.tenant)};`}const[{schema_exists}]=await sql`SELECT EXISTS (
2
+ SELECT schema_name
3
+ FROM information_schema.schemata
4
+ WHERE schema_name = ${this.tenant}
5
+ ) AS schema_exists;`;if(!schema_exists){const answers=await prompts([{type:"confirm",name:"create",message:`Schema ${this.tenant} does not exist. Do you want to create it?`,initial:true}],{onCancel:()=>{this.error("Operation cancelled.");process.exit(0)}});if(answers.create){await sql`CREATE SCHEMA ${sql(this.tenant)};`}else{this.error(`Schema ${this.bold(this.tenant)} creation cancelled.`);return process.exit(1)}}await sql`SET search_path TO ${sql(this.tenant)};`;const[{table_exists}]=await sql`SELECT EXISTS (
6
+ SELECT *
7
+ FROM information_schema.tables
8
+ WHERE table_name = 'elysium_migrations'
9
+ AND table_schema = ${this.tenant}
10
+ ) AS table_exists;`;if(!table_exists){await sql`CREATE TABLE elysium_migrations (
11
+ id SERIAL PRIMARY KEY,
12
+ name TEXT NOT NULL,
13
+ batch INTEGER NOT NULL,
14
+ applied_at TIMESTAMP NOT NULL
15
+ );`}const rows=await sql`SELECT name, applied_at FROM elysium_migrations;`;const completedMigrations=rows.map((row)=>({name:row.name,appliedAt:row.applied_at}));const filesIt=new Bun.Glob(`./src/database/migrations/**/*.sql`).scan({absolute:true,onlyFiles:true});const files=await Array.fromAsync(filesIt);const{pending:sqlFiles,renames}=planMigrations(files,completedMigrations);for(const{from,to}of renames){await sql`UPDATE elysium_migrations SET name = ${to} WHERE name = ${from};`;this.info(`Migration ${this.bold(from)} is now tracked as ${this.bold(to)}.`)}if(sqlFiles.length===0){this.success("No new migrations to apply.");return}const s=this.spinner(`Running migrations for tenant ${this.bold(this.tenant)}...`);const[{max}]=await sql`SELECT MAX(batch) + 1 AS max FROM elysium_migrations;`;const batch=max??0;for(const path of sqlFiles){await sql.file(path);const[{name}]=await sql`INSERT INTO elysium_migrations (name, batch, applied_at) VALUES (${getMigrationName(path)}, ${batch}, ${new Date}) RETURNING name;`;s.pause(`Migration ${this.bold(name)} applied successfully.`)}s.complete(`Migrations for tenant ${this.bold(this.tenant)} applied successfully.`)});await sql.close()}};__decorate([Command.arg({description:"The name of the tenant in which the migrations will be executed.",type:CommandArgumentType.STRING,required:true}),__metadata("design:type",String)],MigrationRunCommand.prototype,"tenant",undefined);__decorate([Command.arg({description:"The name of the connection to use.",type:CommandArgumentType.STRING,default:"default"}),__metadata("design:type",String)],MigrationRunCommand.prototype,"connection",undefined);__decorate([Command.arg({description:"Drop the entire schema before running the migrations.",type:CommandArgumentType.BOOLEAN,default:false}),__metadata("design:type",Boolean)],MigrationRunCommand.prototype,"fresh",undefined);MigrationRunCommand=__decorate([Command.register()],MigrationRunCommand);export{MigrationRunCommand};
@@ -0,0 +1,79 @@
1
+ import type { DatabaseCacheConfig, DatabaseDriver } from '@elysiumjs/mnemosyne';
2
+ import type { DrizzleConfig } from 'drizzle-orm';
3
+ import type { BunSQLDatabase } from 'drizzle-orm/bun-sql/postgres';
4
+ import type { CacheConfig } from 'drizzle-orm/cache/core/types';
5
+ import { AbstractDatabase } from '@elysiumjs/mnemosyne';
6
+ import { Table } from 'drizzle-orm';
7
+ import { Cache as DrizzleCache } from 'drizzle-orm/cache/core';
8
+ /**
9
+ * Connection configuration for Drizzle ORM.
10
+ *
11
+ * Extends `DrizzleConfig` with connection details and optional caching.
12
+ *
13
+ * @author Axel Nana <axel.nana@workbud.com>
14
+ * @template TSchema The Drizzle schema type.
15
+ */
16
+ export type DrizzleConnectionProps<TSchema extends Record<string, unknown> = Record<string, never>> = Omit<DrizzleConfig<TSchema>, 'cache'> & ({
17
+ connection: string | ({
18
+ url?: string;
19
+ } & Bun.SQLOptions);
20
+ } | {
21
+ client: Bun.SQL;
22
+ }) & {
23
+ cache?: true | DatabaseCacheConfig;
24
+ };
25
+ /**
26
+ * A Drizzle database connection backed by Bun's SQL driver.
27
+ * @author Axel Nana <axel.nana@workbud.com>
28
+ */
29
+ export type DrizzleConnection = BunSQLDatabase<Record<string, never>> & {
30
+ $client: Bun.SQL;
31
+ };
32
+ /**
33
+ * Drizzle query cache backed by the framework's cache storage.
34
+ *
35
+ * Extends the Drizzle `Cache` base class and implements the
36
+ * framework's `DatabaseCacheStrategy` interface so that query
37
+ * results can be transparently cached and invalidated.
38
+ *
39
+ * @author Axel Nana <axel.nana@workbud.com>
40
+ */
41
+ export declare class DrizzleDatabaseCache extends DrizzleCache {
42
+ #private;
43
+ private readonly cacheStrategy;
44
+ private readonly globalTtl;
45
+ private usedTablesPerKey;
46
+ constructor(cacheStrategy?: 'explicit' | 'all', globalTtl?: number, storageEngine?: 'redis' | 'memory');
47
+ strategy(): 'explicit' | 'all';
48
+ get(key: string): Promise<any[] | undefined>;
49
+ put(key: string, response: any, tables: string[], _isTag: boolean, config?: CacheConfig): Promise<void>;
50
+ onMutate(params: {
51
+ tags?: string | string[];
52
+ tables?: string | string[] | Table<any> | Table<any>[];
53
+ }): Promise<void>;
54
+ }
55
+ /**
56
+ * Drizzle-specific database manager.
57
+ *
58
+ * Implements the abstract database class with a driver that creates
59
+ * Drizzle connections using Bun's built-in SQL client.
60
+ *
61
+ * @author Axel Nana <axel.nana@workbud.com>
62
+ */
63
+ declare class DrizzleDatabase extends AbstractDatabase<DrizzleConnection, DrizzleConnectionProps> {
64
+ /**
65
+ * Creates a Drizzle connection from its properties. Used by the `DrizzleModule` to build
66
+ * the `db.connection.{name}` services.
67
+ * @author Axel Nana <axel.nana@workbud.com>
68
+ * @param props The connection properties.
69
+ * @returns A new connection.
70
+ */
71
+ createConnection(props: DrizzleConnectionProps): DrizzleConnection;
72
+ protected driver: DatabaseDriver<DrizzleConnection, DrizzleConnectionProps>;
73
+ }
74
+ /**
75
+ * Singleton Drizzle database manager.
76
+ * @author Axel Nana <axel.nana@workbud.com>
77
+ */
78
+ export declare const Database: DrizzleDatabase;
79
+ export {};
@@ -0,0 +1 @@
1
+ import{Service,ServiceNotReadyError}from"@elysiumjs/core";import{AbstractDatabase,createDatabaseCacheStorage}from"@elysiumjs/mnemosyne";import{getTableName,is,Table}from"drizzle-orm";import{drizzle}from"drizzle-orm/bun-sql";import{Cache as DrizzleCache}from"drizzle-orm/cache/core";export class DrizzleDatabaseCache extends DrizzleCache{cacheStrategy;globalTtl;#storageEngine;#cache;usedTablesPerKey={};constructor(cacheStrategy="explicit",globalTtl=1,storageEngine="redis"){super();this.cacheStrategy=cacheStrategy;this.globalTtl=globalTtl;this.#storageEngine=storageEngine}async#storage(){if(this.#cache)return this.#cache;try{this.#cache=createDatabaseCacheStorage({storage:this.#storageEngine})}catch(error){if(!(error instanceof ServiceNotReadyError))throw error;await Service.resolve(error.service);this.#cache=createDatabaseCacheStorage({storage:this.#storageEngine})}return this.#cache}strategy(){return this.cacheStrategy}async get(key){return await(await this.#storage()).get(key)??undefined}async put(key,response,tables,_isTag,config){await(await this.#storage()).set(key,response,(config?.ex??this.globalTtl)*1000);for(const table of tables){const keys=this.usedTablesPerKey[table];if(keys===undefined){this.usedTablesPerKey[table]=[key]}else{keys.push(key)}}}async onMutate(params){const tagsArray=params.tags?Array.isArray(params.tags)?params.tags:[params.tags]:[];const tablesArray=params.tables?Array.isArray(params.tables)?params.tables:[params.tables]:[];const keysToDelete=new Set;for(const table of tablesArray){const tableName=is(table,Table)?getTableName(table):table;const keys=this.usedTablesPerKey[tableName]??[];for(const key of keys)keysToDelete.add(key)}if(keysToDelete.size>0||tagsArray.length>0){const cache=await this.#storage();await cache.mdel(tagsArray);await cache.mdel([...keysToDelete]);for(const table of tablesArray){const tableName=is(table,Table)?getTableName(table):table;this.usedTablesPerKey[tableName]=[]}}}}class DrizzleDatabase extends AbstractDatabase{createConnection(props){return this.driver.createConnection(props)}driver={createConnection(config){const cache=config.cache===undefined?undefined:config.cache===true?new DrizzleDatabaseCache:config.cache.enabled!==false?new DrizzleDatabaseCache(config.cache.strategy,config.cache.ttl,config.cache.storage):undefined;return drizzle({...config,cache})},withTransaction(connection,callback){return connection.transaction((tx)=>callback(tx))},getRawClient(connection){return connection.$client}}}export const Database=new DrizzleDatabase;
@@ -0,0 +1,40 @@
1
+ import type { TenancyConfig } from '@elysiumjs/mnemosyne';
2
+ import type { DrizzleConnectionProps } from './database';
3
+ /**
4
+ * Configuration of the {@link DrizzleModule}.
5
+ * @author Axel Nana <axel.nana@workbud.com>
6
+ */
7
+ export type DrizzleModuleConfig = {
8
+ /** The name of the connection that `default` resolves to. */
9
+ default: string;
10
+ /** The connections to declare, by name. */
11
+ connections: Record<string, DrizzleConnectionProps>;
12
+ /** The tenancy configuration. Defaults to schema-based tenancy. */
13
+ tenancy?: TenancyConfig;
14
+ };
15
+ declare const DrizzleModule_base: (abstract new () => {
16
+ readonly config: DrizzleModuleConfig;
17
+ onInit(): Promise<void> | void;
18
+ onDestroy(): Promise<void> | void;
19
+ onServe(_app: import("elysia").default<any>): Promise<void> | void;
20
+ }) & {
21
+ readonly config: string;
22
+ with(config: Partial<DrizzleModuleConfig> | (() => Partial<DrizzleModuleConfig> | Promise<Partial<DrizzleModuleConfig>>)): import("@elysiumjs/core").ConfiguredModule<DrizzleModuleConfig>;
23
+ resolveConfig(input?: Partial<DrizzleModuleConfig> | undefined): DrizzleModuleConfig;
24
+ register<TConfig_1 = any>(props?: import("@elysiumjs/core").ModuleProps<TConfig_1>): (target: import("@elysiumjs/core").ModuleClass) => void;
25
+ configurable<TConfig extends object>(options?: {
26
+ defaults?: Partial<TConfig>;
27
+ required?: (keyof TConfig)[];
28
+ }): any;
29
+ };
30
+ /**
31
+ * Declares Drizzle connections as global services named `db.connection.{name}`, plus
32
+ * `db.connection.default` aliasing the configured default, and registers the tenancy config
33
+ * and strategy. Connections are created on first use. Also provides the migration commands.
34
+ * @author Axel Nana <axel.nana@workbud.com>
35
+ */
36
+ export declare class DrizzleModule extends DrizzleModule_base {
37
+ /** Closes the clients of the connections that were created. */
38
+ onDestroy(): Promise<void>;
39
+ }
40
+ export {};
@@ -0,0 +1 @@
1
+ var __decorate=function(decorators,target,key,desc){var c=arguments.length,r=c<3?target:desc===null?desc=Object.getOwnPropertyDescriptor(target,key):desc,d;if(typeof Reflect==="object"&&typeof Reflect.decorate==="function")r=Reflect.decorate(decorators,target,key,desc);else for(var i=decorators.length-1;i>=0;i--)if(d=decorators[i])r=(c<3?d(r):c>3?d(target,key,r):d(target,key))||r;return c>3&&r&&Object.defineProperty(target,key,r),r};import{Module,needsDefaultAlias,runAll,Service}from"@elysiumjs/core";import{AbstractDatabase,MnemosyneModule,TENANCY_CONFIG_SERVICE,TENANCY_STRATEGY_SERVICE}from"@elysiumjs/mnemosyne";import{MigrationGenerateCommand}from"./commands/migration_generate.command";import{MigrationRunCommand}from"./commands/migration_run.command";import{Database}from"./database";import{DrizzleRLSTenancy,DrizzleSchemaTenancy}from"./tenancy";let DrizzleModule=class DrizzleModule extends Module.configurable({defaults:{default:"default"},required:["connections"]}){async onDestroy(){const names=Object.keys(this.config.connections).map(AbstractDatabase.getConnectionName);await runAll(names.filter((name)=>Service.isConstructed(name)).map((name)=>()=>Service.get(name)?.$client.close()))}};DrizzleModule=__decorate([Module.register({imports:[MnemosyneModule],commands:[MigrationGenerateCommand,MigrationRunCommand],services:(config)=>{const alias=needsDefaultAlias("DrizzleModule",config);const tenancy=config.tenancy??{mode:"schema"};return[...Object.entries(config.connections).map(([name,props])=>({name:AbstractDatabase.getConnectionName(name),global:true,factory:()=>Database.createConnection(props)})),...alias?[{name:AbstractDatabase.getConnectionName("default"),global:true,factory:(_config,get)=>get(AbstractDatabase.getConnectionName(config.default))}]:[],{name:TENANCY_CONFIG_SERVICE,global:true,factory:()=>tenancy},{name:TENANCY_STRATEGY_SERVICE,global:true,factory:()=>tenancy.mode==="rls"?new DrizzleRLSTenancy:new DrizzleSchemaTenancy}]}})],DrizzleModule);export{DrizzleModule};
@@ -0,0 +1,10 @@
1
+ export * from 'drizzle-orm';
2
+ export * from 'drizzle-orm/pg-core';
3
+ export type { SelectedFields, SelectedFieldsFlat, SelectedFieldsOrdered, TableConfig } from 'drizzle-orm/pg-core';
4
+ export { pgTable, getTableBuilders } from './table';
5
+ export { Database, DrizzleDatabaseCache } from './database';
6
+ export type { DrizzleConnectionProps, DrizzleConnection } from './database';
7
+ export { DrizzleModel, createSchemaFromDrizzle, drizzleAdapter, type DrizzleModelOptions } from './model';
8
+ export { DrizzleModule, type DrizzleModuleConfig } from './drizzle.module';
9
+ export { DrizzleRepository } from './repository';
10
+ export { DrizzleSchemaTenancy, DrizzleRLSTenancy, wrapTenantSchema, registerTenantSchema, getTenantSchema, withRLS, setConnectionTenant, getSessionTenant, createRLSPolicy } from './tenancy';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export*from"drizzle-orm";export*from"drizzle-orm/pg-core";export{pgTable,getTableBuilders}from"./table";export{Database,DrizzleDatabaseCache}from"./database";export{DrizzleModel,createSchemaFromDrizzle,drizzleAdapter}from"./model";export{DrizzleModule}from"./drizzle.module";export{DrizzleRepository}from"./repository";export{DrizzleSchemaTenancy,DrizzleRLSTenancy,wrapTenantSchema,registerTenantSchema,getTenantSchema,withRLS,setConnectionTenant,getSessionTenant,createRLSPolicy}from"./tenancy";
@@ -0,0 +1,33 @@
1
+ /** A migration recorded as applied in `elysium_migrations`. */
2
+ export type AppliedMigration = {
3
+ name: string;
4
+ appliedAt: Date;
5
+ };
6
+ /** The migrations to run, and the applied records to rename to their new names. */
7
+ export type MigrationPlan = {
8
+ pending: string[];
9
+ renames: {
10
+ from: string;
11
+ to: string;
12
+ }[];
13
+ };
14
+ /**
15
+ * Gets the name under which a migration file is tracked. Drizzle Kit 1.0 stores each migration
16
+ * as `<folder>/migration.sql`, in which case the folder name identifies the migration.
17
+ * @author Axel Nana <axel.nana@workbud.com>
18
+ * @param path The path of the migration file.
19
+ */
20
+ export declare const getMigrationName: (path: string) => string;
21
+ /**
22
+ * Decides which migration files to run.
23
+ *
24
+ * `drizzle-kit up` moves each flat `<prefix>_<name>.sql` file into a
25
+ * `<timestamp>_<name>/migration.sql` folder, where the timestamp is when the migration was
26
+ * generated. An applied record whose flat file is gone is matched, in order, to a folder with
27
+ * the same `<name>` generated before the record was applied; that folder is not run again and
28
+ * the record is renamed. Folders generated later are new migrations and run.
29
+ * @author Axel Nana <axel.nana@workbud.com>
30
+ * @param files The paths of the migration files.
31
+ * @param applied The applied migrations.
32
+ */
33
+ export declare const planMigrations: (files: string[], applied: AppliedMigration[]) => MigrationPlan;
@@ -0,0 +1 @@
1
+ import{basename,dirname}from"node:path";const FOLDER_MIGRATION_FILE="migration.sql";export const getMigrationName=(path)=>basename(path)===FOLDER_MIGRATION_FILE?basename(dirname(path)):basename(path);const descriptiveName=(name)=>name.replace(/\.sql$/,"").split("_").slice(1).join("_");const folderTime=(name)=>{const match=/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(name);return match?Date.UTC(+match[1],+match[2]-1,+match[3],+match[4],+match[5],+match[6]):undefined};export const planMigrations=(files,applied)=>{const appliedNames=new Set(applied.map(({name})=>name));const fileNames=new Set(files.map(getMigrationName));const moved=new Map;for(const record of[...applied].sort((a,b)=>a.name.localeCompare(b.name))){if(!record.name.endsWith(".sql")||fileNames.has(record.name))continue;const key=descriptiveName(record.name);moved.set(key,[...moved.get(key)??[],record])}const plan={pending:[],renames:[]};for(const file of[...files].sort()){const name=getMigrationName(file);if(appliedNames.has(name))continue;const from=basename(file)===FOLDER_MIGRATION_FILE?takeMoved(moved,name):undefined;if(from)plan.renames.push({from,to:name});else plan.pending.push(file)}return plan};const takeMoved=(moved,name)=>{const records=moved.get(descriptiveName(name));const generatedAt=folderTime(name);const index=records?.findIndex(({appliedAt})=>generatedAt===undefined||generatedAt<=appliedAt.getTime())??-1;return index<0?undefined:records.splice(index,1)[0].name};
@@ -0,0 +1,61 @@
1
+ import type { ModelAdapter } from '@elysiumjs/mnemosyne';
2
+ import type { PgTableWithColumns, TableConfig } from 'drizzle-orm/pg-core';
3
+ /**
4
+ * Drizzle-specific model adapter.
5
+ *
6
+ * Bridges Drizzle's `PgTableWithColumns` to the framework's
7
+ * normalized model metadata.
8
+ *
9
+ * @author Axel Nana <axel.nana@workbud.com>
10
+ */
11
+ export declare const drizzleAdapter: ModelAdapter<PgTableWithColumns<any>>;
12
+ /**
13
+ * Creates Elysia validation schemas from a Drizzle table.
14
+ *
15
+ * This is a convenience wrapper around `createSchemaFromModel` that
16
+ * automatically extracts column metadata via the `drizzleAdapter`.
17
+ *
18
+ * @author Axel Nana <axel.nana@workbud.com>
19
+ * @param table The Drizzle table to generate schemas for.
20
+ * @param opts Schema generation options.
21
+ * @returns An Elysia validation schema.
22
+ */
23
+ export declare const createSchemaFromDrizzle: (table: PgTableWithColumns<any>, opts?: {
24
+ mode?: 'create' | 'update' | 'select';
25
+ }) => import("@sinclair/typebox").TObject<{}>;
26
+ /**
27
+ * Options for creating a DrizzleModel.
28
+ */
29
+ export interface DrizzleModelOptions {
30
+ /**
31
+ * Enable tenant schema support for this model.
32
+ * @default false
33
+ */
34
+ supportTenancy?: boolean;
35
+ }
36
+ /**
37
+ * Creates a model class backed by a Drizzle pgTable.
38
+ *
39
+ * @author Axel Nana <axel.nana@workbud.com>
40
+ * @param table The Drizzle table (created with enhanced pgTable).
41
+ * @param options Model configuration options.
42
+ * @returns A model class with Drizzle-backed static metadata.
43
+ */
44
+ export declare const DrizzleModel: <TTable extends PgTableWithColumns<TableConfig>>(table: TTable, options?: DrizzleModelOptions) => {
45
+ new (): {};
46
+ readonly $inferSelect: unknown;
47
+ readonly $inferInsert: unknown;
48
+ readonly $inferUpdate: unknown;
49
+ readonly table: any;
50
+ readonly tableName: string;
51
+ readonly columns: Record<string, import("drizzle-orm/pg-core").AnyPgColumnBuilder>;
52
+ readonly insertSchema: import("@sinclair/typebox").TObject<{}>;
53
+ readonly updateSchema: import("@sinclair/typebox").TObject<{}>;
54
+ readonly selectSchema: import("@sinclair/typebox").TObject<{}>;
55
+ readonly supportTenancy: boolean;
56
+ } & {
57
+ readonly $inferSelect: TTable["$inferSelect"];
58
+ readonly $inferInsert: TTable["$inferInsert"];
59
+ readonly $inferUpdate: Partial<TTable["$inferInsert"]>;
60
+ readonly table: TTable;
61
+ };
package/dist/model.js ADDED
@@ -0,0 +1 @@
1
+ import{AbstractModel,createSchemaFromModel}from"@elysiumjs/mnemosyne";import{getTableConfig}from"drizzle-orm/pg-core";import{getTableBuilders}from"./table";import{wrapTenantSchema}from"./tenancy";const mapDrizzleDataType=(col)=>{if(col.columnType==="PgUUID")return"uuid";if(col.dimensions)return"array";const[kind,constraint]=col.dataType.split(" ");switch(kind){case"number":return"number";case"boolean":return"boolean";case"array":return"array";case"bigint":return"bigint";case"object":switch(constraint){case"json":return"json";case"date":return"date";case"buffer":return"buffer";default:return"string"}default:return"string"}};export const drizzleAdapter={getTableName(table){return getTableConfig(table).name},getColumns(table){const config=getTableConfig(table);return config.columns.map((col)=>({name:col.name,dataType:mapDrizzleDataType(col),nullable:!col.notNull,hasDefault:col.hasDefault,isPrimaryKey:col.primary||config.primaryKeys.some((pk)=>pk.columns.some((c)=>c.name===col.name))}))},createTenantTable(table,tenant){const builders=getTableBuilders(table);if(!builders){throw new Error(`No column builders registered for table. `+`Use pgTable() from '@elysiumjs/mnemosyne-drizzle' for tenant support.`)}const config=getTableConfig(table);return wrapTenantSchema(tenant,config.name,builders)}};export const createSchemaFromDrizzle=(table,opts)=>{return createSchemaFromModel(drizzleAdapter.getColumns(table),opts)};export const DrizzleModel=(table,options)=>{const config=getTableConfig(table);const builders=getTableBuilders(table);const Base=AbstractModel(config.name,builders??{},drizzleAdapter,table);if(options?.supportTenancy!==undefined){Base.supportTenancy=options.supportTenancy}return Base};