@elysiumjs/mnemosyne-drizzle 0.2.0 → 0.3.1

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 (46) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/cache.d.ts +60 -0
  3. package/dist/cache.js +1 -0
  4. package/dist/commands/migration_generate.command.js +1 -1
  5. package/dist/commands/migration_run.command.d.ts +13 -0
  6. package/dist/commands/migration_run.command.js +6 -11
  7. package/dist/commands/project.d.ts +11 -0
  8. package/dist/commands/project.js +1 -0
  9. package/dist/database.d.ts +55 -23
  10. package/dist/database.js +1 -1
  11. package/dist/drizzle.module.d.ts +2 -2
  12. package/dist/drizzle.module.js +1 -1
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.js +1 -1
  15. package/dist/migrations.d.ts +12 -0
  16. package/dist/migrations.js +2 -1
  17. package/dist/model.d.ts +5 -5
  18. package/dist/repository.d.ts +20 -5
  19. package/dist/repository.js +1 -1
  20. package/dist/tenancy.d.ts +25 -8
  21. package/dist/tenancy.js +1 -1
  22. package/package.json +19 -9
  23. package/.omc/state/agent-replay-3920b579-6d8e-4f5f-924a-319202936685.jsonl +0 -7
  24. package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/last-tool-error-state.json +0 -7
  25. package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/mission-state.json +0 -93
  26. package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/pre-tool-advisory-throttle.json +0 -18
  27. package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/subagent-tracking-state.json +0 -61
  28. package/.turbo/turbo-build.log +0 -3
  29. package/scripts/minify.ts +0 -10
  30. package/src/commands/migration_generate.command.ts +0 -101
  31. package/src/commands/migration_run.command.ts +0 -160
  32. package/src/database.ts +0 -205
  33. package/src/drizzle.module.ts +0 -97
  34. package/src/index.ts +0 -47
  35. package/src/migrations.ts +0 -100
  36. package/src/model.ts +0 -178
  37. package/src/repository.ts +0 -113
  38. package/src/table.ts +0 -70
  39. package/src/tenancy.ts +0 -249
  40. package/tests/drizzle.module.test.ts +0 -201
  41. package/tests/migrations.test.ts +0 -123
  42. package/tests/model.test.ts +0 -362
  43. package/tests/setup.ts +0 -0
  44. package/tests/table.test.ts +0 -72
  45. package/tests/tenancy.test.ts +0 -212
  46. package/tsconfig.json +0 -12
package/dist/tenancy.d.ts CHANGED
@@ -6,6 +6,7 @@ import type { DrizzleConnection } from './database';
6
6
  * @author Axel Nana <axel.nana@workbud.com>
7
7
  * @param tenant The tenant identifier.
8
8
  * @returns The tenant's PostgreSQL schema.
9
+ * @throws {Error} If the tenant identifier is malformed.
9
10
  */
10
11
  export declare const getTenantSchema: (tenant: string) => PgSchema;
11
12
  /**
@@ -13,6 +14,7 @@ export declare const getTenantSchema: (tenant: string) => PgSchema;
13
14
  * @author Axel Nana <axel.nana@workbud.com>
14
15
  * @param tenant The tenant identifier.
15
16
  * @returns The newly registered (or existing) `PgSchema`.
17
+ * @throws {Error} If the tenant identifier is malformed.
16
18
  */
17
19
  export declare const registerTenantSchema: (tenant: string) => PgSchema;
18
20
  /**
@@ -26,26 +28,27 @@ export declare const registerTenantSchema: (tenant: string) => PgSchema;
26
28
  * @param tableName The table name.
27
29
  * @param columns The column definitions.
28
30
  * @returns A tenant-scoped `PgTable`.
31
+ * @throws {Error} If the tenant identifier is malformed.
29
32
  */
30
33
  export declare const wrapTenantSchema: <TTableName extends string, TColumnsMap extends Record<string, AnyPgColumnBuilder>>(tenant: string, tableName: TTableName, columns: TColumnsMap) => PgTable;
