rip-lang 3.14.1 → 3.14.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.
package/src/schema.js CHANGED
@@ -1432,6 +1432,11 @@ function serializeLiteral(v) {
1432
1432
  // its own arg shape — we centralize the parsing here so Layer 2 can rely
1433
1433
  // on normalized structures.
1434
1434
  function compileDirectiveArgsLiteral(name, tokens) {
1435
+ // @idStart requires its arg, so validate before the generic empty-bail.
1436
+ if (name === 'idStart' && !tokens.length) {
1437
+ throw schemaError(null,
1438
+ '@idStart requires an integer literal, e.g. @idStart 10001.');
1439
+ }
1435
1440
  if (!tokens.length) return null;
1436
1441
 
1437
1442
  // Relation directives: `@belongs_to Org`, `@belongs_to Org?`,
@@ -1489,6 +1494,29 @@ function compileDirectiveArgsLiteral(name, tokens) {
1489
1494
  return `[{${parts.join(', ')}}]`;
1490
1495
  }
1491
1496
 
1497
+ // @idStart N sets the seed value for the table's auto-id sequence.
1498
+ // Accepts a single integer literal (optionally negative). Consumed by
1499
+ // .toSQL(); models that never call .toSQL() simply ignore it.
1500
+ if (name === 'idStart') {
1501
+ let tok = tokens[0];
1502
+ let sign = 1;
1503
+ let numTok = tok;
1504
+ if (tok && tok[0] === '-' && tokens[1] && tokens[1][0] === 'NUMBER') {
1505
+ sign = -1;
1506
+ numTok = tokens[1];
1507
+ }
1508
+ if (!numTok || numTok[0] !== 'NUMBER') {
1509
+ throw schemaError(tok || tokens[tokens.length - 1],
1510
+ '@idStart requires an integer literal, e.g. @idStart 10001.');
1511
+ }
1512
+ let n = sign * Number(numTok[1]);
1513
+ if (!Number.isInteger(n)) {
1514
+ throw schemaError(numTok,
1515
+ '@idStart requires an integer literal; got ' + numTok[1] + '.');
1516
+ }
1517
+ return '[{value: ' + n + '}]';
1518
+ }
1519
+
1492
1520
  // Bare flag-like directives (@timestamps, @softDelete) don't take args.
1493
1521
  // Anything else — capture as raw literal tokens conservatively.
1494
1522
  return null;
@@ -2259,11 +2287,20 @@ class __SchemaDef {
2259
2287
  enumerable: false, configurable: true, writable: true,
2260
2288
  value: function() { return def._validateFields(this, true); },
2261
2289
  });
2290
+ // toJSON mirrors the instance's own enumerable properties, which by
2291
+ // construction are: the primary key, declared fields, @timestamps
2292
+ // columns, @softDelete timestamp, @belongs_to FK columns, and any
2293
+ // !> eager-derived fields. Internal state (_dirty, _persisted,
2294
+ // _snapshot) is defined non-enumerable; methods and ~> computed
2295
+ // getters live on the prototype. So iterating own keys picks up
2296
+ // exactly the user-facing wire shape without special-casing each
2297
+ // category — and stays correct when new implicit columns get added
2298
+ // to the runtime.
2262
2299
  Object.defineProperty(klass.prototype, 'toJSON', {
2263
2300
  enumerable: false, configurable: true, writable: true,
2264
2301
  value: function() {
2265
2302
  const out = {};
2266
- for (const k of norm.fields.keys()) out[k] = this[k];
2303
+ for (const k of Object.keys(this)) out[k] = this[k];
2267
2304
  return out;
2268
2305
  },
2269
2306
  });
@@ -2845,6 +2882,13 @@ async function __schemaSave(def, inst) {
2845
2882
  }
2846
2883
  }
2847
2884
  }
2885
+ // Now that the RETURNING columns (id, @timestamps, FKs) are on the
2886
+ // instance, !> eager-derived fields can see them. Mirrors the hydrate
2887
+ // path, which runs _applyEagerDerived once all declared fields are
2888
+ // populated. Per-docs semantics ("materialize once, not reactive")
2889
+ // still hold — we're firing once, at end of construction, not on
2890
+ // subsequent mutations.
2891
+ def._applyEagerDerived(inst);
2848
2892
  inst._persisted = true;
2849
2893
  } else {
2850
2894
  const sets = [], values = [];
@@ -2901,7 +2945,8 @@ const __SCHEMA_SQL_TYPES = {
2901
2945
  };
2902
2946
 
2903
2947
  function __schemaToSQL(def, options) {
2904
- const { dropFirst = false, header } = options || {};
2948
+ const opts = options || {};
2949
+ const { dropFirst = false, header } = opts;
2905
2950
  const norm = def._normalize();
2906
2951
  const blocks = [];
2907
2952
  if (header) blocks.push(header);
@@ -2912,6 +2957,23 @@ function __schemaToSQL(def, options) {
2912
2957
  blocks.push('DROP TABLE IF EXISTS ' + table + ' CASCADE;\\nDROP SEQUENCE IF EXISTS ' + seq + ';');
2913
2958
  }
2914
2959
 
2960
+ // Sequence seed: explicit option wins over @idStart directive wins over 1.
2961
+ // DuckDB 1.5.2 does not implement ALTER SEQUENCE ... RESTART WITH N, so the
2962
+ // baseline has to be set at creation — hence the knob lives here, not in a
2963
+ // post-create migration.
2964
+ let idStart = 1;
2965
+ for (const d of norm.directives) {
2966
+ if (d.name === 'idStart' && d.args?.[0] && Number.isInteger(d.args[0].value)) {
2967
+ idStart = d.args[0].value;
2968
+ }
2969
+ }
2970
+ if (opts.idStart !== undefined) {
2971
+ if (!Number.isInteger(opts.idStart)) {
2972
+ throw new Error('schema.toSQL(): idStart must be an integer; got ' + String(opts.idStart));
2973
+ }
2974
+ idStart = opts.idStart;
2975
+ }
2976
+
2915
2977
  const columns = [];
2916
2978
  const indexes = [];
2917
2979
  columns.push(' ' + norm.primaryKey + " INTEGER PRIMARY KEY DEFAULT nextval('" + seq + "')");
@@ -2948,7 +3010,7 @@ function __schemaToSQL(def, options) {
2948
3010
  indexes.push('CREATE ' + u + 'INDEX idx_' + table + '_' + fields.join('_') + ' ON ' + table + ' (' + fields.map(f => '"' + f + '"').join(', ') + ');');
2949
3011
  }
2950
3012
 
2951
- blocks.push('CREATE SEQUENCE ' + seq + ' START 1;');
3013
+ blocks.push('CREATE SEQUENCE ' + seq + ' START ' + idStart + ';');
2952
3014
  blocks.push('CREATE TABLE ' + table + ' (\\n' + columns.join(',\\n') + '\\n);');
2953
3015
  if (indexes.length) blocks.push(indexes.join('\\n'));
2954
3016
 
@@ -3071,7 +3133,7 @@ export const SCHEMA_INTRINSIC_DECLS = [
3071
3133
  ' first(): Promise<Instance | null>;',
3072
3134
  ' count(cond?: Record<string, unknown>): Promise<number>;',
3073
3135
  ' create(data: Partial<Data>): Promise<Instance>;',
3074
- ' toSQL(options?: { dropFirst?: boolean; header?: string }): string;',
3136
+ ' toSQL(options?: { dropFirst?: boolean; header?: string; idStart?: number }): string;',
3075
3137
  '}',
3076
3138
  ];
3077
3139