@vibeorm/sql 4.0.0 → 4.1.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/dist/ast.d.ts +15 -5
- package/dist/dialects/postgres-migration.d.ts +2 -0
- package/dist/dialects/sqlite-json-equality.d.ts +15 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +201 -42
- package/dist/index.js.map +17 -14
- package/dist/parameter-snapshot.d.ts +2 -1
- package/dist/query/index.js +5 -3
- package/dist/query/index.js.map +4 -4
- package/dist/result-decoder.d.ts +5 -0
- package/dist/sql-handles.d.ts +7 -0
- package/dist/timestamp-identity.d.ts +40 -0
- package/package.json +2 -2
package/dist/ast.d.ts
CHANGED
|
@@ -19,10 +19,11 @@ export type ColRef = {
|
|
|
19
19
|
* Projection-only wire transform. `jsonText` uses the dialect's JSON text
|
|
20
20
|
* projection to preserve SQL NULL versus the JSON document `null` before
|
|
21
21
|
* driver parsing, without converting a postgres `json` column to `jsonb`.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* `
|
|
25
|
-
*
|
|
22
|
+
* `castText` renders a dialect text cast and preserves `alias ?? name`,
|
|
23
|
+
* including timestamp identity before driver Date truncation. PostgreSQL
|
|
24
|
+
* uses `"col"::text`; SQLite/MySQL use CAST AS TEXT/CHAR. `encodeBase64`
|
|
25
|
+
* is postgres-only and renders `encode("col", 'base64')`. Other users:
|
|
26
|
+
* the lateral subselect (SQL review B3: raw `to_jsonb` would ship BigInt/Decimal as float64 JSON
|
|
26
27
|
* numbers and Bytes as `\x` hex text), and every row projection of a
|
|
27
28
|
* `Float[]`/`Json[]` column (bun:sql's array parser refuses float arrays
|
|
28
29
|
* printed with an exponent and json arrays holding integers beyond int32;
|
|
@@ -77,6 +78,8 @@ export type AggRef = {
|
|
|
77
78
|
export type AggSelect = {
|
|
78
79
|
readonly agg: AggRef;
|
|
79
80
|
readonly alias: string;
|
|
81
|
+
/** Projection-only result cast; native aggregate comparisons and ordering stay typed. */
|
|
82
|
+
readonly castText?: true;
|
|
80
83
|
};
|
|
81
84
|
/**
|
|
82
85
|
* JSON path predicates (Prisma's Json filter surface). `not` is `equals` with
|
|
@@ -467,6 +470,12 @@ export type KeyTableMember = {
|
|
|
467
470
|
} | {
|
|
468
471
|
readonly kind: "decimal";
|
|
469
472
|
readonly scale: number;
|
|
473
|
+
}
|
|
474
|
+
/** MySQL-only lossless temporal operands in mixed collation-aware key tuples. */
|
|
475
|
+
| {
|
|
476
|
+
readonly kind: "timestamp";
|
|
477
|
+
} | {
|
|
478
|
+
readonly kind: "time";
|
|
470
479
|
} | {
|
|
471
480
|
readonly kind: "citext";
|
|
472
481
|
} | {
|
|
@@ -641,7 +650,8 @@ export declare function excludedRef(params: {
|
|
|
641
650
|
export declare function isExcludedRef(value: unknown): value is ExcludedRef;
|
|
642
651
|
/**
|
|
643
652
|
* One RETURNING column: a bare column name renders `"col"`; `castText` renders
|
|
644
|
-
*
|
|
653
|
+
* a dialect text cast (postgres `::text`, sqlite `CAST AS TEXT`) for lossless
|
|
654
|
+
* transport, including timestamps and postgres arrays. `jsonText`
|
|
645
655
|
* applies the dialect's JSON text projection, as {@link ColRef.transform} does
|
|
646
656
|
* for SELECT. Dialects without RETURNING still omit the clause and flag it.
|
|
647
657
|
*/
|
|
@@ -54,6 +54,8 @@ export declare function renderMigrationSessionRestore(params: {
|
|
|
54
54
|
* expressions as PostgreSQL itself deparses them (newline-joined so every
|
|
55
55
|
* adapter decodes one plain text column), its partial predicate and the full
|
|
56
56
|
* `pg_get_indexdef` text for the operator to read in an error message.
|
|
57
|
+
* Complete recovery also reads NULL uniqueness and lossless ordered JSON text
|
|
58
|
+
* for key and included definitions. PostgreSQL 16/17/18 expose these fields.
|
|
57
59
|
*
|
|
58
60
|
* `indisvalid = false` is the state a cancelled/failed concurrent build
|
|
59
61
|
* leaves behind: the index exists, is skipped by the planner, and is still
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SqlDialect } from "../dialect.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Compare both JSON trees by decoded key/index paths, node type and atomic value.
|
|
4
|
+
* Object order and JSON escape spelling do not matter; array positions do.
|
|
5
|
+
* Container nodes retain empty-object/array distinctions, and multiplicity stops
|
|
6
|
+
* duplicate object members from disappearing as they would in a set difference.
|
|
7
|
+
* SQL NULL remains unknown. The tree input is guarded against non-JSON scalar
|
|
8
|
+
* text even when SQLite evaluates this expression before the outer type guard.
|
|
9
|
+
*/
|
|
10
|
+
export declare function renderSqliteJsonEquality(params: {
|
|
11
|
+
dialect: SqlDialect;
|
|
12
|
+
left: string;
|
|
13
|
+
right: string;
|
|
14
|
+
}): string;
|
|
15
|
+
//# sourceMappingURL=sqlite-json-equality.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { cloneTimestampDate, compareTimestampDates, timestampIdentityKey, timestampIdentityText } from "./timestamp-identity.ts";
|
|
1
2
|
export { nativeEnumArrayParameter, isNativeEnumArrayParameter } from "./native-enum-parameter.ts";
|
|
2
3
|
export type { NativeEnumArrayParameter } from "./native-enum-parameter.ts";
|
|
3
4
|
export { isJsonListParameter, jsonListParameter } from "./json-list-parameter.ts";
|
|
@@ -19,6 +20,7 @@ export { MODULE_JOURNAL_COLUMN, renderModuleJournal } from "./module-journal.ts"
|
|
|
19
20
|
export { renderSqliteFtsUpdateTrigger, sqliteFtsUpdateUpgrade } from "./dialects/sqlite-fts.ts";
|
|
20
21
|
export { SQLITE_SESSION_SQL } from "./dialects/sqlite-session.ts";
|
|
21
22
|
export { sql, isSqlFragment, renderSqlFragment, compileSql, sqlUsesIncoming, mapConflictColumns } from "./composable.ts";
|
|
23
|
+
export { registerSqlHandleCheck } from "./sql-handles.ts";
|
|
22
24
|
export type { SqlFragment, SqlTag } from "./composable.ts";
|
|
23
25
|
export { createSqlQuery, createSqlCommand, isSqlQuery, isSqlCommand, compileSqlQuery, compileSqlCommand } from "./query-plan.ts";
|
|
24
26
|
export type { SqlQuery, SqlCommand, SqlPlanRender, CompiledSqlQuery, CompiledSqlCommand, ReusableSqlQuery, ReusableSqlCommand } from "./query-plan.ts";
|
|
@@ -27,7 +29,7 @@ export { isSqlParam, createSqlInput, sqlInputContract, createSqlInputScope, seal
|
|
|
27
29
|
export type { SqlParam, SqlInput, SqlInputMap, SqlInputValue, SqlInputValues, SqlInputTokens, SqlExecutionParams, SqlInputContract, SqlInputScope } from "./sql-input.ts";
|
|
28
30
|
export { isSqlBindingCollector, recordSqlBinding, recordSqlBindingCheck } from "./sql-bindings.ts";
|
|
29
31
|
export type { SqlBindingSource, SqlParameterTransport, SqlBindingEncoder } from "./sql-bindings.ts";
|
|
30
|
-
export { decode, encode, resultScalarContract } from "./result-decoder.ts";
|
|
32
|
+
export { decode, encode, resultScalarContract, sameResultDecoder } from "./result-decoder.ts";
|
|
31
33
|
export type { ResultDecoder, ResultReadDecoder, ResultParameterEncoder, ResultScalarContract, Decoded, ExtendedDateTime } from "./result-decoder.ts";
|
|
32
34
|
/**
|
|
33
35
|
* @vibeorm/sql — dialect-aware SQL layer for VibeORM v2.
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,76 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// packages/sql/src/timestamp-identity.ts
|
|
3
|
+
var identities = new WeakMap;
|
|
4
|
+
function rememberTimestampIdentity(params) {
|
|
5
|
+
const { date, text } = params;
|
|
6
|
+
const fraction = /\.(\d+)/.exec(text)?.[1] ?? "";
|
|
7
|
+
const remainder = fraction.slice(3).replace(/0+$/, "");
|
|
8
|
+
const canonical = Date.prototype.toISOString.call(date).replace(/(\.\d{3})Z$/, `$1${remainder}Z`);
|
|
9
|
+
identities.set(date, {
|
|
10
|
+
milliseconds: Date.prototype.getTime.call(date),
|
|
11
|
+
text,
|
|
12
|
+
...params.numeric === undefined ? {} : { numeric: params.numeric },
|
|
13
|
+
key: params.key ?? `date:${canonical}`,
|
|
14
|
+
kind: params.kind ?? "timestamp"
|
|
15
|
+
});
|
|
16
|
+
return date;
|
|
17
|
+
}
|
|
18
|
+
function identityOf(params) {
|
|
19
|
+
const stored = identities.get(params.date);
|
|
20
|
+
if (stored !== undefined && stored.milliseconds !== Date.prototype.getTime.call(params.date)) {
|
|
21
|
+
identities.delete(params.date);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
return stored;
|
|
25
|
+
}
|
|
26
|
+
function timestampIdentityKey(params) {
|
|
27
|
+
return identityOf(params)?.key ?? `date:${Number.isFinite(Date.prototype.getTime.call(params.date)) ? Date.prototype.toISOString.call(params.date) : "invalid"}`;
|
|
28
|
+
}
|
|
29
|
+
function timestampIdentityText(params) {
|
|
30
|
+
const identity = identityOf(params);
|
|
31
|
+
return identity !== undefined && (params.kind === undefined || identity.kind === params.kind) ? identity.text : undefined;
|
|
32
|
+
}
|
|
33
|
+
function sqliteTimestampIdentityValue(params) {
|
|
34
|
+
const identity = identityOf(params);
|
|
35
|
+
return identity?.kind === "sqlite" ? identity.numeric ?? identity.text : undefined;
|
|
36
|
+
}
|
|
37
|
+
function compareTimestampDates(params) {
|
|
38
|
+
const left = identityOf({ date: params.left });
|
|
39
|
+
const right = identityOf({ date: params.right });
|
|
40
|
+
if (timestampIdentityKey({ date: params.left }) === timestampIdentityKey({ date: params.right }))
|
|
41
|
+
return 0;
|
|
42
|
+
const rank = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
43
|
+
if (left?.kind === "sqlite" || right?.kind === "sqlite") {
|
|
44
|
+
return left?.numeric !== undefined && right?.numeric !== undefined ? rank(left.numeric, right.numeric) : null;
|
|
45
|
+
}
|
|
46
|
+
const clock = (identity) => {
|
|
47
|
+
const [, hours, minutes, seconds, offset] = identity.key.split(":");
|
|
48
|
+
const [whole, fraction = ""] = seconds.split(".");
|
|
49
|
+
const local = BigInt(Number(hours) * 3600 + Number(minutes) * 60 + Number(whole)) * 1000000000n + BigInt(fraction.padEnd(9, "0"));
|
|
50
|
+
const zone = Number(offset);
|
|
51
|
+
return [local - BigInt(zone) * 1000000n, -zone];
|
|
52
|
+
};
|
|
53
|
+
if (left?.kind !== undefined && left.kind !== "timestamp" || right?.kind !== undefined && right.kind !== "timestamp") {
|
|
54
|
+
if (left === undefined || right === undefined || left.kind !== right.kind)
|
|
55
|
+
return null;
|
|
56
|
+
const a = clock(left);
|
|
57
|
+
const b = clock(right);
|
|
58
|
+
return rank(a[0], b[0]) || rank(a[1], b[1]);
|
|
59
|
+
}
|
|
60
|
+
const milliseconds = rank(Date.prototype.getTime.call(params.left), Date.prototype.getTime.call(params.right));
|
|
61
|
+
if (milliseconds !== 0)
|
|
62
|
+
return milliseconds;
|
|
63
|
+
const a = /\.(\d+)/.exec(left?.text ?? "")?.[1]?.slice(3) ?? "";
|
|
64
|
+
const b = /\.(\d+)/.exec(right?.text ?? "")?.[1]?.slice(3) ?? "";
|
|
65
|
+
return rank(a.padEnd(Math.max(a.length, b.length), "0"), b.padEnd(Math.max(a.length, b.length), "0"));
|
|
66
|
+
}
|
|
67
|
+
function cloneTimestampDate(params) {
|
|
68
|
+
const date = new Date(Date.prototype.getTime.call(params.date));
|
|
69
|
+
const identity = identityOf(params);
|
|
70
|
+
if (identity !== undefined)
|
|
71
|
+
identities.set(date, identity);
|
|
72
|
+
return date;
|
|
73
|
+
}
|
|
2
74
|
// packages/sql/src/native-enum-parameter.ts
|
|
3
75
|
var NATIVE_ENUM_ARRAY_PARAMETER = Symbol("vibeorm.nativeEnumArrayParameter");
|
|
4
76
|
function nativeEnumArrayParameter(params) {
|
|
@@ -234,24 +306,24 @@ function parseDateTimeText(params) {
|
|
|
234
306
|
if (ZONE_LESS_DATETIME.test(trimmed)) {
|
|
235
307
|
const asUtc = new Date(`${trimmed.replace(" ", "T").replace(/(\.\d{3})\d+$/, "$1")}Z`);
|
|
236
308
|
if (!Number.isNaN(asUtc.getTime()))
|
|
237
|
-
return asUtc;
|
|
309
|
+
return rememberTimestampIdentity({ date: asUtc, text: trimmed });
|
|
238
310
|
}
|
|
239
311
|
const zoned = PG_ZONED_DATETIME.exec(trimmed);
|
|
240
312
|
if (zoned !== null) {
|
|
241
313
|
const [, date, time, fraction, hours, minutes] = zoned;
|
|
242
314
|
const iso = new Date(`${date}T${time}${(fraction ?? "").slice(0, 4)}${hours}:${minutes ?? "00"}`);
|
|
243
315
|
if (!Number.isNaN(iso.getTime()))
|
|
244
|
-
return iso;
|
|
316
|
+
return rememberTimestampIdentity({ date: iso, text: trimmed });
|
|
245
317
|
}
|
|
246
318
|
const parsed = new Date(trimmed);
|
|
247
|
-
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
319
|
+
return Number.isNaN(parsed.getTime()) ? undefined : rememberTimestampIdentity({ date: parsed, text: trimmed });
|
|
248
320
|
}
|
|
249
321
|
var dateNativeCodec = {
|
|
250
322
|
encode: (value) => {
|
|
251
323
|
if (value instanceof Date && !Number.isFinite(value.getTime()) || typeof value === "number" && !Number.isFinite(value) || typeof value === "string" && /^[+-]?infinity$/i.test(value.trim())) {
|
|
252
324
|
throw new VibeError({ code: "VIBE_VALIDATION", message: "DateTime parameters require finite timestamps; use an extended timestamp encoder for infinity" });
|
|
253
325
|
}
|
|
254
|
-
return value;
|
|
326
|
+
return value instanceof Date ? timestampIdentityText({ date: value }) ?? value : value;
|
|
255
327
|
},
|
|
256
328
|
decode: (value) => {
|
|
257
329
|
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
@@ -265,16 +337,27 @@ var dateTextCodec = {
|
|
|
265
337
|
dateNativeCodec.encode(value);
|
|
266
338
|
const date = value instanceof Date ? value : typeof value === "string" ? parseDateTimeText({ text: value }) : typeof value === "number" ? new Date(value) : undefined;
|
|
267
339
|
if (date !== undefined && Number.isFinite(date.getTime()))
|
|
268
|
-
return date.toISOString();
|
|
340
|
+
return value instanceof Date ? sqliteTimestampIdentityValue({ date: value }) ?? date.toISOString() : date.toISOString();
|
|
269
341
|
throw new VibeError({ code: "VIBE_VALIDATION", message: "DateTime parameters require a valid finite timestamp" });
|
|
270
342
|
},
|
|
271
|
-
decode:
|
|
343
|
+
decode: (value) => {
|
|
344
|
+
const date = dateNativeCodec.decode(value);
|
|
345
|
+
if (typeof value === "number")
|
|
346
|
+
return rememberTimestampIdentity({ date, text: date.toISOString(), numeric: value, key: `epoch:${value}`, kind: "sqlite" });
|
|
347
|
+
return typeof value === "string" ? rememberTimestampIdentity({ date, text: value, key: `date:${value}`, kind: "sqlite" }) : date;
|
|
348
|
+
}
|
|
272
349
|
};
|
|
273
350
|
var mysqlDateCodec = {
|
|
274
351
|
encode: (value) => {
|
|
275
352
|
dateNativeCodec.encode(value);
|
|
276
|
-
if (value instanceof Date)
|
|
353
|
+
if (value instanceof Date) {
|
|
354
|
+
const text = timestampIdentityText({ date: value, kind: "timestamp" });
|
|
355
|
+
if (text !== undefined) {
|
|
356
|
+
const fraction = /\.(\d+)/.exec(text)?.[1] ?? "";
|
|
357
|
+
return value.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, `.${fraction.padEnd(3, "0")}`);
|
|
358
|
+
}
|
|
277
359
|
return value;
|
|
360
|
+
}
|
|
278
361
|
if (typeof value === "number") {
|
|
279
362
|
const parsed = new Date(value);
|
|
280
363
|
if (!Number.isNaN(parsed.getTime()))
|
|
@@ -301,18 +384,26 @@ function makeTimeOfDayCodec(params) {
|
|
|
301
384
|
if (date === undefined || !Number.isFinite(date.getTime())) {
|
|
302
385
|
throw new VibeError({ code: "VIBE_VALIDATION", message: "a time-of-day DateTime parameter requires a valid finite Date", meta: { value } });
|
|
303
386
|
}
|
|
304
|
-
return `${timeOfDayText(utcMillisecondOfDay(date))}${suffix}`;
|
|
387
|
+
return timestampIdentityText({ date, kind: params.zoned ? "timetz" : "time" }) ?? `${timeOfDayText(utcMillisecondOfDay(date))}${suffix}`;
|
|
305
388
|
},
|
|
306
389
|
decode: (value) => {
|
|
307
|
-
if (value instanceof Date && Number.isFinite(value.getTime()))
|
|
308
|
-
|
|
390
|
+
if (value instanceof Date && Number.isFinite(value.getTime())) {
|
|
391
|
+
if (timestampIdentityText({ date: value, kind: params.zoned ? "timetz" : "time" }) !== undefined)
|
|
392
|
+
return value;
|
|
393
|
+
const milliseconds = utcMillisecondOfDay(value);
|
|
394
|
+
const text = timeOfDayText(milliseconds);
|
|
395
|
+
return rememberTimestampIdentity({ date: new Date(milliseconds), text: `${text}${suffix}`, key: `${params.zoned ? "timetz" : "time"}:${text}:0`, kind: params.zoned ? "timetz" : "time" });
|
|
396
|
+
}
|
|
309
397
|
const match = typeof value === "string" ? TIME_OF_DAY_TEXT.exec(value.trim()) : null;
|
|
310
398
|
if (match !== null) {
|
|
311
399
|
const [, hours, minutes, seconds, fraction, sign, offsetHours, offsetMinutes, offsetSeconds] = match;
|
|
312
400
|
const millisecond = Number(hours) * 3600000 + Number(minutes) * 60000 + Number(seconds ?? 0) * 1000 + Number((fraction ?? "").slice(0, 3).padEnd(3, "0"));
|
|
313
401
|
const offset = sign === undefined ? 0 : (sign === "-" ? -1 : 1) * (Number(offsetHours) * 3600000 + Number(offsetMinutes ?? 0) * 60000 + Number(offsetSeconds ?? 0) * 1000);
|
|
314
402
|
if (millisecond <= MILLISECONDS_PER_DAY) {
|
|
315
|
-
|
|
403
|
+
const date = new Date(((millisecond - offset) % MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY);
|
|
404
|
+
const exactFraction = (fraction ?? "").padEnd(3, "0").replace(/(?<=\d{3})0+$/, "");
|
|
405
|
+
const key = `${params.zoned ? "timetz" : "time"}:${hours}:${minutes}:${seconds ?? "00"}.${exactFraction}:${offset}`;
|
|
406
|
+
return rememberTimestampIdentity({ date, text: value.trim(), key, kind: params.zoned ? "timetz" : "time" });
|
|
316
407
|
}
|
|
317
408
|
}
|
|
318
409
|
throw new VibeError({ code: "VIBE_DATA_INVALID", message: "a time-of-day DateTime column returned a value that is not a time of day", meta: { value } });
|
|
@@ -435,12 +526,27 @@ var DIALECT_CODECS = {
|
|
|
435
526
|
|
|
436
527
|
// packages/sql/src/result-decoder.ts
|
|
437
528
|
var scalarContracts = new WeakMap;
|
|
529
|
+
var decoderIdentities = new WeakMap;
|
|
530
|
+
var nullableDecoders = new WeakMap;
|
|
531
|
+
var arrayDecoders = new WeakMap;
|
|
532
|
+
function sameResultDecoder(params) {
|
|
533
|
+
if (params.left === params.right)
|
|
534
|
+
return true;
|
|
535
|
+
const identity = decoderIdentities.get(params.left);
|
|
536
|
+
return identity !== undefined && identity === decoderIdentities.get(params.right);
|
|
537
|
+
}
|
|
538
|
+
function wrappedIdentity(params) {
|
|
539
|
+
const identity = decoderIdentities.get(params.decoder);
|
|
540
|
+
return identity === undefined ? undefined : JSON.stringify([params.kind, identity]);
|
|
541
|
+
}
|
|
438
542
|
function resultScalarContract(params) {
|
|
439
543
|
return scalarContracts.get(params.decoder);
|
|
440
544
|
}
|
|
441
545
|
function registerScalar(params) {
|
|
442
546
|
if (params.contract !== undefined)
|
|
443
547
|
scalarContracts.set(params.decoder, Object.freeze(params.contract));
|
|
548
|
+
if (params.identity !== undefined)
|
|
549
|
+
decoderIdentities.set(params.decoder, params.identity);
|
|
444
550
|
return params.decoder;
|
|
445
551
|
}
|
|
446
552
|
function wrappedContract(params) {
|
|
@@ -473,7 +579,7 @@ function scalar(type, valid) {
|
|
|
473
579
|
refuse(encoding, `Invalid ${type} projection value`);
|
|
474
580
|
return result;
|
|
475
581
|
};
|
|
476
|
-
return registerScalar({ contract: { scalar: type, nullable: false, list: false }, decoder: Object.freeze({
|
|
582
|
+
return registerScalar({ identity: JSON.stringify(["scalar", type]), contract: { scalar: type, nullable: false, list: false }, decoder: Object.freeze({
|
|
477
583
|
decode: (params) => convert(params, false),
|
|
478
584
|
encode: (params) => {
|
|
479
585
|
const value = type === "Json" ? params.value : convert(params, true);
|
|
@@ -485,7 +591,7 @@ function scalar(type, valid) {
|
|
|
485
591
|
}
|
|
486
592
|
var stringDecoder = scalar("String", (value) => typeof value === "string");
|
|
487
593
|
var dateDecoder = scalar("DateTime", (value) => value instanceof Date && Number.isFinite(value.getTime()));
|
|
488
|
-
var extendedDecoder = registerScalar({ contract: { scalar: "DateTime", nullable: false, list: false }, decoder: Object.freeze({
|
|
594
|
+
var extendedDecoder = registerScalar({ identity: "extended-date-time", contract: { scalar: "DateTime", nullable: false, list: false }, decoder: Object.freeze({
|
|
489
595
|
decode: (params) => {
|
|
490
596
|
if (params.value === "infinity" || params.value === Infinity)
|
|
491
597
|
return "infinity";
|
|
@@ -506,15 +612,21 @@ function hasEncoder(decoder) {
|
|
|
506
612
|
return "encode" in decoder && typeof decoder.encode === "function";
|
|
507
613
|
}
|
|
508
614
|
function nullable({ decoder }) {
|
|
615
|
+
const cached = nullableDecoders.get(decoder);
|
|
616
|
+
if (cached !== undefined)
|
|
617
|
+
return cached;
|
|
509
618
|
const read = (params) => params.value === null ? null : decoder.decode(params);
|
|
510
|
-
|
|
511
|
-
return Object.freeze({ decode: read });
|
|
512
|
-
return registerScalar({ contract: wrappedContract({ decoder, nullable: true }), decoder: Object.freeze({
|
|
619
|
+
const wrapped = hasEncoder(decoder) ? registerScalar({ identity: wrappedIdentity({ decoder, kind: "nullable" }), contract: wrappedContract({ decoder, nullable: true }), decoder: Object.freeze({
|
|
513
620
|
decode: read,
|
|
514
621
|
encode: (params) => params.value === null ? null : decoder.encode(params)
|
|
515
|
-
}) });
|
|
622
|
+
}) }) : Object.freeze({ decode: read });
|
|
623
|
+
nullableDecoders.set(decoder, wrapped);
|
|
624
|
+
return wrapped;
|
|
516
625
|
}
|
|
517
626
|
function array({ decoder }) {
|
|
627
|
+
const cached = arrayDecoders.get(decoder);
|
|
628
|
+
if (cached !== undefined)
|
|
629
|
+
return cached;
|
|
518
630
|
const bidirectional = hasEncoder(decoder);
|
|
519
631
|
const read = (params) => {
|
|
520
632
|
const raw = params.value;
|
|
@@ -534,16 +646,16 @@ function array({ decoder }) {
|
|
|
534
646
|
refuse(false, "Expected an array projection");
|
|
535
647
|
return value.map((element) => decoder.decode({ ...params, value: element }));
|
|
536
648
|
};
|
|
537
|
-
|
|
538
|
-
return Object.freeze({ decode: read });
|
|
539
|
-
return registerScalar({ contract: wrappedContract({ decoder, list: true }), decoder: Object.freeze({
|
|
649
|
+
const wrapped = hasEncoder(decoder) ? registerScalar({ identity: wrappedIdentity({ decoder, kind: "array" }), contract: wrappedContract({ decoder, list: true }), decoder: Object.freeze({
|
|
540
650
|
decode: read,
|
|
541
651
|
encode: (params) => {
|
|
542
652
|
if (!Array.isArray(params.value))
|
|
543
653
|
refuse(true, "Expected an array parameter");
|
|
544
654
|
return params.value.map((value) => decoder.encode({ ...params, value }));
|
|
545
655
|
}
|
|
546
|
-
}) });
|
|
656
|
+
}) }) : Object.freeze({ decode: read });
|
|
657
|
+
arrayDecoders.set(decoder, wrapped);
|
|
658
|
+
return wrapped;
|
|
547
659
|
}
|
|
548
660
|
var encode = Object.freeze({
|
|
549
661
|
array: ({ encoder }) => Object.freeze({
|
|
@@ -569,7 +681,7 @@ var decode = Object.freeze({
|
|
|
569
681
|
array,
|
|
570
682
|
enum: ({ values }) => {
|
|
571
683
|
const members = new Set(values);
|
|
572
|
-
return registerScalar({ contract: { scalar: "String", nullable: false, list: false }, decoder: Object.freeze({
|
|
684
|
+
return registerScalar({ identity: JSON.stringify(["enum", [...members].sort()]), contract: { scalar: "String", nullable: false, list: false }, decoder: Object.freeze({
|
|
573
685
|
decode: ({ value }) => typeof value === "string" && members.has(value) ? value : refuse(false, "Unknown enum projection member"),
|
|
574
686
|
encode: ({ value }) => typeof value === "string" && members.has(value) ? value : refuse(true, "Unknown enum parameter member")
|
|
575
687
|
}) });
|
|
@@ -590,10 +702,23 @@ var decode = Object.freeze({
|
|
|
590
702
|
}
|
|
591
703
|
});
|
|
592
704
|
|
|
705
|
+
// packages/sql/src/sql-handles.ts
|
|
706
|
+
var checks = new Set;
|
|
707
|
+
function registerSqlHandleCheck(params) {
|
|
708
|
+
checks.add(params.check);
|
|
709
|
+
}
|
|
710
|
+
function isSqlHandle(value) {
|
|
711
|
+
for (const check of checks)
|
|
712
|
+
if (check(value))
|
|
713
|
+
return true;
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
|
|
593
717
|
// packages/sql/src/sql-input.ts
|
|
594
718
|
var inputs = new WeakMap;
|
|
595
719
|
var tokens = new WeakMap;
|
|
596
720
|
var scopes = new WeakMap;
|
|
721
|
+
registerSqlHandleCheck({ check: (value) => inputs.has(value) || tokens.has(value) || scopes.has(value) });
|
|
597
722
|
function invalid(message) {
|
|
598
723
|
throw new VibeError3({ code: "VIBE_VALIDATION", message });
|
|
599
724
|
}
|
|
@@ -691,7 +816,7 @@ function snapshotSqlValue(value, ancestors = new Set, isHandle) {
|
|
|
691
816
|
};
|
|
692
817
|
if (isSqlParam(value))
|
|
693
818
|
return invalid("SQL input tokens are valid only in admitted reusable builder positions");
|
|
694
|
-
if (
|
|
819
|
+
if (typeof value === "object" && value !== null && (isSqlHandle(value) || isHandle?.(value)))
|
|
695
820
|
return invalid("SQL builder, plan, template, input and fragment handles are not execution values");
|
|
696
821
|
if (value === undefined)
|
|
697
822
|
return invalid("SQL values cannot be undefined; omit a defaulted column or use null");
|
|
@@ -700,7 +825,7 @@ function snapshotSqlValue(value, ancestors = new Set, isHandle) {
|
|
|
700
825
|
if (typeof value === "number")
|
|
701
826
|
return value;
|
|
702
827
|
if (value instanceof Date)
|
|
703
|
-
return Number.isFinite(value.getTime()) ?
|
|
828
|
+
return Number.isFinite(value.getTime()) ? cloneTimestampDate({ date: value }) : invalid("SQL dates must be finite");
|
|
704
829
|
if (value instanceof Uint8Array)
|
|
705
830
|
return new Uint8Array(value);
|
|
706
831
|
if (typeof value !== "object")
|
|
@@ -1407,6 +1532,7 @@ var SQLITE_SESSION_SQL = Object.freeze({
|
|
|
1407
1532
|
// packages/sql/src/composable.ts
|
|
1408
1533
|
import { VibeError as VibeError9 } from "@vibeorm/schema";
|
|
1409
1534
|
var fragments = new WeakMap;
|
|
1535
|
+
registerSqlHandleCheck({ check: (value) => fragments.has(value) });
|
|
1410
1536
|
function invalid3(message) {
|
|
1411
1537
|
throw new VibeError9({ code: "VIBE_VALIDATION", message });
|
|
1412
1538
|
}
|
|
@@ -1492,7 +1618,7 @@ function renderSqlFragment(params) {
|
|
|
1492
1618
|
const encode = (part) => {
|
|
1493
1619
|
if (part.value === undefined)
|
|
1494
1620
|
invalid3("SQL parameters cannot be undefined; use null explicitly");
|
|
1495
|
-
return snapshotSqlValue(part.decoder === undefined ? part.value : part.decoder.encode({ value: part.value, dialect: dialect.name }));
|
|
1621
|
+
return snapshotSqlValue(part.decoder === undefined ? part.value : part.decoder.encode({ value: snapshotSqlValue(part.value), dialect: dialect.name }));
|
|
1496
1622
|
};
|
|
1497
1623
|
const recording = isSqlBindingCollector({ collector });
|
|
1498
1624
|
const recordValue = (part) => recordSqlBinding({
|
|
@@ -1580,6 +1706,7 @@ var queries = new WeakMap;
|
|
|
1580
1706
|
var commands = new WeakMap;
|
|
1581
1707
|
var reusableQueries = new WeakMap;
|
|
1582
1708
|
var reusableCommands = new WeakMap;
|
|
1709
|
+
registerSqlHandleCheck({ check: (value) => queries.has(value) || commands.has(value) || reusableQueries.has(value) || reusableCommands.has(value) });
|
|
1583
1710
|
function invalid4(message) {
|
|
1584
1711
|
throw new VibeError10({ code: "VIBE_VALIDATION", message });
|
|
1585
1712
|
}
|
|
@@ -2249,7 +2376,7 @@ function assertInt32(params) {
|
|
|
2249
2376
|
var JSON_TRANSPORT = Object.freeze({ chunkSize: 100 });
|
|
2250
2377
|
function projectionOutputName(params) {
|
|
2251
2378
|
const { column } = params;
|
|
2252
|
-
return column.
|
|
2379
|
+
return column.alias ?? column.name;
|
|
2253
2380
|
}
|
|
2254
2381
|
// packages/sql/src/dialects/postgres.ts
|
|
2255
2382
|
var postgresDialect = {
|
|
@@ -2398,6 +2525,20 @@ var SQL_DIALECTS = {
|
|
|
2398
2525
|
function getSqlDialect(params) {
|
|
2399
2526
|
return SQL_DIALECTS[params.dialect];
|
|
2400
2527
|
}
|
|
2528
|
+
// packages/sql/src/dialects/sqlite-json-equality.ts
|
|
2529
|
+
function renderSqliteJsonEquality(params) {
|
|
2530
|
+
const q = (name) => params.dialect.quoteIdent({ name });
|
|
2531
|
+
const values = q("__vibe_json_values");
|
|
2532
|
+
const tree = q("__vibe_json_tree");
|
|
2533
|
+
const paths = q("__vibe_json_paths");
|
|
2534
|
+
const child = q("__vibe_json_child");
|
|
2535
|
+
const parent = q("__vibe_json_parent");
|
|
2536
|
+
const [side, id, parentId, key, type, atom, path] = ["side", "id", "parent", "key", "type", "atom", "path"].map(q);
|
|
2537
|
+
const left = `${values}.${q("left")}`;
|
|
2538
|
+
const right = `${values}.${q("right")}`;
|
|
2539
|
+
return `(SELECT CASE WHEN ${left} IS NULL THEN NULL ELSE NOT EXISTS (` + `WITH RECURSIVE ${tree} AS (` + `SELECT 1 AS ${side}, ${id}, ${parentId}, ${key}, ${type}, ${atom} FROM json_tree(CASE WHEN json_valid(${left}) THEN ${left} ELSE 'null' END) ` + `UNION ALL SELECT -1 AS ${side}, ${id}, ${parentId}, ${key}, ${type}, ${atom} FROM json_tree(${right})` + `), ${paths} (${side}, ${id}, ${path}, ${type}, ${atom}) AS (` + `SELECT ${side}, ${id}, json_array(), ${type}, ${atom} FROM ${tree} WHERE ${parentId} IS NULL ` + `UNION ALL SELECT ${child}.${side}, ${child}.${id}, json_insert(${parent}.${path}, '$[#]', ${child}.${key}), ${child}.${type}, ${child}.${atom} ` + `FROM ${tree} AS ${child} JOIN ${paths} AS ${parent} ON ${child}.${side} = ${parent}.${side} AND ${child}.${parentId} = ${parent}.${id}` + `) SELECT 1 FROM ${paths} GROUP BY ${path}, CASE WHEN ${type} IN ('integer', 'real') THEN 'number' ELSE ${type} END, ${atom} HAVING SUM(${side}) <> 0` + `) END FROM (SELECT ${params.left} AS ${q("left")}, ${params.right} AS ${q("right")}) AS ${values})`;
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2401
2542
|
// packages/sql/src/dialects/diagnostics.ts
|
|
2402
2543
|
import { unsupportedCapability as unsupportedCapability12 } from "@vibeorm/schema";
|
|
2403
2544
|
var QUERIES = Object.freeze({
|
|
@@ -2501,7 +2642,7 @@ function requireArg(params) {
|
|
|
2501
2642
|
import { unsupportedCapability as unsupportedCapability15 } from "@vibeorm/schema";
|
|
2502
2643
|
|
|
2503
2644
|
// packages/sql/src/dialects/postgres-rls.ts
|
|
2504
|
-
import { RlsContextError, VibeError as VibeError14, assertValidSchemaIR, unsupportedCapability as unsupportedCapability14, validateRlsContextIR } from "@vibeorm/schema";
|
|
2645
|
+
import { RlsContextError, VibeError as VibeError14, assertNativeRlsAdmission, assertValidSchemaIR, unsupportedCapability as unsupportedCapability14, validateRlsContextIR } from "@vibeorm/schema";
|
|
2505
2646
|
var NATIVE_RLS = Object.freeze({ policyName: "vibeorm_managed", tableSchema: "public", settingPrefix: "vibeorm.", parentAlias: "parent" });
|
|
2506
2647
|
var COLUMN_TYPES = {
|
|
2507
2648
|
String: { default: "text", native: { text: "text", varchar: "varchar", charactervarying: "varchar", uuid: "uuid" } },
|
|
@@ -2570,6 +2711,7 @@ function renderRlsColumnType(params) {
|
|
|
2570
2711
|
}
|
|
2571
2712
|
function protectedModel(params) {
|
|
2572
2713
|
requireRls(params);
|
|
2714
|
+
assertNativeRlsAdmission({ schema: params.schema, dialect: params.dialect.name });
|
|
2573
2715
|
assertValidSchemaIR({ schema: params.schema });
|
|
2574
2716
|
const model = params.schema.models.find((entry) => entry.name === params.model);
|
|
2575
2717
|
if (model === undefined || model.rls === undefined || model.rls === "unprotected") {
|
|
@@ -2948,15 +3090,18 @@ function quoteProjection(dialect, column) {
|
|
|
2948
3090
|
const alias = dialect.quoteIdent({ name: column.alias ?? column.name });
|
|
2949
3091
|
return `${dialect.jsonTextProjection({ expression: ref })} AS ${alias}`;
|
|
2950
3092
|
}
|
|
2951
|
-
if (dialect.name !== "postgres") {
|
|
2952
|
-
throw internalError2({ message: "ColRef.transform is a postgres construct" });
|
|
2953
|
-
}
|
|
2954
3093
|
const ref = quoteCol(dialect, column);
|
|
2955
|
-
const alias = dialect.quoteIdent({ name: column.name });
|
|
3094
|
+
const alias = dialect.quoteIdent({ name: column.alias ?? column.name });
|
|
2956
3095
|
if (column.transform === "castText")
|
|
2957
|
-
return `${ref}
|
|
3096
|
+
return `${textProjection({ dialect, expression: ref })} AS ${alias}`;
|
|
3097
|
+
if (dialect.name !== "postgres") {
|
|
3098
|
+
throw internalError2({ message: "encodeBase64 is a postgres construct" });
|
|
3099
|
+
}
|
|
2958
3100
|
return `encode(${ref}, 'base64') AS ${alias}`;
|
|
2959
3101
|
}
|
|
3102
|
+
function textProjection(params) {
|
|
3103
|
+
return params.dialect.name === "postgres" ? `${params.expression}::text` : `CAST(${params.expression} AS ${params.dialect.name === "mysql" ? "CHAR" : "TEXT"})`;
|
|
3104
|
+
}
|
|
2960
3105
|
function renderProjection(params) {
|
|
2961
3106
|
const { dialect, columns } = params;
|
|
2962
3107
|
return columns.map((column) => quoteProjection(dialect, column)).join(", ");
|
|
@@ -3277,8 +3422,8 @@ function renderJsonFilter(params) {
|
|
|
3277
3422
|
if (value !== null) {
|
|
3278
3423
|
const target = `json_extract(${column}, ${collector.add(path)})`;
|
|
3279
3424
|
const bound = typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
3280
|
-
const
|
|
3281
|
-
base = `(${guard} AND ${
|
|
3425
|
+
const comparison = typeof value === "object" ? renderSqliteJsonEquality({ dialect, left: target, right: collector.add(JSON.stringify(value)) }) : `${target} = ${collector.add(bound)}`;
|
|
3426
|
+
base = `(${guard} AND ${comparison})`;
|
|
3282
3427
|
}
|
|
3283
3428
|
return expr.negate === true ? `NOT (${base})` : base;
|
|
3284
3429
|
};
|
|
@@ -3443,11 +3588,8 @@ function applyReturning(params) {
|
|
|
3443
3588
|
const name = dialect.quoteIdent({ name: column.name });
|
|
3444
3589
|
return `${dialect.jsonTextProjection({ expression: name })} AS ${name}`;
|
|
3445
3590
|
}
|
|
3446
|
-
if (dialect.name !== "postgres") {
|
|
3447
|
-
throw internalError2({ message: "a castText RETURNING column is a postgres construct" });
|
|
3448
|
-
}
|
|
3449
3591
|
const name = dialect.quoteIdent({ name: column.name });
|
|
3450
|
-
return `${name}
|
|
3592
|
+
return `${textProjection({ dialect, expression: name })} AS ${name}`;
|
|
3451
3593
|
}).join(", ");
|
|
3452
3594
|
return { suffix: ` RETURNING ${columns}`, status: "native" };
|
|
3453
3595
|
}
|
|
@@ -3484,6 +3626,10 @@ function renderKeyTableJoin(params) {
|
|
|
3484
3626
|
switch (member.kind) {
|
|
3485
3627
|
case "integer":
|
|
3486
3628
|
return dialect.name === "mysql" ? `CAST(${placeholder} AS SIGNED)` : `CAST(${placeholder} AS bigint)`;
|
|
3629
|
+
case "timestamp":
|
|
3630
|
+
return `CAST(${placeholder} AS DATETIME(6))`;
|
|
3631
|
+
case "time":
|
|
3632
|
+
return `CAST(${placeholder} AS TIME(6))`;
|
|
3487
3633
|
case "decimal":
|
|
3488
3634
|
return `CAST(${placeholder} AS DECIMAL(65,${member.scale}))`;
|
|
3489
3635
|
case "citext":
|
|
@@ -3513,7 +3659,9 @@ function renderSelect(params) {
|
|
|
3513
3659
|
}
|
|
3514
3660
|
if (statement.aggregates !== undefined) {
|
|
3515
3661
|
for (const selection of statement.aggregates) {
|
|
3516
|
-
|
|
3662
|
+
const expression = renderAggRef(dialect, selection.agg);
|
|
3663
|
+
const projected = selection.castText === true ? textProjection({ dialect, expression }) : expression;
|
|
3664
|
+
columnList.push(`${projected} AS ${dialect.quoteIdent({ name: selection.alias })}`);
|
|
3517
3665
|
}
|
|
3518
3666
|
}
|
|
3519
3667
|
if (statement.countSubqueries !== undefined) {
|
|
@@ -4417,8 +4565,13 @@ function renderIndexStateQuery(params) {
|
|
|
4417
4565
|
text: `SELECT c.relname::text AS "index",
|
|
4418
4566
|
t.relname::text AS "table",
|
|
4419
4567
|
am.amname::text AS "method",
|
|
4420
|
-
|
|
4421
|
-
|
|
4568
|
+
i.indisunique AS "unique",
|
|
4569
|
+
i.indnullsnotdistinct AS "nullsNotDistinct",
|
|
4570
|
+
pg_catalog.array_to_json(ARRAY(SELECT pg_catalog.pg_get_indexdef(i.indexrelid, k.k, true)
|
|
4571
|
+
FROM pg_catalog.generate_series(1, i.indnkeyatts) AS k(k) ORDER BY k.k))::text AS "keyColumns",
|
|
4572
|
+
pg_catalog.array_to_json(ARRAY(SELECT pg_catalog.pg_get_indexdef(i.indexrelid, k.k, true)
|
|
4573
|
+
FROM pg_catalog.generate_series(i.indnkeyatts + 1, i.indnatts) AS k(k) ORDER BY k.k))::text AS "includedColumns",
|
|
4574
|
+
i.indisvalid AS "valid",
|
|
4422
4575
|
i.indisready AS "ready",
|
|
4423
4576
|
pg_catalog.pg_get_expr(i.indpred, i.indrelid) AS "predicate",
|
|
4424
4577
|
pg_catalog.pg_get_indexdef(i.indexrelid) AS "definition",
|
|
@@ -4510,6 +4663,8 @@ export {
|
|
|
4510
4663
|
SQL_DIALECTS,
|
|
4511
4664
|
assertSqlParamBuilding,
|
|
4512
4665
|
chainVetoToken,
|
|
4666
|
+
cloneTimestampDate,
|
|
4667
|
+
compareTimestampDates,
|
|
4513
4668
|
compileExclusion,
|
|
4514
4669
|
compileReusableSqlCommand,
|
|
4515
4670
|
compileReusableSqlQuery,
|
|
@@ -4567,6 +4722,7 @@ export {
|
|
|
4567
4722
|
projectionOutputName,
|
|
4568
4723
|
recordSqlBinding,
|
|
4569
4724
|
recordSqlBindingCheck,
|
|
4725
|
+
registerSqlHandleCheck,
|
|
4570
4726
|
renderAdvisoryLock,
|
|
4571
4727
|
renderCheckProbe,
|
|
4572
4728
|
renderColumnStateQuery,
|
|
@@ -4632,6 +4788,7 @@ export {
|
|
|
4632
4788
|
renderStatement,
|
|
4633
4789
|
resultScalarContract,
|
|
4634
4790
|
returningColumnName,
|
|
4791
|
+
sameResultDecoder,
|
|
4635
4792
|
sealSqlInputScope,
|
|
4636
4793
|
shortestFloat32,
|
|
4637
4794
|
snapshotSqlValue,
|
|
@@ -4648,7 +4805,9 @@ export {
|
|
|
4648
4805
|
strictMatchPattern,
|
|
4649
4806
|
tableDefinitionParts,
|
|
4650
4807
|
timeOfDayCodec,
|
|
4808
|
+
timestampIdentityKey,
|
|
4809
|
+
timestampIdentityText,
|
|
4651
4810
|
toPgArrayLiteral
|
|
4652
4811
|
};
|
|
4653
4812
|
|
|
4654
|
-
//# debugId=
|
|
4813
|
+
//# debugId=02AD9A2144138F0F64756E2164756E21
|