@vibeorm/runtime 2.0.0-alpha.8 → 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
@@ -5843,8 +5843,939 @@ function attachPolicy(params) {
5843
5843
  enumerable: true
5844
5844
  });
5845
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
+ }
5846
6777
  // packages/runtime/src/client.ts
5847
- import { VibeError as VibeError13, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
6778
+ import { VibeError as VibeError14, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
5848
6779
  import { ParamCollector, getSqlDialect } from "@vibeorm/sql";
5849
6780
 
5850
6781
  // packages/runtime/src/render-cache.ts
@@ -6031,7 +6962,7 @@ function walkExpr(walk, dialect, expr) {
6031
6962
  return true;
6032
6963
  }
6033
6964
  }
6034
- function walkOrderTerm(walk, dialect, term) {
6965
+ function walkOrderTerm2(walk, dialect, term) {
6035
6966
  if ("agg" in term) {
6036
6967
  walk.key += "a";
6037
6968
  addAgg(walk, term.agg);
@@ -6081,7 +7012,7 @@ function walkSelect(walk, dialect, statement) {
6081
7012
  addCol(walk, column);
6082
7013
  walk.key += ":";
6083
7014
  for (const term of statement.rowNumber.orderBy) {
6084
- if (!walkOrderTerm(walk, dialect, term))
7015
+ if (!walkOrderTerm2(walk, dialect, term))
6085
7016
  return false;
6086
7017
  }
6087
7018
  addIdent(walk, statement.rowNumber.alias);
@@ -6136,7 +7067,7 @@ function walkSelect(walk, dialect, statement) {
6136
7067
  if (statement.orderBy !== undefined && statement.orderBy.length > 0) {
6137
7068
  walk.key += "o(";
6138
7069
  for (const term of statement.orderBy) {
6139
- if (!walkOrderTerm(walk, dialect, term))
7070
+ if (!walkOrderTerm2(walk, dialect, term))
6140
7071
  return false;
6141
7072
  }
6142
7073
  walk.key += ")";
@@ -6294,7 +7225,7 @@ function renderStatementCached(params) {
6294
7225
  // packages/runtime/src/client.ts
6295
7226
  function notFound2(params) {
6296
7227
  const { model, method } = params;
6297
- return new VibeError13({
7228
+ return new VibeError14({
6298
7229
  code: "VIBE_NOT_FOUND",
6299
7230
  message: `no ${model} record matched the where clause of ${method}()`,
6300
7231
  meta: { model, method }
@@ -6302,7 +7233,7 @@ function notFound2(params) {
6302
7233
  }
6303
7234
  function viewIsReadOnly(params) {
6304
7235
  const { model, method } = params;
6305
- return new VibeError13({
7236
+ return new VibeError14({
6306
7237
  code: "VIBE_VALIDATION",
6307
7238
  message: `${model} is a view \u2014 ${method}() is not available on views (they are read-only); write to the underlying table instead`,
6308
7239
  meta: { model, method }
@@ -6431,7 +7362,7 @@ class QueryPromise extends Promise {
6431
7362
  }
6432
7363
  claim() {
6433
7364
  if (this.started) {
6434
- throw new VibeError13({
7365
+ throw new VibeError14({
6435
7366
  code: "VIBE_VALIDATION",
6436
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)"
6437
7368
  });
@@ -6509,7 +7440,7 @@ async function runAffected(params) {
6509
7440
  function plan(params) {
6510
7441
  const { engine, model, method, args } = params;
6511
7442
  if (args !== undefined && args["lock"] !== undefined && engine.inTransaction !== true) {
6512
- throw new VibeError13({
7443
+ throw new VibeError14({
6513
7444
  code: "VIBE_VALIDATION",
6514
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)`,
6515
7446
  meta: { model, method }
@@ -6582,7 +7513,7 @@ async function insertedKeyWhere(params) {
6582
7513
  });
6583
7514
  const id = result.rows[0]?.id;
6584
7515
  if (id === undefined || id === null) {
6585
- throw new VibeError13({
7516
+ throw new VibeError14({
6586
7517
  code: "VIBE_INTERNAL",
6587
7518
  message: `could not read back the generated key for ${source.model.name}`,
6588
7519
  meta: { model: source.model.name }
@@ -6884,7 +7815,7 @@ function createDelegate(params) {
6884
7815
  const skipping = args.onConflict !== undefined;
6885
7816
  if (hasNestedWrites({ model: modelMeta, data: args.data })) {
6886
7817
  if (skipping) {
6887
- throw new VibeError13({
7818
+ throw new VibeError14({
6888
7819
  code: "VIBE_VALIDATION",
6889
7820
  message: `onConflict cannot combine with nested writes on ${modelMeta.name}.create() \u2014 insert the parent row alone, then connect relations`,
6890
7821
  meta: { model: modelMeta.name }
@@ -6968,7 +7899,7 @@ function createDelegate(params) {
6968
7899
  const upsert = async (target, args) => {
6969
7900
  const modelMeta = getModelMeta({ meta: target.meta, model });
6970
7901
  if (hasNestedWrites({ model: modelMeta, data: args.create }) || hasNestedWrites({ model: modelMeta, data: args.update })) {
6971
- throw new VibeError13({
7902
+ throw new VibeError14({
6972
7903
  code: "VIBE_VALIDATION",
6973
7904
  message: `nested writes are not supported inside upsert arms on ${modelMeta.name} \u2014 use create()/update() with nested data`,
6974
7905
  meta: { model: modelMeta.name }
@@ -6976,7 +7907,7 @@ function createDelegate(params) {
6976
7907
  }
6977
7908
  if (!upsertUsesNativeStatement({ model: modelMeta, args })) {
6978
7909
  if (args.onConflict !== undefined) {
6979
- throw new VibeError13({
7910
+ throw new VibeError14({
6980
7911
  code: "VIBE_VALIDATION",
6981
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`,
6982
7913
  meta: { model: modelMeta.name }
@@ -7153,7 +8084,7 @@ function buildClient(params) {
7153
8084
  let claimError = null;
7154
8085
  for (const operation of operations) {
7155
8086
  if (!(operation instanceof QueryPromise)) {
7156
- claimError = new VibeError13({
8087
+ claimError = new VibeError14({
7157
8088
  code: "VIBE_VALIDATION",
7158
8089
  message: "$transaction([...]) takes query promises from model delegate calls \u2014 a foreign promise is already running and cannot join the transaction"
7159
8090
  });
@@ -7192,7 +8123,7 @@ function buildClient(params) {
7192
8123
  $models: models,
7193
8124
  $on: (event, listener) => {
7194
8125
  if (event !== "query") {
7195
- throw new VibeError13({
8126
+ throw new VibeError14({
7196
8127
  code: "VIBE_VALIDATION",
7197
8128
  message: `unknown client event "${String(event)}" \u2014 supported: "query"`,
7198
8129
  meta: { event }
@@ -7226,6 +8157,7 @@ function buildClient(params) {
7226
8157
  }
7227
8158
  attachExtensionMounts({ engine, client, models });
7228
8159
  attachDefineView({ client, meta: engine.meta, models });
8160
+ attachScoped({ client, meta: engine.meta });
7229
8161
  attachPolicy({ client, meta: engine.meta });
7230
8162
  return client;
7231
8163
  }
@@ -7256,7 +8188,7 @@ function makeMountContext(params) {
7256
8188
  compileWhere: ({ where, model: modelName, qualify }) => {
7257
8189
  const target = modelName === undefined ? model : getModelMeta({ meta: engine.meta, model: modelName });
7258
8190
  if (target === null) {
7259
- throw new VibeError13({
8191
+ throw new VibeError14({
7260
8192
  code: "VIBE_INTERNAL",
7261
8193
  message: `extension "${extension}" compiled a where with no model in scope`,
7262
8194
  meta: { extension }
@@ -7282,7 +8214,7 @@ function attachExtensionMounts(params) {
7282
8214
  const model = getModelMeta({ meta: engine.meta, model: mount.model ?? "" });
7283
8215
  const delegate = models[model.name];
7284
8216
  if (delegate === undefined || mount.property in delegate) {
7285
- throw new VibeError13({
8217
+ throw new VibeError14({
7286
8218
  code: "VIBE_CONFIG",
7287
8219
  message: `extension "${mount.extension}" cannot mount "${mount.property}" on ${model.name} \u2014 the name is taken`,
7288
8220
  meta: { extension: mount.extension, model: model.name, property: mount.property }
@@ -7293,7 +8225,7 @@ function attachExtensionMounts(params) {
7293
8225
  continue;
7294
8226
  }
7295
8227
  if (!mount.property.startsWith("$") || mount.property in client) {
7296
- throw new VibeError13({
8228
+ throw new VibeError14({
7297
8229
  code: "VIBE_CONFIG",
7298
8230
  message: `extension "${mount.extension}" cannot mount client property "${mount.property}" \u2014 root mounts are $-prefixed and must not collide`,
7299
8231
  meta: { extension: mount.extension, property: mount.property }
@@ -7313,7 +8245,7 @@ function createClient(params) {
7313
8245
  assertComputedManifest({ manifest: computed.manifest, specs: computed.specs });
7314
8246
  }
7315
8247
  if (computed === undefined && options !== undefined && options["computed"] !== undefined) {
7316
- throw new VibeError13({
8248
+ throw new VibeError14({
7317
8249
  code: "VIBE_CONFIG",
7318
8250
  message: "this client was generated without computed fields \u2014 declare them in vibeorm.config.ts and re-run vibeorm generate",
7319
8251
  meta: {}
@@ -7345,6 +8277,8 @@ export {
7345
8277
  updateWithNested,
7346
8278
  toSqlExecutor,
7347
8279
  toClientName,
8280
+ rewriteScopedCall,
8281
+ resolveScopedModels,
7348
8282
  resolveResultShape,
7349
8283
  resolveRelationSelection,
7350
8284
  resolveRelationLink,
@@ -7368,6 +8302,7 @@ export {
7368
8302
  dbNow,
7369
8303
  createWithNested,
7370
8304
  createView,
8305
+ createScopeRewriter,
7371
8306
  createClient,
7372
8307
  compileWhere,
7373
8308
  compileSelect,
@@ -7376,6 +8311,7 @@ export {
7376
8311
  buildQuery,
7377
8312
  buildExtensionOperatorMap,
7378
8313
  buildConnectHooks,
8314
+ attachScoped,
7379
8315
  attachRelations,
7380
8316
  attachPolicy,
7381
8317
  attachLateralRelations,
@@ -7392,4 +8328,4 @@ export {
7392
8328
  COUNT_ALIAS
7393
8329
  };
7394
8330
 
7395
- //# debugId=EC61C00B9601D9AD64756E2164756E21
8331
+ //# debugId=AB87792190B87F3664756E2164756E21