@gobing-ai/ts-db 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.
Files changed (55) hide show
  1. package/README.md +326 -0
  2. package/dist/adapter.d.ts +86 -0
  3. package/dist/adapter.d.ts.map +1 -0
  4. package/dist/adapter.js +18 -0
  5. package/dist/adapters/bun-sqlite.d.ts +40 -0
  6. package/dist/adapters/bun-sqlite.d.ts.map +1 -0
  7. package/dist/adapters/bun-sqlite.js +70 -0
  8. package/dist/adapters/d1.d.ts +48 -0
  9. package/dist/adapters/d1.d.ts.map +1 -0
  10. package/dist/adapters/d1.js +45 -0
  11. package/dist/base-dao.d.ts +27 -0
  12. package/dist/base-dao.d.ts.map +1 -0
  13. package/dist/base-dao.js +34 -0
  14. package/dist/embedded-migrations.d.ts +15 -0
  15. package/dist/embedded-migrations.d.ts.map +1 -0
  16. package/dist/embedded-migrations.js +25 -0
  17. package/dist/entity-dao.d.ts +143 -0
  18. package/dist/entity-dao.d.ts.map +1 -0
  19. package/dist/entity-dao.js +218 -0
  20. package/dist/index.d.ts +12 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +10 -0
  23. package/dist/index.js.map +9 -0
  24. package/dist/migrate.d.ts +38 -0
  25. package/dist/migrate.d.ts.map +1 -0
  26. package/dist/migrate.js +131 -0
  27. package/dist/queue-job-dao.d.ts +95 -0
  28. package/dist/queue-job-dao.d.ts.map +1 -0
  29. package/dist/queue-job-dao.js +211 -0
  30. package/dist/schema/common.d.ts +87 -0
  31. package/dist/schema/common.d.ts.map +1 -0
  32. package/dist/schema/common.js +76 -0
  33. package/dist/schema/index.d.ts +3 -0
  34. package/dist/schema/index.d.ts.map +1 -0
  35. package/dist/schema/index.js +2 -0
  36. package/dist/schema/queue-jobs.d.ts +225 -0
  37. package/dist/schema/queue-jobs.d.ts.map +1 -0
  38. package/dist/schema/queue-jobs.js +18 -0
  39. package/dist/span-context.d.ts +2 -0
  40. package/dist/span-context.d.ts.map +1 -0
  41. package/dist/span-context.js +0 -0
  42. package/package.json +47 -0
  43. package/src/adapter.ts +109 -0
  44. package/src/adapters/bun-sqlite.ts +108 -0
  45. package/src/adapters/d1.ts +76 -0
  46. package/src/base-dao.ts +37 -0
  47. package/src/embedded-migrations.ts +32 -0
  48. package/src/entity-dao.ts +290 -0
  49. package/src/index.ts +19 -0
  50. package/src/migrate.ts +163 -0
  51. package/src/queue-job-dao.ts +317 -0
  52. package/src/schema/common.ts +94 -0
  53. package/src/schema/index.ts +2 -0
  54. package/src/schema/queue-jobs.ts +23 -0
  55. package/src/span-context.ts +1 -0
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Abstract base DAO providing transaction and timestamp utilities to all entity DAOs.
3
+ */
4
+ export class BaseDao {
5
+ db;
6
+ /**
7
+ * DB transaction utility for subclasses.
8
+ *
9
+ * Constructor is `protected` — instantiate through concrete DAO subclasses,
10
+ * not BaseDao directly. Tests must declare an explicit public constructor
11
+ * that calls `super(db)` to expose the protected constructor publicly.
12
+ */
13
+ constructor(db) {
14
+ this.db = db;
15
+ }
16
+ now() {
17
+ return Date.now();
18
+ }
19
+ /**
20
+ * Execute a function within a database transaction.
21
+ *
22
+ * Works uniformly on both D1 (async) and bun:sqlite (sync wrapped in promise).
23
+ * The callback receives a transaction-scoped DbClient.
24
+ *
25
+ * @param fn - Function to execute within the transaction.
26
+ * @returns The return value of `fn`.
27
+ */
28
+ async withTransaction(fn) {
29
+ // Drizzle's .transaction() works on both backends:
30
+ // - bun:sqlite: sync wrapped in a promise
31
+ // - D1: native async
32
+ return this.db.transaction(async (tx) => fn(tx));
33
+ }
34
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Embedded migration SQL — auto-generated from drizzle/ folder.
3
+ *
4
+ * This file bundles all migration SQL as inline strings so the compiled
5
+ * binary can apply migrations without needing the drizzle/ folder on disk.
6
+ *
7
+ * DO NOT EDIT MANUALLY. Regenerate with: bun run scripts/embed-migrations.ts
8
+ */
9
+ export interface EmbeddedMigration {
10
+ tag: string;
11
+ sql: string;
12
+ hash: string;
13
+ }
14
+ export declare const embeddedMigrations: EmbeddedMigration[];
15
+ //# sourceMappingURL=embedded-migrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedded-migrations.d.ts","sourceRoot":"","sources":["../src/embedded-migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,iBAAiB;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,kBAAkB,EAAE,iBAAiB,EAgBjD,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Embedded migration SQL — auto-generated from drizzle/ folder.
3
+ *
4
+ * This file bundles all migration SQL as inline strings so the compiled
5
+ * binary can apply migrations without needing the drizzle/ folder on disk.
6
+ *
7
+ * DO NOT EDIT MANUALLY. Regenerate with: bun run scripts/embed-migrations.ts
8
+ */
9
+ export const embeddedMigrations = [
10
+ {
11
+ tag: '0000_init',
12
+ sql: "CREATE TABLE `queue_jobs` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`type` text NOT NULL,\n\t`payload` text NOT NULL,\n\t`status` text DEFAULT 'pending' NOT NULL,\n\t`attempts` integer DEFAULT 0 NOT NULL,\n\t`max_retries` integer DEFAULT 3 NOT NULL,\n\t`created_at` integer NOT NULL,\n\t`updated_at` integer NOT NULL,\n\t`next_retry_at` integer,\n\t`last_error` text,\n\t`processing_at` integer\n);\n",
13
+ hash: '558dea3834348925f79b4d30ca79d0afd0d990b2883341377d369444d50ce76e',
14
+ },
15
+ {
16
+ tag: '0001_salty_red_ghost',
17
+ sql: 'CREATE INDEX `queue_jobs_ready_idx` ON `queue_jobs` (`status`,`next_retry_at`,`created_at`);',
18
+ hash: 'f842da3f49edeec8a17bcab399669410db09996ab258dc4fa781357d0400ddbf',
19
+ },
20
+ {
21
+ tag: '0002_nasty_namora',
22
+ sql: 'ALTER TABLE `queue_jobs` ADD `expires_at` integer;',
23
+ hash: '7380f8c162352a61b15205af5a87e0e7313a499203dae98fe62151a1dc7fec0e',
24
+ },
25
+ ];
@@ -0,0 +1,143 @@
1
+ import { type SQL } from 'drizzle-orm';
2
+ import type { SQLiteColumn, SQLiteTable } from 'drizzle-orm/sqlite-core';
3
+ import type { DbClient } from './adapter';
4
+ import { BaseDao } from './base-dao';
5
+ /**
6
+ * Type for tables compatible with EntityDao.
7
+ * Must have standard columns: createdAt, updatedAt.
8
+ */
9
+ export type EntityTable = SQLiteTable & {
10
+ createdAt: SQLiteColumn;
11
+ updatedAt: SQLiteColumn;
12
+ };
13
+ /**
14
+ * Type for tables with soft delete support.
15
+ */
16
+ export type SoftDeletableTable = EntityTable & {
17
+ inUsed: SQLiteColumn;
18
+ };
19
+ /**
20
+ * Type for primary key columns.
21
+ */
22
+ export type PKColumn = SQLiteColumn;
23
+ /**
24
+ * Generic CRUD base class for entity DAOs.
25
+ *
26
+ * Provides standard create, read, update, delete operations with:
27
+ * - Type-safe public API using Drizzle's inference types
28
+ * - Automatic soft delete filtering (if table has `inUsed` column)
29
+ *
30
+ * @typeParam TTable - The table type (must extend EntityTable)
31
+ * @typeParam TPK - The primary key column type
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * export class UsersDao extends EntityDao<typeof users, typeof users.id> {
36
+ * constructor(db: DbClient) {
37
+ * super(db, users, users.id, 'users');
38
+ * }
39
+ *
40
+ * // Add entity-specific methods here
41
+ * async findByEmail(email: string) {
42
+ * return this.findBy(users.email, email);
43
+ * }
44
+ * }
45
+ * ```
46
+ */
47
+ export declare class EntityDao<TTable extends EntityTable, TPK extends SQLiteColumn> extends BaseDao {
48
+ readonly table: TTable;
49
+ protected readonly primaryKey: TPK;
50
+ protected readonly collectionName: string;
51
+ constructor(db: DbClient, table: TTable, primaryKey: TPK, collectionName: string);
52
+ /**
53
+ * Check if the table has soft delete support (inUsed column).
54
+ */
55
+ protected get hasSoftDelete(): boolean;
56
+ /**
57
+ * Build a where condition that filters out soft-deleted records.
58
+ * Returns undefined if the table doesn't support soft delete.
59
+ */
60
+ protected get activeCondition(): SQL | undefined;
61
+ /**
62
+ * Create a new record.
63
+ *
64
+ * `createdAt` and `updatedAt` are auto-filled if not provided.
65
+ *
66
+ * @param data - The data to insert (createdAt/updatedAt optional).
67
+ * @returns The created record.
68
+ */
69
+ create(data: Omit<TTable['$inferInsert'], 'createdAt' | 'updatedAt'> & {
70
+ createdAt?: number;
71
+ updatedAt?: number;
72
+ }): Promise<TTable['$inferSelect']>;
73
+ /**
74
+ * Find a record by its primary key.
75
+ *
76
+ * @param id - The primary key value.
77
+ * @param includeDeleted - Whether to include soft-deleted records.
78
+ * @returns The record if found, otherwise undefined.
79
+ */
80
+ findById(id: string | number, includeDeleted?: boolean): Promise<TTable['$inferSelect'] | undefined>;
81
+ /**
82
+ * Find all records.
83
+ *
84
+ * @param includeDeleted - Whether to include soft-deleted records.
85
+ * @returns Array of records.
86
+ */
87
+ findAll(includeDeleted?: boolean): Promise<TTable['$inferSelect'][]>;
88
+ /**
89
+ * Update a record by its primary key.
90
+ *
91
+ * @param id - The primary key value.
92
+ * @param data - The data to update.
93
+ * @returns The updated record if found, otherwise undefined.
94
+ */
95
+ update(id: string | number, data: Partial<TTable['$inferInsert']>): Promise<TTable['$inferSelect'] | undefined>;
96
+ /**
97
+ * Delete a record by its primary key.
98
+ *
99
+ * @param id - The primary key value.
100
+ * @param soft - Whether to perform a soft delete (default: true if table supports it).
101
+ * @returns The deleted record (for soft delete), otherwise undefined.
102
+ */
103
+ delete(id: string | number, soft?: boolean): Promise<TTable['$inferSelect'] | undefined>;
104
+ /**
105
+ * Find a record by a specific column value.
106
+ *
107
+ * @param column - The column to search.
108
+ * @param value - The value to match.
109
+ * @param includeDeleted - Whether to include soft-deleted records.
110
+ * @returns The record if found, otherwise undefined.
111
+ */
112
+ findBy<TCol extends SQLiteColumn>(column: TCol, value: TCol['_']['data'], includeDeleted?: boolean): Promise<TTable['$inferSelect'] | undefined>;
113
+ /**
114
+ * Find all records matching a specific column value.
115
+ *
116
+ * @param column - The column to search.
117
+ * @param value - The value to match.
118
+ * @param includeDeleted - Whether to include soft-deleted records.
119
+ * @returns Array of matching records.
120
+ */
121
+ findAllBy<TCol extends SQLiteColumn>(column: TCol, value: TCol['_']['data'], includeDeleted?: boolean): Promise<TTable['$inferSelect'][]>;
122
+ /**
123
+ * List records with pagination and optional filtering.
124
+ *
125
+ * @param options - List options (limit, offset, where).
126
+ * @returns Array of records.
127
+ */
128
+ list(options?: {
129
+ limit?: number;
130
+ offset?: number;
131
+ where?: SQL;
132
+ includeDeleted?: boolean;
133
+ }): Promise<TTable['$inferSelect'][]>;
134
+ /**
135
+ * Count records in the table.
136
+ *
137
+ * @param where - Optional filter condition.
138
+ * @param includeDeleted - Whether to include soft-deleted records.
139
+ * @returns The count of matching records.
140
+ */
141
+ count(where?: SQL, includeDeleted?: boolean): Promise<number>;
142
+ }
143
+ //# sourceMappingURL=entity-dao.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-dao.d.ts","sourceRoot":"","sources":["../src/entity-dao.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,218 @@
1
+ import { and, count as countFn, eq } from 'drizzle-orm';
2
+ import { BaseDao } from './base-dao.js';
3
+ /**
4
+ * Generic CRUD base class for entity DAOs.
5
+ *
6
+ * Provides standard create, read, update, delete operations with:
7
+ * - Type-safe public API using Drizzle's inference types
8
+ * - Automatic soft delete filtering (if table has `inUsed` column)
9
+ *
10
+ * @typeParam TTable - The table type (must extend EntityTable)
11
+ * @typeParam TPK - The primary key column type
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * export class UsersDao extends EntityDao<typeof users, typeof users.id> {
16
+ * constructor(db: DbClient) {
17
+ * super(db, users, users.id, 'users');
18
+ * }
19
+ *
20
+ * // Add entity-specific methods here
21
+ * async findByEmail(email: string) {
22
+ * return this.findBy(users.email, email);
23
+ * }
24
+ * }
25
+ * ```
26
+ */
27
+ export class EntityDao extends BaseDao {
28
+ table;
29
+ primaryKey;
30
+ collectionName;
31
+ constructor(db, table, primaryKey, collectionName) {
32
+ super(db);
33
+ this.table = table;
34
+ this.primaryKey = primaryKey;
35
+ this.collectionName = collectionName;
36
+ }
37
+ /**
38
+ * Check if the table has soft delete support (inUsed column).
39
+ */
40
+ get hasSoftDelete() {
41
+ return 'inUsed' in this.table;
42
+ }
43
+ /**
44
+ * Build a where condition that filters out soft-deleted records.
45
+ * Returns undefined if the table doesn't support soft delete.
46
+ */
47
+ get activeCondition() {
48
+ if (this.hasSoftDelete) {
49
+ return eq(this.table.inUsed, 1);
50
+ }
51
+ return undefined;
52
+ }
53
+ /**
54
+ * Create a new record.
55
+ *
56
+ * `createdAt` and `updatedAt` are auto-filled if not provided.
57
+ *
58
+ * @param data - The data to insert (createdAt/updatedAt optional).
59
+ * @returns The created record.
60
+ */
61
+ async create(data) {
62
+ const now = this.now();
63
+ const record = {
64
+ ...data,
65
+ createdAt: data.createdAt ?? now,
66
+ updatedAt: data.updatedAt ?? now,
67
+ };
68
+ await this.db.insert(this.table).values(record);
69
+ return record;
70
+ }
71
+ /**
72
+ * Find a record by its primary key.
73
+ *
74
+ * @param id - The primary key value.
75
+ * @param includeDeleted - Whether to include soft-deleted records.
76
+ * @returns The record if found, otherwise undefined.
77
+ */
78
+ async findById(id, includeDeleted = false) {
79
+ const conditions = [eq(this.primaryKey, id)];
80
+ if (!includeDeleted && this.activeCondition) {
81
+ conditions.push(this.activeCondition);
82
+ }
83
+ const result = await this.db
84
+ .select()
85
+ .from(this.table)
86
+ .where(and(...conditions));
87
+ return result[0];
88
+ }
89
+ /**
90
+ * Find all records.
91
+ *
92
+ * @param includeDeleted - Whether to include soft-deleted records.
93
+ * @returns Array of records.
94
+ */
95
+ async findAll(includeDeleted = false) {
96
+ const query = this.db.select().from(this.table);
97
+ if (!includeDeleted && this.activeCondition) {
98
+ return query.where(this.activeCondition);
99
+ }
100
+ return query;
101
+ }
102
+ /**
103
+ * Update a record by its primary key.
104
+ *
105
+ * @param id - The primary key value.
106
+ * @param data - The data to update.
107
+ * @returns The updated record if found, otherwise undefined.
108
+ */
109
+ async update(id, data) {
110
+ const now = this.now();
111
+ const updateData = {
112
+ ...data,
113
+ updatedAt: now,
114
+ };
115
+ await this.db.update(this.table).set(updateData).where(eq(this.primaryKey, id));
116
+ return this.findById(id);
117
+ }
118
+ /**
119
+ * Delete a record by its primary key.
120
+ *
121
+ * @param id - The primary key value.
122
+ * @param soft - Whether to perform a soft delete (default: true if table supports it).
123
+ * @returns The deleted record (for soft delete), otherwise undefined.
124
+ */
125
+ async delete(id, soft) {
126
+ const useSoftDelete = soft ?? this.hasSoftDelete;
127
+ if (useSoftDelete && this.hasSoftDelete) {
128
+ const now = this.now();
129
+ await this.db
130
+ .update(this.table)
131
+ .set({ inUsed: 0, updatedAt: now })
132
+ .where(eq(this.primaryKey, id));
133
+ return this.findById(id, true);
134
+ }
135
+ await this.db.delete(this.table).where(eq(this.primaryKey, id));
136
+ return undefined;
137
+ }
138
+ /**
139
+ * Find a record by a specific column value.
140
+ *
141
+ * @param column - The column to search.
142
+ * @param value - The value to match.
143
+ * @param includeDeleted - Whether to include soft-deleted records.
144
+ * @returns The record if found, otherwise undefined.
145
+ */
146
+ async findBy(column, value, includeDeleted = false) {
147
+ const conditions = [eq(column, value)];
148
+ if (!includeDeleted && this.activeCondition) {
149
+ conditions.push(this.activeCondition);
150
+ }
151
+ const result = await this.db
152
+ .select()
153
+ .from(this.table)
154
+ .where(and(...conditions));
155
+ return result[0];
156
+ }
157
+ /**
158
+ * Find all records matching a specific column value.
159
+ *
160
+ * @param column - The column to search.
161
+ * @param value - The value to match.
162
+ * @param includeDeleted - Whether to include soft-deleted records.
163
+ * @returns Array of matching records.
164
+ */
165
+ async findAllBy(column, value, includeDeleted = false) {
166
+ const conditions = [eq(column, value)];
167
+ if (!includeDeleted && this.activeCondition) {
168
+ conditions.push(this.activeCondition);
169
+ }
170
+ return this.db
171
+ .select()
172
+ .from(this.table)
173
+ .where(and(...conditions));
174
+ }
175
+ /**
176
+ * List records with pagination and optional filtering.
177
+ *
178
+ * @param options - List options (limit, offset, where).
179
+ * @returns Array of records.
180
+ */
181
+ async list(options = {}) {
182
+ const { limit = 100, offset = 0, where, includeDeleted = false } = options;
183
+ const conditions = [];
184
+ if (!includeDeleted && this.activeCondition) {
185
+ conditions.push(this.activeCondition);
186
+ }
187
+ if (where) {
188
+ conditions.push(where);
189
+ }
190
+ const query = this.db.select().from(this.table);
191
+ if (conditions.length > 0) {
192
+ return query
193
+ .where(and(...conditions))
194
+ .limit(limit)
195
+ .offset(offset);
196
+ }
197
+ return query.limit(limit).offset(offset);
198
+ }
199
+ /**
200
+ * Count records in the table.
201
+ *
202
+ * @param where - Optional filter condition.
203
+ * @param includeDeleted - Whether to include soft-deleted records.
204
+ * @returns The count of matching records.
205
+ */
206
+ async count(where, includeDeleted = false) {
207
+ const conditions = [];
208
+ if (!includeDeleted && this.activeCondition) {
209
+ conditions.push(this.activeCondition);
210
+ }
211
+ if (where) {
212
+ conditions.push(where);
213
+ }
214
+ const query = this.db.select({ value: countFn() }).from(this.table);
215
+ const result = conditions.length > 0 ? await query.where(and(...conditions)) : await query;
216
+ return result[0]?.value ?? 0;
217
+ }
218
+ }
@@ -0,0 +1,12 @@
1
+ export { createDbAdapter, type DbAdapter, type DbAdapterConfig, type DbClient, type DbTable } from './adapter';
2
+ export { BunSqliteAdapter, type BunSqliteOptions } from './adapters/bun-sqlite';
3
+ export { D1Adapter } from './adapters/d1';
4
+ export { BaseDao } from './base-dao';
5
+ export { type EmbeddedMigration, embeddedMigrations } from './embedded-migrations';
6
+ export { EntityDao, type EntityTable, type PKColumn, type SoftDeletableTable } from './entity-dao';
7
+ export { applyMigrations, type MigrationOptions } from './migrate';
8
+ export { QueueJobDao, type QueueJobRecord, type QueueStats } from './queue-job-dao';
9
+ export { appendOnlyColumns, buildAppendOnlyColumns, buildStandardColumns, buildStandardColumnsWithSoftDelete, nowTimestamp, standardColumns, standardColumnsWithSoftDelete, } from './schema/common';
10
+ export { queueJobs } from './schema/queue-jobs';
11
+ export type { SpanContext } from './span-context';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAC/G,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,KAAK,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACnG,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACpF,OAAO,EACH,iBAAiB,EACjB,sBAAsB,EACtB,oBAAoB,EACpB,kCAAkC,EAClC,YAAY,EACZ,eAAe,EACf,6BAA6B,GAChC,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ export { createDbAdapter } from './adapter.js';
2
+ export { BunSqliteAdapter } from './adapters/bun-sqlite.js';
3
+ export { D1Adapter } from './adapters/d1.js';
4
+ export { BaseDao } from './base-dao.js';
5
+ export { embeddedMigrations } from './embedded-migrations.js';
6
+ export { EntityDao } from './entity-dao.js';
7
+ export { applyMigrations } from './migrate.js';
8
+ export { QueueJobDao } from './queue-job-dao.js';
9
+ export { appendOnlyColumns, buildAppendOnlyColumns, buildStandardColumns, buildStandardColumnsWithSoftDelete, nowTimestamp, standardColumns, standardColumnsWithSoftDelete, } from './schema/common.js';
10
+ export { queueJobs } from './schema/queue-jobs.js';
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [
5
+ ],
6
+ "mappings": "",
7
+ "debugId": "8E90F1FD5BA8710F64756E2164756E21",
8
+ "names": []
9
+ }
@@ -0,0 +1,38 @@
1
+ import type { FileSystem } from '@gobing-ai/ts-runtime';
2
+ import type { DbAdapter } from './adapter';
3
+ /**
4
+ * Find project root by walking up looking for bun.lock.
5
+ * @deprecated Use `FileSystem.getProjectRoot()` instead.
6
+ * @internal — only used for backward compatibility.
7
+ */
8
+ /** @internal */
9
+ export declare function findProjectRoot(_startDir: string): string;
10
+ /**
11
+ * Options for configuring migration behaviour (folder path, table name).
12
+ */
13
+ export interface MigrationOptions {
14
+ /** Path to migration SQL files. Default: `fs.resolve('drizzle')` */
15
+ migrationsFolder?: string;
16
+ /** Name of the migrations tracking table. Default: '__drizzle_migrations' */
17
+ migrationsTable?: string;
18
+ /** File system abstraction for path resolution. */
19
+ fs?: FileSystem;
20
+ }
21
+ /**
22
+ * Apply pending migrations using drizzle-orm's built-in migrator.
23
+ *
24
+ * Tracks applied migrations in the `__drizzle_migrations` table.
25
+ * Safe to call on every startup — already-applied migrations are skipped.
26
+ *
27
+ * Two migration strategies:
28
+ * 1. **File-based** (preferred): reads SQL from a `drizzle/` folder on disk.
29
+ * 2. **Embedded** (fallback): uses SQL bundled in the binary when no folder exists.
30
+ *
31
+ * Only works with BunSqliteAdapter. D1 migrations should use
32
+ * `wrangler d1 migrations apply` instead.
33
+ *
34
+ * @param adapter - A DbAdapter instance (must be BunSqliteAdapter).
35
+ * @param options - Optional migration folder and table name overrides.
36
+ */
37
+ export declare function applyMigrations(adapter: DbAdapter, options?: MigrationOptions): Promise<void>;
38
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG3C;;;;GAIG;AACH,gBAAgB;AAChB,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mDAAmD;IACnD,EAAE,CAAC,EAAE,UAAU,CAAC;CACnB;AA0ED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8CnG"}
@@ -0,0 +1,131 @@
1
+ import { resolve } from 'node:path';
2
+ import { embeddedMigrations } from './embedded-migrations.js';
3
+ /**
4
+ * Find project root by walking up looking for bun.lock.
5
+ * @deprecated Use `FileSystem.getProjectRoot()` instead.
6
+ * @internal — only used for backward compatibility.
7
+ */
8
+ /** @internal */
9
+ export function findProjectRoot(_startDir) {
10
+ return process.cwd();
11
+ }
12
+ /**
13
+ * Ensure the migration tracking table exists with proper SQLite types.
14
+ *
15
+ * drizzle-orm 0.45 generates `id SERIAL PRIMARY KEY` for the journal table,
16
+ * but SQLite doesn't recognize SERIAL as auto-increment. Pre-create with
17
+ * proper syntax so drizzle-orm's `CREATE TABLE IF NOT EXISTS` skips it.
18
+ */
19
+ async function ensureJournalTable(adapter, table) {
20
+ await adapter.exec(`CREATE TABLE IF NOT EXISTS "${table}" (` +
21
+ 'id INTEGER PRIMARY KEY AUTOINCREMENT, ' +
22
+ 'hash text NOT NULL, ' +
23
+ 'created_at numeric' +
24
+ ')');
25
+ }
26
+ function validateMigrationTableName(table) {
27
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table)) {
28
+ throw new Error(`Invalid migration journal table name: ${table}`);
29
+ }
30
+ return table;
31
+ }
32
+ /**
33
+ * Apply migrations from embedded SQL strings (for compiled binaries).
34
+ *
35
+ * Checks the journal table and applies only migrations that haven't run yet.
36
+ * Each migration is executed with adapter.exec() for file-based or adapter.run() for journal tracking.
37
+ */
38
+ async function applyEmbeddedMigrations(adapter, journalTable) {
39
+ // Validate journal table name — this is an internal constant, never user input.
40
+ if (!/^__[a-z_]+$/.test(journalTable)) {
41
+ throw new Error(`Invalid migration journal table name: ${journalTable}`);
42
+ }
43
+ // Get already-applied hashes
44
+ const appliedHashes = new Set();
45
+ try {
46
+ const rows = await adapter.queryAll(`SELECT hash FROM "${journalTable}"`);
47
+ for (const row of rows) {
48
+ appliedHashes.add(row.hash);
49
+ }
50
+ }
51
+ catch {
52
+ // Journal table may not exist yet — will be created by ensureJournalTable
53
+ }
54
+ let applied = 0;
55
+ for (const migration of embeddedMigrations) {
56
+ if (appliedHashes.has(migration.hash))
57
+ continue;
58
+ console.info(`Applying embedded migration: ${migration.tag}`);
59
+ // Split on semicolons and execute each non-empty statement
60
+ const statements = migration.sql
61
+ .split(';')
62
+ .map((s) => s.trim())
63
+ .filter((s) => s.length > 0);
64
+ for (const stmt of statements) {
65
+ await adapter.exec(stmt);
66
+ }
67
+ // Record in journal
68
+ await adapter.run(`INSERT INTO "${journalTable}" (hash, created_at) VALUES (?, ?)`, migration.hash, Date.now());
69
+ applied++;
70
+ }
71
+ if (applied > 0) {
72
+ console.info(`Applied ${applied} embedded migration(s)`);
73
+ }
74
+ }
75
+ /**
76
+ * Apply pending migrations using drizzle-orm's built-in migrator.
77
+ *
78
+ * Tracks applied migrations in the `__drizzle_migrations` table.
79
+ * Safe to call on every startup — already-applied migrations are skipped.
80
+ *
81
+ * Two migration strategies:
82
+ * 1. **File-based** (preferred): reads SQL from a `drizzle/` folder on disk.
83
+ * 2. **Embedded** (fallback): uses SQL bundled in the binary when no folder exists.
84
+ *
85
+ * Only works with BunSqliteAdapter. D1 migrations should use
86
+ * `wrangler d1 migrations apply` instead.
87
+ *
88
+ * @param adapter - A DbAdapter instance (must be BunSqliteAdapter).
89
+ * @param options - Optional migration folder and table name overrides.
90
+ */
91
+ export async function applyMigrations(adapter, options) {
92
+ const { BunSqliteAdapter } = await import('./adapters/bun-sqlite.js');
93
+ if (!(adapter instanceof BunSqliteAdapter)) {
94
+ console.warn('Skipping in-app migrations: only supported for bun-sqlite adapter');
95
+ return;
96
+ }
97
+ const table = validateMigrationTableName(options?.migrationsTable ?? '__drizzle_migrations');
98
+ await ensureJournalTable(adapter, table);
99
+ const folder = options?.migrationsFolder ?? resolve(findProjectRoot(process.cwd()), 'drizzle');
100
+ // File-based migrations: attempt if drizzle/ folder is accessible.
101
+ // Use FileSystem.exists when available, otherwise try and fall back to embedded.
102
+ const fs = options?.fs;
103
+ const tryFileBased = fs?.exists(folder) ?? true; // optimistic when no fs
104
+ if (tryFileBased) {
105
+ try {
106
+ const { migrate: drizzleMigrate } = await import('drizzle-orm/bun-sqlite/migrator');
107
+ console.info(`Applying database migrations from ${folder}`);
108
+ await drizzleMigrate(adapter.getDrizzleDb(), {
109
+ migrationsFolder: folder,
110
+ ...(options?.migrationsTable !== undefined ? { migrationsTable: options.migrationsTable } : {}),
111
+ });
112
+ console.info('Database migrations complete');
113
+ return;
114
+ }
115
+ catch (error) {
116
+ // If folder doesn't exist, fall through to embedded migrations.
117
+ // Any other error should be thrown.
118
+ const message = error instanceof Error ? error.message : String(error);
119
+ if (message.includes('journal') || message.includes('ENOENT') || message.includes('meta')) {
120
+ console.info(`File-based migrations unavailable, using embedded: ${message}`);
121
+ }
122
+ else {
123
+ console.error(`[MIGRATE] drizzleMigrate failed: ${message}`);
124
+ throw error;
125
+ }
126
+ }
127
+ }
128
+ // Fallback: embedded migrations (for compiled binaries)
129
+ console.info('No drizzle/ folder found — applying embedded migrations');
130
+ await applyEmbeddedMigrations(adapter, table);
131
+ }