@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 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
- * The other transforms are postgres only. `castText` renders
23
- * `"col"::text AS "col"`, `encodeBase64` renders
24
- * `encode("col", 'base64') AS "col"`. Two users: the lateral subselect (SQL
25
- * review B3 — raw `to_jsonb` would ship BigInt/Decimal as float64 JSON
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
- * `"col"::text AS "col"` (postgres only) for `Float[]`/`Json[]`. `jsonText`
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: dateNativeCodec.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
- return new Date(utcMillisecondOfDay(value));
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
- return new Date(((millisecond - offset) % MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY) % MILLISECONDS_PER_DAY);
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
- if (!hasEncoder(decoder))
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
- if (!hasEncoder(decoder))
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 (isHandle !== undefined && typeof value === "object" && value !== null && isHandle(value))
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()) ? new Date(value.getTime()) : invalid("SQL dates must be finite");
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.transform === undefined || column.transform === "jsonText" ? column.alias ?? column.name : column.name;
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}::text AS ${alias}`;
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 operand = typeof value === "object" ? `json(${collector.add(JSON.stringify(value))})` : collector.add(bound);
3281
- base = `(${guard} AND ${target} = ${operand})`;
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}::text AS ${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
- columnList.push(`${renderAggRef(dialect, selection.agg)} AS ${dialect.quoteIdent({ name: selection.alias })}`);
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
- i.indisunique AS "unique",
4421
- i.indisvalid AS "valid",
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=B8D70B32D9F5E99064756E2164756E21
4813
+ //# debugId=02AD9A2144138F0F64756E2164756E21