@apibara/plugin-drizzle 2.1.0-beta.5 → 2.1.0-beta.7

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 CHANGED
@@ -4,10 +4,21 @@ const indexer = require('@apibara/indexer');
4
4
  const plugins = require('@apibara/indexer/plugins');
5
5
  const internal = require('@apibara/indexer/internal');
6
6
  const plugins$1 = require('@apibara/indexer/internal/plugins');
7
+ const constants = require('./shared/plugin-drizzle.e884ca32.cjs');
8
+ const pglite$1 = require('@electric-sql/pglite');
9
+ const nodePostgres = require('drizzle-orm/node-postgres');
10
+ const migrator$1 = require('drizzle-orm/node-postgres/migrator');
11
+ const pglite = require('drizzle-orm/pglite');
12
+ const migrator = require('drizzle-orm/pglite/migrator');
13
+ const pg = require('pg');
7
14
  const protocol = require('@apibara/protocol');
8
15
  const drizzleOrm = require('drizzle-orm');
9
16
  const pgCore = require('drizzle-orm/pg-core');
10
17
 
18
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
19
+
20
+ const pg__default = /*#__PURE__*/_interopDefaultCompat(pg);
21
+
11
22
  class DrizzleStorageError extends Error {
12
23
  constructor(message, options) {
13
24
  super(message, options);
@@ -36,6 +47,50 @@ function sleep(ms) {
36
47
  return new Promise((resolve) => setTimeout(resolve, ms));
37
48
  }
38
49
 
50
+ function drizzle(options) {
51
+ const {
52
+ connectionString = "memory://",
53
+ schema,
54
+ type = "pglite",
55
+ config,
56
+ poolConfig
57
+ } = options ?? {};
58
+ if (connectionString.startsWith("postgres://") || type === "node-postgres") {
59
+ const pool = new pg__default.Pool({
60
+ connectionString,
61
+ ...poolConfig || {}
62
+ });
63
+ return nodePostgres.drizzle(pool, { schema, ...config || {} });
64
+ }
65
+ if (type === "pglite") {
66
+ return pglite.drizzle({
67
+ schema,
68
+ connection: {
69
+ dataDir: connectionString || "memory://pglite"
70
+ },
71
+ ...config || {}
72
+ });
73
+ }
74
+ throw new Error("Invalid database type");
75
+ }
76
+ async function migrate(db, options) {
77
+ const isPglite = !!("$client" in db && db.$client instanceof pglite$1.PGlite);
78
+ try {
79
+ if (isPglite) {
80
+ await migrator.migrate(db, options);
81
+ } else {
82
+ await migrator$1.migrate(db, options);
83
+ }
84
+ } catch (error) {
85
+ throw new DrizzleStorageError(
86
+ "Failed to apply migrations! Please check if you have generated migrations using drizzle:generate",
87
+ {
88
+ cause: error
89
+ }
90
+ );
91
+ }
92
+ }
93
+
39
94
  const CHECKPOINTS_TABLE_NAME = "__indexer_checkpoints";
40
95
  const FILTERS_TABLE_NAME = "__indexer_filters";
41
96
  const SCHEMA_VERSION_TABLE_NAME = "__indexer_schema_version";
@@ -434,25 +489,25 @@ async function finalize(tx, cursor, indexerId) {
434
489
  }
435
490
  }
436
491
 
437
- const DRIZZLE_PROPERTY = "_drizzle";
438
492
  const MAX_RETRIES = 5;
439
493
  function useDrizzleStorage(_db) {
440
494
  const context = indexer.useIndexerContext();
441
- if (!context[DRIZZLE_PROPERTY]) {
495
+ if (!context[constants.DRIZZLE_PROPERTY]) {
442
496
  throw new DrizzleStorageError(
443
497
  "drizzle storage is not available. Did you register the plugin?"
444
498
  );
445
499
  }
446
- return context[DRIZZLE_PROPERTY];
500
+ return context[constants.DRIZZLE_PROPERTY];
447
501
  }
448
502
  function drizzleStorage({
449
503
  db,
450
504
  persistState: enablePersistence = true,
451
505
  indexerName: identifier = "default",
452
506
  schema,
453
- idColumn = "id"
507
+ idColumn = "id",
508
+ migrate: migrateOptions
454
509
  }) {
455
- return plugins.defineIndexerPlugin((indexer) => {
510
+ return plugins.defineIndexerPlugin((indexer$1) => {
456
511
  let tableNames = [];
457
512
  let indexerId = "";
458
513
  try {
@@ -464,12 +519,22 @@ function drizzleStorage({
464
519
  cause: error
465
520
  });
466
521
  }
467
- indexer.hooks.hook("run:before", async () => {
468
- const { indexerName: indexerFileName, availableIndexers } = plugins$1.useInternalContext();
522
+ indexer$1.hooks.hook("run:before", async () => {
523
+ const internalContext = plugins$1.useInternalContext();
524
+ const context = indexer.useIndexerContext();
525
+ const logger = plugins.useLogger();
526
+ context[constants.DRIZZLE_STORAGE_DB_PROPERTY] = db;
527
+ const { indexerName: indexerFileName, availableIndexers } = internalContext;
469
528
  indexerId = internal.generateIndexerId(indexerFileName, identifier);
470
529
  let retries = 0;
530
+ let migrationsApplied = false;
471
531
  while (retries <= MAX_RETRIES) {
472
532
  try {
533
+ if (migrateOptions && !migrationsApplied) {
534
+ await migrate(db, migrateOptions);
535
+ migrationsApplied = true;
536
+ logger.success("Migrations applied");
537
+ }
473
538
  await withTransaction(db, async (tx) => {
474
539
  await initializeReorgRollbackTable(tx, indexerId);
475
540
  if (enablePersistence) {
@@ -479,6 +544,9 @@ function drizzleStorage({
479
544
  break;
480
545
  } catch (error) {
481
546
  if (retries === MAX_RETRIES) {
547
+ if (error instanceof DrizzleStorageError) {
548
+ throw error;
549
+ }
482
550
  throw new DrizzleStorageError(
483
551
  "Initialization failed after 5 retries",
484
552
  {
@@ -491,7 +559,7 @@ function drizzleStorage({
491
559
  }
492
560
  }
493
561
  });
494
- indexer.hooks.hook("connect:before", async ({ request }) => {
562
+ indexer$1.hooks.hook("connect:before", async ({ request }) => {
495
563
  if (!enablePersistence) {
496
564
  return;
497
565
  }
@@ -508,7 +576,7 @@ function drizzleStorage({
508
576
  }
509
577
  });
510
578
  });
511
- indexer.hooks.hook("connect:after", async ({ request }) => {
579
+ indexer$1.hooks.hook("connect:after", async ({ request }) => {
512
580
  const cursor = request.startingCursor;
513
581
  if (!cursor) {
514
582
  return;
@@ -520,7 +588,7 @@ function drizzleStorage({
520
588
  }
521
589
  });
522
590
  });
523
- indexer.hooks.hook("connect:factory", async ({ request, endCursor }) => {
591
+ indexer$1.hooks.hook("connect:factory", async ({ request, endCursor }) => {
524
592
  if (!enablePersistence) {
525
593
  return;
526
594
  }
@@ -534,7 +602,7 @@ function drizzleStorage({
534
602
  });
535
603
  }
536
604
  });
537
- indexer.hooks.hook("message:finalize", async ({ message }) => {
605
+ indexer$1.hooks.hook("message:finalize", async ({ message }) => {
538
606
  const { cursor } = message.finalize;
539
607
  if (!cursor) {
540
608
  throw new DrizzleStorageError("Finalized Cursor is undefined");
@@ -546,7 +614,7 @@ function drizzleStorage({
546
614
  }
547
615
  });
548
616
  });
549
- indexer.hooks.hook("message:invalidate", async ({ message }) => {
617
+ indexer$1.hooks.hook("message:invalidate", async ({ message }) => {
550
618
  const { cursor } = message.invalidate;
551
619
  if (!cursor) {
552
620
  throw new DrizzleStorageError("Invalidate Cursor is undefined");
@@ -558,7 +626,7 @@ function drizzleStorage({
558
626
  }
559
627
  });
560
628
  });
561
- indexer.hooks.hook("handler:middleware", async ({ use }) => {
629
+ indexer$1.hooks.hook("handler:middleware", async ({ use }) => {
562
630
  use(async (context, next) => {
563
631
  try {
564
632
  const { endCursor, finality } = context;
@@ -566,7 +634,7 @@ function drizzleStorage({
566
634
  throw new DrizzleStorageError("End Cursor is undefined");
567
635
  }
568
636
  await withTransaction(db, async (tx) => {
569
- context[DRIZZLE_PROPERTY] = { db: tx };
637
+ context[constants.DRIZZLE_PROPERTY] = { db: tx };
570
638
  if (finality !== "finalized") {
571
639
  await registerTriggers(
572
640
  tx,
@@ -577,7 +645,7 @@ function drizzleStorage({
577
645
  );
578
646
  }
579
647
  await next();
580
- delete context[DRIZZLE_PROPERTY];
648
+ delete context[constants.DRIZZLE_PROPERTY];
581
649
  if (enablePersistence) {
582
650
  await persistState({
583
651
  tx,
@@ -600,5 +668,7 @@ function drizzleStorage({
600
668
  });
601
669
  }
602
670
 
671
+ exports.drizzle = drizzle;
603
672
  exports.drizzleStorage = drizzleStorage;
673
+ exports.migrate = migrate;
604
674
  exports.useDrizzleStorage = useDrizzleStorage;
package/dist/index.d.cts 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.d.mts 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.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";
@@ -432,7 +483,6 @@ async function finalize(tx, cursor, indexerId) {
432
483
  }
433
484
  }
434
485
 
435
- const DRIZZLE_PROPERTY = "_drizzle";
436
486
  const MAX_RETRIES = 5;
437
487
  function useDrizzleStorage(_db) {
438
488
  const context = useIndexerContext();
@@ -448,7 +498,8 @@ function drizzleStorage({
448
498
  persistState: enablePersistence = true,
449
499
  indexerName: identifier = "default",
450
500
  schema,
451
- idColumn = "id"
501
+ idColumn = "id",
502
+ migrate: migrateOptions
452
503
  }) {
453
504
  return defineIndexerPlugin((indexer) => {
454
505
  let tableNames = [];
@@ -463,11 +514,21 @@ function drizzleStorage({
463
514
  });
464
515
  }
465
516
  indexer.hooks.hook("run:before", async () => {
466
- const { indexerName: indexerFileName, availableIndexers } = useInternalContext();
517
+ const internalContext = useInternalContext();
518
+ const context = useIndexerContext();
519
+ const logger = useLogger();
520
+ context[DRIZZLE_STORAGE_DB_PROPERTY] = db;
521
+ const { indexerName: indexerFileName, availableIndexers } = internalContext;
467
522
  indexerId = generateIndexerId(indexerFileName, identifier);
468
523
  let retries = 0;
524
+ let migrationsApplied = false;
469
525
  while (retries <= MAX_RETRIES) {
470
526
  try {
527
+ if (migrateOptions && !migrationsApplied) {
528
+ await migrate(db, migrateOptions);
529
+ migrationsApplied = true;
530
+ logger.success("Migrations applied");
531
+ }
471
532
  await withTransaction(db, async (tx) => {
472
533
  await initializeReorgRollbackTable(tx, indexerId);
473
534
  if (enablePersistence) {
@@ -477,6 +538,9 @@ function drizzleStorage({
477
538
  break;
478
539
  } catch (error) {
479
540
  if (retries === MAX_RETRIES) {
541
+ if (error instanceof DrizzleStorageError) {
542
+ throw error;
543
+ }
480
544
  throw new DrizzleStorageError(
481
545
  "Initialization failed after 5 retries",
482
546
  {
@@ -598,4 +662,4 @@ function drizzleStorage({
598
662
  });
599
663
  }
600
664
 
601
- export { drizzleStorage, useDrizzleStorage };
665
+ export { drizzle, drizzleStorage, migrate, useDrizzleStorage };
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ const DRIZZLE_PROPERTY = "_drizzle";
4
+ const DRIZZLE_STORAGE_DB_PROPERTY = "_drizzleStorageDB";
5
+
6
+ exports.DRIZZLE_PROPERTY = DRIZZLE_PROPERTY;
7
+ exports.DRIZZLE_STORAGE_DB_PROPERTY = DRIZZLE_STORAGE_DB_PROPERTY;
@@ -0,0 +1,4 @@
1
+ const DRIZZLE_PROPERTY = "_drizzle";
2
+ const DRIZZLE_STORAGE_DB_PROPERTY = "_drizzleStorageDB";
3
+
4
+ export { DRIZZLE_PROPERTY as D, DRIZZLE_STORAGE_DB_PROPERTY as a };
@@ -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;
@@ -0,0 +1,6 @@
1
+ import { VcrResult } from '@apibara/indexer/testing';
2
+ import { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
3
+
4
+ declare function getTestDatabase(context: VcrResult): PgDatabase<PgQueryResultHKT>;
5
+
6
+ export { getTestDatabase };
@@ -0,0 +1,6 @@
1
+ import { VcrResult } from '@apibara/indexer/testing';
2
+ import { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
3
+
4
+ declare function getTestDatabase(context: VcrResult): PgDatabase<PgQueryResultHKT>;
5
+
6
+ export { getTestDatabase };
@@ -0,0 +1,6 @@
1
+ import { VcrResult } from '@apibara/indexer/testing';
2
+ import { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
3
+
4
+ declare function getTestDatabase(context: VcrResult): PgDatabase<PgQueryResultHKT>;
5
+
6
+ export { getTestDatabase };
@@ -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.5",
3
+ "version": "2.1.0-beta.7",
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.5",
43
- "@apibara/protocol": "2.1.0-beta.5",
48
+ "@apibara/indexer": "2.1.0-beta.7",
49
+ "@apibara/protocol": "2.1.0-beta.7",
44
50
  "postgres-range": "^1.1.4"
45
51
  }
46
52
  }
@@ -0,0 +1,2 @@
1
+ export const DRIZZLE_PROPERTY = "_drizzle";
2
+ export const DRIZZLE_STORAGE_DB_PROPERTY = "_drizzleStorageDB";
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
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useIndexerContext } from "@apibara/indexer";
2
- import { defineIndexerPlugin } from "@apibara/indexer/plugins";
2
+ import { defineIndexerPlugin, useLogger } from "@apibara/indexer/plugins";
3
3
 
4
4
  import type {
5
5
  ExtractTablesWithRelations,
@@ -14,6 +14,8 @@ import type {
14
14
  PgQueryResultHKT,
15
15
  PgTransaction,
16
16
  } from "drizzle-orm/pg-core";
17
+ import { DRIZZLE_PROPERTY, DRIZZLE_STORAGE_DB_PROPERTY } from "./constants";
18
+ import { type MigrateOptions, migrate } from "./helper";
17
19
  import {
18
20
  finalizeState,
19
21
  getState,
@@ -30,7 +32,8 @@ import {
30
32
  } from "./storage";
31
33
  import { DrizzleStorageError, sleep, withTransaction } from "./utils";
32
34
 
33
- const DRIZZLE_PROPERTY = "_drizzle";
35
+ export * from "./helper";
36
+
34
37
  const MAX_RETRIES = 5;
35
38
 
36
39
  export type DrizzleStorage<
@@ -67,11 +70,30 @@ export interface DrizzleStorageOptions<
67
70
  TSchema extends
68
71
  TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
69
72
  > {
73
+ /**
74
+ * The Drizzle database instance.
75
+ */
70
76
  db: PgDatabase<TQueryResult, TFullSchema, TSchema>;
77
+ /**
78
+ * Whether to persist the indexer's state. Defaults to true.
79
+ */
71
80
  persistState?: boolean;
81
+ /**
82
+ * The name of the indexer. Default value is 'default'.
83
+ */
72
84
  indexerName?: string;
85
+ /**
86
+ * The schema of the database.
87
+ */
73
88
  schema?: Record<string, unknown>;
89
+ /**
90
+ * The column to use as the id. Defaults to 'id'.
91
+ */
74
92
  idColumn?: string;
93
+ /**
94
+ * The options for the database migration. When provided, the database will automatically run migrations before the indexer runs.
95
+ */
96
+ migrate?: MigrateOptions;
75
97
  }
76
98
 
77
99
  /**
@@ -83,6 +105,7 @@ export interface DrizzleStorageOptions<
83
105
  * @param options.indexerName - The name of the indexer. Defaults value is 'default'.
84
106
  * @param options.schema - The schema of the database.
85
107
  * @param options.idColumn - The column to use as the id. Defaults to 'id'.
108
+ * @param options.migrate - The options for the database migration. when provided, the database will automatically run migrations before the indexer runs.
86
109
  */
87
110
  export function drizzleStorage<
88
111
  TFilter,
@@ -97,6 +120,7 @@ export function drizzleStorage<
97
120
  indexerName: identifier = "default",
98
121
  schema,
99
122
  idColumn = "id",
123
+ migrate: migrateOptions,
100
124
  }: DrizzleStorageOptions<TQueryResult, TFullSchema, TSchema>) {
101
125
  return defineIndexerPlugin<TFilter, TBlock>((indexer) => {
102
126
  let tableNames: string[] = [];
@@ -113,15 +137,31 @@ export function drizzleStorage<
113
137
  }
114
138
 
115
139
  indexer.hooks.hook("run:before", async () => {
140
+ const internalContext = useInternalContext();
141
+ const context = useIndexerContext();
142
+ const logger = useLogger();
143
+
144
+ // For testing purposes using vcr.
145
+ context[DRIZZLE_STORAGE_DB_PROPERTY] = db;
146
+
116
147
  const { indexerName: indexerFileName, availableIndexers } =
117
- useInternalContext();
148
+ internalContext;
118
149
 
119
150
  indexerId = generateIndexerId(indexerFileName, identifier);
120
151
 
121
152
  let retries = 0;
122
153
 
154
+ // incase the migrations are already applied, we don't want to run them again
155
+ let migrationsApplied = false;
156
+
123
157
  while (retries <= MAX_RETRIES) {
124
158
  try {
159
+ if (migrateOptions && !migrationsApplied) {
160
+ // @ts-ignore type mismatch for db
161
+ await migrate(db, migrateOptions);
162
+ migrationsApplied = true;
163
+ logger.success("Migrations applied");
164
+ }
125
165
  await withTransaction(db, async (tx) => {
126
166
  await initializeReorgRollbackTable(tx, indexerId);
127
167
  if (enablePersistence) {
@@ -131,6 +171,9 @@ export function drizzleStorage<
131
171
  break;
132
172
  } catch (error) {
133
173
  if (retries === MAX_RETRIES) {
174
+ if (error instanceof DrizzleStorageError) {
175
+ throw error;
176
+ }
134
177
  throw new DrizzleStorageError(
135
178
  "Initialization failed after 5 retries",
136
179
  {
package/src/testing.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { VcrResult } from "@apibara/indexer/testing";
2
+ import type { PgDatabase, PgQueryResultHKT } from "drizzle-orm/pg-core";
3
+ import { DRIZZLE_STORAGE_DB_PROPERTY } from "./constants";
4
+
5
+ export function getTestDatabase(context: VcrResult) {
6
+ const db = context[DRIZZLE_STORAGE_DB_PROPERTY];
7
+
8
+ if (!db) {
9
+ throw new Error("Drizzle database not found in context");
10
+ }
11
+
12
+ return db as PgDatabase<PgQueryResultHKT>;
13
+ }