@atscript/db 0.1.82 → 0.1.84
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/{db-error-D8tQhNgM.cjs → db-error-CrtPzaZ-.cjs} +24 -0
- package/dist/{db-error-Cepx-RsQ.mjs → db-error-yx2jdg8B.mjs} +19 -1
- package/dist/{db-readable-B2TRNZHv.d.mts → db-readable-BUUVDkLf.d.mts} +18 -4
- package/dist/{db-readable-BSrIy08e.d.cts → db-readable-DgIxrEVQ.d.cts} +18 -4
- package/dist/{db-space-Dx8x-Ke1.d.cts → db-space-CZooARt_.d.mts} +2 -2
- package/dist/{db-space-SUFqMvKz.d.mts → db-space-DiErv7Q1.d.cts} +2 -2
- package/dist/{db-view-CqEIhTFa.mjs → db-view-Dfw-FP5o.mjs} +126 -29
- package/dist/{db-view-Bf5P3_7k.cjs → db-view-da5OTSRH.cjs} +130 -27
- package/dist/index.cjs +57 -3
- package/dist/index.d.cts +76 -5
- package/dist/index.d.mts +76 -5
- package/dist/index.mjs +54 -5
- package/dist/{nested-writer-CT2rLURx.mjs → nested-writer-CVMRAPoF.mjs} +1 -1
- package/dist/{nested-writer-v_LPR1yJ.cjs → nested-writer-Chl_zySG.cjs} +1 -1
- package/dist/{ops-C61kelof.d.mts → ops-Blqr0ipy.d.mts} +48 -1
- package/dist/{ops-DXJ4Zw0P.d.cts → ops-lzmfzuY9.d.cts} +48 -1
- package/dist/ops.cjs +73 -0
- package/dist/ops.d.cts +2 -2
- package/dist/ops.d.mts +2 -2
- package/dist/ops.mjs +72 -1
- package/dist/plugin.cjs +24 -1
- package/dist/plugin.mjs +24 -1
- package/dist/rel.cjs +1 -1
- package/dist/rel.d.cts +1 -1
- package/dist/rel.d.mts +1 -1
- package/dist/rel.mjs +1 -1
- package/dist/sync.cjs +1 -1
- package/dist/sync.d.cts +2 -2
- package/dist/sync.d.mts +2 -2
- package/dist/sync.mjs +1 -1
- package/dist/{validator-BB5h1Le3.mjs → validator-DTDf9yWe.mjs} +1 -1
- package/dist/{validator-BIuw_T0k.cjs → validator-DrmUaZA3.cjs} +1 -1
- package/dist/validator.cjs +2 -1
- package/dist/validator.d.cts +2 -2
- package/dist/validator.d.mts +2 -2
- package/dist/validator.mjs +3 -3
- package/package.json +6 -6
|
@@ -25,7 +25,31 @@ var DepthLimitExceededError = class extends DbError {
|
|
|
25
25
|
this.actual = actual;
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* Thrown by {@link withOptimisticRetry} when `maxAttempts` is reached
|
|
30
|
+
* without a successful CAS commit — the target row kept changing under
|
|
31
|
+
* the read-modify-write loop. Surfaces the attempt count and the
|
|
32
|
+
* last-observed version so callers can log/report the contention.
|
|
33
|
+
*/
|
|
34
|
+
var CasExhaustedError = class extends DbError {
|
|
35
|
+
name = "CasExhaustedError";
|
|
36
|
+
constructor(attempts, lastSeenVersion) {
|
|
37
|
+
const message = `Optimistic concurrency: exhausted ${attempts} attempts; row kept changing under us (last seen version: ${lastSeenVersion ?? "unknown"})`;
|
|
38
|
+
super("CAS_EXHAUSTED", [{
|
|
39
|
+
path: "$cas",
|
|
40
|
+
message
|
|
41
|
+
}], message);
|
|
42
|
+
this.attempts = attempts;
|
|
43
|
+
this.lastSeenVersion = lastSeenVersion;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
28
46
|
//#endregion
|
|
47
|
+
Object.defineProperty(exports, "CasExhaustedError", {
|
|
48
|
+
enumerable: true,
|
|
49
|
+
get: function() {
|
|
50
|
+
return CasExhaustedError;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
29
53
|
Object.defineProperty(exports, "DbError", {
|
|
30
54
|
enumerable: true,
|
|
31
55
|
get: function() {
|
|
@@ -25,5 +25,23 @@ var DepthLimitExceededError = class extends DbError {
|
|
|
25
25
|
this.actual = actual;
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* Thrown by {@link withOptimisticRetry} when `maxAttempts` is reached
|
|
30
|
+
* without a successful CAS commit — the target row kept changing under
|
|
31
|
+
* the read-modify-write loop. Surfaces the attempt count and the
|
|
32
|
+
* last-observed version so callers can log/report the contention.
|
|
33
|
+
*/
|
|
34
|
+
var CasExhaustedError = class extends DbError {
|
|
35
|
+
name = "CasExhaustedError";
|
|
36
|
+
constructor(attempts, lastSeenVersion) {
|
|
37
|
+
const message = `Optimistic concurrency: exhausted ${attempts} attempts; row kept changing under us (last seen version: ${lastSeenVersion ?? "unknown"})`;
|
|
38
|
+
super("CAS_EXHAUSTED", [{
|
|
39
|
+
path: "$cas",
|
|
40
|
+
message
|
|
41
|
+
}], message);
|
|
42
|
+
this.attempts = attempts;
|
|
43
|
+
this.lastSeenVersion = lastSeenVersion;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
28
46
|
//#endregion
|
|
29
|
-
export {
|
|
47
|
+
export { DbError as n, DepthLimitExceededError as r, CasExhaustedError as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as TFieldOps } from "./ops-Blqr0ipy.mjs";
|
|
2
2
|
import { FlatOf, FlatOf as FlatOf$1, NavPropsOf, NavPropsOf as NavPropsOf$1, OwnPropsOf, OwnPropsOf as OwnPropsOf$1, PrimaryKeyOf, PrimaryKeyOf as PrimaryKeyOf$1, TAtscriptAnnotatedType, TAtscriptDataType, TAtscriptTypeObject, TMetadataMap, TSerializedAnnotatedType, TValidatorOptions, TValidatorPlugin, Validator } from "@atscript/typescript/utils";
|
|
3
3
|
import { AggregateControls, AggregateExpr, AggregateExpr as AggregateExpr$1, AggregateFn, AggregateQuery, AggregateQuery as AggregateQuery$1, AggregateResult, FieldOpsFor, FilterExpr, FilterExpr as FilterExpr$1, TypedWithRelation, Uniquery, Uniquery as Uniquery$1, UniqueryControls, UniqueryControls as UniqueryControls$1, UniqueryInsights, WithRelation, WithRelation as WithRelation$1 } from "@uniqu/core";
|
|
4
4
|
|
|
@@ -83,6 +83,8 @@ declare class TableMetadata {
|
|
|
83
83
|
columnMap: Map<string, string>;
|
|
84
84
|
dimensions: string[];
|
|
85
85
|
measures: string[];
|
|
86
|
+
/** Logical field name annotated with `@db.column.version`, if any. */
|
|
87
|
+
versionField?: string;
|
|
86
88
|
/** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
|
|
87
89
|
quantityRefByField: Map<string, string>;
|
|
88
90
|
pathToPhysical: Map<string, string>;
|
|
@@ -236,6 +238,12 @@ interface TMetaResponse {
|
|
|
236
238
|
type: TSerializedAnnotatedType;
|
|
237
239
|
actions: TDbActionInfo[];
|
|
238
240
|
crud: TCrudPermissions;
|
|
241
|
+
/**
|
|
242
|
+
* Physical column name annotated with `@db.column.version`, when the table
|
|
243
|
+
* opts into optimistic concurrency control (OCC). Absent for tables without
|
|
244
|
+
* the annotation, i.e. last-write-wins (default) behavior.
|
|
245
|
+
*/
|
|
246
|
+
versionColumn?: string;
|
|
239
247
|
}
|
|
240
248
|
/** Where the action applies on the UI. */
|
|
241
249
|
type TDbActionLevel = "table" | "row" | "rows";
|
|
@@ -804,7 +812,7 @@ declare abstract class BaseDbAdapter {
|
|
|
804
812
|
* @param patch - The patch payload with array operations.
|
|
805
813
|
* @returns Update result.
|
|
806
814
|
*/
|
|
807
|
-
nativePatch(_filter: FilterExpr, _patch: unknown, _ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
815
|
+
nativePatch(_filter: FilterExpr, _patch: unknown, _ops?: TFieldOps, _expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
808
816
|
/**
|
|
809
817
|
* Called before field flattening begins.
|
|
810
818
|
* Use to extract table-level adapter-specific annotations.
|
|
@@ -951,8 +959,8 @@ declare abstract class BaseDbAdapter {
|
|
|
951
959
|
aggregate(_query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
952
960
|
abstract insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
|
|
953
961
|
abstract insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
|
|
954
|
-
abstract replaceOne(filter: FilterExpr, data: Record<string, unknown
|
|
955
|
-
abstract updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
962
|
+
abstract replaceOne(filter: FilterExpr, data: Record<string, unknown>, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
963
|
+
abstract updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
956
964
|
abstract deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
957
965
|
abstract findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
|
|
958
966
|
abstract findMany(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
@@ -1277,6 +1285,12 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
|
|
|
1277
1285
|
* @internal Adapter-facing surface; not part of the consumer API.
|
|
1278
1286
|
*/
|
|
1279
1287
|
get metaIdPhysical(): string | null;
|
|
1288
|
+
/**
|
|
1289
|
+
* Physical column name of the field annotated with `@db.column.version`, or
|
|
1290
|
+
* `undefined` when the table has no version column. Used by adapters and the
|
|
1291
|
+
* REST integration to drive optimistic concurrency control (OCC).
|
|
1292
|
+
*/
|
|
1293
|
+
get versionColumn(): string | undefined;
|
|
1280
1294
|
/** Dimension fields from `@db.column.dimension`. */
|
|
1281
1295
|
get dimensions(): readonly string[];
|
|
1282
1296
|
/** Measure fields from `@db.column.measure`. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as TFieldOps } from "./ops-lzmfzuY9.cjs";
|
|
2
2
|
import { AggregateControls, AggregateExpr, AggregateExpr as AggregateExpr$1, AggregateFn, AggregateQuery, AggregateQuery as AggregateQuery$1, AggregateResult, FieldOpsFor, FilterExpr, FilterExpr as FilterExpr$1, TypedWithRelation, Uniquery, Uniquery as Uniquery$1, UniqueryControls, UniqueryControls as UniqueryControls$1, UniqueryInsights, WithRelation, WithRelation as WithRelation$1 } from "@uniqu/core";
|
|
3
3
|
import { FlatOf, FlatOf as FlatOf$1, NavPropsOf, NavPropsOf as NavPropsOf$1, OwnPropsOf, OwnPropsOf as OwnPropsOf$1, PrimaryKeyOf, PrimaryKeyOf as PrimaryKeyOf$1, TAtscriptAnnotatedType, TAtscriptDataType, TAtscriptTypeObject, TMetadataMap, TSerializedAnnotatedType, TValidatorOptions, TValidatorPlugin, Validator } from "@atscript/typescript/utils";
|
|
4
4
|
|
|
@@ -83,6 +83,8 @@ declare class TableMetadata {
|
|
|
83
83
|
columnMap: Map<string, string>;
|
|
84
84
|
dimensions: string[];
|
|
85
85
|
measures: string[];
|
|
86
|
+
/** Logical field name annotated with `@db.column.version`, if any. */
|
|
87
|
+
versionField?: string;
|
|
86
88
|
/** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
|
|
87
89
|
quantityRefByField: Map<string, string>;
|
|
88
90
|
pathToPhysical: Map<string, string>;
|
|
@@ -236,6 +238,12 @@ interface TMetaResponse {
|
|
|
236
238
|
type: TSerializedAnnotatedType;
|
|
237
239
|
actions: TDbActionInfo[];
|
|
238
240
|
crud: TCrudPermissions;
|
|
241
|
+
/**
|
|
242
|
+
* Physical column name annotated with `@db.column.version`, when the table
|
|
243
|
+
* opts into optimistic concurrency control (OCC). Absent for tables without
|
|
244
|
+
* the annotation, i.e. last-write-wins (default) behavior.
|
|
245
|
+
*/
|
|
246
|
+
versionColumn?: string;
|
|
239
247
|
}
|
|
240
248
|
/** Where the action applies on the UI. */
|
|
241
249
|
type TDbActionLevel = "table" | "row" | "rows";
|
|
@@ -804,7 +812,7 @@ declare abstract class BaseDbAdapter {
|
|
|
804
812
|
* @param patch - The patch payload with array operations.
|
|
805
813
|
* @returns Update result.
|
|
806
814
|
*/
|
|
807
|
-
nativePatch(_filter: FilterExpr, _patch: unknown, _ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
815
|
+
nativePatch(_filter: FilterExpr, _patch: unknown, _ops?: TFieldOps, _expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
808
816
|
/**
|
|
809
817
|
* Called before field flattening begins.
|
|
810
818
|
* Use to extract table-level adapter-specific annotations.
|
|
@@ -951,8 +959,8 @@ declare abstract class BaseDbAdapter {
|
|
|
951
959
|
aggregate(_query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
952
960
|
abstract insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
|
|
953
961
|
abstract insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
|
|
954
|
-
abstract replaceOne(filter: FilterExpr, data: Record<string, unknown
|
|
955
|
-
abstract updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
962
|
+
abstract replaceOne(filter: FilterExpr, data: Record<string, unknown>, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
963
|
+
abstract updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
956
964
|
abstract deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
957
965
|
abstract findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
|
|
958
966
|
abstract findMany(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
@@ -1277,6 +1285,12 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
|
|
|
1277
1285
|
* @internal Adapter-facing surface; not part of the consumer API.
|
|
1278
1286
|
*/
|
|
1279
1287
|
get metaIdPhysical(): string | null;
|
|
1288
|
+
/**
|
|
1289
|
+
* Physical column name of the field annotated with `@db.column.version`, or
|
|
1290
|
+
* `undefined` when the table has no version column. Used by adapters and the
|
|
1291
|
+
* REST integration to drive optimistic concurrency control (OCC).
|
|
1292
|
+
*/
|
|
1293
|
+
get versionColumn(): string | undefined;
|
|
1280
1294
|
/** Dimension fields from `@db.column.dimension`. */
|
|
1281
1295
|
get dimensions(): readonly string[];
|
|
1282
1296
|
/** Measure fields from `@db.column.measure`. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { B as TDbInsertResult, J as TFkLookupResolver, N as TDbDeleteResult, W as TDbUpdateResult, dt as TableMetadata, it as TTableResolver, o as BaseDbAdapter, ot as TWriteTableResolver, pt as TGenericLogger, t as AtscriptDbReadable, x as TCascadeResolver, z as TDbInsertManyResult } from "./db-readable-
|
|
2
|
-
import { FilterExpr } from "@uniqu/core";
|
|
1
|
+
import { B as TDbInsertResult, J as TFkLookupResolver, N as TDbDeleteResult, W as TDbUpdateResult, dt as TableMetadata, it as TTableResolver, o as BaseDbAdapter, ot as TWriteTableResolver, pt as TGenericLogger, t as AtscriptDbReadable, x as TCascadeResolver, z as TDbInsertManyResult } from "./db-readable-BUUVDkLf.mjs";
|
|
3
2
|
import { AtscriptQueryComparison, AtscriptQueryFieldRef, AtscriptQueryFieldRef as AtscriptQueryFieldRef$1, AtscriptQueryNode, AtscriptQueryNode as AtscriptQueryNode$1, AtscriptRef, FlatOf, NavPropsOf, OwnPropsOf, PrimaryKeyOf, TAtscriptAnnotatedType, TAtscriptDataType, Validator } from "@atscript/typescript/utils";
|
|
3
|
+
import { FilterExpr } from "@uniqu/core";
|
|
4
4
|
|
|
5
5
|
//#region src/strategies/integrity.d.ts
|
|
6
6
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { B as TDbInsertResult, J as TFkLookupResolver, N as TDbDeleteResult, W as TDbUpdateResult, dt as TableMetadata, it as TTableResolver, o as BaseDbAdapter, ot as TWriteTableResolver, pt as TGenericLogger, t as AtscriptDbReadable, x as TCascadeResolver, z as TDbInsertManyResult } from "./db-readable-
|
|
2
|
-
import { AtscriptQueryComparison, AtscriptQueryFieldRef, AtscriptQueryFieldRef as AtscriptQueryFieldRef$1, AtscriptQueryNode, AtscriptQueryNode as AtscriptQueryNode$1, AtscriptRef, FlatOf, NavPropsOf, OwnPropsOf, PrimaryKeyOf, TAtscriptAnnotatedType, TAtscriptDataType, Validator } from "@atscript/typescript/utils";
|
|
1
|
+
import { B as TDbInsertResult, J as TFkLookupResolver, N as TDbDeleteResult, W as TDbUpdateResult, dt as TableMetadata, it as TTableResolver, o as BaseDbAdapter, ot as TWriteTableResolver, pt as TGenericLogger, t as AtscriptDbReadable, x as TCascadeResolver, z as TDbInsertManyResult } from "./db-readable-DgIxrEVQ.cjs";
|
|
3
2
|
import { FilterExpr } from "@uniqu/core";
|
|
3
|
+
import { AtscriptQueryComparison, AtscriptQueryFieldRef, AtscriptQueryFieldRef as AtscriptQueryFieldRef$1, AtscriptQueryNode, AtscriptQueryNode as AtscriptQueryNode$1, AtscriptRef, FlatOf, NavPropsOf, OwnPropsOf, PrimaryKeyOf, TAtscriptAnnotatedType, TAtscriptDataType, Validator } from "@atscript/typescript/utils";
|
|
4
4
|
|
|
5
5
|
//#region src/strategies/integrity.d.ts
|
|
6
6
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as DbError } from "./db-error-yx2jdg8B.mjs";
|
|
2
2
|
import { resolveAlias } from "./agg.mjs";
|
|
3
3
|
import { n as findRemoteFK, t as findFKForRelation } from "./relation-helpers-CLasawQq.mjs";
|
|
4
|
-
import { separateFieldOps } from "./ops.mjs";
|
|
5
|
-
import { i as forceNavNonOptional, r as dbPlugin, s as getKeyProps, t as buildDbValidator } from "./validator-
|
|
6
|
-
import { a as batchPatchNestedTo, c as batchReplaceNestedTo, d as preValidateNestedFrom, f as validateBatch, i as batchPatchNestedFrom, l as batchReplaceNestedVia, m as remapDeleteFkViolation, n as batchInsertNestedTo, o as batchPatchNestedVia, p as enrichFkViolation, r as batchInsertNestedVia, s as batchReplaceNestedFrom, t as batchInsertNestedFrom, u as checkDepthOverflow } from "./nested-writer-
|
|
4
|
+
import { separateCas, separateFieldOps } from "./ops.mjs";
|
|
5
|
+
import { i as forceNavNonOptional, r as dbPlugin, s as getKeyProps, t as buildDbValidator } from "./validator-DTDf9yWe.mjs";
|
|
6
|
+
import { a as batchPatchNestedTo, c as batchReplaceNestedTo, d as preValidateNestedFrom, f as validateBatch, i as batchPatchNestedFrom, l as batchReplaceNestedVia, m as remapDeleteFkViolation, n as batchInsertNestedTo, o as batchPatchNestedVia, p as enrichFkViolation, r as batchInsertNestedVia, s as batchReplaceNestedFrom, t as batchInsertNestedFrom, u as checkDepthOverflow } from "./nested-writer-CVMRAPoF.mjs";
|
|
7
7
|
import { flattenAnnotatedType, isAnnotatedType } from "@atscript/typescript/utils";
|
|
8
8
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
9
9
|
//#region src/logger.ts
|
|
@@ -64,6 +64,8 @@ var TableMetadata = class {
|
|
|
64
64
|
columnMap = /* @__PURE__ */ new Map();
|
|
65
65
|
dimensions = [];
|
|
66
66
|
measures = [];
|
|
67
|
+
/** Logical field name annotated with `@db.column.version`, if any. */
|
|
68
|
+
versionField;
|
|
67
69
|
/** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
|
|
68
70
|
quantityRefByField = /* @__PURE__ */ new Map();
|
|
69
71
|
pathToPhysical = /* @__PURE__ */ new Map();
|
|
@@ -258,6 +260,14 @@ var TableMetadata = class {
|
|
|
258
260
|
if (!hasExplicitIndex) this._addIndexField("plain", fieldName, fieldName);
|
|
259
261
|
}
|
|
260
262
|
if (metadata.has("db.column.measure")) this.measures.push(fieldName);
|
|
263
|
+
if (metadata.has("db.column.version")) if (this.versionField !== void 0) logger.warn(`@db.column.version declared on multiple fields ("${this.versionField}" and "${fieldName}") — only one is allowed; using "${this.versionField}"`);
|
|
264
|
+
else {
|
|
265
|
+
this.versionField = fieldName;
|
|
266
|
+
if (!this.defaults.has(fieldName)) this.defaults.set(fieldName, {
|
|
267
|
+
kind: "value",
|
|
268
|
+
value: "0"
|
|
269
|
+
});
|
|
270
|
+
}
|
|
261
271
|
}
|
|
262
272
|
_addIndexField(type, name, field, opts) {
|
|
263
273
|
const key = indexKey(type, name);
|
|
@@ -1249,6 +1259,17 @@ var AtscriptDbReadable = class {
|
|
|
1249
1259
|
}
|
|
1250
1260
|
return this._metaIdPhysical;
|
|
1251
1261
|
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Physical column name of the field annotated with `@db.column.version`, or
|
|
1264
|
+
* `undefined` when the table has no version column. Used by adapters and the
|
|
1265
|
+
* REST integration to drive optimistic concurrency control (OCC).
|
|
1266
|
+
*/
|
|
1267
|
+
get versionColumn() {
|
|
1268
|
+
this._ensureBuilt();
|
|
1269
|
+
const field = this._meta.versionField;
|
|
1270
|
+
if (field === void 0) return void 0;
|
|
1271
|
+
return this._meta.columnMap.get(field) ?? field;
|
|
1272
|
+
}
|
|
1252
1273
|
/** Dimension fields from `@db.column.dimension`. */
|
|
1253
1274
|
get dimensions() {
|
|
1254
1275
|
this._ensureBuilt();
|
|
@@ -1940,7 +1961,7 @@ var BaseDbAdapter = class {
|
|
|
1940
1961
|
* @param patch - The patch payload with array operations.
|
|
1941
1962
|
* @returns Update result.
|
|
1942
1963
|
*/
|
|
1943
|
-
async nativePatch(_filter, _patch, _ops) {
|
|
1964
|
+
async nativePatch(_filter, _patch, _ops, _expectedVersion) {
|
|
1944
1965
|
throw new Error("Native patch not supported by this adapter");
|
|
1945
1966
|
}
|
|
1946
1967
|
/**
|
|
@@ -2473,6 +2494,37 @@ function decomposeArrayPatch(key, value, fieldType, update, _table) {
|
|
|
2473
2494
|
if (value.$remove !== void 0) update[`${key}.__$remove`] = value.$remove;
|
|
2474
2495
|
if (keyProps.size > 0 && (value.$upsert !== void 0 || value.$update !== void 0 || value.$remove !== void 0)) update[`${key}.__$keys`] = [...keyProps];
|
|
2475
2496
|
}
|
|
2497
|
+
/**
|
|
2498
|
+
* Throws if the payload attempts to write the version column directly.
|
|
2499
|
+
*
|
|
2500
|
+
* The version column is server-managed: the adapter auto-increments it
|
|
2501
|
+
* on every update. Direct writes (plain SET, `$inc`, `$mul`) would
|
|
2502
|
+
* either silently no-op (overwritten by the auto-bump) or corrupt the
|
|
2503
|
+
* OCC invariant. Reject them at the patch layer so the failure is
|
|
2504
|
+
* loud and adapter-agnostic.
|
|
2505
|
+
*
|
|
2506
|
+
* Inspects the payload AFTER `separateCas` has stripped `$cas` (so
|
|
2507
|
+
* that the legitimate `$cas` → expectedVersion path is not flagged).
|
|
2508
|
+
*
|
|
2509
|
+
* The check is intentionally narrow: only the physical column name at
|
|
2510
|
+
* the top level of the (already-decomposed) payload. Nested objects
|
|
2511
|
+
* cannot reach the version column through dot-paths because the
|
|
2512
|
+
* decomposer flattens those into top-level dot-keys; the version
|
|
2513
|
+
* column, being a scalar at the root, cannot be nested.
|
|
2514
|
+
*
|
|
2515
|
+
* Zero-allocation when the version column is not present.
|
|
2516
|
+
*
|
|
2517
|
+
* @throws {DbError} with code `VERSION_COLUMN_WRITE` when the version
|
|
2518
|
+
* column appears as a top-level key in `data` or as the target of a
|
|
2519
|
+
* `$inc`/`$mul` field op.
|
|
2520
|
+
*/
|
|
2521
|
+
function assertNoVersionWrites(data, versionColumn) {
|
|
2522
|
+
if (!(versionColumn in data)) return;
|
|
2523
|
+
throw new DbError("VERSION_COLUMN_WRITE", [{
|
|
2524
|
+
path: versionColumn,
|
|
2525
|
+
message: `Cannot write to version column "${versionColumn}" directly; omit it from the payload or use $cas for conflict detection`
|
|
2526
|
+
}]);
|
|
2527
|
+
}
|
|
2476
2528
|
//#endregion
|
|
2477
2529
|
//#region src/table/db-table.ts
|
|
2478
2530
|
/**
|
|
@@ -2619,7 +2671,13 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
2619
2671
|
const canNest = depth < maxDepth && this._writeTableResolver && this._meta.navFields.size > 0;
|
|
2620
2672
|
if (!canNest && this._meta.navFields.size > 0) checkDepthOverflow(payloads, maxDepth, this._meta);
|
|
2621
2673
|
return enrichFkViolation(this._meta, () => this.adapter.withTransaction(async () => {
|
|
2622
|
-
const
|
|
2674
|
+
const versionColumn = this.versionColumn;
|
|
2675
|
+
const expectedVersions = Array.from({ length: payloads.length });
|
|
2676
|
+
const items = payloads.map((p, i) => {
|
|
2677
|
+
const clone = { ...p };
|
|
2678
|
+
expectedVersions[i] = separateCas(clone, versionColumn);
|
|
2679
|
+
return this._applyDefaults(clone);
|
|
2680
|
+
});
|
|
2623
2681
|
const originals = canNest ? payloads.map((p) => ({ ...p })) : [];
|
|
2624
2682
|
const validator = this.getValidator("bulkReplace");
|
|
2625
2683
|
const ctx = {
|
|
@@ -2634,11 +2692,13 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
2634
2692
|
if (canNest) await preValidateNestedFrom(host, originals);
|
|
2635
2693
|
let matchedCount = 0;
|
|
2636
2694
|
let modifiedCount = 0;
|
|
2637
|
-
for (
|
|
2695
|
+
for (let i = 0; i < items.length; i++) {
|
|
2696
|
+
const data = items[i];
|
|
2638
2697
|
for (const navField of this._meta.navFields) delete data[navField];
|
|
2698
|
+
if (versionColumn !== void 0) assertNoVersionWrites(data, versionColumn);
|
|
2639
2699
|
const filter = this._extractRecordFilter(data);
|
|
2640
2700
|
const prepared = this._fieldMapper.prepareForWrite(data, this._meta, this.adapter);
|
|
2641
|
-
const result = await this.adapter.replaceOne(this._fieldMapper.translateFilter(filter, this._meta), prepared);
|
|
2701
|
+
const result = await this.adapter.replaceOne(this._fieldMapper.translateFilter(filter, this._meta), prepared, expectedVersions[i]);
|
|
2642
2702
|
matchedCount += result.matchedCount;
|
|
2643
2703
|
modifiedCount += result.modifiedCount;
|
|
2644
2704
|
}
|
|
@@ -2671,6 +2731,13 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
2671
2731
|
const canNest = depth < maxDepth && this._writeTableResolver && this._meta.navFields.size > 0;
|
|
2672
2732
|
if (!canNest && this._meta.navFields.size > 0) checkDepthOverflow(payloads, maxDepth, this._meta);
|
|
2673
2733
|
return enrichFkViolation(this._meta, () => this.adapter.withTransaction(async () => {
|
|
2734
|
+
const versionColumn = this.versionColumn;
|
|
2735
|
+
const expectedVersions = Array.from({ length: payloads.length });
|
|
2736
|
+
const cloned = payloads.map((p, i) => {
|
|
2737
|
+
const c = { ...p };
|
|
2738
|
+
expectedVersions[i] = separateCas(c, versionColumn);
|
|
2739
|
+
return c;
|
|
2740
|
+
});
|
|
2674
2741
|
const validator = this.getValidator("bulkUpdate");
|
|
2675
2742
|
const ctx = {
|
|
2676
2743
|
mode: "patch",
|
|
@@ -2678,18 +2745,21 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
2678
2745
|
navFields: this._meta.navFields
|
|
2679
2746
|
};
|
|
2680
2747
|
this._applyDepthCtx(ctx, depth);
|
|
2681
|
-
validateBatch(validator,
|
|
2682
|
-
const originals = canNest ?
|
|
2748
|
+
validateBatch(validator, cloned, ctx);
|
|
2749
|
+
const originals = canNest ? cloned.map((p) => ({ ...p })) : [];
|
|
2683
2750
|
const host = this;
|
|
2684
|
-
if (canNest) await batchPatchNestedTo(host,
|
|
2685
|
-
await this._integrity.validateForeignKeys(
|
|
2751
|
+
if (canNest) await batchPatchNestedTo(host, cloned, maxDepth, depth);
|
|
2752
|
+
await this._integrity.validateForeignKeys(cloned, this._meta, this._fkLookupResolver, this._writeTableResolver, true);
|
|
2686
2753
|
let matchedCount = 0;
|
|
2687
2754
|
let modifiedCount = 0;
|
|
2688
|
-
for (
|
|
2755
|
+
for (let i = 0; i < cloned.length; i++) {
|
|
2756
|
+
const payload = cloned[i];
|
|
2757
|
+
const expectedVersion = expectedVersions[i];
|
|
2689
2758
|
const data = { ...payload };
|
|
2690
2759
|
for (const navField of this._meta.navFields) delete data[navField];
|
|
2691
2760
|
const filter = this._extractRecordFilter(data);
|
|
2692
2761
|
for (const key of Object.keys(filter)) delete data[key];
|
|
2762
|
+
if (versionColumn !== void 0) assertNoVersionWrites(data, versionColumn);
|
|
2693
2763
|
if (_isEmptyObj(data)) {
|
|
2694
2764
|
matchedCount += 1;
|
|
2695
2765
|
modifiedCount += 0;
|
|
@@ -2701,7 +2771,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
2701
2771
|
const ops = separateFieldOps(data);
|
|
2702
2772
|
const translatedOps = ops ? _translateOpsKeys(ops, this._meta) : void 0;
|
|
2703
2773
|
const translatedData = this._fieldMapper.translatePatchKeys(data, this._meta);
|
|
2704
|
-
result = await this.adapter.nativePatch(translatedFilter, translatedData, translatedOps);
|
|
2774
|
+
result = await this.adapter.nativePatch(translatedFilter, translatedData, translatedOps, expectedVersion);
|
|
2705
2775
|
} else {
|
|
2706
2776
|
const update = decomposePatch(data, this);
|
|
2707
2777
|
const ops = separateFieldOps(update);
|
|
@@ -2712,8 +2782,8 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
2712
2782
|
filter: translatedFilter,
|
|
2713
2783
|
controls: {}
|
|
2714
2784
|
}), this);
|
|
2715
|
-
result = await this.adapter.updateOne(translatedFilter, resolved, translatedOps);
|
|
2716
|
-
} else result = await this.adapter.updateOne(translatedFilter, translatedUpdate, translatedOps);
|
|
2785
|
+
result = await this.adapter.updateOne(translatedFilter, resolved, translatedOps, expectedVersion);
|
|
2786
|
+
} else result = await this.adapter.updateOne(translatedFilter, translatedUpdate, translatedOps, expectedVersion);
|
|
2717
2787
|
}
|
|
2718
2788
|
matchedCount += result.matchedCount;
|
|
2719
2789
|
modifiedCount += result.modifiedCount;
|
|
@@ -2746,7 +2816,14 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
2746
2816
|
async updateMany(filter, data) {
|
|
2747
2817
|
this._ensureBuilt();
|
|
2748
2818
|
await this._integrity.validateForeignKeys([data], this._meta, this._fkLookupResolver, this._writeTableResolver, true);
|
|
2749
|
-
const
|
|
2819
|
+
const dataCopy = { ...data };
|
|
2820
|
+
const versionColumn = this.versionColumn;
|
|
2821
|
+
if ("$cas" in dataCopy) throw new DbError("INVALID_QUERY", [{
|
|
2822
|
+
path: "$cas",
|
|
2823
|
+
message: "$cas is not supported on updateMany — use bulkUpdate with per-row $cas for version-locked batch updates"
|
|
2824
|
+
}]);
|
|
2825
|
+
if (versionColumn !== void 0) assertNoVersionWrites(dataCopy, versionColumn);
|
|
2826
|
+
const update = decomposePatch(dataCopy, this);
|
|
2750
2827
|
const ops = separateFieldOps(update);
|
|
2751
2828
|
const translatedOps = ops ? _translateOpsKeys(ops, this._meta) : void 0;
|
|
2752
2829
|
const translatedUpdate = this._fieldMapper.translatePatchKeys(update, this._meta);
|
|
@@ -2787,17 +2864,21 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
2787
2864
|
_applyDefaults(data) {
|
|
2788
2865
|
const nativeValues = this.adapter.supportsNativeValueDefaults();
|
|
2789
2866
|
const nativeFns = this.adapter.nativeDefaultFns();
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
data[field] =
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2867
|
+
const versionField = this._meta.versionField;
|
|
2868
|
+
for (const [field, def] of this._meta.defaults.entries()) {
|
|
2869
|
+
if (field === versionField) continue;
|
|
2870
|
+
if (data[field] === void 0) {
|
|
2871
|
+
if (def.kind === "value" && !nativeValues) {
|
|
2872
|
+
const fieldType = this._meta.flatMap?.get(field);
|
|
2873
|
+
data[field] = (fieldType?.type.kind === "" && fieldType.type.designType) === "string" ? def.value : JSON.parse(def.value);
|
|
2874
|
+
} else if (def.kind === "fn" && !nativeFns.has(def.fn)) switch (def.fn) {
|
|
2875
|
+
case "now":
|
|
2876
|
+
data[field] = Date.now();
|
|
2877
|
+
break;
|
|
2878
|
+
case "uuid":
|
|
2879
|
+
data[field] = crypto.randomUUID();
|
|
2880
|
+
break;
|
|
2881
|
+
}
|
|
2801
2882
|
}
|
|
2802
2883
|
}
|
|
2803
2884
|
return data;
|
|
@@ -2922,6 +3003,22 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
2922
3003
|
const adapterPlugins = this.adapter.getValidatorPlugins();
|
|
2923
3004
|
if (purpose === "insert" || purpose === "patch" || purpose === "bulkReplace") {
|
|
2924
3005
|
const mode = purpose === "bulkReplace" ? "replace" : purpose;
|
|
3006
|
+
const versionField = this._meta.versionField;
|
|
3007
|
+
if (versionField !== void 0) {
|
|
3008
|
+
const plugins = adapterPlugins.length ? [...adapterPlugins, dbPlugin] : [dbPlugin];
|
|
3009
|
+
return this.createValidator({
|
|
3010
|
+
plugins,
|
|
3011
|
+
partial: mode === "patch",
|
|
3012
|
+
replace: (def, path) => {
|
|
3013
|
+
const transformed = forceNavNonOptional(def);
|
|
3014
|
+
if (path === versionField && !transformed.optional) return {
|
|
3015
|
+
...transformed,
|
|
3016
|
+
optional: true
|
|
3017
|
+
};
|
|
3018
|
+
return transformed;
|
|
3019
|
+
}
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
2925
3022
|
return buildDbValidator(this.type, mode, adapterPlugins);
|
|
2926
3023
|
}
|
|
2927
3024
|
if (purpose === "bulkUpdate") {
|
|
@@ -3078,4 +3175,4 @@ var AtscriptDbView = class extends AtscriptDbReadable {
|
|
|
3078
3175
|
}
|
|
3079
3176
|
};
|
|
3080
3177
|
//#endregion
|
|
3081
|
-
export {
|
|
3178
|
+
export { ApplicationIntegrity as a, NativeIntegrity as c, RelationalFieldMapper as d, DocumentFieldMapper as f, NoopLogger as g, TableMetadata as h, decomposePatch as i, AtscriptDbReadable as l, UniquSelect as m, AtscriptDbTable as n, BaseDbAdapter as o, FieldMappingStrategy as p, assertNoVersionWrites as r, IntegrityStrategy as s, AtscriptDbView as t, resolveDesignType as u };
|