@elysiumjs/mnemosyne-drizzle 0.1.4 → 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 (53) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +51 -0
  3. package/dist/cache.d.ts +60 -0
  4. package/dist/cache.js +1 -0
  5. package/dist/commands/migration_generate.command.d.ts +11 -0
  6. package/dist/commands/migration_generate.command.js +1 -0
  7. package/dist/commands/migration_run.command.d.ts +26 -0
  8. package/dist/commands/migration_run.command.js +10 -0
  9. package/dist/commands/project.d.ts +11 -0
  10. package/dist/commands/project.js +1 -0
  11. package/dist/database.d.ts +63 -23
  12. package/dist/database.js +1 -0
  13. package/dist/drizzle.module.d.ts +40 -0
  14. package/dist/drizzle.module.js +1 -0
  15. package/dist/index.d.ts +4 -3
  16. package/dist/index.js +1 -2
  17. package/dist/migrations.d.ts +45 -0
  18. package/dist/migrations.js +2 -0
  19. package/dist/model.d.ts +8 -8
  20. package/dist/model.js +1 -0
  21. package/dist/repository.d.ts +20 -5
  22. package/dist/repository.js +1 -0
  23. package/dist/table.d.ts +47 -123
  24. package/dist/table.js +1 -0
  25. package/dist/tenancy.d.ts +28 -11
  26. package/dist/tenancy.js +1 -0
  27. package/package.json +25 -13
  28. package/.turbo/turbo-build.log +0 -9
  29. package/.turbo/turbo-typecheck.log +0 -1
  30. package/coverage/.lcov.info.01ffde0d9f956dce.tmp +0 -14
  31. package/coverage/.lcov.info.2f80a4def3a82099.tmp +0 -343
  32. package/coverage/.lcov.info.560a2daaba0934cd.tmp +0 -366
  33. package/coverage/.lcov.info.7162c9b788dc0bc6.tmp +0 -366
  34. package/coverage/.lcov.info.b57c0b452c3901e5.tmp +0 -17
  35. package/coverage/.lcov.info.c47de8626f331696.tmp +0 -17
  36. package/coverage/.lcov.info.c710a42b25b08349.tmp +0 -343
  37. package/coverage/.lcov.info.dbc8f065467fc8a2.tmp +0 -287
  38. package/coverage/.lcov.info.de180a43301f82c0.tmp +0 -35
  39. package/coverage/.lcov.info.df18163ab929ab1b.tmp +0 -357
  40. package/coverage/.lcov.info.febf711c192f5c0c.tmp +0 -17
  41. package/coverage/lcov.info +0 -357
  42. package/src/database.ts +0 -175
  43. package/src/index.ts +0 -77
  44. package/src/model.ts +0 -171
  45. package/src/repository.ts +0 -113
  46. package/src/table.ts +0 -70
  47. package/src/tenancy.ts +0 -249
  48. package/tests/database.test.ts +0 -198
  49. package/tests/model.test.ts +0 -362
  50. package/tests/setup.ts +0 -10
  51. package/tests/table.test.ts +0 -72
  52. package/tests/tenancy.test.ts +0 -212
  53. package/tsconfig.json +0 -12
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 { PgTable, PgTableWithColumns } 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?: "create" | "update" | "select";
24
+ mode?: 'create' | 'update' | 'select';
25
25
  }) => import("@sinclair/typebox").TObject<{}>;
