js-bao 0.5.0 → 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.
@@ -80,35 +80,85 @@ var init_DocumentQueryTranslator = __esm({
80
80
  }
81
81
  });
82
82
 
83
+ // src/utils/yjsValues.ts
84
+ var Y;
85
+ var init_yjsValues = __esm({
86
+ "src/utils/yjsValues.ts"() {
87
+ "use strict";
88
+ Y = __toESM(require("yjs"), 1);
89
+ }
90
+ });
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
+
83
116
  // src/models/metaSync.ts
84
117
  function registerFunctionDefault(fn, name) {
85
118
  KNOWN_FUNCTION_DEFAULTS.set(fn, name);
86
119
  }
87
- var Y, KNOWN_FUNCTION_DEFAULTS;
120
+ var Y3, KNOWN_FUNCTION_DEFAULTS;
88
121
  var init_metaSync = __esm({
89
122
  "src/models/metaSync.ts"() {
90
123
  "use strict";
91
- Y = __toESM(require("yjs"), 1);
124
+ Y3 = __toESM(require("yjs"), 1);
125
+ init_yjsValues();
126
+ init_writeLifecycle();
92
127
  KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
93
128
  }
94
129
  });
95
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
+
96
143
  // src/models/BaseModel.ts
97
144
  function generateULID() {
98
- return (0, import_ulid.ulid)();
145
+ return (0, import_ulid2.ulid)();
99
146
  }
100
- var Y2, import_ulid;
147
+ var Y5, import_ulid2;
101
148
  var init_BaseModel = __esm({
102
149
  "src/models/BaseModel.ts"() {
103
150
  "use strict";
104
- Y2 = __toESM(require("yjs"), 1);
105
- import_ulid = require("ulid");
151
+ Y5 = __toESM(require("yjs"), 1);
152
+ import_ulid2 = require("ulid");
106
153
  init_StringSet();
107
154
  init_documentTypes();
108
155
  init_DocumentQueryTranslator();
109
156
  init_CursorManager();
110
157
  init_sql();
111
158
  init_metaSync();
159
+ init_writeLifecycle();
160
+ init_yjsValues();
161
+ init_uniqueIndex();
112
162
  registerFunctionDefault(generateULID, "generate_ulid");
113
163
  }
114
164
  });
@@ -144,6 +194,7 @@ var import_smol_toml = require("smol-toml");
144
194
  // src/models/schema.ts
145
195
  init_BaseModel();
146
196
  init_ModelRegistry();