31
34
  /**
32
35
  * Executes a callback within an RLS-scoped transaction for the given tenant.
36
+ *
37
+ * The transaction is registered in the async context, so repositories and
38
+ * `Database.getConnection()` use it inside the callback. When a transaction is already open on the
39
+ * connection, the callback joins it if it is bound to the same tenant. Work for several tenants
40
+ * (such as an admin fan-out) must use one `withRLS()` per tenant, outside any transaction.
33
41
  * @author Axel Nana <axel.nana@workbud.com>
34
42
  * @param tenant The tenant identifier (or `null` for 'public').
35
43
  * @param callback The function to execute within the transaction.
36
44
  * @param connectionName The database connection name.
37
45
  * @returns The return value of the callback.
46
+ * @throws {TenantMismatchError} If the open transaction is bound to another tenant.
38
47
  */
39
48
  export declare const withRLS: <T>(tenant: string | null, callback: (tx: PgAsyncTransaction<any, any>) => Promise<T>, connectionName?: string) => Promise<T>;
40
49
  /**
41
- * Sets the current tenant on a persistent database connection (session-level).
42
- * @author Axel Nana <axel.nana@workbud.com>
43
- * @param tenant The tenant identifier (or `null` for 'public').
44
- * @param connectionName The database connection name.
45
- */
46
- export declare const setConnectionTenant: (tenant: string | null, connectionName?: string) => Promise<void>;
47
- /**
48
- * Reads the current tenant from the PostgreSQL session variable.
50
+ * Reads the current tenant from the PostgreSQL tenant variable, on the open transaction when
51
+ * there is one.
49
52
  * @author Axel Nana <axel.nana@workbud.com>
50
53
  * @param connectionName The database connection name.
51
54
  * @returns The current tenant identifier, or `null`.
@@ -84,5 +87,19 @@ export declare class DrizzleSchemaTenancy implements TenancyStrategy<PgTable, Dr
84
87
  export declare class DrizzleRLSTenancy implements TenancyStrategy<PgTable, DrizzleConnection> {
85
88
  readonly mode = "rls";
86
89
  resolveTable(table: PgTable, _tenant: string): PgTable;
90
+ /**
91
+ * Runs a callback in a transaction scoped to the given tenant.
92
+ *
93
+ * The transaction is bound to the tenant for its whole life, registered in the async context
94
+ * for the connection, and the tenant set as the current tenant, for the duration of the
95
+ * callback. When a transaction is already open on the connection, the callback joins it, which
96
+ * is only allowed when that transaction is bound to the same tenant.
97
+ * @author Axel Nana <axel.nana@workbud.com>
98
+ * @param connection The base connection (see `Database.getBaseConnection`).
99
+ * @param tenant The tenant identifier.
100
+ * @param callback The function to execute within the isolated context.
101
+ * @returns The return value of the callback.
102
+ * @throws {TenantMismatchError} If the open transaction is bound to another tenant.
103
+ */
87
104
  withIsolation<T>(connection: DrizzleConnection, tenant: string, callback: () => Promise<T>): Promise<T>;
88
105
  }
