@gobing-ai/ts-db 0.1.8 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -21
- package/dist/adapter.d.ts +26 -46
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapters/bun-sqlite.d.ts +3 -2
- package/dist/adapters/bun-sqlite.d.ts.map +1 -1
- package/dist/adapters/bun-sqlite.js +2 -1
- package/dist/adapters/d1.d.ts +7 -2
- package/dist/adapters/d1.d.ts.map +1 -1
- package/dist/adapters/d1.js +6 -1
- package/dist/base-dao.d.ts +36 -16
- package/dist/base-dao.d.ts.map +1 -1
- package/dist/base-dao.js +46 -20
- package/dist/define-table.d.ts +40 -0
- package/dist/define-table.d.ts.map +1 -0
- package/dist/define-table.js +36 -0
- package/dist/entity-dao.d.ts +99 -96
- package/dist/entity-dao.d.ts.map +1 -1
- package/dist/entity-dao.js +145 -170
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/query-spec.d.ts +57 -0
- package/dist/query-spec.d.ts.map +1 -0
- package/dist/query-spec.js +40 -0
- package/dist/queue-job-dao.d.ts +2 -2
- package/dist/queue-job-dao.d.ts.map +1 -1
- package/dist/queue-job-dao.js +3 -3
- package/package.json +17 -5
- package/src/adapter.ts +27 -54
- package/src/adapters/bun-sqlite.ts +4 -3
- package/src/adapters/d1.ts +9 -3
- package/src/base-dao.ts +62 -20
- package/src/define-table.ts +66 -0
- package/src/entity-dao.ts +258 -199
- package/src/index.ts +23 -3
- package/src/query-spec.ts +84 -0
- package/src/queue-job-dao.ts +4 -4
package/dist/entity-dao.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type SQL } from 'drizzle-orm';
|
|
2
2
|
import type { SQLiteColumn, SQLiteTable } from 'drizzle-orm/sqlite-core';
|
|
3
|
-
import type {
|
|
3
|
+
import type { DbAdapter } from './adapter';
|
|
4
4
|
import { BaseDao } from './base-dao';
|
|
5
|
+
import { type OrderTerm, type Predicate } from './query-spec';
|
|
5
6
|
/**
|
|
6
7
|
* Type for tables compatible with EntityDao.
|
|
7
8
|
* Must have standard columns: createdAt, updatedAt.
|
|
@@ -10,134 +11,136 @@ export type EntityTable = SQLiteTable & {
|
|
|
10
11
|
createdAt: SQLiteColumn;
|
|
11
12
|
updatedAt: SQLiteColumn;
|
|
12
13
|
};
|
|
13
|
-
/**
|
|
14
|
-
* Type for tables with soft delete support.
|
|
15
|
-
*/
|
|
14
|
+
/** Type for tables with soft delete support. */
|
|
16
15
|
export type SoftDeletableTable = EntityTable & {
|
|
17
16
|
inUsed: SQLiteColumn;
|
|
18
17
|
};
|
|
19
|
-
/**
|
|
20
|
-
* Type for primary key columns.
|
|
21
|
-
*/
|
|
18
|
+
/** Type for primary key columns (single column or a tuple for composite keys). */
|
|
22
19
|
export type PKColumn = SQLiteColumn;
|
|
20
|
+
/** A primary key value: a single value, or a tuple for composite keys. */
|
|
21
|
+
export type PKValue = string | number | (string | number)[];
|
|
22
|
+
/** Options for the structured `list` operation. */
|
|
23
|
+
export interface EntityListSpec {
|
|
24
|
+
where?: Predicate;
|
|
25
|
+
orderBy?: readonly OrderTerm[];
|
|
26
|
+
limit?: number;
|
|
27
|
+
offset?: number;
|
|
28
|
+
includeDeleted?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Options for keyset/cursor pagination. */
|
|
31
|
+
export interface CursorListSpec {
|
|
32
|
+
/** Opaque cursor from a previous page (the last row's keyset value). */
|
|
33
|
+
cursor?: string | number;
|
|
34
|
+
/** Column the cursor walks (must be unique + ordered, e.g. the primary key). */
|
|
35
|
+
cursorColumn: SQLiteColumn;
|
|
36
|
+
/** Page size. */
|
|
37
|
+
limit: number;
|
|
38
|
+
where?: Predicate;
|
|
39
|
+
direction?: 'asc' | 'desc';
|
|
40
|
+
includeDeleted?: boolean;
|
|
41
|
+
}
|
|
23
42
|
/**
|
|
24
|
-
* Generic CRUD base class for entity DAOs.
|
|
43
|
+
* Generic CRUD base class for entity DAOs — the STRUCTURED tier of the facade.
|
|
25
44
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* -
|
|
45
|
+
* Extends {@link BaseDao} (raw tier) with typed create/read/update/delete over a
|
|
46
|
+
* single table, using RETURNING, drizzle-free predicate filters, soft-delete
|
|
47
|
+
* auto-filtering, batch insert, upsert, and cursor pagination. drizzle is hidden
|
|
48
|
+
* entirely — consumers use ts-db vocabulary. (G1/G3)
|
|
29
49
|
*
|
|
30
|
-
* @typeParam TTable - The table type (must extend EntityTable)
|
|
31
|
-
* @typeParam TPK - The primary key column type
|
|
50
|
+
* @typeParam TTable - The table type (must extend EntityTable).
|
|
51
|
+
* @typeParam TPK - The primary key column type.
|
|
32
52
|
*
|
|
33
53
|
* @example
|
|
34
54
|
* ```ts
|
|
35
|
-
*
|
|
36
|
-
* constructor(
|
|
37
|
-
* super(
|
|
55
|
+
* class UsersDao extends EntityDao<typeof users, typeof users.id> {
|
|
56
|
+
* constructor(adapter: DbAdapter) {
|
|
57
|
+
* super(adapter, users, [users.id], 'users');
|
|
38
58
|
* }
|
|
39
|
-
*
|
|
40
|
-
* // Add entity-specific methods here
|
|
41
|
-
* async findByEmail(email: string) {
|
|
59
|
+
* findByEmail(email: string) {
|
|
42
60
|
* return this.findBy(users.email, email);
|
|
43
61
|
* }
|
|
44
62
|
* }
|
|
45
63
|
* ```
|
|
46
64
|
*/
|
|
65
|
+
/**
|
|
66
|
+
* A structural validator (e.g. a zod schema). Kept structural so EntityDao never
|
|
67
|
+
* imports zod/drizzle-zod — consumers wire `defineTable().insertSchema` here when
|
|
68
|
+
* they want boundary validation.
|
|
69
|
+
*/
|
|
70
|
+
export interface DaoValidator {
|
|
71
|
+
parse(input: unknown): unknown;
|
|
72
|
+
}
|
|
73
|
+
/** Optional EntityDao configuration. */
|
|
74
|
+
export interface EntityDaoOptions {
|
|
75
|
+
/** Schema used to validate input before `create`/`createMany`/`upsert`. */
|
|
76
|
+
insertSchema?: DaoValidator;
|
|
77
|
+
/** Schema used to validate input before `update`. Falls back to `insertSchema` when absent. */
|
|
78
|
+
updateSchema?: DaoValidator;
|
|
79
|
+
/** Which write operations validate. Default: all (`create`, `createMany`, `upsert`, `update`) when a schema is present. */
|
|
80
|
+
validateOn?: ('create' | 'createMany' | 'upsert' | 'update')[];
|
|
81
|
+
}
|
|
47
82
|
export declare class EntityDao<TTable extends EntityTable, TPK extends SQLiteColumn> extends BaseDao {
|
|
48
83
|
readonly table: TTable;
|
|
49
|
-
|
|
84
|
+
/** Primary key columns. A single-element array for single-PK tables, multiple for composite. */
|
|
85
|
+
protected readonly primaryKey: SQLiteColumn[];
|
|
50
86
|
protected readonly collectionName: string;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
87
|
+
private readonly validation;
|
|
88
|
+
constructor(adapter: DbAdapter, table: TTable, primaryKey: TPK | SQLiteColumn[], collectionName: string, options?: EntityDaoOptions);
|
|
89
|
+
/** Validate input against the configured schema for an operation, when enabled. */
|
|
90
|
+
private validate;
|
|
91
|
+
/** Check if the table has soft delete support (inUsed column). */
|
|
55
92
|
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
|
-
*/
|
|
93
|
+
/** Condition filtering out soft-deleted rows, or undefined when unsupported. */
|
|
60
94
|
protected get activeCondition(): SQL | undefined;
|
|
95
|
+
/** Build the where-condition matching a primary key value (single or composite). */
|
|
96
|
+
private pkCondition;
|
|
97
|
+
private get insertBuilder();
|
|
61
98
|
/**
|
|
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.
|
|
99
|
+
* Create a new record. `createdAt`/`updatedAt` are auto-filled if absent.
|
|
100
|
+
* Returns the row as written by the database (RETURNING).
|
|
68
101
|
*/
|
|
69
102
|
create(data: Omit<TTable['$inferInsert'], 'createdAt' | 'updatedAt'> & {
|
|
70
103
|
createdAt?: number;
|
|
71
104
|
updatedAt?: number;
|
|
72
105
|
}): Promise<TTable['$inferSelect']>;
|
|
73
106
|
/**
|
|
74
|
-
*
|
|
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.
|
|
107
|
+
* Insert many records in a single multi-VALUES statement (efficient for ETL).
|
|
108
|
+
* Returns the written rows (RETURNING).
|
|
79
109
|
*/
|
|
80
|
-
|
|
110
|
+
createMany(data: (Omit<TTable['$inferInsert'], 'createdAt' | 'updatedAt'> & {
|
|
111
|
+
createdAt?: number;
|
|
112
|
+
updatedAt?: number;
|
|
113
|
+
})[]): Promise<TTable['$inferSelect'][]>;
|
|
81
114
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* @param includeDeleted - Whether to include soft-deleted records.
|
|
85
|
-
* @returns Array of records.
|
|
115
|
+
* Insert or update on conflict. `conflictColumns` identify the unique target;
|
|
116
|
+
* `update` (or all non-conflict columns) are written on conflict. Returns the row.
|
|
86
117
|
*/
|
|
118
|
+
upsert(data: Omit<TTable['$inferInsert'], 'createdAt' | 'updatedAt'> & {
|
|
119
|
+
createdAt?: number;
|
|
120
|
+
updatedAt?: number;
|
|
121
|
+
}, conflictColumns: SQLiteColumn[], updateColumns?: Partial<TTable['$inferInsert']>): Promise<TTable['$inferSelect']>;
|
|
122
|
+
/** Find a record by its primary key (single value or composite tuple). */
|
|
123
|
+
findById(id: PKValue, includeDeleted?: boolean): Promise<TTable['$inferSelect'] | undefined>;
|
|
124
|
+
/** Find all records (optionally including soft-deleted). */
|
|
87
125
|
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
|
-
*/
|
|
126
|
+
/** Find the first record matching a column value. */
|
|
112
127
|
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
|
-
*/
|
|
128
|
+
/** Find all records matching a column value. */
|
|
121
129
|
findAllBy<TCol extends SQLiteColumn>(column: TCol, value: TCol['_']['data'], includeDeleted?: boolean): Promise<TTable['$inferSelect'][]>;
|
|
122
|
-
/**
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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>;
|
|
130
|
+
/** Update a record by primary key. `updatedAt` is refreshed. Returns the updated row. */
|
|
131
|
+
update(id: PKValue, data: Partial<TTable['$inferInsert']>): Promise<TTable['$inferSelect'] | undefined>;
|
|
132
|
+
/** Delete a record by primary key. Soft-deletes when the table supports it (default). */
|
|
133
|
+
delete(id: PKValue, soft?: boolean): Promise<TTable['$inferSelect'] | undefined>;
|
|
134
|
+
/** List records with predicate filter, ordering, and offset pagination. */
|
|
135
|
+
list(spec?: EntityListSpec): Promise<TTable['$inferSelect'][]>;
|
|
136
|
+
/** List one keyset page; returns the rows and the cursor for the next page. */
|
|
137
|
+
listByCursor(spec: CursorListSpec): Promise<{
|
|
138
|
+
rows: TTable['$inferSelect'][];
|
|
139
|
+
nextCursor?: string | number;
|
|
140
|
+
}>;
|
|
141
|
+
/** Count records matching an optional predicate. */
|
|
142
|
+
count(where?: Predicate, includeDeleted?: boolean): Promise<number>;
|
|
143
|
+
/** Combine a caller predicate with the soft-delete active filter. */
|
|
144
|
+
private withActive;
|
|
142
145
|
}
|
|
143
146
|
//# sourceMappingURL=entity-dao.d.ts.map
|
package/dist/entity-dao.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-dao.d.ts","sourceRoot":"","sources":["../src/entity-dao.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,GAAG,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"entity-dao.d.ts","sourceRoot":"","sources":["../src/entity-dao.ts"],"names":[],"mappings":"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"}
|
package/dist/entity-dao.js
CHANGED
|
@@ -1,218 +1,193 @@
|
|
|
1
1
|
import { and, count as countFn, eq } from 'drizzle-orm';
|
|
2
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
|
-
*/
|
|
3
|
+
import { compilePredicate } from './query-spec.js';
|
|
27
4
|
export class EntityDao extends BaseDao {
|
|
28
5
|
table;
|
|
6
|
+
/** Primary key columns. A single-element array for single-PK tables, multiple for composite. */
|
|
29
7
|
primaryKey;
|
|
30
8
|
collectionName;
|
|
31
|
-
|
|
32
|
-
|
|
9
|
+
validation;
|
|
10
|
+
constructor(adapter, table, primaryKey, collectionName, options = {}) {
|
|
11
|
+
super(adapter);
|
|
33
12
|
this.table = table;
|
|
34
|
-
this.primaryKey = primaryKey;
|
|
13
|
+
this.primaryKey = Array.isArray(primaryKey) ? primaryKey : [primaryKey];
|
|
35
14
|
this.collectionName = collectionName;
|
|
15
|
+
this.validation = options;
|
|
36
16
|
}
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
17
|
+
/** Validate input against the configured schema for an operation, when enabled. */
|
|
18
|
+
validate(op, input) {
|
|
19
|
+
// `update` is partial, so it only validates against an explicit partial
|
|
20
|
+
// `updateSchema`; the full insertSchema would reject a partial payload.
|
|
21
|
+
const schema = op === 'update' ? this.validation.updateSchema : this.validation.insertSchema;
|
|
22
|
+
if (schema === undefined)
|
|
23
|
+
return;
|
|
24
|
+
const enabled = this.validation.validateOn ?? ['create', 'createMany', 'upsert', 'update'];
|
|
25
|
+
if (!enabled.includes(op))
|
|
26
|
+
return;
|
|
27
|
+
schema.parse(input);
|
|
28
|
+
}
|
|
29
|
+
/** Check if the table has soft delete support (inUsed column). */
|
|
40
30
|
get hasSoftDelete() {
|
|
41
31
|
return 'inUsed' in this.table;
|
|
42
32
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Build a where condition that filters out soft-deleted records.
|
|
45
|
-
* Returns undefined if the table doesn't support soft delete.
|
|
46
|
-
*/
|
|
33
|
+
/** Condition filtering out soft-deleted rows, or undefined when unsupported. */
|
|
47
34
|
get activeCondition() {
|
|
48
35
|
if (this.hasSoftDelete) {
|
|
49
36
|
return eq(this.table.inUsed, 1);
|
|
50
37
|
}
|
|
51
38
|
return undefined;
|
|
52
39
|
}
|
|
40
|
+
/** Build the where-condition matching a primary key value (single or composite). */
|
|
41
|
+
pkCondition(id) {
|
|
42
|
+
const values = Array.isArray(id) ? id : [id];
|
|
43
|
+
if (values.length !== this.primaryKey.length) {
|
|
44
|
+
throw new Error(`${this.collectionName}: primary key expects ${this.primaryKey.length} value(s), got ${values.length}`);
|
|
45
|
+
}
|
|
46
|
+
const parts = this.primaryKey.map((col, i) => eq(col, values[i]));
|
|
47
|
+
return parts.length === 1 ? parts[0] : and(...parts);
|
|
48
|
+
}
|
|
49
|
+
get insertBuilder() {
|
|
50
|
+
return this.db.insert(this.table);
|
|
51
|
+
}
|
|
53
52
|
/**
|
|
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.
|
|
53
|
+
* Create a new record. `createdAt`/`updatedAt` are auto-filled if absent.
|
|
54
|
+
* Returns the row as written by the database (RETURNING).
|
|
60
55
|
*/
|
|
61
56
|
async create(data) {
|
|
62
57
|
const now = this.now();
|
|
63
|
-
const record = {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
};
|
|
68
|
-
await this.db.insert(this.table).values(record);
|
|
69
|
-
return record;
|
|
58
|
+
const record = { createdAt: now, updatedAt: now, ...data };
|
|
59
|
+
this.validate('create', record);
|
|
60
|
+
const rows = (await this.insertBuilder.values(record).returning());
|
|
61
|
+
return rows[0];
|
|
70
62
|
}
|
|
71
63
|
/**
|
|
72
|
-
*
|
|
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.
|
|
64
|
+
* Insert many records in a single multi-VALUES statement (efficient for ETL).
|
|
65
|
+
* Returns the written rows (RETURNING).
|
|
77
66
|
*/
|
|
67
|
+
async createMany(data) {
|
|
68
|
+
if (data.length === 0)
|
|
69
|
+
return [];
|
|
70
|
+
const now = this.now();
|
|
71
|
+
const records = data.map((d) => ({ createdAt: now, updatedAt: now, ...d }));
|
|
72
|
+
for (const record of records)
|
|
73
|
+
this.validate('createMany', record);
|
|
74
|
+
return (await this.insertBuilder.values(records).returning());
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Insert or update on conflict. `conflictColumns` identify the unique target;
|
|
78
|
+
* `update` (or all non-conflict columns) are written on conflict. Returns the row.
|
|
79
|
+
*/
|
|
80
|
+
async upsert(data, conflictColumns, updateColumns) {
|
|
81
|
+
const now = this.now();
|
|
82
|
+
const record = { createdAt: now, updatedAt: now, ...data };
|
|
83
|
+
this.validate('upsert', record);
|
|
84
|
+
// Default conflict-update = the supplied data minus identity columns
|
|
85
|
+
// (conflict target + primary key), so a conflict updates the row in place
|
|
86
|
+
// without rewriting the key it matched on. Identity columns are matched by
|
|
87
|
+
// their table property key (not DB column name) to handle snake_case columns.
|
|
88
|
+
const identityCols = new Set([...conflictColumns, ...this.primaryKey]);
|
|
89
|
+
const identityProps = new Set(Object.entries(this.table)
|
|
90
|
+
.filter(([, col]) => identityCols.has(col))
|
|
91
|
+
.map(([key]) => key));
|
|
92
|
+
const defaultSet = Object.fromEntries(Object.entries(data).filter(([key]) => !identityProps.has(key)));
|
|
93
|
+
const setOnConflict = { ...(updateColumns ?? defaultSet), updatedAt: now };
|
|
94
|
+
const rows = (await this.insertBuilder.values(record)
|
|
95
|
+
.onConflictDoUpdate({ target: conflictColumns, set: setOnConflict })
|
|
96
|
+
.returning());
|
|
97
|
+
return rows[0];
|
|
98
|
+
}
|
|
99
|
+
/** Find a record by its primary key (single value or composite tuple). */
|
|
78
100
|
async findById(id, includeDeleted = false) {
|
|
79
|
-
const conditions = [
|
|
80
|
-
if (!includeDeleted && this.activeCondition)
|
|
101
|
+
const conditions = [this.pkCondition(id)];
|
|
102
|
+
if (!includeDeleted && this.activeCondition)
|
|
81
103
|
conditions.push(this.activeCondition);
|
|
82
|
-
|
|
83
|
-
const result = await this.db
|
|
104
|
+
const result = (await this.db
|
|
84
105
|
.select()
|
|
85
106
|
.from(this.table)
|
|
86
|
-
.where(and(...conditions));
|
|
107
|
+
.where(and(...conditions)));
|
|
87
108
|
return result[0];
|
|
88
109
|
}
|
|
89
|
-
/**
|
|
90
|
-
* Find all records.
|
|
91
|
-
*
|
|
92
|
-
* @param includeDeleted - Whether to include soft-deleted records.
|
|
93
|
-
* @returns Array of records.
|
|
94
|
-
*/
|
|
110
|
+
/** Find all records (optionally including soft-deleted). */
|
|
95
111
|
async findAll(includeDeleted = false) {
|
|
96
|
-
|
|
97
|
-
if (!includeDeleted && this.activeCondition) {
|
|
98
|
-
return query.where(this.activeCondition);
|
|
99
|
-
}
|
|
100
|
-
return query;
|
|
112
|
+
return this.list({ includeDeleted });
|
|
101
113
|
}
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
/** Find the first record matching a column value. */
|
|
115
|
+
async findBy(column, value, includeDeleted = false) {
|
|
116
|
+
const rows = await this.list({ where: { col: column, op: 'eq', value }, limit: 1, includeDeleted });
|
|
117
|
+
return rows[0];
|
|
118
|
+
}
|
|
119
|
+
/** Find all records matching a column value. */
|
|
120
|
+
async findAllBy(column, value, includeDeleted = false) {
|
|
121
|
+
return this.list({ where: { col: column, op: 'eq', value }, includeDeleted });
|
|
122
|
+
}
|
|
123
|
+
/** Update a record by primary key. `updatedAt` is refreshed. Returns the updated row. */
|
|
109
124
|
async update(id, data) {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
125
|
+
const updateData = { ...data, updatedAt: this.now() };
|
|
126
|
+
this.validate('update', updateData);
|
|
127
|
+
const rows = (await this.db
|
|
128
|
+
.update(this.table)
|
|
129
|
+
.set(updateData)
|
|
130
|
+
.where(this.pkCondition(id))
|
|
131
|
+
.returning());
|
|
132
|
+
return rows[0];
|
|
117
133
|
}
|
|
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
|
-
*/
|
|
134
|
+
/** Delete a record by primary key. Soft-deletes when the table supports it (default). */
|
|
125
135
|
async delete(id, soft) {
|
|
126
136
|
const useSoftDelete = soft ?? this.hasSoftDelete;
|
|
127
137
|
if (useSoftDelete && this.hasSoftDelete) {
|
|
128
|
-
|
|
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);
|
|
138
|
+
return this.update(id, { inUsed: 0 });
|
|
134
139
|
}
|
|
135
|
-
await this.db.delete(this.table).where(
|
|
140
|
+
await this.db.delete(this.table).where(this.pkCondition(id));
|
|
136
141
|
return undefined;
|
|
137
142
|
}
|
|
138
|
-
/**
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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];
|
|
143
|
+
/** List records with predicate filter, ordering, and offset pagination. */
|
|
144
|
+
async list(spec = {}) {
|
|
145
|
+
const where = this.withActive(spec.where, spec.includeDeleted);
|
|
146
|
+
return this.query(this.table, {
|
|
147
|
+
...(where ? { where } : {}),
|
|
148
|
+
...(spec.orderBy ? { orderBy: spec.orderBy } : {}),
|
|
149
|
+
...(spec.limit !== undefined ? { limit: spec.limit } : {}),
|
|
150
|
+
...(spec.offset !== undefined ? { offset: spec.offset } : {}),
|
|
151
|
+
});
|
|
156
152
|
}
|
|
157
|
-
/**
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
async findAllBy(column, value, includeDeleted = false) {
|
|
166
|
-
const conditions = [eq(column, value)];
|
|
167
|
-
if (!includeDeleted && this.activeCondition) {
|
|
168
|
-
conditions.push(this.activeCondition);
|
|
153
|
+
/** List one keyset page; returns the rows and the cursor for the next page. */
|
|
154
|
+
async listByCursor(spec) {
|
|
155
|
+
const dir = spec.direction ?? 'asc';
|
|
156
|
+
const filters = [];
|
|
157
|
+
if (spec.where)
|
|
158
|
+
filters.push(spec.where);
|
|
159
|
+
if (spec.cursor !== undefined) {
|
|
160
|
+
filters.push({ col: spec.cursorColumn, op: dir === 'asc' ? 'gt' : 'lt', value: spec.cursor });
|
|
169
161
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
.
|
|
162
|
+
const where = this.withActive(filters.length > 0 ? { and: filters } : undefined, spec.includeDeleted);
|
|
163
|
+
const rows = await this.query(this.table, {
|
|
164
|
+
...(where ? { where } : {}),
|
|
165
|
+
orderBy: [{ col: spec.cursorColumn, dir }],
|
|
166
|
+
limit: spec.limit,
|
|
167
|
+
});
|
|
168
|
+
const last = rows[rows.length - 1];
|
|
169
|
+
const nextCursor = rows.length === spec.limit && last
|
|
170
|
+
? last[spec.cursorColumn.name]
|
|
171
|
+
: undefined;
|
|
172
|
+
return nextCursor !== undefined ? { rows, nextCursor } : { rows };
|
|
174
173
|
}
|
|
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
|
-
*/
|
|
174
|
+
/** Count records matching an optional predicate. */
|
|
206
175
|
async count(where, includeDeleted = false) {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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;
|
|
176
|
+
const condition = this.withActive(where, includeDeleted);
|
|
177
|
+
const compiled = condition ? compilePredicate(condition) : undefined;
|
|
178
|
+
const base = this.db
|
|
179
|
+
.select({ value: countFn() })
|
|
180
|
+
.from(this.table);
|
|
181
|
+
const result = (await (compiled ? base.where(compiled) : base));
|
|
216
182
|
return result[0]?.value ?? 0;
|
|
217
183
|
}
|
|
184
|
+
/** Combine a caller predicate with the soft-delete active filter. */
|
|
185
|
+
withActive(where, includeDeleted) {
|
|
186
|
+
if (includeDeleted || !this.hasSoftDelete)
|
|
187
|
+
return where;
|
|
188
|
+
const active = { col: this.table.inUsed, op: 'eq', value: 1 };
|
|
189
|
+
if (!where)
|
|
190
|
+
return active;
|
|
191
|
+
return { and: [where, active] };
|
|
192
|
+
}
|
|
218
193
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
export { createDbAdapter, type DbAdapter, type DbAdapterConfig, type
|
|
1
|
+
export { createDbAdapter, type DbAdapter, type DbAdapterConfig, type InternalDb } from './adapter';
|
|
2
2
|
export { BunSqliteAdapter, type BunSqliteOptions } from './adapters/bun-sqlite';
|
|
3
3
|
export { D1Adapter } from './adapters/d1';
|
|
4
|
-
export { BaseDao } from './base-dao';
|
|
4
|
+
export { BaseDao, type TxHandle } from './base-dao';
|
|
5
|
+
export { type DefinedTable, defineTable } from './define-table';
|
|
5
6
|
export { type EmbeddedMigration, embeddedMigrations } from './embedded-migrations';
|
|
6
|
-
export { EntityDao, type EntityTable, type PKColumn, type SoftDeletableTable } from './entity-dao';
|
|
7
|
+
export { type CursorListSpec, type DaoValidator, EntityDao, type EntityDaoOptions, type EntityListSpec, type EntityTable, type PKColumn, type PKValue, type SoftDeletableTable, } from './entity-dao';
|
|
7
8
|
export { applyMigrations, type MigrationOptions } from './migrate';
|
|
9
|
+
export { type ColRef, type ComparisonOp, compileOrderBy, compilePredicate, type ListSpec, type OrderTerm, type Predicate, } from './query-spec';
|
|
8
10
|
export { QueueJobDao, type QueueJobRecord, type QueueStats } from './queue-job-dao';
|
|
9
11
|
export { appendOnlyColumns, buildAppendOnlyColumns, buildStandardColumns, buildStandardColumnsWithSoftDelete, nowTimestamp, standardColumns, standardColumnsWithSoftDelete, } from './schema/common';
|
|
10
12
|
export { queueJobs } from './schema/queue-jobs';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +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,
|
|
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,UAAU,EAAE,MAAM,WAAW,CAAC;AACnG,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,KAAK,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,EACH,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,SAAS,EACT,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,OAAO,EACZ,KAAK,kBAAkB,GAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,EACH,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,cAAc,EACd,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,SAAS,GACjB,MAAM,cAAc,CAAC;AACtB,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"}
|