@apibara/plugin-drizzle 2.1.0-beta.1 → 2.1.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +139 -15
- package/dist/index.d.cts +131 -3
- package/dist/index.d.mts +131 -3
- package/dist/index.d.ts +131 -3
- package/dist/index.mjs +123 -5
- package/dist/shared/plugin-drizzle.e884ca32.cjs +7 -0
- package/dist/shared/plugin-drizzle.f8d1b186.mjs +4 -0
- package/dist/testing.cjs +13 -0
- package/dist/testing.d.cts +6 -0
- package/dist/testing.d.mts +6 -0
- package/dist/testing.d.ts +6 -0
- package/dist/testing.mjs +11 -0
- package/package.json +9 -3
- package/src/constants.ts +2 -0
- package/src/helper.ts +192 -0
- package/src/index.ts +64 -3
- package/src/persistence.ts +21 -0
- package/src/storage.ts +42 -0
- package/src/testing.ts +13 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,144 @@
|
|
|
1
1
|
import * as _apibara_indexer_plugins from '@apibara/indexer/plugins';
|
|
2
|
-
import { TablesRelationalConfig, ExtractTablesWithRelations } from 'drizzle-orm';
|
|
2
|
+
import { DrizzleConfig, TablesRelationalConfig, ExtractTablesWithRelations } from 'drizzle-orm';
|
|
3
3
|
import { PgQueryResultHKT, PgTransaction, PgDatabase } from 'drizzle-orm/pg-core';
|
|
4
|
+
import { PGliteOptions, PGlite } from '@electric-sql/pglite';
|
|
5
|
+
import { MigrationConfig } from 'drizzle-orm/migrator';
|
|
6
|
+
import { NodePgDatabase as NodePgDatabase$1 } from 'drizzle-orm/node-postgres';
|
|
7
|
+
import { PgliteDatabase as PgliteDatabase$1 } from 'drizzle-orm/pglite';
|
|
8
|
+
import pg from 'pg';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Union type of all possible drizzle database options
|
|
12
|
+
*/
|
|
13
|
+
type DrizzleOptions = PgliteDrizzleOptions | NodePgDrizzleOptions;
|
|
14
|
+
/**
|
|
15
|
+
* Configuration options for Node-Postgres database connection
|
|
16
|
+
*/
|
|
17
|
+
type NodePgDrizzleOptions = {
|
|
18
|
+
/**
|
|
19
|
+
* Type of database to use -
|
|
20
|
+
* - "pglite" - PGLite database
|
|
21
|
+
* - "node-postgres" - Node-Postgres database
|
|
22
|
+
* @default "pglite"
|
|
23
|
+
*/
|
|
24
|
+
type: "node-postgres";
|
|
25
|
+
/**
|
|
26
|
+
* Connection string to use for the database
|
|
27
|
+
* @default ""
|
|
28
|
+
*/
|
|
29
|
+
connectionString?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Pool configuration options for Node-Postgres
|
|
32
|
+
*/
|
|
33
|
+
poolConfig?: pg.PoolConfig;
|
|
34
|
+
/**
|
|
35
|
+
* Additional drizzle configuration options
|
|
36
|
+
*/
|
|
37
|
+
config?: Omit<DrizzleConfig, "schema">;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Configuration options for PGLite database connection
|
|
41
|
+
*/
|
|
42
|
+
type PgliteDrizzleOptions = {
|
|
43
|
+
/**
|
|
44
|
+
* Type of database to use -
|
|
45
|
+
* - "pglite" - PGLite database
|
|
46
|
+
* - "node-postgres" - Node-Postgres database
|
|
47
|
+
*/
|
|
48
|
+
type?: "pglite";
|
|
49
|
+
/**
|
|
50
|
+
* Connection string to use for the database
|
|
51
|
+
* @default "memory://pglite"
|
|
52
|
+
*/
|
|
53
|
+
connectionString?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Pool configuration is not supported for PGLite
|
|
56
|
+
*/
|
|
57
|
+
poolConfig?: never;
|
|
58
|
+
/**
|
|
59
|
+
* Additional drizzle configuration options with PGLite specific connection options
|
|
60
|
+
*/
|
|
61
|
+
config?: Omit<DrizzleConfig, "schema"> & {
|
|
62
|
+
connection?: (PGliteOptions & {
|
|
63
|
+
dataDir?: string;
|
|
64
|
+
}) | string;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Extended PGLite database type with client information
|
|
69
|
+
*/
|
|
70
|
+
type PgliteDatabase<TSchema extends Record<string, unknown>> = PgliteDatabase$1<TSchema> & {
|
|
71
|
+
$client: PGlite;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Extended Node-Postgres database type with client information
|
|
75
|
+
*/
|
|
76
|
+
type NodePgDatabase<TSchema extends Record<string, unknown>> = NodePgDatabase$1<TSchema> & {
|
|
77
|
+
$client: pg.Pool;
|
|
78
|
+
};
|
|
79
|
+
type Database<TOptions extends DrizzleOptions, TSchema extends Record<string, unknown>> = TOptions extends PgliteDrizzleOptions ? PgliteDatabase<TSchema> : NodePgDatabase<TSchema>;
|
|
80
|
+
/**
|
|
81
|
+
* Creates a new Drizzle database instance based on the provided options
|
|
82
|
+
* @param options - Configuration options for the database connection
|
|
83
|
+
* @returns A configured Drizzle database instance
|
|
84
|
+
* @throws {Error} If an invalid database type is specified
|
|
85
|
+
*/
|
|
86
|
+
declare function drizzle<TSchema extends Record<string, unknown>, TOptions extends DrizzleOptions>(options?: TOptions & {
|
|
87
|
+
/**
|
|
88
|
+
* Schema to use for the database
|
|
89
|
+
* @default {}
|
|
90
|
+
*/
|
|
91
|
+
schema?: TSchema;
|
|
92
|
+
}): Database<TOptions, TSchema>;
|
|
93
|
+
/**
|
|
94
|
+
* Options for database migration
|
|
95
|
+
*/
|
|
96
|
+
type MigrateOptions = MigrationConfig;
|
|
97
|
+
/**
|
|
98
|
+
* Performs database migration based on the provided configuration
|
|
99
|
+
* @param db - The database instance to migrate
|
|
100
|
+
* @param options - Migration configuration options
|
|
101
|
+
*
|
|
102
|
+
* @important This function runs migrations on the database instance provided to the `drizzleStorage` plugin.
|
|
103
|
+
* It automatically detects the type of database and runs the appropriate migrate function
|
|
104
|
+
* (PGLite or Node-Postgres).
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* await migrate(db, { migrationsFolder: "./drizzle" });
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
declare function migrate<TSchema extends Record<string, unknown>>(db: PgliteDatabase<TSchema> | NodePgDatabase<TSchema>, options: MigrateOptions): Promise<void>;
|
|
4
112
|
|
|
5
113
|
type DrizzleStorage<TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>> = {
|
|
6
114
|
db: PgTransaction<TQueryResult, TFullSchema, TSchema>;
|
|
7
115
|
};
|
|
8
116
|
declare function useDrizzleStorage<TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>>(_db?: PgDatabase<TQueryResult, TFullSchema, TSchema>): DrizzleStorage<TQueryResult, TFullSchema, TSchema>;
|
|
9
117
|
interface DrizzleStorageOptions<TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>> {
|
|
118
|
+
/**
|
|
119
|
+
* The Drizzle database instance.
|
|
120
|
+
*/
|
|
10
121
|
db: PgDatabase<TQueryResult, TFullSchema, TSchema>;
|
|
122
|
+
/**
|
|
123
|
+
* Whether to persist the indexer's state. Defaults to true.
|
|
124
|
+
*/
|
|
11
125
|
persistState?: boolean;
|
|
126
|
+
/**
|
|
127
|
+
* The name of the indexer. Default value is 'default'.
|
|
128
|
+
*/
|
|
12
129
|
indexerName?: string;
|
|
130
|
+
/**
|
|
131
|
+
* The schema of the database.
|
|
132
|
+
*/
|
|
13
133
|
schema?: Record<string, unknown>;
|
|
134
|
+
/**
|
|
135
|
+
* The column to use as the id. Defaults to 'id'.
|
|
136
|
+
*/
|
|
14
137
|
idColumn?: string;
|
|
138
|
+
/**
|
|
139
|
+
* The options for the database migration. When provided, the database will automatically run migrations before the indexer runs.
|
|
140
|
+
*/
|
|
141
|
+
migrate?: MigrateOptions;
|
|
15
142
|
}
|
|
16
143
|
/**
|
|
17
144
|
* Creates a plugin that uses Drizzle as the storage layer.
|
|
@@ -22,7 +149,8 @@ interface DrizzleStorageOptions<TQueryResult extends PgQueryResultHKT, TFullSche
|
|
|
22
149
|
* @param options.indexerName - The name of the indexer. Defaults value is 'default'.
|
|
23
150
|
* @param options.schema - The schema of the database.
|
|
24
151
|
* @param options.idColumn - The column to use as the id. Defaults to 'id'.
|
|
152
|
+
* @param options.migrate - The options for the database migration. when provided, the database will automatically run migrations before the indexer runs.
|
|
25
153
|
*/
|
|
26
|
-
declare function drizzleStorage<TFilter, TBlock, TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>>({ db, persistState: enablePersistence, indexerName: identifier, schema, idColumn, }: DrizzleStorageOptions<TQueryResult, TFullSchema, TSchema>): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
154
|
+
declare function drizzleStorage<TFilter, TBlock, TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown> = Record<string, never>, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>>({ db, persistState: enablePersistence, indexerName: identifier, schema, idColumn, migrate: migrateOptions, }: DrizzleStorageOptions<TQueryResult, TFullSchema, TSchema>): _apibara_indexer_plugins.IndexerPlugin<TFilter, TBlock>;
|
|
27
155
|
|
|
28
|
-
export { type DrizzleStorage, type DrizzleStorageOptions, drizzleStorage, useDrizzleStorage };
|
|
156
|
+
export { type Database, type DrizzleOptions, type DrizzleStorage, type DrizzleStorageOptions, type MigrateOptions, type NodePgDatabase, type NodePgDrizzleOptions, type PgliteDatabase, type PgliteDrizzleOptions, drizzle, drizzleStorage, migrate, useDrizzleStorage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { useIndexerContext } from '@apibara/indexer';
|
|
2
|
-
import { defineIndexerPlugin } from '@apibara/indexer/plugins';
|
|
2
|
+
import { defineIndexerPlugin, useLogger } from '@apibara/indexer/plugins';
|
|
3
3
|
import { generateIndexerId } from '@apibara/indexer/internal';
|
|
4
4
|
import { useInternalContext } from '@apibara/indexer/internal/plugins';
|
|
5
|
+
import { D as DRIZZLE_PROPERTY, a as DRIZZLE_STORAGE_DB_PROPERTY } from './shared/plugin-drizzle.f8d1b186.mjs';
|
|
6
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
7
|
+
import { drizzle as drizzle$1 } from 'drizzle-orm/node-postgres';
|
|
8
|
+
import { migrate as migrate$2 } from 'drizzle-orm/node-postgres/migrator';
|
|
9
|
+
import { drizzle as drizzle$2 } from 'drizzle-orm/pglite';
|
|
10
|
+
import { migrate as migrate$1 } from 'drizzle-orm/pglite/migrator';
|
|
11
|
+
import pg from 'pg';
|
|
5
12
|
import { normalizeCursor } from '@apibara/protocol';
|
|
6
13
|
import { eq, and, isNull, gt, lt, sql } from 'drizzle-orm';
|
|
7
14
|
import { pgTable, text, integer, primaryKey, serial, char, jsonb } from 'drizzle-orm/pg-core';
|
|
@@ -34,6 +41,50 @@ function sleep(ms) {
|
|
|
34
41
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
35
42
|
}
|
|
36
43
|
|
|
44
|
+
function drizzle(options) {
|
|
45
|
+
const {
|
|
46
|
+
connectionString = "memory://",
|
|
47
|
+
schema,
|
|
48
|
+
type = "pglite",
|
|
49
|
+
config,
|
|
50
|
+
poolConfig
|
|
51
|
+
} = options ?? {};
|
|
52
|
+
if (connectionString.startsWith("postgres://") || type === "node-postgres") {
|
|
53
|
+
const pool = new pg.Pool({
|
|
54
|
+
connectionString,
|
|
55
|
+
...poolConfig || {}
|
|
56
|
+
});
|
|
57
|
+
return drizzle$1(pool, { schema, ...config || {} });
|
|
58
|
+
}
|
|
59
|
+
if (type === "pglite") {
|
|
60
|
+
return drizzle$2({
|
|
61
|
+
schema,
|
|
62
|
+
connection: {
|
|
63
|
+
dataDir: connectionString || "memory://pglite"
|
|
64
|
+
},
|
|
65
|
+
...config || {}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
throw new Error("Invalid database type");
|
|
69
|
+
}
|
|
70
|
+
async function migrate(db, options) {
|
|
71
|
+
const isPglite = !!("$client" in db && db.$client instanceof PGlite);
|
|
72
|
+
try {
|
|
73
|
+
if (isPglite) {
|
|
74
|
+
await migrate$1(db, options);
|
|
75
|
+
} else {
|
|
76
|
+
await migrate$2(db, options);
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new DrizzleStorageError(
|
|
80
|
+
"Failed to apply migrations! Please check if you have generated migrations using drizzle:generate",
|
|
81
|
+
{
|
|
82
|
+
cause: error
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
37
88
|
const CHECKPOINTS_TABLE_NAME = "__indexer_checkpoints";
|
|
38
89
|
const FILTERS_TABLE_NAME = "__indexer_filters";
|
|
39
90
|
const SCHEMA_VERSION_TABLE_NAME = "__indexer_schema_version";
|
|
@@ -211,6 +262,17 @@ async function finalizeState(props) {
|
|
|
211
262
|
});
|
|
212
263
|
}
|
|
213
264
|
}
|
|
265
|
+
async function resetPersistence(props) {
|
|
266
|
+
const { tx, indexerId } = props;
|
|
267
|
+
try {
|
|
268
|
+
await tx.delete(checkpoints).where(eq(checkpoints.id, indexerId));
|
|
269
|
+
await tx.delete(filters).where(eq(filters.id, indexerId));
|
|
270
|
+
} catch (error) {
|
|
271
|
+
throw new DrizzleStorageError("Failed to reset persistence state", {
|
|
272
|
+
cause: error
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
214
276
|
|
|
215
277
|
function getReorgTriggerName(table, indexerId) {
|
|
216
278
|
return `${table}_reorg_${indexerId}`;
|
|
@@ -431,8 +493,37 @@ async function finalize(tx, cursor, indexerId) {
|
|
|
431
493
|
});
|
|
432
494
|
}
|
|
433
495
|
}
|
|
496
|
+
async function cleanupStorage(tx, tables, indexerId) {
|
|
497
|
+
try {
|
|
498
|
+
for (const table of tables) {
|
|
499
|
+
await tx.execute(
|
|
500
|
+
sql.raw(
|
|
501
|
+
`DROP TRIGGER IF EXISTS ${getReorgTriggerName(table, indexerId)} ON ${table};`
|
|
502
|
+
)
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
await tx.execute(
|
|
506
|
+
sql.raw(`
|
|
507
|
+
DELETE FROM __reorg_rollback
|
|
508
|
+
WHERE indexer_id = '${indexerId}'
|
|
509
|
+
`)
|
|
510
|
+
);
|
|
511
|
+
for (const table of tables) {
|
|
512
|
+
try {
|
|
513
|
+
await tx.execute(sql.raw(`TRUNCATE TABLE ${table} CASCADE;`));
|
|
514
|
+
} catch (error) {
|
|
515
|
+
throw new DrizzleStorageError(`Failed to truncate table ${table}`, {
|
|
516
|
+
cause: error
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
} catch (error) {
|
|
521
|
+
throw new DrizzleStorageError("Failed to clean up storage", {
|
|
522
|
+
cause: error
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
434
526
|
|
|
435
|
-
const DRIZZLE_PROPERTY = "_drizzle";
|
|
436
527
|
const MAX_RETRIES = 5;
|
|
437
528
|
function useDrizzleStorage(_db) {
|
|
438
529
|
const context = useIndexerContext();
|
|
@@ -448,11 +539,13 @@ function drizzleStorage({
|
|
|
448
539
|
persistState: enablePersistence = true,
|
|
449
540
|
indexerName: identifier = "default",
|
|
450
541
|
schema,
|
|
451
|
-
idColumn = "id"
|
|
542
|
+
idColumn = "id",
|
|
543
|
+
migrate: migrateOptions
|
|
452
544
|
}) {
|
|
453
545
|
return defineIndexerPlugin((indexer) => {
|
|
454
546
|
let tableNames = [];
|
|
455
547
|
let indexerId = "";
|
|
548
|
+
const alwaysReindex = process.env["APIBARA_ALWAYS_REINDEX"] === "true";
|
|
456
549
|
try {
|
|
457
550
|
tableNames = Object.values(schema ?? db._.schema ?? {}).map(
|
|
458
551
|
(table) => table.dbName
|
|
@@ -463,11 +556,33 @@ function drizzleStorage({
|
|
|
463
556
|
});
|
|
464
557
|
}
|
|
465
558
|
indexer.hooks.hook("run:before", async () => {
|
|
466
|
-
const
|
|
559
|
+
const internalContext = useInternalContext();
|
|
560
|
+
const context = useIndexerContext();
|
|
561
|
+
const logger = useLogger();
|
|
562
|
+
context[DRIZZLE_STORAGE_DB_PROPERTY] = db;
|
|
563
|
+
const { indexerName: indexerFileName, availableIndexers } = internalContext;
|
|
467
564
|
indexerId = generateIndexerId(indexerFileName, identifier);
|
|
565
|
+
if (alwaysReindex) {
|
|
566
|
+
logger.warn(
|
|
567
|
+
`Reindexing: Deleting all data from tables - ${tableNames.join(", ")}`
|
|
568
|
+
);
|
|
569
|
+
await withTransaction(db, async (tx) => {
|
|
570
|
+
await cleanupStorage(tx, tableNames, indexerId);
|
|
571
|
+
if (enablePersistence) {
|
|
572
|
+
await resetPersistence({ tx, indexerId });
|
|
573
|
+
}
|
|
574
|
+
logger.success("Tables have been cleaned up for reindexing");
|
|
575
|
+
});
|
|
576
|
+
}
|
|
468
577
|
let retries = 0;
|
|
578
|
+
let migrationsApplied = false;
|
|
469
579
|
while (retries <= MAX_RETRIES) {
|
|
470
580
|
try {
|
|
581
|
+
if (migrateOptions && !migrationsApplied) {
|
|
582
|
+
await migrate(db, migrateOptions);
|
|
583
|
+
migrationsApplied = true;
|
|
584
|
+
logger.success("Migrations applied");
|
|
585
|
+
}
|
|
471
586
|
await withTransaction(db, async (tx) => {
|
|
472
587
|
await initializeReorgRollbackTable(tx, indexerId);
|
|
473
588
|
if (enablePersistence) {
|
|
@@ -477,6 +592,9 @@ function drizzleStorage({
|
|
|
477
592
|
break;
|
|
478
593
|
} catch (error) {
|
|
479
594
|
if (retries === MAX_RETRIES) {
|
|
595
|
+
if (error instanceof DrizzleStorageError) {
|
|
596
|
+
throw error;
|
|
597
|
+
}
|
|
480
598
|
throw new DrizzleStorageError(
|
|
481
599
|
"Initialization failed after 5 retries",
|
|
482
600
|
{
|
|
@@ -598,4 +716,4 @@ function drizzleStorage({
|
|
|
598
716
|
});
|
|
599
717
|
}
|
|
600
718
|
|
|
601
|
-
export { drizzleStorage, useDrizzleStorage };
|
|
719
|
+
export { drizzle, drizzleStorage, migrate, useDrizzleStorage };
|
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const constants = require('./shared/plugin-drizzle.e884ca32.cjs');
|
|
4
|
+
|
|
5
|
+
function getTestDatabase(context) {
|
|
6
|
+
const db = context[constants.DRIZZLE_STORAGE_DB_PROPERTY];
|
|
7
|
+
if (!db) {
|
|
8
|
+
throw new Error("Drizzle database not found in context");
|
|
9
|
+
}
|
|
10
|
+
return db;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
exports.getTestDatabase = getTestDatabase;
|
package/dist/testing.mjs
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { a as DRIZZLE_STORAGE_DB_PROPERTY } from './shared/plugin-drizzle.f8d1b186.mjs';
|
|
2
|
+
|
|
3
|
+
function getTestDatabase(context) {
|
|
4
|
+
const db = context[DRIZZLE_STORAGE_DB_PROPERTY];
|
|
5
|
+
if (!db) {
|
|
6
|
+
throw new Error("Drizzle database not found in context");
|
|
7
|
+
}
|
|
8
|
+
return db;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export { getTestDatabase };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apibara/plugin-drizzle",
|
|
3
|
-
"version": "2.1.0-beta.
|
|
3
|
+
"version": "2.1.0-beta.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
"import": "./dist/index.mjs",
|
|
16
16
|
"require": "./dist/index.cjs",
|
|
17
17
|
"default": "./dist/index.mjs"
|
|
18
|
+
},
|
|
19
|
+
"./testing": {
|
|
20
|
+
"types": "./dist/testing.d.ts",
|
|
21
|
+
"import": "./dist/testing.mjs",
|
|
22
|
+
"require": "./dist/testing.cjs",
|
|
23
|
+
"default": "./dist/testing.mjs"
|
|
18
24
|
}
|
|
19
25
|
},
|
|
20
26
|
"scripts": {
|
|
@@ -39,8 +45,8 @@
|
|
|
39
45
|
"vitest": "^1.6.0"
|
|
40
46
|
},
|
|
41
47
|
"dependencies": {
|
|
42
|
-
"@apibara/indexer": "2.1.0-beta.
|
|
43
|
-
"@apibara/protocol": "2.1.0-beta.
|
|
48
|
+
"@apibara/indexer": "2.1.0-beta.11",
|
|
49
|
+
"@apibara/protocol": "2.1.0-beta.11",
|
|
44
50
|
"postgres-range": "^1.1.4"
|
|
45
51
|
}
|
|
46
52
|
}
|
package/src/constants.ts
ADDED
package/src/helper.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { PGlite, type PGliteOptions } from "@electric-sql/pglite";
|
|
2
|
+
import type { DrizzleConfig } from "drizzle-orm";
|
|
3
|
+
import type { MigrationConfig } from "drizzle-orm/migrator";
|
|
4
|
+
import {
|
|
5
|
+
type NodePgDatabase as OriginalNodePgDatabase,
|
|
6
|
+
drizzle as drizzleNode,
|
|
7
|
+
} from "drizzle-orm/node-postgres";
|
|
8
|
+
import { migrate as migrateNode } from "drizzle-orm/node-postgres/migrator";
|
|
9
|
+
import {} from "drizzle-orm/pg-core";
|
|
10
|
+
import {
|
|
11
|
+
type PgliteDatabase as OriginalPgliteDatabase,
|
|
12
|
+
drizzle as drizzlePGLite,
|
|
13
|
+
} from "drizzle-orm/pglite";
|
|
14
|
+
import { migrate as migratePGLite } from "drizzle-orm/pglite/migrator";
|
|
15
|
+
import pg from "pg";
|
|
16
|
+
import { DrizzleStorageError } from "./utils";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Union type of all possible drizzle database options
|
|
20
|
+
*/
|
|
21
|
+
export type DrizzleOptions = PgliteDrizzleOptions | NodePgDrizzleOptions;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Configuration options for Node-Postgres database connection
|
|
25
|
+
*/
|
|
26
|
+
export type NodePgDrizzleOptions = {
|
|
27
|
+
/**
|
|
28
|
+
* Type of database to use -
|
|
29
|
+
* - "pglite" - PGLite database
|
|
30
|
+
* - "node-postgres" - Node-Postgres database
|
|
31
|
+
* @default "pglite"
|
|
32
|
+
*/
|
|
33
|
+
type: "node-postgres";
|
|
34
|
+
/**
|
|
35
|
+
* Connection string to use for the database
|
|
36
|
+
* @default ""
|
|
37
|
+
*/
|
|
38
|
+
connectionString?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Pool configuration options for Node-Postgres
|
|
41
|
+
*/
|
|
42
|
+
poolConfig?: pg.PoolConfig;
|
|
43
|
+
/**
|
|
44
|
+
* Additional drizzle configuration options
|
|
45
|
+
*/
|
|
46
|
+
config?: Omit<DrizzleConfig, "schema">;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Configuration options for PGLite database connection
|
|
51
|
+
*/
|
|
52
|
+
export type PgliteDrizzleOptions = {
|
|
53
|
+
/**
|
|
54
|
+
* Type of database to use -
|
|
55
|
+
* - "pglite" - PGLite database
|
|
56
|
+
* - "node-postgres" - Node-Postgres database
|
|
57
|
+
*/
|
|
58
|
+
type?: "pglite";
|
|
59
|
+
/**
|
|
60
|
+
* Connection string to use for the database
|
|
61
|
+
* @default "memory://pglite"
|
|
62
|
+
*/
|
|
63
|
+
connectionString?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Pool configuration is not supported for PGLite
|
|
66
|
+
*/
|
|
67
|
+
poolConfig?: never;
|
|
68
|
+
/**
|
|
69
|
+
* Additional drizzle configuration options with PGLite specific connection options
|
|
70
|
+
*/
|
|
71
|
+
config?: Omit<DrizzleConfig, "schema"> & {
|
|
72
|
+
connection?:
|
|
73
|
+
| (PGliteOptions & {
|
|
74
|
+
dataDir?: string;
|
|
75
|
+
})
|
|
76
|
+
| string;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Extended PGLite database type with client information
|
|
82
|
+
*/
|
|
83
|
+
export type PgliteDatabase<TSchema extends Record<string, unknown>> =
|
|
84
|
+
OriginalPgliteDatabase<TSchema> & {
|
|
85
|
+
$client: PGlite;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Extended Node-Postgres database type with client information
|
|
90
|
+
*/
|
|
91
|
+
export type NodePgDatabase<TSchema extends Record<string, unknown>> =
|
|
92
|
+
OriginalNodePgDatabase<TSchema> & {
|
|
93
|
+
$client: pg.Pool;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type Database<
|
|
97
|
+
TOptions extends DrizzleOptions,
|
|
98
|
+
TSchema extends Record<string, unknown>,
|
|
99
|
+
> = TOptions extends PgliteDrizzleOptions
|
|
100
|
+
? PgliteDatabase<TSchema>
|
|
101
|
+
: NodePgDatabase<TSchema>;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Creates a new Drizzle database instance based on the provided options
|
|
105
|
+
* @param options - Configuration options for the database connection
|
|
106
|
+
* @returns A configured Drizzle database instance
|
|
107
|
+
* @throws {Error} If an invalid database type is specified
|
|
108
|
+
*/
|
|
109
|
+
export function drizzle<
|
|
110
|
+
TSchema extends Record<string, unknown>,
|
|
111
|
+
TOptions extends DrizzleOptions,
|
|
112
|
+
>(
|
|
113
|
+
options?: TOptions & {
|
|
114
|
+
/**
|
|
115
|
+
* Schema to use for the database
|
|
116
|
+
* @default {}
|
|
117
|
+
*/
|
|
118
|
+
schema?: TSchema;
|
|
119
|
+
},
|
|
120
|
+
): Database<TOptions, TSchema> {
|
|
121
|
+
const {
|
|
122
|
+
connectionString = "memory://",
|
|
123
|
+
schema,
|
|
124
|
+
type = "pglite",
|
|
125
|
+
config,
|
|
126
|
+
poolConfig,
|
|
127
|
+
} = options ?? {};
|
|
128
|
+
|
|
129
|
+
if (connectionString.startsWith("postgres://") || type === "node-postgres") {
|
|
130
|
+
const pool = new pg.Pool({
|
|
131
|
+
connectionString,
|
|
132
|
+
...(poolConfig || {}),
|
|
133
|
+
});
|
|
134
|
+
return drizzleNode(pool, { schema, ...(config || {}) }) as Database<
|
|
135
|
+
TOptions,
|
|
136
|
+
TSchema
|
|
137
|
+
>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (type === "pglite") {
|
|
141
|
+
return drizzlePGLite({
|
|
142
|
+
schema: schema as TSchema,
|
|
143
|
+
connection: {
|
|
144
|
+
dataDir: connectionString || "memory://pglite",
|
|
145
|
+
},
|
|
146
|
+
...(config || {}),
|
|
147
|
+
}) as Database<TOptions, TSchema>;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
throw new Error("Invalid database type");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Options for database migration
|
|
155
|
+
*/
|
|
156
|
+
export type MigrateOptions = MigrationConfig;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Performs database migration based on the provided configuration
|
|
160
|
+
* @param db - The database instance to migrate
|
|
161
|
+
* @param options - Migration configuration options
|
|
162
|
+
*
|
|
163
|
+
* @important This function runs migrations on the database instance provided to the `drizzleStorage` plugin.
|
|
164
|
+
* It automatically detects the type of database and runs the appropriate migrate function
|
|
165
|
+
* (PGLite or Node-Postgres).
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* await migrate(db, { migrationsFolder: "./drizzle" });
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
export async function migrate<TSchema extends Record<string, unknown>>(
|
|
173
|
+
db: PgliteDatabase<TSchema> | NodePgDatabase<TSchema>,
|
|
174
|
+
options: MigrateOptions,
|
|
175
|
+
) {
|
|
176
|
+
const isPglite = !!("$client" in db && db.$client instanceof PGlite);
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
if (isPglite) {
|
|
180
|
+
await migratePGLite(db as PgliteDatabase<TSchema>, options);
|
|
181
|
+
} else {
|
|
182
|
+
await migrateNode(db as NodePgDatabase<TSchema>, options);
|
|
183
|
+
}
|
|
184
|
+
} catch (error) {
|
|
185
|
+
throw new DrizzleStorageError(
|
|
186
|
+
"Failed to apply migrations! Please check if you have generated migrations using drizzle:generate",
|
|
187
|
+
{
|
|
188
|
+
cause: error,
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|