@vibeorm/runtime 2.0.0-alpha.6 → 2.0.0-alpha.8
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/adapter.d.ts +30 -2
- package/dist/adapter.d.ts.map +1 -1
- package/dist/client.d.ts +17 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +381 -19
- package/dist/index.js.map +9 -7
- 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/render-cache.d.ts.map +1 -1
- package/dist/validators.d.ts +59 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/views.d.ts.map +1 -1
- package/package.json +3 -3
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,
|
|
@@ -191,6 +192,11 @@ function buildModelMeta(params) {
|
|
|
191
192
|
};
|
|
192
193
|
if (primaryKey.length > 1) {
|
|
193
194
|
addCompound(primaryKey.map((field) => field.name), false, model.primaryKey?.clientName);
|
|
195
|
+
} else {
|
|
196
|
+
const only = primaryKey[0];
|
|
197
|
+
if (only !== undefined && !only.isId && !only.isUnique) {
|
|
198
|
+
singleIndexUniques.push({ fields: [only], nullsNotDistinct: false });
|
|
199
|
+
}
|
|
194
200
|
}
|
|
195
201
|
for (const index of model.indexes) {
|
|
196
202
|
if (index.kind !== "unique" || index.where !== undefined)
|
|
@@ -226,6 +232,7 @@ function buildModelMeta(params) {
|
|
|
226
232
|
singleIndexUniques,
|
|
227
233
|
relations: model.relations,
|
|
228
234
|
relationByName: new Map(model.relations.map((relation) => [relation.name, relation])),
|
|
235
|
+
...model.policy === undefined ? {} : { policy: model.policy },
|
|
229
236
|
isView: model.isView,
|
|
230
237
|
computed
|
|
231
238
|
};
|
|
@@ -1435,6 +1442,7 @@ function compileOperatorFilter(params) {
|
|
|
1435
1442
|
}
|
|
1436
1443
|
const insensitive = mode === "insensitive";
|
|
1437
1444
|
const strict = mode === "strict";
|
|
1445
|
+
const ciCollated = (params2) => params2.dialect.name === "mysql" && params2.field.collation?.toLowerCase().endsWith("_ci") === true;
|
|
1438
1446
|
const requireStringOperand = (operator, operand) => {
|
|
1439
1447
|
if (typeof operand !== "string") {
|
|
1440
1448
|
throw validationError2({
|
|
@@ -1462,7 +1470,8 @@ function compileOperatorFilter(params) {
|
|
|
1462
1470
|
kind: "like",
|
|
1463
1471
|
column,
|
|
1464
1472
|
pattern: escapeLikePattern({ raw: requireStringOperand("equals", operand) }),
|
|
1465
|
-
insensitive: true
|
|
1473
|
+
insensitive: true,
|
|
1474
|
+
...ciCollated({ dialect, field }) ? { ciCollation: true } : {}
|
|
1466
1475
|
});
|
|
1467
1476
|
break;
|
|
1468
1477
|
}
|
|
@@ -1559,7 +1568,8 @@ function compileOperatorFilter(params) {
|
|
|
1559
1568
|
kind: "like",
|
|
1560
1569
|
column,
|
|
1561
1570
|
pattern: compileStringPattern({ operator, operand, field, model }),
|
|
1562
|
-
insensitive
|
|
1571
|
+
insensitive,
|
|
1572
|
+
...insensitive && ciCollated({ dialect, field }) ? { ciCollation: true } : {}
|
|
1563
1573
|
});
|
|
1564
1574
|
break;
|
|
1565
1575
|
}
|
|
@@ -5500,8 +5510,341 @@ function attachDefineView(params) {
|
|
|
5500
5510
|
const value = (options) => createView({ meta, models, name: options.name, definition: options.definition });
|
|
5501
5511
|
Object.defineProperty(client, "$defineView", { value, enumerable: true });
|
|
5502
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
|
+
}
|
|
5503
5846
|
// packages/runtime/src/client.ts
|
|
5504
|
-
import { VibeError as
|
|
5847
|
+
import { VibeError as VibeError13, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
|
|
5505
5848
|
import { ParamCollector, getSqlDialect } from "@vibeorm/sql";
|
|
5506
5849
|
|
|
5507
5850
|
// packages/runtime/src/render-cache.ts
|
|
@@ -5951,7 +6294,7 @@ function renderStatementCached(params) {
|
|
|
5951
6294
|
// packages/runtime/src/client.ts
|
|
5952
6295
|
function notFound2(params) {
|
|
5953
6296
|
const { model, method } = params;
|
|
5954
|
-
return new
|
|
6297
|
+
return new VibeError13({
|
|
5955
6298
|
code: "VIBE_NOT_FOUND",
|
|
5956
6299
|
message: `no ${model} record matched the where clause of ${method}()`,
|
|
5957
6300
|
meta: { model, method }
|
|
@@ -5959,7 +6302,7 @@ function notFound2(params) {
|
|
|
5959
6302
|
}
|
|
5960
6303
|
function viewIsReadOnly(params) {
|
|
5961
6304
|
const { model, method } = params;
|
|
5962
|
-
return new
|
|
6305
|
+
return new VibeError13({
|
|
5963
6306
|
code: "VIBE_VALIDATION",
|
|
5964
6307
|
message: `${model} is a view \u2014 ${method}() is not available on views (they are read-only); write to the underlying table instead`,
|
|
5965
6308
|
meta: { model, method }
|
|
@@ -6088,7 +6431,7 @@ class QueryPromise extends Promise {
|
|
|
6088
6431
|
}
|
|
6089
6432
|
claim() {
|
|
6090
6433
|
if (this.started) {
|
|
6091
|
-
throw new
|
|
6434
|
+
throw new VibeError13({
|
|
6092
6435
|
code: "VIBE_VALIDATION",
|
|
6093
6436
|
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)"
|
|
6094
6437
|
});
|
|
@@ -6166,7 +6509,7 @@ async function runAffected(params) {
|
|
|
6166
6509
|
function plan(params) {
|
|
6167
6510
|
const { engine, model, method, args } = params;
|
|
6168
6511
|
if (args !== undefined && args["lock"] !== undefined && engine.inTransaction !== true) {
|
|
6169
|
-
throw new
|
|
6512
|
+
throw new VibeError13({
|
|
6170
6513
|
code: "VIBE_VALIDATION",
|
|
6171
6514
|
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)`,
|
|
6172
6515
|
meta: { model, method }
|
|
@@ -6239,7 +6582,7 @@ async function insertedKeyWhere(params) {
|
|
|
6239
6582
|
});
|
|
6240
6583
|
const id = result.rows[0]?.id;
|
|
6241
6584
|
if (id === undefined || id === null) {
|
|
6242
|
-
throw new
|
|
6585
|
+
throw new VibeError13({
|
|
6243
6586
|
code: "VIBE_INTERNAL",
|
|
6244
6587
|
message: `could not read back the generated key for ${source.model.name}`,
|
|
6245
6588
|
meta: { model: source.model.name }
|
|
@@ -6541,7 +6884,7 @@ function createDelegate(params) {
|
|
|
6541
6884
|
const skipping = args.onConflict !== undefined;
|
|
6542
6885
|
if (hasNestedWrites({ model: modelMeta, data: args.data })) {
|
|
6543
6886
|
if (skipping) {
|
|
6544
|
-
throw new
|
|
6887
|
+
throw new VibeError13({
|
|
6545
6888
|
code: "VIBE_VALIDATION",
|
|
6546
6889
|
message: `onConflict cannot combine with nested writes on ${modelMeta.name}.create() \u2014 insert the parent row alone, then connect relations`,
|
|
6547
6890
|
meta: { model: modelMeta.name }
|
|
@@ -6625,7 +6968,7 @@ function createDelegate(params) {
|
|
|
6625
6968
|
const upsert = async (target, args) => {
|
|
6626
6969
|
const modelMeta = getModelMeta({ meta: target.meta, model });
|
|
6627
6970
|
if (hasNestedWrites({ model: modelMeta, data: args.create }) || hasNestedWrites({ model: modelMeta, data: args.update })) {
|
|
6628
|
-
throw new
|
|
6971
|
+
throw new VibeError13({
|
|
6629
6972
|
code: "VIBE_VALIDATION",
|
|
6630
6973
|
message: `nested writes are not supported inside upsert arms on ${modelMeta.name} \u2014 use create()/update() with nested data`,
|
|
6631
6974
|
meta: { model: modelMeta.name }
|
|
@@ -6633,7 +6976,7 @@ function createDelegate(params) {
|
|
|
6633
6976
|
}
|
|
6634
6977
|
if (!upsertUsesNativeStatement({ model: modelMeta, args })) {
|
|
6635
6978
|
if (args.onConflict !== undefined) {
|
|
6636
|
-
throw new
|
|
6979
|
+
throw new VibeError13({
|
|
6637
6980
|
code: "VIBE_VALIDATION",
|
|
6638
6981
|
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`,
|
|
6639
6982
|
meta: { model: modelMeta.name }
|
|
@@ -6760,6 +7103,17 @@ function createDelegate(params) {
|
|
|
6760
7103
|
const meta = getModelMeta({ meta: engine.meta, model });
|
|
6761
7104
|
return meta.isView ? readOnlyDelegate({ delegate, model: meta.name }) : delegate;
|
|
6762
7105
|
}
|
|
7106
|
+
var WRITE_METHODS = ["create", "createMany", "createManyAndReturn", "update", "updateMany", "upsert"];
|
|
7107
|
+
function withWriteValidation(params) {
|
|
7108
|
+
const { engine, delegate, model, validators } = params;
|
|
7109
|
+
const meta = getModelMeta({ meta: engine.meta, model });
|
|
7110
|
+
const wrapped = { ...delegate };
|
|
7111
|
+
for (const method of WRITE_METHODS) {
|
|
7112
|
+
const original = delegate[method];
|
|
7113
|
+
wrapped[method] = (args) => original(applyWriteValidators({ meta: engine.meta, model: meta, method, args, validators }));
|
|
7114
|
+
}
|
|
7115
|
+
return wrapped;
|
|
7116
|
+
}
|
|
6763
7117
|
function buildRawStatement(params) {
|
|
6764
7118
|
const { dialect, strings, values } = params;
|
|
6765
7119
|
const collector = new ParamCollector(dialect);
|
|
@@ -6773,7 +7127,11 @@ function buildClient(params) {
|
|
|
6773
7127
|
const { engine } = params;
|
|
6774
7128
|
const models = {};
|
|
6775
7129
|
for (const model of engine.meta.models) {
|
|
6776
|
-
|
|
7130
|
+
let delegate = createDelegate({ engine, model: model.name });
|
|
7131
|
+
const validators = engine.options?.validate;
|
|
7132
|
+
if (validators?.[model.name] !== undefined) {
|
|
7133
|
+
delegate = withWriteValidation({ engine, delegate, model: model.name, validators });
|
|
7134
|
+
}
|
|
6777
7135
|
models[model.name] = delegate;
|
|
6778
7136
|
models[model.clientName] = delegate;
|
|
6779
7137
|
}
|
|
@@ -6795,7 +7153,7 @@ function buildClient(params) {
|
|
|
6795
7153
|
let claimError = null;
|
|
6796
7154
|
for (const operation of operations) {
|
|
6797
7155
|
if (!(operation instanceof QueryPromise)) {
|
|
6798
|
-
claimError = new
|
|
7156
|
+
claimError = new VibeError13({
|
|
6799
7157
|
code: "VIBE_VALIDATION",
|
|
6800
7158
|
message: "$transaction([...]) takes query promises from model delegate calls \u2014 a foreign promise is already running and cannot join the transaction"
|
|
6801
7159
|
});
|
|
@@ -6834,7 +7192,7 @@ function buildClient(params) {
|
|
|
6834
7192
|
$models: models,
|
|
6835
7193
|
$on: (event, listener) => {
|
|
6836
7194
|
if (event !== "query") {
|
|
6837
|
-
throw new
|
|
7195
|
+
throw new VibeError13({
|
|
6838
7196
|
code: "VIBE_VALIDATION",
|
|
6839
7197
|
message: `unknown client event "${String(event)}" \u2014 supported: "query"`,
|
|
6840
7198
|
meta: { event }
|
|
@@ -6868,6 +7226,7 @@ function buildClient(params) {
|
|
|
6868
7226
|
}
|
|
6869
7227
|
attachExtensionMounts({ engine, client, models });
|
|
6870
7228
|
attachDefineView({ client, meta: engine.meta, models });
|
|
7229
|
+
attachPolicy({ client, meta: engine.meta });
|
|
6871
7230
|
return client;
|
|
6872
7231
|
}
|
|
6873
7232
|
function makeMountContext(params) {
|
|
@@ -6897,7 +7256,7 @@ function makeMountContext(params) {
|
|
|
6897
7256
|
compileWhere: ({ where, model: modelName, qualify }) => {
|
|
6898
7257
|
const target = modelName === undefined ? model : getModelMeta({ meta: engine.meta, model: modelName });
|
|
6899
7258
|
if (target === null) {
|
|
6900
|
-
throw new
|
|
7259
|
+
throw new VibeError13({
|
|
6901
7260
|
code: "VIBE_INTERNAL",
|
|
6902
7261
|
message: `extension "${extension}" compiled a where with no model in scope`,
|
|
6903
7262
|
meta: { extension }
|
|
@@ -6923,7 +7282,7 @@ function attachExtensionMounts(params) {
|
|
|
6923
7282
|
const model = getModelMeta({ meta: engine.meta, model: mount.model ?? "" });
|
|
6924
7283
|
const delegate = models[model.name];
|
|
6925
7284
|
if (delegate === undefined || mount.property in delegate) {
|
|
6926
|
-
throw new
|
|
7285
|
+
throw new VibeError13({
|
|
6927
7286
|
code: "VIBE_CONFIG",
|
|
6928
7287
|
message: `extension "${mount.extension}" cannot mount "${mount.property}" on ${model.name} \u2014 the name is taken`,
|
|
6929
7288
|
meta: { extension: mount.extension, model: model.name, property: mount.property }
|
|
@@ -6934,7 +7293,7 @@ function attachExtensionMounts(params) {
|
|
|
6934
7293
|
continue;
|
|
6935
7294
|
}
|
|
6936
7295
|
if (!mount.property.startsWith("$") || mount.property in client) {
|
|
6937
|
-
throw new
|
|
7296
|
+
throw new VibeError13({
|
|
6938
7297
|
code: "VIBE_CONFIG",
|
|
6939
7298
|
message: `extension "${mount.extension}" cannot mount client property "${mount.property}" \u2014 root mounts are $-prefixed and must not collide`,
|
|
6940
7299
|
meta: { extension: mount.extension, property: mount.property }
|
|
@@ -6954,7 +7313,7 @@ function createClient(params) {
|
|
|
6954
7313
|
assertComputedManifest({ manifest: computed.manifest, specs: computed.specs });
|
|
6955
7314
|
}
|
|
6956
7315
|
if (computed === undefined && options !== undefined && options["computed"] !== undefined) {
|
|
6957
|
-
throw new
|
|
7316
|
+
throw new VibeError13({
|
|
6958
7317
|
code: "VIBE_CONFIG",
|
|
6959
7318
|
message: "this client was generated without computed fields \u2014 declare them in vibeorm.config.ts and re-run vibeorm generate",
|
|
6960
7319
|
meta: {}
|
|
@@ -6982,6 +7341,7 @@ function createClient(params) {
|
|
|
6982
7341
|
});
|
|
6983
7342
|
}
|
|
6984
7343
|
export {
|
|
7344
|
+
validateWriteData,
|
|
6985
7345
|
updateWithNested,
|
|
6986
7346
|
toSqlExecutor,
|
|
6987
7347
|
toClientName,
|
|
@@ -7017,10 +7377,12 @@ export {
|
|
|
7017
7377
|
buildExtensionOperatorMap,
|
|
7018
7378
|
buildConnectHooks,
|
|
7019
7379
|
attachRelations,
|
|
7380
|
+
attachPolicy,
|
|
7020
7381
|
attachLateralRelations,
|
|
7021
7382
|
attachDefineView,
|
|
7022
7383
|
assertExtensionManifest,
|
|
7023
7384
|
assertComputedManifest,
|
|
7385
|
+
applyWriteValidators,
|
|
7024
7386
|
applyComputedFields,
|
|
7025
7387
|
ViewResult,
|
|
7026
7388
|
SQLITE_CODECS,
|
|
@@ -7030,4 +7392,4 @@ export {
|
|
|
7030
7392
|
COUNT_ALIAS
|
|
7031
7393
|
};
|
|
7032
7394
|
|
|
7033
|
-
//# debugId=
|
|
7395
|
+
//# debugId=EC61C00B9601D9AD64756E2164756E21
|