@atscript/db 0.1.151 → 0.1.153
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/agg.cjs +1 -1
- package/dist/agg.d.cts +1 -1
- package/dist/agg.d.mts +1 -1
- package/dist/agg.mjs +1 -1
- package/dist/{aggregate-fns-CyaZyb9I.mjs → aggregate-fns-B_sgl4mB.mjs} +1 -1
- package/dist/{aggregate-fns-C-UJRobm.cjs → aggregate-fns-DFtKAv7Q.cjs} +1 -1
- package/dist/{buckets-Cb0zmhDc.d.mts → buckets-CBKL_ZWE.d.mts} +205 -7
- package/dist/{buckets-tzd8iIM2.d.cts → buckets-jy8BfswO.d.cts} +205 -7
- package/dist/{column-diff-CU7KHj9-.cjs → column-diff-CbdWi_uZ.cjs} +729 -28
- package/dist/{column-diff-CnyLjbQ8.mjs → column-diff-DrE4ho_m.mjs} +730 -29
- package/dist/{db-error-DRQH4sLY.cjs → db-error-JcfJqqpk.cjs} +29 -0
- package/dist/{db-error-Az85UhTX.mjs → db-error-eSsQX7rt.mjs} +24 -1
- package/dist/{fk-diff-Bw26lwSc.d.cts → fk-diff-BIJ8l_hF.d.mts} +1 -1
- package/dist/{fk-diff-lcAnwvm9.d.mts → fk-diff-D1mrLMjU.d.cts} +1 -1
- package/dist/index.cjs +93 -5
- package/dist/index.d.cts +29 -5
- package/dist/index.d.mts +29 -5
- package/dist/index.mjs +92 -6
- package/dist/{nested-writer-cvbVAkNo.mjs → nested-writer-BOj_c_lI.mjs} +1 -1
- package/dist/{nested-writer-BzbOPSUp.cjs → nested-writer-Bl1SZS7x.cjs} +1 -1
- package/dist/ops.cjs +1 -1
- package/dist/ops.mjs +1 -1
- package/dist/plugin.cjs +48 -1
- package/dist/plugin.mjs +48 -1
- package/dist/rel.cjs +2 -2
- package/dist/rel.d.cts +2 -2
- package/dist/rel.d.mts +2 -2
- package/dist/rel.mjs +2 -2
- package/dist/{relation-helpers-Bdo4NHPC.d.mts → relation-helpers-BUsxNNGU.d.mts} +1 -1
- package/dist/{relation-helpers-DxIZKvnJ.d.cts → relation-helpers-D_8bydc2.d.cts} +1 -1
- package/dist/{relation-loader-DYab07lw.mjs → relation-loader-CNBMPfRr.mjs} +2 -1
- package/dist/{relation-loader-fNjHgBMs.cjs → relation-loader-DeulLrAw.cjs} +2 -1
- package/dist/sync.cjs +2 -2
- package/dist/sync.d.cts +2 -2
- package/dist/sync.d.mts +2 -2
- package/dist/sync.mjs +2 -2
- package/dist/{validator-tBNvM1qc.d.cts → validator-B3xlbrWf.d.cts} +36 -2
- package/dist/{validator-tBNvM1qc.d.mts → validator-B3xlbrWf.d.mts} +36 -2
- package/dist/{validator-BTiIOTKP.mjs → validator-BACewoRj.mjs} +51 -7
- package/dist/{validator-UcuJxHNT.cjs → validator-BxWwh3wf.cjs} +50 -6
- package/dist/validator.cjs +2 -2
- package/dist/validator.d.cts +1 -1
- package/dist/validator.d.mts +1 -1
- package/dist/validator.mjs +2 -2
- package/package.json +8 -8
package/dist/plugin.cjs
CHANGED
|
@@ -33,7 +33,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
33
|
enumerable: true
|
|
34
34
|
}) : target, mod));
|
|
35
35
|
//#endregion
|
|
36
|
-
const require_aggregate_fns = require("./aggregate-fns-
|
|
36
|
+
const require_aggregate_fns = require("./aggregate-fns-DFtKAv7Q.cjs");
|
|
37
37
|
const require_search_fields = require("./search-fields-DwrsJUC1.cjs");
|
|
38
38
|
require("./consts-BzRfCcH2.cjs");
|
|
39
39
|
const require_validation_utils = require("./validation-utils-debIYpq0.cjs");
|
|
@@ -1695,6 +1695,52 @@ const dbSearchAnnotations = { search: {
|
|
|
1695
1695
|
})
|
|
1696
1696
|
} };
|
|
1697
1697
|
//#endregion
|
|
1698
|
+
//#region src/plugin/annotations/sort.ts
|
|
1699
|
+
/**
|
|
1700
|
+
* `@db.sort.nulls` (since 0.1.153) — a field's default NULL placement when a
|
|
1701
|
+
* read sorts by it without a `$nulls` entry for it.
|
|
1702
|
+
*/
|
|
1703
|
+
const dbSortAnnotations = { sort: { nulls: new _atscript_core.AnnotationSpec({
|
|
1704
|
+
description: "Default **NULL placement** when a read sorts by this field and the request names none (`$nulls` control, URL suffix `$sort=-closedAt:last`).\n\n- **`'first'`** — rows where the field is NULL (or missing) come before every value, in both sort directions\n- **`'last'`** — they come after every value, in both directions\n\nWithout it (and without `$nulls`) NULL placement is the database's own: first in ascending order on SQLite, MySQL, MongoDB and the in-memory adapter, last on PostgreSQL. Applies to `$sort` and to the `$rowOrder` of `first()` / `last()`. Only optional fields can be NULL — on a required table field it has no effect. On PostgreSQL, MySQL and MongoDB a placement the engine does not produce natively costs the index its ORDER BY (a sort step instead).\n\n**Example:**\n```atscript\n@db.sort.nulls 'last'\nclosedAt?: number.timestamp\n```\n",
|
|
1705
|
+
nodeType: ["prop"],
|
|
1706
|
+
passedWhenReferred: false,
|
|
1707
|
+
multiple: false,
|
|
1708
|
+
argument: {
|
|
1709
|
+
name: "placement",
|
|
1710
|
+
type: "string",
|
|
1711
|
+
values: ["first", "last"],
|
|
1712
|
+
description: "Where NULL goes: 'first' (before every value) or 'last' (after every value), whatever the sort direction."
|
|
1713
|
+
},
|
|
1714
|
+
validate(token, _args, doc) {
|
|
1715
|
+
const errors = [];
|
|
1716
|
+
const field = token.parentNode;
|
|
1717
|
+
const fail = (message, severity = 1) => errors.push({
|
|
1718
|
+
message,
|
|
1719
|
+
severity,
|
|
1720
|
+
range: token.range
|
|
1721
|
+
});
|
|
1722
|
+
if (field.countAnnotations("db.json") > 0) {
|
|
1723
|
+
fail("@db.sort.nulls on a @db.json field — JSON-stored fields are not sortable");
|
|
1724
|
+
return errors;
|
|
1725
|
+
}
|
|
1726
|
+
if (field.countAnnotations("db.encrypted") > 0) {
|
|
1727
|
+
fail("@db.sort.nulls on a @db.encrypted field — encrypted fields are not sortable");
|
|
1728
|
+
return errors;
|
|
1729
|
+
}
|
|
1730
|
+
const def = field.getDefinition();
|
|
1731
|
+
let leaf = def;
|
|
1732
|
+
if (def && (0, _atscript_core.isRef)(def)) leaf = doc.unwindType(def.id, def.chain)?.def;
|
|
1733
|
+
const base = require_validation_utils.primitiveBaseType(leaf);
|
|
1734
|
+
if (def && ((0, _atscript_core.isArray)(def) || (0, _atscript_core.isStructure)(def)) || leaf && ((0, _atscript_core.isArray)(leaf) || (0, _atscript_core.isStructure)(leaf) || (0, _atscript_core.isInterface)(leaf)) || leaf && (0, _atscript_core.isPrimitive)(leaf) && (base === "array" || base === "object")) {
|
|
1735
|
+
fail("@db.sort.nulls needs a sortable scalar field — not an object or an array");
|
|
1736
|
+
return errors;
|
|
1737
|
+
}
|
|
1738
|
+
const owner = require_validation_utils.getDbTableOwner(token);
|
|
1739
|
+
if (!field.has("optional") && owner && owner.countAnnotations("db.table") > 0 && field.ownerNode?.ownerNode === owner) fail("@db.sort.nulls on a required field has no effect — the field is never NULL; make it optional (field?: …) or remove the annotation", 2);
|
|
1740
|
+
return errors;
|
|
1741
|
+
}
|
|
1742
|
+
}) } };
|
|
1743
|
+
//#endregion
|
|
1698
1744
|
//#region src/plugin/annotations/table.ts
|
|
1699
1745
|
const dbTableAnnotations = {
|
|
1700
1746
|
table: {
|
|
@@ -2286,6 +2332,7 @@ const dbPlugin = (options) => ({
|
|
|
2286
2332
|
agg: dbAggAnnotations.agg,
|
|
2287
2333
|
compute: dbComputeAnnotations.compute,
|
|
2288
2334
|
search: dbSearchAnnotations.search,
|
|
2335
|
+
sort: dbSortAnnotations.sort,
|
|
2289
2336
|
amount: dbAmountAnnotations.amount,
|
|
2290
2337
|
unit: dbUnitAnnotations.unit
|
|
2291
2338
|
} },
|
package/dist/plugin.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as NULL_WHEN_EMPTY_AGGREGATE_FNS, o as SUPPORTED_AGGREGATE_FNS, t as AGG_ANNOTATIONS } from "./aggregate-fns-
|
|
1
|
+
import { i as NULL_WHEN_EMPTY_AGGREGATE_FNS, o as SUPPORTED_AGGREGATE_FNS, t as AGG_ANNOTATIONS } from "./aggregate-fns-B_sgl4mB.mjs";
|
|
2
2
|
import { a as DERIVED_INCOMPATIBLE, i as DB_ENTITY_ANNOTATIONS, o as JSON_LEAF_TYPES, r as numericTypeProblem } from "./search-fields-DBIZKwyb.mjs";
|
|
3
3
|
import "./consts-C_-5_pFq.mjs";
|
|
4
4
|
import { C as validateFieldBaseType, S as validateExclusiveWith, _ as getParentTypeName, a as hasAnyViewAnnotation, b as refActionAnnotation, d as viewJoins, f as viewScopeTypes, g as getParentStruct, h as getNavTargetTypeName, i as forEachFieldRef, l as validateQueryScope, m as getDbTableOwner, n as findFKFieldsPointingTo, o as isAliasDecl, p as getAnnotationAlias, r as findViewCycle, s as isDbSourceDecl, t as earlierJoinTargets, u as validateRefArgument, v as integerMemberIndexProblem, w as validateSiblingStringField, x as searchFieldVerdict, y as primitiveBaseType } from "./validation-utils-DqZAH6aT.mjs";
|
|
@@ -1659,6 +1659,52 @@ const dbSearchAnnotations = { search: {
|
|
|
1659
1659
|
})
|
|
1660
1660
|
} };
|
|
1661
1661
|
//#endregion
|
|
1662
|
+
//#region src/plugin/annotations/sort.ts
|
|
1663
|
+
/**
|
|
1664
|
+
* `@db.sort.nulls` (since 0.1.153) — a field's default NULL placement when a
|
|
1665
|
+
* read sorts by it without a `$nulls` entry for it.
|
|
1666
|
+
*/
|
|
1667
|
+
const dbSortAnnotations = { sort: { nulls: new AnnotationSpec({
|
|
1668
|
+
description: "Default **NULL placement** when a read sorts by this field and the request names none (`$nulls` control, URL suffix `$sort=-closedAt:last`).\n\n- **`'first'`** — rows where the field is NULL (or missing) come before every value, in both sort directions\n- **`'last'`** — they come after every value, in both directions\n\nWithout it (and without `$nulls`) NULL placement is the database's own: first in ascending order on SQLite, MySQL, MongoDB and the in-memory adapter, last on PostgreSQL. Applies to `$sort` and to the `$rowOrder` of `first()` / `last()`. Only optional fields can be NULL — on a required table field it has no effect. On PostgreSQL, MySQL and MongoDB a placement the engine does not produce natively costs the index its ORDER BY (a sort step instead).\n\n**Example:**\n```atscript\n@db.sort.nulls 'last'\nclosedAt?: number.timestamp\n```\n",
|
|
1669
|
+
nodeType: ["prop"],
|
|
1670
|
+
passedWhenReferred: false,
|
|
1671
|
+
multiple: false,
|
|
1672
|
+
argument: {
|
|
1673
|
+
name: "placement",
|
|
1674
|
+
type: "string",
|
|
1675
|
+
values: ["first", "last"],
|
|
1676
|
+
description: "Where NULL goes: 'first' (before every value) or 'last' (after every value), whatever the sort direction."
|
|
1677
|
+
},
|
|
1678
|
+
validate(token, _args, doc) {
|
|
1679
|
+
const errors = [];
|
|
1680
|
+
const field = token.parentNode;
|
|
1681
|
+
const fail = (message, severity = 1) => errors.push({
|
|
1682
|
+
message,
|
|
1683
|
+
severity,
|
|
1684
|
+
range: token.range
|
|
1685
|
+
});
|
|
1686
|
+
if (field.countAnnotations("db.json") > 0) {
|
|
1687
|
+
fail("@db.sort.nulls on a @db.json field — JSON-stored fields are not sortable");
|
|
1688
|
+
return errors;
|
|
1689
|
+
}
|
|
1690
|
+
if (field.countAnnotations("db.encrypted") > 0) {
|
|
1691
|
+
fail("@db.sort.nulls on a @db.encrypted field — encrypted fields are not sortable");
|
|
1692
|
+
return errors;
|
|
1693
|
+
}
|
|
1694
|
+
const def = field.getDefinition();
|
|
1695
|
+
let leaf = def;
|
|
1696
|
+
if (def && isRef(def)) leaf = doc.unwindType(def.id, def.chain)?.def;
|
|
1697
|
+
const base = primitiveBaseType(leaf);
|
|
1698
|
+
if (def && (isArray(def) || isStructure(def)) || leaf && (isArray(leaf) || isStructure(leaf) || isInterface(leaf)) || leaf && isPrimitive(leaf) && (base === "array" || base === "object")) {
|
|
1699
|
+
fail("@db.sort.nulls needs a sortable scalar field — not an object or an array");
|
|
1700
|
+
return errors;
|
|
1701
|
+
}
|
|
1702
|
+
const owner = getDbTableOwner(token);
|
|
1703
|
+
if (!field.has("optional") && owner && owner.countAnnotations("db.table") > 0 && field.ownerNode?.ownerNode === owner) fail("@db.sort.nulls on a required field has no effect — the field is never NULL; make it optional (field?: …) or remove the annotation", 2);
|
|
1704
|
+
return errors;
|
|
1705
|
+
}
|
|
1706
|
+
}) } };
|
|
1707
|
+
//#endregion
|
|
1662
1708
|
//#region src/plugin/annotations/table.ts
|
|
1663
1709
|
const dbTableAnnotations = {
|
|
1664
1710
|
table: {
|
|
@@ -2250,6 +2296,7 @@ const dbPlugin = (options) => ({
|
|
|
2250
2296
|
agg: dbAggAnnotations.agg,
|
|
2251
2297
|
compute: dbComputeAnnotations.compute,
|
|
2252
2298
|
search: dbSearchAnnotations.search,
|
|
2299
|
+
sort: dbSortAnnotations.sort,
|
|
2253
2300
|
amount: dbAmountAnnotations.amount,
|
|
2254
2301
|
unit: dbUnitAnnotations.unit
|
|
2255
2302
|
} },
|
package/dist/rel.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_nested_writer = require("./nested-writer-
|
|
3
|
-
const require_relation_loader = require("./relation-loader-
|
|
2
|
+
const require_nested_writer = require("./nested-writer-Bl1SZS7x.cjs");
|
|
3
|
+
const require_relation_loader = require("./relation-loader-DeulLrAw.cjs");
|
|
4
4
|
exports.applyPatchNestedTo = require_nested_writer.applyPatchNestedTo;
|
|
5
5
|
exports.batchInsertNestedFrom = require_nested_writer.batchInsertNestedFrom;
|
|
6
6
|
exports.batchInsertNestedTo = require_nested_writer.batchInsertNestedTo;
|
package/dist/rel.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Gn as TGenericLogger, It as TTableResolver, Kt as TableMetadata, Vt as TWriteTableResolver, W as TDbForeignKey, Zt as BaseDbAdapter, h as AtscriptDbWritable, m as AtscriptDbTableLike, nt as TDbRelation } from "./buckets-jy8BfswO.cjs";
|
|
2
2
|
import { t as DbValidationContext } from "./db-validator-plugin-BWy60OvG.cjs";
|
|
3
|
-
import { n as findRemoteFK, r as resolveRelationTargetTable, t as findFKForRelation } from "./relation-helpers-
|
|
3
|
+
import { n as findRemoteFK, r as resolveRelationTargetTable, t as findFKForRelation } from "./relation-helpers-D_8bydc2.cjs";
|
|
4
4
|
import { FilterExpr, WithRelation } from "@uniqu/core";
|
|
5
5
|
import { Validator } from "@atscript/typescript/utils";
|
|
6
6
|
|
package/dist/rel.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Gn as TGenericLogger, It as TTableResolver, Kt as TableMetadata, Vt as TWriteTableResolver, W as TDbForeignKey, Zt as BaseDbAdapter, h as AtscriptDbWritable, m as AtscriptDbTableLike, nt as TDbRelation } from "./buckets-CBKL_ZWE.mjs";
|
|
2
2
|
import { t as DbValidationContext } from "./db-validator-plugin-BWy60OvG.mjs";
|
|
3
|
-
import { n as findRemoteFK, r as resolveRelationTargetTable, t as findFKForRelation } from "./relation-helpers-
|
|
3
|
+
import { n as findRemoteFK, r as resolveRelationTargetTable, t as findFKForRelation } from "./relation-helpers-BUsxNNGU.mjs";
|
|
4
4
|
import { Validator } from "@atscript/typescript/utils";
|
|
5
5
|
import { FilterExpr, WithRelation } from "@uniqu/core";
|
|
6
6
|
|
package/dist/rel.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as loadRelationsImpl } from "./relation-loader-
|
|
2
|
-
import { B as resolveRelationTargetTable, R as findFKForRelation, a as batchPatchNestedFrom, c as batchReplaceNestedFrom, d as checkDepthOverflow, f as planNestedFromVia, h as validateBatch, i as batchInsertNestedVia, l as batchReplaceNestedTo, m as preValidateNestedFrom, n as batchInsertNestedFrom, o as batchPatchNestedTo, p as planPatchNestedTo, r as batchInsertNestedTo, s as batchPatchNestedVia, t as applyPatchNestedTo, u as batchReplaceNestedVia, z as findRemoteFK } from "./nested-writer-
|
|
1
|
+
import { t as loadRelationsImpl } from "./relation-loader-CNBMPfRr.mjs";
|
|
2
|
+
import { B as resolveRelationTargetTable, R as findFKForRelation, a as batchPatchNestedFrom, c as batchReplaceNestedFrom, d as checkDepthOverflow, f as planNestedFromVia, h as validateBatch, i as batchInsertNestedVia, l as batchReplaceNestedTo, m as preValidateNestedFrom, n as batchInsertNestedFrom, o as batchPatchNestedTo, p as planPatchNestedTo, r as batchInsertNestedTo, s as batchPatchNestedVia, t as applyPatchNestedTo, u as batchReplaceNestedVia, z as findRemoteFK } from "./nested-writer-BOj_c_lI.mjs";
|
|
3
3
|
export { applyPatchNestedTo, batchInsertNestedFrom, batchInsertNestedTo, batchInsertNestedVia, batchPatchNestedFrom, batchPatchNestedTo, batchPatchNestedVia, batchReplaceNestedFrom, batchReplaceNestedTo, batchReplaceNestedVia, checkDepthOverflow, findFKForRelation, findRemoteFK, loadRelationsImpl, planNestedFromVia, planPatchNestedTo, preValidateNestedFrom, resolveRelationTargetTable, validateBatch };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { W as TDbForeignKey, nt as TDbRelation } from "./buckets-CBKL_ZWE.mjs";
|
|
2
2
|
import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
|
|
3
3
|
|
|
4
4
|
//#region src/rel/relation-helpers.d.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { W as TDbForeignKey, nt as TDbRelation } from "./buckets-jy8BfswO.cjs";
|
|
2
2
|
import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
|
|
3
3
|
|
|
4
4
|
//#region src/rel/relation-helpers.d.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as resolveRelationTargetTable, E as andFilters, I as compositeKey, P as relationStaticFilter, R as findFKForRelation, V as slicePerGroup, z as findRemoteFK } from "./nested-writer-
|
|
1
|
+
import { B as resolveRelationTargetTable, E as andFilters, I as compositeKey, P as relationStaticFilter, R as findFKForRelation, V as slicePerGroup, z as findRemoteFK } from "./nested-writer-BOj_c_lI.mjs";
|
|
2
2
|
//#region \0rolldown/runtime.js
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -42,6 +42,7 @@ async function loadRelationsImpl(rows, withRelations, host) {
|
|
|
42
42
|
const flatRel = withRel;
|
|
43
43
|
const controls = { ...withRel.controls || {} };
|
|
44
44
|
if (flatRel.$sort && !controls.$sort) controls.$sort = flatRel.$sort;
|
|
45
|
+
if (flatRel.$nulls && !controls.$nulls) controls.$nulls = flatRel.$nulls;
|
|
45
46
|
if (flatRel.$limit !== null && flatRel.$limit !== void 0 && (controls.$limit === null || controls.$limit === void 0)) controls.$limit = flatRel.$limit;
|
|
46
47
|
if (flatRel.$skip !== null && flatRel.$skip !== void 0 && (controls.$skip === null || controls.$skip === void 0)) controls.$skip = flatRel.$skip;
|
|
47
48
|
if (flatRel.$select && !controls.$select) controls.$select = flatRel.$select;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_nested_writer = require("./nested-writer-
|
|
1
|
+
const require_nested_writer = require("./nested-writer-Bl1SZS7x.cjs");
|
|
2
2
|
//#region src/rel/relation-loader.ts
|
|
3
3
|
var relation_loader_exports = /* @__PURE__ */ require("./plugin.cjs").__exportAll({
|
|
4
4
|
ensureSelectIncludesFields: () => ensureSelectIncludesFields,
|
|
@@ -30,6 +30,7 @@ async function loadRelationsImpl(rows, withRelations, host) {
|
|
|
30
30
|
const flatRel = withRel;
|
|
31
31
|
const controls = { ...withRel.controls || {} };
|
|
32
32
|
if (flatRel.$sort && !controls.$sort) controls.$sort = flatRel.$sort;
|
|
33
|
+
if (flatRel.$nulls && !controls.$nulls) controls.$nulls = flatRel.$nulls;
|
|
33
34
|
if (flatRel.$limit !== null && flatRel.$limit !== void 0 && (controls.$limit === null || controls.$limit === void 0)) controls.$limit = flatRel.$limit;
|
|
34
35
|
if (flatRel.$skip !== null && flatRel.$skip !== void 0 && (controls.$skip === null || controls.$skip === void 0)) controls.$skip = flatRel.$skip;
|
|
35
36
|
if (flatRel.$select && !controls.$select) controls.$select = flatRel.$select;
|
package/dist/sync.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_db_error = require("./db-error-
|
|
3
|
-
const require_column_diff = require("./column-diff-
|
|
2
|
+
const require_db_error = require("./db-error-JcfJqqpk.cjs");
|
|
3
|
+
const require_column_diff = require("./column-diff-CbdWi_uZ.cjs");
|
|
4
4
|
//#region src/schema/table-option-diff.ts
|
|
5
5
|
/**
|
|
6
6
|
* Computes the difference between desired and existing table options.
|
package/dist/sync.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { _ as computeTableSnapshot, a as hasForeignKeyChanges, b as snapshotToExistingTableOptions, c as TFieldSnapshot, d as TViewColumnSnapshot, f as TViewJoinSnapshot, g as computeTableHash, h as computeSchemaHash, i as fkKey, l as TForeignKeySnapshot, m as canonicalizeQueryNode, n as computeForeignKeyDiff, o as computeColumnDiff, p as TViewSnapshot, t as TForeignKeyDiff, u as TTableSnapshot, v as computeViewSnapshot, y as snapshotToExistingColumns } from "./fk-diff-
|
|
1
|
+
import { $t as DbSpace, At as TReferencingForeignKey, Ft as TTableOptionDiff, Gn as TGenericLogger, U as TDbFieldMeta, et as TDbObjectKind, gt as TEnsureTableOptions, kt as TPrimaryKeyChange, mt as TDerivedChangeReason, vt as TExistingForeignKey, yt as TExistingTableOption } from "./buckets-jy8BfswO.cjs";
|
|
2
|
+
import { _ as computeTableSnapshot, a as hasForeignKeyChanges, b as snapshotToExistingTableOptions, c as TFieldSnapshot, d as TViewColumnSnapshot, f as TViewJoinSnapshot, g as computeTableHash, h as computeSchemaHash, i as fkKey, l as TForeignKeySnapshot, m as canonicalizeQueryNode, n as computeForeignKeyDiff, o as computeColumnDiff, p as TViewSnapshot, t as TForeignKeyDiff, u as TTableSnapshot, v as computeViewSnapshot, y as snapshotToExistingColumns } from "./fk-diff-D1mrLMjU.cjs";
|
|
3
3
|
import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
|
|
4
4
|
|
|
5
5
|
//#region src/schema/sync-entry.d.ts
|
package/dist/sync.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { _ as computeTableSnapshot, a as hasForeignKeyChanges, b as snapshotToExistingTableOptions, c as TFieldSnapshot, d as TViewColumnSnapshot, f as TViewJoinSnapshot, g as computeTableHash, h as computeSchemaHash, i as fkKey, l as TForeignKeySnapshot, m as canonicalizeQueryNode, n as computeForeignKeyDiff, o as computeColumnDiff, p as TViewSnapshot, t as TForeignKeyDiff, u as TTableSnapshot, v as computeViewSnapshot, y as snapshotToExistingColumns } from "./fk-diff-
|
|
1
|
+
import { $t as DbSpace, At as TReferencingForeignKey, Ft as TTableOptionDiff, Gn as TGenericLogger, U as TDbFieldMeta, et as TDbObjectKind, gt as TEnsureTableOptions, kt as TPrimaryKeyChange, mt as TDerivedChangeReason, vt as TExistingForeignKey, yt as TExistingTableOption } from "./buckets-CBKL_ZWE.mjs";
|
|
2
|
+
import { _ as computeTableSnapshot, a as hasForeignKeyChanges, b as snapshotToExistingTableOptions, c as TFieldSnapshot, d as TViewColumnSnapshot, f as TViewJoinSnapshot, g as computeTableHash, h as computeSchemaHash, i as fkKey, l as TForeignKeySnapshot, m as canonicalizeQueryNode, n as computeForeignKeyDiff, o as computeColumnDiff, p as TViewSnapshot, t as TForeignKeyDiff, u as TTableSnapshot, v as computeViewSnapshot, y as snapshotToExistingColumns } from "./fk-diff-BIJ8l_hF.mjs";
|
|
3
3
|
import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
|
|
4
4
|
|
|
5
5
|
//#region src/schema/sync-entry.d.ts
|
package/dist/sync.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { r as DbError } from "./db-error-
|
|
2
|
-
import { _t as NoopLogger, a as computeTableHash, c as snapshotToExistingColumns, d as computeForeignKeyDiff, dt as aliasTargetOf, f as fkColumns, i as computeSchemaHash, l as snapshotToExistingTableOptions, m as hasForeignKeyChanges, o as computeTableSnapshot, p as fkKey, r as canonicalizeQueryNode, s as computeViewSnapshot, t as computeColumnDiff, u as viewSnapshotSources } from "./column-diff-
|
|
1
|
+
import { r as DbError } from "./db-error-eSsQX7rt.mjs";
|
|
2
|
+
import { _t as NoopLogger, a as computeTableHash, c as snapshotToExistingColumns, d as computeForeignKeyDiff, dt as aliasTargetOf, f as fkColumns, i as computeSchemaHash, l as snapshotToExistingTableOptions, m as hasForeignKeyChanges, o as computeTableSnapshot, p as fkKey, r as canonicalizeQueryNode, s as computeViewSnapshot, t as computeColumnDiff, u as viewSnapshotSources } from "./column-diff-DrE4ho_m.mjs";
|
|
3
3
|
//#region src/schema/table-option-diff.ts
|
|
4
4
|
/**
|
|
5
5
|
* Computes the difference between desired and existing table options.
|
|
@@ -14,7 +14,28 @@ type DbErrorCode = "CONFLICT" | "FK_VIOLATION" | "NOT_FOUND" | "CASCADE_CYCLE" |
|
|
|
14
14
|
* table lives on a different adapter, or the table was built without a
|
|
15
15
|
* `DbSpace` (moost-db: 400). @since 0.1.147
|
|
16
16
|
*/
|
|
17
|
-
| "REL_FILTER_NOT_SUPPORTED" /** The `DbSpace` was closed; the table/view handle can no longer be used (moost-db: 503). @since 0.1.148 */ | "SPACE_CLOSED" /** The adapter does not implement `insertManyIgnore` (moost-db: 400). @since 0.1.148 */ | "ON_CONFLICT_NOT_SUPPORTED" | "TX_WAIT_TIMEOUT"
|
|
17
|
+
| "REL_FILTER_NOT_SUPPORTED" /** The `DbSpace` was closed; the table/view handle can no longer be used (moost-db: 503). @since 0.1.148 */ | "SPACE_CLOSED" /** The adapter does not implement `insertManyIgnore` (moost-db: 400). @since 0.1.148 */ | "ON_CONFLICT_NOT_SUPPORTED" | "TX_WAIT_TIMEOUT"
|
|
18
|
+
/**
|
|
19
|
+
* The engine chose this transaction as a deadlock victim (MySQL errno 1213,
|
|
20
|
+
* PostgreSQL 40P01) and rolled it back. Retry the WHOLE transaction.
|
|
21
|
+
* `retryable` is `true`. @since 0.1.153
|
|
22
|
+
*/
|
|
23
|
+
| "DEADLOCK"
|
|
24
|
+
/**
|
|
25
|
+
* A row lock was not granted in time (MySQL errno 1205
|
|
26
|
+
* `innodb_lock_wait_timeout`, PostgreSQL 55P03 `lock_timeout` / `NOWAIT`).
|
|
27
|
+
* The statement failed; MySQL keeps the transaction open unless
|
|
28
|
+
* `innodb_rollback_on_timeout` is set, PostgreSQL aborts it — retry the
|
|
29
|
+
* whole transaction. `retryable` is `true`. @since 0.1.153
|
|
30
|
+
*/
|
|
31
|
+
| "LOCK_TIMEOUT"
|
|
32
|
+
/**
|
|
33
|
+
* PostgreSQL could not serialize the transaction (`40001`: under
|
|
34
|
+
* `REPEATABLE READ` / `SERIALIZABLE`, e.g. a row locked or updated after it
|
|
35
|
+
* changed since the snapshot). The transaction is aborted — retry it whole.
|
|
36
|
+
* `retryable` is `true`. @since 0.1.153
|
|
37
|
+
*/
|
|
38
|
+
| "SERIALIZATION_FAILURE";
|
|
18
39
|
declare class DbError extends Error {
|
|
19
40
|
readonly code: DbErrorCode;
|
|
20
41
|
readonly errors: Array<{
|
|
@@ -26,7 +47,20 @@ declare class DbError extends Error {
|
|
|
26
47
|
path: string;
|
|
27
48
|
message: string;
|
|
28
49
|
}>, message?: string);
|
|
50
|
+
/**
|
|
51
|
+
* `true` when retrying the whole transaction may succeed — a transient
|
|
52
|
+
* concurrency condition (`DEADLOCK`, `LOCK_TIMEOUT`,
|
|
53
|
+
* `SERIALIZATION_FAILURE`), not a fault of the
|
|
54
|
+
* request. @since 0.1.153
|
|
55
|
+
*/
|
|
56
|
+
get retryable(): boolean;
|
|
29
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Whether `error` is a retryable `DbError` (`DEADLOCK` / `LOCK_TIMEOUT` /
|
|
60
|
+
* `SERIALIZATION_FAILURE`):
|
|
61
|
+
* re-running the whole transaction may succeed. @since 0.1.153
|
|
62
|
+
*/
|
|
63
|
+
declare function isRetryableDbError(error: unknown): error is DbError;
|
|
30
64
|
/** Whether `error` is a `DbError` with code `CONFLICT` (unique / primary-key violation). */
|
|
31
65
|
declare function isConflict(error: unknown): error is DbError;
|
|
32
66
|
/**
|
|
@@ -162,4 +196,4 @@ interface ValidationContext {
|
|
|
162
196
|
*/
|
|
163
197
|
declare function buildValidationContext(type: TAtscriptAnnotatedType<TAtscriptTypeObject>): ValidationContext;
|
|
164
198
|
//#endregion
|
|
165
|
-
export { aggregateFailure as _, buildValidationContext as a, isNavRelation as c, getKeyProps as d, CasExhaustedError as f, aggregateExpressionsNotSupported as g, DbErrorCode as h, buildPatchPartial as i, TArrayPatch as l, DbError as m, ValidatorMode as n, dbPlugin as o, CasMismatchError as p, buildDbValidator as r, forceNavNonOptional as s, ValidationContext as t, TDbPatch as u, bucketTimeZoneUnavailable as v, isConflict as y };
|
|
199
|
+
export { aggregateFailure as _, buildValidationContext as a, isRetryableDbError as b, isNavRelation as c, getKeyProps as d, CasExhaustedError as f, aggregateExpressionsNotSupported as g, DbErrorCode as h, buildPatchPartial as i, TArrayPatch as l, DbError as m, ValidatorMode as n, dbPlugin as o, CasMismatchError as p, buildDbValidator as r, forceNavNonOptional as s, ValidationContext as t, TDbPatch as u, bucketTimeZoneUnavailable as v, isConflict as y };
|
|
@@ -14,7 +14,28 @@ type DbErrorCode = "CONFLICT" | "FK_VIOLATION" | "NOT_FOUND" | "CASCADE_CYCLE" |
|
|
|
14
14
|
* table lives on a different adapter, or the table was built without a
|
|
15
15
|
* `DbSpace` (moost-db: 400). @since 0.1.147
|
|
16
16
|
*/
|
|
17
|
-
| "REL_FILTER_NOT_SUPPORTED" /** The `DbSpace` was closed; the table/view handle can no longer be used (moost-db: 503). @since 0.1.148 */ | "SPACE_CLOSED" /** The adapter does not implement `insertManyIgnore` (moost-db: 400). @since 0.1.148 */ | "ON_CONFLICT_NOT_SUPPORTED" | "TX_WAIT_TIMEOUT"
|
|
17
|
+
| "REL_FILTER_NOT_SUPPORTED" /** The `DbSpace` was closed; the table/view handle can no longer be used (moost-db: 503). @since 0.1.148 */ | "SPACE_CLOSED" /** The adapter does not implement `insertManyIgnore` (moost-db: 400). @since 0.1.148 */ | "ON_CONFLICT_NOT_SUPPORTED" | "TX_WAIT_TIMEOUT"
|
|
18
|
+
/**
|
|
19
|
+
* The engine chose this transaction as a deadlock victim (MySQL errno 1213,
|
|
20
|
+
* PostgreSQL 40P01) and rolled it back. Retry the WHOLE transaction.
|
|
21
|
+
* `retryable` is `true`. @since 0.1.153
|
|
22
|
+
*/
|
|
23
|
+
| "DEADLOCK"
|
|
24
|
+
/**
|
|
25
|
+
* A row lock was not granted in time (MySQL errno 1205
|
|
26
|
+
* `innodb_lock_wait_timeout`, PostgreSQL 55P03 `lock_timeout` / `NOWAIT`).
|
|
27
|
+
* The statement failed; MySQL keeps the transaction open unless
|
|
28
|
+
* `innodb_rollback_on_timeout` is set, PostgreSQL aborts it — retry the
|
|
29
|
+
* whole transaction. `retryable` is `true`. @since 0.1.153
|
|
30
|
+
*/
|
|
31
|
+
| "LOCK_TIMEOUT"
|
|
32
|
+
/**
|
|
33
|
+
* PostgreSQL could not serialize the transaction (`40001`: under
|
|
34
|
+
* `REPEATABLE READ` / `SERIALIZABLE`, e.g. a row locked or updated after it
|
|
35
|
+
* changed since the snapshot). The transaction is aborted — retry it whole.
|
|
36
|
+
* `retryable` is `true`. @since 0.1.153
|
|
37
|
+
*/
|
|
38
|
+
| "SERIALIZATION_FAILURE";
|
|
18
39
|
declare class DbError extends Error {
|
|
19
40
|
readonly code: DbErrorCode;
|
|
20
41
|
readonly errors: Array<{
|
|
@@ -26,7 +47,20 @@ declare class DbError extends Error {
|
|
|
26
47
|
path: string;
|
|
27
48
|
message: string;
|
|
28
49
|
}>, message?: string);
|
|
50
|
+
/**
|
|
51
|
+
* `true` when retrying the whole transaction may succeed — a transient
|
|
52
|
+
* concurrency condition (`DEADLOCK`, `LOCK_TIMEOUT`,
|
|
53
|
+
* `SERIALIZATION_FAILURE`), not a fault of the
|
|
54
|
+
* request. @since 0.1.153
|
|
55
|
+
*/
|
|
56
|
+
get retryable(): boolean;
|
|
29
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Whether `error` is a retryable `DbError` (`DEADLOCK` / `LOCK_TIMEOUT` /
|
|
60
|
+
* `SERIALIZATION_FAILURE`):
|
|
61
|
+
* re-running the whole transaction may succeed. @since 0.1.153
|
|
62
|
+
*/
|
|
63
|
+
declare function isRetryableDbError(error: unknown): error is DbError;
|
|
30
64
|
/** Whether `error` is a `DbError` with code `CONFLICT` (unique / primary-key violation). */
|
|
31
65
|
declare function isConflict(error: unknown): error is DbError;
|
|
32
66
|
/**
|
|
@@ -162,4 +196,4 @@ interface ValidationContext {
|
|
|
162
196
|
*/
|
|
163
197
|
declare function buildValidationContext(type: TAtscriptAnnotatedType<TAtscriptTypeObject>): ValidationContext;
|
|
164
198
|
//#endregion
|
|
165
|
-
export { aggregateFailure as _, buildValidationContext as a, isNavRelation as c, getKeyProps as d, CasExhaustedError as f, aggregateExpressionsNotSupported as g, DbErrorCode as h, buildPatchPartial as i, TArrayPatch as l, DbError as m, ValidatorMode as n, dbPlugin as o, CasMismatchError as p, buildDbValidator as r, forceNavNonOptional as s, ValidationContext as t, TDbPatch as u, bucketTimeZoneUnavailable as v, isConflict as y };
|
|
199
|
+
export { aggregateFailure as _, buildValidationContext as a, isRetryableDbError as b, isNavRelation as c, getKeyProps as d, CasExhaustedError as f, aggregateExpressionsNotSupported as g, DbErrorCode as h, buildPatchPartial as i, TArrayPatch as l, DbError as m, ValidatorMode as n, dbPlugin as o, CasMismatchError as p, buildDbValidator as r, forceNavNonOptional as s, ValidationContext as t, TDbPatch as u, bucketTimeZoneUnavailable as v, isConflict as y };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as DepthLimitExceededError } from "./db-error-
|
|
1
|
+
import { i as DepthLimitExceededError } from "./db-error-eSsQX7rt.mjs";
|
|
2
2
|
import { getDbFieldOp } from "./ops.mjs";
|
|
3
|
-
import { flattenAnnotatedType } from "@atscript/typescript/utils";
|
|
3
|
+
import { createAnnotatedTypeNode, flattenAnnotatedType } from "@atscript/typescript/utils";
|
|
4
4
|
//#region src/patch/patch-types.ts
|
|
5
5
|
/**
|
|
6
6
|
* Extracts `@expect.array.key` properties from an array-of-objects type.
|
|
@@ -40,6 +40,16 @@ function isPatchOperatorObject(value) {
|
|
|
40
40
|
return keys.every((k) => PATCH_OPS.has(k));
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
+
* Items of union / tuple / intersection types the plugin has visited. The
|
|
44
|
+
* validator visits a complex type before its items, so an item is known here
|
|
45
|
+
* by the time it is validated.
|
|
46
|
+
*/
|
|
47
|
+
const complexItems = /* @__PURE__ */ new WeakSet();
|
|
48
|
+
function markComplexItems(def) {
|
|
49
|
+
const kind = def.type.kind;
|
|
50
|
+
if (kind === "union" || kind === "tuple" || kind === "intersection") for (const item of def.type.items) complexItems.add(item);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
43
53
|
* Validator plugin for database operations.
|
|
44
54
|
*
|
|
45
55
|
* Handles navigation field constraints and delegates to the standard validator
|
|
@@ -54,6 +64,7 @@ function createDbValidatorPlugin() {
|
|
|
54
64
|
return (ctx, def, value) => {
|
|
55
65
|
const dbCtx = ctx.context;
|
|
56
66
|
if (!dbCtx) return;
|
|
67
|
+
markComplexItems(def);
|
|
57
68
|
if (def.type.tags?.has("geoPoint") && value !== void 0 && value !== null) {
|
|
58
69
|
if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== "number" || typeof value[1] !== "number" || !Number.isFinite(value[0]) || !Number.isFinite(value[1])) {
|
|
59
70
|
ctx.error("Expected a [lng, lat] tuple of two finite numbers (GeoJSON order)");
|
|
@@ -73,7 +84,7 @@ function createDbValidatorPlugin() {
|
|
|
73
84
|
const isFrom = def.metadata.has("db.rel.from");
|
|
74
85
|
const isVia = def.metadata.has("db.rel.via");
|
|
75
86
|
if (isTo || isFrom || isVia) return handleNavField(ctx, def, value, dbCtx, isTo, isFrom, isVia);
|
|
76
|
-
if (value === void 0 && (dbCtx.mode === "insert" || dbCtx.mode === "replace")) {
|
|
87
|
+
if (value === void 0 && (dbCtx.mode === "insert" || dbCtx.mode === "replace") && !complexItems.has(def)) {
|
|
77
88
|
const meta = def.metadata;
|
|
78
89
|
const serverManaged = meta.has("db.default") || meta.has("db.default.increment") || meta.has("db.default.uuid") || meta.has("db.default.now") || meta.has("db.column.version") || meta.has("db.column.derived");
|
|
79
90
|
const hasFK = meta.has("db.rel.FK");
|
|
@@ -90,7 +101,7 @@ function createDbValidatorPlugin() {
|
|
|
90
101
|
return false;
|
|
91
102
|
}
|
|
92
103
|
if (dbCtx.flatMap && !isFieldOpAllowed(ctx.path, dbCtx.flatMap, dbCtx.navFields)) {
|
|
93
|
-
ctx.error("Field operations ($inc/$dec/$mul) are not supported inside @db.json fields or nested objects without @db.patch.strategy \"merge\"");
|
|
104
|
+
ctx.error("Field operations ($inc/$dec/$mul) are not supported inside @db.json fields, array items, or nested objects without @db.patch.strategy \"merge\"");
|
|
94
105
|
return false;
|
|
95
106
|
}
|
|
96
107
|
if (!(def.type.kind === "" && def.type.designType === "number")) {
|
|
@@ -118,6 +129,8 @@ function createDbValidatorPlugin() {
|
|
|
118
129
|
* - The field itself is `@db.json` (ops on opaque blobs are meaningless).
|
|
119
130
|
* - Any ancestor is `@db.json`.
|
|
120
131
|
* - Any ancestor is a nested object without `@db.patch.strategy "merge"`.
|
|
132
|
+
* - Any ancestor is an embedded array (arrays are written whole, so an op
|
|
133
|
+
* inside an item would be stored verbatim).
|
|
121
134
|
*
|
|
122
135
|
* Accepts immediately when an ancestor is a navigation field (TO/FROM/VIA) —
|
|
123
136
|
* the nested data is extracted and validated against its own table separately.
|
|
@@ -131,6 +144,7 @@ function isFieldOpAllowed(path, flatMap, navFields) {
|
|
|
131
144
|
const entry = flatMap.get(ancestor);
|
|
132
145
|
if (!entry) continue;
|
|
133
146
|
if (entry.metadata.has("db.json")) return false;
|
|
147
|
+
if (entry.type.kind === "array") return false;
|
|
134
148
|
if (entry.type.kind === "object" && entry.metadata.get("db.patch.strategy") !== "merge") return false;
|
|
135
149
|
}
|
|
136
150
|
return true;
|
|
@@ -233,7 +247,10 @@ function validatePartialItems(ctx, arrayDef, items, op, isMerge) {
|
|
|
233
247
|
return false;
|
|
234
248
|
}
|
|
235
249
|
const elementDef = arrayDef.type.of;
|
|
236
|
-
if (elementDef.type.kind !== "object")
|
|
250
|
+
if (elementDef.type.kind !== "object") {
|
|
251
|
+
for (let i = 0; i < items.length; i++) if (!validateAt(ctx, `${op}[${i}]`, elementDef, items[i])) return false;
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
237
254
|
const keyProps = getKeyProps(arrayDef);
|
|
238
255
|
for (let i = 0; i < items.length; i++) {
|
|
239
256
|
const item = items[i];
|
|
@@ -249,12 +266,24 @@ function validatePartialItems(ctx, arrayDef, items, op, isMerge) {
|
|
|
249
266
|
}
|
|
250
267
|
}
|
|
251
268
|
const objType = elementDef.type;
|
|
252
|
-
|
|
269
|
+
let provided;
|
|
270
|
+
let providedValue;
|
|
271
|
+
for (const key of Object.keys(rec)) {
|
|
253
272
|
const propDef = objType.props.get(key);
|
|
254
273
|
if (propDef) {
|
|
255
|
-
|
|
274
|
+
(provided ??= /* @__PURE__ */ new Map()).set(key, propDef);
|
|
275
|
+
(providedValue ??= {})[key] = rec[key];
|
|
256
276
|
}
|
|
257
277
|
}
|
|
278
|
+
if (provided) {
|
|
279
|
+
const itemDef = createAnnotatedTypeNode({
|
|
280
|
+
kind: "object",
|
|
281
|
+
props: provided,
|
|
282
|
+
propsPatterns: [],
|
|
283
|
+
tags: objType.tags
|
|
284
|
+
}, elementDef.metadata);
|
|
285
|
+
if (!validateAt(ctx, `${op}[${i}]`, itemDef, providedValue)) return false;
|
|
286
|
+
}
|
|
258
287
|
if (op === "$update" && isMerge !== true) {
|
|
259
288
|
for (const [propName, propDef] of objType.props) if (!propDef.optional && !keyProps.has(propName) && rec[propName] === void 0) {
|
|
260
289
|
ctx.error(`${op}[${i}]: field '${propName}' is required (replace strategy)`);
|
|
@@ -265,6 +294,21 @@ function validatePartialItems(ctx, arrayDef, items, op, isMerge) {
|
|
|
265
294
|
return true;
|
|
266
295
|
}
|
|
267
296
|
/**
|
|
297
|
+
* Validates `value` against `def` as if it sat under `segment` of the current
|
|
298
|
+
* path (`<path>.<segment>`), through the validator's full `validateSafe` walk
|
|
299
|
+
* (optional/null shortcut, `replace`, plugins). The plugin context has no
|
|
300
|
+
* public path push, so the value is wrapped in a single-prop object.
|
|
301
|
+
*/
|
|
302
|
+
function validateAt(ctx, segment, def, value) {
|
|
303
|
+
const wrapper = createAnnotatedTypeNode({
|
|
304
|
+
kind: "object",
|
|
305
|
+
props: new Map([[segment, def]]),
|
|
306
|
+
propsPatterns: [],
|
|
307
|
+
tags: /* @__PURE__ */ new Set()
|
|
308
|
+
}, /* @__PURE__ */ new Map());
|
|
309
|
+
return ctx.validateAnnotatedType(wrapper, { [segment]: value });
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
268
312
|
* Returns `true` when every char in `[start, end)` is an ASCII digit.
|
|
269
313
|
* Matches segments that represent array indices in dot-notation paths.
|
|
270
314
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_db_error = require("./db-error-
|
|
1
|
+
const require_db_error = require("./db-error-JcfJqqpk.cjs");
|
|
2
2
|
const require_ops = require("./ops.cjs");
|
|
3
3
|
let _atscript_typescript_utils = require("@atscript/typescript/utils");
|
|
4
4
|
//#region src/patch/patch-types.ts
|
|
@@ -40,6 +40,16 @@ function isPatchOperatorObject(value) {
|
|
|
40
40
|
return keys.every((k) => PATCH_OPS.has(k));
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
+
* Items of union / tuple / intersection types the plugin has visited. The
|
|
44
|
+
* validator visits a complex type before its items, so an item is known here
|
|
45
|
+
* by the time it is validated.
|
|
46
|
+
*/
|
|
47
|
+
const complexItems = /* @__PURE__ */ new WeakSet();
|
|
48
|
+
function markComplexItems(def) {
|
|
49
|
+
const kind = def.type.kind;
|
|
50
|
+
if (kind === "union" || kind === "tuple" || kind === "intersection") for (const item of def.type.items) complexItems.add(item);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
43
53
|
* Validator plugin for database operations.
|
|
44
54
|
*
|
|
45
55
|
* Handles navigation field constraints and delegates to the standard validator
|
|
@@ -54,6 +64,7 @@ function createDbValidatorPlugin() {
|
|
|
54
64
|
return (ctx, def, value) => {
|
|
55
65
|
const dbCtx = ctx.context;
|
|
56
66
|
if (!dbCtx) return;
|
|
67
|
+
markComplexItems(def);
|
|
57
68
|
if (def.type.tags?.has("geoPoint") && value !== void 0 && value !== null) {
|
|
58
69
|
if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== "number" || typeof value[1] !== "number" || !Number.isFinite(value[0]) || !Number.isFinite(value[1])) {
|
|
59
70
|
ctx.error("Expected a [lng, lat] tuple of two finite numbers (GeoJSON order)");
|
|
@@ -73,7 +84,7 @@ function createDbValidatorPlugin() {
|
|
|
73
84
|
const isFrom = def.metadata.has("db.rel.from");
|
|
74
85
|
const isVia = def.metadata.has("db.rel.via");
|
|
75
86
|
if (isTo || isFrom || isVia) return handleNavField(ctx, def, value, dbCtx, isTo, isFrom, isVia);
|
|
76
|
-
if (value === void 0 && (dbCtx.mode === "insert" || dbCtx.mode === "replace")) {
|
|
87
|
+
if (value === void 0 && (dbCtx.mode === "insert" || dbCtx.mode === "replace") && !complexItems.has(def)) {
|
|
77
88
|
const meta = def.metadata;
|
|
78
89
|
const serverManaged = meta.has("db.default") || meta.has("db.default.increment") || meta.has("db.default.uuid") || meta.has("db.default.now") || meta.has("db.column.version") || meta.has("db.column.derived");
|
|
79
90
|
const hasFK = meta.has("db.rel.FK");
|
|
@@ -90,7 +101,7 @@ function createDbValidatorPlugin() {
|
|
|
90
101
|
return false;
|
|
91
102
|
}
|
|
92
103
|
if (dbCtx.flatMap && !isFieldOpAllowed(ctx.path, dbCtx.flatMap, dbCtx.navFields)) {
|
|
93
|
-
ctx.error("Field operations ($inc/$dec/$mul) are not supported inside @db.json fields or nested objects without @db.patch.strategy \"merge\"");
|
|
104
|
+
ctx.error("Field operations ($inc/$dec/$mul) are not supported inside @db.json fields, array items, or nested objects without @db.patch.strategy \"merge\"");
|
|
94
105
|
return false;
|
|
95
106
|
}
|
|
96
107
|
if (!(def.type.kind === "" && def.type.designType === "number")) {
|
|
@@ -118,6 +129,8 @@ function createDbValidatorPlugin() {
|
|
|
118
129
|
* - The field itself is `@db.json` (ops on opaque blobs are meaningless).
|
|
119
130
|
* - Any ancestor is `@db.json`.
|
|
120
131
|
* - Any ancestor is a nested object without `@db.patch.strategy "merge"`.
|
|
132
|
+
* - Any ancestor is an embedded array (arrays are written whole, so an op
|
|
133
|
+
* inside an item would be stored verbatim).
|
|
121
134
|
*
|
|
122
135
|
* Accepts immediately when an ancestor is a navigation field (TO/FROM/VIA) —
|
|
123
136
|
* the nested data is extracted and validated against its own table separately.
|
|
@@ -131,6 +144,7 @@ function isFieldOpAllowed(path, flatMap, navFields) {
|
|
|
131
144
|
const entry = flatMap.get(ancestor);
|
|
132
145
|
if (!entry) continue;
|
|
133
146
|
if (entry.metadata.has("db.json")) return false;
|
|
147
|
+
if (entry.type.kind === "array") return false;
|
|
134
148
|
if (entry.type.kind === "object" && entry.metadata.get("db.patch.strategy") !== "merge") return false;
|
|
135
149
|
}
|
|
136
150
|
return true;
|
|
@@ -233,7 +247,10 @@ function validatePartialItems(ctx, arrayDef, items, op, isMerge) {
|
|
|
233
247
|
return false;
|
|
234
248
|
}
|
|
235
249
|
const elementDef = arrayDef.type.of;
|
|
236
|
-
if (elementDef.type.kind !== "object")
|
|
250
|
+
if (elementDef.type.kind !== "object") {
|
|
251
|
+
for (let i = 0; i < items.length; i++) if (!validateAt(ctx, `${op}[${i}]`, elementDef, items[i])) return false;
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
237
254
|
const keyProps = getKeyProps(arrayDef);
|
|
238
255
|
for (let i = 0; i < items.length; i++) {
|
|
239
256
|
const item = items[i];
|
|
@@ -249,12 +266,24 @@ function validatePartialItems(ctx, arrayDef, items, op, isMerge) {
|
|
|
249
266
|
}
|
|
250
267
|
}
|
|
251
268
|
const objType = elementDef.type;
|
|
252
|
-
|
|
269
|
+
let provided;
|
|
270
|
+
let providedValue;
|
|
271
|
+
for (const key of Object.keys(rec)) {
|
|
253
272
|
const propDef = objType.props.get(key);
|
|
254
273
|
if (propDef) {
|
|
255
|
-
|
|
274
|
+
(provided ??= /* @__PURE__ */ new Map()).set(key, propDef);
|
|
275
|
+
(providedValue ??= {})[key] = rec[key];
|
|
256
276
|
}
|
|
257
277
|
}
|
|
278
|
+
if (provided) {
|
|
279
|
+
const itemDef = (0, _atscript_typescript_utils.createAnnotatedTypeNode)({
|
|
280
|
+
kind: "object",
|
|
281
|
+
props: provided,
|
|
282
|
+
propsPatterns: [],
|
|
283
|
+
tags: objType.tags
|
|
284
|
+
}, elementDef.metadata);
|
|
285
|
+
if (!validateAt(ctx, `${op}[${i}]`, itemDef, providedValue)) return false;
|
|
286
|
+
}
|
|
258
287
|
if (op === "$update" && isMerge !== true) {
|
|
259
288
|
for (const [propName, propDef] of objType.props) if (!propDef.optional && !keyProps.has(propName) && rec[propName] === void 0) {
|
|
260
289
|
ctx.error(`${op}[${i}]: field '${propName}' is required (replace strategy)`);
|
|
@@ -265,6 +294,21 @@ function validatePartialItems(ctx, arrayDef, items, op, isMerge) {
|
|
|
265
294
|
return true;
|
|
266
295
|
}
|
|
267
296
|
/**
|
|
297
|
+
* Validates `value` against `def` as if it sat under `segment` of the current
|
|
298
|
+
* path (`<path>.<segment>`), through the validator's full `validateSafe` walk
|
|
299
|
+
* (optional/null shortcut, `replace`, plugins). The plugin context has no
|
|
300
|
+
* public path push, so the value is wrapped in a single-prop object.
|
|
301
|
+
*/
|
|
302
|
+
function validateAt(ctx, segment, def, value) {
|
|
303
|
+
const wrapper = (0, _atscript_typescript_utils.createAnnotatedTypeNode)({
|
|
304
|
+
kind: "object",
|
|
305
|
+
props: new Map([[segment, def]]),
|
|
306
|
+
propsPatterns: [],
|
|
307
|
+
tags: /* @__PURE__ */ new Set()
|
|
308
|
+
}, /* @__PURE__ */ new Map());
|
|
309
|
+
return ctx.validateAnnotatedType(wrapper, { [segment]: value });
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
268
312
|
* Returns `true` when every char in `[start, end)` is an ASCII digit.
|
|
269
313
|
* Matches segments that represent array indices in dot-notation paths.
|
|
270
314
|
*/
|