@elysiumjs/drizzle 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ import type { DrizzleConfig } from 'drizzle-orm';
2
+ import type { DrizzleDatabaseCache } from './cache';
3
+ import type { DrizzleConnection } from './database';
4
+ import type { MigrationConnection } from './migration';
5
+ import { ElysiumError } from '@elysiumjs/olympus';
6
+ /**
7
+ * What a driver connects with: the Drizzle options of the connection and its query cache.
8
+ * @author Axel Nana <ax.lnana@outlook.com>
9
+ */
10
+ export type DrizzleDriverConfig = Omit<DrizzleConfig<Record<string, unknown>>, 'cache'> & {
11
+ cache?: DrizzleDatabaseCache;
12
+ };
13
+ /**
14
+ * Connects Drizzle to PostgreSQL through a client: Bun SQL (`/bun-sql`), postgres.js
15
+ * (`/postgres-js`), node-postgres (`/node-postgres`) or Cloudflare Hyperdrive (`/hyperdrive`).
16
+ * Implement it to plug in another client.
17
+ * @author Axel Nana <ax.lnana@outlook.com>
18
+ */
19
+ export interface DrizzleDriver {
20
+ /** The name of the driver, for errors and logs. */
21
+ readonly name: string;
22
+ /**
23
+ * Creates the Drizzle database of a connection.
24
+ * @param config The Drizzle options and the query cache.
25
+ */
26
+ connect(config: DrizzleDriverConfig): DrizzleConnection;
27
+ /**
28
+ * Closes the client of a database the driver created.
29
+ * @param connection The database.
30
+ */
31
+ close(connection: DrizzleConnection): Promise<void> | void;
32
+ /**
33
+ * Gives the connection `migration:run` works on, over the client of a database the driver
34
+ * created. `migration:run` refuses a connection whose driver has none.
35
+ * @param connection The database.
36
+ */
37
+ migrations?(connection: DrizzleConnection): MigrationConnection;
38
+ }
39
+ /**
40
+ * Raised when a connection sets no driver outside Bun, where the default Bun SQL driver is not
41
+ * available.
42
+ * @author Axel Nana <ax.lnana@outlook.com>
43
+ */
44
+ export declare class MissingDriverError extends ElysiumError {
45
+ constructor(name: string);
46
+ }
47
+ /**
48
+ * Raised when `migration:run` uses a connection whose driver cannot run migrations.
49
+ * @author Axel Nana <ax.lnana@outlook.com>
50
+ */
51
+ export declare class MissingMigrationsError extends ElysiumError {
52
+ constructor(driver: string);
53
+ }
@@ -0,0 +1,25 @@
1
+ import type { SQL } from 'bun';
2
+ import type { DrizzleDriver } from '../driver';
3
+ /**
4
+ * Options of {@link bunSql}: a connection URL or options, or a client.
5
+ * @author Axel Nana <ax.lnana@outlook.com>
6
+ */
7
+ export type BunSqlDriverOptions = {
8
+ connection: string | ({
9
+ url?: string;
10
+ } & SQL.Options);
11
+ } | {
12
+ client: SQL;
13
+ };
14
+ /**
15
+ * Connects with Bun's built-in SQL client. Runs on Bun only.
16
+ * @author Axel Nana <ax.lnana@outlook.com>
17
+ * @param options The connection URL or options, or a client.
18
+ * @example
19
+ * ```ts
20
+ * import { bunSql } from '@elysiumjs/drizzle/bun-sql';
21
+ *
22
+ * DrizzleModule.with({ connections: { default: { driver: bunSql({ connection: url }) } } });
23
+ * ```
24
+ */
25
+ export declare const bunSql: (options: BunSqlDriverOptions) => DrizzleDriver;
@@ -0,0 +1 @@
1
+ import{drizzle}from"drizzle-orm/bun-sql";import{PgDialect}from"drizzle-orm/pg-core";var dialect=new PgDialect,toQuery=(query)=>dialect.sqlToQuery(query),templateDatabase=(client)=>({execute:async(query)=>{let{sql,params}=toQuery(query);return[...await client.unsafe(sql,params)]},script:async(text)=>{let query=client.unsafe(text);await(query.simple?query.simple():query)}}),templateClientMigrations=(client)=>({...templateDatabase(client),transaction:(fn)=>client.begin((tx)=>fn(templateDatabase(tx)))}),pgDatabase=(client)=>({execute:async(query)=>{let{sql,params}=toQuery(query);return(await client.query(sql,params)).rows},script:async(text)=>{await client.query(text)}}),nodePostgresMigrations=(pool)=>({...pgDatabase(pool),transaction:async(fn)=>{let client=await pool.connect();try{await client.query("BEGIN");let result=await fn(pgDatabase(client));return await client.query("COMMIT"),result}catch(error){throw await client.query("ROLLBACK"),error}finally{client.release()}}});var bunSql=(options)=>({name:"bun-sql",connect:(config)=>drizzle({...config,...options}),close:async(connection)=>{await connection.$client.close()},migrations:(connection)=>templateClientMigrations(connection.$client)});export{bunSql};
@@ -0,0 +1,46 @@
1
+ import type { DrizzleDriver } from '../driver';
2
+ /**
3
+ * A Hyperdrive binding, as Cloudflare Workers pass it.
4
+ * @author Axel Nana <ax.lnana@outlook.com>
5
+ */
6
+ export type HyperdriveBinding = {
7
+ readonly connectionString: string;
8
+ };
9
+ /**
10
+ * Options of {@link hyperdrive}.
11
+ * @author Axel Nana <ax.lnana@outlook.com>
12
+ */
13
+ export type HyperdriveDriverOptions = {
14
+ /** The name of the Hyperdrive binding, as declared in the wrangler config. */
15
+ binding: string;
16
+ /**
17
+ * The driver that connects to the connection string of the binding, such as
18
+ * `(url) => postgresJs({ connection: { url, max: 5, fetch_types: false } })`.
19
+ */
20
+ driver: (connectionString: string) => DrizzleDriver;
21
+ };
22
+ /**
23
+ * Connects through a Cloudflare Hyperdrive binding. A Worker cannot reuse a socket opened in
24
+ * another invocation, so the database opens a client per invocation (request, queue batch or
25
+ * scheduled event) on first use. The runtime closes its sockets when the invocation ends, and
26
+ * Hyperdrive pools the connections to the database.
27
+ * @author Axel Nana <ax.lnana@outlook.com>
28
+ * @param options The binding name and the client driver.
29
+ * @example
30
+ * ```ts
31
+ * import { hyperdrive } from '@elysiumjs/drizzle/hyperdrive';
32
+ * import { postgresJs } from '@elysiumjs/drizzle/postgres-js';
33
+ *
34
+ * DrizzleModule.with({
35
+ * connections: {
36
+ * default: {
37
+ * driver: hyperdrive({
38
+ * binding: 'HYPERDRIVE',
39
+ * driver: (url) => postgresJs({ connection: { url, max: 5, fetch_types: false } })
40
+ * })
41
+ * }
42
+ * }
43
+ * });
44
+ * ```
45
+ */
46
+ export declare const hyperdrive: (options: HyperdriveDriverOptions) => DrizzleDriver;
@@ -0,0 +1 @@
1
+ import{Workers}from"@elysiumjs/workers";import{ElysiumError}from"@elysiumjs/olympus";class MissingDriverError extends ElysiumError{constructor(name){super(`Connection "${name}" sets no driver, and the default Bun SQL driver runs on Bun only. Set \`driver\` to postgresJs() or nodePostgres() on Node.js, or hyperdrive() on Cloudflare Workers.`)}}class MissingMigrationsError extends ElysiumError{constructor(driver){super(`The ${driver} driver cannot run migrations: it gives no migration connection. Use a connection on the bun-sql, postgres-js or node-postgres driver.`)}}var hyperdrive=(options)=>({name:"hyperdrive",connect:(config)=>invocationDatabase(options,config),close:()=>{},migrations:(connection)=>{let{connectionString}=Workers.binding(options.binding),migrations=options.driver(connectionString).migrations?.(connection);if(!migrations)throw new MissingMigrationsError("hyperdrive");return migrations}}),invocationDatabase=(options,config)=>{let databases=new WeakMap,current=()=>{let context=Workers.context(),database=databases.get(context);if(!database){let{connectionString}=Workers.binding(options.binding);database=options.driver(connectionString).connect(config),databases.set(context,database)}return database};return new Proxy({},{get(target,property){if(Object.hasOwn(target,property))return Reflect.get(target,property);let database=current(),value=Reflect.get(database,property,database);return typeof value==="function"?value.bind(database):value},has(target,property){return Object.hasOwn(target,property)||property in current()},getPrototypeOf(){return Object.getPrototypeOf(current())}})};export{hyperdrive};
@@ -0,0 +1,23 @@
1
+ import type { Pool, PoolConfig } from 'pg';
2
+ import type { DrizzleDriver } from '../driver';
3
+ /**
4
+ * Options of {@link nodePostgres}: a connection URL or pool options, or a pool.
5
+ * @author Axel Nana <ax.lnana@outlook.com>
6
+ */
7
+ export type NodePostgresDriverOptions = {
8
+ connection: string | PoolConfig;
9
+ } | {
10
+ client: Pool;
11
+ };
12
+ /**
13
+ * Connects with node-postgres (`pg`), on Node.js and Bun.
14
+ * @author Axel Nana <ax.lnana@outlook.com>
15
+ * @param options The connection URL or pool options, or a pool.
16
+ * @example
17
+ * ```ts
18
+ * import { nodePostgres } from '@elysiumjs/drizzle/node-postgres';
19
+ *
20
+ * DrizzleModule.with({ connections: { default: { driver: nodePostgres({ connection: url }) } } });
21
+ * ```
22
+ */
23
+ export declare const nodePostgres: (options: NodePostgresDriverOptions) => DrizzleDriver;
@@ -0,0 +1 @@
1
+ import{drizzle}from"drizzle-orm/node-postgres";import{PgDialect}from"drizzle-orm/pg-core";var dialect=new PgDialect,toQuery=(query)=>dialect.sqlToQuery(query),templateDatabase=(client)=>({execute:async(query)=>{let{sql,params}=toQuery(query);return[...await client.unsafe(sql,params)]},script:async(text)=>{let query=client.unsafe(text);await(query.simple?query.simple():query)}}),templateClientMigrations=(client)=>({...templateDatabase(client),transaction:(fn)=>client.begin((tx)=>fn(templateDatabase(tx)))}),pgDatabase=(client)=>({execute:async(query)=>{let{sql,params}=toQuery(query);return(await client.query(sql,params)).rows},script:async(text)=>{await client.query(text)}}),nodePostgresMigrations=(pool)=>({...pgDatabase(pool),transaction:async(fn)=>{let client=await pool.connect();try{await client.query("BEGIN");let result=await fn(pgDatabase(client));return await client.query("COMMIT"),result}catch(error){throw await client.query("ROLLBACK"),error}finally{client.release()}}});var nodePostgres=(options)=>({name:"node-postgres",connect:(config)=>drizzle({...config,...options}),close:async(connection)=>{await connection.$client.end()},migrations:(connection)=>nodePostgresMigrations(connection.$client)});export{nodePostgres};
@@ -0,0 +1,25 @@
1
+ import type { Options, PostgresType, Sql } from 'postgres';
2
+ import type { DrizzleDriver } from '../driver';
3
+ /**
4
+ * Options of {@link postgresJs}: a connection URL or options, or a client.
5
+ * @author Axel Nana <ax.lnana@outlook.com>
6
+ */
7
+ export type PostgresJsDriverOptions = {
8
+ connection: string | ({
9
+ url?: string;
10
+ } & Options<Record<string, PostgresType>>);
11
+ } | {
12
+ client: Sql;
13
+ };
14
+ /**
15
+ * Connects with postgres.js (`postgres`), on Node.js, Bun and Cloudflare Workers.
16
+ * @author Axel Nana <ax.lnana@outlook.com>
17
+ * @param options The connection URL or options, or a client.
18
+ * @example
19
+ * ```ts
20
+ * import { postgresJs } from '@elysiumjs/drizzle/postgres-js';
21
+ *
22
+ * DrizzleModule.with({ connections: { default: { driver: postgresJs({ connection: url }) } } });
23
+ * ```
24
+ */
25
+ export declare const postgresJs: (options: PostgresJsDriverOptions) => DrizzleDriver;
@@ -0,0 +1 @@
1
+ import{drizzle}from"drizzle-orm/postgres-js";import{PgDialect}from"drizzle-orm/pg-core";var dialect=new PgDialect,toQuery=(query)=>dialect.sqlToQuery(query),templateDatabase=(client)=>({execute:async(query)=>{let{sql,params}=toQuery(query);return[...await client.unsafe(sql,params)]},script:async(text)=>{let query=client.unsafe(text);await(query.simple?query.simple():query)}}),templateClientMigrations=(client)=>({...templateDatabase(client),transaction:(fn)=>client.begin((tx)=>fn(templateDatabase(tx)))}),pgDatabase=(client)=>({execute:async(query)=>{let{sql,params}=toQuery(query);return(await client.query(sql,params)).rows},script:async(text)=>{await client.query(text)}}),nodePostgresMigrations=(pool)=>({...pgDatabase(pool),transaction:async(fn)=>{let client=await pool.connect();try{await client.query("BEGIN");let result=await fn(pgDatabase(client));return await client.query("COMMIT"),result}catch(error){throw await client.query("ROLLBACK"),error}finally{client.release()}}});var postgresJs=(options)=>({name:"postgres-js",connect:(config)=>drizzle({...config,...options}),close:async(connection)=>{await connection.$client.end()},migrations:(connection)=>templateClientMigrations(connection.$client)});export{postgresJs};
@@ -0,0 +1,40 @@
1
+ import type { TenancyConfig } from '@elysiumjs/olympus/mnemosyne';
2
+ import type { DrizzleConnectionProps } from './database';
3
+ /**
4
+ * Configuration of the {@link DrizzleModule}.
5
+ * @author Axel Nana <ax.lnana@outlook.com>
6
+ */
7
+ export type DrizzleModuleConfig = {
8
+ /** The name of the connection that `default` resolves to. */
9
+ default: string;
10
+ /** The connections to declare, by name. */
11
+ connections: Record<string, DrizzleConnectionProps>;
12
+ /** The tenancy configuration. Defaults to schema-based tenancy. */
13
+ tenancy?: TenancyConfig;
14
+ };
15
+ declare const DrizzleModule_base: (abstract new () => {
16
+ readonly config: DrizzleModuleConfig;
17
+ onInit(): Promise<void> | void;
18
+ onDestroy(): Promise<void> | void;
19
+ onServe(_app: import("elysia").Elysia<any>): Promise<void> | void;
20
+ }) & {
21
+ readonly config: string;
22
+ with(config: Partial<DrizzleModuleConfig> | (() => Partial<DrizzleModuleConfig> | Promise<Partial<DrizzleModuleConfig>>)): import("@elysiumjs/olympus").ConfiguredModule<DrizzleModuleConfig>;
23
+ resolveConfig(input?: Partial<DrizzleModuleConfig> | undefined): DrizzleModuleConfig;
24
+ register<TConfig_1 = any>(props?: import("@elysiumjs/olympus").ModuleProps<TConfig_1>): (target: import("@elysiumjs/olympus").ModuleClass) => void;
25
+ configurable<TConfig extends object>(options?: {
26
+ defaults?: Partial<TConfig>;
27
+ required?: (keyof TConfig)[];
28
+ }): any;
29
+ };
30
+ /**
31
+ * Declares Drizzle connections as global services named `db.connection.{name}`, plus
32
+ * `db.connection.default` aliasing the configured default, `db.transaction` (used by
33
+ * `@transactional` handlers), and registers the tenancy config and strategy. Connections are created on first use. Also provides the migration commands.
34
+ * @author Axel Nana <ax.lnana@outlook.com>
35
+ */
36
+ export declare class DrizzleModule extends DrizzleModule_base {
37
+ /** Closes the clients of the connections that were created. */
38
+ onDestroy(): Promise<void>;
39
+ }
40
+ export {};
@@ -0,0 +1,170 @@
1
+ import type { InboxOwner, InboxPage, InboxQuery, NotificationStoreDriver, StoredNotification } from '@elysiumjs/olympus/hermes';
2
+ import type { DrizzleStoreOptions, StoreTable } from './moirai';
3
+ declare module '@elysiumjs/olympus' {
4
+ interface PortDrivers {
5
+ 'notification-store:drizzle': typeof DrizzleNotificationStore;
6
+ }
7
+ }
8
+ /**
9
+ * Builds the table of the notification store, in a schema when one is given. Export it from the
10
+ * Drizzle schema so that `migration:generate` creates it.
11
+ * @author Axel Nana <ax.lnana@outlook.com>
12
+ * @param name The name of the table. Defaults to `notifications`.
13
+ * @param schema The Postgres schema of the table. Defaults to the search path.
14
+ * @example
15
+ * ```ts
16
+ * export const notifications = notificationsTable();
17
+ *
18
+ * HermesModule.with({ store: () => new DrizzleNotificationStore({ table: notifications }) });
19
+ * ```
20
+ */
21
+ export declare const notificationsTable: (name?: string, schema?: string) => import("drizzle-orm/pg-core").PgTableWithColumns<{
22
+ name: string;
23
+ schema: undefined;
24
+ columns: {
25
+ id: import("drizzle-orm/pg-core").PgBuildColumn<string, import("drizzle-orm/pg-core").SetIsPrimaryKey<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
26
+ name: string;
27
+ tableName: string;
28
+ dataType: "string";
29
+ data: string;
30
+ driverParam: string;
31
+ notNull: true;
32
+ hasDefault: false;
33
+ isPrimaryKey: false;
34
+ isAutoincrement: false;
35
+ hasRuntimeDefault: false;
36
+ enumValues: undefined;
37
+ identity: undefined;
38
+ generated: undefined;
39
+ }>;
40
+ deliveryId: import("drizzle-orm/pg-core").PgBuildColumn<string, import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
41
+ name: string;
42
+ tableName: string;
43
+ dataType: "string";
44
+ data: string;
45
+ driverParam: string;
46
+ notNull: true;
47
+ hasDefault: false;
48
+ isPrimaryKey: false;
49
+ isAutoincrement: false;
50
+ hasRuntimeDefault: false;
51
+ enumValues: undefined;
52
+ identity: undefined;
53
+ generated: undefined;
54
+ }>;
55
+ notifiableType: import("drizzle-orm/pg-core").PgBuildColumn<string, import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>, {
56
+ name: string;
57
+ tableName: string;
58
+ dataType: "string";
59
+ data: string;
60
+ driverParam: string;
61
+ notNull: false;
62
+ hasDefault: false;
63
+ isPrimaryKey: false;
64
+ isAutoincrement: false;
65
+ hasRuntimeDefault: false;
66
+ enumValues: undefined;
67
+ identity: undefined;
68
+ generated: undefined;
69
+ }>;
70
+ notifiableId: import("drizzle-orm/pg-core").PgBuildColumn<string, import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
71
+ name: string;
72
+ tableName: string;
73
+ dataType: "string";
74
+ data: string;
75
+ driverParam: string;
76
+ notNull: true;
77
+ hasDefault: false;
78
+ isPrimaryKey: false;
79
+ isAutoincrement: false;
80
+ hasRuntimeDefault: false;
81
+ enumValues: undefined;
82
+ identity: undefined;
83
+ generated: undefined;
84
+ }>;
85
+ type: import("drizzle-orm/pg-core").PgBuildColumn<string, import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
86
+ name: string;
87
+ tableName: string;
88
+ dataType: "string";
89
+ data: string;
90
+ driverParam: string;
91
+ notNull: true;
92
+ hasDefault: false;
93
+ isPrimaryKey: false;
94
+ isAutoincrement: false;
95
+ hasRuntimeDefault: false;
96
+ enumValues: undefined;
97
+ identity: undefined;
98
+ generated: undefined;
99
+ }>;
100
+ data: import("drizzle-orm/pg-core").PgBuildColumn<string, import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").Set$Type<import("drizzle-orm/pg-core").PgJsonbBuilder, Record<string, unknown>>>, {
101
+ name: string;
102
+ tableName: string;
103
+ dataType: "object json";
104
+ data: Record<string, unknown>;
105
+ driverParam: unknown;
106
+ notNull: true;
107
+ hasDefault: false;
108
+ isPrimaryKey: false;
109
+ isAutoincrement: false;
110
+ hasRuntimeDefault: false;
111
+ enumValues: undefined;
112
+ identity: undefined;
113
+ generated: undefined;
114
+ }>;
115
+ readAt: import("drizzle-orm/pg-core").PgBuildColumn<string, import("drizzle-orm/pg-core").PgTimestampBuilder, {
116
+ name: string;
117
+ tableName: string;
118
+ dataType: "object date";
119
+ data: Date;
120
+ driverParam: string;
121
+ notNull: false;
122
+ hasDefault: false;
123
+ isPrimaryKey: false;
124
+ isAutoincrement: false;
125
+ hasRuntimeDefault: false;
126
+ enumValues: undefined;
127
+ identity: undefined;
128
+ generated: undefined;
129
+ }>;
130
+ createdAt: import("drizzle-orm/pg-core").PgBuildColumn<string, import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTimestampBuilder>, {
131
+ name: string;
132
+ tableName: string;
133
+ dataType: "object date";
134
+ data: Date;
135
+ driverParam: string;
136
+ notNull: true;
137
+ hasDefault: false;
138
+ isPrimaryKey: false;
139
+ isAutoincrement: false;
140
+ hasRuntimeDefault: false;
141
+ enumValues: undefined;
142
+ identity: undefined;
143
+ generated: undefined;
144
+ }>;
145
+ };
146
+ dialect: 'pg';
147
+ }>;
148
+ /** The table `notificationsTable()` builds. */
149
+ export type NotificationsTable = ReturnType<typeof notificationsTable>;
150
+ /**
151
+ * Keeps the notifications of the `inbox` channel in a Postgres table, built with
152
+ * `notificationsTable()` or any table with its columns.
153
+ * @author Axel Nana <ax.lnana@outlook.com>
154
+ */
155
+ export declare class DrizzleNotificationStore implements NotificationStoreDriver {
156
+ private readonly options;
157
+ constructor(options: DrizzleStoreOptions<StoreTable<NotificationsTable>>);
158
+ /** The table, read through the columns of the notifications table, which it has. */
159
+ private get table();
160
+ /** The rows of an owner: its id, and its type or none. */
161
+ private owned;
162
+ private get db();
163
+ save(notification: StoredNotification): Promise<void>;
164
+ list(owner: InboxOwner, query?: InboxQuery): Promise<InboxPage>;
165
+ unreadCount(owner: InboxOwner): Promise<number>;
166
+ markAsRead(owner: InboxOwner, ids: readonly string[]): Promise<number>;
167
+ markAllAsRead(owner: InboxOwner): Promise<number>;
168
+ delete(owner: InboxOwner, ids: readonly string[]): Promise<number>;
169
+ private read;
170
+ }
@@ -0,0 +1,20 @@
1
+ export * from 'drizzle-orm';
2
+ export * from 'drizzle-orm/pg-core';
3
+ export type { SelectedFields, SelectedFieldsFlat, SelectedFieldsOrdered, TableConfig } from 'drizzle-orm/pg-core';
4
+ export { pgTable, getTableBuilders } from './table';
5
+ export { rulesToDrizzle, UntranslatableRuleError } from './cerberus';
6
+ export { Database, DEFAULT_CACHE_TTL, DrizzleDatabaseCache, TenantMismatchError } from './database';
7
+ export type { DrizzleConnectionProps, DrizzleConnection, DrizzleQueryResultHKT } from './database';
8
+ export { type DrizzleDriver, type DrizzleDriverConfig, MissingDriverError, MissingMigrationsError } from './driver';
9
+ export { DrizzleModel, createSchemaFromDrizzle, drizzleAdapter, type DrizzleModelOptions } from './model';
10
+ export { DrizzleModule, type DrizzleModuleConfig } from './drizzle.module';
11
+ export { Migration, type MigrationConnection, type MigrationDatabase, type MigrationHook, type MigrationRun, type MigrationStep } from './migration';
12
+ export { nodePostgresMigrations, templateClientMigrations, type PgPool, type TemplateClient } from './migration-clients';
13
+ export { applyTableMarkers, DrizzleTable, getTableMarker, type CreatedTable, type PartitionColumn, type TableMarker } from './table-markers';
14
+ export { compileAfter, compileFilter, compileQuery } from './query';
15
+ export { drizzleRepositoryAdapter } from './repository';
16
+ export { DrizzleSchemaTenancy, DrizzleRLSTenancy, wrapTenantSchema, registerTenantSchema, getTenantSchema, withRLS, getSessionTenant, createRLSPolicy } from './tenancy';
17
+ export { PgAdvisoryLock } from './lock';
18
+ export { DrizzleNotificationStore, notificationsTable, type NotificationsTable } from './hermes';
19
+ export { DrizzleFileStore, filesTable, type FilesTable } from './pithos';
20
+ export { DrizzleEventStore, DrizzleIdempotencyStore, DrizzleInboxStore, DrizzleOutboxStore, DrizzleProjectionStorage, type DrizzleStoreOptions, moiraiTables, type MoiraiTables, type MoiraiTablesOptions, type StoreTable } from './moirai';
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ var __legacyDecorateClassTS=function(decorators,target,key,desc){var c=arguments.length,r=c<3?target:desc===null?desc=Object.getOwnPropertyDescriptor(target,key):desc,d;if(typeof Reflect==="object"&&typeof Reflect.decorate==="function")r=Reflect.decorate(decorators,target,key,desc);else for(var i=decorators.length-1;i>=0;i--)if(d=decorators[i])r=(c<3?d(r):c>3?d(target,key,r):d(target,key))||r;return c>3&&r&&Object.defineProperty(target,key,r),r};var __legacyMetadataTS=(k,v)=>{if(typeof Reflect==="object"&&typeof Reflect.metadata==="function")return Reflect.metadata(k,v)};export*from"drizzle-orm";export*from"drizzle-orm/pg-core";import{pgTable as drizzlePgTable}from"drizzle-orm/pg-core";var tableRegistry=new WeakMap,getTableBuilders=(table)=>tableRegistry.get(table),pgTable=(tableName,columns,extraConfig)=>{let table=drizzlePgTable(tableName,columns,extraConfig);return tableRegistry.set(table,columns),table};import{rulesToAST}from"@casl/ability/extra";import{ElysiumError}from"@elysiumjs/olympus";import{and,eq,gt,gte,inArray,isNotNull,isNull,lt,lte,notInArray,or,sql}from"drizzle-orm";class UntranslatableRuleError extends ElysiumError{constructor(detail){super(`rulesToDrizzle: ${detail}`)}}var TRUE=sql`true`,FALSE=sql`false`,columnOf=(table,field)=>{let column=field?table[field]:null;if(!column)throw new UntranslatableRuleError(`the table has no column "${field}"`);return column},listOf=(value)=>Array.isArray(value)?value:[value],children=(node,table)=>listOf(node.value).map((child)=>compile(child,table)),compile=(node,table)=>{switch(node.operator){case"and":{let parts=children(node,table);return parts.length===0?TRUE:and(...parts)??TRUE}case"or":{let parts=children(node,table);return parts.length===0?FALSE:or(...parts)??FALSE}case"not":{let parts=children(node,table),inner=parts.length===1?parts[0]:and(...parts)??TRUE;return sql`not coalesce(${inner}, false)`}case"eq":return node.value===null?isNull(columnOf(table,node.field)):eq(columnOf(table,node.field),node.value);case"ne":return node.value===null?isNotNull(columnOf(table,node.field)):sql`${columnOf(table,node.field)} is distinct from ${node.value}`;case"in":{let column=columnOf(table,node.field),all=listOf(node.value),values=all.filter((value)=>value!==null),parts=[...values.length>0?[inArray(column,values)]:[],...values.length<all.length?[isNull(column)]:[]];return parts.length===0?FALSE:or(...parts)??FALSE}case"nin":{let column=columnOf(table,node.field),all=listOf(node.value),values=all.filter((value)=>value!==null),listsNull=values.length<all.length;if(values.length===0)return listsNull?isNotNull(column):TRUE;return listsNull?and(isNotNull(column),notInArray(column,values))??TRUE:or(notInArray(column,values),isNull(column))??TRUE}case"lt":return lt(columnOf(table,node.field),node.value);case"lte":return lte(columnOf(table,node.field),node.value);case"gt":return gt(columnOf(table,node.field),node.value);case"gte":return gte(columnOf(table,node.field),node.value);case"exists":return node.value?isNotNull(columnOf(table,node.field)):isNull(columnOf(table,node.field));default:throw new UntranslatableRuleError(`the operator "${node.operator}" is not supported`)}},rulesToDrizzle=(ability,action,subject,table)=>{if(!ability)return FALSE;let ast=rulesToAST(ability,action,subject);return ast?compile(ast,table):FALSE};import{ElysiumError as ElysiumError3}from"@elysiumjs/olympus";import{AbstractDatabase,getCurrentTenant as getCurrentTenant2,getTenancyConfig}from"@elysiumjs/olympus/mnemosyne";import{sql as sql2}from"drizzle-orm";var bunRequire=()=>import.meta.require;import{AsyncLocalStorage}from"node:async_hooks";import{Service,ServiceNotReadyError}from"@elysiumjs/olympus";import{createDatabaseCacheStorage,getCurrentTenant}from"@elysiumjs/olympus/mnemosyne";import{getTableName,is,Table}from"drizzle-orm";import{Cache as DrizzleCache}from"drizzle-orm/cache/core";var DEFAULT_CACHE_TTL=60,VERSION_TTL=86400000,MAX_PENDING_PER_KEY=16,MAX_PENDING_KEYS=1000,TABLE_SCHEMA=Symbol.for("drizzle:Schema"),tableKey=(table)=>{if(!is(table,Table))return table;let schema=table[TABLE_SCHEMA];return schema?`${schema}.${getTableName(table)}`:getTableName(table)},toArray=(value)=>value===void 0?[]:Array.isArray(value)?value:[value];class DrizzleDatabaseCache extends DrizzleCache{#storageEngine;#cache;#cacheStrategy;#globalTtl;#keyPrefix;#transactionScope=new AsyncLocalStorage;#pending=new Map;constructor(cacheStrategy="explicit",globalTtl=DEFAULT_CACHE_TTL,storageEngine="memory",connectionName="default",prefix=""){super();this.#cacheStrategy=cacheStrategy,this.#globalTtl=globalTtl,this.#storageEngine=storageEngine,this.#keyPrefix=prefix?`${prefix}:${connectionName}`:connectionName}async#storage(){if(this.#cache)return this.#cache;try{this.#cache=createDatabaseCacheStorage({storage:this.#storageEngine})}catch(error){if(!(error instanceof ServiceNotReadyError))throw error;await Service.resolve(error.token),this.#cache=createDatabaseCacheStorage({storage:this.#storageEngine})}return this.#cache}strategy(){return this.#cacheStrategy}async trackTransaction(run){let scope={tables:new Set,tags:new Set},result=await this.#transactionScope.run(scope,run);return await this.#invalidate([...scope.tables],[...scope.tags]),result}async get(key,tables=[],isTag=!1,autoInvalidate){if(this.#transactionScope.getStore())return;let storage=await this.#storage(),entryKey=this.#entryKey(key,isTag),versionKeys=autoInvalidate===!1?[]:tables.map((table)=>this.#versionKey(table)),[entry,...versions]=await storage.mget([entryKey,...versionKeys]),missing=versionKeys.flatMap((versionKey,index)=>versions[index]===void 0?[{index,versionKey}]:[]);if(missing.length>0){let created=missing.map(({versionKey})=>({key:versionKey,value:crypto.randomUUID(),ttl:VERSION_TTL}));await storage.mset(created);for(let[i,{index}]of missing.entries())versions[index]=created[i].value}let signature=versions.join("|"),cached=entry;if(cached!==void 0&&cached.v===signature)return cached.r;this.#remember(entryKey,{signature,versionKeys,at:Date.now()});return}async put(key,response,_tables,isTag,config){if(this.#transactionScope.getStore())return;let entryKey=this.#entryKey(key,isTag),pending=this.#live(entryKey);if(pending.length===0||new Set(pending.map((miss)=>miss.signature)).size>1){this.#pending.delete(entryKey);return}let[miss,...rest]=pending;if(rest.length>0)this.#pending.set(entryKey,rest);else this.#pending.delete(entryKey);let storage=await this.#storage();if((await storage.mget(miss.versionKeys)).join("|")!==miss.signature)return;let signature=miss.signature,ttl=await this.#ttl(storage,entryKey,config);if(ttl<=0)return;await storage.set(entryKey,{v:signature,r:response},ttl)}async onMutate(params){let tables=toArray(params.tables).map(tableKey),tags=toArray(params.tags),scope=this.#transactionScope.getStore();if(scope){for(let table of tables)scope.tables.add(table);for(let tag of tags)scope.tags.add(tag);return}await this.#invalidate(tables,tags)}#remember(entryKey,miss){let pending=[...this.#live(entryKey),miss].slice(-MAX_PENDING_PER_KEY);this.#pending.delete(entryKey),this.#pending.set(entryKey,pending);while(this.#pending.size>MAX_PENDING_KEYS)this.#pending.delete(this.#pending.keys().next().value)}#live(entryKey){let since=Date.now()-this.#globalTtl*1000;return(this.#pending.get(entryKey)??[]).filter((miss)=>miss.at>=since)}async#invalidate(tables,tags){if(tables.length===0&&tags.length===0)return;let storage=await this.#storage();if(tables.length>0)await storage.mset(tables.map((table)=>({key:this.#versionKey(table),value:crypto.randomUUID(),ttl:VERSION_TTL})));if(tags.length>0)await storage.mdel(tags.map((tag)=>this.#entryKey(tag,!0)))}async#ttl(storage,entryKey,config){if(config?.px!==void 0)return config.px;if(config?.ex!==void 0)return config.ex*1000;if(config?.pxat!==void 0)return config.pxat-Date.now();if(config?.exat!==void 0)return config.exat*1000-Date.now();if(config?.keepTtl){let expiresAt=await storage.ttl(entryKey);if(expiresAt!==void 0&&expiresAt>Date.now())return expiresAt-Date.now()}return this.#globalTtl*1000}#entryKey(key,isTag){let kind=isTag?"tag":"query";return`${this.#keyPrefix}:${kind}:${getCurrentTenant()??"public"}:${key}`}#versionKey(table){return`${this.#keyPrefix}:version:${table}`}}import{ElysiumError as ElysiumError2}from"@elysiumjs/olympus";class MissingDriverError extends ElysiumError2{constructor(name){super(`Connection "${name}" sets no driver, and the default Bun SQL driver runs on Bun only. Set \`driver\` to postgresJs() or nodePostgres() on Node.js, or hyperdrive() on Cloudflare Workers.`)}}class MissingMigrationsError extends ElysiumError2{constructor(driver){super(`The ${driver} driver cannot run migrations: it gives no migration connection. Use a connection on the bun-sql, postgres-js or node-postgres driver.`)}}import{PgDialect}from"drizzle-orm/pg-core";var dialect=new PgDialect,toQuery=(query)=>dialect.sqlToQuery(query),templateDatabase=(client)=>({execute:async(query)=>{let{sql,params}=toQuery(query);return[...await client.unsafe(sql,params)]},script:async(text)=>{let query=client.unsafe(text);await(query.simple?query.simple():query)}}),templateClientMigrations=(client)=>({...templateDatabase(client),transaction:(fn)=>client.begin((tx)=>fn(templateDatabase(tx)))}),pgDatabase=(client)=>({execute:async(query)=>{let{sql,params}=toQuery(query);return(await client.query(sql,params)).rows},script:async(text)=>{await client.query(text)}}),nodePostgresMigrations=(pool)=>({...pgDatabase(pool),transaction:async(fn)=>{let client=await pool.connect();try{await client.query("BEGIN");let result=await fn(pgDatabase(client));return await client.query("COMMIT"),result}catch(error){throw await client.query("ROLLBACK"),error}finally{client.release()}}});var closedConnections=new WeakSet,connectionDrivers=new WeakMap,defaultDriver=(props,name)=>{if(typeof globalThis.Bun>"u")throw new MissingDriverError(name);let{drizzle}=bunRequire()("drizzle-orm/bun-sql"),client="client"in props?{client:props.client}:{connection:props.connection};return{name:"bun-sql",connect:(config)=>drizzle({...config,...client}),close:async(connection)=>{await connection.$client.close()},migrations:(connection)=>templateClientMigrations(connection.$client)}},getConnectionDriver=(connection)=>connectionDrivers.get(connection),closeConnection=async(connection)=>{let driver=connectionDrivers.get(connection);if(!driver||closedConnections.has(connection))return;closedConnections.add(connection),await driver.close(connection)};class TenantMismatchError extends ElysiumError3{transactionTenant;tenant;constructor(transactionTenant,tenant){super(`Cannot run work for tenant "${tenant}" in a transaction bound to `+(transactionTenant===null?"no tenant":`tenant "${transactionTenant}"`)+". Run it outside the transaction, or in its own withRLS() before opening the transaction.");this.transactionTenant=transactionTenant;this.tenant=tenant}}var transactionTenants=new WeakMap,bindTransactionTenant=async(tx,tenant)=>{let varName=getTenancyConfig().rls?.sessionVariable??"app.current_tenant";await tx.execute(sql2`SELECT set_config(${varName}, ${tenant}, true)`),transactionTenants.set(tx,tenant)},assertTransactionTenant=(tx,tenant)=>{let bound=transactionTenants.get(tx);if(bound!==tenant)throw new TenantMismatchError(bound??null,tenant)};class DrizzleDatabase extends AbstractDatabase{getConnection(name="default"){let connection=super.getConnection(name);if(transactionTenants.has(connection)&&getTenancyConfig().mode==="rls")assertTransactionTenant(connection,getCurrentTenant2()??"public");return connection}async transaction(callback,name="default"){if(getTenancyConfig().mode==="rls"){let active=this.getActiveTransaction(this.getBaseConnection(name));if(active!==void 0)assertTransactionTenant(active,getCurrentTenant2()??"public")}return super.transaction(callback,name)}createConnection(props,name="default"){return this.driver.createConnection(props,name)}driver={createConnection(config,name="default"){let cache=config.cache===void 0?void 0:config.cache===!0?new DrizzleDatabaseCache(void 0,void 0,void 0,name):config.cache.enabled!==!1?new DrizzleDatabaseCache(config.cache.strategy,config.cache.ttl,config.cache.storage,name,config.cache.prefix):void 0,{driver:chosen,connection:_url,client:_client,cache:_cache,...drizzleConfig}=config,driver=chosen??defaultDriver(config,name),connection=driver.connect({...drizzleConfig,cache});if(connectionDrivers.set(connection,driver),cache===void 0)return connection;let transaction=(callback,txConfig)=>Object.getPrototypeOf(connection).transaction.call(connection,callback,txConfig);return Object.assign(connection,{transaction:(callback,txConfig)=>cache.trackTransaction(()=>transaction(callback,txConfig))})},withTransaction(connection,callback){return connection.transaction(async(tx)=>{if(getTenancyConfig().mode==="rls")await bindTransactionTenant(tx,getCurrentTenant2()??"public");return callback(tx)})},getRawClient(connection){return connection.$client}}}var Database=new DrizzleDatabase;import{AbstractModel,createSchemaFromModel}from"@elysiumjs/olympus/mnemosyne";import{getTableConfig as getTableConfig3}from"drizzle-orm/pg-core";import{getCurrentTenant as getCurrentTenant3,getTenancyConfig as getTenancyConfig3,getTenancyStrategy,Listing}from"@elysiumjs/olympus/mnemosyne";import{and as and3,eq as eq3,getTableColumns,inArray as inArray3,or as or3,sql as sql4}from"drizzle-orm";import{getTableConfig as getTableConfig2}from"drizzle-orm/pg-core";import{and as and2,asc,desc,eq as eq2,gt as gt2,gte as gte2,ilike,inArray as inArray2,isNotNull as isNotNull2,isNull as isNull2,lt as lt2,lte as lte2,ne,or as or2}from"drizzle-orm";var escapeLike=(value)=>value.replace(/[\\%_]/g,(char)=>`\\${char}`),columnOf2=(table,column)=>{let found=table[column];if(!found)throw Error(`The table has no column "${column}" for the query.`);return found},compileFilter=(table,filter)=>{let column=columnOf2(table,filter.column),value=filter.value;switch(filter.operator){case"eq":return eq2(column,value);case"ne":return ne(column,value);case"gt":return gt2(column,value);case"gte":return gte2(column,value);case"lt":return lt2(column,value);case"lte":return lte2(column,value);case"in":return inArray2(column,value);case"contains":return ilike(column,`%${escapeLike(String(value))}%`);case"startsWith":return ilike(column,`${escapeLike(String(value))}%`);case"isNull":return value?isNull2(column):isNotNull2(column)}},compileAfter=(table,query)=>{if(!query.after)return;let branches=query.sort.map((key,index)=>{let equal=query.sort.slice(0,index).map((previous,i)=>eq2(columnOf2(table,previous.column),query.after[i])),column=columnOf2(table,key.column),value=query.after[index];return and2(...equal,key.direction==="asc"?gt2(column,value):lt2(column,value))});return or2(...branches)},compileQuery=(table,query)=>({where:and2(...query.filters.map((filter)=>compileFilter(table,filter)),compileAfter(table,query)),orderBy:query.sort.map(({column,direction})=>direction==="asc"?asc(columnOf2(table,column)):desc(columnOf2(table,column)))});import{getTenancyConfig as getTenancyConfig2,isValidTenantId,withTenant}from"@elysiumjs/olympus/mnemosyne";import{sql as sql3}from"drizzle-orm";import{getTableConfig,pgPolicy,pgSchema,pgTable as pgTable2}from"drizzle-orm/pg-core";var tenantSchemas=new Map,schemaRegistry=new Map,assertTenantId=(tenant)=>{if(!isValidTenantId(tenant))throw Error(`Invalid tenant identifier: ${JSON.stringify(tenant.slice(0,64))}`)},getTenantSchema=(tenant)=>(assertTenantId(tenant),schemaRegistry.get(tenant)??registerTenantSchema(tenant)),registerTenantSchema=(tenant)=>{if(assertTenantId(tenant),schemaRegistry.has(tenant))return schemaRegistry.get(tenant);let tenantSchema=pgSchema(tenant);return schemaRegistry.set(tenant,tenantSchema),tenantSchema},wrapTenantSchema=(tenant,tableName,columns)=>{if(assertTenantId(tenant),tenant==="public")return pgTable2(tableName,columns);let fullTableName=`${tenant}.${tableName}`;if(tenantSchemas.has(fullTableName))return tenantSchemas.get(fullTableName);let schemaTable=getTenantSchema(tenant).table(tableName,columns);return tenantSchemas.set(fullTableName,schemaTable),schemaTable},withRLS=async(tenant,callback,connectionName="default")=>new DrizzleRLSTenancy().withIsolation(Database.getBaseConnection(connectionName),tenant??"public",()=>callback(Database.getConnection(connectionName))),getSessionTenant=async(connectionName="default")=>{let db=Database.getConnection(connectionName),varName=getTenancyConfig2().rls?.sessionVariable??"app.current_tenant",result=await db.execute(sql3`SELECT current_setting(${varName}, true) as tenant`),[row]=Array.isArray(result)?result:result.rows;return row?.tenant||null},createRLSPolicy=(tableName,columnName,policyName)=>{let config=getTenancyConfig2(),varName=config.rls?.sessionVariable??"app.current_tenant",column=columnName??config.rls?.defaultColumn??"tenant_id",name=policyName??`${tableName}_tenant_isolation`;return pgPolicy(name,{for:"all",to:"public",using:sql3`${sql3.identifier(column)} = current_setting(${varName}, true)::text`,withCheck:sql3`${sql3.identifier(column)} = current_setting(${varName}, true)::text`})};class DrizzleSchemaTenancy{mode="schema";resolveTable(table,tenant){let builders=getTableBuilders(table);if(!builders)throw Error("No column builders registered for table. Use pgTable() from '@elysiumjs/drizzle' for tenant support.");let config=getTableConfig(table);return wrapTenantSchema(tenant,config.name,builders)}async withIsolation(_connection,_tenant,callback){return callback()}}class DrizzleRLSTenancy{mode="rls";resolveTable(table,_tenant){return table}async withIsolation(connection,tenant,callback){let active=Database.getActiveTransaction(connection);if(active!==void 0)return assertTransactionTenant(active,tenant),withTenant(tenant,callback);return connection.transaction(async(tx)=>(await bindTransactionTenant(tx,tenant),withTenant(tenant,()=>Database.runInTransaction(connection,tx,callback))))}}var tableOf=(target)=>target.table,primaryKeyOf=(table)=>{let names=new Map(Object.entries(getTableColumns(table)).map(([name,column])=>[column.name,name])),config=getTableConfig2(table);return(config.primaryKeys[0]?.columns??config.columns.filter((column)=>column.primary)).map((column)=>names.get(column.name)).filter((name)=>name!==void 0)},whereKey=(target,key)=>{let table=tableOf(target);return and3(...Object.entries(key).map(([column,value])=>eq3(table[column],value)))},both=(a,b)=>a&&b?and3(a,b):a??b,limited=(target,scope,limit)=>limit===void 0?scope:and3(scope,sql4`(tableoid, ctid) in (select tableoid, ctid from ${target.table} where ${scope} limit ${limit})`),run=async(name,operation)=>{if(getTenancyConfig3().mode!=="rls")return operation(Database.getConnection(name));return(getTenancyStrategy()??new DrizzleRLSTenancy).withIsolation(Database.getBaseConnection(name),getCurrentTenant3()??"public",()=>operation(Database.getConnection(name)))},drizzleRepositoryAdapter={primaryKey:primaryKeyOf,connection:(name)=>Database.getConnection(name),run,all(target){return run(target.connection,(db)=>db.select().from(tableOf(target)))},async find(target,key){let[row]=await run(target.connection,(db)=>db.select().from(tableOf(target)).where(whereKey(target,key)));return row??null},async findBy(target,column,value){let[row]=await run(target.connection,(db)=>db.select().from(tableOf(target)).where(eq3(tableOf(target)[column],value)).limit(1));return row??null},findWhere(target,scope,limit){return run(target.connection,(db)=>{let query=db.select().from(tableOf(target)).where(scope);return limit===void 0?query:query.limit(limit)})},async insert(target,data){let[row]=await run(target.connection,(db)=>db.insert(tableOf(target)).values(data).returning());return row},async update(target,key,data){let[row]=await run(target.connection,(db)=>db.update(tableOf(target)).set(data).where(whereKey(target,key)).returning());return row??null},updateAll(target,data){return run(target.connection,(db)=>db.update(tableOf(target)).set(data).returning())},async delete(target,key){let[row]=await run(target.connection,(db)=>db.delete(tableOf(target)).where(whereKey(target,key)).returning());return row??null},async deleteMany(target,keys){if(keys.length===0)return 0;let[only]=target.key,where=target.key.length===1?inArray3(tableOf(target)[only],keys.map((key)=>key[only])):or3(...keys.map((key)=>whereKey(target,key)));return(await run(target.connection,(db)=>db.delete(tableOf(target)).where(where).returning({deleted:sql4`1`}))).length},deleteAll(target){return run(target.connection,(db)=>db.delete(tableOf(target)).returning())},async deleteWhere(target,scope,limit){return(await run(target.connection,(db)=>db.delete(tableOf(target)).where(limited(target,scope,limit)).returning({deleted:sql4`1`}))).length},async exists(target,key){return await run(target.connection,(db)=>db.$count(tableOf(target),whereKey(target,key)))>0},async list(target,query,scope){let table=tableOf(target),compiled=compileQuery(table,query),where=both(compiled.where,scope),{orderBy}=compiled;if(query.page!==void 0){let offsetQuery=query;return run(target.connection,async(db)=>{let rows=await db.select().from(table).where(where).orderBy(...orderBy).offset((offsetQuery.page-1)*offsetQuery.limit).limit(offsetQuery.limit),total=await db.$count(table,where);return Listing.offsetPage(offsetQuery,rows,total)})}let rows=await run(target.connection,(db)=>db.select().from(table).where(where).orderBy(...orderBy).limit(query.limit+1));return Listing.page(query,rows)},count(target,query,scope){let table=tableOf(target),filters=query?compileQuery(table,{...query,after:void 0}).where:void 0;return run(target.connection,(db)=>db.$count(table,both(filters,scope)))}};var mapDrizzleDataType=(col)=>{if(col.columnType==="PgUUID")return"uuid";if(col.dimensions)return"array";let[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"}},drizzleAdapter={getTableName(table){return getTableConfig3(table).name},getColumns(table){let config=getTableConfig3(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){let builders=getTableBuilders(table);if(!builders)throw Error("No column builders registered for table. Use pgTable() from '@elysiumjs/drizzle' for tenant support.");let config=getTableConfig3(table);return wrapTenantSchema(tenant,config.name,builders)},repository:drizzleRepositoryAdapter},createSchemaFromDrizzle=(table,opts)=>createSchemaFromModel(drizzleAdapter.getColumns(table),opts),DrizzleModel=(table,options)=>{let config=getTableConfig3(table),builders=getTableBuilders(table),Base=AbstractModel(config.name,builders??{},drizzleAdapter,table);if(options?.supportTenancy!==void 0)Base.supportTenancy=options.supportTenancy;return Base};import{Module,needsDefaultAlias,runAll,Service as Service4}from"@elysiumjs/olympus";import{AbstractDatabase as AbstractDatabase2,DATABASE_TRANSACTION_SERVICE,MnemosyneModule,TENANCY_CONFIG_SERVICE,TENANCY_STRATEGY_SERVICE,withUnitOfWork}from"@elysiumjs/olympus/mnemosyne";import{access,mkdtemp,rm}from"node:fs/promises";import{tmpdir}from"node:os";import{join as join2}from"node:path";import{Command,CommandArgumentType}from"@elysiumjs/olympus";import{is as is3}from"drizzle-orm";import{PgTable as PgTableClass}from"drizzle-orm/pg-core";var basename=(path)=>path.split(/[\\/]/).pop()??path,dirname=(path)=>path.replace(/[\\/][^\\/]*$/,"");var getMigrationName=(path)=>basename(path)==="migration.sql"?basename(dirname(path)):basename(path),descriptiveName=(name)=>name.replace(/\.sql$/,"").split("_").slice(1).join("_"),folderTime=(name)=>{let match=/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(name);return match?Date.UTC(+match[1],+match[2]-1,+match[3],+match[4],+match[5],+match[6]):void 0},planMigrations=(files,applied)=>{let appliedNames=new Set(applied.map(({name})=>name)),fileNames=new Set(files.map(getMigrationName)),moved=new Map;for(let record of[...applied].sort((a,b)=>a.name.localeCompare(b.name))){if(!record.name.endsWith(".sql")||fileNames.has(record.name))continue;let key=descriptiveName(record.name);moved.set(key,[...moved.get(key)??[],record])}let plan={pending:[],renames:[]};for(let file of[...files].sort()){let name=getMigrationName(file);if(appliedNames.has(name))continue;let from=basename(file)==="migration.sql"?takeMoved(moved,name):void 0;if(from)plan.renames.push({from,to:name});else plan.pending.push(file)}return plan},takeMoved=(moved,name)=>{let records=moved.get(descriptiveName(name)),generatedAt=folderTime(name),index=records?.findIndex(({appliedAt})=>generatedAt===void 0||generatedAt<=appliedAt.getTime())??-1;return index<0?void 0:records.splice(index,1)[0].name},IDENTIFIER_CHAR=/[A-Za-z0-9_$]/;var endOfQuoted=(sql,start,quote,backslash)=>{let i=start+1;while(i<sql.length)if(backslash&&sql[i]==="\\")i+=2;else if(sql[i]!==quote)i++;else if(sql[i+1]===quote)i+=2;else return i+1;return sql.length},endOfVerbatim=(sql,i)=>{let previous=sql[i-1]??"";if(sql.startsWith("--",i)){let end=sql.indexOf(`
2
+ `,i);return end<0?sql.length:end}if(sql.startsWith("/*",i)){let end=sql.indexOf("*/",i+2);return end<0?sql.length:end+2}if(sql[i]==="'"){let escape=/[Ee]/.test(previous)&&!IDENTIFIER_CHAR.test(sql[i-2]??"");return endOfQuoted(sql,i,"'",escape)}if(sql[i]==="$"&&!IDENTIFIER_CHAR.test(previous)){let tag=/^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql.slice(i,i+64))?.[0];if(tag){let end=sql.indexOf(tag,i+tag.length);return end<0?sql.length:end+tag.length}}return-1},stripPublicSchema=(sql)=>{let output="",i=0;while(i<sql.length){let verbatim=endOfVerbatim(sql,i);if(verbatim>=0)output+=sql.slice(i,verbatim),i=verbatim;else if(sql.startsWith('"public".',i)&&sql[i+9]==='"')i+=9;else if(sql[i]==='"'){let end=endOfQuoted(sql,i,'"',!1);output+=sql.slice(i,end),i=end}else output+=sql[i],i++}return output};import{getTableConfig as getTableConfig4,PgDialect as PgDialect2}from"drizzle-orm/pg-core";var MARKERS=Symbol.for("elysium:drizzle:table-markers"),markTable=(table,markers)=>{let current=getTableMarker(table),range=markers.reduce((acc,marker)=>marker.range??acc,current?.range),afterCreate=[...current?.afterCreate??[],...markers.flatMap((marker)=>marker.afterCreate??[])];return Object.defineProperty(table,MARKERS,{value:{range,afterCreate},configurable:!0,enumerable:!1}),table},getTableMarker=(table)=>table[MARKERS],quote=(identifier)=>`"${identifier.replaceAll('"','""')}"`,endOfStatement=(sql,from)=>{let i=from;while(i<sql.length){let char=sql[i];if(char===";")return i+1;if(char==="'"||char==='"'){i++;while(i<sql.length)if(sql[i]===char)if(sql[i+1]===char)i+=2;else break;else i++;i++}else if(char==="-"&&sql[i+1]==="-"){let end=sql.indexOf(`
3
+ `,i);i=end<0?sql.length:end+1}else if(char==="$"){let tag=/^\$[A-Za-z_]*\$/.exec(sql.slice(i))?.[0];if(tag){let end=sql.indexOf(tag,i+tag.length);i=end<0?sql.length:end+tag.length}else i++}else i++}return sql.length},escapeRegExp=(value)=>value.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),applyTableMarkers=(sql,tables)=>{let dialect=new PgDialect2,output=sql;for(let table of tables){let marker=getTableMarker(table);if(!marker)continue;let{schema,name}=getTableConfig4(table),qualified=schema&&schema!=="public"?`${quote(schema)}.${quote(name)}`:quote(name),match=new RegExp(`CREATE TABLE (?:IF NOT EXISTS )?${escapeRegExp(qualified)}\\s*\\(`).exec(output);if(!match)continue;let end=endOfStatement(output,match.index+match[0].length),statement=output.slice(match.index,end).replace(/;$/,"");if(marker.range?.length){let columns=marker.range.map((column)=>quote(typeof column==="string"?column:column(table).name));statement+=` PARTITION BY RANGE (${columns.join(", ")})`}statement+=";";let created={schema:schema==="public"?void 0:schema,name,qualified};for(let after of marker.afterCreate??[]){let value=after(created),text=typeof value==="string"?value:dialect.sqlToQuery(value.inlineParams()).sql;statement+=`
4
+ --> statement-breakpoint
5
+ ${text.trim().replace(/;?$/,";")}`}output=output.slice(0,match.index)+statement+output.slice(end)}return output},DrizzleTable;((DrizzleTable)=>(DrizzleTable.define=(...markers)=>(target)=>markTable(target,markers),DrizzleTable.partitioned=(options)=>({range:Array.isArray(options.range)?options.range:[options.range]}),DrizzleTable.afterCreate=(build)=>({afterCreate:[build]})))(DrizzleTable||={});import{existsSync}from"node:fs";import{dirname as dirname2,join}from"node:path";var getProjectPath=()=>{let startPath=process.cwd(),dir=startPath;while(!0){if(existsSync(join(dir,".elysiumrc")))return dir;let parentDir=dirname2(dir);if(dir===parentDir)break;dir=parentDir}return startPath},getMigrationsPath=()=>join(getProjectPath(),"src/database/migrations");import{resolve,sep}from"node:path";import{is as is2}from"drizzle-orm";import{isPgEnum,isPgSequence,PgMaterializedView,PgPolicy,PgRole,PgSchema,PgTable,PgView}from"drizzle-orm/pg-core";var IGNORED=/(\.d|\.test|\.spec)\.[cm]?[jt]sx?$/,SOURCE=/\.[cm]?[jt]sx?$/,isSchemaObject=(value)=>{if(value===null||typeof value!=="object"&&typeof value!=="function")return!1;return isPgEnum(value)||isPgSequence(value)||is2(value,PgTable)||is2(value,PgSchema)||is2(value,PgView)||is2(value,PgMaterializedView)||is2(value,PgRole)||is2(value,PgPolicy)},isPlainObject=(value)=>{if(value===null||typeof value!=="object")return!1;let prototype=Object.getPrototypeOf(value);return prototype===Object.prototype||prototype===null},findSchemaExports=(modules)=>{let seen=new Set,found=[],add=(value,file,path)=>{if(seen.has(value))return;seen.add(value),found.push({file,path})};for(let[file,exports]of modules)for(let[name,value]of Object.entries(exports)){if(isSchemaObject(value)){add(value,file,[name]);continue}if(!isPlainObject(value))continue;for(let[key,nested]of Object.entries(value))if(isSchemaObject(nested))add(nested,file,[name,key])}return found},loadedProjectFiles=(projectPath,loaded)=>{let root=resolve(projectPath)+sep;return[...loaded].map((file)=>resolve(file)).filter((path)=>path.startsWith(root)&&SOURCE.test(path)&&!IGNORED.test(path)&&!path.includes(`${sep}node_modules${sep}`)).sort()},collectAppSchema=async(projectPath)=>{let cache=bunRequire()?.cache??(await import("node:module")).createRequire(import.meta.url).cache,modules=[];for(let file of loadedProjectFiles(projectPath,Object.keys(cache)))modules.push([file,await import(file)]);return findSchemaExports(modules)},renderSchemaEntry=(exports)=>{let files=[...new Set(exports.map(({file})=>file))],aliases=new Map(files.map((file,i)=>[file,`m${i}`])),lines=files.map((file)=>`import * as ${aliases.get(file)} from ${JSON.stringify(file)};`);return lines.push(""),exports.forEach(({file,path},i)=>{let access=path.map((key)=>`[${JSON.stringify(key)}]`).join("");lines.push(`export const s${i} = ${aliases.get(file)}${access};`)}),lines.join(`
6
+ `)+`
7
+ `};var exists=(path)=>access(path).then(()=>!0,()=>!1),listSqlFiles=async(migrationsPath)=>{if(!await exists(migrationsPath))return[];return Array.fromAsync(new Bun.Glob("**/*.sql").scan({cwd:migrationsPath,absolute:!0,onlyFiles:!0}))},loadTables=async(exports)=>{let tables=[];for(let{file,path}of exports){let value=path.reduce((acc,key)=>acc[key],await import(file));if(is3(value,PgTableClass))tables.push(value)}return tables};class MigrationGenerateCommand extends Command{static command="migration:generate";static description="Generates migration files.";async run(){if(!this.name&&!process.stdin.isTTY)this.error("Give the migration a name with --name when no terminal can answer the prompt."),process.exit(1);if(!this.name){let{default:prompts}=await import("prompts"),answers=await prompts([{type:"text",name:"name",message:"Migration Name:",initial:"create_users_table",validate(value){if(value.length<1)return"Migration name cannot be empty";return!0}}],{onCancel:()=>{this.error("Operation cancelled."),process.exit(0)}});this.name=answers.name}let projectRoot=getProjectPath(),migrationsPath=getMigrationsPath(),schema=await collectAppSchema(projectRoot);if(schema.length===0)this.error("No Drizzle table is exported by the files the application loads."),process.exit(1);let existing=new Set(await listSqlFiles(migrationsPath)),entryDir=await mkdtemp(join2(tmpdir(),"elysium-schema-")),entry=join2(entryDir,"schema.ts");try{await Bun.write(entry,renderSchemaEntry(schema)),await Bun.$`bunx --bun drizzle-kit generate --dialect postgresql --schema ${entry} --out ${migrationsPath} --name ${this.name}`.cwd(projectRoot)}finally{await rm(entryDir,{recursive:!0,force:!0})}let tables=await loadTables(schema);for(let path of await listSqlFiles(migrationsPath)){if(existing.has(path))continue;let file=Bun.file(path);await file.write(applyTableMarkers(stripPublicSchema(await file.text()),tables))}}}__legacyDecorateClassTS([Command.arg({description:"The name of the migration to create",type:CommandArgumentType.STRING}),__legacyMetadataTS("design:type",String)],MigrationGenerateCommand.prototype,"name",void 0),MigrationGenerateCommand=__legacyDecorateClassTS([Command.register()],MigrationGenerateCommand);import{readdir,readFile}from"node:fs/promises";import{join as join3}from"node:path";import{Command as Command2,CommandArgumentType as CommandArgumentType2,Service as Service3}from"@elysiumjs/olympus";import{isValidTenantId as isValidTenantId2}from"@elysiumjs/olympus/mnemosyne";import{sql as sql5}from"drizzle-orm";import{Service as Service2}from"@elysiumjs/olympus";var Migration;((Migration)=>(Migration.Hooks=Service2.tag("mnemosyne.migration.hooks"),Migration.hook=(props={})=>Service2.register({...props,tags:[...props?.tags??[],Migration.Hooks]})))(Migration||={});var listMigrationFiles=async(folder)=>(await readdir(folder,{recursive:!0,withFileTypes:!0}).catch((error)=>{if(error.code==="ENOENT")return[];throw error})).filter((entry)=>entry.isFile()&&entry.name.endsWith(".sql")).map((entry)=>join3(entry.parentPath,entry.name));class MigrationRunCommand extends Command2{constructor(){super(...arguments);this.tenant="";this.connection="default";this.fresh=!1;this.createSchema=!1}static command="migration:run";static description="Runs pending migrations.";async run(){if(!isValidTenantId2(this.tenant))return this.error(`Invalid tenant ${this.bold(this.tenant)}: use 1 to 63 letters, digits, underscores or dashes, not starting with pg_ and other than information_schema.`),process.exit(1);if(!Database.connectionExists(this.connection))return this.error(`Connection ${this.bold(this.connection)} does not exist.`),process.exit(1);let connection=Database.getConnection(this.connection),driver=getConnectionDriver(connection);if(!driver?.migrations)return this.error(`Connection ${this.bold(this.connection)} uses the ${this.bold(driver?.name??"unknown")} driver, which cannot run migrations. Use a connection on the bun-sql, postgres-js or node-postgres driver with --connection.`),process.exit(1);try{let db=driver.migrations(connection);if(!this.fresh&&!await this.ensureSchemaChoice(db))return process.exit(1);await this.migrate(db)}finally{await closeConnection(connection)}}async migrate(connection){await connection.transaction(async(db)=>{let schema=sql5.identifier(this.tenant);if(await db.execute(sql5`SELECT pg_advisory_xact_lock(hashtext(${`elysium_migrations:${this.tenant}`}))`),this.fresh)await db.execute(sql5`DROP SCHEMA IF EXISTS ${schema} CASCADE`);await db.execute(sql5`CREATE SCHEMA IF NOT EXISTS ${schema}`),await db.execute(sql5`SET LOCAL search_path TO ${schema}`),await db.execute(sql5`CREATE TABLE IF NOT EXISTS elysium_migrations (
8
+ id SERIAL PRIMARY KEY,
9
+ name TEXT NOT NULL,
10
+ batch INTEGER NOT NULL,
11
+ applied_at TIMESTAMP NOT NULL
12
+ )`),await db.execute(sql5`CREATE UNIQUE INDEX IF NOT EXISTS elysium_migrations_name_unique ON elysium_migrations (name)`);let completedMigrations=(await db.execute(sql5`SELECT name, applied_at FROM elysium_migrations`)).map((row)=>({name:row.name,appliedAt:row.applied_at})),files=await listMigrationFiles(getMigrationsPath()),{pending:sqlFiles,renames}=planMigrations(files,completedMigrations);for(let{from,to}of renames)await db.execute(sql5`UPDATE elysium_migrations SET name = ${to} WHERE name = ${from}`),this.info(`Migration ${this.bold(from)} is now tracked as ${this.bold(to)}.`);let hooks=await Service3.resolveAll(Migration.Hooks),[{max}={max:null}]=await db.execute(sql5`SELECT MAX(batch) + 1 AS max FROM elysium_migrations`),applied=[],run={db,tenant:this.tenant,connection:this.connection,batch:Number(max??0),pending:sqlFiles.map((path)=>getMigrationName(path)),applied};for(let hook of hooks)await hook.before?.(run);if(sqlFiles.length===0)this.success("No new migrations to apply.");else{let s=this.spinner(`Running migrations for tenant ${this.bold(this.tenant)}...`);for(let path of sqlFiles){let step={db,name:getMigrationName(path),path,run};for(let hook of hooks)await hook.beforeEach?.(step);await db.script(await readFile(path,"utf8")),await db.execute(sql5`INSERT INTO elysium_migrations (name, batch, applied_at) VALUES (${step.name}, ${run.batch}, now())`),applied.push(step.name);for(let hook of hooks)await hook.afterEach?.(step);s.pause(`Migration ${this.bold(step.name)} applied successfully.`)}s.complete(`Migrations for tenant ${this.bold(this.tenant)} applied successfully.`)}for(let hook of hooks)await hook.after?.(run)})}async ensureSchemaChoice(db){let[{schema_exists}={schema_exists:!1}]=await db.execute(sql5`SELECT EXISTS (
13
+ SELECT schema_name
14
+ FROM information_schema.schemata
15
+ WHERE schema_name = ${this.tenant}
16
+ ) AS schema_exists`);if(schema_exists||this.createSchema)return!0;if(!process.stdin.isTTY)return this.error(`Schema ${this.bold(this.tenant)} does not exist. Run again with --create-schema to create it.`),!1;let{default:prompts}=await import("prompts");if(!(await prompts([{type:"confirm",name:"create",message:`Schema ${this.tenant} does not exist. Do you want to create it?`,initial:!0}],{onCancel:()=>{this.error("Operation cancelled."),process.exit(0)}})).create)return this.error(`Schema ${this.bold(this.tenant)} creation cancelled.`),!1;return!0}}__legacyDecorateClassTS([Command2.arg({description:"The name of the tenant in which the migrations will be executed.",type:CommandArgumentType2.STRING,required:!0}),__legacyMetadataTS("design:type",String)],MigrationRunCommand.prototype,"tenant",void 0),__legacyDecorateClassTS([Command2.arg({description:"The name of the connection to use.",type:CommandArgumentType2.STRING,default:"default"}),__legacyMetadataTS("design:type",String)],MigrationRunCommand.prototype,"connection",void 0),__legacyDecorateClassTS([Command2.arg({description:"Drop the entire schema before running the migrations.",type:CommandArgumentType2.BOOLEAN,default:!1}),__legacyMetadataTS("design:type",Boolean)],MigrationRunCommand.prototype,"fresh",void 0),__legacyDecorateClassTS([Command2.arg({name:"create-schema",description:"Create the tenant schema without asking when it does not exist.",type:CommandArgumentType2.BOOLEAN,default:!1}),__legacyMetadataTS("design:type",Boolean)],MigrationRunCommand.prototype,"createSchema",void 0),MigrationRunCommand=__legacyDecorateClassTS([Command2.register()],MigrationRunCommand);class DrizzleModule extends Module.configurable({defaults:{default:"default"},required:["connections"]}){async onDestroy(){let names=Object.keys(this.config.connections).map(AbstractDatabase2.getConnectionName);await runAll(names.filter((name)=>Service4.isConstructed(name)).map((name)=>()=>closeConnection(Service4.get(name))))}}DrizzleModule=__legacyDecorateClassTS([Module.register({imports:[MnemosyneModule],commands:[MigrationGenerateCommand,MigrationRunCommand],services:(config)=>{let alias=needsDefaultAlias("DrizzleModule",config),tenancy=config.tenancy??{mode:"schema"};return[...Object.entries(config.connections).map(([name,props])=>({name:AbstractDatabase2.getConnectionName(name),global:!0,factory:()=>Database.createConnection(props,name)})),...alias?[{name:AbstractDatabase2.getConnectionName("default"),global:!0,factory:(_config,get)=>get(AbstractDatabase2.getConnectionName(config.default))}]:[],{name:DATABASE_TRANSACTION_SERVICE,global:!0,factory:()=>withUnitOfWork((callback,connection)=>Database.transaction(()=>callback(),connection))},{name:TENANCY_CONFIG_SERVICE,global:!0,factory:()=>tenancy},{name:TENANCY_STRATEGY_SERVICE,global:!0,factory:()=>tenancy.mode==="rls"?new DrizzleRLSTenancy:new DrizzleSchemaTenancy}]}})],DrizzleModule);import{Lock,LockDriver}from"@elysiumjs/olympus";import{sql as sql6}from"drizzle-orm";class PgAdvisoryLock extends LockDriver{options;constructor(options={}){super();this.options=options}async acquire(key,_ttl,options={}){let db=Database.getConnection(this.options.connection),wait=Math.max(0,options.wait??0);if(wait===1/0)return await db.execute(sql6`select pg_advisory_xact_lock(hashtextextended(${key}, 0))`),{key,token:crypto.randomUUID()};let deadline=Date.now()+wait;for(;;){let result=await db.execute(sql6`select pg_try_advisory_xact_lock(hashtextextended(${key}, 0)) as taken`);if(((Array.isArray(result)?result:result.rows)??[])[0]?.taken)return{key,token:crypto.randomUUID()};let left=deadline-Date.now();if(left<=0)return null;await new Promise((resolve)=>setTimeout(resolve,Math.min(left,this.options.retryDelay??50)))}}async release(_handle){return!0}async extend(_handle,_ttl){return!0}}PgAdvisoryLock=__legacyDecorateClassTS([Lock.driver("postgres"),__legacyMetadataTS("design:paramtypes",[Object])],PgAdvisoryLock);import{Clock}from"@elysiumjs/olympus";import{DEFAULT_INBOX_LIMIT,NotificationStore}from"@elysiumjs/olympus/hermes";import{and as and4,count,desc as desc2,eq as eq4,inArray as inArray4,isNull as isNull3,lt as lt3}from"drizzle-orm";import{index,jsonb,pgSchema as pgSchema2,pgTable as pgTable3,text,timestamp,uniqueIndex}from"drizzle-orm/pg-core";var at=(name)=>timestamp(name,{withTimezone:!0,mode:"date"}),notificationsTable=(name="notifications",schema)=>(schema?pgSchema2(schema).table:pgTable3)(name,{id:text("id").primaryKey(),deliveryId:text("delivery_id").notNull(),notifiableType:text("notifiable_type"),notifiableId:text("notifiable_id").notNull(),type:text("type").notNull(),data:jsonb("data").$type().notNull(),readAt:at("read_at"),createdAt:at("created_at").notNull()},(t)=>[uniqueIndex(`${name}_delivery_uq`).on(t.deliveryId),index(`${name}_notifiable_idx`).on(t.notifiableId,t.notifiableType,t.id),index(`${name}_unread_idx`).on(t.notifiableId,t.notifiableType).where(isNull3(t.readAt))]);class DrizzleNotificationStore{options;constructor(options){this.options=options}get table(){return this.options.table}owned(owner){let t=this.table;return and4(eq4(t.notifiableId,owner.notifiableId),owner.notifiableType===void 0?isNull3(t.notifiableType):eq4(t.notifiableType,owner.notifiableType))}get db(){return Database.getConnection(this.options.connection)}async save(notification){await this.db.insert(this.table).values(notification).onConflictDoNothing()}async list(owner,query={}){let t=this.table,limit=query.limit??DEFAULT_INBOX_LIMIT,rows=await this.db.select().from(t).where(and4(this.owned(owner),query.unread?isNull3(t.readAt):void 0,query.cursor===void 0?void 0:lt3(t.id,query.cursor))).orderBy(desc2(t.id)).limit(limit+1),items=rows.slice(0,limit);return{items,nextCursor:rows.length>limit?items[items.length-1].id:null}}async unreadCount(owner){let t=this.table,[row]=await this.db.select({total:count()}).from(t).where(and4(this.owned(owner),isNull3(t.readAt)));return Number(row?.total??0)}async markAsRead(owner,ids){if(ids.length===0)return 0;return this.read(inArray4(this.table.id,[...ids]),owner)}async markAllAsRead(owner){return this.read(void 0,owner)}async delete(owner,ids){if(ids.length===0)return 0;let t=this.table;return(await this.db.delete(t).where(and4(this.owned(owner),inArray4(t.id,[...ids]))).returning({id:t.id})).length}async read(filter,owner){let t=this.table;return(await this.db.update(t).set({readAt:Clock.now()}).where(and4(this.owned(owner),isNull3(t.readAt),filter)).returning({id:t.id})).length}}DrizzleNotificationStore=__legacyDecorateClassTS([NotificationStore.driver("drizzle"),__legacyMetadataTS("design:paramtypes",[typeof DrizzleStoreOptions==="undefined"?Object:DrizzleStoreOptions])],DrizzleNotificationStore);import{FileStore}from"@elysiumjs/olympus/pithos";import{and as and5,asc as asc2,count as count2,eq as eq5,gt as gt3,inArray as inArray5,isNotNull as isNotNull3,isNull as isNull4,lte as lte3,or as or4,sql as sql7}from"drizzle-orm";import{bigint,index as index2,pgSchema as pgSchema3,pgTable as pgTable4,text as text2,timestamp as timestamp2}from"drizzle-orm/pg-core";var at2=(name)=>timestamp2(name,{withTimezone:!0,mode:"date"}),filesTable=(name="files",schema)=>(schema?pgSchema3(schema).table:pgTable4)(name,{id:text2("id").primaryKey(),purpose:text2("purpose").notNull(),disk:text2("disk").notNull(),key:text2("key").notNull(),contentType:text2("content_type").notNull(),size:bigint("size",{mode:"number"}).notNull(),ownerType:text2("owner_type").notNull(),ownerId:text2("owner_id").notNull(),createdBy:text2("created_by"),status:text2("status").$type().notNull(),createdAt:at2("created_at").notNull(),readyAt:at2("ready_at"),claimedAt:at2("claimed_at"),releasedAt:at2("released_at"),expiresAt:at2("expires_at"),deletedAt:at2("deleted_at"),rejection:text2("rejection")},(t)=>[index2(`${name}_owner_idx`).on(t.ownerType,t.ownerId),index2(`${name}_pending_idx`).on(t.createdBy,t.expiresAt).where(sql7`${t.status} = 'pending'`),index2(`${name}_due_idx`).on(t.expiresAt).where(sql7`${t.status} in ('pending', 'finishing', 'ready') and ${t.expiresAt} is not null`)]),toRecord=({ownerType,ownerId,...row})=>({...row,owner:{type:ownerType,id:ownerId}});class DrizzleFileStore{options;constructor(options){this.options=options}get table(){return this.options.table}get db(){return Database.getConnection(this.options.connection)}async insert({owner,...record}){await this.db.insert(this.table).values({...record,ownerType:owner.type,ownerId:owner.id})}async find(id){let[row]=await this.db.select().from(this.table).where(eq5(this.table.id,id)).limit(1);return row?toRecord(row):null}async transition(id,when,patch){let t=this.table,condition=and5(eq5(t.id,id),inArray5(t.status,[...when.status]),when.due===void 0?void 0:lte3(t.expiresAt,when.due),when.live===void 0?void 0:or4(isNull4(t.expiresAt),gt3(t.expiresAt,when.live))),[row]=Object.keys(patch).length===0?await this.db.select().from(t).where(condition).limit(1):await this.db.update(t).set(patch).where(condition).returning();return row?toRecord(row):null}async countPending(createdBy,now){let t=this.table,[row]=await this.db.select({total:count2()}).from(t).where(and5(eq5(t.createdBy,createdBy),sql7`${t.status} = 'pending'`,gt3(t.expiresAt,now)));return Number(row?.total??0)}async due(now,limit){let t=this.table;return(await this.db.select().from(t).where(and5(sql7`${t.status} in ('pending', 'finishing', 'ready')`,isNotNull3(t.expiresAt),lte3(t.expiresAt,now))).orderBy(asc2(t.expiresAt)).limit(limit)).map(toRecord)}}DrizzleFileStore=__legacyDecorateClassTS([FileStore.driver("drizzle"),__legacyMetadataTS("design:paramtypes",[typeof DrizzleStoreOptions==="undefined"?Object:DrizzleStoreOptions])],DrizzleFileStore);import{Clock as Clock2}from"@elysiumjs/olympus";import{ConcurrencyConflictError,IdempotencyConflictError}from"@elysiumjs/olympus/moirai";import{and as and6,asc as asc3,eq as eq6,gt as gt4,inArray as inArray6,is as is4,isNull as isNull5,lt as lt4,lte as lte4,max,sql as sql8}from"drizzle-orm";import{bigserial,customType,index as index3,integer,jsonb as jsonb2,pgSchema as pgSchema4,PgTable as PgTable2,pgTable as pgTable5,primaryKey,text as text3,timestamp as timestamp3,unique,uuid}from"drizzle-orm/pg-core";var at3=(name)=>timestamp3(name,{withTimezone:!0,mode:"date"}),xid8=customType({dataType:()=>"xid8"}),moiraiTables=(schema,options={})=>{let table=schema?pgSchema4(schema).table:pgTable5,partitioned=options.partitioned??!1,partition=(range,target)=>partitioned?DrizzleTable.define(DrizzleTable.partitioned({range}))(target):target;return{idempotencyKeys:table("idempotency_keys",{key:text3("key").primaryKey(),action:text3("action").notNull(),requestHash:text3("request_hash").notNull(),response:jsonb2("response"),createdAt:at3("created_at").notNull().defaultNow()}),outbox:partition("created_at",table("outbox",{id:partitioned?uuid("id").notNull():uuid("id").primaryKey(),kind:text3("kind").notNull(),payload:jsonb2("payload").notNull(),createdAt:at3("created_at").notNull().defaultNow(),availableAt:at3("available_at").notNull().defaultNow(),attempts:integer("attempts").notNull().default(0),lastError:text3("last_error"),dispatchedAt:at3("dispatched_at"),deadAt:at3("dead_at")},(t)=>[...partitioned?[primaryKey({columns:[t.id,t.createdAt]})]:[],index3("outbox_due_idx").on(t.availableAt).where(sql8`${t.dispatchedAt} is null and ${t.deadAt} is null`)])),inbox:table("inbox",{messageId:text3("message_id").notNull(),consumer:text3("consumer").notNull(),recordedAt:at3("recorded_at").notNull().defaultNow()},(t)=>[primaryKey({columns:[t.messageId,t.consumer]})]),events:partition("occurred_at",table("events",{position:partitioned?bigserial("position",{mode:"bigint"}).notNull():bigserial("position",{mode:"bigint"}).primaryKey(),transactionId:xid8("transaction_id").notNull().default(sql8`pg_current_xact_id()`),aggregateId:text3("aggregate_id").notNull(),aggregateType:text3("aggregate_type").notNull(),version:integer("version").notNull(),type:text3("type").notNull(),schemaVersion:integer("schema_version").notNull().default(1),data:jsonb2("data").notNull(),metadata:jsonb2("metadata").notNull(),occurredAt:at3("occurred_at").notNull()},(t)=>partitioned?[primaryKey({columns:[t.position,t.occurredAt]}),unique("events_aggregate_version_uq").on(t.aggregateId,t.version,t.occurredAt)]:[unique("events_aggregate_version_uq").on(t.aggregateId,t.version)])),projections:table("projections",{name:text3("name").primaryKey(),version:integer("version").notNull(),builtAt:at3("built_at").notNull().defaultNow()})}},isUniqueViolation=(error)=>{for(let e=error;e;e=e.cause){if(e.code==="23505"||e.errno==="23505")return!0;if(e.cause===e)break}return!1};class DrizzleIdempotencyStore{options;constructor(options){this.options=options}get table(){return this.options.table}async get(key){let t=this.table,[row]=await Database.getConnection(this.options.connection).select().from(t).where(eq6(t.key,key)).limit(1);return row?{...row,response:row.response??null}:void 0}async put(record){try{await Database.getConnection(this.options.connection).insert(this.table).values(record)}catch(error){if(isUniqueViolation(error))throw new IdempotencyConflictError(record.key);throw error}}}class DrizzleOutboxStore{options;constructor(options){this.options=options}get table(){return this.options.table}get db(){return Database.getConnection(this.options.connection)}async insert(rows){if(rows.length===0)return;await this.db.insert(this.table).values(rows.map((row)=>({...row})))}async claimDue(claim){let{now,leaseUntil,limit,kinds,ids}=claim;if(kinds?.length===0||ids?.length===0)return[];let t=this.table,due=this.db.select({id:t.id}).from(t).where(and6(isNull5(t.dispatchedAt),isNull5(t.deadAt),lte4(t.availableAt,now),kinds?inArray6(t.kind,[...kinds]):void 0,ids?inArray6(t.id,[...ids]):void 0)).orderBy(asc3(t.availableAt)).limit(limit).for("update",{skipLocked:!0});return(await this.db.update(t).set({availableAt:leaseUntil}).where(inArray6(t.id,due)).returning()).sort((a,b)=>a.id<b.id?-1:a.id>b.id?1:0)}async markDispatched(id,dispatchedAt){await this.#update(id,{dispatchedAt})}async markFailed(id,update){await this.#update(id,update)}async markDead(id,update){await this.#update(id,update)}async replacePayload(id,payload){await this.#update(id,{payload})}async#update(id,values){let t=this.table;await this.db.update(t).set(values).where(eq6(t.id,id))}}class DrizzleInboxStore{options;constructor(options){this.options=options}get table(){return this.options.table}async record(messageId,consumer){let t=this.table;return(await Database.getConnection(this.options.connection).insert(t).values({messageId,consumer}).onConflictDoNothing().returning({messageId:t.messageId})).length>0}async pruneBefore(cutoff){let t=this.table;return(await Database.getConnection(this.options.connection).delete(t).where(lt4(t.recordedAt,cutoff)).returning({messageId:t.messageId})).length}}var toRecord2=(row)=>({position:row.position,aggregateId:row.aggregateId,aggregateType:row.aggregateType,version:row.version,type:row.type,schemaVersion:row.schemaVersion,data:row.data,metadata:row.metadata,occurredAt:row.occurredAt});class DrizzleEventStore{options;constructor(options){this.options=options}get table(){return this.options.table}get db(){return Database.getConnection(this.options.connection)}async load(aggregateId,afterVersion=0){let t=this.table;return(await this.db.select().from(t).where(and6(eq6(t.aggregateId,aggregateId),gt4(t.version,afterVersion))).orderBy(asc3(t.version))).map(toRecord2)}async readAll(filter){let{types,afterPosition,limit}=filter;if(types?.length===0)return[];let t=this.table;return(await this.db.select().from(t).where(and6(afterPosition===void 0?void 0:gt4(t.position,afterPosition),types?inArray6(t.type,[...types]):void 0)).orderBy(asc3(t.position)).limit(limit)).map(toRecord2)}async append(records){if(records.length===0)return;let t=this.table,first=new Map;for(let record of records){let known=first.get(record.aggregateId);if(!known||record.version<known.version)first.set(record.aggregateId,record)}try{for(let[aggregateId,record]of[...first].sort(([a],[b])=>a.localeCompare(b))){await this.db.execute(sql8`select pg_advisory_xact_lock(hashtextextended(${aggregateId}::text, 0))`);let[head]=await this.db.select({version:max(t.version)}).from(t).where(eq6(t.aggregateId,aggregateId));if((head?.version??0)!==record.version-1)throw new ConcurrencyConflictError(record.aggregateType,aggregateId)}await this.db.insert(t).values(records.map((record)=>({aggregateId:record.aggregateId,aggregateType:record.aggregateType,version:record.version,type:record.type,schemaVersion:record.schemaVersion,data:record.data,metadata:record.metadata,occurredAt:record.occurredAt})))}catch(error){if(isUniqueViolation(error))throw new ConcurrencyConflictError(records[0].aggregateType,records[0].aggregateId);throw error}}}var tableOf2=(model)=>{if(is4(model,PgTable2))return model;let table=model.table;if(table&&is4(table,PgTable2))return table;throw TypeError(`${model.name??"This model"} has no Drizzle table to reset. Pass a DrizzleModel or a Drizzle table in the models of the projection.`)};class DrizzleProjectionStorage{options;constructor(options){this.options=options}get table(){return this.options.table}get db(){return Database.getConnection(this.options.connection)}async reset(models){for(let model of models)await this.db.delete(tableOf2(model))}async version(name){let t=this.table,[row]=await this.db.select({version:t.version}).from(t).where(eq6(t.name,name)).limit(1);return row?.version}async built(name,version){let t=this.table,builtAt=Clock2.now();await this.db.insert(t).values({name,version,builtAt}).onConflictDoUpdate({target:t.name,set:{version,builtAt}})}}export{DEFAULT_CACHE_TTL,Database,DrizzleDatabaseCache,DrizzleEventStore,DrizzleFileStore,DrizzleIdempotencyStore,DrizzleInboxStore,DrizzleModel,DrizzleModule,DrizzleNotificationStore,DrizzleOutboxStore,DrizzleProjectionStorage,DrizzleRLSTenancy,DrizzleSchemaTenancy,DrizzleTable,Migration,MissingDriverError,MissingMigrationsError,PgAdvisoryLock,TenantMismatchError,UntranslatableRuleError,applyTableMarkers,compileAfter,compileFilter,compileQuery,createRLSPolicy,createSchemaFromDrizzle,drizzleAdapter,drizzleRepositoryAdapter,filesTable,getSessionTenant,getTableBuilders,getTableMarker,getTenantSchema,moiraiTables,nodePostgresMigrations,notificationsTable,pgTable,registerTenantSchema,rulesToDrizzle,templateClientMigrations,withRLS,wrapTenantSchema};
package/dist/lock.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import type { LockAcquireOptions, LockHandle } from '@elysiumjs/olympus';
2
+ import { LockDriver } from '@elysiumjs/olympus';
3
+ declare module '@elysiumjs/olympus' {
4
+ interface PortDrivers {
5
+ 'lock:postgres': typeof PgAdvisoryLock;
6
+ }
7
+ }
8
+ /**
9
+ * Takes Postgres advisory locks in the current transaction: a lock lasts until the transaction
10
+ * ends, whatever its time to live, so `release` and `extend` do nothing. Use it inside a unit of
11
+ * work only, such as for the `serializeBy` key of an action or `Moirai.lock()`. Outside a
12
+ * transaction, the lock ends with its own statement and excludes nobody, so it does not suit the
13
+ * scheduler of `HeraclesModule`.
14
+ * @author Axel Nana <ax.lnana@outlook.com>
15
+ * @example
16
+ * ```ts
17
+ * MoiraiModule.with({ lock: () => Lock.for('postgres') });
18
+ * ```
19
+ */
20
+ export declare class PgAdvisoryLock extends LockDriver {
21
+ private readonly options;
22
+ constructor(options?: {
23
+ /** The database connection. Defaults to `default`. */
24
+ connection?: string;
25
+ /** How long to wait between two tries of a bounded wait, in milliseconds. Defaults to 50. */
26
+ retryDelay?: number;
27
+ });
28
+ acquire(key: string, _ttl: number, options?: LockAcquireOptions): Promise<LockHandle | null>;
29
+ /** Does nothing: the lock ends with its transaction. */
30
+ release(_handle: LockHandle): Promise<boolean>;
31
+ /** Does nothing: the lock lasts until its transaction ends. */
32
+ extend(_handle: LockHandle, _ttl: number): Promise<boolean>;
33
+ }