@atscript/db 0.1.163 → 0.1.165
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-DupYzFD7.cjs → aggregate-fns-CNtrSxGH.cjs} +1 -1
- package/dist/{aggregate-fns-DsS9UQVB.mjs → aggregate-fns-CmtSNIHm.mjs} +1 -1
- package/dist/{buckets-B8UOod-v.d.cts → buckets-BDxKfCNo.d.cts} +143 -13
- package/dist/{buckets-CHyspjYP.d.mts → buckets-CHUgky8T.d.mts} +143 -13
- package/dist/{column-diff-BfNIlMwX.mjs → column-diff-9pF0d8JP.mjs} +354 -31
- package/dist/{column-diff-BIGX3sRH.cjs → column-diff-DjeUj9kb.cjs} +371 -30
- package/dist/control-B5BJypqL.cjs +16 -0
- package/dist/control-CevYIJC4.mjs +16 -0
- package/dist/{db-error-hraC12B9.mjs → db-error-BMy4-OWq.mjs} +23 -1
- package/dist/{db-error-B_DRL1E9.d.cts → db-error-DQUw2cED.d.cts} +65 -9
- package/dist/{db-error-B_DRL1E9.d.mts → db-error-DQUw2cED.d.mts} +65 -9
- package/dist/{db-error-DmF1_xrz.cjs → db-error-G3sgvL87.cjs} +28 -0
- package/dist/{fk-diff-BNGawZi9.d.cts → fk-diff-DSM7O7hs.d.cts} +1 -1
- package/dist/{fk-diff-rj9N2QLw.d.mts → fk-diff-Dapr5Dk-.d.mts} +1 -1
- package/dist/index.cjs +17 -11
- package/dist/index.d.cts +38 -12
- package/dist/index.d.mts +38 -12
- package/dist/index.mjs +14 -12
- package/dist/{nested-writer-ClUX0DB0.mjs → nested-writer-BraWjS8a.mjs} +177 -26
- package/dist/{nested-writer-yCy6wGuN.cjs → nested-writer-DOvdbYG-.cjs} +194 -31
- package/dist/ops.cjs +1 -1
- package/dist/ops.mjs +1 -1
- package/dist/plugin.cjs +15 -12
- package/dist/plugin.mjs +15 -12
- 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-DloMywNs.d.cts → relation-helpers-C4UNWYQu.d.cts} +1 -1
- package/dist/{relation-helpers-zwt19E7r.d.mts → relation-helpers-hdbuqwjV.d.mts} +1 -1
- package/dist/{relation-loader-Qv3LrQ42.mjs → relation-loader-BOEVhcBe.mjs} +1 -1
- package/dist/{relation-loader-DujL_Mh9.cjs → relation-loader-BrkakVF_.cjs} +1 -1
- package/dist/shared.cjs +3 -1
- package/dist/shared.d.cts +13 -2
- package/dist/shared.d.mts +13 -2
- package/dist/shared.mjs +2 -2
- package/dist/sync.cjs +107 -25
- package/dist/sync.d.cts +53 -6
- package/dist/sync.d.mts +53 -6
- package/dist/sync.mjs +107 -25
- package/dist/{validator-DK9Ijpo7.cjs → validator-D7E7T0Oy.cjs} +1 -1
- package/dist/{validator-DuDWsIdQ.mjs → validator-UpgPhwvH.mjs} +1 -1
- 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/dist/{view-validation-BiDLCYMw.cjs → view-validation-C0UVLx_H.cjs} +38 -10
- package/dist/{view-validation-BcgDq7M9.mjs → view-validation-KW77xIKn.mjs} +28 -12
- package/package.json +6 -6
- package/dist/control-D5cQmzFj.mjs +0 -14
- package/dist/control-jIVU2hkf.cjs +0 -14
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as ALL_AGGREGATE_FNS } from "./aggregate-fns-
|
|
4
|
-
import {
|
|
5
|
-
import { a as forceNavNonOptional, c as getKeyProps, o as isNavRelation, r as buildValidationContext, s as createDbValidatorPlugin, t as buildDbValidator } from "./validator-
|
|
1
|
+
import { $ as guardQuery, A as fkKey, B as ENCRYPTED_REASON, C as ALL_ISOLATION_LEVELS, Ct as resolveCalendarBuckets, D as createFailureCollector, E as createChangeRecorder, F as resolveDesignType, G as checkHavingKeys, H as assertGeoPoint, I as geoIndexNotFoundMessage, J as groupSourceVerdict, K as classifyQueryPath, L as searchIndexNotFoundMessage, M as IntegrityStrategy, N as NativeIntegrity, P as AtscriptDbReadable, Q as guardPaths, R as vectorIndexNotFoundMessage, S as ALL_BUCKET_UNITS, St as normalizeComputedSelect, T as BaseDbAdapter, Tt as nestDottedWith, U as bucketSourceVerdict, V as acceptedOperatorsHint, W as canFilterLeaf, X as guardFilter, Y as guardAggregate, Z as guardPath, _ as isVersionExemptPatch, at as FieldMappingStrategy, b as fkLookupTargetOf, bt as isJsonValueField, ct as INTEGER_REGEX_OP, dt as resolveFulltextIndex, et as narrowerFilterOps, ft as searchTermInteger, g as AtscriptDbTable, gt as isGeoPointType, h as isViewType, ht as isGeoIndexableType, it as DocumentFieldMapper, k as fkColumns, lt as defaultFulltextIndex, m as isAtscriptDbView, mt as TableMetadata, n as isColumnTypeChanged, nt as unsupportedOperatorMessage, ot as UniquSelect, p as AtscriptDbView, pt as splitFulltextFields, q as collectQueryPaths, rt as RelationalFieldMapper, st as numericOperandProblem, tt as sortFieldNames, ut as describeFulltext, v as assertNoVersionWrites, vt as aliasTargetOf, w as ALL_VIEW_CAPABILITIES, wt as NoopLogger, x as ApplicationIntegrity, xt as jsonValueAncestor, y as decomposePatch, yt as isBucketableField, z as ADAPTER_FILTER_REASON } from "./column-diff-9pF0d8JP.mjs";
|
|
2
|
+
import { a as aggregateExpressionsNotSupported, c as constraintViolation, d as isRetryableDbError, f as spaceClosedError, l as invalidQuery, n as CasMismatchError, o as aggregateFailure, r as DbError, s as bucketTimeZoneUnavailable, t as CasExhaustedError, u as isConflict } from "./db-error-BMy4-OWq.mjs";
|
|
3
|
+
import { n as ALL_AGGREGATE_FNS } from "./aggregate-fns-CmtSNIHm.mjs";
|
|
4
|
+
import { D as ResolvedRelationFilter, E as REL_FILTER_MAX_NODES, I as relationStaticFilter, J as isFieldRef, M as hasRelationOp, N as isResolvedRelationFilter, O as andFilters, T as REL_FILTER_MAX_DEPTH, W as tableNameOf, Y as translateQueryTree, j as forEachResolvedRelation, k as containsRelationFilter, q as evaluateExpr, w as uniqueKeyTuple } from "./nested-writer-BraWjS8a.mjs";
|
|
5
|
+
import { a as forceNavNonOptional, c as getKeyProps, d as soleUnionMember, o as isNavRelation, r as buildValidationContext, s as createDbValidatorPlugin, t as buildDbValidator } from "./validator-UpgPhwvH.mjs";
|
|
6
6
|
import { n as isServerStampedOnUpdate, t as SERVER_STAMPED_ON_UPDATE_KEYS } from "./server-stamped-BoP56K9M.mjs";
|
|
7
7
|
import { a as searchMemberKind, s as DB_ENTITY_ANNOTATIONS } from "./search-fields-BPNsxrCk.mjs";
|
|
8
8
|
import { a as isPlainObject, i as isEmptyObject, n as findAncestorInSet, o as selfOrAncestor, r as getPath, s as stableJson, t as deletePath } from "./object-PQ1J8jri.mjs";
|
|
@@ -533,7 +533,7 @@ var DbSpace = class {
|
|
|
533
533
|
return target;
|
|
534
534
|
}
|
|
535
535
|
_scanFkLookupTarget(tableName) {
|
|
536
|
-
for (const table of this._allTables) if (table.tableName === tableName) return
|
|
536
|
+
for (const table of this._allTables) if (table.tableName === tableName) return fkLookupTargetOf(table);
|
|
537
537
|
}
|
|
538
538
|
};
|
|
539
539
|
if (typeof Symbol.asyncDispose === "symbol") DbSpace.prototype[Symbol.asyncDispose] = function() {
|
|
@@ -544,12 +544,14 @@ if (typeof Symbol.asyncDispose === "symbol") DbSpace.prototype[Symbol.asyncDispo
|
|
|
544
544
|
/**
|
|
545
545
|
* Whether `value` is a compiled type a `DbSpace` accepts: an annotated type
|
|
546
546
|
* whose own declaration carries `@db.table`, `@db.view` or `@db.view.for`.
|
|
547
|
-
* A `@db.alias` type is not one, and neither is a
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
*
|
|
547
|
+
* A `@db.alias` type is not one, and neither is a field typed with a table
|
|
548
|
+
* (`customer: Customer`) — since 0.1.141 the entity annotations stay on the
|
|
549
|
+
* declaring interface instead of travelling with every reference. A plain
|
|
550
|
+
* `export type X = Table` is `Table` itself (@atscript/typescript 0.2), so it
|
|
551
|
+
* passes.
|
|
551
552
|
*
|
|
552
|
-
* Filters a module namespace before a sync
|
|
553
|
+
* Filters a module namespace before a sync (a type listed under two export
|
|
554
|
+
* names is synced once):
|
|
553
555
|
* `syncSchema(db, Object.values(models).filter(isDbEntityType))`.
|
|
554
556
|
* @since 0.1.141
|
|
555
557
|
*/
|
|
@@ -558,4 +560,4 @@ function isDbEntityType(value) {
|
|
|
558
560
|
return DB_ENTITY_ANNOTATIONS.some((name) => value.metadata.has(name));
|
|
559
561
|
}
|
|
560
562
|
//#endregion
|
|
561
|
-
export { $cas, $dec, $inc, $insert, $mul, $remove, $replace, $update, $upsert, ADAPTER_FILTER_REASON, ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, ALL_ISOLATION_LEVELS, ALL_VIEW_CAPABILITIES, ApplicationIntegrity, AtscriptDbReadable, AtscriptDbTable, AtscriptDbView, BaseDbAdapter, CasExhaustedError, CasMismatchError, DEFAULT_DB_SPACE, DbEncryption, DbError, DbSpace, DocumentFieldMapper, ENCRYPTED_REASON, FieldMappingStrategy, INTEGER_REGEX_OP, IntegrityStrategy, NativeIntegrity, NoopLogger, RELATION_OPS, REL_FILTER_MAX_DEPTH, REL_FILTER_MAX_NODES, RelationalFieldMapper, ResolvedRelationFilter, SERVER_STAMPED_ON_UPDATE_KEYS, TableMetadata, UniquSelect, acceptedOperatorsHint, aggregateExpressionsNotSupported, aggregateFailure, aliasTargetOf, andFilters, assertGeoPoint, assertNoVersionWrites, bucketSourceVerdict, bucketTimeZoneUnavailable, buildDbValidator, buildValidationContext, canFilterLeaf, checkHavingKeys, classifyQueryPath, collectQueryPaths, computeInsights, containsRelationFilter, createChangeRecorder, createDbValidatorPlugin, createFailureCollector, decomposePatch, defaultFulltextIndex, deletePath, describeFulltext, evaluateExpr, findAncestorInSet, fkColumns, fkKey, forEachResolvedRelation, forceNavNonOptional, geoIndexNotFoundMessage, getDbFieldOp, getKeyProps, getPath, groupSourceVerdict, guardAggregate, guardFilter, guardPath, guardPaths, guardQuery, hasRelationOp, isAtscriptDbView, isBucketableField, isColumnTypeChanged, isConflict, isDbEntityType, isDbFieldOp, isEmptyObject, isFieldRef, isGeoIndexableType, isGeoPointType, isJsonValueField, isNavRelation, isPlainObject, isPrimitive, isRelationOp, isResolvedRelationFilter, isRetryableDbError, isServerStampedOnUpdate, isVersionExemptPatch, isViewType, jsonValueAncestor, narrowerFilterOps, normalizeComputedSelect, numericOperandProblem, queryReadColumns, reconcileCas, relationStaticFilter, resolveCalendarBuckets, resolveDesignType, searchIndexNotFoundMessage, searchMemberKind, searchTermInteger, selfOrAncestor, separateCas, separateFieldOps, sortFieldNames, splitFulltextFields, stableJson, tableNameOf, translateQueryTree, uniqueKeyTuple, unsupportedOperatorMessage, vectorIndexNotFoundMessage, walkFilter, withOptimisticRetry };
|
|
563
|
+
export { $cas, $dec, $inc, $insert, $mul, $remove, $replace, $update, $upsert, ADAPTER_FILTER_REASON, ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, ALL_ISOLATION_LEVELS, ALL_VIEW_CAPABILITIES, ApplicationIntegrity, AtscriptDbReadable, AtscriptDbTable, AtscriptDbView, BaseDbAdapter, CasExhaustedError, CasMismatchError, DEFAULT_DB_SPACE, DbEncryption, DbError, DbSpace, DocumentFieldMapper, ENCRYPTED_REASON, FieldMappingStrategy, INTEGER_REGEX_OP, IntegrityStrategy, NativeIntegrity, NoopLogger, RELATION_OPS, REL_FILTER_MAX_DEPTH, REL_FILTER_MAX_NODES, RelationalFieldMapper, ResolvedRelationFilter, SERVER_STAMPED_ON_UPDATE_KEYS, TableMetadata, UniquSelect, acceptedOperatorsHint, aggregateExpressionsNotSupported, aggregateFailure, aliasTargetOf, andFilters, assertGeoPoint, assertNoVersionWrites, bucketSourceVerdict, bucketTimeZoneUnavailable, buildDbValidator, buildValidationContext, canFilterLeaf, checkHavingKeys, classifyQueryPath, collectQueryPaths, computeInsights, constraintViolation, containsRelationFilter, createChangeRecorder, createDbValidatorPlugin, createFailureCollector, decomposePatch, defaultFulltextIndex, deletePath, describeFulltext, evaluateExpr, findAncestorInSet, fkColumns, fkKey, forEachResolvedRelation, forceNavNonOptional, geoIndexNotFoundMessage, getDbFieldOp, getKeyProps, getPath, groupSourceVerdict, guardAggregate, guardFilter, guardPath, guardPaths, guardQuery, hasRelationOp, isAtscriptDbView, isBucketableField, isColumnTypeChanged, isConflict, isDbEntityType, isDbFieldOp, isEmptyObject, isFieldRef, isGeoIndexableType, isGeoPointType, isJsonValueField, isNavRelation, isPlainObject, isPrimitive, isRelationOp, isResolvedRelationFilter, isRetryableDbError, isServerStampedOnUpdate, isVersionExemptPatch, isViewType, jsonValueAncestor, narrowerFilterOps, nestDottedWith, normalizeComputedSelect, numericOperandProblem, queryReadColumns, reconcileCas, relationStaticFilter, resolveCalendarBuckets, resolveDesignType, resolveFulltextIndex, searchIndexNotFoundMessage, searchMemberKind, searchTermInteger, selfOrAncestor, separateCas, separateFieldOps, soleUnionMember, sortFieldNames, splitFulltextFields, stableJson, tableNameOf, translateQueryTree, uniqueKeyTuple, unsupportedOperatorMessage, vectorIndexNotFoundMessage, walkFilter, withOptimisticRetry };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as DepthLimitExceededError, l as invalidQuery, r as DbError } from "./db-error-BMy4-OWq.mjs";
|
|
2
2
|
import { a as isPlainObject, r as getPath } from "./object-PQ1J8jri.mjs";
|
|
3
3
|
import { ValidatorError } from "@atscript/typescript/utils";
|
|
4
4
|
import { isRelationOp } from "@uniqu/core";
|
|
@@ -740,10 +740,18 @@ function keyTupleString(source, fields, plain = false) {
|
|
|
740
740
|
* get paths like `comments[0].authorId` instead of just `authorId`.
|
|
741
741
|
*/
|
|
742
742
|
function prefixErrorPaths(errors, prefix) {
|
|
743
|
-
return errors.map((err) =>
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
743
|
+
return errors.map((err) => {
|
|
744
|
+
const out = {
|
|
745
|
+
...err,
|
|
746
|
+
path: err.path ? `${prefix}.${err.path}` : prefix
|
|
747
|
+
};
|
|
748
|
+
const params = err.params;
|
|
749
|
+
if (params && Array.isArray(params.fields)) out.params = {
|
|
750
|
+
...params,
|
|
751
|
+
fields: params.fields.map((f) => `${prefix}.${String(f)}`)
|
|
752
|
+
};
|
|
753
|
+
return out;
|
|
754
|
+
});
|
|
747
755
|
}
|
|
748
756
|
/**
|
|
749
757
|
* Wraps an async nested operation and prefixes error paths with the nav field context.
|
|
@@ -758,41 +766,184 @@ async function wrapNestedError(navField, fn) {
|
|
|
758
766
|
}
|
|
759
767
|
}
|
|
760
768
|
/**
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
* the error with all FK field names from table metadata.
|
|
769
|
+
* Rethrows a write's constraint error normalized against the table's
|
|
770
|
+
* metadata (see {@link normalizeWriteError}); anything else as is.
|
|
764
771
|
*/
|
|
765
|
-
async function
|
|
772
|
+
async function normalizeWriteErrors(meta, tableName, fn) {
|
|
766
773
|
try {
|
|
767
774
|
return await fn();
|
|
768
775
|
} catch (error) {
|
|
769
|
-
|
|
770
|
-
const msg = error.errors[0]?.message ?? error.message;
|
|
771
|
-
const errors = [];
|
|
772
|
-
for (const [, fk] of meta.foreignKeys) for (const field of fk.fields) errors.push({
|
|
773
|
-
path: field,
|
|
774
|
-
message: msg
|
|
775
|
-
});
|
|
776
|
-
throw new DbError("FK_VIOLATION", errors.length > 0 ? errors : error.errors);
|
|
777
|
-
}
|
|
778
|
-
throw error;
|
|
776
|
+
throw normalizeWriteError(meta, tableName, error);
|
|
779
777
|
}
|
|
780
778
|
}
|
|
781
779
|
/**
|
|
782
|
-
*
|
|
783
|
-
*
|
|
780
|
+
* The table layer's constraint-error normaliser (since 0.1.165). An adapter
|
|
781
|
+
* reports a unique / foreign-key violation with whatever the engine told it
|
|
782
|
+
* ({@link constraintViolation}: columns, constraint / index name); this
|
|
783
|
+
* resolves that against the table's indexes and foreign keys into ONE entry
|
|
784
|
+
* per logical field of the violated constraint, each with a `code`
|
|
785
|
+
* (`UNIQUE` / `FK_MISSING` / `FK_RESTRICT`), `params` and the engine's raw
|
|
786
|
+
* text in `details`.
|
|
787
|
+
*
|
|
788
|
+
* An `FK_VIOLATION` with no hint and no path (an adapter that can't tell
|
|
789
|
+
* which key failed) lists every foreign-key field, `params.ambiguous: true`.
|
|
790
|
+
* Errors already normalized, or of other codes, pass through unchanged.
|
|
791
|
+
*/
|
|
792
|
+
function normalizeWriteError(meta, tableName, error) {
|
|
793
|
+
if (!(error instanceof DbError)) return error;
|
|
794
|
+
const raw = rawText(error);
|
|
795
|
+
if (error.code === "FK_VIOLATION" && error.hint?.referencedBy) return new DbError("FK_VIOLATION", [fkRestrictEntry(tableName, {
|
|
796
|
+
op: "update",
|
|
797
|
+
referencedBy: error.hint.referencedBy,
|
|
798
|
+
details: raw
|
|
799
|
+
})]);
|
|
800
|
+
if (error.hint && error.code === "CONFLICT") return new DbError("CONFLICT", uniqueEntries(meta, error.hint, raw));
|
|
801
|
+
if (error.code === "FK_VIOLATION" && (error.hint || error.errors.every((e) => !e.path))) return new DbError("FK_VIOLATION", fkEntries(meta, error.hint, raw));
|
|
802
|
+
return error;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Wraps a delete: a native foreign-key refusal (`FK_VIOLATION`, e.g. a
|
|
806
|
+
* RESTRICT) is rethrown as `CONFLICT` (409) with one `FK_RESTRICT` entry —
|
|
807
|
+
* `params { table, referencedBy? }`, the engine text in `details`.
|
|
784
808
|
*/
|
|
785
809
|
async function remapDeleteFkViolation(tableName, fn) {
|
|
786
810
|
try {
|
|
787
811
|
return await fn();
|
|
788
812
|
} catch (error) {
|
|
789
|
-
if (error instanceof DbError && error.code === "FK_VIOLATION") throw new DbError("CONFLICT", [{
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
}]);
|
|
813
|
+
if (error instanceof DbError && error.code === "FK_VIOLATION") throw new DbError("CONFLICT", [fkRestrictEntry(tableName, {
|
|
814
|
+
referencedBy: error.hint?.referencedBy,
|
|
815
|
+
details: rawText(error)
|
|
816
|
+
})]);
|
|
793
817
|
throw error;
|
|
794
818
|
}
|
|
795
819
|
}
|
|
820
|
+
/** The engine text an adapter error carries (its first entry's `details`, else its message). */
|
|
821
|
+
function rawText(error) {
|
|
822
|
+
return error.errors[0]?.details ?? error.errors[0]?.message ?? error.message;
|
|
823
|
+
}
|
|
824
|
+
/** Whether `names` is exactly the column `set` (in any order). */
|
|
825
|
+
function sameColumns(names, set) {
|
|
826
|
+
return names.length === set.size && names.every((n) => set.has(n));
|
|
827
|
+
}
|
|
828
|
+
/** A physical column's logical path (the column itself when unmapped). */
|
|
829
|
+
function logicalPath(meta, column) {
|
|
830
|
+
return meta.physicalToPath.get(column) ?? column;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* The `FK_RESTRICT` entry of a write blocked by referencing rows — a delete
|
|
834
|
+
* (default) or an update of a referenced key. One source for the native
|
|
835
|
+
* refusal and the application-level RESTRICT check.
|
|
836
|
+
* @internal
|
|
837
|
+
*/
|
|
838
|
+
function fkRestrictEntry(tableName, opts = {}) {
|
|
839
|
+
const { referencedBy, count, details, fields } = opts;
|
|
840
|
+
return {
|
|
841
|
+
path: tableName,
|
|
842
|
+
message: `Cannot ${opts.op === "update" ? "update" : "delete from"} "${tableName}": referenced by ${referencedBy === void 0 ? "child records" : `${count === void 0 ? "records" : `${count} record(s)`} in "${referencedBy}"${fields ? ` (${fields.join(", ")})` : ""}`} (RESTRICT)`,
|
|
843
|
+
code: "FK_RESTRICT",
|
|
844
|
+
params: {
|
|
845
|
+
table: tableName,
|
|
846
|
+
...referencedBy === void 0 ? {} : { referencedBy },
|
|
847
|
+
...fields ? { fields: [...fields] } : {}
|
|
848
|
+
},
|
|
849
|
+
...details === void 0 ? {} : { details }
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* The `FK_MISSING` entries of one foreign key: one per field, the same
|
|
854
|
+
* message on each. @internal
|
|
855
|
+
*/
|
|
856
|
+
function fkMissingEntries(fields, target, details, ambiguous) {
|
|
857
|
+
const message = fields.length === 1 ? `References a non-existent record in "${target}"` : `"${fields.join("\", \"")}" reference a non-existent record in "${target}"`;
|
|
858
|
+
return fields.map((path) => ({
|
|
859
|
+
path,
|
|
860
|
+
message,
|
|
861
|
+
code: "FK_MISSING",
|
|
862
|
+
params: {
|
|
863
|
+
fields: [...fields],
|
|
864
|
+
target,
|
|
865
|
+
...ambiguous ? { ambiguous: true } : {}
|
|
866
|
+
},
|
|
867
|
+
...details === void 0 ? {} : { details }
|
|
868
|
+
}));
|
|
869
|
+
}
|
|
870
|
+
function uniqueEntries(meta, hint, raw) {
|
|
871
|
+
const resolved = resolveUnique(meta, hint);
|
|
872
|
+
const fields = resolved?.fields ?? [];
|
|
873
|
+
if (fields.length === 0) return [{
|
|
874
|
+
path: "",
|
|
875
|
+
message: "Value must be unique",
|
|
876
|
+
code: "UNIQUE",
|
|
877
|
+
params: hint.name ? { index: hint.name } : {},
|
|
878
|
+
details: raw
|
|
879
|
+
}];
|
|
880
|
+
const message = fields.length === 1 ? "Value must be unique" : `The combination of "${fields.join("\", \"")}" must be unique`;
|
|
881
|
+
const params = {
|
|
882
|
+
...resolved?.index ? { index: resolved.index } : {},
|
|
883
|
+
fields: [...fields]
|
|
884
|
+
};
|
|
885
|
+
return fields.map((path) => ({
|
|
886
|
+
path,
|
|
887
|
+
message,
|
|
888
|
+
code: "UNIQUE",
|
|
889
|
+
params,
|
|
890
|
+
details: raw
|
|
891
|
+
}));
|
|
892
|
+
}
|
|
893
|
+
/** The unique key a hint names — by name, then by its exact column set. */
|
|
894
|
+
function resolveUnique(meta, hint) {
|
|
895
|
+
const logical = (column) => logicalPath(meta, column);
|
|
896
|
+
if (hint.primaryKey) return {
|
|
897
|
+
index: "primaryKey",
|
|
898
|
+
fields: [...meta.primaryKeys]
|
|
899
|
+
};
|
|
900
|
+
const uniques = [...meta.indexes.values()].filter((i) => i.type === "unique");
|
|
901
|
+
if (hint.name) {
|
|
902
|
+
const named = uniques.find((i) => i.key === hint.name || i.name === hint.name);
|
|
903
|
+
if (named) return {
|
|
904
|
+
index: named.name,
|
|
905
|
+
fields: named.fields.map((f) => logical(f.name))
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
if (!hint.columns?.length) return void 0;
|
|
909
|
+
const columns = new Set(hint.columns);
|
|
910
|
+
const sameSet = (names) => sameColumns(names, columns);
|
|
911
|
+
const byColumns = uniques.find((i) => sameSet(i.fields.map((f) => f.name)));
|
|
912
|
+
if (byColumns) return {
|
|
913
|
+
index: byColumns.name,
|
|
914
|
+
fields: byColumns.fields.map((f) => logical(f.name))
|
|
915
|
+
};
|
|
916
|
+
if (meta.primaryKeys.length > 0 && sameSet(meta.primaryKeys.map((k) => meta.physicalPath(k)))) return {
|
|
917
|
+
index: "primaryKey",
|
|
918
|
+
fields: [...meta.primaryKeys]
|
|
919
|
+
};
|
|
920
|
+
return { fields: hint.columns.map(logical) };
|
|
921
|
+
}
|
|
922
|
+
function fkEntries(meta, hint, raw) {
|
|
923
|
+
const fks = [...meta.foreignKeys.values()];
|
|
924
|
+
const physical = (fk) => fk.physicalFields ?? fk.fields;
|
|
925
|
+
const columns = hint?.columns;
|
|
926
|
+
if (columns?.length) {
|
|
927
|
+
const set = new Set(columns);
|
|
928
|
+
const fk = fks.find((f) => sameColumns(physical(f), set)) ?? fks.find((f) => physical(f).includes(columns[0]));
|
|
929
|
+
if (fk) return fkMissingEntries(fk.fields, fk.targetTable, raw);
|
|
930
|
+
const fields = columns.map((c) => logicalPath(meta, c));
|
|
931
|
+
return fields.map((path) => ({
|
|
932
|
+
path,
|
|
933
|
+
message: "References a non-existent record",
|
|
934
|
+
code: "FK_MISSING",
|
|
935
|
+
params: { fields },
|
|
936
|
+
details: raw
|
|
937
|
+
}));
|
|
938
|
+
}
|
|
939
|
+
const all = fks.flatMap((fk) => fkMissingEntries(fk.fields, fk.targetTable, raw, fks.length > 1));
|
|
940
|
+
return all.length > 0 ? all : [{
|
|
941
|
+
path: "",
|
|
942
|
+
message: "References a non-existent record",
|
|
943
|
+
code: "FK_MISSING",
|
|
944
|
+
details: raw
|
|
945
|
+
}];
|
|
946
|
+
}
|
|
796
947
|
//#endregion
|
|
797
948
|
//#region src/rel/nested-writer.ts
|
|
798
949
|
/** A TO relation's target table and the local → target foreign key. */
|
|
@@ -1581,4 +1732,4 @@ async function viaReplace(via, targets, parentPK, maxDepth, depth) {
|
|
|
1581
1732
|
await viaLinkTargets(via, targets, parentPK, maxDepth, depth);
|
|
1582
1733
|
}
|
|
1583
1734
|
//#endregion
|
|
1584
|
-
export {
|
|
1735
|
+
export { createRelationFilterHost as A, findFKForRelation as B, sameKey as C, ResolvedRelationFilter as D, REL_FILTER_MAX_NODES as E, relGuardState as F, computeOf as G, resolveRelationTargetTable as H, relationStaticFilter as I, isFieldRef as J, computedOperands as K, resolveRelationFilterTree as L, hasRelationOp as M, isResolvedRelationFilter as N, andFilters as O, noteRelationFilter as P, compositeKey as R, rowMatchesKey as S, REL_FILTER_MAX_DEPTH as T, slicePerGroup as U, findRemoteFK as V, tableNameOf as W, viewExprNullable as X, translateQueryTree as Y, walkViewExpr as Z, fkRestrictEntry as _, batchPatchNestedFrom as a, findRowsByKeys as b, batchReplaceNestedFrom as c, checkDepthOverflow as d, planNestedFromVia as f, fkMissingEntries as g, validateBatch as h, batchInsertNestedVia as i, forEachResolvedRelation as j, containsRelationFilter as k, batchReplaceNestedTo as l, preValidateNestedFrom as m, batchInsertNestedFrom as n, batchPatchNestedTo as o, planPatchNestedTo as p, evaluateExpr as q, batchInsertNestedTo as r, batchPatchNestedVia as s, applyPatchNestedTo as t, batchReplaceNestedVia as u, normalizeWriteErrors as v, uniqueKeyTuple as w, pkTupleKey as x, remapDeleteFkViolation as y, findFKEntryForRelation as z };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_db_error = require("./db-error-
|
|
1
|
+
const require_db_error = require("./db-error-G3sgvL87.cjs");
|
|
2
2
|
const require_object = require("./object-GjsuPNqo.cjs");
|
|
3
3
|
let _atscript_typescript_utils = require("@atscript/typescript/utils");
|
|
4
4
|
let _uniqu_core = require("@uniqu/core");
|
|
@@ -740,10 +740,18 @@ function keyTupleString(source, fields, plain = false) {
|
|
|
740
740
|
* get paths like `comments[0].authorId` instead of just `authorId`.
|
|
741
741
|
*/
|
|
742
742
|
function prefixErrorPaths(errors, prefix) {
|
|
743
|
-
return errors.map((err) =>
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
743
|
+
return errors.map((err) => {
|
|
744
|
+
const out = {
|
|
745
|
+
...err,
|
|
746
|
+
path: err.path ? `${prefix}.${err.path}` : prefix
|
|
747
|
+
};
|
|
748
|
+
const params = err.params;
|
|
749
|
+
if (params && Array.isArray(params.fields)) out.params = {
|
|
750
|
+
...params,
|
|
751
|
+
fields: params.fields.map((f) => `${prefix}.${String(f)}`)
|
|
752
|
+
};
|
|
753
|
+
return out;
|
|
754
|
+
});
|
|
747
755
|
}
|
|
748
756
|
/**
|
|
749
757
|
* Wraps an async nested operation and prefixes error paths with the nav field context.
|
|
@@ -758,41 +766,184 @@ async function wrapNestedError(navField, fn) {
|
|
|
758
766
|
}
|
|
759
767
|
}
|
|
760
768
|
/**
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
* the error with all FK field names from table metadata.
|
|
769
|
+
* Rethrows a write's constraint error normalized against the table's
|
|
770
|
+
* metadata (see {@link normalizeWriteError}); anything else as is.
|
|
764
771
|
*/
|
|
765
|
-
async function
|
|
772
|
+
async function normalizeWriteErrors(meta, tableName, fn) {
|
|
766
773
|
try {
|
|
767
774
|
return await fn();
|
|
768
775
|
} catch (error) {
|
|
769
|
-
|
|
770
|
-
const msg = error.errors[0]?.message ?? error.message;
|
|
771
|
-
const errors = [];
|
|
772
|
-
for (const [, fk] of meta.foreignKeys) for (const field of fk.fields) errors.push({
|
|
773
|
-
path: field,
|
|
774
|
-
message: msg
|
|
775
|
-
});
|
|
776
|
-
throw new require_db_error.DbError("FK_VIOLATION", errors.length > 0 ? errors : error.errors);
|
|
777
|
-
}
|
|
778
|
-
throw error;
|
|
776
|
+
throw normalizeWriteError(meta, tableName, error);
|
|
779
777
|
}
|
|
780
778
|
}
|
|
781
779
|
/**
|
|
782
|
-
*
|
|
783
|
-
*
|
|
780
|
+
* The table layer's constraint-error normaliser (since 0.1.165). An adapter
|
|
781
|
+
* reports a unique / foreign-key violation with whatever the engine told it
|
|
782
|
+
* ({@link constraintViolation}: columns, constraint / index name); this
|
|
783
|
+
* resolves that against the table's indexes and foreign keys into ONE entry
|
|
784
|
+
* per logical field of the violated constraint, each with a `code`
|
|
785
|
+
* (`UNIQUE` / `FK_MISSING` / `FK_RESTRICT`), `params` and the engine's raw
|
|
786
|
+
* text in `details`.
|
|
787
|
+
*
|
|
788
|
+
* An `FK_VIOLATION` with no hint and no path (an adapter that can't tell
|
|
789
|
+
* which key failed) lists every foreign-key field, `params.ambiguous: true`.
|
|
790
|
+
* Errors already normalized, or of other codes, pass through unchanged.
|
|
791
|
+
*/
|
|
792
|
+
function normalizeWriteError(meta, tableName, error) {
|
|
793
|
+
if (!(error instanceof require_db_error.DbError)) return error;
|
|
794
|
+
const raw = rawText(error);
|
|
795
|
+
if (error.code === "FK_VIOLATION" && error.hint?.referencedBy) return new require_db_error.DbError("FK_VIOLATION", [fkRestrictEntry(tableName, {
|
|
796
|
+
op: "update",
|
|
797
|
+
referencedBy: error.hint.referencedBy,
|
|
798
|
+
details: raw
|
|
799
|
+
})]);
|
|
800
|
+
if (error.hint && error.code === "CONFLICT") return new require_db_error.DbError("CONFLICT", uniqueEntries(meta, error.hint, raw));
|
|
801
|
+
if (error.code === "FK_VIOLATION" && (error.hint || error.errors.every((e) => !e.path))) return new require_db_error.DbError("FK_VIOLATION", fkEntries(meta, error.hint, raw));
|
|
802
|
+
return error;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Wraps a delete: a native foreign-key refusal (`FK_VIOLATION`, e.g. a
|
|
806
|
+
* RESTRICT) is rethrown as `CONFLICT` (409) with one `FK_RESTRICT` entry —
|
|
807
|
+
* `params { table, referencedBy? }`, the engine text in `details`.
|
|
784
808
|
*/
|
|
785
809
|
async function remapDeleteFkViolation(tableName, fn) {
|
|
786
810
|
try {
|
|
787
811
|
return await fn();
|
|
788
812
|
} catch (error) {
|
|
789
|
-
if (error instanceof require_db_error.DbError && error.code === "FK_VIOLATION") throw new require_db_error.DbError("CONFLICT", [{
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
}]);
|
|
813
|
+
if (error instanceof require_db_error.DbError && error.code === "FK_VIOLATION") throw new require_db_error.DbError("CONFLICT", [fkRestrictEntry(tableName, {
|
|
814
|
+
referencedBy: error.hint?.referencedBy,
|
|
815
|
+
details: rawText(error)
|
|
816
|
+
})]);
|
|
793
817
|
throw error;
|
|
794
818
|
}
|
|
795
819
|
}
|
|
820
|
+
/** The engine text an adapter error carries (its first entry's `details`, else its message). */
|
|
821
|
+
function rawText(error) {
|
|
822
|
+
return error.errors[0]?.details ?? error.errors[0]?.message ?? error.message;
|
|
823
|
+
}
|
|
824
|
+
/** Whether `names` is exactly the column `set` (in any order). */
|
|
825
|
+
function sameColumns(names, set) {
|
|
826
|
+
return names.length === set.size && names.every((n) => set.has(n));
|
|
827
|
+
}
|
|
828
|
+
/** A physical column's logical path (the column itself when unmapped). */
|
|
829
|
+
function logicalPath(meta, column) {
|
|
830
|
+
return meta.physicalToPath.get(column) ?? column;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* The `FK_RESTRICT` entry of a write blocked by referencing rows — a delete
|
|
834
|
+
* (default) or an update of a referenced key. One source for the native
|
|
835
|
+
* refusal and the application-level RESTRICT check.
|
|
836
|
+
* @internal
|
|
837
|
+
*/
|
|
838
|
+
function fkRestrictEntry(tableName, opts = {}) {
|
|
839
|
+
const { referencedBy, count, details, fields } = opts;
|
|
840
|
+
return {
|
|
841
|
+
path: tableName,
|
|
842
|
+
message: `Cannot ${opts.op === "update" ? "update" : "delete from"} "${tableName}": referenced by ${referencedBy === void 0 ? "child records" : `${count === void 0 ? "records" : `${count} record(s)`} in "${referencedBy}"${fields ? ` (${fields.join(", ")})` : ""}`} (RESTRICT)`,
|
|
843
|
+
code: "FK_RESTRICT",
|
|
844
|
+
params: {
|
|
845
|
+
table: tableName,
|
|
846
|
+
...referencedBy === void 0 ? {} : { referencedBy },
|
|
847
|
+
...fields ? { fields: [...fields] } : {}
|
|
848
|
+
},
|
|
849
|
+
...details === void 0 ? {} : { details }
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* The `FK_MISSING` entries of one foreign key: one per field, the same
|
|
854
|
+
* message on each. @internal
|
|
855
|
+
*/
|
|
856
|
+
function fkMissingEntries(fields, target, details, ambiguous) {
|
|
857
|
+
const message = fields.length === 1 ? `References a non-existent record in "${target}"` : `"${fields.join("\", \"")}" reference a non-existent record in "${target}"`;
|
|
858
|
+
return fields.map((path) => ({
|
|
859
|
+
path,
|
|
860
|
+
message,
|
|
861
|
+
code: "FK_MISSING",
|
|
862
|
+
params: {
|
|
863
|
+
fields: [...fields],
|
|
864
|
+
target,
|
|
865
|
+
...ambiguous ? { ambiguous: true } : {}
|
|
866
|
+
},
|
|
867
|
+
...details === void 0 ? {} : { details }
|
|
868
|
+
}));
|
|
869
|
+
}
|
|
870
|
+
function uniqueEntries(meta, hint, raw) {
|
|
871
|
+
const resolved = resolveUnique(meta, hint);
|
|
872
|
+
const fields = resolved?.fields ?? [];
|
|
873
|
+
if (fields.length === 0) return [{
|
|
874
|
+
path: "",
|
|
875
|
+
message: "Value must be unique",
|
|
876
|
+
code: "UNIQUE",
|
|
877
|
+
params: hint.name ? { index: hint.name } : {},
|
|
878
|
+
details: raw
|
|
879
|
+
}];
|
|
880
|
+
const message = fields.length === 1 ? "Value must be unique" : `The combination of "${fields.join("\", \"")}" must be unique`;
|
|
881
|
+
const params = {
|
|
882
|
+
...resolved?.index ? { index: resolved.index } : {},
|
|
883
|
+
fields: [...fields]
|
|
884
|
+
};
|
|
885
|
+
return fields.map((path) => ({
|
|
886
|
+
path,
|
|
887
|
+
message,
|
|
888
|
+
code: "UNIQUE",
|
|
889
|
+
params,
|
|
890
|
+
details: raw
|
|
891
|
+
}));
|
|
892
|
+
}
|
|
893
|
+
/** The unique key a hint names — by name, then by its exact column set. */
|
|
894
|
+
function resolveUnique(meta, hint) {
|
|
895
|
+
const logical = (column) => logicalPath(meta, column);
|
|
896
|
+
if (hint.primaryKey) return {
|
|
897
|
+
index: "primaryKey",
|
|
898
|
+
fields: [...meta.primaryKeys]
|
|
899
|
+
};
|
|
900
|
+
const uniques = [...meta.indexes.values()].filter((i) => i.type === "unique");
|
|
901
|
+
if (hint.name) {
|
|
902
|
+
const named = uniques.find((i) => i.key === hint.name || i.name === hint.name);
|
|
903
|
+
if (named) return {
|
|
904
|
+
index: named.name,
|
|
905
|
+
fields: named.fields.map((f) => logical(f.name))
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
if (!hint.columns?.length) return void 0;
|
|
909
|
+
const columns = new Set(hint.columns);
|
|
910
|
+
const sameSet = (names) => sameColumns(names, columns);
|
|
911
|
+
const byColumns = uniques.find((i) => sameSet(i.fields.map((f) => f.name)));
|
|
912
|
+
if (byColumns) return {
|
|
913
|
+
index: byColumns.name,
|
|
914
|
+
fields: byColumns.fields.map((f) => logical(f.name))
|
|
915
|
+
};
|
|
916
|
+
if (meta.primaryKeys.length > 0 && sameSet(meta.primaryKeys.map((k) => meta.physicalPath(k)))) return {
|
|
917
|
+
index: "primaryKey",
|
|
918
|
+
fields: [...meta.primaryKeys]
|
|
919
|
+
};
|
|
920
|
+
return { fields: hint.columns.map(logical) };
|
|
921
|
+
}
|
|
922
|
+
function fkEntries(meta, hint, raw) {
|
|
923
|
+
const fks = [...meta.foreignKeys.values()];
|
|
924
|
+
const physical = (fk) => fk.physicalFields ?? fk.fields;
|
|
925
|
+
const columns = hint?.columns;
|
|
926
|
+
if (columns?.length) {
|
|
927
|
+
const set = new Set(columns);
|
|
928
|
+
const fk = fks.find((f) => sameColumns(physical(f), set)) ?? fks.find((f) => physical(f).includes(columns[0]));
|
|
929
|
+
if (fk) return fkMissingEntries(fk.fields, fk.targetTable, raw);
|
|
930
|
+
const fields = columns.map((c) => logicalPath(meta, c));
|
|
931
|
+
return fields.map((path) => ({
|
|
932
|
+
path,
|
|
933
|
+
message: "References a non-existent record",
|
|
934
|
+
code: "FK_MISSING",
|
|
935
|
+
params: { fields },
|
|
936
|
+
details: raw
|
|
937
|
+
}));
|
|
938
|
+
}
|
|
939
|
+
const all = fks.flatMap((fk) => fkMissingEntries(fk.fields, fk.targetTable, raw, fks.length > 1));
|
|
940
|
+
return all.length > 0 ? all : [{
|
|
941
|
+
path: "",
|
|
942
|
+
message: "References a non-existent record",
|
|
943
|
+
code: "FK_MISSING",
|
|
944
|
+
details: raw
|
|
945
|
+
}];
|
|
946
|
+
}
|
|
796
947
|
//#endregion
|
|
797
948
|
//#region src/rel/nested-writer.ts
|
|
798
949
|
/** A TO relation's target table and the local → target foreign key. */
|
|
@@ -1701,12 +1852,6 @@ Object.defineProperty(exports, "createRelationFilterHost", {
|
|
|
1701
1852
|
return createRelationFilterHost;
|
|
1702
1853
|
}
|
|
1703
1854
|
});
|
|
1704
|
-
Object.defineProperty(exports, "enrichFkViolation", {
|
|
1705
|
-
enumerable: true,
|
|
1706
|
-
get: function() {
|
|
1707
|
-
return enrichFkViolation;
|
|
1708
|
-
}
|
|
1709
|
-
});
|
|
1710
1855
|
Object.defineProperty(exports, "evaluateExpr", {
|
|
1711
1856
|
enumerable: true,
|
|
1712
1857
|
get: function() {
|
|
@@ -1737,6 +1882,18 @@ Object.defineProperty(exports, "findRowsByKeys", {
|
|
|
1737
1882
|
return findRowsByKeys;
|
|
1738
1883
|
}
|
|
1739
1884
|
});
|
|
1885
|
+
Object.defineProperty(exports, "fkMissingEntries", {
|
|
1886
|
+
enumerable: true,
|
|
1887
|
+
get: function() {
|
|
1888
|
+
return fkMissingEntries;
|
|
1889
|
+
}
|
|
1890
|
+
});
|
|
1891
|
+
Object.defineProperty(exports, "fkRestrictEntry", {
|
|
1892
|
+
enumerable: true,
|
|
1893
|
+
get: function() {
|
|
1894
|
+
return fkRestrictEntry;
|
|
1895
|
+
}
|
|
1896
|
+
});
|
|
1740
1897
|
Object.defineProperty(exports, "forEachResolvedRelation", {
|
|
1741
1898
|
enumerable: true,
|
|
1742
1899
|
get: function() {
|
|
@@ -1761,6 +1918,12 @@ Object.defineProperty(exports, "isResolvedRelationFilter", {
|
|
|
1761
1918
|
return isResolvedRelationFilter;
|
|
1762
1919
|
}
|
|
1763
1920
|
});
|
|
1921
|
+
Object.defineProperty(exports, "normalizeWriteErrors", {
|
|
1922
|
+
enumerable: true,
|
|
1923
|
+
get: function() {
|
|
1924
|
+
return normalizeWriteErrors;
|
|
1925
|
+
}
|
|
1926
|
+
});
|
|
1764
1927
|
Object.defineProperty(exports, "noteRelationFilter", {
|
|
1765
1928
|
enumerable: true,
|
|
1766
1929
|
get: function() {
|
package/dist/ops.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_db_error = require("./db-error-
|
|
2
|
+
const require_db_error = require("./db-error-G3sgvL87.cjs");
|
|
3
3
|
//#region src/ops.ts
|
|
4
4
|
/** Increment a numeric field by `value` (default 1). */
|
|
5
5
|
function $inc(value = 1) {
|
package/dist/ops.mjs
CHANGED