js-bao 0.2.11 → 0.2.12
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 +174 -0
- package/dist/BaseModel-5YQCROYE.js +17 -0
- package/dist/BaseModel-5YQCROYE.js.map +1 -0
- package/dist/BaseModel-FCNWDJBH.js +17 -0
- package/dist/BaseModel-FCNWDJBH.js.map +1 -0
- package/dist/BrowserDatabaseFactory-PXOTK2DQ.js +119 -0
- package/dist/BrowserDatabaseFactory-PXOTK2DQ.js.map +1 -0
- package/dist/BrowserDatabaseFactory-WD4VX2VZ.js +119 -0
- package/dist/BrowserDatabaseFactory-WD4VX2VZ.js.map +1 -0
- package/dist/IncludeResolver-RCKQGNPZ.js +385 -0
- package/dist/IncludeResolver-RCKQGNPZ.js.map +1 -0
- package/dist/IncludeResolver-WGSQDMS7.js +385 -0
- package/dist/IncludeResolver-WGSQDMS7.js.map +1 -0
- package/dist/NodeDatabaseFactory-J4Z36UF3.js +165 -0
- package/dist/NodeDatabaseFactory-J4Z36UF3.js.map +1 -0
- package/dist/NodeDatabaseFactory-QIEKAXBM.js +10 -0
- package/dist/NodeDatabaseFactory-QIEKAXBM.js.map +1 -0
- package/dist/NodeSqliteEngine-HJSAYE4E.js +383 -0
- package/dist/NodeSqliteEngine-HJSAYE4E.js.map +1 -0
- package/dist/NodeSqliteEngine-I5SLWLME.js +383 -0
- package/dist/NodeSqliteEngine-I5SLWLME.js.map +1 -0
- package/dist/browser.cjs +3779 -3370
- package/dist/browser.d.cts +18 -1
- package/dist/browser.d.ts +18 -1
- package/dist/browser.js +3750 -3341
- package/dist/chunk-3PZWHUZO.js +4153 -0
- package/dist/chunk-3PZWHUZO.js.map +1 -0
- package/dist/chunk-53MS4MN7.js +373 -0
- package/dist/chunk-53MS4MN7.js.map +1 -0
- package/dist/chunk-65G2P4GL.js +709 -0
- package/dist/chunk-65G2P4GL.js.map +1 -0
- package/dist/chunk-6UX3YSCW.js +4151 -0
- package/dist/chunk-6UX3YSCW.js.map +1 -0
- package/dist/chunk-DANSD6BE.js +709 -0
- package/dist/chunk-DANSD6BE.js.map +1 -0
- package/dist/chunk-DF3JEQXA.js +373 -0
- package/dist/chunk-DF3JEQXA.js.map +1 -0
- package/dist/chunk-GO3APTPX.js +61 -0
- package/dist/chunk-GO3APTPX.js.map +1 -0
- package/dist/chunk-ID4U6IQC.js +53 -0
- package/dist/chunk-ID4U6IQC.js.map +1 -0
- package/dist/chunk-RQVS3LVL.js +165 -0
- package/dist/chunk-RQVS3LVL.js.map +1 -0
- package/dist/client.cjs +837 -0
- package/dist/client.d.cts +1101 -0
- package/dist/client.d.ts +1101 -0
- package/dist/client.js +806 -0
- package/dist/cloudflare-do.cjs +3637 -0
- package/dist/cloudflare-do.d.cts +1366 -0
- package/dist/cloudflare-do.d.ts +1366 -0
- package/dist/cloudflare-do.js +3614 -0
- package/dist/cloudflare.cjs +1048 -0
- package/dist/cloudflare.d.cts +1381 -0
- package/dist/cloudflare.d.ts +1381 -0
- package/dist/cloudflare.js +1017 -0
- package/dist/codegen.cjs +260 -19
- package/dist/environment-TOTQICSE.js +17 -0
- package/dist/environment-TOTQICSE.js.map +1 -0
- package/dist/index.cjs +1905 -1492
- package/dist/index.d.cts +19 -2
- package/dist/index.d.ts +19 -2
- package/dist/index.js +1870 -1457
- package/dist/node.cjs +4779 -4366
- package/dist/node.d.cts +18 -1
- package/dist/node.d.ts +18 -1
- package/dist/node.js +4758 -4345
- package/package.json +42 -13
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/models/StringSet.ts
|
|
34
|
+
var init_StringSet = __esm({
|
|
35
|
+
"src/models/StringSet.ts"() {
|
|
36
|
+
"use strict";
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// src/types/documentTypes.ts
|
|
41
|
+
var init_documentTypes = __esm({
|
|
42
|
+
"src/types/documentTypes.ts"() {
|
|
43
|
+
"use strict";
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// src/types/queryTypes.ts
|
|
48
|
+
var init_queryTypes = __esm({
|
|
49
|
+
"src/types/queryTypes.ts"() {
|
|
50
|
+
"use strict";
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// src/query/CursorManager.ts
|
|
55
|
+
var init_CursorManager = __esm({
|
|
56
|
+
"src/query/CursorManager.ts"() {
|
|
57
|
+
"use strict";
|
|
58
|
+
init_queryTypes();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// src/utils/sql.ts
|
|
63
|
+
var init_sql = __esm({
|
|
64
|
+
"src/utils/sql.ts"() {
|
|
65
|
+
"use strict";
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// src/utils/patterns.ts
|
|
70
|
+
var init_patterns = __esm({
|
|
71
|
+
"src/utils/patterns.ts"() {
|
|
72
|
+
"use strict";
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// src/query/DocumentQueryTranslator.ts
|
|
77
|
+
var init_DocumentQueryTranslator = __esm({
|
|
78
|
+
"src/query/DocumentQueryTranslator.ts"() {
|
|
79
|
+
"use strict";
|
|
80
|
+
init_queryTypes();
|
|
81
|
+
init_CursorManager();
|
|
82
|
+
init_sql();
|
|
83
|
+
init_patterns();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// src/models/BaseModel.ts
|
|
88
|
+
var Y, import_ulid, Logger;
|
|
89
|
+
var init_BaseModel = __esm({
|
|
90
|
+
"src/models/BaseModel.ts"() {
|
|
91
|
+
"use strict";
|
|
92
|
+
Y = __toESM(require("yjs"), 1);
|
|
93
|
+
import_ulid = require("ulid");
|
|
94
|
+
init_StringSet();
|
|
95
|
+
init_documentTypes();
|
|
96
|
+
init_DocumentQueryTranslator();
|
|
97
|
+
init_CursorManager();
|
|
98
|
+
init_sql();
|
|
99
|
+
Logger = class {
|
|
100
|
+
static _logLevel = 1 /* ERROR */;
|
|
101
|
+
static _logCallback = null;
|
|
102
|
+
static setLogLevel(level) {
|
|
103
|
+
this._logLevel = level;
|
|
104
|
+
}
|
|
105
|
+
static getLogLevel() {
|
|
106
|
+
return this._logLevel;
|
|
107
|
+
}
|
|
108
|
+
static setLogCallback(callback) {
|
|
109
|
+
this._logCallback = callback;
|
|
110
|
+
}
|
|
111
|
+
static error(message, ...args) {
|
|
112
|
+
if (this._logLevel >= 1 /* ERROR */) {
|
|
113
|
+
const fullMessage = args.length > 0 ? `${message} ${args.map(
|
|
114
|
+
(arg) => typeof arg === "object" ? JSON.stringify(arg) : String(arg)
|
|
115
|
+
).join(" ")}` : message;
|
|
116
|
+
console.error(`[ERROR] ${message}`, ...args);
|
|
117
|
+
if (this._logCallback) {
|
|
118
|
+
this._logCallback(fullMessage, 1 /* ERROR */);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
static warn(message, ...args) {
|
|
123
|
+
if (this._logLevel >= 2 /* WARN */) {
|
|
124
|
+
const fullMessage = args.length > 0 ? `${message} ${args.map(
|
|
125
|
+
(arg) => typeof arg === "object" ? JSON.stringify(arg) : String(arg)
|
|
126
|
+
).join(" ")}` : message;
|
|
127
|
+
console.warn(`[WARN] ${message}`, ...args);
|
|
128
|
+
if (this._logCallback) {
|
|
129
|
+
this._logCallback(fullMessage, 2 /* WARN */);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
static info(message, ...args) {
|
|
134
|
+
if (this._logLevel >= 3 /* INFO */) {
|
|
135
|
+
const fullMessage = args.length > 0 ? `${message} ${args.map(
|
|
136
|
+
(arg) => typeof arg === "object" ? JSON.stringify(arg) : String(arg)
|
|
137
|
+
).join(" ")}` : message;
|
|
138
|
+
console.log(`[INFO] ${message}`, ...args);
|
|
139
|
+
if (this._logCallback) {
|
|
140
|
+
this._logCallback(fullMessage, 3 /* INFO */);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
static debug(message, ...args) {
|
|
145
|
+
if (this._logLevel >= 4 /* DEBUG */) {
|
|
146
|
+
const fullMessage = args.length > 0 ? `${message} ${args.map(
|
|
147
|
+
(arg) => typeof arg === "object" ? JSON.stringify(arg) : String(arg)
|
|
148
|
+
).join(" ")}` : message;
|
|
149
|
+
console.log(`[DEBUG] ${message}`, ...args);
|
|
150
|
+
if (this._logCallback) {
|
|
151
|
+
this._logCallback(fullMessage, 4 /* DEBUG */);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
static verbose(message, ...args) {
|
|
156
|
+
if (this._logLevel >= 5 /* VERBOSE */) {
|
|
157
|
+
const fullMessage = args.length > 0 ? `${message} ${args.map(
|
|
158
|
+
(arg) => typeof arg === "object" ? JSON.stringify(arg) : String(arg)
|
|
159
|
+
).join(" ")}` : message;
|
|
160
|
+
console.log(`[VERBOSE] ${message}`, ...args);
|
|
161
|
+
if (this._logCallback) {
|
|
162
|
+
this._logCallback(fullMessage, 5 /* VERBOSE */);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// src/client.ts
|
|
171
|
+
var client_exports = {};
|
|
172
|
+
__export(client_exports, {
|
|
173
|
+
DOClientEngine: () => DOClientEngine,
|
|
174
|
+
connectDoDb: () => connectDoDb
|
|
175
|
+
});
|
|
176
|
+
module.exports = __toCommonJS(client_exports);
|
|
177
|
+
|
|
178
|
+
// src/initialize-do.ts
|
|
179
|
+
init_BaseModel();
|
|
180
|
+
|
|
181
|
+
// src/engines/DatabaseEngine.ts
|
|
182
|
+
var DatabaseEngine = class {
|
|
183
|
+
createTable(_modelName, _schema, _options) {
|
|
184
|
+
throw new Error("Method not implemented.");
|
|
185
|
+
}
|
|
186
|
+
createStringSetJunctionTable(_modelName, _fieldName) {
|
|
187
|
+
throw new Error("Method not implemented.");
|
|
188
|
+
}
|
|
189
|
+
insertStringSetValues(_modelName, _fieldName, _recordId, _values) {
|
|
190
|
+
throw new Error("Method not implemented.");
|
|
191
|
+
}
|
|
192
|
+
removeStringSetValues(_modelName, _fieldName, _recordId, _values) {
|
|
193
|
+
throw new Error("Method not implemented.");
|
|
194
|
+
}
|
|
195
|
+
insert(_modelName, _data) {
|
|
196
|
+
throw new Error("Method not implemented.");
|
|
197
|
+
}
|
|
198
|
+
delete(_modelName, _id) {
|
|
199
|
+
throw new Error("Method not implemented.");
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Deletes all records for a specific document from the given model table.
|
|
203
|
+
* This is used when disconnecting a document to remove all its data.
|
|
204
|
+
* @param modelName The name of the model/table
|
|
205
|
+
* @param docId The document ID to filter by
|
|
206
|
+
*/
|
|
207
|
+
deleteByDocumentId(_modelName, _docId) {
|
|
208
|
+
throw new Error("Method not implemented.");
|
|
209
|
+
}
|
|
210
|
+
getTableName(_modelName) {
|
|
211
|
+
throw new Error("Method not implemented.");
|
|
212
|
+
}
|
|
213
|
+
// Transaction support
|
|
214
|
+
async withTransaction(_callback) {
|
|
215
|
+
throw new Error("Method not implemented.");
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// src/engines/cloudflare/DOClientEngine.ts
|
|
220
|
+
var DOClientEngine = class extends DatabaseEngine {
|
|
221
|
+
endpoint;
|
|
222
|
+
customFetch;
|
|
223
|
+
authorization;
|
|
224
|
+
timeout;
|
|
225
|
+
currentDocId = null;
|
|
226
|
+
constructor(config) {
|
|
227
|
+
super();
|
|
228
|
+
this.endpoint = config.endpoint.replace(/\/$/, "");
|
|
229
|
+
this.customFetch = config.fetch || fetch;
|
|
230
|
+
this.authorization = config.authorization;
|
|
231
|
+
this.timeout = config.timeout || 3e4;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Set the current document ID for subsequent operations.
|
|
235
|
+
*/
|
|
236
|
+
setCurrentDocument(docId) {
|
|
237
|
+
this.currentDocId = docId;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get the current document ID.
|
|
241
|
+
*/
|
|
242
|
+
getCurrentDocument() {
|
|
243
|
+
return this.currentDocId;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Build the URL for a DO endpoint.
|
|
247
|
+
*/
|
|
248
|
+
buildUrl(path, extraParams) {
|
|
249
|
+
if (!this.currentDocId) {
|
|
250
|
+
throw new Error("No document ID set. Call setCurrentDocument() first.");
|
|
251
|
+
}
|
|
252
|
+
const url = new URL(`${this.endpoint}${path}`);
|
|
253
|
+
url.searchParams.set("docId", this.currentDocId);
|
|
254
|
+
if (extraParams) {
|
|
255
|
+
for (const [key, value] of Object.entries(extraParams)) {
|
|
256
|
+
url.searchParams.set(key, value);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return url.toString();
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Make a fetch request to the DO.
|
|
263
|
+
*/
|
|
264
|
+
async doFetch(path, body) {
|
|
265
|
+
const url = this.buildUrl(path);
|
|
266
|
+
const headers = {
|
|
267
|
+
"Content-Type": "application/json"
|
|
268
|
+
};
|
|
269
|
+
if (this.authorization) {
|
|
270
|
+
headers["Authorization"] = this.authorization;
|
|
271
|
+
}
|
|
272
|
+
const controller = new AbortController();
|
|
273
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
274
|
+
try {
|
|
275
|
+
const response = await this.customFetch(url, {
|
|
276
|
+
method: "POST",
|
|
277
|
+
headers,
|
|
278
|
+
body: JSON.stringify(body),
|
|
279
|
+
signal: controller.signal
|
|
280
|
+
});
|
|
281
|
+
clearTimeout(timeoutId);
|
|
282
|
+
if (!response.ok) {
|
|
283
|
+
const errorBody = await response.json();
|
|
284
|
+
throw new Error(errorBody.error || `HTTP ${response.status}`);
|
|
285
|
+
}
|
|
286
|
+
return response.json();
|
|
287
|
+
} catch (error) {
|
|
288
|
+
clearTimeout(timeoutId);
|
|
289
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
290
|
+
throw new Error(`Request timeout after ${this.timeout}ms`);
|
|
291
|
+
}
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Query records from the DO.
|
|
297
|
+
*/
|
|
298
|
+
async queryModel(modelName, filter = {}, options = {}) {
|
|
299
|
+
const request = {
|
|
300
|
+
modelName,
|
|
301
|
+
filter,
|
|
302
|
+
options
|
|
303
|
+
};
|
|
304
|
+
const response = await this.doFetch("/query", request);
|
|
305
|
+
return {
|
|
306
|
+
data: response.data,
|
|
307
|
+
hasMore: response.hasMore ?? false,
|
|
308
|
+
nextCursor: response.nextCursor,
|
|
309
|
+
prevCursor: response.prevCursor
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Save a record to the DO.
|
|
314
|
+
*/
|
|
315
|
+
async saveModel(modelName, id, data, stringSets, options) {
|
|
316
|
+
const request = {
|
|
317
|
+
modelName,
|
|
318
|
+
id,
|
|
319
|
+
data,
|
|
320
|
+
stringSets,
|
|
321
|
+
ifNotExists: options?.ifNotExists,
|
|
322
|
+
condition: options?.condition
|
|
323
|
+
};
|
|
324
|
+
const response = await this.doFetch("/save", request);
|
|
325
|
+
return response.id;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Patch (partial update) a record in the DO.
|
|
329
|
+
* Only the provided fields are updated; existing fields are preserved.
|
|
330
|
+
*/
|
|
331
|
+
async patchModel(modelName, id, data, stringSets, options) {
|
|
332
|
+
const request = {
|
|
333
|
+
modelName,
|
|
334
|
+
id,
|
|
335
|
+
data,
|
|
336
|
+
stringSets,
|
|
337
|
+
condition: options?.condition
|
|
338
|
+
};
|
|
339
|
+
const response = await this.doFetch("/patch", request);
|
|
340
|
+
return response.id;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Delete a record from the DO.
|
|
344
|
+
*/
|
|
345
|
+
async deleteModel(modelName, id, options) {
|
|
346
|
+
const request = {
|
|
347
|
+
modelName,
|
|
348
|
+
id,
|
|
349
|
+
condition: options?.condition
|
|
350
|
+
};
|
|
351
|
+
const response = await this.doFetch("/delete", request);
|
|
352
|
+
return response.success;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Count records matching a filter.
|
|
356
|
+
*/
|
|
357
|
+
async countModel(modelName, filter = {}) {
|
|
358
|
+
const request = {
|
|
359
|
+
modelName,
|
|
360
|
+
filter
|
|
361
|
+
};
|
|
362
|
+
const response = await this.doFetch("/count", request);
|
|
363
|
+
return response.count;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Aggregate records with groupBy and operations (count, sum, avg, min, max).
|
|
367
|
+
*/
|
|
368
|
+
async aggregateModel(modelName, options) {
|
|
369
|
+
const request = {
|
|
370
|
+
modelName,
|
|
371
|
+
options
|
|
372
|
+
};
|
|
373
|
+
const response = await this.doFetch("/aggregate", request);
|
|
374
|
+
return response.result;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Atomically add values to StringSet fields on a record.
|
|
378
|
+
*/
|
|
379
|
+
async addToStringSet(modelName, id, sets, options) {
|
|
380
|
+
const request = { modelName, id, sets, condition: options?.condition };
|
|
381
|
+
await this.doFetch("/stringset/add", request);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Atomically remove values from StringSet fields on a record.
|
|
385
|
+
*/
|
|
386
|
+
async removeFromStringSet(modelName, id, sets, options) {
|
|
387
|
+
const request = { modelName, id, sets, condition: options?.condition };
|
|
388
|
+
await this.doFetch("/stringset/remove", request);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Atomically increment/decrement numeric fields on a record.
|
|
392
|
+
* Returns the new values after the increment.
|
|
393
|
+
*/
|
|
394
|
+
async incrementFields(modelName, id, fields, options) {
|
|
395
|
+
const request = { modelName, id, fields, condition: options?.condition };
|
|
396
|
+
const response = await this.doFetch("/increment", request);
|
|
397
|
+
return response.values;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Execute multiple save/patch/delete operations in a single request.
|
|
401
|
+
* All operations run in a single transaction on the server.
|
|
402
|
+
*/
|
|
403
|
+
async batchWrite(operations) {
|
|
404
|
+
const request = { operations };
|
|
405
|
+
const response = await this.doFetch("/batch", request);
|
|
406
|
+
return response.results;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Check if the DO is healthy.
|
|
410
|
+
*/
|
|
411
|
+
async healthCheck() {
|
|
412
|
+
const url = this.buildUrl("/health");
|
|
413
|
+
const response = await this.customFetch(url, {
|
|
414
|
+
method: "GET",
|
|
415
|
+
headers: this.authorization ? { Authorization: this.authorization } : void 0
|
|
416
|
+
});
|
|
417
|
+
if (!response.ok) {
|
|
418
|
+
throw new Error(`Health check failed: HTTP ${response.status}`);
|
|
419
|
+
}
|
|
420
|
+
return response.json();
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Batch sync indexes: send all desired indexes in one request.
|
|
424
|
+
* The DO compares against its _indexes table and registers only what's missing.
|
|
425
|
+
* Returns the number of indexes/constraints newly registered.
|
|
426
|
+
*/
|
|
427
|
+
async syncIndexesBatch(request) {
|
|
428
|
+
const response = await this.doFetch("/indexes/sync", request);
|
|
429
|
+
return response.registered;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Register an index on a model field.
|
|
433
|
+
* Creates a SQLite index on json_extract(data_json, '$.fieldName').
|
|
434
|
+
* Set unique=true to enforce uniqueness on this field.
|
|
435
|
+
*/
|
|
436
|
+
async registerIndex(modelName, fieldName, fieldType = "string", unique = false) {
|
|
437
|
+
const request = {
|
|
438
|
+
modelName,
|
|
439
|
+
fieldName,
|
|
440
|
+
fieldType,
|
|
441
|
+
unique
|
|
442
|
+
};
|
|
443
|
+
await this.doFetch("/index/register", request);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Drop an index from a model field.
|
|
447
|
+
*/
|
|
448
|
+
async dropIndex(modelName, fieldName) {
|
|
449
|
+
const request = {
|
|
450
|
+
modelName,
|
|
451
|
+
fieldName
|
|
452
|
+
};
|
|
453
|
+
await this.doFetch("/index/drop", request);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* List indexes, optionally filtered by model name.
|
|
457
|
+
*/
|
|
458
|
+
async listIndexes(modelName) {
|
|
459
|
+
const url = this.buildUrl(
|
|
460
|
+
"/indexes",
|
|
461
|
+
modelName ? { modelName } : void 0
|
|
462
|
+
);
|
|
463
|
+
const response = await this.customFetch(url, {
|
|
464
|
+
method: "GET",
|
|
465
|
+
headers: this.authorization ? { Authorization: this.authorization } : void 0
|
|
466
|
+
});
|
|
467
|
+
if (!response.ok) {
|
|
468
|
+
throw new Error(`List indexes failed: HTTP ${response.status}`);
|
|
469
|
+
}
|
|
470
|
+
const body = await response.json();
|
|
471
|
+
return body.indexes;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Describe tracked fields for a model.
|
|
475
|
+
*/
|
|
476
|
+
async describe(modelName) {
|
|
477
|
+
const url = this.buildUrl("/describe", { modelName });
|
|
478
|
+
const response = await this.customFetch(url, {
|
|
479
|
+
method: "GET",
|
|
480
|
+
headers: this.authorization ? { Authorization: this.authorization } : void 0
|
|
481
|
+
});
|
|
482
|
+
if (!response.ok) {
|
|
483
|
+
throw new Error(`Describe failed: HTTP ${response.status}`);
|
|
484
|
+
}
|
|
485
|
+
const body = await response.json();
|
|
486
|
+
return body.fields;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Register a composite unique constraint across multiple fields.
|
|
490
|
+
*/
|
|
491
|
+
async registerUniqueConstraint(modelName, constraintName, fields) {
|
|
492
|
+
const request = {
|
|
493
|
+
modelName,
|
|
494
|
+
constraintName,
|
|
495
|
+
fields
|
|
496
|
+
};
|
|
497
|
+
await this.doFetch(
|
|
498
|
+
"/unique-constraint/register",
|
|
499
|
+
request
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Drop a composite unique constraint.
|
|
504
|
+
*/
|
|
505
|
+
async dropUniqueConstraint(modelName, constraintName) {
|
|
506
|
+
const request = {
|
|
507
|
+
modelName,
|
|
508
|
+
constraintName
|
|
509
|
+
};
|
|
510
|
+
await this.doFetch(
|
|
511
|
+
"/unique-constraint/drop",
|
|
512
|
+
request
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* List composite unique constraints, optionally filtered by model name.
|
|
517
|
+
*/
|
|
518
|
+
async listUniqueConstraints(modelName) {
|
|
519
|
+
const url = this.buildUrl(
|
|
520
|
+
"/unique-constraints",
|
|
521
|
+
modelName ? { modelName } : void 0
|
|
522
|
+
);
|
|
523
|
+
const response = await this.customFetch(url, {
|
|
524
|
+
method: "GET",
|
|
525
|
+
headers: this.authorization ? { Authorization: this.authorization } : void 0
|
|
526
|
+
});
|
|
527
|
+
if (!response.ok) {
|
|
528
|
+
throw new Error(
|
|
529
|
+
`List unique constraints failed: HTTP ${response.status}`
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
const body = await response.json();
|
|
533
|
+
return body.constraints;
|
|
534
|
+
}
|
|
535
|
+
// ========================================
|
|
536
|
+
// DatabaseEngine interface implementation
|
|
537
|
+
// ========================================
|
|
538
|
+
/**
|
|
539
|
+
* Ensure the engine is ready.
|
|
540
|
+
* For DOClient, this verifies connectivity.
|
|
541
|
+
*/
|
|
542
|
+
async ensureReady() {
|
|
543
|
+
if (this.currentDocId) {
|
|
544
|
+
await this.healthCheck();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Execute raw SQL.
|
|
549
|
+
* Not supported in DO client mode - use queryModel instead.
|
|
550
|
+
*/
|
|
551
|
+
async query(_sql, _params) {
|
|
552
|
+
throw new Error(
|
|
553
|
+
"Raw SQL queries not supported in DO client mode. Use queryModel() instead."
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Get last error message.
|
|
558
|
+
*/
|
|
559
|
+
getLastErrorMessage() {
|
|
560
|
+
return void 0;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Get table schema.
|
|
564
|
+
* Not directly supported - schema is managed by the DO.
|
|
565
|
+
*/
|
|
566
|
+
async getTableSchema(_tableName) {
|
|
567
|
+
throw new Error("getTableSchema not supported in DO client mode");
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Destroy the engine.
|
|
571
|
+
* Nothing to clean up for the client.
|
|
572
|
+
*/
|
|
573
|
+
async destroy() {
|
|
574
|
+
this.currentDocId = null;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Create table.
|
|
578
|
+
* No-op in DO client mode - schema is managed by the DO.
|
|
579
|
+
*/
|
|
580
|
+
async createTable(_modelName, _schema, _options) {
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Create StringSet junction table.
|
|
584
|
+
* No-op in DO client mode.
|
|
585
|
+
*/
|
|
586
|
+
async createStringSetJunctionTable(_modelName, _fieldName) {
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Insert a record.
|
|
590
|
+
* Delegates to saveModel.
|
|
591
|
+
*/
|
|
592
|
+
async insert(modelName, data) {
|
|
593
|
+
const { id, ...rest } = data;
|
|
594
|
+
await this.saveModel(modelName, id, rest);
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Delete a record.
|
|
598
|
+
* Delegates to deleteModel.
|
|
599
|
+
*/
|
|
600
|
+
async delete(modelName, id) {
|
|
601
|
+
await this.deleteModel(modelName, id);
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Get table name.
|
|
605
|
+
* All models use 'records' in JSON schema.
|
|
606
|
+
*/
|
|
607
|
+
getTableName(_modelName) {
|
|
608
|
+
return "records";
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Transaction support.
|
|
612
|
+
* DO client doesn't support client-side transactions.
|
|
613
|
+
* Operations are atomic on the DO side.
|
|
614
|
+
*/
|
|
615
|
+
async withTransaction(callback) {
|
|
616
|
+
const ops = {
|
|
617
|
+
insert: async (modelName, data) => {
|
|
618
|
+
await this.insert(modelName, data);
|
|
619
|
+
},
|
|
620
|
+
delete: async (modelName, id) => {
|
|
621
|
+
await this.delete(modelName, id);
|
|
622
|
+
},
|
|
623
|
+
query: async (_sql, _params) => {
|
|
624
|
+
throw new Error("Raw SQL not supported in DO client transactions");
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
return callback(ops);
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
// src/initialize-do.ts
|
|
632
|
+
function resolveModelName(model) {
|
|
633
|
+
if (typeof model === "string") {
|
|
634
|
+
if (!model) throw new Error("Model name must be a non-empty string");
|
|
635
|
+
return model;
|
|
636
|
+
}
|
|
637
|
+
const name = model.modelName;
|
|
638
|
+
if (!name) {
|
|
639
|
+
throw new Error(
|
|
640
|
+
`Model ${model.name} has no modelName. Ensure it was defined with defineModelSchema + attachSchemaToClass.`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
return name;
|
|
644
|
+
}
|
|
645
|
+
function createModelAccessor(engine, modelName) {
|
|
646
|
+
return {
|
|
647
|
+
query: (filter = {}, options = {}) => {
|
|
648
|
+
return engine.queryModel(modelName, filter, options);
|
|
649
|
+
},
|
|
650
|
+
find: async (id) => {
|
|
651
|
+
const result = await engine.queryModel(modelName, { id }, {});
|
|
652
|
+
return result.data.length > 0 ? result.data[0] : null;
|
|
653
|
+
},
|
|
654
|
+
save: (data, options) => {
|
|
655
|
+
if (!data.id) {
|
|
656
|
+
throw new Error("Record must have an 'id' field");
|
|
657
|
+
}
|
|
658
|
+
let stringSets;
|
|
659
|
+
let ifNotExists;
|
|
660
|
+
let condition;
|
|
661
|
+
if (options && typeof options === "object") {
|
|
662
|
+
if (options.ifNotExists !== void 0 || options.stringSets !== void 0 || options.condition !== void 0) {
|
|
663
|
+
stringSets = options.stringSets;
|
|
664
|
+
ifNotExists = options.ifNotExists;
|
|
665
|
+
condition = options.condition;
|
|
666
|
+
} else {
|
|
667
|
+
stringSets = options;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return engine.saveModel(modelName, data.id, data, stringSets, { ifNotExists, condition });
|
|
671
|
+
},
|
|
672
|
+
patch: (id, data, options) => {
|
|
673
|
+
let stringSets;
|
|
674
|
+
let condition;
|
|
675
|
+
if (options && typeof options === "object") {
|
|
676
|
+
if (options.condition !== void 0 || options.stringSets !== void 0) {
|
|
677
|
+
stringSets = options.stringSets;
|
|
678
|
+
condition = options.condition;
|
|
679
|
+
} else {
|
|
680
|
+
stringSets = options;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return engine.patchModel(modelName, id, data, stringSets, { condition });
|
|
684
|
+
},
|
|
685
|
+
delete: (id, options) => {
|
|
686
|
+
return engine.deleteModel(modelName, id, { condition: options?.condition });
|
|
687
|
+
},
|
|
688
|
+
count: (filter = {}) => {
|
|
689
|
+
return engine.countModel(modelName, filter);
|
|
690
|
+
},
|
|
691
|
+
aggregate: (options) => {
|
|
692
|
+
return engine.aggregateModel(modelName, options);
|
|
693
|
+
},
|
|
694
|
+
increment: (id, fields, options) => {
|
|
695
|
+
return engine.incrementFields(modelName, id, fields, { condition: options?.condition });
|
|
696
|
+
},
|
|
697
|
+
addToSet: (id, sets, options) => {
|
|
698
|
+
return engine.addToStringSet(modelName, id, sets, { condition: options?.condition });
|
|
699
|
+
},
|
|
700
|
+
removeFromSet: (id, sets, options) => {
|
|
701
|
+
return engine.removeFromStringSet(modelName, id, sets, { condition: options?.condition });
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
function extractModelSyncState(modelClass) {
|
|
706
|
+
const schema = modelClass.getSchema?.();
|
|
707
|
+
if (!schema || !schema.fields || !schema.options?.name) {
|
|
708
|
+
throw new Error(
|
|
709
|
+
`Cannot sync indexes: model ${modelClass.name} has no schema. Ensure it was defined with defineModelSchema + attachAndRegisterModel.`
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
const modelName = schema.options.name;
|
|
713
|
+
const fields = schema.fields;
|
|
714
|
+
const indexes = [];
|
|
715
|
+
for (const [fieldName, opts] of fields.entries()) {
|
|
716
|
+
if (fieldName === "id") continue;
|
|
717
|
+
if (!opts.indexed && !opts.unique) continue;
|
|
718
|
+
indexes.push({
|
|
719
|
+
fieldName,
|
|
720
|
+
fieldType: opts.type || "string",
|
|
721
|
+
unique: opts.unique ?? false
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
const uniqueConstraints = (schema.options.uniqueConstraints ?? []).map(
|
|
725
|
+
(c) => ({ name: c.name, fields: c.fields })
|
|
726
|
+
);
|
|
727
|
+
return { modelName, indexes, uniqueConstraints };
|
|
728
|
+
}
|
|
729
|
+
function buildSyncIndexes(engine) {
|
|
730
|
+
return async (modelClass) => {
|
|
731
|
+
const modelState = extractModelSyncState(modelClass);
|
|
732
|
+
return engine.syncIndexesBatch({ models: [modelState] });
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
function connectDoDb(options) {
|
|
736
|
+
const { endpoint, id, models, authorization, fetch: customFetch, timeout } = options;
|
|
737
|
+
const engine = new DOClientEngine({
|
|
738
|
+
endpoint,
|
|
739
|
+
authorization,
|
|
740
|
+
fetch: customFetch,
|
|
741
|
+
timeout
|
|
742
|
+
});
|
|
743
|
+
engine.setCurrentDocument(id);
|
|
744
|
+
const syncIndexes = buildSyncIndexes(engine);
|
|
745
|
+
const db = {
|
|
746
|
+
engine,
|
|
747
|
+
docId: id,
|
|
748
|
+
// Ad-hoc methods
|
|
749
|
+
query: (model, filter = {}, opts = {}) => {
|
|
750
|
+
return engine.queryModel(resolveModelName(model), filter, opts);
|
|
751
|
+
},
|
|
752
|
+
find: async (model, findId) => {
|
|
753
|
+
const result = await engine.queryModel(resolveModelName(model), { id: findId }, {});
|
|
754
|
+
return result.data.length > 0 ? result.data[0] : null;
|
|
755
|
+
},
|
|
756
|
+
save: (model, data, options2) => {
|
|
757
|
+
if (!data.id) {
|
|
758
|
+
throw new Error("Record must have an 'id' field");
|
|
759
|
+
}
|
|
760
|
+
let stringSets;
|
|
761
|
+
let ifNotExists;
|
|
762
|
+
let condition;
|
|
763
|
+
if (options2 && typeof options2 === "object") {
|
|
764
|
+
if (options2.ifNotExists !== void 0 || options2.stringSets !== void 0 || options2.condition !== void 0) {
|
|
765
|
+
stringSets = options2.stringSets;
|
|
766
|
+
ifNotExists = options2.ifNotExists;
|
|
767
|
+
condition = options2.condition;
|
|
768
|
+
} else {
|
|
769
|
+
stringSets = options2;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return engine.saveModel(resolveModelName(model), data.id, data, stringSets, { ifNotExists, condition });
|
|
773
|
+
},
|
|
774
|
+
patch: (model, id2, data, options2) => {
|
|
775
|
+
let stringSets;
|
|
776
|
+
let condition;
|
|
777
|
+
if (options2 && typeof options2 === "object") {
|
|
778
|
+
if (options2.condition !== void 0 || options2.stringSets !== void 0) {
|
|
779
|
+
stringSets = options2.stringSets;
|
|
780
|
+
condition = options2.condition;
|
|
781
|
+
} else {
|
|
782
|
+
stringSets = options2;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return engine.patchModel(resolveModelName(model), id2, data, stringSets, { condition });
|
|
786
|
+
},
|
|
787
|
+
delete: (model, deleteId, options2) => {
|
|
788
|
+
return engine.deleteModel(resolveModelName(model), deleteId, { condition: options2?.condition });
|
|
789
|
+
},
|
|
790
|
+
count: (model, filter = {}) => {
|
|
791
|
+
return engine.countModel(resolveModelName(model), filter);
|
|
792
|
+
},
|
|
793
|
+
aggregate: (model, options2) => {
|
|
794
|
+
return engine.aggregateModel(resolveModelName(model), options2);
|
|
795
|
+
},
|
|
796
|
+
increment: (model, id2, fields, options2) => {
|
|
797
|
+
return engine.incrementFields(resolveModelName(model), id2, fields, { condition: options2?.condition });
|
|
798
|
+
},
|
|
799
|
+
addToSet: (model, id2, sets, options2) => {
|
|
800
|
+
return engine.addToStringSet(resolveModelName(model), id2, sets, { condition: options2?.condition });
|
|
801
|
+
},
|
|
802
|
+
removeFromSet: (model, id2, sets, options2) => {
|
|
803
|
+
return engine.removeFromStringSet(resolveModelName(model), id2, sets, { condition: options2?.condition });
|
|
804
|
+
},
|
|
805
|
+
batch: (operations) => {
|
|
806
|
+
return engine.batchWrite(operations);
|
|
807
|
+
},
|
|
808
|
+
// Schema introspection
|
|
809
|
+
describe: (modelName) => {
|
|
810
|
+
return engine.describe(modelName);
|
|
811
|
+
},
|
|
812
|
+
// Index management
|
|
813
|
+
registerIndex: (modelName, fieldName, fieldType = "string", unique = false) => engine.registerIndex(modelName, fieldName, fieldType, unique),
|
|
814
|
+
dropIndex: (modelName, fieldName) => engine.dropIndex(modelName, fieldName),
|
|
815
|
+
listIndexes: (modelName) => engine.listIndexes(modelName),
|
|
816
|
+
registerUniqueConstraint: (modelName, constraintName, fields) => engine.registerUniqueConstraint(modelName, constraintName, fields),
|
|
817
|
+
dropUniqueConstraint: (modelName, constraintName) => engine.dropUniqueConstraint(modelName, constraintName),
|
|
818
|
+
listUniqueConstraints: (modelName) => engine.listUniqueConstraints(modelName),
|
|
819
|
+
syncIndexes,
|
|
820
|
+
syncAllIndexes: async () => {
|
|
821
|
+
if (!models || models.length === 0) return 0;
|
|
822
|
+
const request = {
|
|
823
|
+
models: models.map(extractModelSyncState)
|
|
824
|
+
};
|
|
825
|
+
return engine.syncIndexesBatch(request);
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
if (models) {
|
|
829
|
+
for (const model of models) {
|
|
830
|
+
const modelName = resolveModelName(model);
|
|
831
|
+
const className = model.name;
|
|
832
|
+
db[className] = createModelAccessor(engine, modelName);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
Logger.info(`[connectDoDb] Connected to document: ${id} at ${endpoint}`);
|
|
836
|
+
return db;
|
|
837
|
+
}
|