@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.
- package/CHANGELOG.md +60 -0
- package/dist/cache.d.ts +60 -0
- package/dist/cache.js +1 -0
- package/dist/commands/migration_generate.command.js +1 -1
- package/dist/commands/migration_run.command.d.ts +13 -0
- package/dist/commands/migration_run.command.js +6 -11
- package/dist/commands/project.d.ts +11 -0
- package/dist/commands/project.js +1 -0
- package/dist/database.d.ts +55 -23
- package/dist/database.js +1 -1
- package/dist/drizzle.module.d.ts +2 -2
- package/dist/drizzle.module.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/migrations.d.ts +12 -0
- package/dist/migrations.js +2 -1
- package/dist/model.d.ts +5 -5
- package/dist/repository.d.ts +20 -5
- package/dist/repository.js +1 -1
- package/dist/tenancy.d.ts +25 -8
- package/dist/tenancy.js +1 -1
- package/package.json +19 -9
- package/.omc/state/agent-replay-3920b579-6d8e-4f5f-924a-319202936685.jsonl +0 -7
- package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/last-tool-error-state.json +0 -7
- package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/mission-state.json +0 -93
- package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/pre-tool-advisory-throttle.json +0 -18
- package/.omc/state/sessions/3920b579-6d8e-4f5f-924a-319202936685/subagent-tracking-state.json +0 -61
- package/.turbo/turbo-build.log +0 -3
- package/scripts/minify.ts +0 -10
- package/src/commands/migration_generate.command.ts +0 -101
- package/src/commands/migration_run.command.ts +0 -160
- package/src/database.ts +0 -205
- package/src/drizzle.module.ts +0 -97
- package/src/index.ts +0 -47
- package/src/migrations.ts +0 -100
- package/src/model.ts +0 -178
- package/src/repository.ts +0 -113
- package/src/table.ts +0 -70
- package/src/tenancy.ts +0 -249
- package/tests/drizzle.module.test.ts +0 -201
- package/tests/migrations.test.ts +0 -123
- package/tests/model.test.ts +0 -362
- package/tests/setup.ts +0 -0
- package/tests/table.test.ts +0 -72
- package/tests/tenancy.test.ts +0 -212
- package/tsconfig.json +0 -12
package/src/database.ts
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026-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 type { CacheInterface, DatabaseCacheConfig, DatabaseDriver } from '@elysiumjs/mnemosyne';
|
|
16
|
-
import type { DrizzleConfig } from 'drizzle-orm';
|
|
17
|
-
import type { BunSQLDatabase } from 'drizzle-orm/bun-sql/postgres';
|
|
18
|
-
import type { CacheConfig } from 'drizzle-orm/cache/core/types';
|
|
19
|
-
|
|
20
|
-
import { Service, ServiceNotReadyError } from '@elysiumjs/core';
|
|
21
|
-
import { AbstractDatabase, createDatabaseCacheStorage } from '@elysiumjs/mnemosyne';
|
|
22
|
-
import { getTableName, is, Table } from 'drizzle-orm';
|
|
23
|
-
import { drizzle } from 'drizzle-orm/bun-sql';
|
|
24
|
-
import { Cache as DrizzleCache } from 'drizzle-orm/cache/core';
|
|
25
|
-
|
|
26
|
-
// ============================================================================
|
|
27
|
-
// Types
|
|
28
|
-
// ============================================================================
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Connection configuration for Drizzle ORM.
|
|
32
|
-
*
|
|
33
|
-
* Extends `DrizzleConfig` with connection details and optional caching.
|
|
34
|
-
*
|
|
35
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
36
|
-
* @template TSchema The Drizzle schema type.
|
|
37
|
-
*/
|
|
38
|
-
export type DrizzleConnectionProps<
|
|
39
|
-
TSchema extends Record<string, unknown> = Record<string, never>
|
|
40
|
-
> = Omit<DrizzleConfig<TSchema>, 'cache'> &
|
|
41
|
-
({ connection: string | ({ url?: string } & Bun.SQLOptions) } | { client: Bun.SQL }) & {
|
|
42
|
-
cache?: true | DatabaseCacheConfig;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* A Drizzle database connection backed by Bun's SQL driver.
|
|
47
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
48
|
-
*/
|
|
49
|
-
export type DrizzleConnection = BunSQLDatabase<Record<string, never>> & { $client: Bun.SQL };
|
|
50
|
-
|
|
51
|
-
// ============================================================================
|
|
52
|
-
// DrizzleDatabaseCache
|
|
53
|
-
// ============================================================================
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Drizzle query cache backed by the framework's cache storage.
|
|
57
|
-
*
|
|
58
|
-
* Extends the Drizzle `Cache` base class and implements the
|
|
59
|
-
* framework's `DatabaseCacheStrategy` interface so that query
|
|
60
|
-
* results can be transparently cached and invalidated.
|
|
61
|
-
*
|
|
62
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
63
|
-
*/
|
|
64
|
-
export class DrizzleDatabaseCache extends DrizzleCache {
|
|
65
|
-
#storageEngine: 'redis' | 'memory';
|
|
66
|
-
#cache?: Omit<CacheInterface, 'tags'>;
|
|
67
|
-
private usedTablesPerKey: Record<string, string[]> = {};
|
|
68
|
-
|
|
69
|
-
constructor(
|
|
70
|
-
private readonly cacheStrategy: 'explicit' | 'all' = 'explicit',
|
|
71
|
-
private readonly globalTtl: number = 1,
|
|
72
|
-
storageEngine: 'redis' | 'memory' = 'redis'
|
|
73
|
-
) {
|
|
74
|
-
super();
|
|
75
|
-
this.#storageEngine = storageEngine;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Resolves the cache storage on first use rather than at construction, so that a connection
|
|
80
|
-
* can be built before the module providing the cache has finished initialising.
|
|
81
|
-
*/
|
|
82
|
-
async #storage(): Promise<Omit<CacheInterface, 'tags'>> {
|
|
83
|
-
if (this.#cache) return this.#cache;
|
|
84
|
-
try {
|
|
85
|
-
this.#cache = createDatabaseCacheStorage({ storage: this.#storageEngine });
|
|
86
|
-
} catch (error) {
|
|
87
|
-
if (!(error instanceof ServiceNotReadyError)) throw error;
|
|
88
|
-
await Service.resolve(error.service);
|
|
89
|
-
this.#cache = createDatabaseCacheStorage({ storage: this.#storageEngine });
|
|
90
|
-
}
|
|
91
|
-
return this.#cache;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
public override strategy(): 'explicit' | 'all' {
|
|
95
|
-
return this.cacheStrategy;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
public override async get(key: string): Promise<any[] | undefined> {
|
|
99
|
-
return (await (await this.#storage()).get<any[]>(key)) ?? undefined;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
public override async put(
|
|
103
|
-
key: string,
|
|
104
|
-
response: any,
|
|
105
|
-
tables: string[],
|
|
106
|
-
_isTag: boolean,
|
|
107
|
-
config?: CacheConfig
|
|
108
|
-
): Promise<void> {
|
|
109
|
-
await (await this.#storage()).set(key, response, (config?.ex ?? this.globalTtl) * 1000);
|
|
110
|
-
for (const table of tables) {
|
|
111
|
-
const keys = this.usedTablesPerKey[table];
|
|
112
|
-
if (keys === undefined) {
|
|
113
|
-
this.usedTablesPerKey[table] = [key];
|
|
114
|
-
} else {
|
|
115
|
-
keys.push(key);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
public override async onMutate(params: {
|
|
121
|
-
tags?: string | string[];
|
|
122
|
-
tables?: string | string[] | Table<any> | Table<any>[];
|
|
123
|
-
}): Promise<void> {
|
|
124
|
-
const tagsArray = params.tags ? (Array.isArray(params.tags) ? params.tags : [params.tags]) : [];
|
|
125
|
-
const tablesArray = params.tables
|
|
126
|
-
? Array.isArray(params.tables)
|
|
127
|
-
? params.tables
|
|
128
|
-
: [params.tables]
|
|
129
|
-
: [];
|
|
130
|
-
const keysToDelete = new Set<string>();
|
|
131
|
-
|
|
132
|
-
for (const table of tablesArray) {
|
|
133
|
-
const tableName = is(table, Table) ? getTableName(table as Table) : (table as string);
|
|
134
|
-
const keys = this.usedTablesPerKey[tableName] ?? [];
|
|
135
|
-
for (const key of keys) keysToDelete.add(key);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (keysToDelete.size > 0 || tagsArray.length > 0) {
|
|
139
|
-
const cache = await this.#storage();
|
|
140
|
-
await cache.mdel(tagsArray);
|
|
141
|
-
await cache.mdel([...keysToDelete]);
|
|
142
|
-
for (const table of tablesArray) {
|
|
143
|
-
const tableName = is(table, Table) ? getTableName(table as Table) : (table as string);
|
|
144
|
-
this.usedTablesPerKey[tableName] = [];
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ============================================================================
|
|
151
|
-
// DrizzleDatabase
|
|
152
|
-
// ============================================================================
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Drizzle-specific database manager.
|
|
156
|
-
*
|
|
157
|
-
* Implements the abstract database class with a driver that creates
|
|
158
|
-
* Drizzle connections using Bun's built-in SQL client.
|
|
159
|
-
*
|
|
160
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
161
|
-
*/
|
|
162
|
-
class DrizzleDatabase extends AbstractDatabase<DrizzleConnection, DrizzleConnectionProps> {
|
|
163
|
-
/**
|
|
164
|
-
* Creates a Drizzle connection from its properties. Used by the `DrizzleModule` to build
|
|
165
|
-
* the `db.connection.{name}` services.
|
|
166
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
167
|
-
* @param props The connection properties.
|
|
168
|
-
* @returns A new connection.
|
|
169
|
-
*/
|
|
170
|
-
public createConnection(props: DrizzleConnectionProps): DrizzleConnection {
|
|
171
|
-
return this.driver.createConnection(props);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
protected driver: DatabaseDriver<DrizzleConnection, DrizzleConnectionProps> = {
|
|
175
|
-
createConnection(config) {
|
|
176
|
-
const cache =
|
|
177
|
-
config.cache === undefined
|
|
178
|
-
? undefined
|
|
179
|
-
: config.cache === true
|
|
180
|
-
? new DrizzleDatabaseCache()
|
|
181
|
-
: config.cache.enabled !== false
|
|
182
|
-
? new DrizzleDatabaseCache(
|
|
183
|
-
config.cache.strategy,
|
|
184
|
-
config.cache.ttl,
|
|
185
|
-
config.cache.storage
|
|
186
|
-
)
|
|
187
|
-
: undefined;
|
|
188
|
-
return drizzle({ ...config, cache }) as DrizzleConnection;
|
|
189
|
-
},
|
|
190
|
-
|
|
191
|
-
withTransaction(connection, callback) {
|
|
192
|
-
return (connection as any).transaction((tx: any) => callback(tx));
|
|
193
|
-
},
|
|
194
|
-
|
|
195
|
-
getRawClient(connection) {
|
|
196
|
-
return connection.$client;
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Singleton Drizzle database manager.
|
|
203
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
204
|
-
*/
|
|
205
|
-
export const Database = new DrizzleDatabase();
|
package/src/drizzle.module.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026-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 type { TenancyConfig } from '@elysiumjs/mnemosyne';
|
|
16
|
-
import type { DrizzleConnection, DrizzleConnectionProps } from './database';
|
|
17
|
-
|
|
18
|
-
import { Module, needsDefaultAlias, runAll, Service } from '@elysiumjs/core';
|
|
19
|
-
import {
|
|
20
|
-
AbstractDatabase,
|
|
21
|
-
MnemosyneModule,
|
|
22
|
-
TENANCY_CONFIG_SERVICE,
|
|
23
|
-
TENANCY_STRATEGY_SERVICE
|
|
24
|
-
} from '@elysiumjs/mnemosyne';
|
|
25
|
-
|
|
26
|
-
import { MigrationGenerateCommand } from './commands/migration_generate.command';
|
|
27
|
-
import { MigrationRunCommand } from './commands/migration_run.command';
|
|
28
|
-
import { Database } from './database';
|
|
29
|
-
import { DrizzleRLSTenancy, DrizzleSchemaTenancy } from './tenancy';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Configuration of the {@link DrizzleModule}.
|
|
33
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
34
|
-
*/
|
|
35
|
-
export type DrizzleModuleConfig = {
|
|
36
|
-
/** The name of the connection that `default` resolves to. */
|
|
37
|
-
default: string;
|
|
38
|
-
|
|
39
|
-
/** The connections to declare, by name. */
|
|
40
|
-
connections: Record<string, DrizzleConnectionProps>;
|
|
41
|
-
|
|
42
|
-
/** The tenancy configuration. Defaults to schema-based tenancy. */
|
|
43
|
-
tenancy?: TenancyConfig;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Declares Drizzle connections as global services named `db.connection.{name}`, plus
|
|
48
|
-
* `db.connection.default` aliasing the configured default, and registers the tenancy config
|
|
49
|
-
* and strategy. Connections are created on first use. Also provides the migration commands.
|
|
50
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
51
|
-
*/
|
|
52
|
-
@Module.register<DrizzleModuleConfig>({
|
|
53
|
-
imports: [MnemosyneModule],
|
|
54
|
-
commands: [MigrationGenerateCommand, MigrationRunCommand],
|
|
55
|
-
services: (config) => {
|
|
56
|
-
const alias = needsDefaultAlias('DrizzleModule', config);
|
|
57
|
-
const tenancy = config.tenancy ?? { mode: 'schema' };
|
|
58
|
-
return [
|
|
59
|
-
...Object.entries(config.connections).map(([name, props]) => ({
|
|
60
|
-
name: AbstractDatabase.getConnectionName(name),
|
|
61
|
-
global: true,
|
|
62
|
-
factory: () => Database.createConnection(props)
|
|
63
|
-
})),
|
|
64
|
-
...(alias
|
|
65
|
-
? [
|
|
66
|
-
{
|
|
67
|
-
name: AbstractDatabase.getConnectionName('default'),
|
|
68
|
-
global: true,
|
|
69
|
-
factory: (_config: unknown, get: <T>(name: string) => T) =>
|
|
70
|
-
get<DrizzleConnection>(AbstractDatabase.getConnectionName(config.default))
|
|
71
|
-
}
|
|
72
|
-
]
|
|
73
|
-
: []),
|
|
74
|
-
{ name: TENANCY_CONFIG_SERVICE, global: true, factory: () => tenancy },
|
|
75
|
-
{
|
|
76
|
-
name: TENANCY_STRATEGY_SERVICE,
|
|
77
|
-
global: true,
|
|
78
|
-
factory: () =>
|
|
79
|
-
tenancy.mode === 'rls' ? new DrizzleRLSTenancy() : new DrizzleSchemaTenancy()
|
|
80
|
-
}
|
|
81
|
-
];
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
export class DrizzleModule extends Module.configurable<DrizzleModuleConfig>({
|
|
85
|
-
defaults: { default: 'default' },
|
|
86
|
-
required: ['connections']
|
|
87
|
-
}) {
|
|
88
|
-
/** Closes the clients of the connections that were created. */
|
|
89
|
-
public override async onDestroy(): Promise<void> {
|
|
90
|
-
const names = Object.keys(this.config.connections).map(AbstractDatabase.getConnectionName);
|
|
91
|
-
await runAll(
|
|
92
|
-
names
|
|
93
|
-
.filter((name) => Service.isConstructed(name))
|
|
94
|
-
.map((name) => () => Service.get<DrizzleConnection>(name)?.$client.close())
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026-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
|
-
// Re-export drizzle-orm/pg-core with enhanced pgTable
|
|
16
|
-
export * from 'drizzle-orm';
|
|
17
|
-
export * from 'drizzle-orm/pg-core';
|
|
18
|
-
export type {
|
|
19
|
-
SelectedFields,
|
|
20
|
-
SelectedFieldsFlat,
|
|
21
|
-
SelectedFieldsOrdered,
|
|
22
|
-
TableConfig
|
|
23
|
-
} from 'drizzle-orm/pg-core';
|
|
24
|
-
export { pgTable, getTableBuilders } from './table';
|
|
25
|
-
|
|
26
|
-
// Re-exports
|
|
27
|
-
export { Database, DrizzleDatabaseCache } from './database';
|
|
28
|
-
export type { DrizzleConnectionProps, DrizzleConnection } from './database';
|
|
29
|
-
export {
|
|
30
|
-
DrizzleModel,
|
|
31
|
-
createSchemaFromDrizzle,
|
|
32
|
-
drizzleAdapter,
|
|
33
|
-
type DrizzleModelOptions
|
|
34
|
-
} from './model';
|
|
35
|
-
export { DrizzleModule, type DrizzleModuleConfig } from './drizzle.module';
|
|
36
|
-
export { DrizzleRepository } from './repository';
|
|
37
|
-
export {
|
|
38
|
-
DrizzleSchemaTenancy,
|
|
39
|
-
DrizzleRLSTenancy,
|
|
40
|
-
wrapTenantSchema,
|
|
41
|
-
registerTenantSchema,
|
|
42
|
-
getTenantSchema,
|
|
43
|
-
withRLS,
|
|
44
|
-
setConnectionTenant,
|
|
45
|
-
getSessionTenant,
|
|
46
|
-
createRLSPolicy
|
|
47
|
-
} from './tenancy';
|
package/src/migrations.ts
DELETED
|
@@ -1,100 +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 { basename, dirname } from 'node:path';
|
|
16
|
-
|
|
17
|
-
/** A migration recorded as applied in `elysium_migrations`. */
|
|
18
|
-
export type AppliedMigration = {
|
|
19
|
-
name: string;
|
|
20
|
-
appliedAt: Date;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/** The migrations to run, and the applied records to rename to their new names. */
|
|
24
|
-
export type MigrationPlan = {
|
|
25
|
-
pending: string[];
|
|
26
|
-
renames: { from: string; to: string }[];
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const FOLDER_MIGRATION_FILE = 'migration.sql';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Gets the name under which a migration file is tracked. Drizzle Kit 1.0 stores each migration
|
|
33
|
-
* as `<folder>/migration.sql`, in which case the folder name identifies the migration.
|
|
34
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
35
|
-
* @param path The path of the migration file.
|
|
36
|
-
*/
|
|
37
|
-
export const getMigrationName = (path: string): string =>
|
|
38
|
-
basename(path) === FOLDER_MIGRATION_FILE ? basename(dirname(path)) : basename(path);
|
|
39
|
-
|
|
40
|
-
/** Drops the `.sql` extension and the leading index or timestamp: `0001_users.sql` → `users`. */
|
|
41
|
-
const descriptiveName = (name: string): string =>
|
|
42
|
-
name
|
|
43
|
-
.replace(/\.sql$/, '')
|
|
44
|
-
.split('_')
|
|
45
|
-
.slice(1)
|
|
46
|
-
.join('_');
|
|
47
|
-
|
|
48
|
-
/** Reads the UTC `YYYYMMDDHHmmss` timestamp that starts a Drizzle Kit 1.0 folder name. */
|
|
49
|
-
const folderTime = (name: string): number | undefined => {
|
|
50
|
-
const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(name);
|
|
51
|
-
return match
|
|
52
|
-
? Date.UTC(+match[1]!, +match[2]! - 1, +match[3]!, +match[4]!, +match[5]!, +match[6]!)
|
|
53
|
-
: undefined;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Decides which migration files to run.
|
|
58
|
-
*
|
|
59
|
-
* `drizzle-kit up` moves each flat `<prefix>_<name>.sql` file into a
|
|
60
|
-
* `<timestamp>_<name>/migration.sql` folder, where the timestamp is when the migration was
|
|
61
|
-
* generated. An applied record whose flat file is gone is matched, in order, to a folder with
|
|
62
|
-
* the same `<name>` generated before the record was applied; that folder is not run again and
|
|
63
|
-
* the record is renamed. Folders generated later are new migrations and run.
|
|
64
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
65
|
-
* @param files The paths of the migration files.
|
|
66
|
-
* @param applied The applied migrations.
|
|
67
|
-
*/
|
|
68
|
-
export const planMigrations = (files: string[], applied: AppliedMigration[]): MigrationPlan => {
|
|
69
|
-
const appliedNames = new Set(applied.map(({ name }) => name));
|
|
70
|
-
const fileNames = new Set(files.map(getMigrationName));
|
|
71
|
-
|
|
72
|
-
const moved = new Map<string, AppliedMigration[]>();
|
|
73
|
-
for (const record of [...applied].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
74
|
-
if (!record.name.endsWith('.sql') || fileNames.has(record.name)) continue;
|
|
75
|
-
const key = descriptiveName(record.name);
|
|
76
|
-
moved.set(key, [...(moved.get(key) ?? []), record]);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const plan: MigrationPlan = { pending: [], renames: [] };
|
|
80
|
-
for (const file of [...files].sort()) {
|
|
81
|
-
const name = getMigrationName(file);
|
|
82
|
-
if (appliedNames.has(name)) continue;
|
|
83
|
-
|
|
84
|
-
const from = basename(file) === FOLDER_MIGRATION_FILE ? takeMoved(moved, name) : undefined;
|
|
85
|
-
if (from) plan.renames.push({ from, to: name });
|
|
86
|
-
else plan.pending.push(file);
|
|
87
|
-
}
|
|
88
|
-
return plan;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
/** Removes and returns the first moved record that the folder `name` can be the new home of. */
|
|
92
|
-
const takeMoved = (moved: Map<string, AppliedMigration[]>, name: string): string | undefined => {
|
|
93
|
-
const records = moved.get(descriptiveName(name));
|
|
94
|
-
const generatedAt = folderTime(name);
|
|
95
|
-
const index =
|
|
96
|
-
records?.findIndex(
|
|
97
|
-
({ appliedAt }) => generatedAt === undefined || generatedAt <= appliedAt.getTime()
|
|
98
|
-
) ?? -1;
|
|
99
|
-
return index < 0 ? undefined : records!.splice(index, 1)[0]!.name;
|
|
100
|
-
};
|
package/src/model.ts
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026-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 type { ColumnMetadata, ModelAdapter } from '@elysiumjs/mnemosyne';
|
|
16
|
-
import type { PgColumn, PgTableWithColumns, TableConfig } from 'drizzle-orm/pg-core';
|
|
17
|
-
|
|
18
|
-
import { AbstractModel, createSchemaFromModel } from '@elysiumjs/mnemosyne';
|
|
19
|
-
import { getTableConfig } from 'drizzle-orm/pg-core';
|
|
20
|
-
|
|
21
|
-
import { getTableBuilders } from './table';
|
|
22
|
-
import { wrapTenantSchema } from './tenancy';
|
|
23
|
-
|
|
24
|
-
// ============================================================================
|
|
25
|
-
// Column Type Mapping
|
|
26
|
-
// ============================================================================
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Maps a Drizzle column to a normalized data type.
|
|
30
|
-
* @param col The Drizzle column to inspect.
|
|
31
|
-
* @returns A normalized data type string.
|
|
32
|
-
*/
|
|
33
|
-
const mapDrizzleDataType = (col: PgColumn): ColumnMetadata['dataType'] => {
|
|
34
|
-
if (col.columnType === 'PgUUID') return 'uuid';
|
|
35
|
-
if ((col as { dimensions?: number }).dimensions) return 'array';
|
|
36
|
-
|
|
37
|
-
// Drizzle 1.0 column data types are `<kind>` or `<kind> <constraint>` (e.g. `object json`).
|
|
38
|
-
const [kind, constraint] = col.dataType.split(' ');
|
|
39
|
-
switch (kind) {
|
|
40
|
-
case 'number':
|
|
41
|
-
return 'number';
|
|
42
|
-
case 'boolean':
|
|
43
|
-
return 'boolean';
|
|
44
|
-
case 'array':
|
|
45
|
-
return 'array';
|
|
46
|
-
case 'bigint':
|
|
47
|
-
return 'bigint';
|
|
48
|
-
case 'object':
|
|
49
|
-
switch (constraint) {
|
|
50
|
-
case 'json':
|
|
51
|
-
return 'json';
|
|
52
|
-
case 'date':
|
|
53
|
-
return 'date';
|
|
54
|
-
case 'buffer':
|
|
55
|
-
return 'buffer';
|
|
56
|
-
default:
|
|
57
|
-
return 'string';
|
|
58
|
-
}
|
|
59
|
-
default:
|
|
60
|
-
return 'string';
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// ============================================================================
|
|
65
|
-
// Drizzle Model Adapter
|
|
66
|
-
// ============================================================================
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Drizzle-specific model adapter.
|
|
70
|
-
*
|
|
71
|
-
* Bridges Drizzle's `PgTableWithColumns` to the framework's
|
|
72
|
-
* normalized model metadata.
|
|
73
|
-
*
|
|
74
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
75
|
-
*/
|
|
76
|
-
export const drizzleAdapter: ModelAdapter<PgTableWithColumns<any>> = {
|
|
77
|
-
getTableName(table) {
|
|
78
|
-
return getTableConfig(table).name;
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
getColumns(table): ColumnMetadata[] {
|
|
82
|
-
const config = getTableConfig(table);
|
|
83
|
-
return config.columns.map((col: PgColumn) => ({
|
|
84
|
-
name: col.name,
|
|
85
|
-
dataType: mapDrizzleDataType(col),
|
|
86
|
-
nullable: !col.notNull,
|
|
87
|
-
hasDefault: col.hasDefault,
|
|
88
|
-
isPrimaryKey:
|
|
89
|
-
col.primary || config.primaryKeys.some((pk) => pk.columns.some((c) => c.name === col.name))
|
|
90
|
-
}));
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
createTenantTable(table, tenant) {
|
|
94
|
-
const builders = getTableBuilders(table);
|
|
95
|
-
if (!builders) {
|
|
96
|
-
throw new Error(
|
|
97
|
-
`No column builders registered for table. ` +
|
|
98
|
-
`Use pgTable() from '@elysiumjs/mnemosyne-drizzle' for tenant support.`
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
const config = getTableConfig(table);
|
|
102
|
-
return wrapTenantSchema(tenant, config.name, builders) as PgTableWithColumns<any>;
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// ============================================================================
|
|
107
|
-
// Schema Helper
|
|
108
|
-
// ============================================================================
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Creates Elysia validation schemas from a Drizzle table.
|
|
112
|
-
*
|
|
113
|
-
* This is a convenience wrapper around `createSchemaFromModel` that
|
|
114
|
-
* automatically extracts column metadata via the `drizzleAdapter`.
|
|
115
|
-
*
|
|
116
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
117
|
-
* @param table The Drizzle table to generate schemas for.
|
|
118
|
-
* @param opts Schema generation options.
|
|
119
|
-
* @returns An Elysia validation schema.
|
|
120
|
-
*/
|
|
121
|
-
export const createSchemaFromDrizzle = (
|
|
122
|
-
table: PgTableWithColumns<any>,
|
|
123
|
-
opts?: { mode?: 'create' | 'update' | 'select' }
|
|
124
|
-
) => {
|
|
125
|
-
return createSchemaFromModel(drizzleAdapter.getColumns(table), opts);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// ============================================================================
|
|
129
|
-
// DrizzleModel Options
|
|
130
|
-
// ============================================================================
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Options for creating a DrizzleModel.
|
|
134
|
-
*/
|
|
135
|
-
export interface DrizzleModelOptions {
|
|
136
|
-
/**
|
|
137
|
-
* Enable tenant schema support for this model.
|
|
138
|
-
* @default false
|
|
139
|
-
*/
|
|
140
|
-
supportTenancy?: boolean;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ============================================================================
|
|
144
|
-
// DrizzleModel Function
|
|
145
|
-
// ============================================================================
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Creates a model class backed by a Drizzle pgTable.
|
|
149
|
-
*
|
|
150
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
151
|
-
* @param table The Drizzle table (created with enhanced pgTable).
|
|
152
|
-
* @param options Model configuration options.
|
|
153
|
-
* @returns A model class with Drizzle-backed static metadata.
|
|
154
|
-
*/
|
|
155
|
-
export const DrizzleModel = <TTable extends PgTableWithColumns<TableConfig>>(
|
|
156
|
-
table: TTable,
|
|
157
|
-
options?: DrizzleModelOptions
|
|
158
|
-
) => {
|
|
159
|
-
const config = getTableConfig(table);
|
|
160
|
-
const builders = getTableBuilders(table);
|
|
161
|
-
|
|
162
|
-
type TSelect = TTable['$inferSelect'];
|
|
163
|
-
type TInsert = TTable['$inferInsert'];
|
|
164
|
-
|
|
165
|
-
const Base = AbstractModel(config.name, builders ?? {}, drizzleAdapter, table);
|
|
166
|
-
|
|
167
|
-
// Override supportTenancy if specified
|
|
168
|
-
if (options?.supportTenancy !== undefined) {
|
|
169
|
-
(Base as any).supportTenancy = options.supportTenancy;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return Base as typeof Base & {
|
|
173
|
-
readonly $inferSelect: TSelect;
|
|
174
|
-
readonly $inferInsert: TInsert;
|
|
175
|
-
readonly $inferUpdate: Partial<TInsert>;
|
|
176
|
-
readonly table: TTable;
|
|
177
|
-
};
|
|
178
|
-
};
|