js-bao 0.5.1 → 0.5.2

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.
@@ -72,8 +72,203 @@ var init_yjsValues = __esm({
72
72
  }
73
73
  });
74
74
 
75
- // src/models/metaSync.ts
75
+ // src/models/writeLifecycle.ts
76
76
  import * as Y2 from "yjs";
77
+ import { ulid } from "ulid";
78
+ function assertNever(x) {
79
+ throw new Error(`Unhandled field type: ${String(x)}`);
80
+ }
81
+ function fieldWriteKind(type) {
82
+ switch (type) {
83
+ case "string":
84
+ case "number":
85
+ case "boolean":
86
+ case "date":
87
+ case "id":
88
+ return "scalar";
89
+ case "stringset":
90
+ return "stringset";
91
+ default:
92
+ return assertNever(type);
93
+ }
94
+ }
95
+ function isKnownFieldType(type) {
96
+ return KNOWN_FIELD_TYPES.has(type);
97
+ }
98
+ function ensureStringSetYMap(rm, fieldName) {
99
+ const nested = rm.get(fieldName);
100
+ if (nested instanceof Y2.Map) return nested;
101
+ const fresh = new Y2.Map();
102
+ if (nested && typeof nested === "object") {
103
+ for (const [member, marker] of Object.entries(
104
+ nested
105
+ )) {
106
+ if (Boolean(marker)) fresh.set(member, true);
107
+ }
108
+ }
109
+ rm.set(fieldName, fresh);
110
+ return fresh;
111
+ }
112
+ function applyStringSetDelta(target, delta) {
113
+ if (delta.clear) {
114
+ for (const member of Array.from(target.keys())) target.delete(member);
115
+ }
116
+ if (delta.remove) {
117
+ for (const member of delta.remove) target.delete(String(member));
118
+ }
119
+ if (delta.add) {
120
+ for (const member of delta.add) {
121
+ const key = String(member);
122
+ if (target.get(key) !== true) target.set(key, true);
123
+ }
124
+ }
125
+ }
126
+ function replaceStringSet(rm, fieldName, members) {
127
+ const target = ensureStringSetYMap(rm, fieldName);
128
+ const desired = /* @__PURE__ */ new Set();
129
+ for (const member of members) desired.add(String(member));
130
+ for (const existing of Array.from(target.keys())) {
131
+ if (!desired.has(existing)) target.delete(existing);
132
+ }
133
+ for (const member of desired) {
134
+ if (target.get(member) !== true) target.set(member, true);
135
+ }
136
+ }
137
+ function applyStringSetChange(recordYMap, fieldName, change) {
138
+ const target = ensureStringSetYMap(recordYMap, fieldName);
139
+ applyStringSetDelta(target, {
140
+ add: change.additions,
141
+ remove: change.removals
142
+ });
143
+ }
144
+ function parseStringSetOp(value) {
145
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Y2.AbstractType) {
146
+ return null;
147
+ }
148
+ const keys = Object.keys(value);
149
+ const opKeys = keys.filter((k) => k.startsWith("$"));
150
+ if (opKeys.length === 0) return null;
151
+ const known = /* @__PURE__ */ new Set(["$add", "$remove", "$clear"]);
152
+ const unknown = keys.filter((k) => !known.has(k));
153
+ if (unknown.length > 0) {
154
+ throw new Error(
155
+ `Invalid stringset op: unexpected key(s) [${unknown.join(", ")}]. Only $add, $remove and $clear are supported.`
156
+ );
157
+ }
158
+ const obj = value;
159
+ const toMembers = (raw, opName) => {
160
+ if (raw === void 0) return [];
161
+ if (!Array.isArray(raw)) {
162
+ throw new Error(`Invalid stringset op: ${opName} must be an array.`);
163
+ }
164
+ return raw.map((m) => String(m));
165
+ };
166
+ return {
167
+ add: toMembers(obj.$add, "$add"),
168
+ remove: toMembers(obj.$remove, "$remove"),
169
+ clear: obj.$clear === true
170
+ };
171
+ }
172
+ function writeField(rm, declaredType, key, value, modelName) {
173
+ const kind = declaredType && isKnownFieldType(declaredType) ? fieldWriteKind(declaredType) : "scalar";
174
+ if (Array.isArray(value)) {
175
+ if (kind !== "stringset") {
176
+ throw new Error(
177
+ `Cannot write array value to field "${key}" on model "${modelName}": its declared type could not be resolved to a collection (stringset). Collection fields require schema metadata in the document (_meta_${modelName}); the model must be saved by a client at least once before a workflow writes one of its collection fields.`
178
+ );
179
+ }
180
+ replaceStringSet(rm, key, value);
181
+ return;
182
+ }
183
+ const op = parseStringSetOp(value);
184
+ if (op) {
185
+ if (kind !== "stringset") {
186
+ throw new Error(
187
+ `Cannot apply a stringset op ({ $add/$remove/$clear }) to field "${key}" on model "${modelName}": its declared type is ${declaredType ? `"${declaredType}"` : "unresolved"}, not a collection (stringset). Op-objects are only valid for stringset / refersToMany fields with schema metadata (_meta_${modelName}).`
188
+ );
189
+ }
190
+ applyStringSetDelta(ensureStringSetYMap(rm, key), op);
191
+ return;
192
+ }
193
+ if (kind === "stringset") {
194
+ throw new Error(
195
+ `Field "${key}" on model "${modelName}" is a stringset and requires an array (replace) or { $add/$remove/$clear } (delta) value; received ${value === null ? "null" : typeof value}.`
196
+ );
197
+ }
198
+ rm.set(key, value);
199
+ }
200
+ function registerFunctionDefaultGenerator(name, fn) {
201
+ FUNCTION_DEFAULT_GENERATORS.set(name, fn);
202
+ }
203
+ function decodeFunctionDefault(name) {
204
+ return FUNCTION_DEFAULT_GENERATORS.get(name);
205
+ }
206
+ function resolveDefaultValue(rawDefault, opts = {}) {
207
+ if (typeof rawDefault === "function") return rawDefault();
208
+ if (typeof rawDefault === "string" && rawDefault.startsWith("$")) {
209
+ if (rawDefault.startsWith("$$")) return rawDefault.slice(1);
210
+ const name = rawDefault.slice(1);
211
+ const gen = opts.decode?.(name) ?? decodeFunctionDefault(name);
212
+ if (!gen) {
213
+ throw new Error(
214
+ `Unknown function default "${rawDefault}" for field "${opts.fieldName ?? "?"}"${opts.modelName ? ` on model "${opts.modelName}"` : ""}: no generator is registered for "${name}". Register it via registerFunctionDefaultGenerator("${name}", fn) so the server can populate the field the same way the client does.`
215
+ );
216
+ }
217
+ return gen();
218
+ }
219
+ return rawDefault;
220
+ }
221
+ function applyDefaultsFromSchema(fields, data, opts = {}) {
222
+ for (const [fieldName, field] of Object.entries(fields)) {
223
+ if (fieldName === "id") continue;
224
+ if (!field || field.default === void 0) continue;
225
+ if (field.default === UNKNOWN_FUNCTION_DEFAULT) {
226
+ continue;
227
+ }
228
+ if (Object.prototype.hasOwnProperty.call(data, fieldName) && data[fieldName] !== void 0) {
229
+ continue;
230
+ }
231
+ data[fieldName] = resolveDefaultValue(field.default, {
232
+ fieldName,
233
+ modelName: opts.modelName,
234
+ decode: opts.decodeFunctionDefault
235
+ });
236
+ }
237
+ return data;
238
+ }
239
+ function applyAutoStampsFromSchema(fields, data, opts) {
240
+ const { isNew, now } = opts;
241
+ for (const [fieldName, field] of Object.entries(fields)) {
242
+ const stamp = field?.autoStamp;
243
+ if (!stamp) continue;
244
+ const explicit = Object.prototype.hasOwnProperty.call(data, fieldName) && data[fieldName] !== null && data[fieldName] !== void 0;
245
+ if (explicit) continue;
246
+ if (stamp === "create" && !isNew) continue;
247
+ data[fieldName] = now;
248
+ }
249
+ return data;
250
+ }
251
+ var KNOWN_FIELD_TYPE_MAP, KNOWN_FIELD_TYPES, FUNCTION_DEFAULT_GENERATORS, UNKNOWN_FUNCTION_DEFAULT;
252
+ var init_writeLifecycle = __esm({
253
+ "src/models/writeLifecycle.ts"() {
254
+ "use strict";
255
+ KNOWN_FIELD_TYPE_MAP = {
256
+ string: true,
257
+ number: true,
258
+ boolean: true,
259
+ date: true,
260
+ id: true,
261
+ stringset: true
262
+ };
263
+ KNOWN_FIELD_TYPES = new Set(Object.keys(KNOWN_FIELD_TYPE_MAP));
264
+ FUNCTION_DEFAULT_GENERATORS = /* @__PURE__ */ new Map();
265
+ registerFunctionDefaultGenerator("generate_ulid", () => ulid());
266
+ UNKNOWN_FUNCTION_DEFAULT = "$unknown_function";
267
+ }
268
+ });
269
+
270
+ // src/models/metaSync.ts
271
+ import * as Y3 from "yjs";
77
272
  function inferFieldType(value) {
78
273
  if (isStringSetYMap(value)) return "stringset";
79
274
  switch (typeof value) {
@@ -94,7 +289,10 @@ function encodeDefault(value) {
94
289
  if (value === void 0 || value === null) return void 0;
95
290
  if (typeof value === "function") {
96
291
  const name = KNOWN_FUNCTION_DEFAULTS.get(value);
97
- return name ? `$${name}` : "$unknown_function";
292
+ return name ? `$${name}` : UNKNOWN_FUNCTION_DEFAULT;
293
+ }
294
+ if (typeof value === "string" && value.startsWith("$")) {
295
+ return `$${value}`;
98
296
  }
99
297
  return value;
100
298
  }
@@ -120,7 +318,7 @@ function syncModelMeta(yDoc, modelName, schema) {
120
318
  if (compoundConstraints.length > 0) {
121
319
  let constraints = meta.get("_constraints");
122
320
  if (!constraints) {
123
- constraints = new Y2.Map();
321
+ constraints = new Y3.Map();
124
322
  meta.set("_constraints", constraints);
125
323
  }
126
324
  for (const constraint of compoundConstraints) {
@@ -131,7 +329,7 @@ function syncModelMeta(yDoc, modelName, schema) {
131
329
  if (relationships && Object.keys(relationships).length > 0) {
132
330
  let rels = meta.get("_relationships");
133
331
  if (!rels) {
134
- rels = new Y2.Map();
332
+ rels = new Y3.Map();
135
333
  meta.set("_relationships", rels);
136
334
  }
137
335
  for (const [relName, relConfig] of Object.entries(relationships)) {
@@ -143,7 +341,7 @@ function syncModelMeta(yDoc, modelName, schema) {
143
341
  function syncFieldMeta(metaMap, fieldName, fieldOpts) {
144
342
  let fieldMeta = metaMap.get(fieldName);
145
343
  if (!fieldMeta) {
146
- fieldMeta = new Y2.Map();
344
+ fieldMeta = new Y3.Map();
147
345
  metaMap.set(fieldName, fieldMeta);
148
346
  }
149
347
  setIfChanged(fieldMeta, "type", fieldOpts.type);
@@ -160,7 +358,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
160
358
  function syncConstraintMeta(constraintsMap, constraint) {
161
359
  let cMeta = constraintsMap.get(constraint.name);
162
360
  if (!cMeta) {
163
- cMeta = new Y2.Map();
361
+ cMeta = new Y3.Map();
164
362
  constraintsMap.set(constraint.name, cMeta);
165
363
  }
166
364
  setIfChanged(cMeta, "type", "unique");
@@ -170,7 +368,7 @@ function syncConstraintMeta(constraintsMap, constraint) {
170
368
  function syncRelationshipMeta(relsMap, relName, relConfig) {
171
369
  let relMeta = relsMap.get(relName);
172
370
  if (!relMeta) {
173
- relMeta = new Y2.Map();
371
+ relMeta = new Y3.Map();
174
372
  relsMap.set(relName, relMeta);
175
373
  }
176
374
  for (const [key, value] of Object.entries(relConfig)) {
@@ -185,7 +383,7 @@ function syncInferredMeta(yDoc, modelName, recordData) {
185
383
  if (fieldName.startsWith("_")) continue;
186
384
  let fieldMeta = meta.get(fieldName);
187
385
  if (!fieldMeta) {
188
- fieldMeta = new Y2.Map();
386
+ fieldMeta = new Y3.Map();
189
387
  meta.set(fieldName, fieldMeta);
190
388
  }
191
389
  if (!fieldMeta.has("type")) {
@@ -206,16 +404,42 @@ var init_metaSync = __esm({
206
404
  "src/models/metaSync.ts"() {
207
405
  "use strict";
208
406
  init_yjsValues();
407
+ init_writeLifecycle();
209
408
  KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
210
409
  _syncedCache = /* @__PURE__ */ new WeakMap();
211
410
  }
212
411
  });
213
412
 
413
+ // src/utils/uniqueIndex.ts
414
+ import * as Y4 from "yjs";
415
+ function buildUniqueKey(recordData, fields) {
416
+ const keyParts = [];
417
+ for (const field of fields) {
418
+ const value = recordData instanceof Y4.Map ? recordData.get(field) : recordData[field];
419
+ if (value === null || value === void 0) {
420
+ return null;
421
+ }
422
+ keyParts.push(String(value));
423
+ }
424
+ return keyParts.join("|");
425
+ }
426
+ function uniqueIndexMapName(modelName, constraintName) {
427
+ return `_uniqueIdx_${modelName}_${constraintName}`;
428
+ }
429
+ function fieldUniqueConstraintName(modelName, fieldName) {
430
+ return `${modelName}_${fieldName}_unique`;
431
+ }
432
+ var init_uniqueIndex = __esm({
433
+ "src/utils/uniqueIndex.ts"() {
434
+ "use strict";
435
+ }
436
+ });
437
+
214
438
  // src/models/BaseModel.ts
215
- import * as Y3 from "yjs";
216
- import { ulid } from "ulid";
439
+ import * as Y5 from "yjs";
440
+ import { ulid as ulid2 } from "ulid";
217
441
  function generateULID() {
218
- return ulid();
442
+ return ulid2();
219
443
  }
220
444
  var Logger;
221
445
  var init_BaseModel = __esm({
@@ -227,7 +451,9 @@ var init_BaseModel = __esm({
227
451
  init_CursorManager();
228
452
  init_sql();
229
453
  init_metaSync();
454
+ init_writeLifecycle();
230
455
  init_yjsValues();
456
+ init_uniqueIndex();
231
457
  Logger = class {
232
458
  static _logLevel = 1 /* ERROR */;
233
459
  static _logCallback = null;
@@ -1195,7 +1421,7 @@ function connectDoDb(options) {
1195
1421
 
1196
1422
  // src/utils/yDocSchema.ts
1197
1423
  init_yjsValues();
1198
- import * as Y4 from "yjs";
1424
+ import * as Y6 from "yjs";
1199
1425
  function discoverSchema(yDoc) {
1200
1426
  const models = {};
1201
1427
  const metaNames = /* @__PURE__ */ new Set();
@@ -1229,7 +1455,7 @@ function discoverModelNames(yDoc) {
1229
1455
  function materializeMap(yDoc, key) {
1230
1456
  try {
1231
1457
  const map = yDoc.getMap(key);
1232
- return map instanceof Y4.Map ? map : null;
1458
+ return map instanceof Y6.Map ? map : null;
1233
1459
  } catch {
1234
1460
  return null;
1235
1461
  }
@@ -1239,11 +1465,11 @@ function readModelMeta(metaMap) {
1239
1465
  let constraints;
1240
1466
  let relationships;
1241
1467
  for (const [key, value] of metaMap.entries()) {
1242
- if (key === "_constraints" && value instanceof Y4.Map) {
1468
+ if (key === "_constraints" && value instanceof Y6.Map) {
1243
1469
  constraints = readConstraints(value);
1244
- } else if (key === "_relationships" && value instanceof Y4.Map) {
1470
+ } else if (key === "_relationships" && value instanceof Y6.Map) {
1245
1471
  relationships = readRelationships(value);
1246
- } else if (value instanceof Y4.Map) {
1472
+ } else if (value instanceof Y6.Map) {
1247
1473
  fields[key] = readFieldMeta(value);
1248
1474
  }
1249
1475
  }
@@ -1278,7 +1504,7 @@ function inferModelFromData(dataMap) {
1278
1504
  const fields = {};
1279
1505
  let sampled = 0;
1280
1506
  for (const [_recordId, recordValue] of dataMap.entries()) {
1281
- if (!(recordValue instanceof Y4.Map)) continue;
1507
+ if (!(recordValue instanceof Y6.Map)) continue;
1282
1508
  if (++sampled > 5) break;
1283
1509
  for (const [fieldName, value] of recordValue.entries()) {
1284
1510
  if (fieldName.startsWith("_")) continue;
@@ -1306,7 +1532,7 @@ function inferTypeFromValue(value) {
1306
1532
  function readConstraints(constraintsMap) {
1307
1533
  const out = {};
1308
1534
  for (const [name, value] of constraintsMap.entries()) {
1309
- if (!(value instanceof Y4.Map)) continue;
1535
+ if (!(value instanceof Y6.Map)) continue;
1310
1536
  let fields = [];
1311
1537
  const rawFields = value.get("fields");
1312
1538
  if (typeof rawFields === "string") {
@@ -1325,7 +1551,7 @@ function readConstraints(constraintsMap) {
1325
1551
  function readRelationships(relsMap) {
1326
1552
  const out = {};
1327
1553
  for (const [name, value] of relsMap.entries()) {
1328
- if (!(value instanceof Y4.Map)) continue;
1554
+ if (!(value instanceof Y6.Map)) continue;
1329
1555
  const rel = {};
1330
1556
  for (const [k, v] of value.entries()) {
1331
1557
  rel[k] = v;
@@ -1404,6 +1630,7 @@ import { parse as parseToml } from "smol-toml";
1404
1630
  // src/models/schema.ts
1405
1631
  init_BaseModel();
1406
1632
  init_ModelRegistry();
1633
+ init_uniqueIndex();
1407
1634
  function defineModelSchema(input) {
1408
1635
  const { name, fields } = input;
1409
1636
  const options = {
@@ -1446,7 +1673,7 @@ function resolveUniqueConstraints(modelName, fields, customConstraints) {
1446
1673
  for (const [fieldName, options] of fields.entries()) {
1447
1674
  if (options.unique) {
1448
1675
  resolved.push({
1449
- name: `${modelName}_${fieldName}_unique`,
1676
+ name: fieldUniqueConstraintName(modelName, fieldName),
1450
1677
  fields: [fieldName]
1451
1678
  });
1452
1679
  }
@@ -1692,20 +1919,39 @@ function loadSchemaFromTomlString(tomlString, options = {}) {
1692
1919
  }
1693
1920
 
1694
1921
  // src/cloudflare.ts
1922
+ init_uniqueIndex();
1695
1923
  init_metaSync();
1924
+ init_writeLifecycle();
1696
1925
  export {
1697
1926
  DOClientEngine,
1927
+ applyAutoStampsFromSchema,
1928
+ applyDefaultsFromSchema,
1929
+ applyStringSetChange,
1930
+ applyStringSetDelta,
1931
+ assertNever,
1932
+ buildUniqueKey,
1698
1933
  clearMetaSyncCache,
1699
1934
  connectDoDb,
1935
+ decodeFunctionDefault,
1700
1936
  discoverModelNames,
1701
1937
  discoverSchema,
1938
+ ensureStringSetYMap,
1939
+ fieldUniqueConstraintName,
1940
+ fieldWriteKind,
1702
1941
  getActiveDOEngine,
1703
1942
  inferFieldType,
1704
1943
  initJsBaoDO,
1944
+ isKnownFieldType,
1705
1945
  loadSchemaFromTomlString,
1946
+ parseStringSetOp,
1706
1947
  registerFunctionDefault,
1948
+ registerFunctionDefaultGenerator,
1949
+ replaceStringSet,
1707
1950
  resetJsBaoDO,
1951
+ resolveDefaultValue,
1708
1952
  schemaToToml,
1709
1953
  syncInferredMeta,
1710
- syncModelMeta
1954
+ syncModelMeta,
1955
+ uniqueIndexMapName,
1956
+ writeField
1711
1957
  };
@@ -89,37 +89,76 @@ var init_yjsValues = __esm({
89
89
  }
90
90
  });
91
91
 
92
+ // src/models/writeLifecycle.ts
93
+ function registerFunctionDefaultGenerator(name, fn) {
94
+ FUNCTION_DEFAULT_GENERATORS.set(name, fn);
95
+ }
96
+ var Y2, import_ulid, KNOWN_FIELD_TYPE_MAP, KNOWN_FIELD_TYPES, FUNCTION_DEFAULT_GENERATORS;
97
+ var init_writeLifecycle = __esm({
98
+ "src/models/writeLifecycle.ts"() {
99
+ "use strict";
100
+ Y2 = __toESM(require("yjs"), 1);
101
+ import_ulid = require("ulid");
102
+ KNOWN_FIELD_TYPE_MAP = {
103
+ string: true,
104
+ number: true,
105
+ boolean: true,
106
+ date: true,
107
+ id: true,
108
+ stringset: true
109
+ };
110
+ KNOWN_FIELD_TYPES = new Set(Object.keys(KNOWN_FIELD_TYPE_MAP));
111
+ FUNCTION_DEFAULT_GENERATORS = /* @__PURE__ */ new Map();
112
+ registerFunctionDefaultGenerator("generate_ulid", () => (0, import_ulid.ulid)());
113
+ }
114
+ });
115
+
92
116
  // src/models/metaSync.ts
93
117
  function registerFunctionDefault(fn, name) {
94
118
  KNOWN_FUNCTION_DEFAULTS.set(fn, name);
95
119
  }
96
- var Y2, KNOWN_FUNCTION_DEFAULTS;
120
+ var Y3, KNOWN_FUNCTION_DEFAULTS;
97
121
  var init_metaSync = __esm({
98
122
  "src/models/metaSync.ts"() {
99
123
  "use strict";
100
- Y2 = __toESM(require("yjs"), 1);
124
+ Y3 = __toESM(require("yjs"), 1);
101
125
  init_yjsValues();
126
+ init_writeLifecycle();
102
127
  KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
103
128
  }
104
129
  });
105
130
 
131
+ // src/utils/uniqueIndex.ts
132
+ function fieldUniqueConstraintName(modelName, fieldName) {
133
+ return `${modelName}_${fieldName}_unique`;
134
+ }
135
+ var Y4;
136
+ var init_uniqueIndex = __esm({
137
+ "src/utils/uniqueIndex.ts"() {
138
+ "use strict";
139
+ Y4 = __toESM(require("yjs"), 1);
140
+ }
141
+ });
142
+
106
143
  // src/models/BaseModel.ts
107
144
  function generateULID() {
108
- return (0, import_ulid.ulid)();
145
+ return (0, import_ulid2.ulid)();
109
146
  }
110
- var Y3, import_ulid;
147
+ var Y5, import_ulid2;
111
148
  var init_BaseModel = __esm({
112
149
  "src/models/BaseModel.ts"() {
113
150
  "use strict";
114
- Y3 = __toESM(require("yjs"), 1);
115
- import_ulid = require("ulid");
151
+ Y5 = __toESM(require("yjs"), 1);
152
+ import_ulid2 = require("ulid");
116
153
  init_StringSet();
117
154
  init_documentTypes();
118
155
  init_DocumentQueryTranslator();
119
156
  init_CursorManager();
120
157
  init_sql();
121
158
  init_metaSync();
159
+ init_writeLifecycle();
122
160
  init_yjsValues();
161
+ init_uniqueIndex();
123
162
  registerFunctionDefault(generateULID, "generate_ulid");
124
163
  }
125
164
  });
@@ -155,6 +194,7 @@ var import_smol_toml = require("smol-toml");
155
194
  // src/models/schema.ts
156
195
  init_BaseModel();
157
196
  init_ModelRegistry();
197
+ init_uniqueIndex();
158
198
  function defineModelSchema(input) {
159
199
  const { name, fields } = input;
160
200
  const options = {
@@ -197,7 +237,7 @@ function resolveUniqueConstraints(modelName, fields, customConstraints) {
197
237
  for (const [fieldName, options] of fields.entries()) {
198
238
  if (options.unique) {
199
239
  resolved.push({
200
- name: `${modelName}_${fieldName}_unique`,
240
+ name: fieldUniqueConstraintName(modelName, fieldName),
201
241
  fields: [fieldName]
202
242
  });
203
243
  }
@@ -616,14 +656,12 @@ function renderModelFile(input) {
616
656
  lines.push("");
617
657
  lines.push(`export class ${className} extends BaseModelImpl {}`);
618
658
  lines.push("");
619
- lines.push(
620
- `export const ${className}_modelName: "${escapeStringLiteral(modelName)}" = ${JSON.stringify(modelName)};`
621
- );
659
+ lines.push(`export const ${className}_modelName = ${JSON.stringify(modelName)} as const;`);
622
660
  lines.push("");
623
661
  return lines.join("\n");
624
662
  }
625
663
  function renderBarrel(input) {
626
- const { fingerprint, tomlFilename, entries } = input;
664
+ const { fingerprint, tomlImportPath, entries } = input;
627
665
  const lines = [];
628
666
  lines.push(HEADER_BANNER);
629
667
  lines.push(REGEN_INSTRUCTION);
@@ -644,7 +682,7 @@ function renderBarrel(input) {
644
682
  lines.push(
645
683
  `import { attachAndRegisterModel, loadSchemaFromTomlString } from "js-bao";`
646
684
  );
647
- lines.push(`import modelsToml from "./${tomlFilename}?raw";`);
685
+ lines.push(`import modelsToml from ${JSON.stringify(`${tomlImportPath}?raw`)};`);
648
686
  for (const { className } of entries) {
649
687
  lines.push(`import { ${className} } from "./${className}.generated";`);
650
688
  }
@@ -697,11 +735,18 @@ function renderBarrel(input) {
697
735
  lines.push("");
698
736
  return lines.join("\n");
699
737
  }
700
- function escapeStringLiteral(s) {
701
- return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
702
- }
703
738
 
704
739
  // src/cli/v2/generator.ts
740
+ function computeSchemaImportSpecifier(outputDirAbs, inputTomlAbs, pathImpl = path) {
741
+ const relative2 = pathImpl.relative(outputDirAbs, inputTomlAbs);
742
+ if (pathImpl.isAbsolute(relative2)) {
743
+ throw new Error(
744
+ `Cannot build a relative import from the output directory ${outputDirAbs} to the schema ${inputTomlAbs} \u2014 they share no common root. Pass --schema-import <specifier> to name the runtime schema path explicitly.`
745
+ );
746
+ }
747
+ const posix = relative2.split(pathImpl.sep).join("/");
748
+ return posix.startsWith("./") || posix.startsWith("../") ? posix : `./${posix}`;
749
+ }
705
750
  var GENERATED_FILE_SUFFIX = ".generated.ts";
706
751
  var BARREL_FILENAME = "index.ts";
707
752
  async function generate(options) {
@@ -733,7 +778,7 @@ async function generate(options) {
733
778
  modelName: s.name,
734
779
  className: classNamesByModelName[s.name]
735
780
  })).sort((a, b) => a.className.localeCompare(b.className));
736
- const tomlFilename = path.basename(inputAbs);
781
+ const tomlImportPath = options.schemaImportPath !== void 0 && options.schemaImportPath.trim() !== "" ? options.schemaImportPath : computeSchemaImportSpecifier(outAbs, inputAbs);
737
782
  const filesToWrite = /* @__PURE__ */ new Map();
738
783
  for (const schema of schemas) {
739
784
  const className = classNamesByModelName[schema.name];
@@ -755,7 +800,7 @@ async function generate(options) {
755
800
  barrelPath,
756
801
  renderBarrel({
757
802
  fingerprint,
758
- tomlFilename,
803
+ tomlImportPath,
759
804
  entries
760
805
  })
761
806
  );
@@ -1548,7 +1593,7 @@ var Logger2 = class _Logger {
1548
1593
  // package.json
1549
1594
  var package_default = {
1550
1595
  name: "js-bao",
1551
- version: "0.5.1",
1596
+ version: "0.5.2",
1552
1597
  description: "A library providing data modeling capabilities which support live updates and queries.",
1553
1598
  types: "dist/index.d.ts",
1554
1599
  type: "module",
@@ -1604,6 +1649,7 @@ var package_default = {
1604
1649
  "jsbao-codegen-v2": "./dist/codegen-v2.cjs"
1605
1650
  },
1606
1651
  scripts: {
1652
+ typecheck: "tsc --noEmit -p tsconfig.json",
1607
1653
  build: "pnpm build:cli && pnpm build:cli-v2 && pnpm codegen && rm -f dist/index.* dist/node.* dist/browser.* dist/cloudflare.* dist/cloudflare-do.* dist/client.* && pnpm build:main",
1608
1654
  "build:main": "pnpm build:browser && pnpm build:node && pnpm build:universal && pnpm build:cloudflare && pnpm build:cloudflare-do && pnpm build:client",
1609
1655
  "build:browser": "tsup src/browser.ts --format esm,cjs --dts --platform browser --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs --external fs --no-splitting",
@@ -1686,6 +1732,9 @@ var CodegenV2CLI = class {
1686
1732
  ).requiredOption("-i, --input <file>", "Path to models.toml").requiredOption(
1687
1733
  "-o, --output <dir>",
1688
1734
  "Output directory for *.generated.ts and index.ts"
1735
+ ).option(
1736
+ "--schema-import <specifier>",
1737
+ "Import specifier for the schema in the generated barrel (default: path from --output to --input, e.g. ./models.toml)"
1689
1738
  ).option(
1690
1739
  "--check",
1691
1740
  "Exit non-zero if generated output would change (CI-friendly)",
@@ -1726,6 +1775,14 @@ var CodegenV2CLI = class {
1726
1775
  const outputAbs = path3.resolve(options.output);
1727
1776
  Logger2.verbose(`Input: ${inputAbs}`, "js-bao-codegen-v2");
1728
1777
  Logger2.verbose(`Output: ${outputAbs}`, "js-bao-codegen-v2");
1778
+ if (options.schemaImport !== void 0 && options.schemaImport.trim() === "") {
1779
+ Logger2.error(
1780
+ "--schema-import was empty. Pass a module specifier (e.g. @shared/models.toml), or omit the flag to import the schema relative to --output.",
1781
+ "js-bao-codegen-v2"
1782
+ );
1783
+ process.exit(1);
1784
+ return;
1785
+ }
1729
1786
  let result;
1730
1787
  try {
1731
1788
  result = await generate({
@@ -1735,7 +1792,8 @@ var CodegenV2CLI = class {
1735
1792
  // commander turns `--no-strict` into `strict: false`; otherwise
1736
1793
  // `options.strict` is `true` by default (its negation-flag
1737
1794
  // behavior — see the option declaration above).
1738
- strict: options.strict !== false
1795
+ strict: options.strict !== false,
1796
+ schemaImportPath: options.schemaImport
1739
1797
  });
1740
1798
  } catch (err) {
1741
1799
  Logger2.error(String(err instanceof Error ? err.message : err), "js-bao-codegen-v2");
package/dist/codegen.cjs CHANGED
@@ -1186,7 +1186,7 @@ var SchemaExtractor = class {
1186
1186
  // package.json
1187
1187
  var package_default = {
1188
1188
  name: "js-bao",
1189
- version: "0.5.1",
1189
+ version: "0.5.2",
1190
1190
  description: "A library providing data modeling capabilities which support live updates and queries.",
1191
1191
  types: "dist/index.d.ts",
1192
1192
  type: "module",
@@ -1242,6 +1242,7 @@ var package_default = {
1242
1242
  "jsbao-codegen-v2": "./dist/codegen-v2.cjs"
1243
1243
  },
1244
1244
  scripts: {
1245
+ typecheck: "tsc --noEmit -p tsconfig.json",
1245
1246
  build: "pnpm build:cli && pnpm build:cli-v2 && pnpm codegen && rm -f dist/index.* dist/node.* dist/browser.* dist/cloudflare.* dist/cloudflare-do.* dist/client.* && pnpm build:main",
1246
1247
  "build:main": "pnpm build:browser && pnpm build:node && pnpm build:universal && pnpm build:cloudflare && pnpm build:cloudflare-do && pnpm build:client",
1247
1248
  "build:browser": "tsup src/browser.ts --format esm,cjs --dts --platform browser --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs --external fs --no-splitting",