@vibeorm/runtime 2.0.0-alpha.1 → 2.0.0-alpha.3

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/index.js CHANGED
@@ -11,7 +11,7 @@ function toSqlExecutor(params) {
11
11
  };
12
12
  }
13
13
  // packages/runtime/src/model-meta.ts
14
- import { VibeError, isEnumType } from "@vibeorm/schema";
14
+ import { VibeError, isEnumType, validateComputedConfig } from "@vibeorm/schema";
15
15
  function toClientName(params) {
16
16
  const { name } = params;
17
17
  if (name.length === 0)
@@ -165,6 +165,11 @@ function buildModelMeta(params) {
165
165
  if (index.kind === "unique")
166
166
  addCompound(index.fields.map((entry) => entry.name));
167
167
  }
168
+ const computedSpecs = params.computed;
169
+ const computed = computedSpecs === undefined || Object.keys(computedSpecs).length === 0 ? null : new Map(Object.entries(computedSpecs).map(([name, spec]) => [
170
+ name,
171
+ { name, type: spec.type, needs: spec.needs, compute: spec.compute }
172
+ ]));
168
173
  return {
169
174
  name: model.name,
170
175
  clientName: toClientName({ name: model.name }),
@@ -177,12 +182,19 @@ function buildModelMeta(params) {
177
182
  compoundUniques,
178
183
  relations: model.relations,
179
184
  relationByName: new Map(model.relations.map((relation) => [relation.name, relation])),
180
- isView: model.isView
185
+ isView: model.isView,
186
+ computed
181
187
  };
182
188
  }
183
189
  function buildRuntimeMeta(params) {
184
- const { schema } = params;
185
- const models = schema.models.map((model) => buildModelMeta({ model }));
190
+ const { schema, computed } = params;
191
+ if (computed !== undefined && Object.keys(computed).length > 0) {
192
+ validateComputedConfig({ schema, computed });
193
+ }
194
+ const models = schema.models.map((model) => {
195
+ const specs = computed?.[model.name];
196
+ return buildModelMeta({ model, ...specs === undefined ? {} : { computed: specs } });
197
+ });
186
198
  const modelByName = new Map;
187
199
  for (const model of models) {
188
200
  modelByName.set(model.name, model);
@@ -210,6 +222,13 @@ function getFieldMeta(params) {
210
222
  const { model, field, context } = params;
211
223
  const found = model.fieldByName.get(field);
212
224
  if (found === undefined) {
225
+ if (model.computed !== null && model.computed.has(field)) {
226
+ throw new VibeError({
227
+ code: "VIBE_VALIDATION",
228
+ message: `"${field}" on model "${model.name}" is a computed field \u2014 computed fields appear in results (select/omit) only, not in ${context}`,
229
+ meta: { model: model.name, field, context, computed: true }
230
+ });
231
+ }
213
232
  if (model.relationByName.has(field)) {
214
233
  throw new VibeError({
215
234
  code: "VIBE_VALIDATION",
@@ -678,6 +697,15 @@ function compileChildSelect(params) {
678
697
  continue;
679
698
  if (name === "_count" || model.relationByName.has(name))
680
699
  continue;
700
+ if (model.computed !== null && model.computed.has(name)) {
701
+ if (value !== true) {
702
+ throw validationError({
703
+ message: `select.${name} must be true or false on model "${model.name}"`,
704
+ meta: { model: model.name, field: name }
705
+ });
706
+ }
707
+ continue;
708
+ }
681
709
  const field = model.fieldByName.get(name);
682
710
  if (field === undefined) {
683
711
  throw validationError({
@@ -719,6 +747,8 @@ function compileChildOmit(params) {
719
747
  meta: { model: model.name, relation, field: name }
720
748
  });
721
749
  }
750
+ if (model.computed !== null && model.computed.has(name))
751
+ continue;
722
752
  if (!model.fieldByName.has(name)) {
723
753
  throw validationError({
724
754
  message: `unknown field "${name}" on model "${model.name}" in omit`,
@@ -827,6 +857,19 @@ function resolveNode(params) {
827
857
  include: raw.include
828
858
  });
829
859
  const select = raw.select !== undefined ? compileChildSelect({ model: link.target, select: raw.select }) : raw.omit !== undefined ? compileChildOmit({ model: link.target, omit: raw.omit, relation: relation.name }) : null;
860
+ const targetComputed = link.target.computed;
861
+ let computed = null;
862
+ if (targetComputed !== null) {
863
+ if (raw.select !== undefined) {
864
+ const names = Object.entries(raw.select).filter(([name, value]) => targetComputed.has(name) && value === true).map(([name]) => name);
865
+ computed = names.length === 0 ? null : names;
866
+ } else if (raw.omit !== undefined) {
867
+ const names = [...targetComputed.keys()].filter((name) => raw.omit[name] !== true);
868
+ computed = names.length === 0 ? null : names;
869
+ } else {
870
+ computed = [...targetComputed.keys()];
871
+ }
872
+ }
830
873
  let projected = null;
831
874
  const strip = [];
832
875
  if (select !== null) {
@@ -837,6 +880,13 @@ function resolveNode(params) {
837
880
  additions.add(name);
838
881
  }
839
882
  }
883
+ if (computed !== null && targetComputed !== null) {
884
+ for (const name of computed) {
885
+ for (const need of targetComputed.get(name)?.needs ?? []) {
886
+ additions.add(need);
887
+ }
888
+ }
889
+ }
840
890
  for (const name of additions) {
841
891
  if (!fetch.includes(name)) {
842
892
  fetch.push(name);
@@ -848,7 +898,7 @@ function resolveNode(params) {
848
898
  return {
849
899
  name: relation.name,
850
900
  link,
851
- args: { where, orderBy: raw.orderBy, take, skip, select, projected, strip, nested }
901
+ args: { where, orderBy: raw.orderBy, take, skip, select, projected, strip, computed, nested }
852
902
  };
853
903
  }
854
904
  function resolveRelationSelection(params) {
@@ -1807,6 +1857,8 @@ function compileSelect(params) {
1807
1857
  continue;
1808
1858
  if (name === "_count" || model.relationByName.has(name))
1809
1859
  continue;
1860
+ if (model.computed !== null && model.computed.has(name))
1861
+ continue;
1810
1862
  if (value !== true) {
1811
1863
  throw validationError2({
1812
1864
  message: `select.${name} must be true or false on model "${model.name}"`,
@@ -1823,6 +1875,38 @@ function compileSelect(params) {
1823
1875
  }
1824
1876
  return names;
1825
1877
  }
1878
+ function resolveComputedSelection(params) {
1879
+ const { model, args, aggregating } = params;
1880
+ const specs = model.computed;
1881
+ if (specs === null || aggregating)
1882
+ return null;
1883
+ if (isPlainObject2(args.select)) {
1884
+ const names = [];
1885
+ for (const [name, value] of Object.entries(args.select)) {
1886
+ if (!specs.has(name) || value === undefined || value === false)
1887
+ continue;
1888
+ if (value !== true) {
1889
+ throw validationError2({
1890
+ message: `select.${name} must be true or false on model "${model.name}"`,
1891
+ meta: { model: model.name, field: name }
1892
+ });
1893
+ }
1894
+ names.push(name);
1895
+ }
1896
+ return names.length === 0 ? null : names;
1897
+ }
1898
+ if (isPlainObject2(args.omit)) {
1899
+ const names = [];
1900
+ for (const name of specs.keys()) {
1901
+ const value = args.omit[name];
1902
+ if (value === true)
1903
+ continue;
1904
+ names.push(name);
1905
+ }
1906
+ return names.length === 0 ? null : names;
1907
+ }
1908
+ return [...specs.keys()];
1909
+ }
1826
1910
  var fullProjectionCache = new WeakMap;
1827
1911
  function projectionColumns(params) {
1828
1912
  const { model, selectedFields, table } = params;
@@ -2567,6 +2651,8 @@ function compileOmit(params) {
2567
2651
  meta: { model: model.name, field: name }
2568
2652
  });
2569
2653
  }
2654
+ if (model.computed !== null && model.computed.has(name))
2655
+ continue;
2570
2656
  omitted.add(getFieldMeta({ model, field: name, context: "omit" }).name);
2571
2657
  }
2572
2658
  const kept = model.fields.filter((field) => !omitted.has(field.name)).map((field) => field.name);
@@ -2907,7 +2993,8 @@ function resolveResultShape(params) {
2907
2993
  const { meta, model, method, args } = params;
2908
2994
  assertArgs({ method, args, model });
2909
2995
  const relations = resolveRelationSelection({ meta, model, select: args.select, include: args.include });
2910
- const scalarSelect = args.select === undefined ? null : compileSelect({ model, select: args.select, allowEmpty: relations !== null });
2996
+ const computed = resolveComputedSelection({ model, args, aggregating: false });
2997
+ const scalarSelect = args.select === undefined ? null : compileSelect({ model, select: args.select, allowEmpty: relations !== null || computed !== null });
2911
2998
  const resultFields = scalarSelect === null ? null : [...scalarSelect];
2912
2999
  if (relations !== null && resultFields !== null) {
2913
3000
  for (const node of relations.relations)
@@ -2915,7 +3002,11 @@ function resolveResultShape(params) {
2915
3002
  if (relations.counts.length > 0)
2916
3003
  resultFields.push("_count");
2917
3004
  }
2918
- return { relations, resultFields };
3005
+ if (computed !== null && resultFields !== null) {
3006
+ for (const name of computed)
3007
+ resultFields.push(name);
3008
+ }
3009
+ return { relations, resultFields, computed };
2919
3010
  }
2920
3011
  function buildQuery(params) {
2921
3012
  const { meta, dialect, method } = params;
@@ -2951,7 +3042,8 @@ function buildQuery(params) {
2951
3042
  meta: { model: model.name, method }
2952
3043
  });
2953
3044
  }
2954
- const scalarSelect = aggregating ? null : args.select !== undefined ? compileSelect({ model, select: args.select, allowEmpty: relations !== null }) : args.omit !== undefined ? compileOmit({ model, omit: args.omit }) : null;
3045
+ const computedSelection = resolveComputedSelection({ model, args, aggregating });
3046
+ const scalarSelect = aggregating ? null : args.select !== undefined ? compileSelect({ model, select: args.select, allowEmpty: relations !== null || computedSelection !== null }) : args.omit !== undefined ? compileOmit({ model, omit: args.omit }) : null;
2955
3047
  let selectedFields = scalarSelect === null ? null : [...scalarSelect];
2956
3048
  let resultFields = scalarSelect === null ? null : [...scalarSelect];
2957
3049
  if (relations !== null && resultFields !== null) {
@@ -2960,6 +3052,10 @@ function buildQuery(params) {
2960
3052
  if (relations.counts.length > 0)
2961
3053
  resultFields.push("_count");
2962
3054
  }
3055
+ if (computedSelection !== null && resultFields !== null) {
3056
+ for (const name of computedSelection)
3057
+ resultFields.push(name);
3058
+ }
2963
3059
  if (relations !== null && selectedFields !== null) {
2964
3060
  for (const name of requiredParentFields({ meta, model, selection: relations })) {
2965
3061
  if (!selectedFields.includes(name))
@@ -2972,12 +3068,21 @@ function buildQuery(params) {
2972
3068
  selectedFields.push(field.name);
2973
3069
  }
2974
3070
  }
3071
+ if (computedSelection !== null && selectedFields !== null && model.computed !== null) {
3072
+ for (const name of computedSelection) {
3073
+ for (const need of model.computed.get(name)?.needs ?? []) {
3074
+ if (!selectedFields.includes(need))
3075
+ selectedFields.push(need);
3076
+ }
3077
+ }
3078
+ }
2975
3079
  const selectArg = args.select === undefined || aggregating ? null : args.select;
2976
3080
  const base = {
2977
3081
  method,
2978
3082
  model,
2979
3083
  selectedFields,
2980
3084
  resultFields,
3085
+ computed: computedSelection,
2981
3086
  relations,
2982
3087
  relationStrategy,
2983
3088
  takeApplied: args.take !== undefined,
@@ -3352,7 +3457,68 @@ function buildQuery(params) {
3352
3457
  }
3353
3458
  }
3354
3459
  // packages/runtime/src/relation-loader.ts
3460
+ import { VibeError as VibeError6 } from "@vibeorm/schema";
3461
+
3462
+ // packages/runtime/src/computed.ts
3355
3463
  import { VibeError as VibeError5 } from "@vibeorm/schema";
3464
+ function sameNeeds(a, b) {
3465
+ if (a.length !== b.length)
3466
+ return false;
3467
+ const left = [...a].sort();
3468
+ const right = [...b].sort();
3469
+ return left.every((value, index) => value === right[index]);
3470
+ }
3471
+ function assertComputedManifest(params) {
3472
+ const { manifest } = params;
3473
+ const specs = params.specs ?? {};
3474
+ const live = new Map;
3475
+ for (const [model, fields] of Object.entries(specs)) {
3476
+ for (const [field, spec] of Object.entries(fields)) {
3477
+ live.set(`${model}.${field}`, { type: spec.type, needs: spec.needs });
3478
+ }
3479
+ }
3480
+ const problems = [];
3481
+ for (const baked of manifest) {
3482
+ const key = `${baked.model}.${baked.field}`;
3483
+ const current = live.get(key);
3484
+ if (current === undefined) {
3485
+ problems.push(`"${key}" was baked into the client but is missing from the computed config`);
3486
+ continue;
3487
+ }
3488
+ if (current.type !== baked.type) {
3489
+ problems.push(`"${key}" type changed (generated ${JSON.stringify(baked.type)}, live ${JSON.stringify(current.type)})`);
3490
+ } else if (!sameNeeds(current.needs, baked.needs)) {
3491
+ problems.push(`"${key}" needs changed since generate`);
3492
+ }
3493
+ live.delete(key);
3494
+ }
3495
+ for (const key of live.keys()) {
3496
+ problems.push(`"${key}" is configured but missing from the generated client`);
3497
+ }
3498
+ if (problems.length > 0) {
3499
+ throw new VibeError5({
3500
+ code: "VIBE_STALE_CLIENT",
3501
+ message: `computed-field config changed since generate; run vibeorm generate \u2014 ${problems.join("; ")}`,
3502
+ meta: { problems }
3503
+ });
3504
+ }
3505
+ }
3506
+ function applyComputedFields(params) {
3507
+ const { model, rows, names } = params;
3508
+ const specs = model.computed;
3509
+ if (names === null || names.length === 0 || specs === null || rows.length === 0)
3510
+ return;
3511
+ for (const row of rows) {
3512
+ for (const name of names) {
3513
+ const spec = specs.get(name);
3514
+ if (spec === undefined)
3515
+ continue;
3516
+ row[name] = spec.compute(row);
3517
+ }
3518
+ }
3519
+ }
3520
+
3521
+ // packages/runtime/src/relation-loader.ts
3356
3522
  var COUNT_VALUE_ALIAS = "__vibe_count";
3357
3523
  function distinctKeys(params) {
3358
3524
  const { rows, field } = params;
@@ -3494,7 +3660,7 @@ async function loadManyToMany(params) {
3494
3660
  const link = node.link;
3495
3661
  const join = link.join;
3496
3662
  if (join === null) {
3497
- throw new VibeError5({
3663
+ throw new VibeError6({
3498
3664
  code: "VIBE_INTERNAL",
3499
3665
  message: `manyToMany relation "${node.name}" resolved without join-table wiring`
3500
3666
  });
@@ -3643,6 +3809,9 @@ async function loadNode(params) {
3643
3809
  }
3644
3810
  async function finishNode(params) {
3645
3811
  const { ctx, node, kept } = params;
3812
+ if (node.args.computed !== null && kept.length > 0) {
3813
+ applyComputedFields({ model: node.link.target, rows: kept, names: node.args.computed });
3814
+ }
3646
3815
  if (node.args.nested !== null && kept.length > 0) {
3647
3816
  await attachRelations({
3648
3817
  dialect: ctx.dialect,
@@ -3689,7 +3858,7 @@ async function attachLateralRelations(params) {
3689
3858
  try {
3690
3859
  parsed = JSON.parse(raw);
3691
3860
  } catch (cause) {
3692
- throw new VibeError5({
3861
+ throw new VibeError6({
3693
3862
  code: "VIBE_INTERNAL",
3694
3863
  message: `relation column "${column}" returned text that is not valid JSON`,
3695
3864
  meta: { relation: node.name },
@@ -3726,12 +3895,12 @@ async function attachLateralRelations(params) {
3726
3895
  await loadCounts({ ctx, model, rows, counts: selection.counts });
3727
3896
  }
3728
3897
  // packages/runtime/src/nested-writes.ts
3729
- import { VibeError as VibeError6 } from "@vibeorm/schema";
3898
+ import { VibeError as VibeError7 } from "@vibeorm/schema";
3730
3899
  function validationError3(params) {
3731
- return new VibeError6({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
3900
+ return new VibeError7({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
3732
3901
  }
3733
3902
  function notFound(params) {
3734
- return new VibeError6({ code: "VIBE_NOT_FOUND", message: params.message, meta: params.meta });
3903
+ return new VibeError7({ code: "VIBE_NOT_FOUND", message: params.message, meta: params.meta });
3735
3904
  }
3736
3905
  function isPlainObject3(value) {
3737
3906
  return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
@@ -4561,7 +4730,7 @@ async function updateWithNested(params) {
4561
4730
  });
4562
4731
  }
4563
4732
  // packages/runtime/src/extensions.ts
4564
- import { VibeError as VibeError7, extensionManifestEntry } from "@vibeorm/schema";
4733
+ import { VibeError as VibeError8, extensionManifestEntry } from "@vibeorm/schema";
4565
4734
  function assertExtensionManifest(params) {
4566
4735
  const { manifest } = params;
4567
4736
  const instances = params.instances ?? [];
@@ -4584,7 +4753,7 @@ function assertExtensionManifest(params) {
4584
4753
  problems.push(`"${name}" is configured but missing from the generated client`);
4585
4754
  }
4586
4755
  if (problems.length > 0) {
4587
- throw new VibeError7({
4756
+ throw new VibeError8({
4588
4757
  code: "VIBE_STALE_CLIENT",
4589
4758
  message: `extension list changed since generate; run vibeorm generate \u2014 ${problems.join("; ")}`,
4590
4759
  meta: { manifest, problems }
@@ -4599,7 +4768,7 @@ function buildExtensionOperatorMap(params) {
4599
4768
  for (const operator of extension.sql?.whereOperators ?? []) {
4600
4769
  const existing = owner.get(operator.name);
4601
4770
  if (existing !== undefined) {
4602
- throw new VibeError7({
4771
+ throw new VibeError8({
4603
4772
  code: "VIBE_CONFIG",
4604
4773
  message: `where operator "${operator.name}" is claimed by both "${existing}" and "${extension.name}"`,
4605
4774
  meta: { operator: operator.name }
@@ -4615,7 +4784,7 @@ function buildExtensionOperatorMap(params) {
4615
4784
  options: extension.options
4616
4785
  });
4617
4786
  if ("error" in compiled) {
4618
- throw new VibeError7({
4787
+ throw new VibeError8({
4619
4788
  code: "VIBE_VALIDATION",
4620
4789
  message: `"${operator.name}" on ${model}.${field}: ${compiled.error}`,
4621
4790
  meta: { model, field, operator: operator.name, extension: extension.name }
@@ -4647,9 +4816,9 @@ function buildConnectHooks(params) {
4647
4816
  return hooks;
4648
4817
  }
4649
4818
  // packages/runtime/src/views.ts
4650
- import { VibeError as VibeError8 } from "@vibeorm/schema";
4819
+ import { VibeError as VibeError9 } from "@vibeorm/schema";
4651
4820
  function validationError4(params) {
4652
- return new VibeError8({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
4821
+ return new VibeError9({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
4653
4822
  }
4654
4823
 
4655
4824
  class ViewResult {
@@ -5001,7 +5170,7 @@ function createView(params) {
5001
5170
  for (const [clientName, mask] of resolved) {
5002
5171
  const delegate = models[clientName];
5003
5172
  if (delegate === undefined) {
5004
- throw new VibeError8({
5173
+ throw new VibeError9({
5005
5174
  code: "VIBE_INTERNAL",
5006
5175
  message: `view "${name}": no delegate for model "${mask.model.name}"`,
5007
5176
  meta: { view: name, model: mask.model.name }
@@ -5017,7 +5186,7 @@ function attachDefineView(params) {
5017
5186
  Object.defineProperty(client, "$defineView", { value, enumerable: true });
5018
5187
  }
5019
5188
  // packages/runtime/src/client.ts
5020
- import { VibeError as VibeError9, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
5189
+ import { VibeError as VibeError10, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
5021
5190
  import { ParamCollector, getSqlDialect } from "@vibeorm/sql";
5022
5191
 
5023
5192
  // packages/runtime/src/render-cache.ts
@@ -5437,7 +5606,7 @@ function renderStatementCached(params) {
5437
5606
  // packages/runtime/src/client.ts
5438
5607
  function notFound2(params) {
5439
5608
  const { model, method } = params;
5440
- return new VibeError9({
5609
+ return new VibeError10({
5441
5610
  code: "VIBE_NOT_FOUND",
5442
5611
  message: `no ${model} record matched the where clause of ${method}()`,
5443
5612
  meta: { model, method }
@@ -5445,7 +5614,7 @@ function notFound2(params) {
5445
5614
  }
5446
5615
  function viewIsReadOnly(params) {
5447
5616
  const { model, method } = params;
5448
- return new VibeError9({
5617
+ return new VibeError10({
5449
5618
  code: "VIBE_VALIDATION",
5450
5619
  message: `${model} is a view \u2014 ${method}() is not available on views (they are read-only); write to the underlying table instead`,
5451
5620
  meta: { model, method }
@@ -5574,7 +5743,7 @@ class QueryPromise extends Promise {
5574
5743
  }
5575
5744
  claim() {
5576
5745
  if (this.started) {
5577
- throw new VibeError9({
5746
+ throw new VibeError10({
5578
5747
  code: "VIBE_VALIDATION",
5579
5748
  message: "$transaction([...]) needs operations that have not dispatched \u2014 pass delegate calls in the same tick they are created (this one was awaited or created earlier and already executed outside the transaction)"
5580
5749
  });
@@ -5718,7 +5887,7 @@ async function insertedKeyWhere(params) {
5718
5887
  });
5719
5888
  const id = result.rows[0]?.id;
5720
5889
  if (id === undefined || id === null) {
5721
- throw new VibeError9({
5890
+ throw new VibeError10({
5722
5891
  code: "VIBE_INTERNAL",
5723
5892
  message: `could not read back the generated key for ${source.model.name}`,
5724
5893
  meta: { model: source.model.name }
@@ -5895,6 +6064,9 @@ async function finalizeRows(params) {
5895
6064
  });
5896
6065
  }
5897
6066
  }
6067
+ if (shape.computed !== null && rows.length > 0) {
6068
+ applyComputedFields({ model, rows, names: shape.computed });
6069
+ }
5898
6070
  if (shape.resultFields === null)
5899
6071
  return rows;
5900
6072
  return rows.map((row) => project({ row, fields: shape.resultFields }));
@@ -5968,7 +6140,8 @@ function createDelegate(params) {
5968
6140
  const shapeOf = (built) => ({
5969
6141
  relations: built.relations,
5970
6142
  resultFields: built.resultFields,
5971
- relationStrategy: built.relationStrategy
6143
+ relationStrategy: built.relationStrategy,
6144
+ computed: built.computed
5972
6145
  });
5973
6146
  const findMany = async (target, args) => {
5974
6147
  const built = plan({ engine: target, model, method: "findMany", args });
@@ -6061,7 +6234,7 @@ function createDelegate(params) {
6061
6234
  const upsert = async (target, args) => {
6062
6235
  const modelMeta = getModelMeta({ meta: target.meta, model });
6063
6236
  if (hasNestedWrites({ model: modelMeta, data: args.create }) || hasNestedWrites({ model: modelMeta, data: args.update })) {
6064
- throw new VibeError9({
6237
+ throw new VibeError10({
6065
6238
  code: "VIBE_VALIDATION",
6066
6239
  message: `nested writes are not supported inside upsert arms on ${modelMeta.name} \u2014 use create()/update() with nested data`,
6067
6240
  meta: { model: modelMeta.name }
@@ -6216,7 +6389,7 @@ function buildClient(params) {
6216
6389
  let claimError = null;
6217
6390
  for (const operation of operations) {
6218
6391
  if (!(operation instanceof QueryPromise)) {
6219
- claimError = new VibeError9({
6392
+ claimError = new VibeError10({
6220
6393
  code: "VIBE_VALIDATION",
6221
6394
  message: "$transaction([...]) takes query promises from model delegate calls \u2014 a foreign promise is already running and cannot join the transaction"
6222
6395
  });
@@ -6255,7 +6428,7 @@ function buildClient(params) {
6255
6428
  $models: models,
6256
6429
  $on: (event, listener) => {
6257
6430
  if (event !== "query") {
6258
- throw new VibeError9({
6431
+ throw new VibeError10({
6259
6432
  code: "VIBE_VALIDATION",
6260
6433
  message: `unknown client event "${String(event)}" \u2014 supported: "query"`,
6261
6434
  meta: { event }
@@ -6318,7 +6491,7 @@ function makeMountContext(params) {
6318
6491
  compileWhere: ({ where, model: modelName, qualify }) => {
6319
6492
  const target = modelName === undefined ? model : getModelMeta({ meta: engine.meta, model: modelName });
6320
6493
  if (target === null) {
6321
- throw new VibeError9({
6494
+ throw new VibeError10({
6322
6495
  code: "VIBE_INTERNAL",
6323
6496
  message: `extension "${extension}" compiled a where with no model in scope`,
6324
6497
  meta: { extension }
@@ -6344,7 +6517,7 @@ function attachExtensionMounts(params) {
6344
6517
  const model = getModelMeta({ meta: engine.meta, model: mount.model ?? "" });
6345
6518
  const delegate = models[model.name];
6346
6519
  if (delegate === undefined || mount.property in delegate) {
6347
- throw new VibeError9({
6520
+ throw new VibeError10({
6348
6521
  code: "VIBE_CONFIG",
6349
6522
  message: `extension "${mount.extension}" cannot mount "${mount.property}" on ${model.name} \u2014 the name is taken`,
6350
6523
  meta: { extension: mount.extension, model: model.name, property: mount.property }
@@ -6355,7 +6528,7 @@ function attachExtensionMounts(params) {
6355
6528
  continue;
6356
6529
  }
6357
6530
  if (!mount.property.startsWith("$") || mount.property in client) {
6358
- throw new VibeError9({
6531
+ throw new VibeError10({
6359
6532
  code: "VIBE_CONFIG",
6360
6533
  message: `extension "${mount.extension}" cannot mount client property "${mount.property}" \u2014 root mounts are $-prefixed and must not collide`,
6361
6534
  meta: { extension: mount.extension, property: mount.property }
@@ -6366,15 +6539,28 @@ function attachExtensionMounts(params) {
6366
6539
  }
6367
6540
  }
6368
6541
  function createClient(params) {
6369
- const { schema, adapter, options, extensions } = params;
6542
+ const { schema, adapter, options, extensions, computed } = params;
6370
6543
  const dialectName = adapter.dialect;
6371
6544
  if (extensions?.manifest !== undefined) {
6372
6545
  assertExtensionManifest({ manifest: extensions.manifest, instances: extensions.instances });
6373
6546
  }
6547
+ if (computed?.manifest !== undefined) {
6548
+ assertComputedManifest({ manifest: computed.manifest, specs: computed.specs });
6549
+ }
6550
+ if (computed === undefined && options !== undefined && options["computed"] !== undefined) {
6551
+ throw new VibeError10({
6552
+ code: "VIBE_CONFIG",
6553
+ message: "this client was generated without computed fields \u2014 declare them in vibeorm.config.ts and re-run vibeorm generate",
6554
+ meta: {}
6555
+ });
6556
+ }
6374
6557
  const instances = extensions?.instances ?? [];
6375
6558
  const operatorMap = instances.length === 0 ? null : buildExtensionOperatorMap({ instances, dialect: dialectName, provider: adapter.provider });
6376
6559
  const connectHooks = instances.length === 0 ? [] : buildConnectHooks({ instances, dialect: dialectName, provider: adapter.provider });
6377
- const baseMeta = buildRuntimeMeta({ schema });
6560
+ const baseMeta = buildRuntimeMeta({
6561
+ schema,
6562
+ ...computed?.specs === undefined ? {} : { computed: computed.specs }
6563
+ });
6378
6564
  const meta = operatorMap === null ? baseMeta : { ...baseMeta, extensionOperators: operatorMap };
6379
6565
  return buildClient({
6380
6566
  engine: {
@@ -6426,6 +6612,8 @@ export {
6426
6612
  attachLateralRelations,
6427
6613
  attachDefineView,
6428
6614
  assertExtensionManifest,
6615
+ assertComputedManifest,
6616
+ applyComputedFields,
6429
6617
  ViewResult,
6430
6618
  SQLITE_CODECS,
6431
6619
  POSTGRES_CODECS,
@@ -6434,4 +6622,4 @@ export {
6434
6622
  COUNT_ALIAS
6435
6623
  };
6436
6624
 
6437
- //# debugId=E85807B9EEEC573F64756E2164756E21
6625
+ //# debugId=D09B25CE72AA9DC064756E2164756E21