@elysiumjs/mnemosyne-drizzle 0.1.4 → 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.
- package/.omc/state/agent-replay-3920b579-6d8e-4f5f-924a-319202936685.jsonl +7 -0
- package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/last-tool-error-state.json +7 -0
- package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/mission-state.json +93 -0
- package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/pre-tool-advisory-throttle.json +18 -0
- package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/subagent-tracking-state.json +61 -0
- package/.turbo/turbo-build.log +3 -9
- package/CHANGELOG.md +40 -0
- package/README.md +51 -0
- package/dist/commands/migration_generate.command.d.ts +11 -0
- package/dist/commands/migration_generate.command.js +1 -0
- package/dist/commands/migration_run.command.d.ts +13 -0
- package/dist/commands/migration_run.command.js +15 -0
- package/dist/database.d.ts +9 -1
- package/dist/database.js +1 -0
- package/dist/drizzle.module.d.ts +40 -0
- package/dist/drizzle.module.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -2
- package/dist/migrations.d.ts +33 -0
- package/dist/migrations.js +1 -0
- package/dist/model.d.ts +5 -5
- package/dist/model.js +1 -0
- package/dist/repository.js +1 -0
- package/dist/table.d.ts +47 -123
- package/dist/table.js +1 -0
- package/dist/tenancy.d.ts +3 -3
- package/dist/tenancy.js +1 -0
- package/package.json +11 -9
- package/scripts/minify.ts +10 -0
- package/src/commands/migration_generate.command.ts +101 -0
- package/src/commands/migration_run.command.ts +160 -0
- package/src/database.ts +37 -7
- package/src/drizzle.module.ts +97 -0
- package/src/index.ts +1 -31
- package/src/migrations.ts +100 -0
- package/src/model.ts +18 -11
- package/src/table.ts +4 -4
- package/src/tenancy.ts +5 -5
- package/tests/drizzle.module.test.ts +201 -0
- package/tests/migrations.test.ts +123 -0
- package/tests/setup.ts +0 -10
- package/tsconfig.json +2 -2
- package/.turbo/turbo-typecheck.log +0 -1
- package/coverage/.lcov.info.01ffde0d9f956dce.tmp +0 -14
- package/coverage/.lcov.info.2f80a4def3a82099.tmp +0 -343
- package/coverage/.lcov.info.560a2daaba0934cd.tmp +0 -366
- package/coverage/.lcov.info.7162c9b788dc0bc6.tmp +0 -366
- package/coverage/.lcov.info.b57c0b452c3901e5.tmp +0 -17
- package/coverage/.lcov.info.c47de8626f331696.tmp +0 -17
- package/coverage/.lcov.info.c710a42b25b08349.tmp +0 -343
- package/coverage/.lcov.info.dbc8f065467fc8a2.tmp +0 -287
- package/coverage/.lcov.info.de180a43301f82c0.tmp +0 -35
- package/coverage/.lcov.info.df18163ab929ab1b.tmp +0 -357
- package/coverage/.lcov.info.febf711c192f5c0c.tmp +0 -17
- package/coverage/lcov.info +0 -357
- 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
|
+
}
|
package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/pre-tool-advisory-throttle.json
ADDED
|
@@ -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
|
+
}
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
[0m[2m[35m$[0m [2m[1mbun build ./src/index.ts --outdir ./dist --target bun --packages external --root ./src --minify[0m
|
|
5
|
-
[32mBundled 4 modules in 2ms[0m
|
|
6
|
-
|
|
7
|
-
[34mindex.js[0m 3.27 KB [2m(entry point)[0m
|
|
8
|
-
|
|
9
|
-
[0m[2m[35m$[0m [2m[1mtsc[0m
|
|
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};
|
package/dist/database.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DatabaseCacheConfig, DatabaseDriver } from '@elysiumjs/mnemosyne';
|
|
2
2
|
import type { DrizzleConfig } from 'drizzle-orm';
|
|
3
|
-
import type { BunSQLDatabase } from 'drizzle-orm/bun-sql';
|
|
3
|
+
import type { BunSQLDatabase } from 'drizzle-orm/bun-sql/postgres';
|
|
4
4
|
import type { CacheConfig } from 'drizzle-orm/cache/core/types';
|
|
5
5
|
import { AbstractDatabase } from '@elysiumjs/mnemosyne';
|
|
6
6
|
import { Table } from 'drizzle-orm';
|
|
@@ -61,6 +61,14 @@ export declare class DrizzleDatabaseCache extends DrizzleCache {
|
|
|
61
61
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
62
62
|
*/
|
|
63
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;
|
|
64
72
|
protected driver: DatabaseDriver<DrizzleConnection, DrizzleConnectionProps>;
|
|
65
73
|
}
|
|
66
74
|
/**
|
package/dist/database.js
ADDED
|
@@ -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};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export * from 'drizzle-orm';
|
|
2
2
|
export * from 'drizzle-orm/pg-core';
|
|
3
|
-
export type {
|
|
3
|
+
export type { SelectedFields, SelectedFieldsFlat, SelectedFieldsOrdered, TableConfig } from 'drizzle-orm/pg-core';
|
|
4
4
|
export { pgTable, getTableBuilders } from './table';
|
|
5
5
|
export { Database, DrizzleDatabaseCache } from './database';
|
|
6
6
|
export type { DrizzleConnectionProps, DrizzleConnection } from './database';
|
|
7
7
|
export { DrizzleModel, createSchemaFromDrizzle, drizzleAdapter, type DrizzleModelOptions } from './model';
|
|
8
|
+
export { DrizzleModule, type DrizzleModuleConfig } from './drizzle.module';
|
|
8
9
|
export { DrizzleRepository } from './repository';
|
|
9
10
|
export { DrizzleSchemaTenancy, DrizzleRLSTenancy, wrapTenantSchema, registerTenantSchema, getTenantSchema, withRLS, setConnectionTenant, getSessionTenant, createRLSPolicy } from './tenancy';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import{Application as T,Event as d}from"@elysiumjs/core";import{registerTenancyStrategy as N}from"@elysiumjs/mnemosyne";import{AbstractDatabase as j,createDatabaseCacheStorage as w}from"@elysiumjs/mnemosyne";import{getTableName as $,is as B,Table as I}from"drizzle-orm";import{drizzle as L}from"drizzle-orm/bun-sql";import{Cache as P}from"drizzle-orm/cache/core";class X extends P{cacheStrategy;globalTtl;#F;usedTablesPerKey={};constructor(F="explicit",G=1,H="redis"){super();this.cacheStrategy=F;this.globalTtl=G;this.#F=w({storage:H})}strategy(){return this.cacheStrategy}async get(F){return await this.#F.get(F)??void 0}async put(F,G,H,J,K){await this.#F.set(F,G,(K?.ex??this.globalTtl)*1000);for(let M of H){let U=this.usedTablesPerKey[M];if(U===void 0)this.usedTablesPerKey[M]=[F];else U.push(F)}}async onMutate(F){let G=F.tags?Array.isArray(F.tags)?F.tags:[F.tags]:[],H=F.tables?Array.isArray(F.tables)?F.tables:[F.tables]:[],J=new Set;for(let K of H){let M=B(K,I)?$(K):K,U=this.usedTablesPerKey[M]??[];for(let E of U)J.add(E)}if(J.size>0||G.length>0){await this.#F.mdel(G),await this.#F.mdel([...J]);for(let K of H){let M=B(K,I)?$(K):K;this.usedTablesPerKey[M]=[]}}}}class O extends j{driver={createConnection(F){let G=F.cache===void 0?void 0:F.cache===!0?new X:F.cache.enabled!==!1?new X(F.cache.strategy,F.cache.ttl,F.cache.storage):void 0;return L({...F,cache:G})},withTransaction(F,G){return F.transaction((H)=>G(H))},getRawClient(F){return F.$client}}}var V=new O;import{getTenancyConfig as q}from"@elysiumjs/mnemosyne";import{sql as A}from"drizzle-orm";import{getTableConfig as v,pgPolicy as s,pgSchema as x,pgTable as C}from"drizzle-orm/pg-core";import{pgTable as g}from"drizzle-orm/pg-core";var R=new WeakMap,Q=(F)=>{return R.get(F)};var Y=new Map,W=new Map,S=(F)=>{return W.get(F)??k(F)},k=(F)=>{if(W.has(F))return W.get(F);let G=x(F);return W.set(F,G),G},D=(F,G,H)=>{if(F==="public")return C(G,H);let J=`${F}.${G}`;if(Y.has(J))return Y.get(J);let M=S(F).table(G,H);return Y.set(J,M),M};class Z{mode="schema";resolveTable(F,G){let H=Q(F);if(!H)throw Error("No column builders registered for table. Use pgTable() from '@elysiumjs/mnemosyne-drizzle' for tenant support.");let J=v(F);return D(G,J.name,H)}async withIsolation(F,G,H){return H()}}class _{mode="rls";resolveTable(F,G){return F}async withIsolation(F,G,H){let K=q().rls?.sessionVariable??"app.current_tenant";return F.transaction(async(M)=>{return await M.execute(A`SELECT set_config(${K}, ${G}, true)`),H()})}}export*from"drizzle-orm";export*from"drizzle-orm/pg-core";d.once("elysium:app:launched",()=>{let G=T.instance.getConfig("elysium:mnemosyne");if(G?.database){for(let[H,J]of Object.entries(G.database.connections))V.registerConnection(H,J);if(V.connectionExists(G.database.default))V.setDefaultConnection(G.database.default)}if(G?.tenancy){let H=G.tenancy.mode==="rls"?new _:new Z;N(H)}});export{QF as wrapTenantSchema,wF as withRLS,LF as setConnectionTenant,EF as registerTenantSchema,JF as pgTable,jF as getTenantSchema,KF as getTableBuilders,PF as getSessionTenant,ZF as drizzleAdapter,YF as createSchemaFromDrizzle,RF as createRLSPolicy,IF as DrizzleSchemaTenancy,$F as DrizzleRepository,OF as DrizzleRLSTenancy,XF as DrizzleModel,VF as DrizzleDatabaseCache,UF as Database};
|
|
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};
|
package/dist/model.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ModelAdapter } from '@elysiumjs/mnemosyne';
|
|
2
|
-
import type { PgTableWithColumns } from 'drizzle-orm/pg-core';
|
|
2
|
+
import type { PgTableWithColumns, TableConfig } from 'drizzle-orm/pg-core';
|
|
3
3
|
/**
|
|
4
4
|
* Drizzle-specific model adapter.
|
|
5
5
|
*
|
|
@@ -21,7 +21,7 @@ export declare const drizzleAdapter: ModelAdapter<PgTableWithColumns<any>>;
|
|
|
21
21
|
* @returns An Elysia validation schema.
|
|
22
22
|
*/
|
|
23
23
|
export declare const createSchemaFromDrizzle: (table: PgTableWithColumns<any>, opts?: {
|
|
24
|
-
mode?:
|
|
24
|
+
mode?: 'create' | 'update' | 'select';
|
|
25
25
|
}) => import("@sinclair/typebox").TObject<{}>;
|
|
26
26
|
/**
|
|
27
27
|
* Options for creating a DrizzleModel.
|
|
@@ -41,14 +41,14 @@ export interface DrizzleModelOptions {
|
|
|
41
41
|
* @param options Model configuration options.
|
|
42
42
|
* @returns A model class with Drizzle-backed static metadata.
|
|
43
43
|
*/
|
|
44
|
-
export declare const DrizzleModel: <TTable extends PgTableWithColumns<
|
|
44
|
+
export declare const DrizzleModel: <TTable extends PgTableWithColumns<TableConfig>>(table: TTable, options?: DrizzleModelOptions) => {
|
|
45
45
|
new (): {};
|
|
46
46
|
readonly $inferSelect: unknown;
|
|
47
47
|
readonly $inferInsert: unknown;
|
|
48
48
|
readonly $inferUpdate: unknown;
|
|
49
|
-
readonly table:
|
|
49
|
+
readonly table: any;
|
|
50
50
|
readonly tableName: string;
|
|
51
|
-
readonly columns: Record<string, import("drizzle-orm/pg-core").
|
|
51
|
+
readonly columns: Record<string, import("drizzle-orm/pg-core").AnyPgColumnBuilder>;
|
|
52
52
|
readonly insertSchema: import("@sinclair/typebox").TObject<{}>;
|
|
53
53
|
readonly updateSchema: import("@sinclair/typebox").TObject<{}>;
|
|
54
54
|
readonly selectSchema: import("@sinclair/typebox").TObject<{}>;
|
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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AbstractRepository}from"@elysiumjs/mnemosyne";import{eq}from"drizzle-orm";import{Database}from"./database";export const DrizzleRepository=(model)=>{class R extends AbstractRepository(model,Database){async all(){return await this.db.select().from(model.table)}async paginate(count,page=1){const total=await this.db.$count(model.table);const offset=Math.max(page-1,0)*count;return{page,total,data:await this.db.select().from(model.table).offset(offset).limit(count)}}async find(id){const[row]=await this.db.select().from(model.table).where(eq(model.table.id,id));return row??null}async findBy(column,value){const[row]=await this.db.select().from(model.table).where(eq(model.table[column],value));return row??null}async insert(data){const[row]=await this.db.insert(model.table).values(data).returning();return row}async update(id,data){const[row]=await this.db.update(model.table).set(data).where(eq(model.table.id,id)).returning();return row}async updateAll(data){return await this.db.update(model.table).set(data).returning()}async delete(id){const[row]=await this.db.delete(model.table).where(eq(model.table.id,id)).returning();return row}async deleteAll(){return await this.db.delete(model.table).returning()}async exists(id){const count=await this.db.$count(model.table,eq(model.table.id,id));return count>0}}return R};
|