26
26
  /**
27
27
  * Options for creating a DrizzleModel.
@@ -41,21 +41,21 @@ 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<any>>(table: TTable, options?: DrizzleModelOptions) => {
44
+ export declare const DrizzleModel: <TTable extends PgTable>(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: PgTableWithColumns<any>;
49
+ readonly table: any;
50
50
  readonly tableName: string;
51
- readonly columns: Record<string, import("drizzle-orm/pg-core").PgColumnBuilderBase<import("drizzle-orm").ColumnBuilderBaseConfig<import("drizzle-orm").ColumnDataType, string>, object>>;
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<{}>;
55
55
  readonly supportTenancy: boolean;
56
56
  } & {
57
- readonly $inferSelect: TTable["$inferSelect"];
58
- readonly $inferInsert: TTable["$inferInsert"];
59
- readonly $inferUpdate: Partial<TTable["$inferInsert"]>;
57
+ readonly $inferSelect: { [Key in keyof TTable["_"]["columns"] & string]: TTable["_"]["columns"][Key]["_"]["notNull"] extends true ? TTable["_"]["columns"][Key]["_"]["data"] : TTable["_"]["columns"][Key]["_"]["data"] | null; } extends infer T ? { [K in keyof T]: T[K]; } : never;
58
+ readonly $inferInsert: { [Key in keyof TTable["_"]["columns"] & string as import("drizzle-orm").RequiredKeyOnly<Key, TTable["_"]["columns"][Key]>]: TTable["_"]["columns"][Key]["_"]["notNull"] extends true ? TTable["_"]["columns"][Key]["_"]["data"] : TTable["_"]["columns"][Key]["_"]["data"] | null; } & { [Key in keyof TTable["_"]["columns"] & string as import("drizzle-orm").OptionalKeyOnly<Key, TTable["_"]["columns"][Key], false>]?: (TTable["_"]["columns"][Key]["_"]["notNull"] extends true ? TTable["_"]["columns"][Key]["_"]["data"] : TTable["_"]["columns"][Key]["_"]["data"] | null) | undefined; } extends infer T_1 ? { [K in keyof T_1]: T_1[K]; } : never;
59
+ readonly $inferUpdate: Partial<{ [Key in keyof TTable["_"]["columns"] & string as import("drizzle-orm").RequiredKeyOnly<Key, TTable["_"]["columns"][Key]>]: TTable["_"]["columns"][Key]["_"]["notNull"] extends true ? TTable["_"]["columns"][Key]["_"]["data"] : TTable["_"]["columns"][Key]["_"]["data"] | null; } & { [Key in keyof TTable["_"]["columns"] & string as import("drizzle-orm").OptionalKeyOnly<Key, TTable["_"]["columns"][Key], false>]?: (TTable["_"]["columns"][Key]["_"]["notNull"] extends true ? TTable["_"]["columns"][Key]["_"]["data"] : TTable["_"]["columns"][Key]["_"]["data"] | null) | undefined; } extends infer T_1 ? { [K in keyof T_1]: T_1[K]; } : never>;
60
60
  readonly table: TTable;
61
61
  };
package/dist/model.js ADDED
@@ -0,0 +1 @@
1
+ import{AbstractModel,createSchemaFromModel}from"@elysiumjs/mnemosyne";import{getTableConfig}from"drizzle-orm/pg-core";import{getTableBuilders}from"./table";import{wrapTenantSchema}from"./tenancy";const mapDrizzleDataType=(col)=>{if(col.columnType==="PgUUID")return"uuid";if(col.dimensions)return"array";const[kind,constraint]=col.dataType.split(" ");switch(kind){case"number":return"number";case"boolean":return"boolean";case"array":return"array";case"bigint":return"bigint";case"object":switch(constraint){case"json":return"json";case"date":return"date";case"buffer":return"buffer";default:return"string"}default:return"string"}};export const drizzleAdapter={getTableName(table){return getTableConfig(table).name},getColumns(table){const config=getTableConfig(table);return config.columns.map((col)=>({name:col.name,dataType:mapDrizzleDataType(col),nullable:!col.notNull,hasDefault:col.hasDefault,isPrimaryKey:col.primary||config.primaryKeys.some((pk)=>pk.columns.some((c)=>c.name===col.name))}))},createTenantTable(table,tenant){const builders=getTableBuilders(table);if(!builders){throw new Error(`No column builders registered for table. `+`Use pgTable() from '@elysiumjs/mnemosyne-drizzle' for tenant support.`)}const config=getTableConfig(table);return wrapTenantSchema(tenant,config.name,builders)}};export const createSchemaFromDrizzle=(table,opts)=>{return createSchemaFromModel(drizzleAdapter.getColumns(table),opts)};export const DrizzleModel=(table,options)=>{const config=getTableConfig(table);const builders=getTableBuilders(table);const Base=AbstractModel(config.name,builders??{},drizzleAdapter,table);if(options?.supportTenancy!==undefined){Base.supportTenancy=options.supportTenancy}return Base};
@@ -1,19 +1,34 @@
1
1
  import type { IdType, ModelClass } from '@elysiumjs/mnemosyne';
2
- import type { PgTableWithColumns } from 'drizzle-orm/pg-core';
2
+ import type { PgColumn, PgTable } from 'drizzle-orm/pg-core';
3
3
  import type { DrizzleConnection } from './database';
4
4
  /**
5
5
  * Drizzle-specific repository mixin with concrete CRUD operations.
6
6
  *
7
7
  * Extends the abstract repository with Drizzle query builder calls
8
- * for all standard CRUD methods.
8
+ * for all standard CRUD methods. The model table must have an `id` column, used as the
9
+ * primary key by `find`, `update`, `delete` and `exists`.
9
10
  *
10
11
  * @author Axel Nana <axel.nana@workbud.com>
11
12
  * @template TModel The model class type.
12
13
  * @param model The model class to create the repository for.
13
14
  * @returns A concrete repository class with CRUD methods.
14
15
  */
