@gobing-ai/ts-db 0.2.7 → 0.2.9
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 +53 -2
- package/dist/drizzle-builders.d.ts +96 -0
- package/dist/drizzle-builders.d.ts.map +1 -0
- package/dist/drizzle-builders.js +17 -0
- package/dist/embedded-migrations.d.ts.map +1 -1
- package/dist/embedded-migrations.js +10 -0
- package/dist/entity-dao.d.ts.map +1 -1
- package/dist/inbox-message-dao.d.ts +19 -0
- package/dist/inbox-message-dao.d.ts.map +1 -0
- package/dist/inbox-message-dao.js +75 -0
- package/dist/inbox.d.ts +3 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/inbox.js +2 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/migrate.d.ts +12 -5
- package/dist/migrate.d.ts.map +1 -1
- package/dist/migrate.js +19 -25
- package/dist/queue-job-dao.d.ts.map +1 -1
- package/dist/schema/ddl.d.ts.map +1 -1
- package/dist/schema/ddl.js +4 -36
- package/dist/schema/drizzle-internals.d.ts +25 -0
- package/dist/schema/drizzle-internals.d.ts.map +1 -0
- package/dist/schema/drizzle-internals.js +47 -0
- package/dist/schema/inbox-messages.d.ts +212 -0
- package/dist/schema/inbox-messages.d.ts.map +1 -0
- package/dist/schema/inbox-messages.js +17 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -0
- package/dist/schema/runtime.d.ts +1 -0
- package/dist/schema/runtime.d.ts.map +1 -1
- package/dist/schema/runtime.js +1 -0
- package/package.json +7 -2
- package/src/drizzle-builders.ts +98 -0
- package/src/embedded-migrations.ts +10 -0
- package/src/entity-dao.ts +2 -21
- package/src/inbox-message-dao.ts +91 -0
- package/src/inbox.ts +2 -0
- package/src/index.ts +3 -1
- package/src/migrate.ts +34 -22
- package/src/queue-job-dao.ts +14 -59
- package/src/schema/ddl.ts +4 -40
- package/src/schema/drizzle-internals.ts +55 -0
- package/src/schema/inbox-messages.ts +22 -0
- package/src/schema/index.ts +1 -0
- package/src/schema/runtime.ts +1 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quarantine for the *unversioned* drizzle-orm internal shapes ts-db reaches into
|
|
3
|
+
* for DDL generation.
|
|
4
|
+
*
|
|
5
|
+
* Everything else in ts-db consumes drizzle's public API (`getTableConfig`,
|
|
6
|
+
* column accessors). These two helpers are the only places that touch private
|
|
7
|
+
* structure — the SQL-expression `queryChunks` array and the
|
|
8
|
+
* `Symbol.for('drizzle:Name')` table-name slot — neither of which is covered by
|
|
9
|
+
* drizzle's semver contract. Keeping both here means a drizzle bump that changes
|
|
10
|
+
* an internal shape breaks in ONE file with ONE focused test, instead of silently
|
|
11
|
+
* mis-generating DDL across `ddl.ts`. If this module starts failing after an
|
|
12
|
+
* upgrade, the fix lives here.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Render a drizzle `sql\`...\`` expression to its literal SQL text by walking its
|
|
16
|
+
* internal `queryChunks`. Used to emit SQL-level column defaults
|
|
17
|
+
* (e.g. `DEFAULT (unixepoch())`) into generated DDL.
|
|
18
|
+
*
|
|
19
|
+
* Returns `undefined` when `value` is not a drizzle SQL expression with chunks,
|
|
20
|
+
* so callers can fall through to their primitive/`String()` handling.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sqlExpressionToText(value: unknown): string | undefined;
|
|
23
|
+
/** Resolve a drizzle table object to its declared name (stored at a private symbol). */
|
|
24
|
+
export declare function getDrizzleTableName(table: object): string;
|
|
25
|
+
//# sourceMappingURL=drizzle-internals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drizzle-internals.d.ts","sourceRoot":"","sources":["../../src/schema/drizzle-internals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAoBtE;AAED,wFAAwF;AACxF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGzD"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quarantine for the *unversioned* drizzle-orm internal shapes ts-db reaches into
|
|
3
|
+
* for DDL generation.
|
|
4
|
+
*
|
|
5
|
+
* Everything else in ts-db consumes drizzle's public API (`getTableConfig`,
|
|
6
|
+
* column accessors). These two helpers are the only places that touch private
|
|
7
|
+
* structure — the SQL-expression `queryChunks` array and the
|
|
8
|
+
* `Symbol.for('drizzle:Name')` table-name slot — neither of which is covered by
|
|
9
|
+
* drizzle's semver contract. Keeping both here means a drizzle bump that changes
|
|
10
|
+
* an internal shape breaks in ONE file with ONE focused test, instead of silently
|
|
11
|
+
* mis-generating DDL across `ddl.ts`. If this module starts failing after an
|
|
12
|
+
* upgrade, the fix lives here.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Render a drizzle `sql\`...\`` expression to its literal SQL text by walking its
|
|
16
|
+
* internal `queryChunks`. Used to emit SQL-level column defaults
|
|
17
|
+
* (e.g. `DEFAULT (unixepoch())`) into generated DDL.
|
|
18
|
+
*
|
|
19
|
+
* Returns `undefined` when `value` is not a drizzle SQL expression with chunks,
|
|
20
|
+
* so callers can fall through to their primitive/`String()` handling.
|
|
21
|
+
*/
|
|
22
|
+
export function sqlExpressionToText(value) {
|
|
23
|
+
if (typeof value !== 'object' || value === null || !('queryChunks' in value)) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const chunks = value.queryChunks;
|
|
27
|
+
if (!chunks)
|
|
28
|
+
return undefined;
|
|
29
|
+
return chunks
|
|
30
|
+
.map((chunk) => {
|
|
31
|
+
// StringChunk — the literal SQL fragment
|
|
32
|
+
if ('value' in chunk && typeof chunk.value === 'string') {
|
|
33
|
+
return chunk.value;
|
|
34
|
+
}
|
|
35
|
+
// Param — use the input value if available
|
|
36
|
+
if ('input' in chunk && chunk.input !== undefined) {
|
|
37
|
+
return String(chunk.input);
|
|
38
|
+
}
|
|
39
|
+
return String(chunk.value ?? '?');
|
|
40
|
+
})
|
|
41
|
+
.join('');
|
|
42
|
+
}
|
|
43
|
+
/** Resolve a drizzle table object to its declared name (stored at a private symbol). */
|
|
44
|
+
export function getDrizzleTableName(table) {
|
|
45
|
+
const nameSym = Symbol.for('drizzle:Name');
|
|
46
|
+
return String(table[nameSym]);
|
|
47
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drizzle schema definition for durable inter-agent inbox messages.
|
|
3
|
+
*/
|
|
4
|
+
export declare const inboxMessages: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
5
|
+
name: "inbox_messages";
|
|
6
|
+
schema: undefined;
|
|
7
|
+
columns: {
|
|
8
|
+
id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
9
|
+
name: "id";
|
|
10
|
+
tableName: "inbox_messages";
|
|
11
|
+
dataType: "string";
|
|
12
|
+
columnType: "SQLiteText";
|
|
13
|
+
data: string;
|
|
14
|
+
driverParam: string;
|
|
15
|
+
notNull: true;
|
|
16
|
+
hasDefault: false;
|
|
17
|
+
isPrimaryKey: true;
|
|
18
|
+
isAutoincrement: false;
|
|
19
|
+
hasRuntimeDefault: false;
|
|
20
|
+
enumValues: [string, ...string[]];
|
|
21
|
+
baseColumn: never;
|
|
22
|
+
identity: undefined;
|
|
23
|
+
generated: undefined;
|
|
24
|
+
}, {}, {
|
|
25
|
+
length: number | undefined;
|
|
26
|
+
}>;
|
|
27
|
+
fromId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
28
|
+
name: "from_id";
|
|
29
|
+
tableName: "inbox_messages";
|
|
30
|
+
dataType: "string";
|
|
31
|
+
columnType: "SQLiteText";
|
|
32
|
+
data: string;
|
|
33
|
+
driverParam: string;
|
|
34
|
+
notNull: false;
|
|
35
|
+
hasDefault: false;
|
|
36
|
+
isPrimaryKey: false;
|
|
37
|
+
isAutoincrement: false;
|
|
38
|
+
hasRuntimeDefault: false;
|
|
39
|
+
enumValues: [string, ...string[]];
|
|
40
|
+
baseColumn: never;
|
|
41
|
+
identity: undefined;
|
|
42
|
+
generated: undefined;
|
|
43
|
+
}, {}, {
|
|
44
|
+
length: number | undefined;
|
|
45
|
+
}>;
|
|
46
|
+
toId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
47
|
+
name: "to_id";
|
|
48
|
+
tableName: "inbox_messages";
|
|
49
|
+
dataType: "string";
|
|
50
|
+
columnType: "SQLiteText";
|
|
51
|
+
data: string;
|
|
52
|
+
driverParam: string;
|
|
53
|
+
notNull: true;
|
|
54
|
+
hasDefault: false;
|
|
55
|
+
isPrimaryKey: false;
|
|
56
|
+
isAutoincrement: false;
|
|
57
|
+
hasRuntimeDefault: false;
|
|
58
|
+
enumValues: [string, ...string[]];
|
|
59
|
+
baseColumn: never;
|
|
60
|
+
identity: undefined;
|
|
61
|
+
generated: undefined;
|
|
62
|
+
}, {}, {
|
|
63
|
+
length: number | undefined;
|
|
64
|
+
}>;
|
|
65
|
+
body: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
66
|
+
name: "body";
|
|
67
|
+
tableName: "inbox_messages";
|
|
68
|
+
dataType: "string";
|
|
69
|
+
columnType: "SQLiteText";
|
|
70
|
+
data: string;
|
|
71
|
+
driverParam: string;
|
|
72
|
+
notNull: true;
|
|
73
|
+
hasDefault: false;
|
|
74
|
+
isPrimaryKey: false;
|
|
75
|
+
isAutoincrement: false;
|
|
76
|
+
hasRuntimeDefault: false;
|
|
77
|
+
enumValues: [string, ...string[]];
|
|
78
|
+
baseColumn: never;
|
|
79
|
+
identity: undefined;
|
|
80
|
+
generated: undefined;
|
|
81
|
+
}, {}, {
|
|
82
|
+
length: number | undefined;
|
|
83
|
+
}>;
|
|
84
|
+
status: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
85
|
+
name: "status";
|
|
86
|
+
tableName: "inbox_messages";
|
|
87
|
+
dataType: "string";
|
|
88
|
+
columnType: "SQLiteText";
|
|
89
|
+
data: string;
|
|
90
|
+
driverParam: string;
|
|
91
|
+
notNull: true;
|
|
92
|
+
hasDefault: true;
|
|
93
|
+
isPrimaryKey: false;
|
|
94
|
+
isAutoincrement: false;
|
|
95
|
+
hasRuntimeDefault: false;
|
|
96
|
+
enumValues: [string, ...string[]];
|
|
97
|
+
baseColumn: never;
|
|
98
|
+
identity: undefined;
|
|
99
|
+
generated: undefined;
|
|
100
|
+
}, {}, {
|
|
101
|
+
length: number | undefined;
|
|
102
|
+
}>;
|
|
103
|
+
inReplyTo: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
104
|
+
name: "in_reply_to";
|
|
105
|
+
tableName: "inbox_messages";
|
|
106
|
+
dataType: "string";
|
|
107
|
+
columnType: "SQLiteText";
|
|
108
|
+
data: string;
|
|
109
|
+
driverParam: string;
|
|
110
|
+
notNull: false;
|
|
111
|
+
hasDefault: false;
|
|
112
|
+
isPrimaryKey: false;
|
|
113
|
+
isAutoincrement: false;
|
|
114
|
+
hasRuntimeDefault: false;
|
|
115
|
+
enumValues: [string, ...string[]];
|
|
116
|
+
baseColumn: never;
|
|
117
|
+
identity: undefined;
|
|
118
|
+
generated: undefined;
|
|
119
|
+
}, {}, {
|
|
120
|
+
length: number | undefined;
|
|
121
|
+
}>;
|
|
122
|
+
createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
123
|
+
name: "created_at";
|
|
124
|
+
tableName: "inbox_messages";
|
|
125
|
+
dataType: "number";
|
|
126
|
+
columnType: "SQLiteInteger";
|
|
127
|
+
data: number;
|
|
128
|
+
driverParam: number;
|
|
129
|
+
notNull: true;
|
|
130
|
+
hasDefault: false;
|
|
131
|
+
isPrimaryKey: false;
|
|
132
|
+
isAutoincrement: false;
|
|
133
|
+
hasRuntimeDefault: false;
|
|
134
|
+
enumValues: undefined;
|
|
135
|
+
baseColumn: never;
|
|
136
|
+
identity: undefined;
|
|
137
|
+
generated: undefined;
|
|
138
|
+
}, {}, {}>;
|
|
139
|
+
updatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
140
|
+
name: "updated_at";
|
|
141
|
+
tableName: "inbox_messages";
|
|
142
|
+
dataType: "number";
|
|
143
|
+
columnType: "SQLiteInteger";
|
|
144
|
+
data: number;
|
|
145
|
+
driverParam: number;
|
|
146
|
+
notNull: true;
|
|
147
|
+
hasDefault: false;
|
|
148
|
+
isPrimaryKey: false;
|
|
149
|
+
isAutoincrement: false;
|
|
150
|
+
hasRuntimeDefault: false;
|
|
151
|
+
enumValues: undefined;
|
|
152
|
+
baseColumn: never;
|
|
153
|
+
identity: undefined;
|
|
154
|
+
generated: undefined;
|
|
155
|
+
}, {}, {}>;
|
|
156
|
+
deliveredAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
157
|
+
name: "delivered_at";
|
|
158
|
+
tableName: "inbox_messages";
|
|
159
|
+
dataType: "number";
|
|
160
|
+
columnType: "SQLiteInteger";
|
|
161
|
+
data: number;
|
|
162
|
+
driverParam: number;
|
|
163
|
+
notNull: false;
|
|
164
|
+
hasDefault: false;
|
|
165
|
+
isPrimaryKey: false;
|
|
166
|
+
isAutoincrement: false;
|
|
167
|
+
hasRuntimeDefault: false;
|
|
168
|
+
enumValues: undefined;
|
|
169
|
+
baseColumn: never;
|
|
170
|
+
identity: undefined;
|
|
171
|
+
generated: undefined;
|
|
172
|
+
}, {}, {}>;
|
|
173
|
+
injectAttempts: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
174
|
+
name: "inject_attempts";
|
|
175
|
+
tableName: "inbox_messages";
|
|
176
|
+
dataType: "number";
|
|
177
|
+
columnType: "SQLiteInteger";
|
|
178
|
+
data: number;
|
|
179
|
+
driverParam: number;
|
|
180
|
+
notNull: true;
|
|
181
|
+
hasDefault: true;
|
|
182
|
+
isPrimaryKey: false;
|
|
183
|
+
isAutoincrement: false;
|
|
184
|
+
hasRuntimeDefault: false;
|
|
185
|
+
enumValues: undefined;
|
|
186
|
+
baseColumn: never;
|
|
187
|
+
identity: undefined;
|
|
188
|
+
generated: undefined;
|
|
189
|
+
}, {}, {}>;
|
|
190
|
+
injectError: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
191
|
+
name: "inject_error";
|
|
192
|
+
tableName: "inbox_messages";
|
|
193
|
+
dataType: "string";
|
|
194
|
+
columnType: "SQLiteText";
|
|
195
|
+
data: string;
|
|
196
|
+
driverParam: string;
|
|
197
|
+
notNull: false;
|
|
198
|
+
hasDefault: false;
|
|
199
|
+
isPrimaryKey: false;
|
|
200
|
+
isAutoincrement: false;
|
|
201
|
+
hasRuntimeDefault: false;
|
|
202
|
+
enumValues: [string, ...string[]];
|
|
203
|
+
baseColumn: never;
|
|
204
|
+
identity: undefined;
|
|
205
|
+
generated: undefined;
|
|
206
|
+
}, {}, {
|
|
207
|
+
length: number | undefined;
|
|
208
|
+
}>;
|
|
209
|
+
};
|
|
210
|
+
dialect: "sqlite";
|
|
211
|
+
}>;
|
|
212
|
+
//# sourceMappingURL=inbox-messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox-messages.d.ts","sourceRoot":"","sources":["../../src/schema/inbox-messages.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBzB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
/**
|
|
3
|
+
* Drizzle schema definition for durable inter-agent inbox messages.
|
|
4
|
+
*/
|
|
5
|
+
export const inboxMessages = sqliteTable('inbox_messages', {
|
|
6
|
+
id: text('id').primaryKey(),
|
|
7
|
+
fromId: text('from_id'),
|
|
8
|
+
toId: text('to_id').notNull(),
|
|
9
|
+
body: text('body').notNull(),
|
|
10
|
+
status: text('status').notNull().default('queued'),
|
|
11
|
+
inReplyTo: text('in_reply_to'),
|
|
12
|
+
createdAt: integer('created_at').notNull(),
|
|
13
|
+
updatedAt: integer('updated_at').notNull(),
|
|
14
|
+
deliveredAt: integer('delivered_at'),
|
|
15
|
+
injectAttempts: integer('inject_attempts').notNull().default(0),
|
|
16
|
+
injectError: text('inject_error'),
|
|
17
|
+
}, (table) => [index('idx_inbox_messages_to_status').on(table.toId, table.status)]);
|
package/dist/schema/index.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ export { index, integer, text } from 'drizzle-orm/sqlite-core';
|
|
|
2
2
|
export * from './common';
|
|
3
3
|
export { generateCreateTableSql } from './ddl';
|
|
4
4
|
export { type DefinedTable, defineTable } from './define-table';
|
|
5
|
+
export { inboxMessages } from './inbox-messages';
|
|
5
6
|
export { queueJobs } from './queue-jobs';
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAC/D,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,sBAAsB,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAE,KAAK,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAC/D,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,sBAAsB,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAE,KAAK,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/schema/index.js
CHANGED
|
@@ -2,4 +2,5 @@ export { index, integer, text } from 'drizzle-orm/sqlite-core';
|
|
|
2
2
|
export * from './common.js';
|
|
3
3
|
export { generateCreateTableSql } from './ddl.js';
|
|
4
4
|
export { defineTable } from './define-table.js';
|
|
5
|
+
export { inboxMessages } from './inbox-messages.js';
|
|
5
6
|
export { queueJobs } from './queue-jobs.js';
|
package/dist/schema/runtime.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/schema/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/schema/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/schema/runtime.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-db",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "@gobing-ai/ts-db — a drizzle-free database facade: typed DAOs over Bun SQLite / Cloudflare D1, a small predicate query spec, single-source-of-truth tables, and migrations. Drizzle stays an internal detail.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -40,6 +40,10 @@
|
|
|
40
40
|
"types": "./dist/adapters/d1.d.ts",
|
|
41
41
|
"import": "./dist/adapters/d1.js"
|
|
42
42
|
},
|
|
43
|
+
"./inbox": {
|
|
44
|
+
"types": "./dist/inbox.d.ts",
|
|
45
|
+
"import": "./dist/inbox.js"
|
|
46
|
+
},
|
|
43
47
|
"./schema": {
|
|
44
48
|
"types": "./dist/schema/index.d.ts",
|
|
45
49
|
"import": "./dist/schema/index.js"
|
|
@@ -62,7 +66,8 @@
|
|
|
62
66
|
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-db-v<version> && git push --tags' && exit 1"
|
|
63
67
|
},
|
|
64
68
|
"dependencies": {
|
|
65
|
-
"@gobing-ai/ts-runtime": "^0.2.
|
|
69
|
+
"@gobing-ai/ts-runtime": "^0.2.9",
|
|
70
|
+
"@gobing-ai/ts-utils": "^0.2.9"
|
|
66
71
|
},
|
|
67
72
|
"peerDependencies": {
|
|
68
73
|
"drizzle-orm": ">=0.38.0",
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared structural types for drizzle's fluent query builders.
|
|
3
|
+
*
|
|
4
|
+
* `InternalDb` is typed as the bare drizzle union (ADR-005 keeps drizzle internal),
|
|
5
|
+
* so its fluent builders (`insert().values().returning()`, `update().set().where()`,
|
|
6
|
+
* `select(proj).from()...`) are not surfaced as call-shapes. Each DAO previously
|
|
7
|
+
* re-declared these narrowings inline — three files independently describing the
|
|
8
|
+
* same `update → set → where → returning` chain. Centralising them here means the
|
|
9
|
+
* cast shape lives in one place: when drizzle's builder surface shifts, this file
|
|
10
|
+
* changes, not every DAO.
|
|
11
|
+
*
|
|
12
|
+
* These are deliberately minimal — each type narrows only the chain a DAO actually
|
|
13
|
+
* invokes. Table-specific projections (`select({ status, count })`) stay local to
|
|
14
|
+
* their DAO; only the recurring chain skeletons live here.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Terminal `.returning()` returning the written rows. */
|
|
20
|
+
export interface ReturningRows {
|
|
21
|
+
returning: () => Promise<unknown[]>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** `insert(table).values(record)` → returning, with optional upsert. */
|
|
25
|
+
export interface InsertBuilder {
|
|
26
|
+
values: (record: unknown) => ReturningRows & {
|
|
27
|
+
onConflictDoUpdate: (cfg: { target: unknown; set: unknown }) => ReturningRows;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** `update(table).set(data).where(cond)` → `.returning()` rows. */
|
|
32
|
+
export interface UpdateReturningDb {
|
|
33
|
+
update: (table: unknown) => {
|
|
34
|
+
set: (data: unknown) => {
|
|
35
|
+
where: (cond: unknown) => ReturningRows;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** `update(table).set(data).where(cond)` → void (no rows). */
|
|
41
|
+
export interface UpdateVoidDb {
|
|
42
|
+
update: (table: unknown) => {
|
|
43
|
+
set: (data: unknown) => {
|
|
44
|
+
where: (cond: unknown) => Promise<unknown>;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** `update(table).set(data).where(cond)` → `{ changes }` (affected-row count). */
|
|
50
|
+
export interface UpdateChangesDb {
|
|
51
|
+
update: (table: unknown) => {
|
|
52
|
+
set: (data: unknown) => {
|
|
53
|
+
where: (cond: unknown) => Promise<{ changes: number }>;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** `update(table).set(data).where(cond)` → arbitrary single condition (no projection). */
|
|
59
|
+
export interface SimpleUpdateDb {
|
|
60
|
+
update: (table: unknown) => {
|
|
61
|
+
set: (data: unknown) => {
|
|
62
|
+
where: (cond: unknown) => ReturningRows;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** `select(projection).from(table)` exposing `.where(...)` and `.groupBy(...)` terminals. */
|
|
68
|
+
export interface SelectProjectionDb {
|
|
69
|
+
select: (projection: unknown) => {
|
|
70
|
+
from: (table: unknown) => {
|
|
71
|
+
where: (cond: unknown) => Promise<unknown[]>;
|
|
72
|
+
groupBy: (group: unknown) => Promise<unknown[]>;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** `select().from(table).where(cond).orderBy(o).limit(n)` — the ready-rows read chain. */
|
|
78
|
+
export interface SelectOrderedLimitDb {
|
|
79
|
+
select: () => {
|
|
80
|
+
from: (table: unknown) => {
|
|
81
|
+
where: (cond: unknown) => {
|
|
82
|
+
orderBy: (order: unknown) => { limit: (limit: number) => Promise<unknown[]> };
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** `select(count).from(table)` that is awaitable and chainable with `.where(...)`. */
|
|
89
|
+
export type CountQuery = Promise<unknown[]> & {
|
|
90
|
+
where: (condition: unknown) => Promise<unknown[]>;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** `select({ value: count() }).from(table)` → awaitable count, optionally filtered. */
|
|
94
|
+
export interface CountSelectDb {
|
|
95
|
+
select: (projection: unknown) => {
|
|
96
|
+
from: (table: unknown) => CountQuery;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -29,4 +29,14 @@ export const embeddedMigrations: EmbeddedMigration[] = [
|
|
|
29
29
|
sql: 'ALTER TABLE `queue_jobs` ADD `expires_at` integer;',
|
|
30
30
|
hash: '7380f8c162352a61b15205af5a87e0e7313a499203dae98fe62151a1dc7fec0e',
|
|
31
31
|
},
|
|
32
|
+
{
|
|
33
|
+
tag: '0003_inbox_messages',
|
|
34
|
+
sql: "CREATE TABLE `inbox_messages` (\n\t`id` text PRIMARY KEY NOT NULL,\n\t`from_id` text,\n\t`to_id` text NOT NULL,\n\t`body` text NOT NULL,\n\t`status` text DEFAULT 'queued' NOT NULL,\n\t`in_reply_to` text,\n\t`created_at` integer NOT NULL,\n\t`updated_at` integer NOT NULL,\n\t`delivered_at` integer,\n\t`inject_attempts` integer DEFAULT 0 NOT NULL,\n\t`inject_error` text\n);",
|
|
35
|
+
hash: 'c4ba569172d1be276c42b5c164c668404e6229dcf48b41d7fb2bc93e8d29d11b',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
tag: '0004_inbox_messages_to_status_idx',
|
|
39
|
+
sql: 'CREATE INDEX `idx_inbox_messages_to_status` ON `inbox_messages` (`to_id`,`status`);',
|
|
40
|
+
hash: '3c00f1bce4f569e442f3142df31ff478d6c9b3d460824d46575f50a324bfae61',
|
|
41
|
+
},
|
|
32
42
|
];
|
package/src/entity-dao.ts
CHANGED
|
@@ -2,18 +2,9 @@ import { and, count as countFn, eq, type SQL } from 'drizzle-orm';
|
|
|
2
2
|
import type { SQLiteColumn, SQLiteTable } from 'drizzle-orm/sqlite-core';
|
|
3
3
|
import type { DbAdapter } from './adapter';
|
|
4
4
|
import { BaseDao } from './base-dao';
|
|
5
|
+
import type { CountSelectDb, InsertBuilder, ReturningRows } from './drizzle-builders';
|
|
5
6
|
import { compilePredicate, type OrderTerm, type Predicate } from './query-spec';
|
|
6
7
|
|
|
7
|
-
type ReturningRows = {
|
|
8
|
-
returning: () => Promise<unknown[]>;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
type InsertBuilder = {
|
|
12
|
-
values: (record: unknown) => ReturningRows & {
|
|
13
|
-
onConflictDoUpdate: (cfg: { target: SQLiteColumn[]; set: unknown }) => ReturningRows;
|
|
14
|
-
};
|
|
15
|
-
};
|
|
16
|
-
|
|
17
8
|
type UpdateBuilder = {
|
|
18
9
|
set: (data: unknown) => {
|
|
19
10
|
where: (condition: SQL) => ReturningRows;
|
|
@@ -24,16 +15,6 @@ type DeleteBuilder = {
|
|
|
24
15
|
where: (condition: SQL) => Promise<unknown>;
|
|
25
16
|
};
|
|
26
17
|
|
|
27
|
-
type CountQuery = Promise<unknown[]> & {
|
|
28
|
-
where: (condition: SQL) => Promise<unknown[]>;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
type CountDb = {
|
|
32
|
-
select: (projection: unknown) => {
|
|
33
|
-
from: (table: unknown) => CountQuery;
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
|
|
37
18
|
/**
|
|
38
19
|
* Type for tables compatible with EntityDao.
|
|
39
20
|
* Must have standard columns: createdAt, updatedAt.
|
|
@@ -347,7 +328,7 @@ export class EntityDao<TTable extends EntityTable, TPK extends SQLiteColumn> ext
|
|
|
347
328
|
async count(where?: Predicate, includeDeleted = false): Promise<number> {
|
|
348
329
|
const condition = this.withActive(where, includeDeleted);
|
|
349
330
|
const compiled = condition ? compilePredicate(condition) : undefined;
|
|
350
|
-
const base = (this.db as
|
|
331
|
+
const base = (this.db as CountSelectDb).select({ value: countFn() }).from(this.table);
|
|
351
332
|
const result = (await (compiled ? base.where(compiled) : base)) as {
|
|
352
333
|
value: number;
|
|
353
334
|
}[];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { sql } from 'drizzle-orm';
|
|
2
|
+
import type { DbAdapter } from './adapter';
|
|
3
|
+
import type { UpdateReturningDb } from './drizzle-builders';
|
|
4
|
+
import { EntityDao } from './entity-dao';
|
|
5
|
+
import { inboxMessages } from './schema/inbox-messages';
|
|
6
|
+
|
|
7
|
+
/** Row type inferred from the inbox_messages Drizzle schema. */
|
|
8
|
+
export type InboxMessage = typeof inboxMessages.$inferSelect;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* DAO for durable inter-agent inbox messages.
|
|
12
|
+
*/
|
|
13
|
+
export class InboxMessageDao extends EntityDao<typeof inboxMessages, typeof inboxMessages.id> {
|
|
14
|
+
constructor(adapter: DbAdapter) {
|
|
15
|
+
super(adapter, inboxMessages, [inboxMessages.id], 'inbox_messages');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async enqueue(fromId: string | null, toId: string, body: string, inReplyTo?: string): Promise<string> {
|
|
19
|
+
const id = crypto.randomUUID();
|
|
20
|
+
await this.create({
|
|
21
|
+
id,
|
|
22
|
+
fromId,
|
|
23
|
+
toId,
|
|
24
|
+
body,
|
|
25
|
+
status: 'queued',
|
|
26
|
+
injectAttempts: 0,
|
|
27
|
+
...(inReplyTo !== undefined ? { inReplyTo } : {}),
|
|
28
|
+
});
|
|
29
|
+
return id;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async drainPending(toId: string): Promise<InboxMessage[]> {
|
|
33
|
+
const now = this.now();
|
|
34
|
+
const rows = await (this.db as UpdateReturningDb)
|
|
35
|
+
.update(inboxMessages)
|
|
36
|
+
.set({
|
|
37
|
+
status: 'injected',
|
|
38
|
+
injectAttempts: sql`${inboxMessages.injectAttempts} + 1`,
|
|
39
|
+
updatedAt: now,
|
|
40
|
+
})
|
|
41
|
+
.where(
|
|
42
|
+
sql`${inboxMessages.id} IN (
|
|
43
|
+
SELECT id
|
|
44
|
+
FROM ${inboxMessages}
|
|
45
|
+
WHERE to_id = ${toId}
|
|
46
|
+
AND status = 'queued'
|
|
47
|
+
ORDER BY created_at
|
|
48
|
+
)
|
|
49
|
+
AND ${inboxMessages.status} = 'queued'`,
|
|
50
|
+
)
|
|
51
|
+
.returning();
|
|
52
|
+
|
|
53
|
+
return rows as InboxMessage[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async markDelivered(msgId: string): Promise<void> {
|
|
57
|
+
await this.update(msgId, {
|
|
58
|
+
status: 'delivered',
|
|
59
|
+
deliveredAt: this.now(),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async markFailed(msgId: string, error: string): Promise<void> {
|
|
64
|
+
await this.update(msgId, {
|
|
65
|
+
status: 'failed',
|
|
66
|
+
injectError: error,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async inbox(toId: string, limit?: number, offset?: number): Promise<InboxMessage[]> {
|
|
71
|
+
return this.list({
|
|
72
|
+
where: { col: inboxMessages.toId, op: 'eq', value: toId },
|
|
73
|
+
orderBy: [{ col: inboxMessages.createdAt, dir: 'desc' }],
|
|
74
|
+
...(limit !== undefined ? { limit } : {}),
|
|
75
|
+
...(offset !== undefined ? { offset } : {}),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async countPending(toId: string): Promise<number> {
|
|
80
|
+
return this.count({
|
|
81
|
+
and: [
|
|
82
|
+
{ col: inboxMessages.toId, op: 'eq', value: toId },
|
|
83
|
+
{ col: inboxMessages.status, op: 'eq', value: 'queued' },
|
|
84
|
+
],
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getById(id: string): Promise<InboxMessage | undefined> {
|
|
89
|
+
return this.findBy(inboxMessages.id, id);
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/inbox.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -14,7 +14,8 @@ export {
|
|
|
14
14
|
type PKValue,
|
|
15
15
|
type SoftDeletableTable,
|
|
16
16
|
} from './entity-dao';
|
|
17
|
-
export {
|
|
17
|
+
export { type InboxMessage, InboxMessageDao } from './inbox-message-dao';
|
|
18
|
+
export { applyMigrations, type MigrationLogger, type MigrationOptions } from './migrate';
|
|
18
19
|
export {
|
|
19
20
|
type ColRef,
|
|
20
21
|
type ComparisonOp,
|
|
@@ -34,5 +35,6 @@ export {
|
|
|
34
35
|
standardColumns,
|
|
35
36
|
standardColumnsWithSoftDelete,
|
|
36
37
|
} from './schema/common';
|
|
38
|
+
export { inboxMessages } from './schema/inbox-messages';
|
|
37
39
|
export { queueJobs } from './schema/queue-jobs';
|
|
38
40
|
export type { SpanContext } from './span-context';
|