@happyvertical/smrt-core 0.40.69 → 0.41.0
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/AGENTS.md +29 -4
- package/README.md +20 -1
- package/agents/change-feed.md +1 -1
- package/agents/query-bounds.md +45 -0
- package/agents/schema-paths.md +786 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +5 -3
- package/dist/cascade.d.ts +120 -0
- package/dist/cascade.d.ts.map +1 -0
- package/dist/cascade.js +430 -0
- package/dist/cascade.js.map +1 -0
- package/dist/change-feed.d.ts +34 -2
- package/dist/change-feed.d.ts.map +1 -1
- package/dist/change-feed.js +52 -11
- package/dist/change-feed.js.map +1 -1
- package/dist/class.d.ts +36 -3
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +87 -9
- package/dist/class.js.map +1 -1
- package/dist/collection-cache.js +0 -0
- package/dist/collection-cache.js.map +1 -1
- package/dist/collection.d.ts +130 -2
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +290 -57
- package/dist/collection.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/database.d.ts +8 -0
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +16 -8
- package/dist/database.js.map +1 -1
- package/dist/db-errors.d.ts +105 -0
- package/dist/db-errors.d.ts.map +1 -0
- package/dist/db-errors.js +382 -0
- package/dist/db-errors.js.map +1 -0
- package/dist/decorators/index.d.ts +80 -6
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +102 -12
- package/dist/decorators/index.js.map +1 -1
- package/dist/dispatch/bus.d.ts.map +1 -1
- package/dist/dispatch/bus.js +4 -3
- package/dist/dispatch/bus.js.map +1 -1
- package/dist/dispatch/collections/Dispatches.d.ts.map +1 -1
- package/dist/dispatch/collections/Dispatches.js +19 -4
- package/dist/dispatch/collections/Dispatches.js.map +1 -1
- package/dist/dispatch/types.d.ts +5 -0
- package/dist/dispatch/types.d.ts.map +1 -1
- package/dist/embedded-write-queue.d.ts +46 -0
- package/dist/embedded-write-queue.d.ts.map +1 -0
- package/dist/embedded-write-queue.js +66 -0
- package/dist/embedded-write-queue.js.map +1 -0
- package/dist/embeddings/storage.d.ts +7 -0
- package/dist/embeddings/storage.d.ts.map +1 -1
- package/dist/embeddings/storage.js +29 -12
- package/dist/embeddings/storage.js.map +1 -1
- package/dist/errors.d.ts +31 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +34 -2
- package/dist/errors.js.map +1 -1
- package/dist/generators/changes-route.d.ts.map +1 -1
- package/dist/generators/changes-route.js +6 -3
- package/dist/generators/changes-route.js.map +1 -1
- package/dist/generators/mcp-runtime-template.d.ts +8 -0
- package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
- package/dist/generators/mcp-runtime-template.js +38 -4
- package/dist/generators/mcp-runtime-template.js.map +1 -1
- package/dist/generators/mcp.d.ts +16 -0
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +41 -3
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/rest.d.ts +22 -0
- package/dist/generators/rest.d.ts.map +1 -1
- package/dist/generators/rest.js +34 -3
- package/dist/generators/rest.js.map +1 -1
- package/dist/hierarchical.js +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -5
- package/dist/interceptors.d.ts +21 -0
- package/dist/interceptors.d.ts.map +1 -1
- package/dist/interceptors.js +27 -1
- package/dist/interceptors.js.map +1 -1
- package/dist/manifest/generator.d.ts.map +1 -1
- package/dist/manifest/generator.js +4 -7
- package/dist/manifest/generator.js.map +1 -1
- package/dist/manifest/static-manifest.js +10 -10
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest/store.js.map +1 -1
- package/dist/manifest.json +19 -19
- package/dist/migrations/differ.d.ts +211 -9
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +613 -50
- package/dist/migrations/differ.js.map +1 -1
- package/dist/migrations/generator.d.ts +31 -4
- package/dist/migrations/generator.d.ts.map +1 -1
- package/dist/migrations/generator.js +49 -5
- package/dist/migrations/generator.js.map +1 -1
- package/dist/migrations/index.d.ts +4 -2
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +6 -3
- package/dist/migrations/minor-units.d.ts +162 -0
- package/dist/migrations/minor-units.d.ts.map +1 -0
- package/dist/migrations/minor-units.js +381 -0
- package/dist/migrations/minor-units.js.map +1 -0
- package/dist/migrations/orchestrate.js +35 -6
- package/dist/migrations/orchestrate.js.map +1 -1
- package/dist/migrations/sqlite-rebuild.d.ts +142 -0
- package/dist/migrations/sqlite-rebuild.d.ts.map +1 -0
- package/dist/migrations/sqlite-rebuild.js +514 -0
- package/dist/migrations/sqlite-rebuild.js.map +1 -0
- package/dist/migrations/tracker.d.ts +114 -1
- package/dist/migrations/tracker.d.ts.map +1 -1
- package/dist/migrations/tracker.js +331 -16
- package/dist/migrations/tracker.js.map +1 -1
- package/dist/migrations/types.d.ts +19 -4
- package/dist/migrations/types.d.ts.map +1 -1
- package/dist/migrations.js +6 -3
- package/dist/object.d.ts +142 -10
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +196 -41
- package/dist/object.js.map +1 -1
- package/dist/postgres-timeouts.d.ts +240 -0
- package/dist/postgres-timeouts.d.ts.map +1 -0
- package/dist/postgres-timeouts.js +204 -0
- package/dist/postgres-timeouts.js.map +1 -0
- package/dist/query-bounds.d.ts +101 -0
- package/dist/query-bounds.d.ts.map +1 -0
- package/dist/query-bounds.js +177 -0
- package/dist/query-bounds.js.map +1 -0
- package/dist/registry/class-registration.d.ts.map +1 -1
- package/dist/registry/class-registration.js +3 -1
- package/dist/registry/class-registration.js.map +1 -1
- package/dist/registry/manifest-field-merge.d.ts +12 -0
- package/dist/registry/manifest-field-merge.d.ts.map +1 -1
- package/dist/registry/manifest-field-merge.js +14 -2
- package/dist/registry/manifest-field-merge.js.map +1 -1
- package/dist/registry/schema-builder.d.ts +22 -1
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +205 -165
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry/types.d.ts +35 -3
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.d.ts +41 -46
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +61 -83
- package/dist/registry.js.map +1 -1
- package/dist/scanner/manifest-generator.d.ts +45 -0
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +92 -28
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/scanner/types.d.ts +5 -0
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js.map +1 -1
- package/dist/schema/conflict-target.d.ts +104 -0
- package/dist/schema/conflict-target.d.ts.map +1 -0
- package/dist/schema/conflict-target.js +129 -0
- package/dist/schema/conflict-target.js.map +1 -0
- package/dist/schema/ddl/base-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/base-strategy.js +2 -2
- package/dist/schema/ddl/base-strategy.js.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.js +2 -1
- package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
- package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/postgres-strategy.js +12 -1
- package/dist/schema/ddl/postgres-strategy.js.map +1 -1
- package/dist/schema/generator.d.ts +307 -6
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +510 -87
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/index-utils.d.ts +120 -0
- package/dist/schema/index-utils.d.ts.map +1 -1
- package/dist/schema/index-utils.js +242 -1
- package/dist/schema/index-utils.js.map +1 -1
- package/dist/schema/index.d.ts +3 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +4 -1
- package/dist/schema/live-parity.d.ts +90 -0
- package/dist/schema/live-parity.d.ts.map +1 -0
- package/dist/schema/live-parity.js +602 -0
- package/dist/schema/live-parity.js.map +1 -0
- package/dist/schema/manifest-schema.d.ts +121 -0
- package/dist/schema/manifest-schema.d.ts.map +1 -0
- package/dist/schema/manifest-schema.js +267 -0
- package/dist/schema/manifest-schema.js.map +1 -0
- package/dist/schema/schema-aggregator.d.ts +24 -10
- package/dist/schema/schema-aggregator.d.ts.map +1 -1
- package/dist/schema/schema-aggregator.js +35 -90
- package/dist/schema/schema-aggregator.js.map +1 -1
- package/dist/schema/system-table-shapes.d.ts +65 -0
- package/dist/schema/system-table-shapes.d.ts.map +1 -0
- package/dist/schema/system-table-shapes.js +187 -0
- package/dist/schema/system-table-shapes.js.map +1 -0
- package/dist/schema/types.d.ts +103 -4
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/utils.d.ts +2 -1
- package/dist/schema/utils.d.ts.map +1 -1
- package/dist/schema/utils.js +5 -3
- package/dist/schema/utils.js.map +1 -1
- package/dist/schema.js +4 -1
- package/dist/smrt-knowledge.json +20 -8
- package/dist/sync/apply.d.ts.map +1 -1
- package/dist/sync/apply.js +9 -16
- package/dist/sync/apply.js.map +1 -1
- package/dist/system/compatibility.d.ts +42 -0
- package/dist/system/compatibility.d.ts.map +1 -1
- package/dist/system/compatibility.js +182 -9
- package/dist/system/compatibility.js.map +1 -1
- package/dist/system/index.d.ts +1 -0
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js +3 -2
- package/dist/system/retention.d.ts +237 -0
- package/dist/system/retention.d.ts.map +1 -0
- package/dist/system/retention.js +497 -0
- package/dist/system/retention.js.map +1 -0
- package/dist/system/schema.d.ts +100 -15
- package/dist/system/schema.d.ts.map +1 -1
- package/dist/system/schema.js +81 -45
- package/dist/system/schema.js.map +1 -1
- package/dist/system/types.d.ts +0 -2
- package/dist/system/types.d.ts.map +1 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +1 -0
- package/dist/testing/database.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +4 -9
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +71 -5
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/web-collections.d.ts.map +1 -1
- package/dist/vite-plugin/web-collections.js +6 -4
- package/dist/vite-plugin/web-collections.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
//#region src/db-errors.ts
|
|
2
|
+
/** Maximum chain depth walked, guarding against pathological nesting. */
|
|
3
|
+
var MAX_CHAIN_DEPTH = 12;
|
|
4
|
+
/** `formatDbError` renders driver codes as `code=<value>` inside its string. */
|
|
5
|
+
var FORMATTED_CODE_PATTERN = /(?:^|[\s,;([{])code=([A-Za-z0-9_]+)/g;
|
|
6
|
+
/**
|
|
7
|
+
* PostgreSQL SQLSTATE codes whose failure is fully determined by the statement
|
|
8
|
+
* and its data. Retrying re-executes the same deterministic rejection.
|
|
9
|
+
*/
|
|
10
|
+
var DETERMINISTIC_SQLSTATES = /* @__PURE__ */ new Map([
|
|
11
|
+
["23000", "check_violation"],
|
|
12
|
+
["23001", "foreign_key_violation"],
|
|
13
|
+
["23502", "not_null_violation"],
|
|
14
|
+
["23503", "foreign_key_violation"],
|
|
15
|
+
["23505", "unique_violation"],
|
|
16
|
+
["23514", "check_violation"],
|
|
17
|
+
["23P01", "exclusion_violation"],
|
|
18
|
+
["22001", "invalid_input"],
|
|
19
|
+
["22003", "invalid_input"],
|
|
20
|
+
["22007", "invalid_input"],
|
|
21
|
+
["22008", "invalid_input"],
|
|
22
|
+
["22023", "invalid_input"],
|
|
23
|
+
["22P02", "invalid_input"],
|
|
24
|
+
["22P03", "invalid_input"],
|
|
25
|
+
["25P02", "aborted_transaction"],
|
|
26
|
+
["42501", "insufficient_privilege"],
|
|
27
|
+
["42601", "syntax_error"],
|
|
28
|
+
["42703", "undefined_object"],
|
|
29
|
+
["42804", "invalid_input"],
|
|
30
|
+
["42883", "undefined_object"],
|
|
31
|
+
["42P01", "undefined_object"],
|
|
32
|
+
["42P07", "undefined_object"],
|
|
33
|
+
["42P10", "undefined_object"],
|
|
34
|
+
["3D000", "undefined_object"],
|
|
35
|
+
["3F000", "undefined_object"]
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* PostgreSQL SQLSTATE codes describing a contention or availability failure.
|
|
39
|
+
* The same statement may well succeed on a later attempt.
|
|
40
|
+
*/
|
|
41
|
+
var TRANSIENT_SQLSTATES = /* @__PURE__ */ new Set([
|
|
42
|
+
"25P03",
|
|
43
|
+
"40001",
|
|
44
|
+
"40003",
|
|
45
|
+
"40P01",
|
|
46
|
+
"53200",
|
|
47
|
+
"53300",
|
|
48
|
+
"55P03",
|
|
49
|
+
"57014",
|
|
50
|
+
"57P01",
|
|
51
|
+
"57P02",
|
|
52
|
+
"57P03",
|
|
53
|
+
"58030",
|
|
54
|
+
"08000",
|
|
55
|
+
"08001",
|
|
56
|
+
"08003",
|
|
57
|
+
"08004",
|
|
58
|
+
"08006",
|
|
59
|
+
"08007",
|
|
60
|
+
"08P01"
|
|
61
|
+
]);
|
|
62
|
+
/**
|
|
63
|
+
* SQLite extended result codes. `better-sqlite3`/`node:sqlite` frequently
|
|
64
|
+
* report the bare `SQLITE_CONSTRAINT` parent code, so the constraint sub-kind
|
|
65
|
+
* is resolved from the message instead (see {@link classifyDialectMessage}).
|
|
66
|
+
*/
|
|
67
|
+
var DETERMINISTIC_SQLITE_CODES = /* @__PURE__ */ new Map([
|
|
68
|
+
["SQLITE_CONSTRAINT_UNIQUE", "unique_violation"],
|
|
69
|
+
["SQLITE_CONSTRAINT_PRIMARYKEY", "unique_violation"],
|
|
70
|
+
["SQLITE_CONSTRAINT_ROWID", "unique_violation"],
|
|
71
|
+
["SQLITE_CONSTRAINT_NOTNULL", "not_null_violation"],
|
|
72
|
+
["SQLITE_CONSTRAINT_FOREIGNKEY", "foreign_key_violation"],
|
|
73
|
+
["SQLITE_CONSTRAINT_CHECK", "check_violation"],
|
|
74
|
+
["SQLITE_CONSTRAINT_TRIGGER", "check_violation"],
|
|
75
|
+
["SQLITE_MISMATCH", "invalid_input"],
|
|
76
|
+
["SQLITE_RANGE", "invalid_input"],
|
|
77
|
+
["SQLITE_TOOBIG", "invalid_input"],
|
|
78
|
+
["SQLITE_READONLY", "insufficient_privilege"],
|
|
79
|
+
["SQLITE_AUTH", "insufficient_privilege"]
|
|
80
|
+
]);
|
|
81
|
+
/** SQLite result codes that clear once contention or a lock resolves. */
|
|
82
|
+
var TRANSIENT_SQLITE_CODES = /* @__PURE__ */ new Set([
|
|
83
|
+
"SQLITE_BUSY",
|
|
84
|
+
"SQLITE_BUSY_SNAPSHOT",
|
|
85
|
+
"SQLITE_BUSY_TIMEOUT",
|
|
86
|
+
"SQLITE_LOCKED",
|
|
87
|
+
"SQLITE_LOCKED_SHAREDCACHE",
|
|
88
|
+
"SQLITE_PROTOCOL"
|
|
89
|
+
]);
|
|
90
|
+
/** Node/libuv socket errno values raised when a pooled connection drops. */
|
|
91
|
+
var TRANSIENT_NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
92
|
+
"ECONNRESET",
|
|
93
|
+
"ECONNREFUSED",
|
|
94
|
+
"ECONNABORTED",
|
|
95
|
+
"EPIPE",
|
|
96
|
+
"ETIMEDOUT",
|
|
97
|
+
"ENETUNREACH",
|
|
98
|
+
"ENETRESET",
|
|
99
|
+
"EHOSTUNREACH",
|
|
100
|
+
"EAI_AGAIN",
|
|
101
|
+
"ENOTFOUND"
|
|
102
|
+
]);
|
|
103
|
+
/**
|
|
104
|
+
* Dialect wording, in precedence order. Only DuckDB has no machine-readable
|
|
105
|
+
* code, but SQLite's bare `SQLITE_CONSTRAINT` and PostgreSQL errors that have
|
|
106
|
+
* lost their code through string formatting are recovered here too.
|
|
107
|
+
*
|
|
108
|
+
* NOT NULL is tested before UNIQUE because DuckDB phrases both with the same
|
|
109
|
+
* `Constraint Error … violates` shape, and foreign-key/check wording must not
|
|
110
|
+
* be mistaken for a unique violation (#1578).
|
|
111
|
+
*/
|
|
112
|
+
var DIALECT_MESSAGE_RULES = [
|
|
113
|
+
{
|
|
114
|
+
kind: "aborted_transaction",
|
|
115
|
+
pattern: /current transaction is aborted/i
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
kind: "not_null_violation",
|
|
119
|
+
pattern: /NOT NULL constraint failed|null value in column .* violates not-null/i
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
kind: "foreign_key_violation",
|
|
123
|
+
pattern: /violates foreign key constraint|FOREIGN KEY constraint failed/i
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
kind: "check_violation",
|
|
127
|
+
pattern: /violates check constraint|CHECK constraint failed/i
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
kind: "unique_violation",
|
|
131
|
+
pattern: /UNIQUE constraint failed|violates unique constraint|violates primary key constraint|duplicate key value violates/i
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
kind: "invalid_input",
|
|
135
|
+
pattern: /invalid input syntax for type|value too long for type/i
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
kind: "undefined_object",
|
|
139
|
+
pattern: /no such table|no such column|(?:relation|column|table|function) "[^"]*" does not exist/i
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
kind: "transient",
|
|
143
|
+
pattern: /database is locked|database table is locked|connection terminated|server closed the connection|too many clients|deadlock detected|could not serialize access/i
|
|
144
|
+
}
|
|
145
|
+
];
|
|
146
|
+
/** SMRT-level codes that already express a driver classification. */
|
|
147
|
+
var SMRT_CODE_KINDS = /* @__PURE__ */ new Map([["VALIDATION_UNIQUE_CONSTRAINT", "unique_violation"], ["DB_CONSTRAINT_VIOLATION", "check_violation"]]);
|
|
148
|
+
/**
|
|
149
|
+
* Reporting precedence among deterministic kinds, most specific first.
|
|
150
|
+
*
|
|
151
|
+
* `aborted_transaction` ranks last on purpose: PostgreSQL reports `25P02` for
|
|
152
|
+
* every statement issued after the transaction already failed, so a chain
|
|
153
|
+
* carrying both it and the constraint that caused the abort must name the
|
|
154
|
+
* constraint. Every kind here is non-retryable, so ranking changes only what
|
|
155
|
+
* is reported, never whether the operation is retried.
|
|
156
|
+
*/
|
|
157
|
+
var DETERMINISTIC_KIND_RANK = /* @__PURE__ */ new Map([
|
|
158
|
+
["unique_violation", 0],
|
|
159
|
+
["not_null_violation", 1],
|
|
160
|
+
["foreign_key_violation", 2],
|
|
161
|
+
["check_violation", 3],
|
|
162
|
+
["exclusion_violation", 4],
|
|
163
|
+
["invalid_input", 5],
|
|
164
|
+
["undefined_object", 6],
|
|
165
|
+
["insufficient_privilege", 7],
|
|
166
|
+
["syntax_error", 8],
|
|
167
|
+
["aborted_transaction", 9]
|
|
168
|
+
]);
|
|
169
|
+
function rankKind(kind) {
|
|
170
|
+
return DETERMINISTIC_KIND_RANK.get(kind) ?? Number.MAX_SAFE_INTEGER;
|
|
171
|
+
}
|
|
172
|
+
function pushUnique(collection, value) {
|
|
173
|
+
if (typeof value !== "string") return;
|
|
174
|
+
const entry = value.trim();
|
|
175
|
+
if (entry && !collection.includes(entry)) collection.push(entry);
|
|
176
|
+
}
|
|
177
|
+
function pushCode(signals, value) {
|
|
178
|
+
pushUnique(signals.codes, value);
|
|
179
|
+
}
|
|
180
|
+
function pushMessage(signals, value) {
|
|
181
|
+
if (typeof value !== "string") return;
|
|
182
|
+
const message = value.trim();
|
|
183
|
+
if (!message || signals.messages.includes(message)) return;
|
|
184
|
+
signals.messages.push(message);
|
|
185
|
+
FORMATTED_CODE_PATTERN.lastIndex = 0;
|
|
186
|
+
let match = FORMATTED_CODE_PATTERN.exec(message);
|
|
187
|
+
while (match) {
|
|
188
|
+
pushCode(signals, match[1]);
|
|
189
|
+
match = FORMATTED_CODE_PATTERN.exec(message);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Walks an error's causal chain, collecting every driver code and message.
|
|
194
|
+
*
|
|
195
|
+
* Visits `cause`, `context.originalError`, `originalError`,
|
|
196
|
+
* `details.causeMessage`/`details.causeMessages` and `AggregateError.errors`,
|
|
197
|
+
* because the SDK, core and consumer wrappers each nest the driver error
|
|
198
|
+
* differently. Cycles and depth are bounded.
|
|
199
|
+
*/
|
|
200
|
+
function collectChainSignals(error) {
|
|
201
|
+
const signals = {
|
|
202
|
+
codes: [],
|
|
203
|
+
messages: [],
|
|
204
|
+
columns: [],
|
|
205
|
+
constraints: []
|
|
206
|
+
};
|
|
207
|
+
const seen = /* @__PURE__ */ new Set();
|
|
208
|
+
const pending = [{
|
|
209
|
+
value: error,
|
|
210
|
+
depth: 0
|
|
211
|
+
}];
|
|
212
|
+
while (pending.length > 0) {
|
|
213
|
+
const entry = pending.pop();
|
|
214
|
+
if (!entry) break;
|
|
215
|
+
const { value, depth } = entry;
|
|
216
|
+
if (value === null || value === void 0 || depth > MAX_CHAIN_DEPTH) continue;
|
|
217
|
+
if (typeof value === "string") {
|
|
218
|
+
pushMessage(signals, value);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (typeof value !== "object" || seen.has(value)) continue;
|
|
222
|
+
seen.add(value);
|
|
223
|
+
const node = value;
|
|
224
|
+
pushCode(signals, node.code);
|
|
225
|
+
pushCode(signals, node.sqlState);
|
|
226
|
+
pushCode(signals, node.sqlstate);
|
|
227
|
+
pushMessage(signals, node.message);
|
|
228
|
+
pushMessage(signals, node.detail);
|
|
229
|
+
pushMessage(signals, node.hint);
|
|
230
|
+
pushUnique(signals.columns, node.column);
|
|
231
|
+
pushUnique(signals.constraints, node.constraint);
|
|
232
|
+
const details = node.details;
|
|
233
|
+
if (details && typeof details === "object") {
|
|
234
|
+
pushMessage(signals, details.causeMessage);
|
|
235
|
+
if (Array.isArray(details.causeMessages)) for (const causeMessage of details.causeMessages) pushMessage(signals, causeMessage);
|
|
236
|
+
}
|
|
237
|
+
const context = node.context;
|
|
238
|
+
const next = [
|
|
239
|
+
node.cause,
|
|
240
|
+
node.originalError,
|
|
241
|
+
context && typeof context === "object" ? context.originalError : void 0
|
|
242
|
+
];
|
|
243
|
+
for (const child of next) if (child !== null && child !== void 0) pending.push({
|
|
244
|
+
value: child,
|
|
245
|
+
depth: depth + 1
|
|
246
|
+
});
|
|
247
|
+
if (Array.isArray(node.errors)) for (const child of node.errors) pending.push({
|
|
248
|
+
value: child,
|
|
249
|
+
depth: depth + 1
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return signals;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Classifies a raw dialect message. Exposed so
|
|
256
|
+
* `SmrtObject.classifyConstraintError()` and this module share one set of
|
|
257
|
+
* regexes rather than drifting apart.
|
|
258
|
+
*/
|
|
259
|
+
function classifyDialectMessage(message) {
|
|
260
|
+
if (!message) return "unknown";
|
|
261
|
+
for (const rule of DIALECT_MESSAGE_RULES) if (rule.pattern.test(message)) return rule.kind;
|
|
262
|
+
return "unknown";
|
|
263
|
+
}
|
|
264
|
+
function isSqlState(code) {
|
|
265
|
+
return /^[0-9A-Z]{5}$/.test(code);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Classifies a database failure by walking its causal chain.
|
|
269
|
+
*
|
|
270
|
+
* Codes win over wording: a PostgreSQL SQLSTATE or SQLite result code is
|
|
271
|
+
* authoritative, and dialect message matching only runs when no code in the
|
|
272
|
+
* chain was recognized (DuckDB, or an error already flattened to a string).
|
|
273
|
+
*
|
|
274
|
+
* Constraint violations are reported ahead of `aborted_transaction` so a chain
|
|
275
|
+
* carrying both surfaces the root cause rather than the downstream symptom;
|
|
276
|
+
* both are deterministic, so retry behavior is identical either way.
|
|
277
|
+
*
|
|
278
|
+
* @param error - Any thrown value. Non-database errors classify as `'unknown'`.
|
|
279
|
+
* @returns The structured classification; never throws.
|
|
280
|
+
*/
|
|
281
|
+
function classifyDatabaseError(error) {
|
|
282
|
+
const signals = collectChainSignals(error);
|
|
283
|
+
const sqlstate = signals.codes.find((code) => isSqlState(code) && (DETERMINISTIC_SQLSTATES.has(code) || TRANSIENT_SQLSTATES.has(code)));
|
|
284
|
+
const base = {
|
|
285
|
+
driverCodes: signals.codes,
|
|
286
|
+
driverMessages: signals.messages,
|
|
287
|
+
...sqlstate ? { sqlstate } : {},
|
|
288
|
+
...signals.columns[0] ? { column: signals.columns[0] } : {},
|
|
289
|
+
...signals.constraints[0] ? { constraint: signals.constraints[0] } : {}
|
|
290
|
+
};
|
|
291
|
+
const deterministicCandidates = [];
|
|
292
|
+
for (const code of signals.codes) {
|
|
293
|
+
const kind = DETERMINISTIC_SQLSTATES.get(code) ?? DETERMINISTIC_SQLITE_CODES.get(code) ?? SMRT_CODE_KINDS.get(code) ?? (code === "SQLITE_CONSTRAINT" ? signals.messages.map(classifyDialectMessage).find((candidate) => DETERMINISTIC_KIND_RANK.has(candidate)) ?? "check_violation" : void 0);
|
|
294
|
+
if (kind) deterministicCandidates.push({
|
|
295
|
+
code,
|
|
296
|
+
kind
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
if (deterministicCandidates.length > 0) {
|
|
300
|
+
const best = deterministicCandidates.reduce((winner, candidate) => rankKind(candidate.kind) < rankKind(winner.kind) ? candidate : winner);
|
|
301
|
+
return {
|
|
302
|
+
...base,
|
|
303
|
+
kind: best.kind,
|
|
304
|
+
deterministic: true,
|
|
305
|
+
retryable: false,
|
|
306
|
+
driverCode: best.code
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
for (const code of signals.codes) {
|
|
310
|
+
if (TRANSIENT_SQLSTATES.has(code) || TRANSIENT_SQLITE_CODES.has(code)) return {
|
|
311
|
+
...base,
|
|
312
|
+
kind: "transient",
|
|
313
|
+
deterministic: false,
|
|
314
|
+
retryable: true,
|
|
315
|
+
driverCode: code
|
|
316
|
+
};
|
|
317
|
+
if (TRANSIENT_NETWORK_CODES.has(code)) return {
|
|
318
|
+
...base,
|
|
319
|
+
kind: "transient",
|
|
320
|
+
deterministic: false,
|
|
321
|
+
retryable: true,
|
|
322
|
+
driverCode: code
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
const messageKinds = signals.messages.map(classifyDialectMessage).filter((kind) => kind !== "unknown");
|
|
326
|
+
const deterministicFromMessage = messageKinds.filter((kind) => DETERMINISTIC_KIND_RANK.has(kind)).sort((left, right) => rankKind(left) - rankKind(right))[0];
|
|
327
|
+
if (deterministicFromMessage) return {
|
|
328
|
+
...base,
|
|
329
|
+
kind: deterministicFromMessage,
|
|
330
|
+
deterministic: true,
|
|
331
|
+
retryable: false
|
|
332
|
+
};
|
|
333
|
+
if (messageKinds.includes("transient")) return {
|
|
334
|
+
...base,
|
|
335
|
+
kind: "transient",
|
|
336
|
+
deterministic: false,
|
|
337
|
+
retryable: true
|
|
338
|
+
};
|
|
339
|
+
return {
|
|
340
|
+
...base,
|
|
341
|
+
kind: "unknown",
|
|
342
|
+
deterministic: false,
|
|
343
|
+
retryable: false
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* True when retrying the statement cannot change the outcome.
|
|
348
|
+
*
|
|
349
|
+
* Used by {@link ErrorUtils.withRetry} to fail fast instead of burning the
|
|
350
|
+
* full 500 + 1000 + 2000 ms backoff on a constraint violation.
|
|
351
|
+
*/
|
|
352
|
+
function isDeterministicDatabaseError(error) {
|
|
353
|
+
return classifyDatabaseError(error).deterministic;
|
|
354
|
+
}
|
|
355
|
+
/** True when the failure is contention or availability and may clear. */
|
|
356
|
+
function isTransientDatabaseError(error) {
|
|
357
|
+
return classifyDatabaseError(error).retryable;
|
|
358
|
+
}
|
|
359
|
+
/** True when a unique or primary-key constraint was violated. */
|
|
360
|
+
function isUniqueViolationError(error) {
|
|
361
|
+
return classifyDatabaseError(error).kind === "unique_violation";
|
|
362
|
+
}
|
|
363
|
+
/** True when a NOT NULL constraint was violated. */
|
|
364
|
+
function isNotNullViolationError(error) {
|
|
365
|
+
return classifyDatabaseError(error).kind === "not_null_violation";
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* True when the failure came from running inside an already-aborted
|
|
369
|
+
* PostgreSQL transaction (SQLSTATE `25P02`).
|
|
370
|
+
*
|
|
371
|
+
* Detected independently of {@link DatabaseErrorKind} so a chain that also
|
|
372
|
+
* carries the root constraint code still answers `true` here.
|
|
373
|
+
*/
|
|
374
|
+
function isAbortedTransactionError(error) {
|
|
375
|
+
const signals = collectChainSignals(error);
|
|
376
|
+
if (signals.codes.includes("25P02")) return true;
|
|
377
|
+
return signals.messages.some((message) => /current transaction is aborted/i.test(message));
|
|
378
|
+
}
|
|
379
|
+
//#endregion
|
|
380
|
+
export { classifyDatabaseError, classifyDialectMessage, isAbortedTransactionError, isDeterministicDatabaseError, isNotNullViolationError, isTransientDatabaseError, isUniqueViolationError };
|
|
381
|
+
|
|
382
|
+
//# sourceMappingURL=db-errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-errors.js","names":[],"sources":["../src/db-errors.ts"],"sourcesContent":["/**\n * Driver-error classification for the persistence path (#2366).\n *\n * `@happyvertical/sql` does not normalize driver errors: every adapter catches\n * the driver failure and rethrows\n * `DatabaseError('Failed to upsert record into table', { …, originalError })`,\n * where `originalError` is the **string** produced by the SDK's `formatDbError`\n * (`\"<driver message>, code=<driver code>, detail=…, hint=…\"`). The typed\n * message therefore carries none of the constraint wording, which is why\n * matching on `error.message` alone silently stopped recognizing constraint\n * violations and let {@link ErrorUtils.withRetry} retry them.\n *\n * This module walks the whole causal chain — `cause`, `context.originalError`\n * (Error *or* formatted string), `originalError`, `details.causeMessages`, and\n * `AggregateError.errors` — collecting every driver code and message it finds,\n * then classifies on the codes first (PostgreSQL SQLSTATE, SQLite result\n * codes, Node network errno) and only falls back to dialect wording for DuckDB,\n * which reports no machine-readable code.\n *\n * The classification separates two independent questions:\n * - `deterministic` — a retry cannot change the outcome (constraint violation,\n * bad input syntax, aborted transaction, missing table). Fail fast.\n * - `retryable` — a retry plausibly can succeed (serialization failure,\n * deadlock, lock timeout, connection loss, `SQLITE_BUSY`).\n *\n * They are not complements: `kind: 'unknown'` means \"no opinion\", leaving the\n * caller's own default policy in charge.\n *\n * @module\n */\n\n/** Coarse classification of a database driver failure. */\nexport type DatabaseErrorKind =\n | 'unique_violation'\n | 'not_null_violation'\n | 'foreign_key_violation'\n | 'check_violation'\n | 'exclusion_violation'\n | 'invalid_input'\n | 'aborted_transaction'\n | 'undefined_object'\n | 'insufficient_privilege'\n | 'syntax_error'\n | 'transient'\n | 'unknown';\n\n/** Structured result of {@link classifyDatabaseError}. */\nexport interface DatabaseErrorClassification {\n /** What kind of failure this is, or `'unknown'` when nothing matched. */\n kind: DatabaseErrorKind;\n /** True when retrying cannot change the outcome. */\n deterministic: boolean;\n /** True when retrying plausibly can succeed. */\n retryable: boolean;\n /** PostgreSQL SQLSTATE recovered from the chain, when present. */\n sqlstate?: string;\n /** Raw driver code that produced the classification, when present. */\n driverCode?: string;\n /** Every driver code found anywhere in the chain, in discovery order. */\n driverCodes: readonly string[];\n /**\n * Every message found anywhere in the chain, in discovery order. Includes\n * the SDK's formatted `originalError` strings and the driver's own\n * `detail`/`hint` fields, which carry the PostgreSQL\n * `Key (col)=(value) already exists.` line used for column recovery.\n */\n driverMessages: readonly string[];\n /**\n * Column named directly by the driver (`node-postgres` sets `error.column`\n * on a `23502`), when one was reported. More reliable than parsing it back\n * out of the message.\n */\n column?: string;\n /** Constraint named directly by the driver (`error.constraint`). */\n constraint?: string;\n}\n\n/** Maximum chain depth walked, guarding against pathological nesting. */\nconst MAX_CHAIN_DEPTH = 12;\n\n/** `formatDbError` renders driver codes as `code=<value>` inside its string. */\nconst FORMATTED_CODE_PATTERN = /(?:^|[\\s,;([{])code=([A-Za-z0-9_]+)/g;\n\n/**\n * PostgreSQL SQLSTATE codes whose failure is fully determined by the statement\n * and its data. Retrying re-executes the same deterministic rejection.\n */\nconst DETERMINISTIC_SQLSTATES = new Map<string, DatabaseErrorKind>([\n // Class 23 — integrity constraint violation\n ['23000', 'check_violation'], // integrity_constraint_violation (generic)\n ['23001', 'foreign_key_violation'], // restrict_violation\n ['23502', 'not_null_violation'],\n ['23503', 'foreign_key_violation'],\n ['23505', 'unique_violation'],\n ['23514', 'check_violation'],\n // An EXCLUDE constraint is not a unique index: the conflicting rows differ on\n // the constrained columns and merely overlap under the operator. Reporting it\n // as `unique_violation` would make `save()` raise\n // VALIDATION_UNIQUE_CONSTRAINT naming a field that did not collide, so it\n // keeps its own kind and stays on the generic DatabaseError path.\n ['23P01', 'exclusion_violation'],\n // Class 22 — data exception (bad literal, overflow, bad uuid cast)\n ['22001', 'invalid_input'], // string_data_right_truncation\n ['22003', 'invalid_input'], // numeric_value_out_of_range\n ['22007', 'invalid_input'], // invalid_datetime_format\n ['22008', 'invalid_input'], // datetime_field_overflow\n ['22023', 'invalid_input'], // invalid_parameter_value\n ['22P02', 'invalid_input'], // invalid_text_representation — the bad-uuid case\n ['22P03', 'invalid_input'], // invalid_binary_representation\n // Class 25 — invalid transaction state. Only `25P02` belongs here:\n // `25P03 idle_in_transaction_session_timeout` means the server *terminated\n // the session*, which is a connection-level failure a fresh connection can\n // survive, so it lives in TRANSIENT_SQLSTATES instead. Calling it\n // deterministic would both block that retry and make\n // `isAbortedTransactionError()` report a timed-out session as an OIDC race\n // conflict in the profiles coordinator.\n ['25P02', 'aborted_transaction'], // in_failed_sql_transaction\n // Class 42 — syntax error or access rule violation\n ['42501', 'insufficient_privilege'],\n ['42601', 'syntax_error'],\n ['42703', 'undefined_object'], // undefined_column\n ['42804', 'invalid_input'], // datatype_mismatch\n ['42883', 'undefined_object'], // undefined_function\n ['42P01', 'undefined_object'], // undefined_table\n ['42P07', 'undefined_object'], // duplicate_table\n // invalid_column_reference — \"no unique or exclusion constraint matching the\n // ON CONFLICT specification\". A conflict target that does not match a real\n // unique index is a schema defect, not contention; retrying it four times\n // only delays the report (see epic #2382 finding A4).\n ['42P10', 'undefined_object'],\n ['3D000', 'undefined_object'], // invalid_catalog_name\n ['3F000', 'undefined_object'], // invalid_schema_name\n]);\n\n/**\n * PostgreSQL SQLSTATE codes describing a contention or availability failure.\n * The same statement may well succeed on a later attempt.\n */\nconst TRANSIENT_SQLSTATES = new Set([\n '25P03', // idle_in_transaction_session_timeout — the session was terminated\n '40001', // serialization_failure\n '40003', // statement_completion_unknown\n '40P01', // deadlock_detected\n '53200', // out_of_memory\n '53300', // too_many_connections\n '55P03', // lock_not_available\n '57014', // query_canceled\n '57P01', // admin_shutdown\n '57P02', // crash_shutdown\n '57P03', // cannot_connect_now\n '58030', // io_error\n '08000', // connection_exception\n '08001', // sqlclient_unable_to_establish_sqlconnection\n '08003', // connection_does_not_exist\n '08004', // sqlserver_rejected_establishment_of_sqlconnection\n '08006', // connection_failure\n '08007', // transaction_resolution_unknown\n '08P01', // protocol_violation\n]);\n\n/**\n * SQLite extended result codes. `better-sqlite3`/`node:sqlite` frequently\n * report the bare `SQLITE_CONSTRAINT` parent code, so the constraint sub-kind\n * is resolved from the message instead (see {@link classifyDialectMessage}).\n */\nconst DETERMINISTIC_SQLITE_CODES = new Map<string, DatabaseErrorKind>([\n ['SQLITE_CONSTRAINT_UNIQUE', 'unique_violation'],\n ['SQLITE_CONSTRAINT_PRIMARYKEY', 'unique_violation'],\n ['SQLITE_CONSTRAINT_ROWID', 'unique_violation'],\n ['SQLITE_CONSTRAINT_NOTNULL', 'not_null_violation'],\n ['SQLITE_CONSTRAINT_FOREIGNKEY', 'foreign_key_violation'],\n ['SQLITE_CONSTRAINT_CHECK', 'check_violation'],\n ['SQLITE_CONSTRAINT_TRIGGER', 'check_violation'],\n ['SQLITE_MISMATCH', 'invalid_input'],\n ['SQLITE_RANGE', 'invalid_input'],\n ['SQLITE_TOOBIG', 'invalid_input'],\n ['SQLITE_READONLY', 'insufficient_privilege'],\n ['SQLITE_AUTH', 'insufficient_privilege'],\n]);\n\n/** SQLite result codes that clear once contention or a lock resolves. */\nconst TRANSIENT_SQLITE_CODES = new Set([\n 'SQLITE_BUSY',\n 'SQLITE_BUSY_SNAPSHOT',\n 'SQLITE_BUSY_TIMEOUT',\n 'SQLITE_LOCKED',\n 'SQLITE_LOCKED_SHAREDCACHE',\n 'SQLITE_PROTOCOL',\n]);\n\n/** Node/libuv socket errno values raised when a pooled connection drops. */\nconst TRANSIENT_NETWORK_CODES = new Set([\n 'ECONNRESET',\n 'ECONNREFUSED',\n 'ECONNABORTED',\n 'EPIPE',\n 'ETIMEDOUT',\n 'ENETUNREACH',\n 'ENETRESET',\n 'EHOSTUNREACH',\n 'EAI_AGAIN',\n 'ENOTFOUND',\n]);\n\n/**\n * Dialect wording, in precedence order. Only DuckDB has no machine-readable\n * code, but SQLite's bare `SQLITE_CONSTRAINT` and PostgreSQL errors that have\n * lost their code through string formatting are recovered here too.\n *\n * NOT NULL is tested before UNIQUE because DuckDB phrases both with the same\n * `Constraint Error … violates` shape, and foreign-key/check wording must not\n * be mistaken for a unique violation (#1578).\n */\nconst DIALECT_MESSAGE_RULES: ReadonlyArray<{\n kind: DatabaseErrorKind;\n pattern: RegExp;\n}> = [\n {\n kind: 'aborted_transaction',\n pattern: /current transaction is aborted/i,\n },\n {\n kind: 'not_null_violation',\n pattern:\n /NOT NULL constraint failed|null value in column .* violates not-null/i,\n },\n {\n // SQLite reports `FOREIGN KEY constraint failed` under the bare\n // `SQLITE_CONSTRAINT` parent code, so without this arm the sub-kind\n // recovery below would mislabel every SQLite FK failure.\n kind: 'foreign_key_violation',\n pattern: /violates foreign key constraint|FOREIGN KEY constraint failed/i,\n },\n {\n kind: 'check_violation',\n pattern: /violates check constraint|CHECK constraint failed/i,\n },\n {\n kind: 'unique_violation',\n pattern:\n /UNIQUE constraint failed|violates unique constraint|violates primary key constraint|duplicate key value violates/i,\n },\n {\n kind: 'invalid_input',\n pattern: /invalid input syntax for type|value too long for type/i,\n },\n {\n // Deliberately narrow: a bare /does not exist/ matches far too much to be\n // safe as a fail-fast signal.\n kind: 'undefined_object',\n pattern:\n /no such table|no such column|(?:relation|column|table|function) \"[^\"]*\" does not exist/i,\n },\n {\n kind: 'transient',\n pattern:\n /database is locked|database table is locked|connection terminated|server closed the connection|too many clients|deadlock detected|could not serialize access/i,\n },\n];\n\n/** SMRT-level codes that already express a driver classification. */\nconst SMRT_CODE_KINDS = new Map<string, DatabaseErrorKind>([\n ['VALIDATION_UNIQUE_CONSTRAINT', 'unique_violation'],\n ['DB_CONSTRAINT_VIOLATION', 'check_violation'],\n]);\n\n/**\n * Reporting precedence among deterministic kinds, most specific first.\n *\n * `aborted_transaction` ranks last on purpose: PostgreSQL reports `25P02` for\n * every statement issued after the transaction already failed, so a chain\n * carrying both it and the constraint that caused the abort must name the\n * constraint. Every kind here is non-retryable, so ranking changes only what\n * is reported, never whether the operation is retried.\n */\nconst DETERMINISTIC_KIND_RANK = new Map<DatabaseErrorKind, number>([\n ['unique_violation', 0],\n ['not_null_violation', 1],\n ['foreign_key_violation', 2],\n ['check_violation', 3],\n ['exclusion_violation', 4],\n ['invalid_input', 5],\n ['undefined_object', 6],\n ['insufficient_privilege', 7],\n ['syntax_error', 8],\n ['aborted_transaction', 9],\n]);\n\nfunction rankKind(kind: DatabaseErrorKind): number {\n return DETERMINISTIC_KIND_RANK.get(kind) ?? Number.MAX_SAFE_INTEGER;\n}\n\ninterface ChainSignals {\n codes: string[];\n messages: string[];\n columns: string[];\n constraints: string[];\n}\n\nfunction pushUnique(collection: string[], value: unknown): void {\n if (typeof value !== 'string') return;\n const entry = value.trim();\n if (entry && !collection.includes(entry)) {\n collection.push(entry);\n }\n}\n\nfunction pushCode(signals: ChainSignals, value: unknown): void {\n pushUnique(signals.codes, value);\n}\n\nfunction pushMessage(signals: ChainSignals, value: unknown): void {\n if (typeof value !== 'string') return;\n const message = value.trim();\n if (!message || signals.messages.includes(message)) return;\n signals.messages.push(message);\n\n // `formatDbError` folds the driver's own `code` into its string, so the\n // machine-readable classification survives even after stringification.\n FORMATTED_CODE_PATTERN.lastIndex = 0;\n let match = FORMATTED_CODE_PATTERN.exec(message);\n while (match) {\n pushCode(signals, match[1]);\n match = FORMATTED_CODE_PATTERN.exec(message);\n }\n}\n\n/**\n * Walks an error's causal chain, collecting every driver code and message.\n *\n * Visits `cause`, `context.originalError`, `originalError`,\n * `details.causeMessage`/`details.causeMessages` and `AggregateError.errors`,\n * because the SDK, core and consumer wrappers each nest the driver error\n * differently. Cycles and depth are bounded.\n */\nfunction collectChainSignals(error: unknown): ChainSignals {\n const signals: ChainSignals = {\n codes: [],\n messages: [],\n columns: [],\n constraints: [],\n };\n const seen = new Set<unknown>();\n const pending: Array<{ value: unknown; depth: number }> = [\n { value: error, depth: 0 },\n ];\n\n while (pending.length > 0) {\n const entry = pending.pop();\n if (!entry) break;\n const { value, depth } = entry;\n if (value === null || value === undefined || depth > MAX_CHAIN_DEPTH) {\n continue;\n }\n\n if (typeof value === 'string') {\n pushMessage(signals, value);\n continue;\n }\n if (typeof value !== 'object' || seen.has(value)) continue;\n seen.add(value);\n\n const node = value as {\n code?: unknown;\n sqlState?: unknown;\n sqlstate?: unknown;\n message?: unknown;\n detail?: unknown;\n hint?: unknown;\n column?: unknown;\n constraint?: unknown;\n cause?: unknown;\n context?: { originalError?: unknown } | unknown;\n originalError?: unknown;\n details?: unknown;\n errors?: unknown;\n };\n\n pushCode(signals, node.code);\n pushCode(signals, node.sqlState);\n pushCode(signals, node.sqlstate);\n pushMessage(signals, node.message);\n // `node-postgres` surfaces the PostgreSQL error fields as properties. On\n // the direct INSERT path nothing stringifies them into the message, so the\n // column would otherwise be unrecoverable (`error.detail` carries\n // \"Key (sku)=(s1) already exists.\" for a 23505; `error.column` names the\n // column outright for a 23502).\n pushMessage(signals, node.detail);\n pushMessage(signals, node.hint);\n pushUnique(signals.columns, node.column);\n pushUnique(signals.constraints, node.constraint);\n\n const details = node.details as\n | { causeMessage?: unknown; causeMessages?: unknown }\n | undefined;\n if (details && typeof details === 'object') {\n pushMessage(signals, details.causeMessage);\n if (Array.isArray(details.causeMessages)) {\n for (const causeMessage of details.causeMessages) {\n pushMessage(signals, causeMessage);\n }\n }\n }\n\n const context = node.context as { originalError?: unknown } | undefined;\n const next = [\n node.cause,\n node.originalError,\n context && typeof context === 'object'\n ? context.originalError\n : undefined,\n ];\n for (const child of next) {\n if (child !== null && child !== undefined) {\n pending.push({ value: child, depth: depth + 1 });\n }\n }\n if (Array.isArray(node.errors)) {\n for (const child of node.errors) {\n pending.push({ value: child, depth: depth + 1 });\n }\n }\n }\n\n return signals;\n}\n\n/**\n * Classifies a raw dialect message. Exposed so\n * `SmrtObject.classifyConstraintError()` and this module share one set of\n * regexes rather than drifting apart.\n */\nexport function classifyDialectMessage(message: string): DatabaseErrorKind {\n if (!message) return 'unknown';\n for (const rule of DIALECT_MESSAGE_RULES) {\n if (rule.pattern.test(message)) return rule.kind;\n }\n return 'unknown';\n}\n\nfunction isSqlState(code: string): boolean {\n return /^[0-9A-Z]{5}$/.test(code);\n}\n\n/**\n * Classifies a database failure by walking its causal chain.\n *\n * Codes win over wording: a PostgreSQL SQLSTATE or SQLite result code is\n * authoritative, and dialect message matching only runs when no code in the\n * chain was recognized (DuckDB, or an error already flattened to a string).\n *\n * Constraint violations are reported ahead of `aborted_transaction` so a chain\n * carrying both surfaces the root cause rather than the downstream symptom;\n * both are deterministic, so retry behavior is identical either way.\n *\n * @param error - Any thrown value. Non-database errors classify as `'unknown'`.\n * @returns The structured classification; never throws.\n */\nexport function classifyDatabaseError(\n error: unknown,\n): DatabaseErrorClassification {\n const signals = collectChainSignals(error);\n const sqlstate = signals.codes.find(\n (code) =>\n isSqlState(code) &&\n (DETERMINISTIC_SQLSTATES.has(code) || TRANSIENT_SQLSTATES.has(code)),\n );\n\n const base = {\n driverCodes: signals.codes,\n driverMessages: signals.messages,\n ...(sqlstate ? { sqlstate } : {}),\n ...(signals.columns[0] ? { column: signals.columns[0] } : {}),\n ...(signals.constraints[0] ? { constraint: signals.constraints[0] } : {}),\n };\n\n // Deterministic codes first: a chain that carries both a constraint code and\n // a downstream contention code must fail fast, not retry.\n const deterministicCandidates: Array<{\n code: string;\n kind: DatabaseErrorKind;\n }> = [];\n for (const code of signals.codes) {\n const kind =\n DETERMINISTIC_SQLSTATES.get(code) ??\n DETERMINISTIC_SQLITE_CODES.get(code) ??\n // SMRT's own typed codes: a `ValidationError` already carrying\n // `VALIDATION_UNIQUE_CONSTRAINT` is the classified form of the same\n // driver failure.\n SMRT_CODE_KINDS.get(code) ??\n // A bare `SQLITE_CONSTRAINT` carries no sub-kind; recover it from the\n // message, which SQLite always phrases explicitly.\n (code === 'SQLITE_CONSTRAINT'\n ? (signals.messages\n .map(classifyDialectMessage)\n .find((candidate) => DETERMINISTIC_KIND_RANK.has(candidate)) ??\n 'check_violation')\n : undefined);\n if (kind) deterministicCandidates.push({ code, kind });\n }\n\n if (deterministicCandidates.length > 0) {\n // Rank rather than take the first discovered: an error whose chain holds\n // both `23505` and the `25P02` it caused must report the root constraint,\n // not the downstream symptom.\n const best = deterministicCandidates.reduce((winner, candidate) =>\n rankKind(candidate.kind) < rankKind(winner.kind) ? candidate : winner,\n );\n return {\n ...base,\n kind: best.kind,\n deterministic: true,\n retryable: false,\n driverCode: best.code,\n };\n }\n\n for (const code of signals.codes) {\n if (TRANSIENT_SQLSTATES.has(code) || TRANSIENT_SQLITE_CODES.has(code)) {\n return {\n ...base,\n kind: 'transient',\n deterministic: false,\n retryable: true,\n driverCode: code,\n };\n }\n if (TRANSIENT_NETWORK_CODES.has(code)) {\n return {\n ...base,\n kind: 'transient',\n deterministic: false,\n retryable: true,\n driverCode: code,\n };\n }\n }\n\n // No recognized code anywhere — fall back to dialect wording. This is the\n // only path DuckDB ever takes.\n const messageKinds = signals.messages\n .map(classifyDialectMessage)\n .filter((kind) => kind !== 'unknown');\n const deterministicFromMessage = messageKinds\n .filter((kind) => DETERMINISTIC_KIND_RANK.has(kind))\n .sort((left, right) => rankKind(left) - rankKind(right))[0];\n if (deterministicFromMessage) {\n return {\n ...base,\n kind: deterministicFromMessage,\n deterministic: true,\n retryable: false,\n };\n }\n if (messageKinds.includes('transient')) {\n return {\n ...base,\n kind: 'transient',\n deterministic: false,\n retryable: true,\n };\n }\n\n return {\n ...base,\n kind: 'unknown',\n deterministic: false,\n retryable: false,\n };\n}\n\n/**\n * True when retrying the statement cannot change the outcome.\n *\n * Used by {@link ErrorUtils.withRetry} to fail fast instead of burning the\n * full 500 + 1000 + 2000 ms backoff on a constraint violation.\n */\nexport function isDeterministicDatabaseError(error: unknown): boolean {\n return classifyDatabaseError(error).deterministic;\n}\n\n/** True when the failure is contention or availability and may clear. */\nexport function isTransientDatabaseError(error: unknown): boolean {\n return classifyDatabaseError(error).retryable;\n}\n\n/** True when a unique or primary-key constraint was violated. */\nexport function isUniqueViolationError(error: unknown): boolean {\n return classifyDatabaseError(error).kind === 'unique_violation';\n}\n\n/** True when a NOT NULL constraint was violated. */\nexport function isNotNullViolationError(error: unknown): boolean {\n return classifyDatabaseError(error).kind === 'not_null_violation';\n}\n\n/**\n * True when the failure came from running inside an already-aborted\n * PostgreSQL transaction (SQLSTATE `25P02`).\n *\n * Detected independently of {@link DatabaseErrorKind} so a chain that also\n * carries the root constraint code still answers `true` here.\n */\nexport function isAbortedTransactionError(error: unknown): boolean {\n const signals = collectChainSignals(error);\n // `25P02` only. `25P03` is a terminated session, not a failed transaction,\n // and reporting it here would make the profiles coordinator treat an\n // idle-timeout disconnect as a provisioning race conflict.\n if (signals.codes.includes('25P02')) {\n return true;\n }\n return signals.messages.some((message) =>\n /current transaction is aborted/i.test(message),\n );\n}\n"],"mappings":"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"}
|
|
@@ -142,6 +142,32 @@ export interface RelationshipFieldOptions extends FieldOptions {
|
|
|
142
142
|
through?: string;
|
|
143
143
|
/** Relationship type */
|
|
144
144
|
type?: 'foreignKey' | 'crossPackageRef' | 'oneToMany' | 'manyToMany';
|
|
145
|
+
/**
|
|
146
|
+
* What happens to this row when the referenced object is deleted (#2371).
|
|
147
|
+
*
|
|
148
|
+
* SMRT emits no DB-level `FOREIGN KEY` constraints, so this is enforced by
|
|
149
|
+
* `SmrtObject.delete()` in the application layer rather than by the engine:
|
|
150
|
+
*
|
|
151
|
+
* - `'CASCADE'` — this row is deleted with the target.
|
|
152
|
+
* - `'SET NULL'` — this column is set to `NULL`. The field must be nullable;
|
|
153
|
+
* declaring it on a `required` field throws `ConfigurationError`.
|
|
154
|
+
* - `'RESTRICT'` — deleting the target throws `DatabaseError` while any row
|
|
155
|
+
* still points at it.
|
|
156
|
+
*
|
|
157
|
+
* When omitted, a column that is part of this class's `conflictColumns`
|
|
158
|
+
* (junction and association rows, which are *identified* by the reference)
|
|
159
|
+
* defaults to `CASCADE`; every other column is left untouched on delete.
|
|
160
|
+
* `@tenantId()` fields are the one exception: `smrt-tenancy` leads a
|
|
161
|
+
* tenant-scoped class's default `conflictColumns` with the tenant column,
|
|
162
|
+
* but that column scopes ownership rather than identifying the row, so it
|
|
163
|
+
* is never defaulted to `CASCADE` — deleting a `Tenant` must not cascade
|
|
164
|
+
* through every tenant-scoped table.
|
|
165
|
+
*
|
|
166
|
+
* Cascaded rows are removed set-based: their `beforeDelete`/`afterDelete`
|
|
167
|
+
* hooks and interceptors do not run and no change-feed entry is written, the
|
|
168
|
+
* same as a database-level `ON DELETE CASCADE`.
|
|
169
|
+
*/
|
|
170
|
+
onDelete?: 'CASCADE' | 'SET NULL' | 'RESTRICT';
|
|
145
171
|
}
|
|
146
172
|
/**
|
|
147
173
|
* Options specific to cross-package references.
|
|
@@ -217,9 +243,34 @@ export declare function field(options?: FieldOptions | NumericFieldOptions | Tex
|
|
|
217
243
|
* For cross-package foreign keys, use a plain `string` property instead to avoid
|
|
218
244
|
* circular dependencies between packages.
|
|
219
245
|
*
|
|
220
|
-
*
|
|
246
|
+
* **No DDL constraint is emitted.** SMRT generates no `FOREIGN KEY` clause on
|
|
247
|
+
* any engine — the relationship is enforced by the framework, not the database
|
|
248
|
+
* (#2371). What that buys you is `loadRelated()`, eager `include:` loading, and
|
|
249
|
+
* the `onDelete` behaviour applied by `SmrtObject.delete()`:
|
|
250
|
+
*
|
|
251
|
+
* ```typescript
|
|
252
|
+
* @foreignKey(Order, { onDelete: 'CASCADE' }) // deleted with the order
|
|
253
|
+
* orderId: string = '';
|
|
254
|
+
*
|
|
255
|
+
* @foreignKey(Customer, { onDelete: 'RESTRICT' }) // blocks the customer delete
|
|
256
|
+
* customerId: string = '';
|
|
257
|
+
* ```
|
|
258
|
+
*
|
|
259
|
+
* Without an `onDelete`, a column that is part of this class's
|
|
260
|
+
* `conflictColumns` defaults to `CASCADE` (this is what cleans up junction
|
|
261
|
+
* rows); any other column is left untouched when the target is deleted.
|
|
262
|
+
*
|
|
263
|
+
* @param relatedClass - The target class constructor, its name as a string, or a
|
|
264
|
+
* `() => Target` thunk. A thunk is **invoked at decoration time**, so its
|
|
265
|
+
* target must already be initialized: a class from an already-evaluated module
|
|
266
|
+
* or the decorated class itself. Use the string form for a class declared
|
|
267
|
+
* later in the same module or reached through an import cycle — it is never
|
|
268
|
+
* evaluated, so it cannot hit the temporal dead zone.
|
|
221
269
|
* @param options - Optional field constraints (required, nullable, etc.)
|
|
222
270
|
* @returns A TypeScript property decorator
|
|
271
|
+
* @throws Error when the target cannot be resolved to a class name (an empty
|
|
272
|
+
* string, a thunk that throws — including on an uninitialized target — or a
|
|
273
|
+
* thunk returning an anonymous value)
|
|
223
274
|
*
|
|
224
275
|
* @example
|
|
225
276
|
* ```typescript
|
|
@@ -228,6 +279,14 @@ export declare function field(options?: FieldOptions | NumericFieldOptions | Tex
|
|
|
228
279
|
* // Same-package FK — enables loadRelated() and eager loading
|
|
229
280
|
* @foreignKey(Customer)
|
|
230
281
|
* customerId: string = '';
|
|
282
|
+
*
|
|
283
|
+
* // Self-reference: the class binding exists when its decorators run
|
|
284
|
+
* @foreignKey(() => Order)
|
|
285
|
+
* parentOrderId: string = '';
|
|
286
|
+
*
|
|
287
|
+
* // Declared later in this module — name string, never evaluated
|
|
288
|
+
* @foreignKey('Invoice')
|
|
289
|
+
* invoiceId: string = '';
|
|
231
290
|
* }
|
|
232
291
|
*
|
|
233
292
|
* // Cross-package: use a plain string instead
|
|
@@ -246,10 +305,12 @@ export declare function foreignKey(relatedClass: string | Function, options?: Om
|
|
|
246
305
|
*
|
|
247
306
|
* Use this for relationships that point to a `SmrtObject` in a *different* package
|
|
248
307
|
* (e.g. `Customer.profileId` pointing at `@happyvertical/smrt-profiles:Profile`).
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
308
|
+
*
|
|
309
|
+
* Like `@foreignKey()`, this emits **no** DDL `FOREIGN KEY` constraint — SMRT
|
|
310
|
+
* does not emit them on any engine (#2371). The decorated property is a
|
|
311
|
+
* `UUID` column on PostgreSQL/DuckDB and `TEXT` on SQLite, matching the target's
|
|
312
|
+
* id type; pass `{ idType: 'text' }` when the target declares
|
|
313
|
+
* `@smrt({ idType: 'text' })`.
|
|
253
314
|
*
|
|
254
315
|
* What you get over a plain string field:
|
|
255
316
|
* - The relationship is registered with the `ObjectRegistry`, so `loadRelated()`
|
|
@@ -257,6 +318,8 @@ export declare function foreignKey(relatedClass: string | Function, options?: Om
|
|
|
257
318
|
* manifest is loaded.
|
|
258
319
|
* - Optional save-time validation (`validate: true`) confirms the referenced
|
|
259
320
|
* object exists, catching typos and stale IDs before they hit the database.
|
|
321
|
+
* - `onDelete` is honoured by `SmrtObject.delete()` when the target package's
|
|
322
|
+
* manifest is loaded in the same runtime.
|
|
260
323
|
*
|
|
261
324
|
* The `qualifiedName` is a fully-qualified class identifier in the form
|
|
262
325
|
* `@package/scope:ClassName` — for example `@happyvertical/smrt-profiles:Profile`.
|
|
@@ -278,7 +341,7 @@ export declare function foreignKey(relatedClass: string | Function, options?: Om
|
|
|
278
341
|
* }
|
|
279
342
|
* ```
|
|
280
343
|
*
|
|
281
|
-
* @see {@link foreignKey} for same-package relationships
|
|
344
|
+
* @see {@link foreignKey} for same-package relationships
|
|
282
345
|
* @see SmrtObject.loadRelated for runtime resolution
|
|
283
346
|
*/
|
|
284
347
|
export declare function crossPackageRef(qualifiedName: string, options?: CrossPackageRefOptions): CompatiblePropertyDecorator;
|
|
@@ -303,6 +366,17 @@ export declare function crossPackageRef(qualifiedName: string, options?: CrossPa
|
|
|
303
366
|
* `loadRelatedMany` and the generated accessor resolve the intended inverse
|
|
304
367
|
* side. Without it the first matching foreign key is used.
|
|
305
368
|
*
|
|
369
|
+
* **Delete behaviour is declared on the child, not here.** `@oneToMany` is a
|
|
370
|
+
* transient read-side accessor; to have children removed with their parent,
|
|
371
|
+
* put `onDelete: 'CASCADE'` on the inverse `@foreignKey` (#2371):
|
|
372
|
+
*
|
|
373
|
+
* ```typescript
|
|
374
|
+
* class OrderItem extends SmrtObject {
|
|
375
|
+
* @foreignKey(Order, { onDelete: 'CASCADE' })
|
|
376
|
+
* orderId: string = '';
|
|
377
|
+
* }
|
|
378
|
+
* ```
|
|
379
|
+
*
|
|
306
380
|
* @param relatedClass - The class constructor of the child/related objects
|
|
307
381
|
* @param options - Optional relationship options. `foreignKey` selects the
|
|
308
382
|
* inverse foreign-key field on `relatedClass` when it has more than one.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACL,KAAK,2BAA2B,EAIjC,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;AAExB;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC1B,MAAM,GACN,SAAS,GACT,SAAS,GACT,SAAS,GACT,UAAU,GACV,MAAM,CAAC;AAEX,MAAM,MAAM,SAAS,GACjB,kBAAkB,GAClB,MAAM,GACN,YAAY,GACZ,iBAAiB,GACjB,WAAW,GACX,YAAY,CAAC;AAEjB,MAAM,WAAW,YAAY;IAC3B,8DAA8D;IAC9D,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kCAAkC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kCAAkC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,EAAE,CAAC,EAAE,YAAY,CAAC;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACvD,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,wBAAyB,SAAQ,YAAY;IAC5D,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wBAAwB;IACxB,IAAI,CAAC,EAAE,YAAY,GAAG,iBAAiB,GAAG,WAAW,GAAG,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"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"}
|