@vibeorm/runtime 2.0.0-alpha.7 → 2.0.0-alpha.9

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
@@ -133,6 +133,7 @@ function buildFieldMeta(params) {
133
133
  return {
134
134
  name: field.name,
135
135
  columnName: field.dbName ?? field.name,
136
+ ...field.collation === undefined ? {} : { collation: field.collation },
136
137
  type: field.type,
137
138
  scalarType: enumRef ? null : field.type,
138
139
  enumName: enumRef ? field.type.enum : null,
@@ -231,6 +232,7 @@ function buildModelMeta(params) {
231
232
  singleIndexUniques,
232
233
  relations: model.relations,
233
234
  relationByName: new Map(model.relations.map((relation) => [relation.name, relation])),
235
+ ...model.policy === undefined ? {} : { policy: model.policy },
234
236
  isView: model.isView,
235
237
  computed
236
238
  };
@@ -1440,6 +1442,7 @@ function compileOperatorFilter(params) {
1440
1442
  }
1441
1443
  const insensitive = mode === "insensitive";
1442
1444
  const strict = mode === "strict";
1445
+ const ciCollated = (params2) => params2.dialect.name === "mysql" && params2.field.collation?.toLowerCase().endsWith("_ci") === true;
1443
1446
  const requireStringOperand = (operator, operand) => {
1444
1447
  if (typeof operand !== "string") {
1445
1448
  throw validationError2({
@@ -1467,7 +1470,8 @@ function compileOperatorFilter(params) {
1467
1470
  kind: "like",
1468
1471
  column,
1469
1472
  pattern: escapeLikePattern({ raw: requireStringOperand("equals", operand) }),
1470
- insensitive: true
1473
+ insensitive: true,
1474
+ ...ciCollated({ dialect, field }) ? { ciCollation: true } : {}
1471
1475
  });
1472
1476
  break;
1473
1477
  }
@@ -1564,7 +1568,8 @@ function compileOperatorFilter(params) {
1564
1568
  kind: "like",
1565
1569
  column,
1566
1570
  pattern: compileStringPattern({ operator, operand, field, model }),
1567
- insensitive
1571
+ insensitive,
1572
+ ...insensitive && ciCollated({ dialect, field }) ? { ciCollation: true } : {}
1568
1573
  });
1569
1574
  break;
1570
1575
  }
@@ -5505,8 +5510,1272 @@ function attachDefineView(params) {
5505
5510
  const value = (options) => createView({ meta, models, name: options.name, definition: options.definition });
5506
5511
  Object.defineProperty(client, "$defineView", { value, enumerable: true });
5507
5512
  }
5513
+ // packages/runtime/src/validators.ts
5514
+ import { VibeError as VibeError11 } from "@vibeorm/schema";
5515
+ function validationFailed(params) {
5516
+ return new VibeError11({
5517
+ code: "VIBE_VALIDATION",
5518
+ message: `validation failed for ${params.model}.${params.field}: ${params.issues[0]?.message ?? "invalid value"}`,
5519
+ meta: { model: params.model, field: params.field, issues: params.issues }
5520
+ });
5521
+ }
5522
+ function validateValue(params) {
5523
+ const result = params.schema["~standard"].validate(params.value);
5524
+ if (result instanceof Promise) {
5525
+ throw new VibeError11({
5526
+ code: "VIBE_VALIDATION",
5527
+ message: `async validator on ${params.model}.${params.field} \u2014 write validators must be synchronous (validate before calling the client for async rules)`,
5528
+ meta: { model: params.model, field: params.field }
5529
+ });
5530
+ }
5531
+ if (result.issues !== undefined) {
5532
+ throw validationFailed({ model: params.model, field: params.field, issues: result.issues });
5533
+ }
5534
+ return result.value;
5535
+ }
5536
+ function validateWriteData(params) {
5537
+ const { meta, model, data, validators } = params;
5538
+ const fieldSchemas = validators[model.name];
5539
+ const out = { ...data };
5540
+ for (const [key, value] of Object.entries(data)) {
5541
+ if (value === undefined)
5542
+ continue;
5543
+ const schema = fieldSchemas?.[key];
5544
+ if (schema !== undefined) {
5545
+ const isOperator = typeof value === "object" && value !== null && !Array.isArray(value) && "set" in value;
5546
+ if (isOperator) {
5547
+ const inner = value.set;
5548
+ out[key] = { set: validateValue({ model: model.name, field: key, schema, value: inner }) };
5549
+ } else {
5550
+ out[key] = validateValue({ model: model.name, field: key, schema, value });
5551
+ }
5552
+ continue;
5553
+ }
5554
+ const relation = model.relationByName.get(key);
5555
+ if (relation === undefined || typeof value !== "object" || value === null)
5556
+ continue;
5557
+ const target = meta.modelByName.get(relation.targetModel);
5558
+ if (target === undefined || validators[target.name] === undefined)
5559
+ continue;
5560
+ const verbs = value;
5561
+ const recurse = (row) => typeof row === "object" && row !== null ? validateWriteData({ meta, model: target, data: row, validators }) : row;
5562
+ const next = { ...verbs };
5563
+ if (verbs.create !== undefined) {
5564
+ next.create = Array.isArray(verbs.create) ? verbs.create.map(recurse) : recurse(verbs.create);
5565
+ }
5566
+ if (verbs.connectOrCreate !== undefined) {
5567
+ const entries = Array.isArray(verbs.connectOrCreate) ? verbs.connectOrCreate : [verbs.connectOrCreate];
5568
+ const mapped = entries.map((entry) => {
5569
+ const pair = entry;
5570
+ return { ...pair, create: recurse(pair.create) };
5571
+ });
5572
+ next.connectOrCreate = Array.isArray(verbs.connectOrCreate) ? mapped : mapped[0];
5573
+ }
5574
+ if (verbs.update !== undefined && !Array.isArray(verbs.update)) {
5575
+ const body = verbs.update;
5576
+ next.update = body.data !== undefined ? { ...body, data: recurse(body.data) } : recurse(body);
5577
+ }
5578
+ if (verbs.upsert !== undefined && !Array.isArray(verbs.upsert)) {
5579
+ const body = verbs.upsert;
5580
+ next.upsert = { ...body, create: recurse(body.create), update: recurse(body.update) };
5581
+ }
5582
+ out[key] = next;
5583
+ }
5584
+ return out;
5585
+ }
5586
+ function applyWriteValidators(params) {
5587
+ const { meta, model, method, args, validators } = params;
5588
+ const validate = (row) => typeof row === "object" && row !== null ? validateWriteData({ meta, model, data: row, validators }) : row;
5589
+ if (method === "create" || method === "update" || method === "updateMany") {
5590
+ return args.data === undefined ? args : { ...args, data: validate(args.data) };
5591
+ }
5592
+ if (method === "createMany" || method === "createManyAndReturn") {
5593
+ return Array.isArray(args.data) ? { ...args, data: args.data.map(validate) } : args;
5594
+ }
5595
+ if (method === "upsert") {
5596
+ return { ...args, create: validate(args.create), update: validate(args.update) };
5597
+ }
5598
+ return args;
5599
+ }
5600
+ // packages/runtime/src/policy.ts
5601
+ import { VibeError as VibeError12 } from "@vibeorm/schema";
5602
+ function policyError(message, meta) {
5603
+ return new VibeError12({ code: "VIBE_VALIDATION", message, meta: meta ?? {} });
5604
+ }
5605
+ function policiedModels(meta) {
5606
+ return meta.models.filter((model) => model.policy !== undefined);
5607
+ }
5608
+ function mergeWhere(params) {
5609
+ const { where, field, value } = params;
5610
+ const predicate = { [field]: value };
5611
+ return where === undefined ? predicate : { AND: [where, predicate] };
5612
+ }
5613
+ function uniqueToFilter(params) {
5614
+ const out = {};
5615
+ for (const [key, value] of Object.entries(params.where)) {
5616
+ const isCompound = typeof value === "object" && value !== null && !Array.isArray(value) && params.model.compoundUniques.has(key);
5617
+ if (isCompound)
5618
+ Object.assign(out, value);
5619
+ else
5620
+ out[key] = value;
5621
+ }
5622
+ return out;
5623
+ }
5624
+ function scopeSelection(params) {
5625
+ const { meta, model, args, context } = params;
5626
+ const out = { ...args };
5627
+ for (const key of ["include", "select"]) {
5628
+ const block = args[key];
5629
+ if (typeof block !== "object" || block === null)
5630
+ continue;
5631
+ const next = { ...block };
5632
+ for (const [name, spec] of Object.entries(block)) {
5633
+ const relation = model.relationByName.get(name);
5634
+ if (relation === undefined)
5635
+ continue;
5636
+ const target = meta.modelByName.get(relation.targetModel);
5637
+ if (target?.policy === undefined)
5638
+ continue;
5639
+ const value = context[target.policy.context];
5640
+ const spec2 = spec === true ? {} : { ...spec };
5641
+ spec2.where = mergeWhere({ where: spec2.where, field: target.policy.field, value });
5642
+ next[name] = scopeSelection({ meta, model: target, args: spec2, context });
5643
+ }
5644
+ out[key] = next;
5645
+ }
5646
+ return out;
5647
+ }
5648
+ var REFERENCE_VERBS = ["connect", "connectOrCreate", "set", "disconnect", "update", "updateMany", "delete", "deleteMany", "upsert"];
5649
+ function forceCreateData(params) {
5650
+ const { meta, model, data, context } = params;
5651
+ const out = { ...data };
5652
+ if (model.policy !== undefined) {
5653
+ const value = context[model.policy.context];
5654
+ const given = data[model.policy.field];
5655
+ if (given !== undefined && given !== value) {
5656
+ throw policyError(`create on ${model.name}: data.${model.policy.field} conflicts with the bound policy context`, { model: model.name, field: model.policy.field });
5657
+ }
5658
+ out[model.policy.field] = value;
5659
+ }
5660
+ for (const [name, spec] of Object.entries(data)) {
5661
+ const relation = model.relationByName.get(name);
5662
+ if (relation === undefined || typeof spec !== "object" || spec === null)
5663
+ continue;
5664
+ const target = meta.modelByName.get(relation.targetModel);
5665
+ if (target?.policy === undefined)
5666
+ continue;
5667
+ const verbs = spec;
5668
+ for (const verb of REFERENCE_VERBS) {
5669
+ if (verbs[verb] !== undefined) {
5670
+ throw policyError(`nested ${verb} on policied model ${target.name} is not policy-safe from a scoped client \u2014 perform it in an explicit $transaction on the scoped delegate`, { model: model.name, relation: name, verb });
5671
+ }
5672
+ }
5673
+ if (verbs.create !== undefined) {
5674
+ const recurse = (row) => forceCreateData({ meta, model: target, data: row, context });
5675
+ out[name] = { ...verbs, create: Array.isArray(verbs.create) ? verbs.create.map(recurse) : recurse(verbs.create) };
5676
+ }
5677
+ }
5678
+ return out;
5679
+ }
5680
+ function scopedDelegate(params) {
5681
+ const { meta, model, base, context, transaction } = params;
5682
+ const policy = model.policy;
5683
+ const value = context[policy.context];
5684
+ const pk = model.primaryKey.map((field) => field.name);
5685
+ const scopeFind = (args) => {
5686
+ const scoped = scopeSelection({ meta, model, args: args ?? {}, context });
5687
+ return { ...scoped, where: mergeWhere({ where: scoped.where, field: policy.field, value }) };
5688
+ };
5689
+ const scopeUnique = (args) => {
5690
+ const scoped = scopeSelection({ meta, model, args, context });
5691
+ const flat = uniqueToFilter({ model, where: args.where ?? {} });
5692
+ return { ...scoped, where: mergeWhere({ where: flat, field: policy.field, value }) };
5693
+ };
5694
+ const pkClientKey = () => {
5695
+ for (const [key, group] of model.compoundUniques) {
5696
+ const names = group.fields.map((field) => field.name);
5697
+ if (names.length === pk.length && names.every((name) => pk.includes(name)))
5698
+ return key;
5699
+ }
5700
+ return pk.join("_");
5701
+ };
5702
+ const lockThen = (args, write) => transaction(async (tx) => {
5703
+ const raw = tx.$bypassPolicy === undefined ? tx : tx.$bypassPolicy();
5704
+ const target = await raw.$models[model.name].findFirst({
5705
+ where: scopeUnique(args).where,
5706
+ select: Object.fromEntries(pk.map((name) => [name, true])),
5707
+ lock: "update"
5708
+ });
5709
+ if (target === null)
5710
+ return null;
5711
+ const pkWhere = pk.length === 1 ? { [pk[0]]: target[pk[0]] } : { [pkClientKey()]: Object.fromEntries(pk.map((name) => [name, target[name]])) };
5712
+ return write(raw, pkWhere);
5713
+ });
5714
+ const notFound2 = (method) => new VibeError12({ code: "VIBE_NOT_FOUND", message: `${method} on ${model.name}: no row matches the unique where inside the bound policy context`, meta: { model: model.name, method } });
5715
+ return {
5716
+ findMany: (args) => base.findMany(scopeFind(args)),
5717
+ findFirst: (args) => base.findFirst(scopeFind(args)),
5718
+ findFirstOrThrow: (args) => base.findFirstOrThrow(scopeFind(args)),
5719
+ findUnique: (args) => base.findFirst(scopeUnique(args)),
5720
+ findUniqueOrThrow: (args) => base.findFirstOrThrow(scopeUnique(args)),
5721
+ create: async (args) => base.create({ ...args, data: forceCreateData({ meta, model, data: args.data ?? {}, context }) }),
5722
+ createMany: async (args) => base.createMany({
5723
+ ...args,
5724
+ data: (args.data ?? []).map((row) => forceCreateData({ meta, model, data: row, context }))
5725
+ }),
5726
+ createManyAndReturn: async (args) => base.createManyAndReturn({
5727
+ ...args,
5728
+ data: (args.data ?? []).map((row) => forceCreateData({ meta, model, data: row, context }))
5729
+ }),
5730
+ update: async (args) => {
5731
+ const row = await lockThen(args, (raw, pkWhere) => raw.$models[model.name].update({ ...args, where: pkWhere }));
5732
+ if (row === null)
5733
+ throw notFound2("update");
5734
+ return row;
5735
+ },
5736
+ updateMany: (args) => base.updateMany({ ...args, where: mergeWhere({ where: args.where, field: policy.field, value }) }),
5737
+ upsert: async (args) => {
5738
+ const row = await lockThen(args, (raw, pkWhere) => raw.$models[model.name].update({ where: pkWhere, data: args.update, ...args.select === undefined ? {} : { select: args.select } }));
5739
+ if (row !== null)
5740
+ return row;
5741
+ return await base.create({
5742
+ data: forceCreateData({ meta, model, data: args.create ?? {}, context }),
5743
+ ...args.select === undefined ? {} : { select: args.select }
5744
+ });
5745
+ },
5746
+ delete: async (args) => {
5747
+ const row = await lockThen(args, (raw, pkWhere) => raw.$models[model.name].delete({ ...args, where: pkWhere }));
5748
+ if (row === null)
5749
+ throw notFound2("delete");
5750
+ return row;
5751
+ },
5752
+ deleteMany: (args) => base.deleteMany({ ...args ?? {}, where: mergeWhere({ where: args?.where, field: policy.field, value }) }),
5753
+ count: (args) => base.count(scopeFind(args)),
5754
+ aggregate: (args) => base.aggregate(scopeFind(args)),
5755
+ groupBy: (args) => base.groupBy(scopeFind(args))
5756
+ };
5757
+ }
5758
+ function scopeExtensionProps(params) {
5759
+ const out = {};
5760
+ for (const [key, prop] of Object.entries(params.base)) {
5761
+ if (key in params.delegate)
5762
+ continue;
5763
+ if (typeof prop === "function") {
5764
+ out[key] = (args) => prop({ ...args ?? {}, where: mergeWhere({ where: args?.where, field: params.field, value: params.value }) });
5765
+ } else if (typeof prop === "object" && prop !== null) {
5766
+ out[key] = scopeExtensionProps({ base: prop, delegate: {}, field: params.field, value: params.value });
5767
+ }
5768
+ }
5769
+ return out;
5770
+ }
5771
+ function attachPolicy(params) {
5772
+ const { client, meta } = params;
5773
+ const policied = policiedModels(meta);
5774
+ if (policied.length === 0)
5775
+ return;
5776
+ const originals = new Map;
5777
+ for (const model of policied) {
5778
+ const original = client.$models[model.name];
5779
+ originals.set(model.name, { ...original });
5780
+ const guard = (method) => () => Promise.reject(policyError(`model ${model.name} declares @@policy \u2014 bind a context with $withPolicy({ ${model.policy?.context}: ... }) or bypass deliberately with $bypassPolicy()`, { model: model.name, method }));
5781
+ const guarded = {};
5782
+ for (const key of Object.keys(original)) {
5783
+ guarded[key] = typeof original[key] === "function" ? guard(key) : original[key];
5784
+ }
5785
+ Object.assign(original, guarded);
5786
+ }
5787
+ const buildScoped = (context) => {
5788
+ for (const model of policied) {
5789
+ const key = model.policy?.context;
5790
+ if (context[key] === undefined || context[key] === null) {
5791
+ throw policyError(`$withPolicy: missing context value "${key}" (required by ${model.name})`, { context: key });
5792
+ }
5793
+ }
5794
+ const scoped = Object.create(client);
5795
+ const scopedModels = { ...client.$models };
5796
+ for (const model of policied) {
5797
+ const base = originals.get(model.name);
5798
+ const value = context[model.policy?.context];
5799
+ const delegate = scopedDelegate({ meta, model, base, context, transaction: client.$transaction });
5800
+ const full = { ...delegate, ...scopeExtensionProps({ base, delegate, field: model.policy?.field, value }) };
5801
+ scopedModels[model.name] = full;
5802
+ scopedModels[model.clientName] = full;
5803
+ Object.defineProperty(scoped, model.clientName, { value: full, enumerable: true });
5804
+ }
5805
+ Object.defineProperty(scoped, "$models", { value: scopedModels, enumerable: true });
5806
+ attachDefineView({ client: scoped, meta, models: scopedModels });
5807
+ Object.defineProperty(scoped, "$transaction", {
5808
+ value: (arg, options) => typeof arg === "function" ? client.$transaction((tx) => arg(buildScopedOver(tx, context)), options) : client.$transaction(arg, options),
5809
+ enumerable: true
5810
+ });
5811
+ return scoped;
5812
+ };
5813
+ const buildScopedOver = (tx, context) => {
5814
+ const rawTx = tx.$bypassPolicy === undefined ? tx : tx.$bypassPolicy();
5815
+ const inner = Object.create(tx);
5816
+ const scopedModels = { ...tx.$models };
5817
+ for (const model of policied) {
5818
+ const base = rawTx.$models[model.name];
5819
+ const delegate = scopedDelegate({ meta, model, base, context, transaction: tx.$transaction });
5820
+ scopedModels[model.name] = delegate;
5821
+ scopedModels[model.clientName] = delegate;
5822
+ Object.defineProperty(inner, model.clientName, { value: delegate, enumerable: true });
5823
+ }
5824
+ Object.defineProperty(inner, "$models", { value: scopedModels, enumerable: true });
5825
+ attachDefineView({ client: inner, meta, models: scopedModels });
5826
+ return inner;
5827
+ };
5828
+ Object.defineProperty(client, "$withPolicy", { value: (context) => buildScoped(context), enumerable: true });
5829
+ Object.defineProperty(client, "$bypassPolicy", {
5830
+ value: () => {
5831
+ const bypass = Object.create(client);
5832
+ const models = { ...client.$models };
5833
+ for (const model of policied) {
5834
+ const original = originals.get(model.name);
5835
+ models[model.name] = original;
5836
+ models[model.clientName] = original;
5837
+ Object.defineProperty(bypass, model.clientName, { value: original, enumerable: true });
5838
+ }
5839
+ Object.defineProperty(bypass, "$models", { value: models, enumerable: true });
5840
+ attachDefineView({ client: bypass, meta, models });
5841
+ return bypass;
5842
+ },
5843
+ enumerable: true
5844
+ });
5845
+ }
5846
+ // packages/runtime/src/scoped.ts
5847
+ import { ScopedQueryError, VibeError as VibeError13 } from "@vibeorm/schema";
5848
+ function refusal(params) {
5849
+ return new ScopedQueryError(params);
5850
+ }
5851
+ function isPlainObject5(value) {
5852
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
5853
+ }
5854
+ var SCOPEABLE_COLUMN_TYPES = ["String", "Int", "BigInt"];
5855
+ var resolvedCache = new WeakMap;
5856
+ function nestedModeOf(params) {
5857
+ const raw = params.entry.nested;
5858
+ if (raw === undefined)
5859
+ return "verify";
5860
+ if (raw === "verify" || raw === "refuse")
5861
+ return raw;
5862
+ throw refusal({
5863
+ model: params.model,
5864
+ path: "models",
5865
+ reason: "invalid_entry",
5866
+ message: `$scoped: models.${params.model}.nested must be "verify" or "refuse", got ${String(raw)}`
5867
+ });
5868
+ }
5869
+ function resolveScopedModels(params) {
5870
+ const { meta, models } = params;
5871
+ const byMap = resolvedCache.get(models) ?? new WeakMap;
5872
+ const cached = byMap.get(meta);
5873
+ if (cached !== undefined)
5874
+ return cached;
5875
+ for (const key of Object.keys(models)) {
5876
+ if (!meta.modelByName.has(key)) {
5877
+ throw refusal({
5878
+ model: key,
5879
+ path: "models",
5880
+ reason: "unknown_model",
5881
+ message: `$scoped: models map names unknown model "${key}"`
5882
+ });
5883
+ }
5884
+ }
5885
+ const resolved = new Map;
5886
+ const missing = [];
5887
+ for (const model of meta.models) {
5888
+ const entry = models[model.name] ?? models[model.clientName];
5889
+ if (entry === undefined) {
5890
+ missing.push(model.name);
5891
+ continue;
5892
+ }
5893
+ if (entry === "none") {
5894
+ resolved.set(model.name, { kind: "none" });
5895
+ continue;
5896
+ }
5897
+ if (!isPlainObject5(entry)) {
5898
+ throw refusal({
5899
+ model: model.name,
5900
+ path: "models",
5901
+ reason: "invalid_entry",
5902
+ message: `$scoped: models.${model.name} must be "none", { column } or { through }`
5903
+ });
5904
+ }
5905
+ const keys = Object.keys(entry).filter((key) => entry[key] !== undefined);
5906
+ const extras = keys.filter((key) => key !== "column" && key !== "through" && key !== "nested");
5907
+ if (extras.length > 0 || "column" in entry === "through" in entry) {
5908
+ throw refusal({
5909
+ model: model.name,
5910
+ path: "models",
5911
+ reason: "invalid_entry",
5912
+ message: `$scoped: models.${model.name} must carry exactly one of column/through (plus optional nested)${extras.length > 0 ? ` \u2014 unknown propert${extras.length === 1 ? "y" : "ies"}: ${extras.join(", ")}` : ""}`
5913
+ });
5914
+ }
5915
+ const nested = nestedModeOf({ entry, model: model.name });
5916
+ if ("column" in entry) {
5917
+ const field = model.fieldByName.get(String(entry.column));
5918
+ if (field === undefined) {
5919
+ throw refusal({
5920
+ model: model.name,
5921
+ path: "models",
5922
+ reason: "unknown_column",
5923
+ message: `$scoped: models.${model.name}.column names unknown field "${String(entry.column)}"`
5924
+ });
5925
+ }
5926
+ if (field.isList || field.scalarType === null || !SCOPEABLE_COLUMN_TYPES.includes(field.scalarType)) {
5927
+ throw refusal({
5928
+ model: model.name,
5929
+ path: "models",
5930
+ reason: "column_not_scopeable",
5931
+ message: `$scoped: models.${model.name}.column "${field.name}" must be a non-list String, Int or BigInt field`
5932
+ });
5933
+ }
5934
+ resolved.set(model.name, { kind: "column", field, nested });
5935
+ continue;
5936
+ }
5937
+ const relation = model.relationByName.get(String(entry.through));
5938
+ if (relation === undefined || relation.isList) {
5939
+ throw refusal({
5940
+ model: model.name,
5941
+ path: "models",
5942
+ reason: "unknown_relation",
5943
+ message: `$scoped: models.${model.name}.through must name a to-one relation \u2014 "${String(entry.through)}" ${relation === undefined ? "does not exist" : "is a list relation"}`
5944
+ });
5945
+ }
5946
+ resolveRelationLink({ meta, model, relation });
5947
+ resolved.set(model.name, { kind: "through", relation, targetModel: relation.targetModel, nested });
5948
+ }
5949
+ if (missing.length > 0) {
5950
+ throw refusal({
5951
+ model: missing[0],
5952
+ path: "models",
5953
+ reason: "unclassified_model",
5954
+ message: `$scoped: the models map must classify EVERY model \u2014 missing: ${missing.join(", ")} (use "none" for global models)`
5955
+ });
5956
+ }
5957
+ for (const [name, entry] of resolved) {
5958
+ if (entry.kind !== "through")
5959
+ continue;
5960
+ const seen = new Set([name]);
5961
+ let current = entry;
5962
+ while (current.kind === "through") {
5963
+ const nextName = current.targetModel;
5964
+ if (seen.has(nextName)) {
5965
+ throw refusal({
5966
+ model: name,
5967
+ path: "models",
5968
+ reason: "through_chain_invalid",
5969
+ message: `$scoped: models.${name} through-chain cycles (${[...seen, nextName].join(" \u2192 ")})`
5970
+ });
5971
+ }
5972
+ seen.add(nextName);
5973
+ const next = resolved.get(nextName);
5974
+ if (next === undefined || next.kind === "none") {
5975
+ throw refusal({
5976
+ model: name,
5977
+ path: "models",
5978
+ reason: "through_chain_invalid",
5979
+ message: `$scoped: models.${name} through-chain must end on a { column } model \u2014 "${nextName}" is ${next === undefined ? "unclassified" : '"none"'}`
5980
+ });
5981
+ }
5982
+ current = next;
5983
+ }
5984
+ }
5985
+ byMap.set(meta, resolved);
5986
+ resolvedCache.set(models, byMap);
5987
+ return resolved;
5988
+ }
5989
+ function assertScopeType(params) {
5990
+ const { resolved, scope } = params;
5991
+ for (const [name, entry] of resolved) {
5992
+ if (entry.kind !== "column")
5993
+ continue;
5994
+ const scalar = entry.field.scalarType;
5995
+ const ok = scalar === "String" ? typeof scope === "string" : scalar === "Int" ? typeof scope === "number" : typeof scope === "bigint" || typeof scope === "number";
5996
+ if (!ok) {
5997
+ throw refusal({
5998
+ model: name,
5999
+ path: "scope",
6000
+ reason: "scope_type_mismatch",
6001
+ message: `$scoped: scope value (${typeof scope}) does not match ${name}.${entry.field.name} (${scalar})`
6002
+ });
6003
+ }
6004
+ }
6005
+ }
6006
+ function buildPreds(params) {
6007
+ const { resolved, scope } = params;
6008
+ const preds = new Map;
6009
+ const predOf = (name) => {
6010
+ const known = preds.get(name);
6011
+ if (known !== undefined)
6012
+ return known;
6013
+ const entry = resolved.get(name);
6014
+ const value = entry.kind === "none" ? null : entry.kind === "column" ? { [entry.field.name]: scope } : { [entry.relation.name]: predOf(entry.targetModel) };
6015
+ preds.set(name, value);
6016
+ return value;
6017
+ };
6018
+ for (const name of resolved.keys())
6019
+ predOf(name);
6020
+ return preds;
6021
+ }
6022
+ function entryOf(ctx, model) {
6023
+ return ctx.resolved.get(model.name);
6024
+ }
6025
+ function requirePred(params) {
6026
+ const { ctx, model, path } = params;
6027
+ if (ctx.scope === null) {
6028
+ throw refusal({
6029
+ model: model.name,
6030
+ path,
6031
+ reason: "null_scope",
6032
+ message: `$scoped: scope is null \u2014 calls touching scoped model ${model.name} refuse until a scope is bound`
6033
+ });
6034
+ }
6035
+ return ctx.preds.get(model.name);
6036
+ }
6037
+ function mergeWhere2(params) {
6038
+ const { where, pred } = params;
6039
+ if (where === undefined)
6040
+ return pred;
6041
+ return { AND: [where, pred] };
6042
+ }
6043
+ function injectUniqueWhere(params) {
6044
+ const { where, pred } = params;
6045
+ if (!isPlainObject5(where))
6046
+ return { AND: [pred] };
6047
+ const existing = where.AND === undefined ? [] : Array.isArray(where.AND) ? where.AND : [where.AND];
6048
+ return { ...where, AND: [...existing, pred] };
6049
+ }
6050
+ function flattenUniqueWhere(params) {
6051
+ const { model, where } = params;
6052
+ if (model.compoundUniques.size === 0)
6053
+ return where;
6054
+ const out = {};
6055
+ for (const [key, value] of Object.entries(where)) {
6056
+ if (isPlainObject5(value) && model.compoundUniques.has(key))
6057
+ Object.assign(out, value);
6058
+ else
6059
+ out[key] = value;
6060
+ }
6061
+ return out;
6062
+ }
6063
+ function isTenancyInverse(params) {
6064
+ const { ctx, model, relation } = params;
6065
+ const targetEntry = ctx.resolved.get(relation.targetModel);
6066
+ if (targetEntry === undefined || targetEntry.kind !== "through")
6067
+ return false;
6068
+ const through = targetEntry.relation;
6069
+ return through.targetModel === model.name && (through.relationName ?? null) === (relation.relationName ?? null) && !(relation.model === relation.targetModel && through.name === relation.name);
6070
+ }
6071
+ function walkWhere(params) {
6072
+ const { ctx, model, where, path } = params;
6073
+ const out = {};
6074
+ const extra = [];
6075
+ const walkGroup = (value, key) => {
6076
+ const walkItem = (item) => isPlainObject5(item) ? walkWhere({ ctx, model, where: item, path: `${path}.${key}` }) : item;
6077
+ return Array.isArray(value) ? value.map(walkItem) : walkItem(value);
6078
+ };
6079
+ for (const [key, value] of Object.entries(where)) {
6080
+ if (value === undefined)
6081
+ continue;
6082
+ if (key === "AND" || key === "OR" || key === "NOT") {
6083
+ out[key] = walkGroup(value, key);
6084
+ continue;
6085
+ }
6086
+ const relation = model.relationByName.get(key);
6087
+ if (relation === undefined) {
6088
+ out[key] = value;
6089
+ continue;
6090
+ }
6091
+ const target = ctx.meta.modelByName.get(relation.targetModel);
6092
+ const targetEntry = entryOf(ctx, target);
6093
+ const scoped = targetEntry.kind !== "none";
6094
+ const childPath = `${path}.${key}`;
6095
+ const pred = () => requirePred({ ctx, model: target, path: childPath });
6096
+ const walkChild = (child, op) => isPlainObject5(child) ? walkWhere({ ctx, model: target, where: child, path: `${childPath}.${op}` }) : child;
6097
+ const parts = [];
6098
+ if (relation.isList) {
6099
+ if (!isPlainObject5(value)) {
6100
+ out[key] = value;
6101
+ continue;
6102
+ }
6103
+ for (const [op, operand] of Object.entries(value)) {
6104
+ if (operand === undefined)
6105
+ continue;
6106
+ if (op === "some" || op === "none") {
6107
+ const walked = walkChild(operand, op);
6108
+ parts.push({ [op]: scoped ? { AND: [walked, pred()] } : walked });
6109
+ } else if (op === "every") {
6110
+ const walked = walkChild(operand, op);
6111
+ parts.push(scoped ? { none: { AND: [pred(), { NOT: [walked] }] } } : { every: walked });
6112
+ } else {
6113
+ parts.push({ [op]: operand });
6114
+ }
6115
+ }
6116
+ } else {
6117
+ const arms = [];
6118
+ if (value === null) {
6119
+ arms.push({ arm: "is", operand: null });
6120
+ } else if (isPlainObject5(value) && (("is" in value) || ("isNot" in value))) {
6121
+ let mixed = false;
6122
+ for (const [op, operand] of Object.entries(value)) {
6123
+ if (operand === undefined)
6124
+ continue;
6125
+ if (op === "is" || op === "isNot")
6126
+ arms.push({ arm: op, operand });
6127
+ else
6128
+ mixed = true;
6129
+ }
6130
+ if (mixed) {
6131
+ out[key] = value;
6132
+ continue;
6133
+ }
6134
+ } else if (isPlainObject5(value)) {
6135
+ arms.push({ arm: "is", operand: value });
6136
+ } else {
6137
+ out[key] = value;
6138
+ continue;
6139
+ }
6140
+ for (const { arm, operand } of arms) {
6141
+ if (operand === null) {
6142
+ parts.push(scoped ? arm === "is" ? { isNot: pred() } : { is: pred() } : { [arm]: null });
6143
+ } else {
6144
+ const walked = walkChild(operand, arm);
6145
+ parts.push({ [arm]: scoped ? { AND: [walked, pred()] } : walked });
6146
+ }
6147
+ }
6148
+ }
6149
+ if (parts.length === 0) {
6150
+ out[key] = value;
6151
+ continue;
6152
+ }
6153
+ const combined = {};
6154
+ for (const part of parts) {
6155
+ const opKey = Object.keys(part)[0];
6156
+ if (opKey in combined)
6157
+ extra.push({ [key]: part });
6158
+ else
6159
+ combined[opKey] = part[opKey];
6160
+ }
6161
+ out[key] = combined;
6162
+ }
6163
+ if (extra.length > 0) {
6164
+ const existing = out.AND === undefined ? [] : Array.isArray(out.AND) ? out.AND : [out.AND];
6165
+ out.AND = [...existing, ...extra];
6166
+ }
6167
+ return out;
6168
+ }
6169
+ function walkOrderTerm(params) {
6170
+ const { ctx, model, term, path } = params;
6171
+ const entry = entryOf(ctx, model);
6172
+ for (const [key, spec] of Object.entries(term)) {
6173
+ if (spec === undefined)
6174
+ continue;
6175
+ const relation = model.relationByName.get(key);
6176
+ if (relation === undefined)
6177
+ continue;
6178
+ const target = ctx.meta.modelByName.get(relation.targetModel);
6179
+ const targetEntry = entryOf(ctx, target);
6180
+ const tenancyEdge = entry.kind === "through" && entry.relation.name === key;
6181
+ if (targetEntry.kind !== "none" && !tenancyEdge) {
6182
+ throw refusal({
6183
+ model: target.name,
6184
+ path: `${path}.${key}`,
6185
+ reason: "relation_orderby_on_scoped",
6186
+ message: `$scoped: orderBy through ${model.name}.${key} reaches scoped model ${target.name} \u2014 ordering by a foreign row's values would leak; order by own columns instead`
6187
+ });
6188
+ }
6189
+ if (isPlainObject5(spec)) {
6190
+ walkOrderTerm({ ctx, model: target, term: spec, path: `${path}.${key}` });
6191
+ }
6192
+ }
6193
+ }
6194
+ function walkOrderBy(params) {
6195
+ const { ctx, model, orderBy, path } = params;
6196
+ const terms = Array.isArray(orderBy) ? orderBy : [orderBy];
6197
+ for (const term of terms) {
6198
+ if (isPlainObject5(term))
6199
+ walkOrderTerm({ ctx, model, term, path });
6200
+ }
6201
+ }
6202
+ function walkCountSpec(params) {
6203
+ const { ctx, model, spec, path } = params;
6204
+ const guard = (relation) => {
6205
+ const target = ctx.meta.modelByName.get(relation.targetModel);
6206
+ const targetEntry = entryOf(ctx, target);
6207
+ if (targetEntry.kind === "none")
6208
+ return;
6209
+ if (isTenancyInverse({ ctx, model, relation }))
6210
+ return;
6211
+ throw refusal({
6212
+ model: target.name,
6213
+ path: `${path}.${relation.name}`,
6214
+ reason: "count_on_scoped_relation",
6215
+ message: `$scoped: _count of ${model.name}.${relation.name} would count ${target.name} rows across tenants (relation counts take no filter) \u2014 select the relation with a where instead`
6216
+ });
6217
+ };
6218
+ if (spec === true) {
6219
+ for (const relation of model.relations) {
6220
+ if (relation.isList)
6221
+ guard(relation);
6222
+ }
6223
+ return;
6224
+ }
6225
+ if (!isPlainObject5(spec) || !isPlainObject5(spec.select))
6226
+ return;
6227
+ for (const [name, flag] of Object.entries(spec.select)) {
6228
+ if (flag === undefined || flag === false)
6229
+ continue;
6230
+ const relation = model.relationByName.get(name);
6231
+ if (relation !== undefined)
6232
+ guard(relation);
6233
+ }
6234
+ }
6235
+ function walkSelection(params) {
6236
+ const { ctx, model, args, path } = params;
6237
+ for (const blockKey of ["include", "select"]) {
6238
+ const block = args[blockKey];
6239
+ if (!isPlainObject5(block))
6240
+ continue;
6241
+ const next = { ...block };
6242
+ for (const [name, spec] of Object.entries(block)) {
6243
+ if (spec === undefined || spec === false)
6244
+ continue;
6245
+ const blockPath = `${path}.${blockKey}`;
6246
+ if (name === "_count") {
6247
+ walkCountSpec({ ctx, model, spec, path: `${blockPath}._count` });
6248
+ continue;
6249
+ }
6250
+ const relation = model.relationByName.get(name);
6251
+ if (relation === undefined)
6252
+ continue;
6253
+ const target = ctx.meta.modelByName.get(relation.targetModel);
6254
+ const targetEntry = entryOf(ctx, target);
6255
+ const scoped = targetEntry.kind !== "none";
6256
+ const childPath = `${blockPath}.${name}`;
6257
+ if (spec === true && !scoped)
6258
+ continue;
6259
+ const child = spec === true ? {} : isPlainObject5(spec) ? { ...spec } : spec;
6260
+ if (!isPlainObject5(child))
6261
+ continue;
6262
+ const walkedWhere = isPlainObject5(child.where) ? walkWhere({ ctx, model: target, where: child.where, path: `${childPath}.where` }) : child.where;
6263
+ if (scoped) {
6264
+ child.where = mergeWhere2({ where: walkedWhere, pred: requirePred({ ctx, model: target, path: childPath }) });
6265
+ } else if (walkedWhere !== undefined) {
6266
+ child.where = walkedWhere;
6267
+ }
6268
+ if (child.orderBy !== undefined) {
6269
+ walkOrderBy({ ctx, model: target, orderBy: child.orderBy, path: `${childPath}.orderBy` });
6270
+ }
6271
+ walkSelection({ ctx, model: target, args: child, path: childPath });
6272
+ next[name] = child;
6273
+ }
6274
+ args[blockKey] = next;
6275
+ }
6276
+ }
6277
+ var REFERENCE_VERBS2 = [
6278
+ "connect",
6279
+ "connectOrCreate",
6280
+ "set",
6281
+ "disconnect",
6282
+ "update",
6283
+ "updateMany",
6284
+ "upsert",
6285
+ "delete",
6286
+ "deleteMany"
6287
+ ];
6288
+ function nestedModeFor(entry) {
6289
+ return entry.kind === "none" ? "verify" : entry.nested;
6290
+ }
6291
+ function mapItems(value, fn) {
6292
+ return Array.isArray(value) ? value.map(fn) : fn(value);
6293
+ }
6294
+ function walkCreateData(params) {
6295
+ const { ctx, model, data, path } = params;
6296
+ if (!isPlainObject5(data))
6297
+ return data;
6298
+ const entry = entryOf(ctx, model);
6299
+ const out = { ...data };
6300
+ if (entry.kind === "column") {
6301
+ requirePred({ ctx, model, path });
6302
+ const given = out[entry.field.name];
6303
+ if (given !== undefined && given !== ctx.scope) {
6304
+ throw refusal({
6305
+ model: model.name,
6306
+ path: `${path}.${entry.field.name}`,
6307
+ reason: "explicit_scope_mismatch",
6308
+ message: `$scoped: create on ${model.name} sets ${entry.field.name} to ${given === null ? "null" : "a different tenant"} \u2014 the scoped handle owns that column`
6309
+ });
6310
+ }
6311
+ out[entry.field.name] = ctx.scope;
6312
+ }
6313
+ if (entry.kind === "through" && params.viaTenancyEdge !== true) {
6314
+ requirePred({ ctx, model, path });
6315
+ const link = resolveRelationLink({ meta: ctx.meta, model, relation: entry.relation });
6316
+ if (link.kind === "toOneOwner" && out[link.parentKey.name] !== undefined) {
6317
+ throw refusal({
6318
+ model: model.name,
6319
+ path: `${path}.${link.parentKey.name}`,
6320
+ reason: "through_fk_write",
6321
+ message: `$scoped: writing ${model.name}.${link.parentKey.name} directly would bypass tenant verification \u2014 use ${entry.relation.name}: { connect: \u2026 } instead`
6322
+ });
6323
+ }
6324
+ if (out[entry.relation.name] === undefined) {
6325
+ throw refusal({
6326
+ model: model.name,
6327
+ path,
6328
+ reason: "through_fk_write",
6329
+ message: `$scoped: create on ${model.name} must bind its tenancy parent \u2014 provide ${entry.relation.name}: { connect | create | connectOrCreate: \u2026 }`
6330
+ });
6331
+ }
6332
+ }
6333
+ for (const [key, value] of Object.entries(out)) {
6334
+ const relation = model.relationByName.get(key);
6335
+ if (relation === undefined || !isPlainObject5(value))
6336
+ continue;
6337
+ out[key] = walkNestedVerbs({ ctx, model, relation, verbs: value, path: `${path}.${key}`, phase: "create" });
6338
+ }
6339
+ return out;
6340
+ }
6341
+ function walkUpdateData(params) {
6342
+ const { ctx, model, data, path } = params;
6343
+ if (!isPlainObject5(data))
6344
+ return data;
6345
+ const entry = entryOf(ctx, model);
6346
+ const out = { ...data };
6347
+ if (entry.kind === "column") {
6348
+ const given = out[entry.field.name];
6349
+ if (given !== undefined) {
6350
+ const setValue = isPlainObject5(given) && Object.keys(given).length === 1 ? given.set : given;
6351
+ if (setValue !== ctx.scope) {
6352
+ throw refusal({
6353
+ model: model.name,
6354
+ path: `${path}.${entry.field.name}`,
6355
+ reason: "explicit_scope_mismatch",
6356
+ message: `$scoped: update on ${model.name} writes ${entry.field.name} \u2014 a scoped handle cannot move rows between tenants`
6357
+ });
6358
+ }
6359
+ }
6360
+ }
6361
+ if (entry.kind === "through") {
6362
+ const link = resolveRelationLink({ meta: ctx.meta, model, relation: entry.relation });
6363
+ if (link.kind === "toOneOwner" && out[link.parentKey.name] !== undefined) {
6364
+ throw refusal({
6365
+ model: model.name,
6366
+ path: `${path}.${link.parentKey.name}`,
6367
+ reason: "through_fk_write",
6368
+ message: `$scoped: writing ${model.name}.${link.parentKey.name} directly would bypass tenant verification \u2014 use ${entry.relation.name}: { connect: \u2026 } instead`
6369
+ });
6370
+ }
6371
+ }
6372
+ for (const [key, value] of Object.entries(out)) {
6373
+ const relation = model.relationByName.get(key);
6374
+ if (relation === undefined || !isPlainObject5(value))
6375
+ continue;
6376
+ out[key] = walkNestedVerbs({ ctx, model, relation, verbs: value, path: `${path}.${key}`, phase: "update" });
6377
+ }
6378
+ return out;
6379
+ }
6380
+ function walkNestedVerbs(params) {
6381
+ const { ctx, model, relation, verbs, path, phase } = params;
6382
+ const target = ctx.meta.modelByName.get(relation.targetModel);
6383
+ const targetEntry = entryOf(ctx, target);
6384
+ const scoped = targetEntry.kind !== "none";
6385
+ const modelEntry = entryOf(ctx, model);
6386
+ const pairedDown = scoped && isTenancyInverse({ ctx, model, relation });
6387
+ const tenancyEdge = modelEntry.kind === "through" && modelEntry.relation.name === relation.name;
6388
+ const implicitSafe = pairedDown || tenancyEdge;
6389
+ const mode = nestedModeFor(targetEntry);
6390
+ const out = { ...verbs };
6391
+ const pred = (verbPath) => requirePred({ ctx, model: target, path: verbPath });
6392
+ const refuse = (verb, why) => {
6393
+ throw refusal({
6394
+ model: target.name,
6395
+ path: `${path}.${verb}`,
6396
+ reason: "nested_refused",
6397
+ message: `$scoped: nested "${verb}" on ${model.name}.${relation.name} (${target.name}) ${why}`
6398
+ });
6399
+ };
6400
+ const walkTargetWhere = (where, verbPath) => isPlainObject5(where) ? walkWhere({ ctx, model: target, where, path: verbPath }) : where;
6401
+ const uniqueWhere = (where, verbPath) => {
6402
+ const walked = walkTargetWhere(where, verbPath);
6403
+ return scoped ? injectUniqueWhere({ where: walked, pred: pred(verbPath) }) : walked;
6404
+ };
6405
+ const filterWhere = (where, verbPath) => {
6406
+ const walked = walkTargetWhere(where, verbPath);
6407
+ return scoped ? mergeWhere2({ where: walked ?? {}, pred: pred(verbPath) }) : walked;
6408
+ };
6409
+ const walkChildCreate = (item, verbPath) => walkCreateData({ ctx, model: target, data: item, path: verbPath, viaTenancyEdge: pairedDown });
6410
+ const walkChildUpdate = (item, verbPath) => walkUpdateData({ ctx, model: target, data: item, path: verbPath });
6411
+ const link = () => resolveRelationLink({ meta: ctx.meta, model, relation });
6412
+ for (const [verb, operand] of Object.entries(verbs)) {
6413
+ if (operand === undefined)
6414
+ continue;
6415
+ const verbPath = `${path}.${verb}`;
6416
+ if (verb === "create") {
6417
+ out[verb] = mapItems(operand, (item) => walkChildCreate(item, verbPath));
6418
+ continue;
6419
+ }
6420
+ if (scoped && mode === "refuse" && REFERENCE_VERBS2.includes(verb)) {
6421
+ refuse(verb, `is disabled \u2014 ${target.name} is configured nested: "refuse"; go through the base client or its own delegate`);
6422
+ }
6423
+ switch (verb) {
6424
+ case "connect":
6425
+ out[verb] = mapItems(operand, (item) => isPlainObject5(item) ? uniqueWhere(item, verbPath) : item);
6426
+ break;
6427
+ case "connectOrCreate":
6428
+ out[verb] = mapItems(operand, (item) => isPlainObject5(item) ? { ...item, where: uniqueWhere(item.where, verbPath), create: walkChildCreate(item.create, verbPath) } : item);
6429
+ break;
6430
+ case "set": {
6431
+ if (phase === "create")
6432
+ break;
6433
+ if (scoped) {
6434
+ const kind = link().kind;
6435
+ if (kind === "manyToMany")
6436
+ refuse(verb, "clears the whole join set \u2014 unlinking other tenants' rows cannot be verified; disconnect explicit rows instead");
6437
+ if (!implicitSafe)
6438
+ refuse(verb, "first detaches EVERY current child, which cannot be tenant-filtered on this edge; disconnect explicit rows instead");
6439
+ }
6440
+ out[verb] = mapItems(operand ?? [], (item) => isPlainObject5(item) ? uniqueWhere(item, verbPath) : item);
6441
+ break;
6442
+ }
6443
+ case "disconnect": {
6444
+ if (!relation.isList) {
6445
+ if (scoped) {
6446
+ const kind = link().kind;
6447
+ if (kind === "toOneOwner") {
6448
+ if (tenancyEdge) {
6449
+ refuse(verb, "would orphan the row out of its tenant (through-parent set to null) \u2014 delete it or connect a replacement");
6450
+ }
6451
+ } else if (!implicitSafe) {
6452
+ refuse(verb, "targets whatever row currently points here \u2014 not tenant-verifiable on this edge");
6453
+ }
6454
+ }
6455
+ break;
6456
+ }
6457
+ out[verb] = mapItems(operand, (item) => isPlainObject5(item) ? uniqueWhere(item, verbPath) : item);
6458
+ break;
6459
+ }
6460
+ case "delete": {
6461
+ if (!relation.isList) {
6462
+ if (scoped && !implicitSafe) {
6463
+ refuse(verb, "targets whatever row is currently connected \u2014 not tenant-verifiable on this edge");
6464
+ }
6465
+ break;
6466
+ }
6467
+ out[verb] = mapItems(operand, (item) => isPlainObject5(item) ? uniqueWhere(item, verbPath) : item);
6468
+ break;
6469
+ }
6470
+ case "update": {
6471
+ if (!relation.isList) {
6472
+ if (scoped && !implicitSafe) {
6473
+ refuse(verb, "targets whatever row is currently connected \u2014 not tenant-verifiable on this edge; update it through its own delegate");
6474
+ }
6475
+ out[verb] = isPlainObject5(operand) ? "data" in operand && isPlainObject5(operand.data) ? { ...operand, data: walkChildUpdate(operand.data, verbPath) } : walkChildUpdate(operand, verbPath) : operand;
6476
+ break;
6477
+ }
6478
+ out[verb] = mapItems(operand, (item) => isPlainObject5(item) ? { ...item, where: uniqueWhere(item.where, verbPath), data: walkChildUpdate(item.data, verbPath) } : item);
6479
+ break;
6480
+ }
6481
+ case "updateMany":
6482
+ out[verb] = mapItems(operand, (item) => isPlainObject5(item) ? { ...item, where: filterWhere(item.where, verbPath), data: walkChildUpdate(item.data, verbPath) } : item);
6483
+ break;
6484
+ case "deleteMany":
6485
+ out[verb] = mapItems(operand, (item) => filterWhere(item, verbPath));
6486
+ break;
6487
+ case "upsert": {
6488
+ if (!relation.isList) {
6489
+ if (scoped && !implicitSafe) {
6490
+ refuse(verb, "updates whatever row is currently connected \u2014 not tenant-verifiable on this edge");
6491
+ }
6492
+ out[verb] = isPlainObject5(operand) ? {
6493
+ ...operand,
6494
+ create: walkChildCreate(operand.create, verbPath),
6495
+ update: walkChildUpdate(operand.update, verbPath)
6496
+ } : operand;
6497
+ break;
6498
+ }
6499
+ out[verb] = mapItems(operand, (item) => isPlainObject5(item) ? {
6500
+ ...item,
6501
+ where: uniqueWhere(item.where, verbPath),
6502
+ create: walkChildCreate(item.create, verbPath),
6503
+ update: walkChildUpdate(item.update, verbPath)
6504
+ } : item);
6505
+ break;
6506
+ }
6507
+ default:
6508
+ break;
6509
+ }
6510
+ }
6511
+ return out;
6512
+ }
6513
+ function createScopeRewriter(params) {
6514
+ const { meta, models, scope } = params;
6515
+ const resolved = resolveScopedModels({ meta, models });
6516
+ if (scope !== null)
6517
+ assertScopeType({ meta, resolved, scope });
6518
+ const preds = scope === null ? new Map : buildPreds({ meta, resolved, scope });
6519
+ return {
6520
+ rewrite: (call) => rewriteScopedCall({
6521
+ meta,
6522
+ resolved,
6523
+ scope,
6524
+ preds,
6525
+ model: call.model,
6526
+ method: call.method,
6527
+ args: call.args
6528
+ })
6529
+ };
6530
+ }
6531
+ var FIND_LIKE = ["findMany", "findFirst", "findFirstOrThrow", "count", "aggregate", "groupBy"];
6532
+ function rewriteScopedCall(params) {
6533
+ const { meta, resolved, scope, preds, method } = params;
6534
+ const ctx = { meta, resolved, scope, preds };
6535
+ const model = getModelMeta({ meta, model: params.model });
6536
+ const entry = entryOf(ctx, model);
6537
+ const scoped = entry.kind !== "none";
6538
+ const root = `${model.clientName}.${method}`;
6539
+ const args = { ...params.args ?? {} };
6540
+ const ownPred = () => requirePred({ ctx, model, path: root });
6541
+ const walkOwnWhere = (where, path) => isPlainObject5(where) ? walkWhere({ ctx, model, where, path }) : where;
6542
+ if (FIND_LIKE.includes(method)) {
6543
+ const walked = walkOwnWhere(args.where, `${root}.where`);
6544
+ if (scoped)
6545
+ args.where = mergeWhere2({ where: walked, pred: ownPred() });
6546
+ else if (walked !== undefined)
6547
+ args.where = walked;
6548
+ if (args.orderBy !== undefined)
6549
+ walkOrderBy({ ctx, model, orderBy: args.orderBy, path: `${root}.orderBy` });
6550
+ walkSelection({ ctx, model, args, path: root });
6551
+ return { model: model.name, method, args };
6552
+ }
6553
+ switch (method) {
6554
+ case "findUnique":
6555
+ case "findUniqueOrThrow": {
6556
+ walkSelection({ ctx, model, args, path: root });
6557
+ if (!scoped) {
6558
+ const walked2 = walkOwnWhere(args.where, `${root}.where`);
6559
+ if (walked2 !== undefined)
6560
+ args.where = walked2;
6561
+ return { model: model.name, method, args };
6562
+ }
6563
+ const flat = isPlainObject5(args.where) ? flattenUniqueWhere({ model, where: args.where }) : args.where;
6564
+ const walked = isPlainObject5(flat) ? walkWhere({ ctx, model, where: flat, path: `${root}.where` }) : flat;
6565
+ args.where = mergeWhere2({ where: walked, pred: ownPred() });
6566
+ return {
6567
+ model: model.name,
6568
+ method: method === "findUnique" ? "findFirst" : "findFirstOrThrow",
6569
+ args
6570
+ };
6571
+ }
6572
+ case "create": {
6573
+ args.data = walkCreateData({ ctx, model, data: args.data ?? {}, path: `${root}.data` });
6574
+ walkSelection({ ctx, model, args, path: root });
6575
+ return { model: model.name, method, args };
6576
+ }
6577
+ case "createMany":
6578
+ case "createManyAndReturn": {
6579
+ const rows = args.data;
6580
+ args.data = Array.isArray(rows) ? rows.map((row) => walkCreateData({ ctx, model, data: row, path: `${root}.data` })) : walkCreateData({ ctx, model, data: rows ?? {}, path: `${root}.data` });
6581
+ if (method === "createManyAndReturn")
6582
+ walkSelection({ ctx, model, args, path: root });
6583
+ return { model: model.name, method, args };
6584
+ }
6585
+ case "update": {
6586
+ const walked = walkOwnWhere(args.where, `${root}.where`);
6587
+ args.where = scoped ? injectUniqueWhere({ where: walked, pred: ownPred() }) : walked;
6588
+ args.data = walkUpdateData({ ctx, model, data: args.data ?? {}, path: `${root}.data` });
6589
+ walkSelection({ ctx, model, args, path: root });
6590
+ return { model: model.name, method, args };
6591
+ }
6592
+ case "updateMany": {
6593
+ const walked = walkOwnWhere(args.where, `${root}.where`);
6594
+ if (scoped)
6595
+ args.where = mergeWhere2({ where: walked, pred: ownPred() });
6596
+ else if (walked !== undefined)
6597
+ args.where = walked;
6598
+ args.data = walkUpdateData({ ctx, model, data: args.data ?? {}, path: `${root}.data` });
6599
+ return { model: model.name, method, args };
6600
+ }
6601
+ case "upsert": {
6602
+ const walked = walkOwnWhere(args.where, `${root}.where`);
6603
+ args.where = scoped ? injectUniqueWhere({ where: walked, pred: ownPred() }) : walked;
6604
+ args.create = walkCreateData({ ctx, model, data: args.create ?? {}, path: `${root}.create` });
6605
+ args.update = walkUpdateData({ ctx, model, data: args.update ?? {}, path: `${root}.update` });
6606
+ walkSelection({ ctx, model, args, path: root });
6607
+ return { model: model.name, method, args };
6608
+ }
6609
+ case "delete": {
6610
+ const walked = walkOwnWhere(args.where, `${root}.where`);
6611
+ args.where = scoped ? injectUniqueWhere({ where: walked, pred: ownPred() }) : walked;
6612
+ walkSelection({ ctx, model, args, path: root });
6613
+ return { model: model.name, method, args };
6614
+ }
6615
+ case "deleteMany": {
6616
+ const walked = walkOwnWhere(args.where, `${root}.where`);
6617
+ if (scoped)
6618
+ args.where = mergeWhere2({ where: walked, pred: ownPred() });
6619
+ else if (walked !== undefined)
6620
+ args.where = walked;
6621
+ return { model: model.name, method, args };
6622
+ }
6623
+ default:
6624
+ return { model: model.name, method, args: params.args };
6625
+ }
6626
+ }
6627
+ function scopedDelegate2(params) {
6628
+ const { ctx, model, base } = params;
6629
+ const run = (method, args) => {
6630
+ let rewritten;
6631
+ try {
6632
+ rewritten = rewriteScopedCall({
6633
+ meta: ctx.meta,
6634
+ resolved: ctx.resolved,
6635
+ scope: ctx.scope,
6636
+ preds: ctx.preds,
6637
+ model: model.name,
6638
+ method,
6639
+ args
6640
+ });
6641
+ } catch (error) {
6642
+ return Promise.reject(error);
6643
+ }
6644
+ const target = base[rewritten.method];
6645
+ return target(rewritten.args);
6646
+ };
6647
+ return {
6648
+ findMany: (args) => run("findMany", args),
6649
+ findFirst: (args) => run("findFirst", args),
6650
+ findFirstOrThrow: (args) => run("findFirstOrThrow", args),
6651
+ findUnique: (args) => run("findUnique", args),
6652
+ findUniqueOrThrow: (args) => run("findUniqueOrThrow", args),
6653
+ create: (args) => run("create", args),
6654
+ createMany: (args) => run("createMany", args),
6655
+ createManyAndReturn: (args) => run("createManyAndReturn", args),
6656
+ update: (args) => run("update", args),
6657
+ updateMany: (args) => run("updateMany", args),
6658
+ upsert: (args) => run("upsert", args),
6659
+ delete: (args) => run("delete", args),
6660
+ deleteMany: (args) => run("deleteMany", args),
6661
+ count: (args) => run("count", args),
6662
+ aggregate: (args) => run("aggregate", args),
6663
+ groupBy: (args) => run("groupBy", args)
6664
+ };
6665
+ }
6666
+ function scopeExtensionProps2(params) {
6667
+ const { ctx, model, base, delegate } = params;
6668
+ const entry = entryOf(ctx, model);
6669
+ const out = {};
6670
+ for (const [key, prop] of Object.entries(base)) {
6671
+ if (key in delegate)
6672
+ continue;
6673
+ if (typeof prop === "function") {
6674
+ out[key] = (args) => {
6675
+ try {
6676
+ const path = `${model.clientName}.${key}`;
6677
+ const walked = isPlainObject5(args?.where) ? walkWhere({ ctx, model, where: args.where, path: `${path}.where` }) : args?.where;
6678
+ const where = entry.kind === "none" ? walked : mergeWhere2({ where: walked, pred: requirePred({ ctx, model, path }) });
6679
+ return prop({ ...args ?? {}, ...where === undefined ? {} : { where } });
6680
+ } catch (error) {
6681
+ return Promise.reject(error);
6682
+ }
6683
+ };
6684
+ } else if (isPlainObject5(prop)) {
6685
+ out[key] = scopeExtensionProps2({
6686
+ ctx,
6687
+ model,
6688
+ base: prop,
6689
+ delegate: {}
6690
+ });
6691
+ }
6692
+ }
6693
+ return out;
6694
+ }
6695
+ function forbidden(params) {
6696
+ return refusal({
6697
+ model: "*",
6698
+ path: params.method,
6699
+ reason: "raw_forbidden",
6700
+ message: `$scoped: ${params.method} is not available on a scoped handle \u2014 raw SQL cannot be tenant-verified; run it on the BASE client (the deliberate unscoped escape)`
6701
+ });
6702
+ }
6703
+ var RAW_METHODS = ["$queryRaw", "$executeRaw", "$queryRawUnsafe", "$executeRawUnsafe"];
6704
+ function buildScopedOver(params) {
6705
+ const { ctx, client } = params;
6706
+ const scoped = Object.create(client);
6707
+ const scopedModels = { ...client.$models };
6708
+ for (const model of ctx.meta.models) {
6709
+ const base = client.$models[model.name];
6710
+ const delegate = scopedDelegate2({ ctx, model, base });
6711
+ const full = { ...delegate, ...scopeExtensionProps2({ ctx, model, base, delegate }) };
6712
+ scopedModels[model.name] = full;
6713
+ scopedModels[model.clientName] = full;
6714
+ Object.defineProperty(scoped, model.clientName, { value: full, enumerable: true });
6715
+ }
6716
+ Object.defineProperty(scoped, "$models", { value: scopedModels, enumerable: true });
6717
+ attachDefineView({ client: scoped, meta: ctx.meta, models: scopedModels });
6718
+ Object.defineProperty(scoped, "$transaction", {
6719
+ value: (arg, options) => typeof arg === "function" ? client.$transaction((tx) => arg(buildScopedOver({ ctx, client: tx })), options) : client.$transaction(arg, options),
6720
+ enumerable: true
6721
+ });
6722
+ for (const method of RAW_METHODS) {
6723
+ Object.defineProperty(scoped, method, {
6724
+ value: () => Promise.reject(forbidden({ method })),
6725
+ enumerable: true
6726
+ });
6727
+ }
6728
+ Object.defineProperty(scoped, "$scoped", {
6729
+ value: () => {
6730
+ throw refusal({
6731
+ model: "*",
6732
+ path: "$scoped",
6733
+ reason: "rescope_forbidden",
6734
+ message: "$scoped: this handle is already scoped \u2014 build a differently-scoped handle from the BASE client"
6735
+ });
6736
+ },
6737
+ enumerable: true
6738
+ });
6739
+ return scoped;
6740
+ }
6741
+ function attachScoped(params) {
6742
+ const { client, meta } = params;
6743
+ Object.defineProperty(client, "$scoped", {
6744
+ value: (config) => {
6745
+ if (!isPlainObject5(config) || !isPlainObject5(config.models)) {
6746
+ throw new VibeError13({
6747
+ code: "VIBE_VALIDATION",
6748
+ message: "$scoped takes { scope: string | number | bigint | null, models: { <Model>: \u2026 } }"
6749
+ });
6750
+ }
6751
+ const policied = meta.models.filter((model) => model.policy !== undefined);
6752
+ if (policied.length > 0) {
6753
+ throw refusal({
6754
+ model: policied[0]?.name,
6755
+ path: "$scoped",
6756
+ reason: "policy_conflict",
6757
+ message: `$scoped: the schema declares @@policy on ${policied.map((model) => model.name).join(", ")} \u2014 use $withPolicy for policied schemas; the two scoping layers do not combine`
6758
+ });
6759
+ }
6760
+ const scope = config.scope;
6761
+ if (scope !== null && typeof scope !== "string" && typeof scope !== "number" && typeof scope !== "bigint") {
6762
+ throw new VibeError13({
6763
+ code: "VIBE_VALIDATION",
6764
+ message: `$scoped: scope must be a string, number, bigint or null \u2014 got ${typeof scope}`
6765
+ });
6766
+ }
6767
+ const resolved = resolveScopedModels({ meta, models: config.models });
6768
+ if (scope !== null)
6769
+ assertScopeType({ meta, resolved, scope });
6770
+ const preds = scope === null ? new Map : buildPreds({ meta, resolved, scope });
6771
+ const ctx = { meta, resolved, scope, preds };
6772
+ return buildScopedOver({ ctx, client });
6773
+ },
6774
+ enumerable: true
6775
+ });
6776
+ }
5508
6777
  // packages/runtime/src/client.ts
5509
- import { VibeError as VibeError11, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
6778
+ import { VibeError as VibeError14, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
5510
6779
  import { ParamCollector, getSqlDialect } from "@vibeorm/sql";
5511
6780
 
5512
6781
  // packages/runtime/src/render-cache.ts
@@ -5693,7 +6962,7 @@ function walkExpr(walk, dialect, expr) {
5693
6962
  return true;
5694
6963
  }
5695
6964
  }
5696
- function walkOrderTerm(walk, dialect, term) {
6965
+ function walkOrderTerm2(walk, dialect, term) {
5697
6966
  if ("agg" in term) {
5698
6967
  walk.key += "a";
5699
6968
  addAgg(walk, term.agg);
@@ -5743,7 +7012,7 @@ function walkSelect(walk, dialect, statement) {
5743
7012
  addCol(walk, column);
5744
7013
  walk.key += ":";
5745
7014
  for (const term of statement.rowNumber.orderBy) {
5746
- if (!walkOrderTerm(walk, dialect, term))
7015
+ if (!walkOrderTerm2(walk, dialect, term))
5747
7016
  return false;
5748
7017
  }
5749
7018
  addIdent(walk, statement.rowNumber.alias);
@@ -5798,7 +7067,7 @@ function walkSelect(walk, dialect, statement) {
5798
7067
  if (statement.orderBy !== undefined && statement.orderBy.length > 0) {
5799
7068
  walk.key += "o(";
5800
7069
  for (const term of statement.orderBy) {
5801
- if (!walkOrderTerm(walk, dialect, term))
7070
+ if (!walkOrderTerm2(walk, dialect, term))
5802
7071
  return false;
5803
7072
  }
5804
7073
  walk.key += ")";
@@ -5956,7 +7225,7 @@ function renderStatementCached(params) {
5956
7225
  // packages/runtime/src/client.ts
5957
7226
  function notFound2(params) {
5958
7227
  const { model, method } = params;
5959
- return new VibeError11({
7228
+ return new VibeError14({
5960
7229
  code: "VIBE_NOT_FOUND",
5961
7230
  message: `no ${model} record matched the where clause of ${method}()`,
5962
7231
  meta: { model, method }
@@ -5964,7 +7233,7 @@ function notFound2(params) {
5964
7233
  }
5965
7234
  function viewIsReadOnly(params) {
5966
7235
  const { model, method } = params;
5967
- return new VibeError11({
7236
+ return new VibeError14({
5968
7237
  code: "VIBE_VALIDATION",
5969
7238
  message: `${model} is a view \u2014 ${method}() is not available on views (they are read-only); write to the underlying table instead`,
5970
7239
  meta: { model, method }
@@ -6093,7 +7362,7 @@ class QueryPromise extends Promise {
6093
7362
  }
6094
7363
  claim() {
6095
7364
  if (this.started) {
6096
- throw new VibeError11({
7365
+ throw new VibeError14({
6097
7366
  code: "VIBE_VALIDATION",
6098
7367
  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)"
6099
7368
  });
@@ -6171,7 +7440,7 @@ async function runAffected(params) {
6171
7440
  function plan(params) {
6172
7441
  const { engine, model, method, args } = params;
6173
7442
  if (args !== undefined && args["lock"] !== undefined && engine.inTransaction !== true) {
6174
- throw new VibeError11({
7443
+ throw new VibeError14({
6175
7444
  code: "VIBE_VALIDATION",
6176
7445
  message: `lock on ${model}.${method}() requires a transaction \u2014 call it on the client $transaction hands its callback (outside one, the lock would release at statement end)`,
6177
7446
  meta: { model, method }
@@ -6244,7 +7513,7 @@ async function insertedKeyWhere(params) {
6244
7513
  });
6245
7514
  const id = result.rows[0]?.id;
6246
7515
  if (id === undefined || id === null) {
6247
- throw new VibeError11({
7516
+ throw new VibeError14({
6248
7517
  code: "VIBE_INTERNAL",
6249
7518
  message: `could not read back the generated key for ${source.model.name}`,
6250
7519
  meta: { model: source.model.name }
@@ -6546,7 +7815,7 @@ function createDelegate(params) {
6546
7815
  const skipping = args.onConflict !== undefined;
6547
7816
  if (hasNestedWrites({ model: modelMeta, data: args.data })) {
6548
7817
  if (skipping) {
6549
- throw new VibeError11({
7818
+ throw new VibeError14({
6550
7819
  code: "VIBE_VALIDATION",
6551
7820
  message: `onConflict cannot combine with nested writes on ${modelMeta.name}.create() \u2014 insert the parent row alone, then connect relations`,
6552
7821
  meta: { model: modelMeta.name }
@@ -6630,7 +7899,7 @@ function createDelegate(params) {
6630
7899
  const upsert = async (target, args) => {
6631
7900
  const modelMeta = getModelMeta({ meta: target.meta, model });
6632
7901
  if (hasNestedWrites({ model: modelMeta, data: args.create }) || hasNestedWrites({ model: modelMeta, data: args.update })) {
6633
- throw new VibeError11({
7902
+ throw new VibeError14({
6634
7903
  code: "VIBE_VALIDATION",
6635
7904
  message: `nested writes are not supported inside upsert arms on ${modelMeta.name} \u2014 use create()/update() with nested data`,
6636
7905
  meta: { model: modelMeta.name }
@@ -6638,7 +7907,7 @@ function createDelegate(params) {
6638
7907
  }
6639
7908
  if (!upsertUsesNativeStatement({ model: modelMeta, args })) {
6640
7909
  if (args.onConflict !== undefined) {
6641
- throw new VibeError11({
7910
+ throw new VibeError14({
6642
7911
  code: "VIBE_VALIDATION",
6643
7912
  message: `onConflict on ${modelMeta.name}.upsert() needs the native single-statement path \u2014 this shape (a guard field beyond the conflict target, or create data not binding the where's key) runs the find-then-write fallback, which cannot honor an explicit conflict target`,
6644
7913
  meta: { model: modelMeta.name }
@@ -6765,6 +8034,17 @@ function createDelegate(params) {
6765
8034
  const meta = getModelMeta({ meta: engine.meta, model });
6766
8035
  return meta.isView ? readOnlyDelegate({ delegate, model: meta.name }) : delegate;
6767
8036
  }
8037
+ var WRITE_METHODS = ["create", "createMany", "createManyAndReturn", "update", "updateMany", "upsert"];
8038
+ function withWriteValidation(params) {
8039
+ const { engine, delegate, model, validators } = params;
8040
+ const meta = getModelMeta({ meta: engine.meta, model });
8041
+ const wrapped = { ...delegate };
8042
+ for (const method of WRITE_METHODS) {
8043
+ const original = delegate[method];
8044
+ wrapped[method] = (args) => original(applyWriteValidators({ meta: engine.meta, model: meta, method, args, validators }));
8045
+ }
8046
+ return wrapped;
8047
+ }
6768
8048
  function buildRawStatement(params) {
6769
8049
  const { dialect, strings, values } = params;
6770
8050
  const collector = new ParamCollector(dialect);
@@ -6778,7 +8058,11 @@ function buildClient(params) {
6778
8058
  const { engine } = params;
6779
8059
  const models = {};
6780
8060
  for (const model of engine.meta.models) {
6781
- const delegate = createDelegate({ engine, model: model.name });
8061
+ let delegate = createDelegate({ engine, model: model.name });
8062
+ const validators = engine.options?.validate;
8063
+ if (validators?.[model.name] !== undefined) {
8064
+ delegate = withWriteValidation({ engine, delegate, model: model.name, validators });
8065
+ }
6782
8066
  models[model.name] = delegate;
6783
8067
  models[model.clientName] = delegate;
6784
8068
  }
@@ -6800,7 +8084,7 @@ function buildClient(params) {
6800
8084
  let claimError = null;
6801
8085
  for (const operation of operations) {
6802
8086
  if (!(operation instanceof QueryPromise)) {
6803
- claimError = new VibeError11({
8087
+ claimError = new VibeError14({
6804
8088
  code: "VIBE_VALIDATION",
6805
8089
  message: "$transaction([...]) takes query promises from model delegate calls \u2014 a foreign promise is already running and cannot join the transaction"
6806
8090
  });
@@ -6839,7 +8123,7 @@ function buildClient(params) {
6839
8123
  $models: models,
6840
8124
  $on: (event, listener) => {
6841
8125
  if (event !== "query") {
6842
- throw new VibeError11({
8126
+ throw new VibeError14({
6843
8127
  code: "VIBE_VALIDATION",
6844
8128
  message: `unknown client event "${String(event)}" \u2014 supported: "query"`,
6845
8129
  meta: { event }
@@ -6873,6 +8157,8 @@ function buildClient(params) {
6873
8157
  }
6874
8158
  attachExtensionMounts({ engine, client, models });
6875
8159
  attachDefineView({ client, meta: engine.meta, models });
8160
+ attachScoped({ client, meta: engine.meta });
8161
+ attachPolicy({ client, meta: engine.meta });
6876
8162
  return client;
6877
8163
  }
6878
8164
  function makeMountContext(params) {
@@ -6902,7 +8188,7 @@ function makeMountContext(params) {
6902
8188
  compileWhere: ({ where, model: modelName, qualify }) => {
6903
8189
  const target = modelName === undefined ? model : getModelMeta({ meta: engine.meta, model: modelName });
6904
8190
  if (target === null) {
6905
- throw new VibeError11({
8191
+ throw new VibeError14({
6906
8192
  code: "VIBE_INTERNAL",
6907
8193
  message: `extension "${extension}" compiled a where with no model in scope`,
6908
8194
  meta: { extension }
@@ -6928,7 +8214,7 @@ function attachExtensionMounts(params) {
6928
8214
  const model = getModelMeta({ meta: engine.meta, model: mount.model ?? "" });
6929
8215
  const delegate = models[model.name];
6930
8216
  if (delegate === undefined || mount.property in delegate) {
6931
- throw new VibeError11({
8217
+ throw new VibeError14({
6932
8218
  code: "VIBE_CONFIG",
6933
8219
  message: `extension "${mount.extension}" cannot mount "${mount.property}" on ${model.name} \u2014 the name is taken`,
6934
8220
  meta: { extension: mount.extension, model: model.name, property: mount.property }
@@ -6939,7 +8225,7 @@ function attachExtensionMounts(params) {
6939
8225
  continue;
6940
8226
  }
6941
8227
  if (!mount.property.startsWith("$") || mount.property in client) {
6942
- throw new VibeError11({
8228
+ throw new VibeError14({
6943
8229
  code: "VIBE_CONFIG",
6944
8230
  message: `extension "${mount.extension}" cannot mount client property "${mount.property}" \u2014 root mounts are $-prefixed and must not collide`,
6945
8231
  meta: { extension: mount.extension, property: mount.property }
@@ -6959,7 +8245,7 @@ function createClient(params) {
6959
8245
  assertComputedManifest({ manifest: computed.manifest, specs: computed.specs });
6960
8246
  }
6961
8247
  if (computed === undefined && options !== undefined && options["computed"] !== undefined) {
6962
- throw new VibeError11({
8248
+ throw new VibeError14({
6963
8249
  code: "VIBE_CONFIG",
6964
8250
  message: "this client was generated without computed fields \u2014 declare them in vibeorm.config.ts and re-run vibeorm generate",
6965
8251
  meta: {}
@@ -6987,9 +8273,12 @@ function createClient(params) {
6987
8273
  });
6988
8274
  }
6989
8275
  export {
8276
+ validateWriteData,
6990
8277
  updateWithNested,
6991
8278
  toSqlExecutor,
6992
8279
  toClientName,
8280
+ rewriteScopedCall,
8281
+ resolveScopedModels,
6993
8282
  resolveResultShape,
6994
8283
  resolveRelationSelection,
6995
8284
  resolveRelationLink,
@@ -7013,6 +8302,7 @@ export {
7013
8302
  dbNow,
7014
8303
  createWithNested,
7015
8304
  createView,
8305
+ createScopeRewriter,
7016
8306
  createClient,
7017
8307
  compileWhere,
7018
8308
  compileSelect,
@@ -7021,11 +8311,14 @@ export {
7021
8311
  buildQuery,
7022
8312
  buildExtensionOperatorMap,
7023
8313
  buildConnectHooks,
8314
+ attachScoped,
7024
8315
  attachRelations,
8316
+ attachPolicy,
7025
8317
  attachLateralRelations,
7026
8318
  attachDefineView,
7027
8319
  assertExtensionManifest,
7028
8320
  assertComputedManifest,
8321
+ applyWriteValidators,
7029
8322
  applyComputedFields,
7030
8323
  ViewResult,
7031
8324
  SQLITE_CODECS,
@@ -7035,4 +8328,4 @@ export {
7035
8328
  COUNT_ALIAS
7036
8329
  };
7037
8330
 
7038
- //# debugId=96AA73705B84E72364756E2164756E21
8331
+ //# debugId=AB87792190B87F3664756E2164756E21