package/dist/tenancy.js CHANGED
@@ -1 +1 @@
1
- import{getTenancyConfig}from"@elysiumjs/mnemosyne";import{sql}from"drizzle-orm";import{getTableConfig,pgPolicy,pgSchema,pgTable}from"drizzle-orm/pg-core";import{Database}from"./database";import{getTableBuilders}from"./table";const tenantSchemas=new Map;const schemaRegistry=new Map;export const getTenantSchema=(tenant)=>{return schemaRegistry.get(tenant)??registerTenantSchema(tenant)};export const registerTenantSchema=(tenant)=>{if(schemaRegistry.has(tenant)){return schemaRegistry.get(tenant)}const tenantSchema=pgSchema(tenant);schemaRegistry.set(tenant,tenantSchema);return tenantSchema};export const wrapTenantSchema=(tenant,tableName,columns)=>{if(tenant==="public"){return pgTable(tableName,columns)}const fullTableName=`${tenant}.${tableName}`;if(tenantSchemas.has(fullTableName)){return tenantSchemas.get(fullTableName)}const tenantSchema=getTenantSchema(tenant);const schemaTable=tenantSchema.table(tableName,columns);tenantSchemas.set(fullTableName,schemaTable);return schemaTable};export const withRLS=async(tenant,callback,connectionName="default")=>{const db=Database.getConnection(connectionName);const config=getTenancyConfig();const varName=config.rls?.sessionVariable??"app.current_tenant";const value=tenant??"public";return db.transaction(async(tx)=>{await tx.execute(sql`SELECT set_config(${varName}, ${value}, true)`);return callback(tx)})};export const setConnectionTenant=async(tenant,connectionName="default")=>{const db=Database.getConnection(connectionName);const config=getTenancyConfig();const varName=config.rls?.sessionVariable??"app.current_tenant";const value=tenant??"public";await db.execute(sql`SELECT set_config(${varName}, ${value}, false)`)};export const getSessionTenant=async(connectionName="default")=>{const db=Database.getConnection(connectionName);const config=getTenancyConfig();const varName=config.rls?.sessionVariable??"app.current_tenant";const result=await db.execute(sql`SELECT current_setting(${varName}, true) as tenant`);return result?.rows?.[0]?.tenant??null};export const createRLSPolicy=(tableName,columnName,policyName)=>{const config=getTenancyConfig();const varName=config.rls?.sessionVariable??"app.current_tenant";const column=columnName??config.rls?.defaultColumn??"tenant_id";const name=policyName??`${tableName}_tenant_isolation`;return pgPolicy(name,{for:"all",to:"public",using:sql`${sql.identifier(column)} = current_setting(${varName}, true)::text`,withCheck:sql`${sql.identifier(column)} = current_setting(${varName}, true)::text`})};export class DrizzleSchemaTenancy{mode="schema";resolveTable(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)}async withIsolation(_connection,_tenant,callback){return callback()}}export class DrizzleRLSTenancy{mode="rls";resolveTable(table,_tenant){return table}async withIsolation(connection,tenant,callback){const config=getTenancyConfig();const varName=config.rls?.sessionVariable??"app.current_tenant";return connection.transaction(async(tx)=>{await tx.execute(sql`SELECT set_config(${varName}, ${tenant}, true)`);return callback()})}}
1
+ import{getTenancyConfig,isValidTenantId,withTenant}from"@elysiumjs/mnemosyne";import{sql}from"drizzle-orm";import{getTableConfig,pgPolicy,pgSchema,pgTable}from"drizzle-orm/pg-core";import{assertTransactionTenant,bindTransactionTenant,Database}from"./database";import{getTableBuilders}from"./table";const tenantSchemas=new Map;const schemaRegistry=new Map;const assertTenantId=(tenant)=>{if(!isValidTenantId(tenant)){throw new Error(`Invalid tenant identifier: ${JSON.stringify(tenant.slice(0,64))}`)}};export const getTenantSchema=(tenant)=>{assertTenantId(tenant);return schemaRegistry.get(tenant)??registerTenantSchema(tenant)};export const registerTenantSchema=(tenant)=>{assertTenantId(tenant);if(schemaRegistry.has(tenant)){return schemaRegistry.get(tenant)}const tenantSchema=pgSchema(tenant);schemaRegistry.set(tenant,tenantSchema);return tenantSchema};export const wrapTenantSchema=(tenant,tableName,columns)=>{assertTenantId(tenant);if(tenant==="public"){return pgTable(tableName,columns)}const fullTableName=`${tenant}.${tableName}`;if(tenantSchemas.has(fullTableName)){return tenantSchemas.get(fullTableName)}const tenantSchema=getTenantSchema(tenant);const schemaTable=tenantSchema.table(tableName,columns);tenantSchemas.set(fullTableName,schemaTable);return schemaTable};export const withRLS=async(tenant,callback,connectionName="default")=>{return new DrizzleRLSTenancy().withIsolation(Database.getBaseConnection(connectionName),tenant??"public",()=>callback(Database.getConnection(connectionName)))};export const getSessionTenant=async(connectionName="default")=>{const db=Database.getConnection(connectionName);const config=getTenancyConfig();const varName=config.rls?.sessionVariable??"app.current_tenant";const[row]=await db.execute(sql`SELECT current_setting(${varName}, true) as tenant`);return row?.tenant||null};export const createRLSPolicy=(tableName,columnName,policyName)=>{const config=getTenancyConfig();const varName=config.rls?.sessionVariable??"app.current_tenant";const column=columnName??config.rls?.defaultColumn??"tenant_id";const name=policyName??`${tableName}_tenant_isolation`;return pgPolicy(name,{for:"all",to:"public",using:sql`${sql.identifier(column)} = current_setting(${varName}, true)::text`,withCheck:sql`${sql.identifier(column)} = current_setting(${varName}, true)::text`})};export class DrizzleSchemaTenancy{mode="schema";resolveTable(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)}async withIsolation(_connection,_tenant,callback){return callback()}}export class DrizzleRLSTenancy{mode="rls";resolveTable(table,_tenant){return table}async withIsolation(connection,tenant,callback){const active=Database.getActiveTransaction(connection);if(active!==undefined){assertTransactionTenant(active,tenant);return withTenant(tenant,callback)}return connection.transaction(async(tx)=>{await bindTransactionTenant(tx,tenant);return withTenant(tenant,()=>Database.runInTransaction(connection,tx,callback))})}}
package/package.json CHANGED
@@ -1,22 +1,28 @@
1
1
  {
2
2
  "name": "@elysiumjs/mnemosyne-drizzle",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Drizzle ORM driver for @elysiumjs/mnemosyne",
6
6
  "private": false,
7
7
  "author": "Workbud Technologies Inc.",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/workbud/elysium"
10
+ "url": "git+https://github.com/na2axl/elysiumjs.git",
11
+ "directory": "@elysiumjs/mnemosyne-drizzle"
12
+ },
13
+ "homepage": "https://github.com/na2axl/elysiumjs/tree/main/@elysiumjs/mnemosyne-drizzle#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/na2axl/elysiumjs/issues"
11
16
  },
