@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/client.d.ts +24 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1315 -22
- package/dist/index.js.map +9 -6
- package/dist/model-meta.d.ts +7 -0
- package/dist/model-meta.d.ts.map +1 -1
- package/dist/policy.d.ts +40 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/query-builder.d.ts.map +1 -1
- package/dist/scoped.d.ts +131 -0
- package/dist/scoped.d.ts.map +1 -0
- package/dist/validators.d.ts +59 -0
- package/dist/validators.d.ts.map +1 -0
- package/package.json +5 -4
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
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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=
|
|
8331
|
+
//# debugId=AB87792190B87F3664756E2164756E21
|