15
- export declare const DrizzleRepository: <TModel extends ModelClass<any, any, any, PgTableWithColumns<any>>>(model: TModel) => {
16
+ export declare const DrizzleRepository: <TModel extends ModelClass<any, any, any, PgTable & {
17
+ id: PgColumn;
18
+ }>>(model: TModel) => {
16
19
  new (): {
20
+ /**
21
+ * Runs a database operation on the repository connection.
22
+ *
23
+ * In RLS tenancy mode, an operation that runs outside a transaction is wrapped in one
24
+ * scoped to the current tenant (through the tenancy strategy's `withIsolation`), so RLS
25
+ * policies apply to it. Inside an open transaction, the operation joins it when the
26
+ * transaction is bound to the current tenant, and throws `TenantMismatchError` otherwise
27
+ * (see `DrizzleRLSTenancy.withIsolation`).
28
+ * @param operation The operation to run with the connection to use.
29
+ * @returns The return value of the operation.
30
+ */
31
+ withConnection<T>(operation: (db: DrizzleConnection) => Promise<T>): Promise<T>;
17
32
  all(): Promise<TModel["$inferSelect"][]>;
18
33
  paginate(count: number, page?: number): Promise<{
19
34
  page: number;
@@ -23,9 +38,9 @@ export declare const DrizzleRepository: <TModel extends ModelClass<any, any, any
23
38
  find(id: IdType): Promise<TModel["$inferSelect"] | null>;
24
39
  findBy<TKey extends keyof TModel["$inferSelect"]>(column: TKey, value: TModel["$inferSelect"][TKey]): Promise<TModel["$inferSelect"] | null>;
25
40
  insert(data: TModel["$inferInsert"]): Promise<TModel["$inferSelect"]>;
26
- update(id: IdType, data: TModel["$inferUpdate"]): Promise<TModel["$inferSelect"]>;
41
+ update(id: IdType, data: TModel["$inferUpdate"]): Promise<TModel["$inferSelect"] | null>;
27
42
  updateAll(data: TModel["$inferUpdate"]): Promise<TModel["$inferSelect"][]>;
28
- delete(id: IdType): Promise<TModel["$inferSelect"]>;
43
+ delete(id: IdType): Promise<TModel["$inferSelect"] | null>;
29
44
  deleteAll(): Promise<TModel["$inferSelect"][]>;
30
45
  exists(id: IdType): Promise<boolean>;
31
46
  readonly db: DrizzleConnection;
@@ -0,0 +1 @@
1
+ import{AbstractRepository,getCurrentTenant,getTenancyConfig,getTenancyStrategy}from"@elysiumjs/mnemosyne";import{eq}from"drizzle-orm";import{Database}from"./database";import{DrizzleRLSTenancy}from"./tenancy";export const DrizzleRepository=(model)=>{const table=()=>model.table;class R extends AbstractRepository(model,Database){async withConnection(operation){const name=this.constructor.connection;if(getTenancyConfig().mode!=="rls")return operation(this.db);const strategy=getTenancyStrategy()??new DrizzleRLSTenancy;return strategy.withIsolation(Database.getBaseConnection(name),getCurrentTenant()??"public",()=>operation(this.db))}async all(){return this.withConnection((db)=>db.select().from(table()))}async paginate(count,page=1){const offset=Math.max(page-1,0)*count;return this.withConnection(async(db)=>({page,total:await db.$count(table()),data:await db.select().from(table()).offset(offset).limit(count)}))}async find(id){const[row]=await this.withConnection((db)=>db.select().from(table()).where(eq(table().id,id)));return row??null}async findBy(column,value){const[row]=await this.withConnection((db)=>db.select().from(table()).where(eq(table()[column],value)));return row??null}async insert(data){const[row]=await this.withConnection((db)=>db.insert(table()).values(data).returning());return row}async update(id,data){const[row]=await this.withConnection((db)=>db.update(table()).set(data).where(eq(table().id,id)).returning());return row??null}async updateAll(data){return this.withConnection((db)=>db.update(table()).set(data).returning())}async delete(id){const[row]=await this.withConnection((db)=>db.delete(table()).where(eq(table().id,id)).returning());return row??null}async deleteAll(){return this.withConnection((db)=>db.delete(table()).returning())}async exists(id){const count=await this.withConnection((db)=>db.$count(table(),eq(table().id,id)));return count>0}}return R};
package/dist/table.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import type { PgColumnBuilderBase, PgTableWithColumns } from 'drizzle-orm/pg-core';
1
+ import type { AnyPgColumnBuilder, PgTableWithColumns } from 'drizzle-orm/pg-core';
2
2
  /**
3
3
  * Retrieves the original column builders for a table.
4
4
  * @param table The table created with enhanced pgTable.
5
5
  * @returns The original column builder definitions, or `undefined`.
6
6
  */
7
- export declare const getTableBuilders: (table: PgTableWithColumns<any>) => Record<string, PgColumnBuilderBase> | undefined;
7
+ export declare const getTableBuilders: (table: PgTableWithColumns<any>) => Record<string, AnyPgColumnBuilder> | undefined;
8
8
  /**
9
9
  * Creates a PostgreSQL table with automatic column builder registration.
10
10
  *
@@ -17,129 +17,53 @@ export declare const getTableBuilders: (table: PgTableWithColumns<any>) => Recor
17
17
  * @param extraConfig Optional extra Drizzle table configuration.
18
18
  * @returns A Drizzle PostgreSQL table with registered column builders.
19
19
  */
20
- export declare const pgTable: <TTableName extends string, TColumns extends Record<string, PgColumnBuilderBase>>(tableName: TTableName, columns: TColumns, extraConfig?: (table: any) => any) => PgTableWithColumns<{
20
+ export declare const pgTable: <TTableName extends string, TColumns extends Record<string, AnyPgColumnBuilder>>(tableName: TTableName, columns: TColumns, extraConfig?: (table: any) => any) => PgTableWithColumns<{
21
21
  name: TTableName;
22
22
  schema: undefined;
23
- columns: { [Key in keyof TColumns]: import("drizzle-orm/pg-core").PgColumn<{
24
- name: (Omit<TColumns[Key]["_"], "name"> & {
25
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
26
- })["name"];
23
+ columns: { [Key in keyof TColumns]: import("drizzle-orm/pg-core").PgBuildColumn<TTableName, TColumns[Key], {
24
+ name: string;
27
25
  tableName: TTableName;
28
- dataType: (Omit<TColumns[Key]["_"], "name"> & {
29
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
30
- })["dataType"];
31
- columnType: (Omit<TColumns[Key]["_"], "name"> & {
32
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
33
- })["columnType"];
34
- data: Omit<TColumns[Key]["_"], "name"> & {
35
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
36
- } extends infer T ? T extends Omit<TColumns[Key]["_"], "name"> & {
37
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
38
- } ? T extends {
26
+ dataType: TColumns[Key]["_"]["dataType"];
27
+ data: TColumns[Key]["_"]["dimensions"] extends 1 | 2 | 3 | 4 | 5 ? TColumns[Key]["_"]["dimensions"] extends infer T ? T extends TColumns[Key]["_"]["dimensions"] ? T extends 1 ? (TColumns[Key]["_"] extends infer T_1 ? T_1 extends TColumns[Key]["_"] ? T_1 extends {
39
28
  $type: infer U;
40
- } ? U : T["data"] : never : never;
41
- driverParam: (Omit<TColumns[Key]["_"], "name"> & {
42
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
43
- })["driverParam"];
44
- notNull: Omit<TColumns[Key]["_"], "name"> & {
45
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
46
- } extends infer T_1 ? T_1 extends Omit<TColumns[Key]["_"], "name"> & {
47
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
48
- } ? T_1 extends {
49
- notNull: true;
50
- } ? true : false : never : never;
51
- hasDefault: Omit<TColumns[Key]["_"], "name"> & {
52
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
53
- } extends infer T_2 ? T_2 extends Omit<TColumns[Key]["_"], "name"> & {
54
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
55
- } ? T_2 extends {
56
- hasDefault: true;
57
- } ? true : false : never : never;
58
- isPrimaryKey: Omit<TColumns[Key]["_"], "name"> & {
59
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
60
- } extends infer T_3 ? T_3 extends Omit<TColumns[Key]["_"], "name"> & {
61
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
62
- } ? T_3 extends {
63
- isPrimaryKey: true;
64
- } ? true : false : never : never;
65
- isAutoincrement: Omit<TColumns[Key]["_"], "name"> & {
66
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
67
- } extends infer T_4 ? T_4 extends Omit<TColumns[Key]["_"], "name"> & {
68
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
69
- } ? T_4 extends {
70
- isAutoincrement: true;
71
- } ? true : false : never : never;
72
- hasRuntimeDefault: Omit<TColumns[Key]["_"], "name"> & {
73
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
74
- } extends infer T_5 ? T_5 extends Omit<TColumns[Key]["_"], "name"> & {
75
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
76
- } ? T_5 extends {
77
- hasRuntimeDefault: true;
78
- } ? true : false : never : never;
79
- enumValues: (Omit<TColumns[Key]["_"], "name"> & {
80
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
81
- })["enumValues"];
82
- baseColumn: Omit<TColumns[Key]["_"], "name"> & {
83
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
84
- } extends infer T_6 ? T_6 extends Omit<TColumns[Key]["_"], "name"> & {
85
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
86
- } ? T_6 extends {
87
- baseBuilder: infer U_1 extends import("drizzle-orm").ColumnBuilderBase;
88
- } ? import("drizzle-orm").Column<{
89
- name: U_1["_"]["name"];
90
- tableName: TTableName;
91
- dataType: U_1["_"]["dataType"];
92
- columnType: U_1["_"]["columnType"];
93
- data: U_1["_"] extends infer T_7 ? T_7 extends U_1["_"] ? T_7 extends {
94
- $type: infer U;
95
- } ? U : T_7["data"] : never : never;
96
- driverParam: U_1["_"]["driverParam"];
97
- notNull: U_1["_"] extends infer T_8 ? T_8 extends U_1["_"] ? T_8 extends {
98
- notNull: true;
99
- } ? true : false : never : never;
100
- hasDefault: U_1["_"] extends infer T_9 ? T_9 extends U_1["_"] ? T_9 extends {
101
- hasDefault: true;
102
- } ? true : false : never : never;
103
- isPrimaryKey: U_1["_"] extends infer T_10 ? T_10 extends U_1["_"] ? T_10 extends {
104
- isPrimaryKey: true;
105
- } ? true : false : never : never;
106
- isAutoincrement: U_1["_"] extends infer T_11 ? T_11 extends U_1["_"] ? T_11 extends {
107
- isAutoincrement: true;
108
- } ? true : false : never : never;
109
- hasRuntimeDefault: U_1["_"] extends infer T_12 ? T_12 extends U_1["_"] ? T_12 extends {
110
- hasRuntimeDefault: true;
111
- } ? true : false : never : never;
112
- enumValues: U_1["_"]["enumValues"];
113
- baseColumn: U_1["_"] extends infer T_13 ? T_13 extends U_1["_"] ? T_13 extends {
114
- baseBuilder: infer U_1 extends import("drizzle-orm").ColumnBuilderBase;
115
- } ? import("drizzle-orm").Column</*elided*/ any, {}, Omit<U_1["_"], "name" | "data" | "driverParam" | "tableName" | "notNull" | "hasDefault" | "isPrimaryKey" | "isAutoincrement" | "hasRuntimeDefault" | "dataType" | "columnType" | "enumValues" | "identity" | "dialect" | "baseColumn" | "generated" | "brand"> extends infer T_14 ? { [K in keyof T_14]: T_14[K]; } : never> : never : never : never;
116
- identity: U_1["_"] extends infer T_15 ? T_15 extends U_1["_"] ? T_15 extends {
117
- identity: "always";
118
- } ? "always" : T_15 extends {
119
- identity: "byDefault";
120
- } ? "byDefault" : undefined : never : never;
121
- generated: U_1["_"] extends infer T_16 ? T_16 extends U_1["_"] ? T_16 extends {
122
- generated: infer G;
123
- } ? unknown extends G ? undefined : G extends undefined ? undefined : G : undefined : never : never;
124
- }, {}, Omit<U_1["_"], "name" | "data" | "driverParam" | "tableName" | "notNull" | "hasDefault" | "isPrimaryKey" | "isAutoincrement" | "hasRuntimeDefault" | "dataType" | "columnType" | "enumValues" | "identity" | "dialect" | "baseColumn" | "generated" | "brand"> extends infer T_17 ? { [K in keyof T_17]: T_17[K]; } : never> : never : never : never;
125
- identity: Omit<TColumns[Key]["_"], "name"> & {
126
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
127
- } extends infer T_18 ? T_18 extends Omit<TColumns[Key]["_"], "name"> & {
128
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
129
- } ? T_18 extends {
130
- identity: "always";
131
- } ? "always" : T_18 extends {
132
- identity: "byDefault";
133
- } ? "byDefault" : undefined : never : never;
134
- generated: Omit<TColumns[Key]["_"], "name"> & {
135
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
136
- } extends infer T_19 ? T_19 extends Omit<TColumns[Key]["_"], "name"> & {
137
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
138
- } ? T_19 extends {
139
- generated: infer G;
140
- } ? unknown extends G ? undefined : G extends undefined ? undefined : G : undefined : never : never;
141
- }, {}, Omit<Omit<TColumns[Key]["_"], "name"> & {
142
- name: TColumns[Key]["_"]["name"] extends "" ? import("drizzle-orm").Assume<Key, string> : TColumns[Key]["_"]["name"];
143
- }, "name" | "data" | "driverParam" | "tableName" | "notNull" | "hasDefault" | "isPrimaryKey" | "isAutoincrement" | "hasRuntimeDefault" | "dataType" | "columnType" | "enumValues" | "identity" | "dialect" | "baseColumn" | "generated" | "brand"> extends infer T_20 ? { [K_1 in keyof T_20]: T_20[K_1]; } : never>; };
144
- dialect: "pg";
29
+ } ? U : T_1 extends {
30
+ data: infer D;
31
+ } ? D : unknown : never : never)[] : T extends 2 ? (TColumns[Key]["_"] extends infer T_1 ? T_1 extends TColumns[Key]["_"] ? T_1 extends {
32
+ $type: infer U;
33
+ } ? U : T_1 extends {
34
+ data: infer D;
35
+ } ? D : unknown : never : never)[][] : T extends 3 ? (TColumns[Key]["_"] extends infer T_1 ? T_1 extends TColumns[Key]["_"] ? T_1 extends {
36
+ $type: infer U;
37
+ } ? U : T_1 extends {
38
+ data: infer D;
39
+ } ? D : unknown : never : never)[][][] : T extends 4 ? (TColumns[Key]["_"] extends infer T_1 ? T_1 extends TColumns[Key]["_"] ? T_1 extends {
40
+ $type: infer U;
41
+ } ? U : T_1 extends {
42
+ data: infer D;
43
+ } ? D : unknown : never : never)[][][][] : T extends 5 ? (TColumns[Key]["_"] extends infer T_1 ? T_1 extends TColumns[Key]["_"] ? T_1 extends {
44
+ $type: infer U;
45
+ } ? U : T_1 extends {
46
+ data: infer D;
47
+ } ? D : unknown : never : never)[][][][][] : TColumns[Key]["_"] extends infer T_1 ? T_1 extends TColumns[Key]["_"] ? T_1 extends {
48
+ $type: infer U;
49
+ } ? U : T_1 extends {
50
+ data: infer D;
51
+ } ? D : unknown : never : never : never : never : TColumns[Key]["_"] extends infer T_1 ? T_1 extends TColumns[Key]["_"] ? T_1 extends {
52
+ $type: infer U;
53
+ } ? U : T_1 extends {
54
+ data: infer D;
55
+ } ? D : unknown : never : never;
56
+ driverParam: TColumns[Key]["_"]["dimensions"] extends 1 | 2 | 3 | 4 | 5 ? string | (TColumns[Key]["_"]["dimensions"] extends infer T_2 ? T_2 extends TColumns[Key]["_"]["dimensions"] ? T_2 extends 1 ? TColumns[Key]["_"]["driverParam"][] : T_2 extends 2 ? TColumns[Key]["_"]["driverParam"][][] : T_2 extends 3 ? TColumns[Key]["_"]["driverParam"][][][] : T_2 extends 4 ? TColumns[Key]["_"]["driverParam"][][][][] : T_2 extends 5 ? TColumns[Key]["_"]["driverParam"][][][][][] : TColumns[Key]["_"]["driverParam"] : never : never) : TColumns[Key]["_"]["driverParam"];
57
+ notNull: TColumns[Key]["_"]["notNull"] extends true ? true : false;
58
+ hasDefault: TColumns[Key]["_"]["hasDefault"] extends true ? true : false;
59
+ isPrimaryKey: false;
60
+ isAutoincrement: false;
61
+ hasRuntimeDefault: false;
62
+ enumValues: TColumns[Key]["_"] extends infer T_3 ? T_3 extends TColumns[Key]["_"] ? T_3 extends {
63
+ enumValues: infer E extends string[];
64
+ } ? E : undefined : never : never;
65
+ identity: TColumns[Key]["_"]["identity"] extends "always" | "byDefault" ? TColumns[Key]["_"]["identity"] : undefined;
66
+ generated: TColumns[Key]["_"]["generated"] extends true ? true : undefined;
67
+ }>; };
68
+ dialect: 'pg';
145
69
  }>;
package/dist/table.js ADDED
@@ -0,0 +1 @@
1
+ import{pgTable as drizzlePgTable}from"drizzle-orm/pg-core";const tableRegistry=new WeakMap;export const getTableBuilders=(table)=>{return tableRegistry.get(table)};export const pgTable=(tableName,columns,extraConfig)=>{const table=drizzlePgTable(tableName,columns,extraConfig);tableRegistry.set(table,columns);return table};
package/dist/tenancy.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import type { TenancyStrategy } from '@elysiumjs/mnemosyne';
2
- import type { PgColumnBuilderBase, PgSchema, PgTable, PgTransaction } from 'drizzle-orm/pg-core';
2
+ import type { AnyPgColumnBuilder, PgAsyncTransaction, PgSchema, PgTable } from 'drizzle-orm/pg-core';
3
3
  import type { DrizzleConnection } from './database';
4
4
  /**
5
5
  * Returns the Drizzle `PgSchema` for the given tenant, creating it if needed.
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
- export declare const wrapTenantSchema: <TTableName extends string, TColumnsMap extends Record<string, PgColumnBuilderBase>>(tenant: string, tableName: TTableName, columns: TColumnsMap) => PgTable;
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
- export declare const withRLS: <T>(tenant: string | null, callback: (tx: PgTransaction<any, any, any>) => Promise<T>, connectionName?: string) => Promise<T>;
40
- /**
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>;
48
+ export declare const withRLS: <T>(tenant: string | null, callback: (tx: PgAsyncTransaction<any, any>) => Promise<T>, connectionName?: string) => Promise<T>;
47
49
  /**
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
  }
@@ -0,0 +1 @@
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,23 +1,28 @@
1
1
  {
2
2
  "name": "@elysiumjs/mnemosyne-drizzle",
3
- "version": "0.1.4",
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
- "build:js": "bun build ./src/index.ts --outdir ./dist --target bun --packages external --root ./src --minify",
17
- "build:ts": "tsc",
18
- "build": "bun run clean && bun run build:js && bun run build:ts",
19
- "typecheck": "tsc --noEmit",
20
- "prepublishOnly": "bun run build"
20
+ "build": "bun run clean && tsc && bun run build:minify",
21
+ "build:minify": "bun scripts/minify.ts",
22
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.types.json",
23
+ "prepublishOnly": "bun run build",
24
+ "test": "bun test",
25
+ "test:coverage": "bun test --coverage"
21
26
  },
22
27
  "keywords": [
23
28
  "elysium",
@@ -29,16 +34,23 @@
29
34
  "module": "dist/index.js",
30
35
  "type": "module",
31
36
  "types": "dist/index.d.ts",
37
+ "files": [
38
+ "dist",
39
+ "CHANGELOG.md"
40
+ ],
32
41
  "sideEffects": false,
33
42
  "peerDependencies": {
34
- "@elysiumjs/core": "^0.7.0",
35
- "@elysiumjs/mnemosyne": "^0.1.1"
43
+ "@elysiumjs/core": "^0.10.0",
44
+ "@elysiumjs/mnemosyne": "^0.3.1"
36
45
  },
37
46
  "dependencies": {
38
- "drizzle-orm": "^0.45.1"
47
+ "drizzle-orm": "^1.0.0-rc.5",
48
+ "prompts": "^2.4.2",
49
+ "radash": "^12.1.1"
39
50
  },
40
51
  "devDependencies": {
41
- "@elysiumjs/core": "0.7.0",
42
- "@elysiumjs/mnemosyne": "0.1.1"
52
+ "@types/prompts": "^2.4.9",
53
+ "@elysiumjs/core": "0.10.0",
54
+ "@elysiumjs/mnemosyne": "0.3.1"
43
55
  }
44
56
  }
@@ -1,9 +0,0 @@
1
-
2
- $ bun run clean && bun run build:js && bun run build:ts
3
- $ rimraf dist
4
- $ bun build ./src/index.ts --outdir ./dist --target bun --packages external --root ./src --minify
5
- Bundled 4 modules in 2ms
6
-
7
- index.js 3.27 KB (entry point)
8
-
9
- $ tsc
@@ -1 +0,0 @@
1
- $ tsc --noEmit
@@ -1,14 +0,0 @@
1
- TN:
2
- SF:tests/setup.ts
3
- FNF:1
4
- FNH:0
5
- DA:1,25
6
- DA:3,32
7
- DA:5,0
8
- DA:6,0
9
- DA:7,0
10
- DA:8,0
11
- DA:10,2
12
- LF:7
13
- LH:3
14
- end_of_record