12
- "homepage": "https://github.com/workbud/elysium",
13
17
  "scripts": {
14
18
  "format": "prettier --write \"./src/**/*.ts\"",
15
19
  "clean": "rimraf dist",
16
20
  "build": "bun run clean && tsc && bun run build:minify",
17
21
  "build:minify": "bun scripts/minify.ts",
18
- "typecheck": "tsc --noEmit",
19
- "prepublishOnly": "bun run build"
22
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.types.json",
23
+ "prepublishOnly": "bun run build",
24
+ "test": "bun test",
25
+ "test:coverage": "bun test --coverage"
20
26
  },
21
27
  "keywords": [
22
28
  "elysium",
@@ -28,10 +34,14 @@
28
34
  "module": "dist/index.js",
29
35
  "type": "module",
30
36
  "types": "dist/index.d.ts",
37
+ "files": [
38
+ "dist",
39
+ "CHANGELOG.md"
40
+ ],
31
41
  "sideEffects": false,
32
42
  "peerDependencies": {
33
- "@elysiumjs/core": "^0.8.0",
34
- "@elysiumjs/mnemosyne": "^0.2.0"
43
+ "@elysiumjs/core": "^0.10.0",
44
+ "@elysiumjs/mnemosyne": "^0.3.1"
35
45
  },
36
46
  "dependencies": {
37
47
  "drizzle-orm": "^1.0.0-rc.5",
@@ -40,7 +50,7 @@
40
50
  },
41
51
  "devDependencies": {
42
52
  "@types/prompts": "^2.4.9",
43
- "@elysiumjs/core": "0.8.0",
44
- "@elysiumjs/mnemosyne": "0.2.0"
53
+ "@elysiumjs/core": "0.10.0",
54
+ "@elysiumjs/mnemosyne": "0.3.1"
45
55
  }
46
56
  }