197
+ init_uniqueIndex();
147
198
  function defineModelSchema(input) {
148
199
  const { name, fields } = input;
149
200
  const options = {
@@ -186,7 +237,7 @@ function resolveUniqueConstraints(modelName, fields, customConstraints) {
186
237
  for (const [fieldName, options] of fields.entries()) {
187
238
  if (options.unique) {
188
239
  resolved.push({
189
- name: `${modelName}_${fieldName}_unique`,
240
+ name: fieldUniqueConstraintName(modelName, fieldName),
190
241
  fields: [fieldName]
191
242
  });
192
243
  }
@@ -231,10 +282,13 @@ var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
231
282
  "unique",
232
283
  "required",
233
284
  "auto_assign",
285
+ "auto_stamp",
234
286
  "max_length",
235
287
  "max_count",
236
- "default"
288
+ "default",
289
+ "enum"
237
290
  ]);
291
+ var VALID_AUTO_STAMP_VALUES = /* @__PURE__ */ new Set(["create", "update", "both"]);
238
292
  var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
239
293
  "fields",
240
294
  "relationships",
@@ -276,9 +330,37 @@ function parseFieldOptions(raw, context, strict) {
276
330
  if (raw.unique === true) opts.unique = true;
277
331
  if (raw.required === true) opts.required = true;
278
332
  if (raw.auto_assign === true) opts.autoAssign = true;
333
+ if (raw.auto_stamp !== void 0) {
334
+ if (typeof raw.auto_stamp !== "string" || !VALID_AUTO_STAMP_VALUES.has(raw.auto_stamp)) {
335
+ throw new Error(
336
+ `${context}: invalid auto_stamp value "${raw.auto_stamp}". Must be one of: ${[
337
+ ...VALID_AUTO_STAMP_VALUES
338
+ ].join(", ")}`
339
+ );
340
+ }
341
+ opts.autoStamp = raw.auto_stamp;
342
+ }
279
343
  if (raw.max_length !== void 0) opts.maxLength = raw.max_length;
280
344
  if (raw.max_count !== void 0) opts.maxCount = raw.max_count;
281
345
  if (raw.default !== void 0) opts.default = raw.default;
346
+ if (raw.enum !== void 0) {
347
+ if (raw.type !== "string") {
348
+ throw new Error(
349
+ `${context}: \`enum\` is only valid on a "string" field, not "${raw.type}".`
350
+ );
351
+ }
352
+ if (!Array.isArray(raw.enum) || raw.enum.length === 0) {
353
+ throw new Error(
354
+ `${context}: \`enum\` must be a non-empty array of strings.`
355
+ );
356
+ }
357
+ if (!raw.enum.every((v) => typeof v === "string")) {
358
+ throw new Error(
359
+ `${context}: \`enum\` values must all be strings.`
360
+ );
361
+ }
362
+ opts.enum = [...raw.enum];
363
+ }
282
364
  return opts;
283
365
  }
284
366
  function requireField(raw, field, context) {
@@ -479,6 +561,12 @@ function tsTypeForFieldType(fieldType) {
479
561
  }
480
562
  }
481
563
  }
564
+ function tsTypeForFieldOptions(opts) {
565
+ if (opts.type === "string" && Array.isArray(opts.enum) && opts.enum.length > 0) {
566
+ return opts.enum.map((v) => JSON.stringify(v)).join(" | ");
567
+ }
568
+ return tsTypeForFieldType(opts.type);
569
+ }
482
570
 
483
571
  // src/cli/v2/templates.ts
484
572
  var HEADER_BANNER = "// AUTO-GENERATED FROM models.toml \u2014 DO NOT EDIT.";
@@ -532,7 +620,7 @@ function renderModelFile(input) {
532
620
  lines.push("");
533
621
  lines.push(`export interface ${className}Attrs {`);
534
622
  for (const [fieldName, opts] of Object.entries(fields)) {
535
- const tsType = tsTypeForFieldType(opts.type);
623
+ const tsType = tsTypeForFieldOptions(opts);
536
624
  const isRequired = opts.required === true || opts.autoAssign === true && fieldName === "id";
537
625
  const optional = isRequired ? "" : "?";
538
626
  lines.push(` ${fieldName}${optional}: ${tsType};`);
@@ -568,14 +656,12 @@ function renderModelFile(input) {
568
656
  lines.push("");
569
657
  lines.push(`export class ${className} extends BaseModelImpl {}`);
570
658
  lines.push("");
571
- lines.push(
572
- `export const ${className}_modelName: "${escapeStringLiteral(modelName)}" = ${JSON.stringify(modelName)};`
573
- );
659
+ lines.push(`export const ${className}_modelName = ${JSON.stringify(modelName)} as const;`);
574
660
  lines.push("");
575
661
  return lines.join("\n");
576
662
  }
577
663
  function renderBarrel(input) {
578
- const { fingerprint, tomlFilename, entries } = input;
664
+ const { fingerprint, tomlImportPath, entries } = input;
579
665
  const lines = [];
580
666
  lines.push(HEADER_BANNER);
581
667
  lines.push(REGEN_INSTRUCTION);
@@ -596,7 +682,7 @@ function renderBarrel(input) {
596
682
  lines.push(
597
683
  `import { attachAndRegisterModel, loadSchemaFromTomlString } from "js-bao";`
598
684
  );
599
- lines.push(`import modelsToml from "./${tomlFilename}?raw";`);
685
+ lines.push(`import modelsToml from ${JSON.stringify(`${tomlImportPath}?raw`)};`);
600
686
  for (const { className } of entries) {
601
687
  lines.push(`import { ${className} } from "./${className}.generated";`);
602
688
  }
@@ -649,11 +735,18 @@ function renderBarrel(input) {
649
735
  lines.push("");
650
736
  return lines.join("\n");
651
737
  }
652
- function escapeStringLiteral(s) {
653
- return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
654
- }
655
738
 
656
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
+ }
657
750
  var GENERATED_FILE_SUFFIX = ".generated.ts";
658
751
  var BARREL_FILENAME = "index.ts";
659
752
  async function generate(options) {
@@ -685,7 +778,7 @@ async function generate(options) {
685
778
  modelName: s.name,
686
779
  className: classNamesByModelName[s.name]
687
780
  })).sort((a, b) => a.className.localeCompare(b.className));
688
- const tomlFilename = path.basename(inputAbs);
781
+ const tomlImportPath = options.schemaImportPath !== void 0 && options.schemaImportPath.trim() !== "" ? options.schemaImportPath : computeSchemaImportSpecifier(outAbs, inputAbs);
689
782
  const filesToWrite = /* @__PURE__ */ new Map();
690
783
  for (const schema of schemas) {
691
784
  const className = classNamesByModelName[schema.name];
@@ -707,7 +800,7 @@ async function generate(options) {
707
800
  barrelPath,
708
801
  renderBarrel({
709
802
  fingerprint,
710
- tomlFilename,
803
+ tomlImportPath,
711
804
  entries
712
805
  })
713
806
  );
@@ -804,6 +897,7 @@ var ts = __toESM(require("typescript"), 1);
804
897
  // src/cli/v2/toml-writer.ts
805
898
  var CAMEL_TO_SNAKE = {
806
899
  autoAssign: "auto_assign",
900
+ autoStamp: "auto_stamp",
807
901
  maxLength: "max_length",
808
902
  maxCount: "max_count",
809
903
  relatedIdField: "related_id_field",
@@ -844,11 +938,17 @@ function renderModelsToml(models) {
844
938
  lines.push(`[models.${model.name}.fields.${fieldName}]`);
845
939
  lines.push(`type = ${tomlValue(opts.type)}`);
846
940
  if (opts.autoAssign === true) lines.push("auto_assign = true");
941
+ if (opts.autoStamp !== void 0) {
942
+ lines.push(`auto_stamp = ${tomlValue(opts.autoStamp)}`);
943
+ }
847
944
  if (opts.indexed === true) lines.push("indexed = true");
848
945
  if (opts.unique === true) lines.push("unique = true");
849
946
  if (opts.required === true) lines.push("required = true");
850
947
  if (opts.maxLength !== void 0) lines.push(`max_length = ${opts.maxLength}`);
851
948
  if (opts.maxCount !== void 0) lines.push(`max_count = ${opts.maxCount}`);
949
+ if (opts.enum !== void 0) {
950
+ lines.push(`enum = [${opts.enum.map((v) => tomlValue(v)).join(", ")}]`);
951
+ }
852
952
  if (opts.default !== void 0) {
853
953
  lines.push(`default = ${tomlValue(opts.default)}`);
854
954
  }
@@ -1108,6 +1208,11 @@ function parseFields(obj, sf) {
1108
1208
  case "autoAssign":
1109
1209
  if (typeof value === "boolean") opts.autoAssign = value;
1110
1210
  break;
1211
+ case "autoStamp":
1212
+ if (value === "create" || value === "update" || value === "both") {
1213
+ opts.autoStamp = value;
1214
+ }
1215
+ break;
1111
1216
  case "maxLength":
1112
1217
  if (typeof value === "number") opts.maxLength = value;
1113
1218
  break;
@@ -1488,7 +1593,7 @@ var Logger2 = class _Logger {
1488
1593
  // package.json
1489
1594
  var package_default = {
1490
1595
  name: "js-bao",
1491
- version: "0.5.0",
1596
+ version: "0.5.2",
1492
1597
  description: "A library providing data modeling capabilities which support live updates and queries.",
1493
1598
  types: "dist/index.d.ts",
1494
1599
  type: "module",
@@ -1544,6 +1649,7 @@ var package_default = {
1544
1649
  "jsbao-codegen-v2": "./dist/codegen-v2.cjs"
1545
1650
  },
1546
1651
  scripts: {
1652
+ typecheck: "tsc --noEmit -p tsconfig.json",
1547
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",
1548
1654
  "build:main": "pnpm build:browser && pnpm build:node && pnpm build:universal && pnpm build:cloudflare && pnpm build:cloudflare-do && pnpm build:client",
1549
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",
@@ -1626,6 +1732,9 @@ var CodegenV2CLI = class {
1626
1732
  ).requiredOption("-i, --input <file>", "Path to models.toml").requiredOption(
1627
1733
  "-o, --output <dir>",
1628
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)"
1629
1738
  ).option(
1630
1739
  "--check",
1631
1740
  "Exit non-zero if generated output would change (CI-friendly)",
@@ -1666,6 +1775,14 @@ var CodegenV2CLI = class {
1666
1775
  const outputAbs = path3.resolve(options.output);
1667
1776
  Logger2.verbose(`Input: ${inputAbs}`, "js-bao-codegen-v2");
1668
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
+ }
1669
1786
  let result;
1670
1787
  try {
1671
1788
  result = await generate({
@@ -1675,7 +1792,8 @@ var CodegenV2CLI = class {
1675
1792
  // commander turns `--no-strict` into `strict: false`; otherwise
1676
1793
  // `options.strict` is `true` by default (its negation-flag
1677
1794
  // behavior — see the option declaration above).
1678
- strict: options.strict !== false
1795
+ strict: options.strict !== false,
1796
+ schemaImportPath: options.schemaImport
1679
1797
  });
1680
1798
  } catch (err) {
1681
1799
  Logger2.error(String(err instanceof Error ? err.message : err), "js-bao-codegen-v2");
package/dist/codegen.cjs CHANGED
@@ -1136,6 +1136,11 @@ var SchemaExtractor = class {
1136
1136
  fieldDef.autoAssign = value;
1137
1137
  }
1138
1138
  break;
1139
+ case "autoStamp":
1140
+ if (value === "create" || value === "update" || value === "both") {
1141
+ fieldDef.autoStamp = value;
1142
+ }
1143
+ break;
1139
1144
  case "primaryKey":
1140
1145
  if (typeof value === "boolean") {
1141
1146
  fieldDef.primaryKey = value;
@@ -1181,7 +1186,7 @@ var SchemaExtractor = class {
1181
1186
  // package.json
1182
1187
  var package_default = {
1183
1188
  name: "js-bao",
1184
- version: "0.5.0",
1189
+ version: "0.5.2",
1185
1190
  description: "A library providing data modeling capabilities which support live updates and queries.",
1186
1191
  types: "dist/index.d.ts",
1187
1192
  type: "module",
@@ -1237,6 +1242,7 @@ var package_default = {
1237
1242
  "jsbao-codegen-v2": "./dist/codegen-v2.cjs"
1238
1243
  },
1239
1244
  scripts: {
1245
+ typecheck: "tsc --noEmit -p tsconfig.json",
1240
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",
1241
1247
  "build:main": "pnpm build:browser && pnpm build:node && pnpm build:universal && pnpm build:cloudflare && pnpm build:cloudflare-do && pnpm build:client",
1242
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",