js-bao 0.3.0 → 0.4.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.
- package/dist/browser.cjs +382 -143
- package/dist/browser.d.cts +127 -5
- package/dist/browser.d.ts +127 -5
- package/dist/browser.js +380 -141
- package/dist/client.cjs +16 -11
- package/dist/client.d.cts +3 -1
- package/dist/client.d.ts +3 -1
- package/dist/client.js +16 -11
- package/dist/cloudflare-do.cjs +937 -286
- package/dist/cloudflare-do.d.cts +517 -15
- package/dist/cloudflare-do.d.ts +517 -15
- package/dist/cloudflare-do.js +928 -286
- package/dist/cloudflare.cjs +573 -18
- package/dist/cloudflare.d.cts +147 -2
- package/dist/cloudflare.d.ts +147 -2
- package/dist/cloudflare.js +573 -18
- package/dist/codegen.cjs +7 -15
- package/dist/index.cjs +32 -154
- package/dist/index.d.cts +2 -5
- package/dist/index.d.ts +2 -5
- package/dist/index.js +32 -152
- package/dist/node.cjs +404 -151
- package/dist/node.d.cts +132 -5
- package/dist/node.d.ts +132 -5
- package/dist/node.js +390 -145
- package/package.json +6 -6
package/dist/cloudflare-do.js
CHANGED
|
@@ -1,3 +1,515 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __esm = (fn, res) => function __init() {
|
|
3
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// src/utils/sql.ts
|
|
7
|
+
function isValidIdentifier(name) {
|
|
8
|
+
return IDENTIFIER_PATTERN.test(name);
|
|
9
|
+
}
|
|
10
|
+
function assertValidIdentifier(name, context) {
|
|
11
|
+
if (!isValidIdentifier(name)) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`${context}: Identifier "${name}" must match ${IDENTIFIER_PATTERN.source}`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function quoteIdentifier(name) {
|
|
18
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
19
|
+
}
|
|
20
|
+
var IDENTIFIER_PATTERN;
|
|
21
|
+
var init_sql = __esm({
|
|
22
|
+
"src/utils/sql.ts"() {
|
|
23
|
+
"use strict";
|
|
24
|
+
IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// src/types/queryTypes.ts
|
|
29
|
+
var DocumentQueryError, InvalidOperatorError, InvalidFieldError, InvalidCursorError;
|
|
30
|
+
var init_queryTypes = __esm({
|
|
31
|
+
"src/types/queryTypes.ts"() {
|
|
32
|
+
"use strict";
|
|
33
|
+
DocumentQueryError = class _DocumentQueryError extends Error {
|
|
34
|
+
constructor(message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "DocumentQueryError";
|
|
37
|
+
Object.setPrototypeOf(this, _DocumentQueryError.prototype);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
InvalidOperatorError = class _InvalidOperatorError extends DocumentQueryError {
|
|
41
|
+
field;
|
|
42
|
+
operator;
|
|
43
|
+
fieldType;
|
|
44
|
+
constructor(message, field, operator, fieldType) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "InvalidOperatorError";
|
|
47
|
+
this.field = field;
|
|
48
|
+
this.operator = operator;
|
|
49
|
+
this.fieldType = fieldType;
|
|
50
|
+
Object.setPrototypeOf(this, _InvalidOperatorError.prototype);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
InvalidFieldError = class _InvalidFieldError extends DocumentQueryError {
|
|
54
|
+
field;
|
|
55
|
+
modelName;
|
|
56
|
+
constructor(message, field, modelName) {
|
|
57
|
+
super(message);
|
|
58
|
+
this.name = "InvalidFieldError";
|
|
59
|
+
this.field = field;
|
|
60
|
+
this.modelName = modelName;
|
|
61
|
+
Object.setPrototypeOf(this, _InvalidFieldError.prototype);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
InvalidCursorError = class _InvalidCursorError extends DocumentQueryError {
|
|
65
|
+
cursor;
|
|
66
|
+
constructor(message, cursor) {
|
|
67
|
+
super(message);
|
|
68
|
+
this.name = "InvalidCursorError";
|
|
69
|
+
this.cursor = cursor;
|
|
70
|
+
Object.setPrototypeOf(this, _InvalidCursorError.prototype);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// src/query/CursorManager.ts
|
|
77
|
+
var base64Encode, base64Decode, CursorManager;
|
|
78
|
+
var init_CursorManager = __esm({
|
|
79
|
+
"src/query/CursorManager.ts"() {
|
|
80
|
+
"use strict";
|
|
81
|
+
init_queryTypes();
|
|
82
|
+
base64Encode = (str) => {
|
|
83
|
+
if (typeof btoa !== "undefined") {
|
|
84
|
+
return btoa(str);
|
|
85
|
+
} else if (typeof Buffer !== "undefined") {
|
|
86
|
+
return Buffer.from(str, "utf-8").toString("base64");
|
|
87
|
+
} else {
|
|
88
|
+
throw new Error("No base64 encoding available");
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
base64Decode = (str) => {
|
|
92
|
+
if (typeof atob !== "undefined") {
|
|
93
|
+
return atob(str);
|
|
94
|
+
} else if (typeof Buffer !== "undefined") {
|
|
95
|
+
return Buffer.from(str, "base64").toString("utf-8");
|
|
96
|
+
} else {
|
|
97
|
+
throw new Error("No base64 decoding available");
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
CursorManager = class {
|
|
101
|
+
/**
|
|
102
|
+
* Encode cursor data to base64 string
|
|
103
|
+
*/
|
|
104
|
+
static encodeCursor(cursorData) {
|
|
105
|
+
try {
|
|
106
|
+
const jsonString = JSON.stringify(cursorData);
|
|
107
|
+
return base64Encode(jsonString);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
throw new InvalidCursorError(
|
|
110
|
+
`Failed to encode cursor: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
111
|
+
JSON.stringify(cursorData)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Decode base64 cursor string to cursor data
|
|
117
|
+
*/
|
|
118
|
+
static decodeCursor(cursor) {
|
|
119
|
+
try {
|
|
120
|
+
const jsonString = base64Decode(cursor);
|
|
121
|
+
const parsed = JSON.parse(jsonString);
|
|
122
|
+
if (!parsed || typeof parsed !== "object") {
|
|
123
|
+
throw new Error("Cursor must be an object");
|
|
124
|
+
}
|
|
125
|
+
if (!parsed.values || typeof parsed.values !== "object") {
|
|
126
|
+
throw new Error("Cursor must have values object");
|
|
127
|
+
}
|
|
128
|
+
if (!Array.isArray(parsed.sortFields)) {
|
|
129
|
+
throw new Error("Cursor must have sortFields array");
|
|
130
|
+
}
|
|
131
|
+
if (parsed.direction !== 1 && parsed.direction !== -1) {
|
|
132
|
+
throw new Error("Cursor direction must be 1 or -1");
|
|
133
|
+
}
|
|
134
|
+
return parsed;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
throw new InvalidCursorError(
|
|
137
|
+
`Failed to decode cursor: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
138
|
+
cursor
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Generate cursor from a record and sort specification
|
|
144
|
+
*/
|
|
145
|
+
static generateCursor(record, sortFields, direction) {
|
|
146
|
+
const values = {};
|
|
147
|
+
for (const field of sortFields) {
|
|
148
|
+
if (record.hasOwnProperty(field)) {
|
|
149
|
+
values[field] = record[field];
|
|
150
|
+
} else {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Cannot generate cursor: record missing sort field '${field}'`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const cursorData = {
|
|
157
|
+
values,
|
|
158
|
+
sortFields,
|
|
159
|
+
direction
|
|
160
|
+
};
|
|
161
|
+
return this.encodeCursor(cursorData);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Build SQL pagination conditions based on cursor
|
|
165
|
+
* Uses lexicographic ordering for stable pagination with multiple sort fields
|
|
166
|
+
*/
|
|
167
|
+
static buildPaginationConditions(cursor, currentSortFields, sortDirections, requestedDirection, fieldFormatter = (field) => field) {
|
|
168
|
+
if (!this.arraysEqual(cursor.sortFields, currentSortFields)) {
|
|
169
|
+
throw new InvalidCursorError(
|
|
170
|
+
`Cursor sort fields [${cursor.sortFields.join(
|
|
171
|
+
", "
|
|
172
|
+
)}] don't match query sort fields [${currentSortFields.join(", ")}]`,
|
|
173
|
+
JSON.stringify(cursor)
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const conditions = [];
|
|
177
|
+
const params = [];
|
|
178
|
+
const sortFields = cursor.sortFields;
|
|
179
|
+
const direction = requestedDirection;
|
|
180
|
+
for (let i = 0; i < sortFields.length; i++) {
|
|
181
|
+
const fieldConditions = [];
|
|
182
|
+
const fieldParams = [];
|
|
183
|
+
for (let j = 0; j < i; j++) {
|
|
184
|
+
const field = sortFields[j];
|
|
185
|
+
const value = cursor.values[field];
|
|
186
|
+
fieldConditions.push(`${fieldFormatter(field)} = ?`);
|
|
187
|
+
fieldParams.push(value);
|
|
188
|
+
}
|
|
189
|
+
const currentField = sortFields[i];
|
|
190
|
+
const currentValue = cursor.values[currentField];
|
|
191
|
+
const fieldSortDir = sortDirections[i] ?? 1;
|
|
192
|
+
const forwardOp = fieldSortDir === 1 ? ">" : "<";
|
|
193
|
+
const operator = direction === 1 ? forwardOp : forwardOp === ">" ? "<" : ">";
|
|
194
|
+
fieldConditions.push(`${fieldFormatter(currentField)} ${operator} ?`);
|
|
195
|
+
fieldParams.push(currentValue);
|
|
196
|
+
const levelCondition = fieldConditions.join(" AND ");
|
|
197
|
+
conditions.push(`(${levelCondition})`);
|
|
198
|
+
params.push(...fieldParams);
|
|
199
|
+
}
|
|
200
|
+
const sql = `(${conditions.join(" OR ")})`;
|
|
201
|
+
return { sql, params };
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Extract sort fields from sort specification, ensuring 'id' is always included for stability
|
|
205
|
+
*/
|
|
206
|
+
static extractSortFields(sort) {
|
|
207
|
+
const fields = [];
|
|
208
|
+
if (sort && Object.keys(sort).length > 0) {
|
|
209
|
+
fields.push(...Object.keys(sort));
|
|
210
|
+
}
|
|
211
|
+
if (!fields.includes("id")) {
|
|
212
|
+
fields.push("id");
|
|
213
|
+
}
|
|
214
|
+
return fields;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Build ORDER BY clause from sort specification
|
|
218
|
+
*/
|
|
219
|
+
static buildOrderClause(sort, fieldFormatter = (field) => field) {
|
|
220
|
+
const fields = this.extractSortFields(sort);
|
|
221
|
+
const clauses = [];
|
|
222
|
+
const directions = [];
|
|
223
|
+
if (sort && Object.keys(sort).length > 0) {
|
|
224
|
+
for (const [field, dir] of Object.entries(sort)) {
|
|
225
|
+
const sqlDirection = dir === 1 ? "ASC" : "DESC";
|
|
226
|
+
clauses.push(`${fieldFormatter(field)} ${sqlDirection}`);
|
|
227
|
+
directions.push(dir);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (!sort || !sort.hasOwnProperty("id")) {
|
|
231
|
+
clauses.push(`${fieldFormatter("id")} ASC`);
|
|
232
|
+
directions.push(1);
|
|
233
|
+
} else if (sort && sort.hasOwnProperty("id")) {
|
|
234
|
+
directions.push(sort["id"]);
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
sql: clauses.join(", "),
|
|
238
|
+
fields,
|
|
239
|
+
directions
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Determine if there are more results available for pagination
|
|
244
|
+
*/
|
|
245
|
+
static hasMoreResults(requestedLimit, actualResultCount) {
|
|
246
|
+
if (!requestedLimit || requestedLimit <= 0) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
return actualResultCount >= requestedLimit;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Generate next and previous cursors from result set
|
|
253
|
+
*/
|
|
254
|
+
static generateResultCursors(results, sortFields, _requestedDirection, hasMore, isFirstPage = false) {
|
|
255
|
+
if (results.length === 0) {
|
|
256
|
+
return {};
|
|
257
|
+
}
|
|
258
|
+
const cursors = {};
|
|
259
|
+
if (hasMore && results.length > 0) {
|
|
260
|
+
const lastResult = results[results.length - 1];
|
|
261
|
+
cursors.nextCursor = this.generateCursor(lastResult, sortFields, 1);
|
|
262
|
+
}
|
|
263
|
+
if (results.length > 0 && !isFirstPage) {
|
|
264
|
+
const firstResult = results[0];
|
|
265
|
+
cursors.prevCursor = this.generateCursor(firstResult, sortFields, -1);
|
|
266
|
+
}
|
|
267
|
+
return cursors;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Utility to compare arrays for equality
|
|
271
|
+
*/
|
|
272
|
+
static arraysEqual(a, b) {
|
|
273
|
+
if (a.length !== b.length) return false;
|
|
274
|
+
return a.every((val, index) => val === b[index]);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// src/utils/patterns.ts
|
|
281
|
+
function escapeLikeLiteral(input) {
|
|
282
|
+
return input.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
283
|
+
}
|
|
284
|
+
function buildLikePattern(value, mode) {
|
|
285
|
+
const trimmed = (value ?? "").trim();
|
|
286
|
+
if (trimmed.length === 0) return null;
|
|
287
|
+
if (trimmed.length > 1024) {
|
|
288
|
+
throw new Error("substring value exceeds 1024 characters");
|
|
289
|
+
}
|
|
290
|
+
const escaped = escapeLikeLiteral(trimmed);
|
|
291
|
+
switch (mode) {
|
|
292
|
+
case "startsWith":
|
|
293
|
+
return `${escaped}%`;
|
|
294
|
+
case "endsWith":
|
|
295
|
+
return `%${escaped}`;
|
|
296
|
+
case "containsText":
|
|
297
|
+
return `%${escaped}%`;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function shouldLogLikeEscapes() {
|
|
301
|
+
try {
|
|
302
|
+
if (typeof process !== "undefined" && process.env) {
|
|
303
|
+
return !!process.env.JS_BAO_DEBUG_LIKE_ESCAPES;
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
}
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
var init_patterns = __esm({
|
|
310
|
+
"src/utils/patterns.ts"() {
|
|
311
|
+
"use strict";
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// src/models/StringSet.ts
|
|
316
|
+
var init_StringSet = __esm({
|
|
317
|
+
"src/models/StringSet.ts"() {
|
|
318
|
+
"use strict";
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// src/types/documentTypes.ts
|
|
323
|
+
var init_documentTypes = __esm({
|
|
324
|
+
"src/types/documentTypes.ts"() {
|
|
325
|
+
"use strict";
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// src/query/DocumentQueryTranslator.ts
|
|
330
|
+
var init_DocumentQueryTranslator = __esm({
|
|
331
|
+
"src/query/DocumentQueryTranslator.ts"() {
|
|
332
|
+
"use strict";
|
|
333
|
+
init_queryTypes();
|
|
334
|
+
init_CursorManager();
|
|
335
|
+
init_sql();
|
|
336
|
+
init_patterns();
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// src/models/metaSync.ts
|
|
341
|
+
import * as Y2 from "yjs";
|
|
342
|
+
function inferFieldType(value) {
|
|
343
|
+
if (value instanceof Y2.Map) return "stringset";
|
|
344
|
+
switch (typeof value) {
|
|
345
|
+
case "string":
|
|
346
|
+
return "string";
|
|
347
|
+
case "number":
|
|
348
|
+
return "number";
|
|
349
|
+
case "boolean":
|
|
350
|
+
return "boolean";
|
|
351
|
+
default:
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function registerFunctionDefault(fn, name) {
|
|
356
|
+
KNOWN_FUNCTION_DEFAULTS.set(fn, name);
|
|
357
|
+
}
|
|
358
|
+
function encodeDefault(value) {
|
|
359
|
+
if (value === void 0 || value === null) return void 0;
|
|
360
|
+
if (typeof value === "function") {
|
|
361
|
+
const name = KNOWN_FUNCTION_DEFAULTS.get(value);
|
|
362
|
+
return name ? `$${name}` : "$unknown_function";
|
|
363
|
+
}
|
|
364
|
+
return value;
|
|
365
|
+
}
|
|
366
|
+
function clearMetaSyncCache(yDoc) {
|
|
367
|
+
if (yDoc) {
|
|
368
|
+
_syncedCache.delete(yDoc);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function syncModelMeta(yDoc, modelName, schema) {
|
|
372
|
+
let synced = _syncedCache.get(yDoc);
|
|
373
|
+
if (synced?.has(modelName)) return;
|
|
374
|
+
if (!synced) {
|
|
375
|
+
synced = /* @__PURE__ */ new Set();
|
|
376
|
+
_syncedCache.set(yDoc, synced);
|
|
377
|
+
}
|
|
378
|
+
const meta = yDoc.getMap(`_meta_${modelName}`);
|
|
379
|
+
for (const [fieldName, fieldOpts] of schema.fields.entries()) {
|
|
380
|
+
syncFieldMeta(meta, fieldName, fieldOpts);
|
|
381
|
+
}
|
|
382
|
+
const compoundConstraints = schema.resolvedUniqueConstraints.filter(
|
|
383
|
+
(c) => c.fields.length > 1
|
|
384
|
+
);
|
|
385
|
+
if (compoundConstraints.length > 0) {
|
|
386
|
+
let constraints = meta.get("_constraints");
|
|
387
|
+
if (!constraints) {
|
|
388
|
+
constraints = new Y2.Map();
|
|
389
|
+
meta.set("_constraints", constraints);
|
|
390
|
+
}
|
|
391
|
+
for (const constraint of compoundConstraints) {
|
|
392
|
+
syncConstraintMeta(constraints, constraint);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const relationships = schema.options.relationships;
|
|
396
|
+
if (relationships && Object.keys(relationships).length > 0) {
|
|
397
|
+
let rels = meta.get("_relationships");
|
|
398
|
+
if (!rels) {
|
|
399
|
+
rels = new Y2.Map();
|
|
400
|
+
meta.set("_relationships", rels);
|
|
401
|
+
}
|
|
402
|
+
for (const [relName, relConfig] of Object.entries(relationships)) {
|
|
403
|
+
syncRelationshipMeta(rels, relName, relConfig);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
synced.add(modelName);
|
|
407
|
+
}
|
|
408
|
+
function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
409
|
+
let fieldMeta = metaMap.get(fieldName);
|
|
410
|
+
if (!fieldMeta) {
|
|
411
|
+
fieldMeta = new Y2.Map();
|
|
412
|
+
metaMap.set(fieldName, fieldMeta);
|
|
413
|
+
}
|
|
414
|
+
setIfChanged(fieldMeta, "type", fieldOpts.type);
|
|
415
|
+
if (fieldOpts.indexed) setIfChanged(fieldMeta, "indexed", true);
|
|
416
|
+
if (fieldOpts.unique) setIfChanged(fieldMeta, "unique", true);
|
|
417
|
+
if (fieldOpts.required) setIfChanged(fieldMeta, "required", true);
|
|
418
|
+
if (fieldOpts.autoAssign) setIfChanged(fieldMeta, "autoAssign", true);
|
|
419
|
+
if (fieldOpts.maxLength !== void 0) setIfChanged(fieldMeta, "maxLength", fieldOpts.maxLength);
|
|
420
|
+
if (fieldOpts.maxCount !== void 0) setIfChanged(fieldMeta, "maxCount", fieldOpts.maxCount);
|
|
421
|
+
const encoded = encodeDefault(fieldOpts.default);
|
|
422
|
+
if (encoded !== void 0) setIfChanged(fieldMeta, "default", encoded);
|
|
423
|
+
}
|
|
424
|
+
function syncConstraintMeta(constraintsMap, constraint) {
|
|
425
|
+
let cMeta = constraintsMap.get(constraint.name);
|
|
426
|
+
if (!cMeta) {
|
|
427
|
+
cMeta = new Y2.Map();
|
|
428
|
+
constraintsMap.set(constraint.name, cMeta);
|
|
429
|
+
}
|
|
430
|
+
setIfChanged(cMeta, "type", "unique");
|
|
431
|
+
const fieldsJson = JSON.stringify(constraint.fields);
|
|
432
|
+
setIfChanged(cMeta, "fields", fieldsJson);
|
|
433
|
+
}
|
|
434
|
+
function syncRelationshipMeta(relsMap, relName, relConfig) {
|
|
435
|
+
let relMeta = relsMap.get(relName);
|
|
436
|
+
if (!relMeta) {
|
|
437
|
+
relMeta = new Y2.Map();
|
|
438
|
+
relsMap.set(relName, relMeta);
|
|
439
|
+
}
|
|
440
|
+
for (const [key, value] of Object.entries(relConfig)) {
|
|
441
|
+
if (value !== void 0) {
|
|
442
|
+
setIfChanged(relMeta, key, value);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function syncInferredMeta(yDoc, modelName, recordData) {
|
|
447
|
+
const meta = yDoc.getMap(`_meta_${modelName}`);
|
|
448
|
+
for (const [fieldName, value] of Object.entries(recordData)) {
|
|
449
|
+
if (fieldName.startsWith("_")) continue;
|
|
450
|
+
let fieldMeta = meta.get(fieldName);
|
|
451
|
+
if (!fieldMeta) {
|
|
452
|
+
fieldMeta = new Y2.Map();
|
|
453
|
+
meta.set(fieldName, fieldMeta);
|
|
454
|
+
}
|
|
455
|
+
if (!fieldMeta.has("type")) {
|
|
456
|
+
const inferredType = inferFieldType(value);
|
|
457
|
+
if (inferredType) {
|
|
458
|
+
fieldMeta.set("type", inferredType);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function setIfChanged(map, key, value) {
|
|
464
|
+
if (map.get(key) !== value) {
|
|
465
|
+
map.set(key, value);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
var KNOWN_FUNCTION_DEFAULTS, _syncedCache;
|
|
469
|
+
var init_metaSync = __esm({
|
|
470
|
+
"src/models/metaSync.ts"() {
|
|
471
|
+
"use strict";
|
|
472
|
+
KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
|
|
473
|
+
_syncedCache = /* @__PURE__ */ new WeakMap();
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// src/models/BaseModel.ts
|
|
478
|
+
import * as Y3 from "yjs";
|
|
479
|
+
import { ulid as ulid2 } from "ulid";
|
|
480
|
+
function generateULID() {
|
|
481
|
+
return ulid2();
|
|
482
|
+
}
|
|
483
|
+
var init_BaseModel = __esm({
|
|
484
|
+
"src/models/BaseModel.ts"() {
|
|
485
|
+
"use strict";
|
|
486
|
+
init_StringSet();
|
|
487
|
+
init_documentTypes();
|
|
488
|
+
init_DocumentQueryTranslator();
|
|
489
|
+
init_CursorManager();
|
|
490
|
+
init_sql();
|
|
491
|
+
init_metaSync();
|
|
492
|
+
registerFunctionDefault(generateULID, "generate_ulid");
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// src/models/relationshipManager.ts
|
|
497
|
+
var init_relationshipManager = __esm({
|
|
498
|
+
"src/models/relationshipManager.ts"() {
|
|
499
|
+
"use strict";
|
|
500
|
+
init_BaseModel();
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// src/models/ModelRegistry.ts
|
|
505
|
+
var init_ModelRegistry = __esm({
|
|
506
|
+
"src/models/ModelRegistry.ts"() {
|
|
507
|
+
"use strict";
|
|
508
|
+
init_BaseModel();
|
|
509
|
+
init_relationshipManager();
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
|
|
1
513
|
// src/engines/DatabaseEngine.ts
|
|
2
514
|
var DatabaseEngine = class {
|
|
3
515
|
createTable(_modelName, _schema, _options) {
|
|
@@ -36,23 +548,8 @@ var DatabaseEngine = class {
|
|
|
36
548
|
}
|
|
37
549
|
};
|
|
38
550
|
|
|
39
|
-
// src/utils/sql.ts
|
|
40
|
-
var IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
41
|
-
function isValidIdentifier(name) {
|
|
42
|
-
return IDENTIFIER_PATTERN.test(name);
|
|
43
|
-
}
|
|
44
|
-
function assertValidIdentifier(name, context) {
|
|
45
|
-
if (!isValidIdentifier(name)) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
`${context}: Identifier "${name}" must match ${IDENTIFIER_PATTERN.source}`
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function quoteIdentifier(name) {
|
|
52
|
-
return `"${name.replace(/"/g, '""')}"`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
551
|
// src/schema/JsonSchemaDDL.ts
|
|
552
|
+
init_sql();
|
|
56
553
|
var JsonSchemaDDL = class {
|
|
57
554
|
/**
|
|
58
555
|
* Generate CREATE TABLE statement for the records table
|
|
@@ -446,6 +943,7 @@ var JsonSchemaEngine = class extends DatabaseEngine {
|
|
|
446
943
|
};
|
|
447
944
|
|
|
448
945
|
// src/engines/cloudflare/DurableObjectEngine.ts
|
|
946
|
+
init_sql();
|
|
449
947
|
var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
450
948
|
sql;
|
|
451
949
|
storage;
|
|
@@ -1016,276 +1514,11 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
1016
1514
|
}
|
|
1017
1515
|
};
|
|
1018
1516
|
|
|
1019
|
-
// src/types/queryTypes.ts
|
|
1020
|
-
var DocumentQueryError = class _DocumentQueryError extends Error {
|
|
1021
|
-
constructor(message) {
|
|
1022
|
-
super(message);
|
|
1023
|
-
this.name = "DocumentQueryError";
|
|
1024
|
-
Object.setPrototypeOf(this, _DocumentQueryError.prototype);
|
|
1025
|
-
}
|
|
1026
|
-
};
|
|
1027
|
-
var InvalidOperatorError = class _InvalidOperatorError extends DocumentQueryError {
|
|
1028
|
-
field;
|
|
1029
|
-
operator;
|
|
1030
|
-
fieldType;
|
|
1031
|
-
constructor(message, field, operator, fieldType) {
|
|
1032
|
-
super(message);
|
|
1033
|
-
this.name = "InvalidOperatorError";
|
|
1034
|
-
this.field = field;
|
|
1035
|
-
this.operator = operator;
|
|
1036
|
-
this.fieldType = fieldType;
|
|
1037
|
-
Object.setPrototypeOf(this, _InvalidOperatorError.prototype);
|
|
1038
|
-
}
|
|
1039
|
-
};
|
|
1040
|
-
var InvalidFieldError = class _InvalidFieldError extends DocumentQueryError {
|
|
1041
|
-
field;
|
|
1042
|
-
modelName;
|
|
1043
|
-
constructor(message, field, modelName) {
|
|
1044
|
-
super(message);
|
|
1045
|
-
this.name = "InvalidFieldError";
|
|
1046
|
-
this.field = field;
|
|
1047
|
-
this.modelName = modelName;
|
|
1048
|
-
Object.setPrototypeOf(this, _InvalidFieldError.prototype);
|
|
1049
|
-
}
|
|
1050
|
-
};
|
|
1051
|
-
var InvalidCursorError = class _InvalidCursorError extends DocumentQueryError {
|
|
1052
|
-
cursor;
|
|
1053
|
-
constructor(message, cursor) {
|
|
1054
|
-
super(message);
|
|
1055
|
-
this.name = "InvalidCursorError";
|
|
1056
|
-
this.cursor = cursor;
|
|
1057
|
-
Object.setPrototypeOf(this, _InvalidCursorError.prototype);
|
|
1058
|
-
}
|
|
1059
|
-
};
|
|
1060
|
-
|
|
1061
|
-
// src/query/CursorManager.ts
|
|
1062
|
-
var base64Encode = (str) => {
|
|
1063
|
-
if (typeof btoa !== "undefined") {
|
|
1064
|
-
return btoa(str);
|
|
1065
|
-
} else if (typeof Buffer !== "undefined") {
|
|
1066
|
-
return Buffer.from(str, "utf-8").toString("base64");
|
|
1067
|
-
} else {
|
|
1068
|
-
throw new Error("No base64 encoding available");
|
|
1069
|
-
}
|
|
1070
|
-
};
|
|
1071
|
-
var base64Decode = (str) => {
|
|
1072
|
-
if (typeof atob !== "undefined") {
|
|
1073
|
-
return atob(str);
|
|
1074
|
-
} else if (typeof Buffer !== "undefined") {
|
|
1075
|
-
return Buffer.from(str, "base64").toString("utf-8");
|
|
1076
|
-
} else {
|
|
1077
|
-
throw new Error("No base64 decoding available");
|
|
1078
|
-
}
|
|
1079
|
-
};
|
|
1080
|
-
var CursorManager = class {
|
|
1081
|
-
/**
|
|
1082
|
-
* Encode cursor data to base64 string
|
|
1083
|
-
*/
|
|
1084
|
-
static encodeCursor(cursorData) {
|
|
1085
|
-
try {
|
|
1086
|
-
const jsonString = JSON.stringify(cursorData);
|
|
1087
|
-
return base64Encode(jsonString);
|
|
1088
|
-
} catch (error) {
|
|
1089
|
-
throw new InvalidCursorError(
|
|
1090
|
-
`Failed to encode cursor: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1091
|
-
JSON.stringify(cursorData)
|
|
1092
|
-
);
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
/**
|
|
1096
|
-
* Decode base64 cursor string to cursor data
|
|
1097
|
-
*/
|
|
1098
|
-
static decodeCursor(cursor) {
|
|
1099
|
-
try {
|
|
1100
|
-
const jsonString = base64Decode(cursor);
|
|
1101
|
-
const parsed = JSON.parse(jsonString);
|
|
1102
|
-
if (!parsed || typeof parsed !== "object") {
|
|
1103
|
-
throw new Error("Cursor must be an object");
|
|
1104
|
-
}
|
|
1105
|
-
if (!parsed.values || typeof parsed.values !== "object") {
|
|
1106
|
-
throw new Error("Cursor must have values object");
|
|
1107
|
-
}
|
|
1108
|
-
if (!Array.isArray(parsed.sortFields)) {
|
|
1109
|
-
throw new Error("Cursor must have sortFields array");
|
|
1110
|
-
}
|
|
1111
|
-
if (parsed.direction !== 1 && parsed.direction !== -1) {
|
|
1112
|
-
throw new Error("Cursor direction must be 1 or -1");
|
|
1113
|
-
}
|
|
1114
|
-
return parsed;
|
|
1115
|
-
} catch (error) {
|
|
1116
|
-
throw new InvalidCursorError(
|
|
1117
|
-
`Failed to decode cursor: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1118
|
-
cursor
|
|
1119
|
-
);
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
/**
|
|
1123
|
-
* Generate cursor from a record and sort specification
|
|
1124
|
-
*/
|
|
1125
|
-
static generateCursor(record, sortFields, direction) {
|
|
1126
|
-
const values = {};
|
|
1127
|
-
for (const field of sortFields) {
|
|
1128
|
-
if (record.hasOwnProperty(field)) {
|
|
1129
|
-
values[field] = record[field];
|
|
1130
|
-
} else {
|
|
1131
|
-
throw new Error(
|
|
1132
|
-
`Cannot generate cursor: record missing sort field '${field}'`
|
|
1133
|
-
);
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
const cursorData = {
|
|
1137
|
-
values,
|
|
1138
|
-
sortFields,
|
|
1139
|
-
direction
|
|
1140
|
-
};
|
|
1141
|
-
return this.encodeCursor(cursorData);
|
|
1142
|
-
}
|
|
1143
|
-
/**
|
|
1144
|
-
* Build SQL pagination conditions based on cursor
|
|
1145
|
-
* Uses lexicographic ordering for stable pagination with multiple sort fields
|
|
1146
|
-
*/
|
|
1147
|
-
static buildPaginationConditions(cursor, currentSortFields, sortDirections, requestedDirection, fieldFormatter = (field) => field) {
|
|
1148
|
-
if (!this.arraysEqual(cursor.sortFields, currentSortFields)) {
|
|
1149
|
-
throw new InvalidCursorError(
|
|
1150
|
-
`Cursor sort fields [${cursor.sortFields.join(
|
|
1151
|
-
", "
|
|
1152
|
-
)}] don't match query sort fields [${currentSortFields.join(", ")}]`,
|
|
1153
|
-
JSON.stringify(cursor)
|
|
1154
|
-
);
|
|
1155
|
-
}
|
|
1156
|
-
const conditions = [];
|
|
1157
|
-
const params = [];
|
|
1158
|
-
const sortFields = cursor.sortFields;
|
|
1159
|
-
const direction = requestedDirection;
|
|
1160
|
-
for (let i = 0; i < sortFields.length; i++) {
|
|
1161
|
-
const fieldConditions = [];
|
|
1162
|
-
const fieldParams = [];
|
|
1163
|
-
for (let j = 0; j < i; j++) {
|
|
1164
|
-
const field = sortFields[j];
|
|
1165
|
-
const value = cursor.values[field];
|
|
1166
|
-
fieldConditions.push(`${fieldFormatter(field)} = ?`);
|
|
1167
|
-
fieldParams.push(value);
|
|
1168
|
-
}
|
|
1169
|
-
const currentField = sortFields[i];
|
|
1170
|
-
const currentValue = cursor.values[currentField];
|
|
1171
|
-
const fieldSortDir = sortDirections[i] ?? 1;
|
|
1172
|
-
const forwardOp = fieldSortDir === 1 ? ">" : "<";
|
|
1173
|
-
const operator = direction === 1 ? forwardOp : forwardOp === ">" ? "<" : ">";
|
|
1174
|
-
fieldConditions.push(`${fieldFormatter(currentField)} ${operator} ?`);
|
|
1175
|
-
fieldParams.push(currentValue);
|
|
1176
|
-
const levelCondition = fieldConditions.join(" AND ");
|
|
1177
|
-
conditions.push(`(${levelCondition})`);
|
|
1178
|
-
params.push(...fieldParams);
|
|
1179
|
-
}
|
|
1180
|
-
const sql = `(${conditions.join(" OR ")})`;
|
|
1181
|
-
return { sql, params };
|
|
1182
|
-
}
|
|
1183
|
-
/**
|
|
1184
|
-
* Extract sort fields from sort specification, ensuring 'id' is always included for stability
|
|
1185
|
-
*/
|
|
1186
|
-
static extractSortFields(sort) {
|
|
1187
|
-
const fields = [];
|
|
1188
|
-
if (sort && Object.keys(sort).length > 0) {
|
|
1189
|
-
fields.push(...Object.keys(sort));
|
|
1190
|
-
}
|
|
1191
|
-
if (!fields.includes("id")) {
|
|
1192
|
-
fields.push("id");
|
|
1193
|
-
}
|
|
1194
|
-
return fields;
|
|
1195
|
-
}
|
|
1196
|
-
/**
|
|
1197
|
-
* Build ORDER BY clause from sort specification
|
|
1198
|
-
*/
|
|
1199
|
-
static buildOrderClause(sort, fieldFormatter = (field) => field) {
|
|
1200
|
-
const fields = this.extractSortFields(sort);
|
|
1201
|
-
const clauses = [];
|
|
1202
|
-
const directions = [];
|
|
1203
|
-
if (sort && Object.keys(sort).length > 0) {
|
|
1204
|
-
for (const [field, dir] of Object.entries(sort)) {
|
|
1205
|
-
const sqlDirection = dir === 1 ? "ASC" : "DESC";
|
|
1206
|
-
clauses.push(`${fieldFormatter(field)} ${sqlDirection}`);
|
|
1207
|
-
directions.push(dir);
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
if (!sort || !sort.hasOwnProperty("id")) {
|
|
1211
|
-
clauses.push(`${fieldFormatter("id")} ASC`);
|
|
1212
|
-
directions.push(1);
|
|
1213
|
-
} else if (sort && sort.hasOwnProperty("id")) {
|
|
1214
|
-
directions.push(sort["id"]);
|
|
1215
|
-
}
|
|
1216
|
-
return {
|
|
1217
|
-
sql: clauses.join(", "),
|
|
1218
|
-
fields,
|
|
1219
|
-
directions
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
|
-
/**
|
|
1223
|
-
* Determine if there are more results available for pagination
|
|
1224
|
-
*/
|
|
1225
|
-
static hasMoreResults(requestedLimit, actualResultCount) {
|
|
1226
|
-
if (!requestedLimit || requestedLimit <= 0) {
|
|
1227
|
-
return false;
|
|
1228
|
-
}
|
|
1229
|
-
return actualResultCount >= requestedLimit;
|
|
1230
|
-
}
|
|
1231
|
-
/**
|
|
1232
|
-
* Generate next and previous cursors from result set
|
|
1233
|
-
*/
|
|
1234
|
-
static generateResultCursors(results, sortFields, _requestedDirection, hasMore, isFirstPage = false) {
|
|
1235
|
-
if (results.length === 0) {
|
|
1236
|
-
return {};
|
|
1237
|
-
}
|
|
1238
|
-
const cursors = {};
|
|
1239
|
-
if (hasMore && results.length > 0) {
|
|
1240
|
-
const lastResult = results[results.length - 1];
|
|
1241
|
-
cursors.nextCursor = this.generateCursor(lastResult, sortFields, 1);
|
|
1242
|
-
}
|
|
1243
|
-
if (results.length > 0 && !isFirstPage) {
|
|
1244
|
-
const firstResult = results[0];
|
|
1245
|
-
cursors.prevCursor = this.generateCursor(firstResult, sortFields, -1);
|
|
1246
|
-
}
|
|
1247
|
-
return cursors;
|
|
1248
|
-
}
|
|
1249
|
-
/**
|
|
1250
|
-
* Utility to compare arrays for equality
|
|
1251
|
-
*/
|
|
1252
|
-
static arraysEqual(a, b) {
|
|
1253
|
-
if (a.length !== b.length) return false;
|
|
1254
|
-
return a.every((val, index) => val === b[index]);
|
|
1255
|
-
}
|
|
1256
|
-
};
|
|
1257
|
-
|
|
1258
|
-
// src/utils/patterns.ts
|
|
1259
|
-
function escapeLikeLiteral(input) {
|
|
1260
|
-
return input.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
1261
|
-
}
|
|
1262
|
-
function buildLikePattern(value, mode) {
|
|
1263
|
-
const trimmed = (value ?? "").trim();
|
|
1264
|
-
if (trimmed.length === 0) return null;
|
|
1265
|
-
if (trimmed.length > 1024) {
|
|
1266
|
-
throw new Error("substring value exceeds 1024 characters");
|
|
1267
|
-
}
|
|
1268
|
-
const escaped = escapeLikeLiteral(trimmed);
|
|
1269
|
-
switch (mode) {
|
|
1270
|
-
case "startsWith":
|
|
1271
|
-
return `${escaped}%`;
|
|
1272
|
-
case "endsWith":
|
|
1273
|
-
return `%${escaped}`;
|
|
1274
|
-
case "containsText":
|
|
1275
|
-
return `%${escaped}%`;
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
function shouldLogLikeEscapes() {
|
|
1279
|
-
try {
|
|
1280
|
-
if (typeof process !== "undefined" && process.env) {
|
|
1281
|
-
return !!process.env.JS_BAO_DEBUG_LIKE_ESCAPES;
|
|
1282
|
-
}
|
|
1283
|
-
} catch {
|
|
1284
|
-
}
|
|
1285
|
-
return false;
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
1517
|
// src/query/JsonQueryTranslator.ts
|
|
1518
|
+
init_queryTypes();
|
|
1519
|
+
init_CursorManager();
|
|
1520
|
+
init_sql();
|
|
1521
|
+
init_patterns();
|
|
1289
1522
|
var SYSTEM_FIELDS = /* @__PURE__ */ new Set(["id", "type"]);
|
|
1290
1523
|
var JsonQueryTranslator = class {
|
|
1291
1524
|
modelName;
|
|
@@ -1964,6 +2197,8 @@ var JsonQueryTranslator = class {
|
|
|
1964
2197
|
};
|
|
1965
2198
|
|
|
1966
2199
|
// src/engines/cloudflare/createDocumentDO.ts
|
|
2200
|
+
init_CursorManager();
|
|
2201
|
+
init_sql();
|
|
1967
2202
|
import { ulid } from "ulid";
|
|
1968
2203
|
function createDatabaseDO(config = {}) {
|
|
1969
2204
|
const hooks = config.hooks;
|
|
@@ -3773,11 +4008,418 @@ async function handleRequest(request, env) {
|
|
|
3773
4008
|
const response = await stub.fetch(doRequest);
|
|
3774
4009
|
return addCorsHeaders(response);
|
|
3775
4010
|
}
|
|
4011
|
+
|
|
4012
|
+
// src/utils/yDocSchema.ts
|
|
4013
|
+
import * as Y from "yjs";
|
|
4014
|
+
function discoverSchema(yDoc) {
|
|
4015
|
+
const models = {};
|
|
4016
|
+
const metaNames = /* @__PURE__ */ new Set();
|
|
4017
|
+
for (const key of yDoc.share.keys()) {
|
|
4018
|
+
if (!key.startsWith("_meta_")) continue;
|
|
4019
|
+
const map = materializeMap(yDoc, key);
|
|
4020
|
+
if (!map) continue;
|
|
4021
|
+
const modelName = key.slice("_meta_".length);
|
|
4022
|
+
metaNames.add(modelName);
|
|
4023
|
+
models[modelName] = readModelMeta(map);
|
|
4024
|
+
}
|
|
4025
|
+
for (const key of yDoc.share.keys()) {
|
|
4026
|
+
if (key.startsWith("_")) continue;
|
|
4027
|
+
if (metaNames.has(key)) continue;
|
|
4028
|
+
const map = materializeMap(yDoc, key);
|
|
4029
|
+
if (!map || map.size === 0) continue;
|
|
4030
|
+
const inferred = inferModelFromData(map);
|
|
4031
|
+
if (inferred) models[key] = inferred;
|
|
4032
|
+
}
|
|
4033
|
+
return { models };
|
|
4034
|
+
}
|
|
4035
|
+
function discoverModelNames(yDoc) {
|
|
4036
|
+
const names = [];
|
|
4037
|
+
for (const key of yDoc.share.keys()) {
|
|
4038
|
+
if (key.startsWith("_")) continue;
|
|
4039
|
+
const map = materializeMap(yDoc, key);
|
|
4040
|
+
if (map) names.push(key);
|
|
4041
|
+
}
|
|
4042
|
+
return names.sort();
|
|
4043
|
+
}
|
|
4044
|
+
function materializeMap(yDoc, key) {
|
|
4045
|
+
try {
|
|
4046
|
+
const map = yDoc.getMap(key);
|
|
4047
|
+
return map instanceof Y.Map ? map : null;
|
|
4048
|
+
} catch {
|
|
4049
|
+
return null;
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
function readModelMeta(metaMap) {
|
|
4053
|
+
const fields = {};
|
|
4054
|
+
let constraints;
|
|
4055
|
+
let relationships;
|
|
4056
|
+
for (const [key, value] of metaMap.entries()) {
|
|
4057
|
+
if (key === "_constraints" && value instanceof Y.Map) {
|
|
4058
|
+
constraints = readConstraints(value);
|
|
4059
|
+
} else if (key === "_relationships" && value instanceof Y.Map) {
|
|
4060
|
+
relationships = readRelationships(value);
|
|
4061
|
+
} else if (value instanceof Y.Map) {
|
|
4062
|
+
fields[key] = readFieldMeta(value);
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
const model = { fields };
|
|
4066
|
+
if (constraints && Object.keys(constraints).length > 0) {
|
|
4067
|
+
model.constraints = constraints;
|
|
4068
|
+
}
|
|
4069
|
+
if (relationships && Object.keys(relationships).length > 0) {
|
|
4070
|
+
model.relationships = relationships;
|
|
4071
|
+
}
|
|
4072
|
+
return model;
|
|
4073
|
+
}
|
|
4074
|
+
function readFieldMeta(fieldMap) {
|
|
4075
|
+
const field = { type: fieldMap.get("type") ?? "unknown" };
|
|
4076
|
+
if (fieldMap.get("indexed") === true) field.indexed = true;
|
|
4077
|
+
if (fieldMap.get("unique") === true) field.unique = true;
|
|
4078
|
+
if (fieldMap.get("required") === true) field.required = true;
|
|
4079
|
+
if (fieldMap.get("autoAssign") === true) field.autoAssign = true;
|
|
4080
|
+
const def = fieldMap.get("default");
|
|
4081
|
+
if (def !== void 0) field.default = def;
|
|
4082
|
+
const maxLength = fieldMap.get("maxLength");
|
|
4083
|
+
if (maxLength !== void 0) field.maxLength = maxLength;
|
|
4084
|
+
const maxCount = fieldMap.get("maxCount");
|
|
4085
|
+
if (maxCount !== void 0) field.maxCount = maxCount;
|
|
4086
|
+
return field;
|
|
4087
|
+
}
|
|
4088
|
+
function inferModelFromData(dataMap) {
|
|
4089
|
+
const fields = {};
|
|
4090
|
+
let sampled = 0;
|
|
4091
|
+
for (const [_recordId, recordValue] of dataMap.entries()) {
|
|
4092
|
+
if (!(recordValue instanceof Y.Map)) continue;
|
|
4093
|
+
if (++sampled > 5) break;
|
|
4094
|
+
for (const [fieldName, value] of recordValue.entries()) {
|
|
4095
|
+
if (fieldName.startsWith("_")) continue;
|
|
4096
|
+
if (fields[fieldName]) continue;
|
|
4097
|
+
const type = inferTypeFromValue(value);
|
|
4098
|
+
if (type) fields[fieldName] = { type };
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
if (Object.keys(fields).length === 0) return null;
|
|
4102
|
+
return { fields };
|
|
4103
|
+
}
|
|
4104
|
+
function inferTypeFromValue(value) {
|
|
4105
|
+
if (value instanceof Y.Map) return "stringset";
|
|
4106
|
+
switch (typeof value) {
|
|
4107
|
+
case "string":
|
|
4108
|
+
return "string";
|
|
4109
|
+
case "number":
|
|
4110
|
+
return "number";
|
|
4111
|
+
case "boolean":
|
|
4112
|
+
return "boolean";
|
|
4113
|
+
default:
|
|
4114
|
+
return null;
|
|
4115
|
+
}
|
|
4116
|
+
}
|
|
4117
|
+
function readConstraints(constraintsMap) {
|
|
4118
|
+
const out = {};
|
|
4119
|
+
for (const [name, value] of constraintsMap.entries()) {
|
|
4120
|
+
if (!(value instanceof Y.Map)) continue;
|
|
4121
|
+
let fields = [];
|
|
4122
|
+
const rawFields = value.get("fields");
|
|
4123
|
+
if (typeof rawFields === "string") {
|
|
4124
|
+
try {
|
|
4125
|
+
fields = JSON.parse(rawFields);
|
|
4126
|
+
} catch {
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
out[name] = {
|
|
4130
|
+
type: value.get("type") ?? "unknown",
|
|
4131
|
+
fields
|
|
4132
|
+
};
|
|
4133
|
+
}
|
|
4134
|
+
return out;
|
|
4135
|
+
}
|
|
4136
|
+
function readRelationships(relsMap) {
|
|
4137
|
+
const out = {};
|
|
4138
|
+
for (const [name, value] of relsMap.entries()) {
|
|
4139
|
+
if (!(value instanceof Y.Map)) continue;
|
|
4140
|
+
const rel = {};
|
|
4141
|
+
for (const [k, v] of value.entries()) {
|
|
4142
|
+
rel[k] = v;
|
|
4143
|
+
}
|
|
4144
|
+
out[name] = rel;
|
|
4145
|
+
}
|
|
4146
|
+
return out;
|
|
4147
|
+
}
|
|
4148
|
+
var CAMEL_TO_SNAKE = {
|
|
4149
|
+
autoAssign: "auto_assign",
|
|
4150
|
+
maxLength: "max_length",
|
|
4151
|
+
maxCount: "max_count",
|
|
4152
|
+
relatedIdField: "related_id_field",
|
|
4153
|
+
joinModel: "join_model",
|
|
4154
|
+
joinModelLocalField: "join_model_local_field",
|
|
4155
|
+
joinModelRelatedField: "join_model_related_field",
|
|
4156
|
+
joinModelOrderByField: "join_model_order_by_field",
|
|
4157
|
+
joinModelOrderDirection: "join_model_order_direction",
|
|
4158
|
+
orderByField: "order_by_field",
|
|
4159
|
+
orderDirection: "order_direction"
|
|
4160
|
+
};
|
|
4161
|
+
function toSnake(key) {
|
|
4162
|
+
return CAMEL_TO_SNAKE[key] ?? key;
|
|
4163
|
+
}
|
|
4164
|
+
function tomlValue(v) {
|
|
4165
|
+
if (typeof v === "string") {
|
|
4166
|
+
return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")}"`;
|
|
4167
|
+
}
|
|
4168
|
+
return String(v);
|
|
4169
|
+
}
|
|
4170
|
+
function schemaToToml(schema) {
|
|
4171
|
+
const lines = [];
|
|
4172
|
+
for (const [modelName, model] of Object.entries(schema.models)) {
|
|
4173
|
+
if (lines.length > 0) lines.push("", "");
|
|
4174
|
+
lines.push(`[models.${modelName}]`);
|
|
4175
|
+
for (const [fieldName, field] of Object.entries(model.fields)) {
|
|
4176
|
+
lines.push("");
|
|
4177
|
+
lines.push(`[models.${modelName}.fields.${fieldName}]`);
|
|
4178
|
+
lines.push(`type = ${tomlValue(field.type)}`);
|
|
4179
|
+
if (field.autoAssign) lines.push("auto_assign = true");
|
|
4180
|
+
if (field.indexed) lines.push("indexed = true");
|
|
4181
|
+
if (field.unique) lines.push("unique = true");
|
|
4182
|
+
if (field.required) lines.push("required = true");
|
|
4183
|
+
if (field.maxLength !== void 0) lines.push(`max_length = ${field.maxLength}`);
|
|
4184
|
+
if (field.maxCount !== void 0) lines.push(`max_count = ${field.maxCount}`);
|
|
4185
|
+
if (field.default !== void 0) lines.push(`default = ${tomlValue(field.default)}`);
|
|
4186
|
+
}
|
|
4187
|
+
if (model.relationships) {
|
|
4188
|
+
for (const [relName, rel] of Object.entries(model.relationships)) {
|
|
4189
|
+
lines.push("");
|
|
4190
|
+
lines.push(`[models.${modelName}.relationships.${relName}]`);
|
|
4191
|
+
for (const [k, v] of Object.entries(rel)) {
|
|
4192
|
+
if (v === void 0) continue;
|
|
4193
|
+
lines.push(`${toSnake(k)} = ${tomlValue(v)}`);
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
if (model.constraints) {
|
|
4198
|
+
for (const [cName, c] of Object.entries(model.constraints)) {
|
|
4199
|
+
lines.push("");
|
|
4200
|
+
lines.push(`[[models.${modelName}.unique_constraints]]`);
|
|
4201
|
+
lines.push(`name = ${tomlValue(cName)}`);
|
|
4202
|
+
lines.push(`fields = [${c.fields.map((f) => tomlValue(f)).join(", ")}]`);
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
lines.push("");
|
|
4207
|
+
return lines.join("\n");
|
|
4208
|
+
}
|
|
4209
|
+
|
|
4210
|
+
// src/models/tomlLoader.ts
|
|
4211
|
+
import { parse as parseToml } from "smol-toml";
|
|
4212
|
+
|
|
4213
|
+
// src/models/schema.ts
|
|
4214
|
+
init_BaseModel();
|
|
4215
|
+
init_ModelRegistry();
|
|
4216
|
+
function defineModelSchema(input) {
|
|
4217
|
+
const { name, fields } = input;
|
|
4218
|
+
const options = {
|
|
4219
|
+
name,
|
|
4220
|
+
uniqueConstraints: input.options?.uniqueConstraints ? [...input.options.uniqueConstraints] : void 0,
|
|
4221
|
+
relationships: input.options?.relationships
|
|
4222
|
+
};
|
|
4223
|
+
const schema = {
|
|
4224
|
+
name,
|
|
4225
|
+
fields,
|
|
4226
|
+
options,
|
|
4227
|
+
runtimeShape: void 0,
|
|
4228
|
+
buildRuntimeShape(modelClass) {
|
|
4229
|
+
if (schema.runtimeShape && schema.runtimeShape.class === modelClass) {
|
|
4230
|
+
return schema.runtimeShape;
|
|
4231
|
+
}
|
|
4232
|
+
const fieldsMap = /* @__PURE__ */ new Map();
|
|
4233
|
+
for (const [fieldName, fieldOptions] of Object.entries(fields)) {
|
|
4234
|
+
fieldsMap.set(fieldName, { ...fieldOptions });
|
|
4235
|
+
}
|
|
4236
|
+
const resolvedUniqueConstraints = resolveUniqueConstraints(
|
|
4237
|
+
name,
|
|
4238
|
+
fieldsMap,
|
|
4239
|
+
options.uniqueConstraints
|
|
4240
|
+
);
|
|
4241
|
+
schema.runtimeShape = {
|
|
4242
|
+
class: modelClass,
|
|
4243
|
+
options,
|
|
4244
|
+
fields: fieldsMap,
|
|
4245
|
+
resolvedUniqueConstraints
|
|
4246
|
+
};
|
|
4247
|
+
return schema.runtimeShape;
|
|
4248
|
+
}
|
|
4249
|
+
};
|
|
4250
|
+
return schema;
|
|
4251
|
+
}
|
|
4252
|
+
function resolveUniqueConstraints(modelName, fields, customConstraints) {
|
|
4253
|
+
const resolved = [];
|
|
4254
|
+
for (const [fieldName, options] of fields.entries()) {
|
|
4255
|
+
if (options.unique) {
|
|
4256
|
+
resolved.push({
|
|
4257
|
+
name: `${modelName}_${fieldName}_unique`,
|
|
4258
|
+
fields: [fieldName]
|
|
4259
|
+
});
|
|
4260
|
+
}
|
|
4261
|
+
}
|
|
4262
|
+
if (customConstraints) {
|
|
4263
|
+
for (const constraint of customConstraints) {
|
|
4264
|
+
const missingField = constraint.fields.find(
|
|
4265
|
+
(field) => !fields.has(field)
|
|
4266
|
+
);
|
|
4267
|
+
if (missingField) {
|
|
4268
|
+
console.warn(
|
|
4269
|
+
`[defineModelSchema] Unique constraint "${constraint.name}" for model "${modelName}" references unknown field "${missingField}". Skipping.`
|
|
4270
|
+
);
|
|
4271
|
+
continue;
|
|
4272
|
+
}
|
|
4273
|
+
if (resolved.some((item) => item.name === constraint.name)) {
|
|
4274
|
+
console.warn(
|
|
4275
|
+
`[defineModelSchema] Duplicate unique constraint name "${constraint.name}" in model "${modelName}".`
|
|
4276
|
+
);
|
|
4277
|
+
}
|
|
4278
|
+
resolved.push({
|
|
4279
|
+
name: constraint.name,
|
|
4280
|
+
fields: [...constraint.fields]
|
|
4281
|
+
});
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
return resolved;
|
|
4285
|
+
}
|
|
4286
|
+
|
|
4287
|
+
// src/models/tomlLoader.ts
|
|
4288
|
+
var VALID_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
4289
|
+
"string",
|
|
4290
|
+
"number",
|
|
4291
|
+
"boolean",
|
|
4292
|
+
"date",
|
|
4293
|
+
"id",
|
|
4294
|
+
"stringset"
|
|
4295
|
+
]);
|
|
4296
|
+
function parseFieldOptions(raw) {
|
|
4297
|
+
if (!raw.type || !VALID_FIELD_TYPES.has(raw.type)) {
|
|
4298
|
+
throw new Error(
|
|
4299
|
+
`Invalid field type "${raw.type}". Must be one of: ${[...VALID_FIELD_TYPES].join(", ")}`
|
|
4300
|
+
);
|
|
4301
|
+
}
|
|
4302
|
+
const opts = { type: raw.type };
|
|
4303
|
+
if (raw.indexed === true) opts.indexed = true;
|
|
4304
|
+
if (raw.unique === true) opts.unique = true;
|
|
4305
|
+
if (raw.required === true) opts.required = true;
|
|
4306
|
+
if (raw.auto_assign === true) opts.autoAssign = true;
|
|
4307
|
+
if (raw.max_length !== void 0) opts.maxLength = raw.max_length;
|
|
4308
|
+
if (raw.max_count !== void 0) opts.maxCount = raw.max_count;
|
|
4309
|
+
if (raw.default !== void 0) opts.default = raw.default;
|
|
4310
|
+
return opts;
|
|
4311
|
+
}
|
|
4312
|
+
function requireField(raw, field, context) {
|
|
4313
|
+
if (!raw[field]) {
|
|
4314
|
+
throw new Error(`Relationship ${context}: missing required field "${field}"`);
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
function parseRelationship(raw) {
|
|
4318
|
+
const type = raw.type;
|
|
4319
|
+
if (type === "refersTo") {
|
|
4320
|
+
requireField(raw, "model", "refersTo");
|
|
4321
|
+
requireField(raw, "related_id_field", "refersTo");
|
|
4322
|
+
return {
|
|
4323
|
+
type: "refersTo",
|
|
4324
|
+
model: raw.model,
|
|
4325
|
+
relatedIdField: raw.related_id_field
|
|
4326
|
+
};
|
|
4327
|
+
}
|
|
4328
|
+
if (type === "hasMany") {
|
|
4329
|
+
requireField(raw, "model", "hasMany");
|
|
4330
|
+
requireField(raw, "related_id_field", "hasMany");
|
|
4331
|
+
const rel = {
|
|
4332
|
+
type: "hasMany",
|
|
4333
|
+
model: raw.model,
|
|
4334
|
+
relatedIdField: raw.related_id_field
|
|
4335
|
+
};
|
|
4336
|
+
if (raw.order_by_field) rel.orderByField = raw.order_by_field;
|
|
4337
|
+
if (raw.order_direction) rel.orderDirection = raw.order_direction;
|
|
4338
|
+
return rel;
|
|
4339
|
+
}
|
|
4340
|
+
if (type === "hasManyThrough") {
|
|
4341
|
+
requireField(raw, "model", "hasManyThrough");
|
|
4342
|
+
requireField(raw, "join_model", "hasManyThrough");
|
|
4343
|
+
requireField(raw, "join_model_local_field", "hasManyThrough");
|
|
4344
|
+
requireField(raw, "join_model_related_field", "hasManyThrough");
|
|
4345
|
+
const rel = {
|
|
4346
|
+
type: "hasManyThrough",
|
|
4347
|
+
model: raw.model,
|
|
4348
|
+
joinModel: raw.join_model,
|
|
4349
|
+
joinModelLocalField: raw.join_model_local_field,
|
|
4350
|
+
joinModelRelatedField: raw.join_model_related_field
|
|
4351
|
+
};
|
|
4352
|
+
if (raw.join_model_order_by_field)
|
|
4353
|
+
rel.joinModelOrderByField = raw.join_model_order_by_field;
|
|
4354
|
+
if (raw.join_model_order_direction)
|
|
4355
|
+
rel.joinModelOrderDirection = raw.join_model_order_direction;
|
|
4356
|
+
return rel;
|
|
4357
|
+
}
|
|
4358
|
+
throw new Error(`Unknown relationship type: ${type}`);
|
|
4359
|
+
}
|
|
4360
|
+
function loadSchemaFromTomlString(tomlString) {
|
|
4361
|
+
const parsed = parseToml(tomlString);
|
|
4362
|
+
const models = parsed.models;
|
|
4363
|
+
if (!models || typeof models !== "object") {
|
|
4364
|
+
throw new Error("TOML schema must have a [models] section");
|
|
4365
|
+
}
|
|
4366
|
+
const schemas = [];
|
|
4367
|
+
for (const [modelName, modelDef] of Object.entries(models)) {
|
|
4368
|
+
const fields = {};
|
|
4369
|
+
if (modelDef.fields) {
|
|
4370
|
+
for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
|
|
4371
|
+
fields[fieldName] = parseFieldOptions(fieldDef);
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
let relationships;
|
|
4375
|
+
if (modelDef.relationships) {
|
|
4376
|
+
relationships = {};
|
|
4377
|
+
for (const [relName, relDef] of Object.entries(
|
|
4378
|
+
modelDef.relationships
|
|
4379
|
+
)) {
|
|
4380
|
+
relationships[relName] = parseRelationship(relDef);
|
|
4381
|
+
}
|
|
4382
|
+
}
|
|
4383
|
+
let uniqueConstraints;
|
|
4384
|
+
if (modelDef.unique_constraints) {
|
|
4385
|
+
uniqueConstraints = [];
|
|
4386
|
+
for (const raw of modelDef.unique_constraints) {
|
|
4387
|
+
uniqueConstraints.push({
|
|
4388
|
+
name: raw.name,
|
|
4389
|
+
fields: [...raw.fields]
|
|
4390
|
+
});
|
|
4391
|
+
}
|
|
4392
|
+
}
|
|
4393
|
+
schemas.push(
|
|
4394
|
+
defineModelSchema({
|
|
4395
|
+
name: modelName,
|
|
4396
|
+
fields,
|
|
4397
|
+
options: {
|
|
4398
|
+
uniqueConstraints,
|
|
4399
|
+
relationships
|
|
4400
|
+
}
|
|
4401
|
+
})
|
|
4402
|
+
);
|
|
4403
|
+
}
|
|
4404
|
+
return schemas;
|
|
4405
|
+
}
|
|
4406
|
+
|
|
4407
|
+
// src/cloudflare-do.ts
|
|
4408
|
+
init_metaSync();
|
|
3776
4409
|
export {
|
|
3777
4410
|
DurableObjectEngine,
|
|
3778
4411
|
JsonQueryTranslator,
|
|
3779
4412
|
JsonSchemaDDL,
|
|
4413
|
+
clearMetaSyncCache,
|
|
3780
4414
|
createDatabaseDO,
|
|
3781
4415
|
createDocumentDO,
|
|
3782
|
-
|
|
4416
|
+
discoverModelNames,
|
|
4417
|
+
discoverSchema,
|
|
4418
|
+
handleRequest,
|
|
4419
|
+
inferFieldType,
|
|
4420
|
+
loadSchemaFromTomlString,
|
|
4421
|
+
registerFunctionDefault,
|
|
4422
|
+
schemaToToml,
|
|
4423
|
+
syncInferredMeta,
|
|
4424
|
+
syncModelMeta
|
|
3783
4425
|
};
|