js-bao 0.2.10 → 0.3.0-alpha.0

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