@@ -1,7 +0,0 @@
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}
@@ -1,7 +0,0 @@
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
- }
@@ -1,93 +0,0 @@
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
- }
@@ -1,18 +0,0 @@
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
- }
@@ -1,61 +0,0 @@
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
- }
@@ -1,3 +0,0 @@
1
- $ bun run clean && tsc && bun run build:minify
2
- $ rimraf dist
3
- $ bun scripts/minify.ts
package/scripts/minify.ts DELETED
@@ -1,10 +0,0 @@
1
- import { Glob } from 'bun';
2
-
3
- const glob = new Glob('dist/**/*.js');
4
-
5
- for await (const path of glob.scan('.')) {
6
- const file = Bun.file(path);
7
- const code = await file.text();
8
- const result = new Bun.Transpiler({ minifyWhitespace: true, trimUnusedImports: true }).transformSync(code);
9
- await Bun.write(file, result);
10
- }
@@ -1,101 +0,0 @@
1
- // Copyright (c) 2025-present Workbud Technologies Inc. All rights reserved.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import { existsSync } from 'node:fs';
16
- import { exists } from 'node:fs/promises';
17
- import { dirname, join } from 'node:path';
18
-
19
- import { Command, CommandArgumentType } from '@elysiumjs/core';
20
- import prompts from 'prompts';
21
-
22
- /**
23
- * Gets the root path of the Elysium.js project: the closest parent holding an `.elysiumrc`.
24
- * @author Axel Nana <axel.nana@workbud.com>
25
- */
26
- const getProjectPath = () => {
27
- const startPath = process.cwd();
28
- let dir = startPath;
29
-
30
- while (true) {
31
- if (existsSync(join(dir, '.elysiumrc'))) return dir;
32
-
33
- const parentDir = dirname(dir);
34
- if (dir === parentDir) break;
35
-
36
- dir = parentDir;
37
- }
38
-
39
- return startPath;
40
- };
41
-
42
- /**
43
- * Generates migration files.
44
- * @author Axel Nana <axel.nana@workbud.com>
45
- */
46
- @Command.register()
47
- export class MigrationGenerateCommand extends Command {
48
- public static readonly command: string = 'migration:generate';
49
- public static readonly description: string = 'Generates migration files.';
50
-
51
- @Command.arg({
52
- description: 'The name of the migration to create',
53
- type: CommandArgumentType.STRING
54
- })
55
- private name?: string;
56
-
57
- public async run(): Promise<void> {
58
- if (!this.name) {
59
- const answers = await prompts(
60
- [
61
- {
62
- type: 'text',
63
- name: 'name',
64
- message: 'Migration Name:',
65
- initial: 'create_users_table',
66
- validate(value: string) {
67
- if (value.length < 1) {
68
- return 'Migration name cannot be empty';
69
- }
70
-
71
- return true;
72
- }
73
- }
74
- ],
75
- {
76
- onCancel: () => {
77
- this.error('Operation cancelled.');
78
- process.exit(0);
79
- }
80
- }
81
- );
82
-
83
- this.name = answers.name;
84
- }
85
-
86
- const projectRoot = getProjectPath();
87
- let schemaPath = `${projectRoot}/src/database/schemas`;
88
- if (await exists(`${projectRoot}/src/database/schema.ts`)) {
89
- schemaPath = `${projectRoot}/src/database/schema.ts`;
90
- }
91
-
92
- await Bun.$`bunx --bun drizzle-kit generate --dialect postgresql --schema ${schemaPath} --out ${projectRoot}/src/database/migrations --name ${this.name}`;
93
-
94
- const sqlFiles = new Bun.Glob(`${projectRoot}/src/database/migrations/**/*.sql`).scan();
95
- for await (const path of sqlFiles) {
96
- const file = Bun.file(path);
97
- const content = await file.text();
98
- await file.write(content.replace(/"public"\./gm, ''));
99
- }
100
- }
101
- }
@@ -1,160 +0,0 @@
1
- // Copyright (c) 2025-present Workbud Technologies Inc. All rights reserved.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import { Command, CommandArgumentType } from '@elysiumjs/core';
16
- import prompts from 'prompts';
17
-
18
- import { Database } from '../database';
19
- import { getMigrationName, planMigrations } from '../migrations';
20
-
21
- /**
22
- * Execute migration files.
23
- * @author Axel Nana <axel.nana@workbud.com>
24
- */
25
- @Command.register()
26
- export class MigrationRunCommand extends Command {
27
- public static readonly command: string = 'migration:run';
28
- public static readonly description: string = 'Runs pending migrations.';
29
-
30
- @Command.arg({
31
- description: 'The name of the tenant in which the migrations will be executed.',
32
- type: CommandArgumentType.STRING,
33
- required: true
34
- })
35
- private tenant: string = '';
36
-
37
- @Command.arg({
38
- description: 'The name of the connection to use.',
39
- type: CommandArgumentType.STRING,
40
- default: 'default'
41
- })
42
- private connection: string = 'default';
43
-
44
- @Command.arg({
45
- description: 'Drop the entire schema before running the migrations.',
46
- type: CommandArgumentType.BOOLEAN,
47
- default: false
48
- })
49
- private fresh: boolean = false;
50
-
51
- public async run(): Promise<void> {
52
- if (!Database.connectionExists(this.connection)) {
53
- this.error(`Connection ${this.bold(this.connection)} does not exist.`);
54
- return process.exit(1);
55
- }
56
-
57
- const sql = Database.getConnection(this.connection).$client;
58
- await sql.connect();
59
-
60
- await sql.begin(async (sql: Bun.SQL) => {
61
- if (this.fresh) {
62
- await sql`DROP SCHEMA IF EXISTS ${sql(this.tenant)} CASCADE;`;
63
- await sql`CREATE SCHEMA ${sql(this.tenant)};`;
64
- }
65
-
66
- const [{ schema_exists }] = await sql`SELECT EXISTS (
67
- SELECT schema_name
68
- FROM information_schema.schemata
69
- WHERE schema_name = ${this.tenant}
70
- ) AS schema_exists;`;
71
-
72
- if (!schema_exists) {
73
- const answers = await prompts(
74
- [
75
- {
76
- type: 'confirm',
77
- name: 'create',
78
- message: `Schema ${this.tenant} does not exist. Do you want to create it?`,
79
- initial: true
80
- }
81
- ],
82
- {
83
- onCancel: () => {
84
- this.error('Operation cancelled.');
85
- process.exit(0);
86
- }
87
- }
88
- );
89
-
90
- if (answers.create) {
91
- await sql`CREATE SCHEMA ${sql(this.tenant)};`;
92
- } else {
93
- this.error(`Schema ${this.bold(this.tenant)} creation cancelled.`);
94
- return process.exit(1);
95
- }
96
- }
97
-
98
- await sql`SET search_path TO ${sql(this.tenant)};`;
99
-
100
- const [{ table_exists }] = await sql`SELECT EXISTS (
101
- SELECT *
102
- FROM information_schema.tables
103
- WHERE table_name = 'elysium_migrations'
104
- AND table_schema = ${this.tenant}
105
- ) AS table_exists;`;
106
-
107
- if (!table_exists) {
108
- await sql`CREATE TABLE elysium_migrations (
109
- id SERIAL PRIMARY KEY,
110
- name TEXT NOT NULL,
111
- batch INTEGER NOT NULL,
112
- applied_at TIMESTAMP NOT NULL
113
- );`;
114
- }
115
-
116
- const rows: { name: string; applied_at: Date }[] =
117
- await sql`SELECT name, applied_at FROM elysium_migrations;`;
118
- const completedMigrations = rows.map((row) => ({
119
- name: row.name,
120
- appliedAt: row.applied_at
121
- }));
122
-
123
- const filesIt = new Bun.Glob(`./src/database/migrations/**/*.sql`).scan({
124
- absolute: true,
125
- onlyFiles: true
126
- });
127
-
128
- const files = await Array.fromAsync(filesIt);
129
- const { pending: sqlFiles, renames } = planMigrations(files, completedMigrations);
130
-
131
- for (const { from, to } of renames) {
132
- await sql`UPDATE elysium_migrations SET name = ${to} WHERE name = ${from};`;
133
- this.info(`Migration ${this.bold(from)} is now tracked as ${this.bold(to)}.`);
134
- }
135
-
136
- if (sqlFiles.length === 0) {
137
- this.success('No new migrations to apply.');
138
- return;
139
- }
140
-
141
- const s = this.spinner(`Running migrations for tenant ${this.bold(this.tenant)}...`);
142
-
143
- const [{ max }]: [{ max?: number }] =
144
- await sql`SELECT MAX(batch) + 1 AS max FROM elysium_migrations;`;
145
-
146
- const batch = max ?? 0;
147
-
148
- for (const path of sqlFiles) {
149
- await sql.file(path);
150
- const [{ name }] =
151
- await sql`INSERT INTO elysium_migrations (name, batch, applied_at) VALUES (${getMigrationName(path)}, ${batch}, ${new Date()}) RETURNING name;`;
152
- s.pause(`Migration ${this.bold(name)} applied successfully.`);
153
- }
154
-
155
- s.complete(`Migrations for tenant ${this.bold(this.tenant)} applied successfully.`);
156
- });
157
-
158
- await sql.close();
159
- }
160
- }