@typed-sql/sqlite 2.2.0 → 2.3.0-rc.1

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.
@@ -1,5 +1,5 @@
1
1
  var _a;
2
- import { ParameterCollector, ResolverSchemaIndex, unionTypeLiterals, } from "@typed-sql/core";
2
+ import { jsonArrayShape, jsonObjectShape, jsonShapeAtPath, jsonShapeFromValue, jsonShapeType, jsonTupleShape, nullJsonShape, ParameterCollector, ResolverSchemaIndex, unionJsonShapes, unionTypeLiterals, unknownJsonShape, updateJsonShapePath, } from "@typed-sql/core";
3
3
  import { isSqliteAggregateFunction, isSqliteWindowFunction, normalizeSqliteDatabaseType, resolveSqliteBuiltin, SQLITE_CURRENT_TIME_KEYWORDS, SQLITE_EXTENSION_TABLE_FUNCTIONS, SQLITE_JSON_TABLE_FUNCTIONS, SQLITE_MATH_FUNCTIONS, SQLITE_UNSUPPORTED_OPERATORS, sqliteNumericOperands, sqliteOperator, } from "./catalog/index.js";
4
4
  import { walkStatement } from "./parser/index.js";
5
5
  import { compareSqliteVersions, parseSqliteVersion, sqliteVersionSupport } from "./support.js";
@@ -72,6 +72,9 @@ function isConstantFrameExpression(expression) {
72
72
  return false;
73
73
  }
74
74
  class Resolver {
75
+ #runtimeValidation;
76
+ #requiresJsonValidation = false;
77
+ #projections;
75
78
  #schema;
76
79
  #policy;
77
80
  #strict;
@@ -79,7 +82,9 @@ class Resolver {
79
82
  #parameters = new ParameterCollector();
80
83
  #index;
81
84
  #activeWindows = new Map();
82
- constructor(schema, options) {
85
+ constructor(schema, options, projections = new WeakMap()) {
86
+ this.#projections = projections;
87
+ this.#runtimeValidation = options.runtimeValidation === true;
83
88
  this.#schema = schema;
84
89
  this.#index = ResolverSchemaIndex.for(schema);
85
90
  this.#policy = options.typePolicy ?? defaultSqliteTypePolicy;
@@ -306,12 +311,7 @@ class Resolver {
306
311
  const columns = {};
307
312
  result.columns.forEach((column, index) => {
308
313
  const columnName = query.columns[index] === undefined ? column.name : name(query.columns[index]);
309
- columns[columnName] = {
310
- name: columnName,
311
- databaseType: column.databaseType ?? "unknown",
312
- tsType: column.tsType,
313
- nullable: column.nullable,
314
- };
314
+ columns[columnName] = this.#project(columnName, column);
315
315
  });
316
316
  ctes.set(key, { name: key, columns });
317
317
  }
@@ -371,12 +371,13 @@ class Resolver {
371
371
  const columnName = query.columns[index] === undefined ? seed?.name : name(query.columns[index]);
372
372
  if (columnName === undefined)
373
373
  continue;
374
- provisionalColumns[columnName] = {
375
- name: columnName,
376
- databaseType: seed?.databaseType ?? "unknown",
377
- tsType: seed?.tsType ?? "unknown",
378
- nullable: seed?.nullable ?? true,
379
- };
374
+ provisionalColumns[columnName] = this.#project(columnName, seed?.json === undefined
375
+ ? {
376
+ tsType: seed?.tsType ?? "unknown",
377
+ nullable: seed?.nullable ?? true,
378
+ ...(seed?.databaseType === undefined ? {} : { databaseType: seed.databaseType }),
379
+ }
380
+ : this.#jsonResult(unknownJsonShape, seed.nullable, seed.json.representation === "binary"));
380
381
  }
381
382
  const recursiveScope = new Map(inherited);
382
383
  recursiveScope.set(name(query.name), { name: name(query.name), columns: provisionalColumns });
@@ -444,7 +445,7 @@ class Resolver {
444
445
  #mergeCteSeedColumns(statements, outer, inherited) {
445
446
  const merged = [];
446
447
  for (const statement of statements) {
447
- const probe = new _a(this.#schema, { typePolicy: this.#policy, strictExpressions: false });
448
+ const probe = new _a(this.#schema, { typePolicy: this.#policy, strictExpressions: false, runtimeValidation: this.#runtimeValidation }, this.#projections);
448
449
  const columns = probe.#select(statement, outer, inherited, false);
449
450
  if (merged.length === 0) {
450
451
  merged.push(...columns);
@@ -453,12 +454,9 @@ class Resolver {
453
454
  for (let index = 0; index < Math.min(merged.length, columns.length); index += 1) {
454
455
  const current = merged[index];
455
456
  const candidate = columns[index];
456
- const sameDatabaseType = current.databaseType === candidate.databaseType && current.databaseType !== undefined;
457
457
  merged[index] = {
458
+ ...this.#mergeTypes([current, candidate], current.nullable || candidate.nullable),
458
459
  name: current.name,
459
- tsType: unionSqliteTypes([current.tsType, candidate.tsType]),
460
- nullable: current.nullable || candidate.nullable,
461
- ...(sameDatabaseType ? { databaseType: current.databaseType } : {}),
462
460
  range: current.range,
463
461
  };
464
462
  }
@@ -529,15 +527,9 @@ class Resolver {
529
527
  for (let index = 0; index < columns.length; index += 1) {
530
528
  const left = columns[index];
531
529
  const candidate = right[index];
532
- const { databaseType: _databaseType, ...leftWithoutDatabaseType } = left;
533
- columns[index] = {
534
- ...leftWithoutDatabaseType,
535
- tsType: unionSqliteTypes([left.tsType, candidate.tsType]),
536
- nullable: left.nullable || candidate.nullable,
537
- ...(left.databaseType === candidate.databaseType && left.databaseType !== undefined
538
- ? { databaseType: left.databaseType }
539
- : {}),
540
- };
530
+ const merged = this.#mergeTypes([left, candidate], left.nullable || candidate.nullable);
531
+ const { jsonSubtype: _subtype, jsonEmbedding: _embedding, ...projected } = merged;
532
+ columns[index] = { ...projected, name: left.name, range: left.range };
541
533
  }
542
534
  }
543
535
  this.#activeWindows = previousWindows;
@@ -681,11 +673,7 @@ class Resolver {
681
673
  this.#expression(reference.arguments[2], scope, ctes, this.#databaseType("text", false));
682
674
  }
683
675
  else {
684
- const jsonInput = {
685
- tsType: sqliteFlexibleType(this.#policy),
686
- nullable: true,
687
- databaseType: "json",
688
- };
676
+ const jsonInput = this.#jsonInput();
689
677
  const rootPath = { tsType: "string", nullable: true, databaseType: "text" };
690
678
  if (reference.arguments[0]?.kind === "parameter")
691
679
  this.#expression(reference.arguments[0], scope, ctes, jsonInput);
@@ -741,12 +729,7 @@ class Resolver {
741
729
  const result = this.#statement(reference.query, reference.lateral ? scope : scope.outer, ctes);
742
730
  const columns = {};
743
731
  for (const column of result.columns)
744
- columns[column.name] = {
745
- name: column.name,
746
- databaseType: column.databaseType ?? "unknown",
747
- tsType: column.tsType,
748
- nullable: column.nullable,
749
- };
732
+ columns[column.name] = this.#project(column.name, column);
750
733
  table = { name: name(reference.alias), columns };
751
734
  alias = name(reference.alias);
752
735
  }
@@ -867,6 +850,21 @@ class Resolver {
867
850
  const source = this.#expression(expression.expression, scope, ctes, this.#databaseType(expression.databaseType.name, true));
868
851
  if (!isKnownSqliteType(expression.databaseType.name))
869
852
  this.#diagnostic("TSQ106", `Invalid or unknown SQLite cast type ${expression.databaseType.name}`, expression.databaseType.range);
853
+ if (mapSqliteCastType(expression.databaseType.name, this.#policy) === "string" &&
854
+ source.json?.representation !== "binary" &&
855
+ source.jsonSubtype)
856
+ return source;
857
+ if (mapSqliteCastType(expression.databaseType.name, this.#policy) === "string" &&
858
+ source.jsonEmbedding !== undefined &&
859
+ source.json?.representation !== "binary") {
860
+ const variants = source.jsonEmbedding.kind === "union" ? source.jsonEmbedding.variants : [source.jsonEmbedding];
861
+ return {
862
+ tsType: "string",
863
+ nullable: source.nullable,
864
+ databaseType: "text",
865
+ jsonEmbedding: unionJsonShapes(variants.map((item) => ["object", "array", "tuple", "null", "unknown"].includes(item.kind) ? item : { kind: "string" })),
866
+ };
867
+ }
870
868
  return {
871
869
  tsType: mapSqliteCastType(expression.databaseType.name, this.#policy),
872
870
  nullable: source.nullable,
@@ -875,6 +873,15 @@ class Resolver {
875
873
  }
876
874
  if (expression.kind === "unary") {
877
875
  const operand = this.#expression(expression.expression, scope, ctes);
876
+ if (expression.operator !== "+" &&
877
+ expression.operator !== "NOT" &&
878
+ (operand.json !== undefined || operand.scalarJsonShape !== undefined || operand.jsonEmbedding !== undefined))
879
+ return {
880
+ tsType: unionSqliteTypes([this.#policy.integer, "number"]),
881
+ nullable: operand.nullable,
882
+ databaseType: "numeric",
883
+ runtimeType: { union: [this.#policy.integer, "number"] },
884
+ };
878
885
  return expression.operator === "NOT"
879
886
  ? { tsType: this.#policy.integer, nullable: operand.nullable, databaseType: "integer" }
880
887
  : operand;
@@ -891,10 +898,7 @@ class Resolver {
891
898
  const values = expression.branches.map((branch) => this.#expression(branch.then, scope, ctes));
892
899
  if (expression.elseExpression !== undefined)
893
900
  values.push(this.#expression(expression.elseExpression, scope, ctes));
894
- return {
895
- tsType: unionSqliteTypes(values.map((value) => value.tsType)),
896
- nullable: expression.elseExpression === undefined || values.some((value) => value.nullable),
897
- };
901
+ return this.#mergeTypes(values, expression.elseExpression === undefined || values.some((value) => value.nullable));
898
902
  }
899
903
  if (expression.kind === "subquery") {
900
904
  const result = this.#statement(expression.query, scope, ctes);
@@ -903,11 +907,8 @@ class Resolver {
903
907
  return { tsType: "unknown", nullable: true };
904
908
  }
905
909
  const column = result.columns[0];
906
- return {
907
- tsType: column.tsType,
908
- nullable: true,
909
- ...(column.databaseType === undefined ? {} : { databaseType: column.databaseType }),
910
- };
910
+ const { name: _name, range: _range, ...type } = column;
911
+ return { ...type, nullable: true };
911
912
  }
912
913
  if (expression.kind === "exists") {
913
914
  this.#statement(expression.query, scope, ctes);
@@ -955,9 +956,18 @@ class Resolver {
955
956
  if (SQLITE_UNSUPPORTED_OPERATORS.has(expression.operator)) {
956
957
  this.#unsupported(`SQLite does not support operator ${expression.operator}`, expression.range);
957
958
  }
959
+ const operator = sqliteOperator(expression.operator);
958
960
  let left;
959
961
  let right;
960
- if (expression.left.kind === "parameter" && expression.right.kind !== "parameter") {
962
+ if (operator?.result === "json-text" || operator?.result === "json-flexible") {
963
+ left = this.#expression(expression.left, scope, ctes, this.#jsonInput());
964
+ right = this.#expression(expression.right, scope, ctes, {
965
+ tsType: "string",
966
+ nullable: true,
967
+ databaseType: "text",
968
+ });
969
+ }
970
+ else if (expression.left.kind === "parameter" && expression.right.kind !== "parameter") {
961
971
  right = this.#expression(expression.right, scope, ctes);
962
972
  left = this.#expression(expression.left, scope, ctes, right);
963
973
  }
@@ -965,7 +975,6 @@ class Resolver {
965
975
  left = this.#expression(expression.left, scope, ctes);
966
976
  right = this.#expression(expression.right, scope, ctes, expression.right.kind === "parameter" ? left : undefined);
967
977
  }
968
- const operator = sqliteOperator(expression.operator);
969
978
  if (operator?.result === "json-text" || operator?.result === "json-flexible") {
970
979
  const availability = resolveSqliteBuiltin("JSON", 1, this.#schema.server?.versionKey ?? this.#schema.version, this.#schema.server?.features);
971
980
  if (!this.#catalogAvailable(availability, `operator ${expression.operator}`, expression.range))
@@ -980,13 +989,15 @@ class Resolver {
980
989
  if (operator?.result === "text") {
981
990
  return { tsType: "string", nullable: left.nullable || right.nullable, databaseType: "text" };
982
991
  }
983
- if (operator?.result === "json-text")
984
- return { tsType: "string", nullable: true, databaseType: "text" };
985
- if (operator?.result === "json-flexible")
986
- return {
987
- tsType: sqliteFlexibleType(this.#policy),
988
- nullable: true,
989
- };
992
+ if (operator?.result === "json-text" || operator?.result === "json-flexible") {
993
+ const argument = expression.right;
994
+ const path = argument.kind === "literal" && typeof argument.value === "string"
995
+ ? this.#jsonPath(argument.value.startsWith("$") ? argument.value : `$.${JSON.stringify(argument.value)}`)
996
+ : argument.kind === "literal" && typeof argument.value === "number"
997
+ ? [argument.value]
998
+ : undefined;
999
+ return this.#jsonExtract(this.#jsonDocument(left, expression.left), path, operator.result === "json-text", false, left.nullable || right.nullable);
1000
+ }
990
1001
  if (operator?.result === "numeric" && sqliteNumericOperands(left.databaseType, right.databaseType)) {
991
1002
  return {
992
1003
  tsType: unionSqliteTypes([this.#policy.integer, "number"]),
@@ -1064,7 +1075,13 @@ class Resolver {
1064
1075
  }
1065
1076
  this.#builtinParameters(functionName, expression, scope, ctes);
1066
1077
  this.#dateTimeArguments(functionName, expression);
1067
- return this.#builtinResult(builtin.definition, values, expression.arguments);
1078
+ const result = this.#jsonCall(functionName, values, expression.arguments) ??
1079
+ this.#builtinResult(builtin.definition, values, expression.arguments);
1080
+ if (builtinWindow) {
1081
+ const { jsonSubtype: _subtype, jsonEmbedding: _embedding, ...stored } = result;
1082
+ return stored;
1083
+ }
1084
+ return result;
1068
1085
  }
1069
1086
  if (builtin.status === "arity") {
1070
1087
  const ranges = [...new Set(builtin.definitions.map(({ arguments: range }) => this.#arity(range)))].join(" or ");
@@ -1198,7 +1215,7 @@ class Resolver {
1198
1215
  databaseType: "numeric",
1199
1216
  };
1200
1217
  const text = { tsType: "string", nullable: true, databaseType: "text" };
1201
- const json = { tsType: sqliteFlexibleType(this.#policy), nullable: true, databaseType: "json" };
1218
+ const json = this.#jsonInput();
1202
1219
  const jsonValue = { tsType: sqliteFlexibleType(this.#policy), nullable: true };
1203
1220
  const timeValue = {
1204
1221
  tsType: unionSqliteTypes([this.#policy.integer, "number", "string"]),
@@ -1313,10 +1330,7 @@ class Resolver {
1313
1330
  case "blob-nullable":
1314
1331
  return { tsType: "Uint8Array", nullable: true, databaseType: "blob" };
1315
1332
  case "coalesce":
1316
- return {
1317
- tsType: unionSqliteTypes(values.map((value) => value.tsType)),
1318
- nullable: values.every((value) => value.nullable),
1319
- };
1333
+ return this.#mergeTypes(values, values.every((value) => value.nullable));
1320
1334
  case "concat":
1321
1335
  return { tsType: "string", nullable: false, databaseType: "text" };
1322
1336
  case "concat-ws":
@@ -1326,16 +1340,10 @@ class Resolver {
1326
1340
  case "first-argument-nullable":
1327
1341
  return { ...first, nullable: true };
1328
1342
  case "flexible-nullable":
1329
- return {
1330
- tsType: unionSqliteTypes(values.map((value) => value.tsType)),
1331
- nullable: values.some((value) => value.nullable),
1332
- };
1343
+ return this.#mergeTypes(values, values.some((value) => value.nullable));
1333
1344
  case "iif": {
1334
1345
  const branches = values.filter((_, index) => index % 2 === 1 || (values.length % 2 === 1 && index === values.length - 1));
1335
- return {
1336
- tsType: unionSqliteTypes(branches.map((value) => value.tsType)),
1337
- nullable: values.length % 2 === 0 || branches.some((value) => value.nullable),
1338
- };
1346
+ return this.#mergeTypes(branches, values.length % 2 === 0 || branches.some((value) => value.nullable));
1339
1347
  }
1340
1348
  case "integer":
1341
1349
  return { tsType: this.#policy.integer, nullable: false, databaseType: "integer" };
@@ -1357,11 +1365,7 @@ class Resolver {
1357
1365
  : { tsType: "Uint8Array", nullable: true, databaseType: "blob" };
1358
1366
  case "lag-lead": {
1359
1367
  const fallback = values[2];
1360
- return {
1361
- ...first,
1362
- tsType: fallback === undefined ? first.tsType : unionSqliteTypes([first.tsType, fallback.tsType]),
1363
- nullable: first.nullable || fallback === undefined || fallback.nullable,
1364
- };
1368
+ return this.#mergeTypes(fallback === undefined ? [first] : [first, fallback], first.nullable || fallback === undefined || fallback.nullable);
1365
1369
  }
1366
1370
  case "nullif":
1367
1371
  return { ...first, nullable: true };
@@ -1457,8 +1461,414 @@ class Resolver {
1457
1461
  this.#diagnostic("TSQ101", `Unknown column ${identifier.name}`, identifier.range);
1458
1462
  return column;
1459
1463
  }
1464
+ #project(name, value) {
1465
+ const column = {
1466
+ name,
1467
+ databaseType: value.databaseType ?? "unknown",
1468
+ tsType: value.tsType,
1469
+ nullable: value.nullable,
1470
+ };
1471
+ const { name: _name, range: _range, jsonSubtype: _subtype, jsonEmbedding: _embedding, ...stored } = value;
1472
+ this.#projections.set(column, stored);
1473
+ return column;
1474
+ }
1475
+ #mergeTypes(values, nullable) {
1476
+ const first = values[0];
1477
+ if (first?.json !== undefined &&
1478
+ values.every((value) => value.json?.representation === first.json.representation)) {
1479
+ const result = this.#jsonResult(unionJsonShapes(values.map((value) => value.json.shape)), nullable, first.json.representation === "binary");
1480
+ return {
1481
+ ...result,
1482
+ jsonEmbedding: unionJsonShapes(values.map((value) => this.#jsonValue(value))),
1483
+ json: {
1484
+ ...result.json,
1485
+ ...(values.some((value) => value.json?.requiresRuntimeValidation) ? { requiresRuntimeValidation: true } : {}),
1486
+ },
1487
+ };
1488
+ }
1489
+ // Mixed parsed JSON/native SQL branches have no single physical decoding contract.
1490
+ if (values.some((value) => value.json?.representation === "parsed"))
1491
+ return { tsType: "unknown", nullable };
1492
+ const hasScalar = values.some((value) => value.scalarJsonShape !== undefined);
1493
+ const scalarShapes = values.map((value) => value.scalarJsonShape ??
1494
+ (value.tsType === "string" || value.json?.representation === "text"
1495
+ ? { kind: "string" }
1496
+ : value.tsType === "bigint" || value.tsType === "number"
1497
+ ? { kind: "number" }
1498
+ : undefined));
1499
+ if (hasScalar && scalarShapes.some((shape) => shape === undefined))
1500
+ return { tsType: "unknown", nullable };
1501
+ return {
1502
+ tsType: unionSqliteTypes(values.map((value) => value.tsType)),
1503
+ nullable,
1504
+ ...(hasScalar
1505
+ ? {
1506
+ scalarJsonShape: unionJsonShapes(scalarShapes),
1507
+ runtimeType: {
1508
+ union: values.map((value) => value.runtimeType ??
1509
+ (value.tsType === "string" || value.tsType === "number" || value.tsType === "bigint"
1510
+ ? value.tsType
1511
+ : "unknown")),
1512
+ },
1513
+ }
1514
+ : {}),
1515
+ ...(first?.databaseType !== undefined && values.every((value) => value.databaseType === first.databaseType)
1516
+ ? { databaseType: first.databaseType }
1517
+ : {}),
1518
+ ...(values.some((value) => value.json !== undefined || value.jsonEmbedding !== undefined)
1519
+ ? { jsonEmbedding: unionJsonShapes(values.map((value) => this.#jsonValue(value))) }
1520
+ : {}),
1521
+ };
1522
+ }
1523
+ #jsonInput() {
1524
+ return {
1525
+ tsType: sqliteFlexibleType(this.#policy),
1526
+ nullable: true,
1527
+ databaseType: "json",
1528
+ acceptsJsonInput: true,
1529
+ };
1530
+ }
1531
+ #jsonResult(shape, nullable, binary = false) {
1532
+ const representation = binary
1533
+ ? "binary"
1534
+ : this.#policy.json === "parsed" && this.#runtimeValidation
1535
+ ? "parsed"
1536
+ : "text";
1537
+ return {
1538
+ tsType: binary ? "Uint8Array" : representation === "parsed" ? jsonShapeType(shape) : "string",
1539
+ nullable,
1540
+ databaseType: binary ? "blob" : "text",
1541
+ jsonSubtype: true,
1542
+ json: {
1543
+ shape,
1544
+ representation,
1545
+ ...(representation === "parsed" || this.#requiresJsonValidation ? { requiresRuntimeValidation: true } : {}),
1546
+ },
1547
+ };
1548
+ }
1549
+ #jsonValue(value, expression) {
1550
+ if (expression?.kind === "literal" && expression.value === null)
1551
+ return nullJsonShape;
1552
+ let shape = value.jsonEmbedding ??
1553
+ (value.jsonSubtype || value.json?.representation === "binary" ? value.json?.shape : undefined) ??
1554
+ value.scalarJsonShape;
1555
+ if (shape === undefined) {
1556
+ const type = value.databaseType?.toLowerCase();
1557
+ if (["integer", "int", "real", "numeric"].includes(type ?? ""))
1558
+ shape = { kind: "number" };
1559
+ else if (["text", "varchar"].includes(type ?? ""))
1560
+ shape = { kind: "string" };
1561
+ else if (value.tsType === "string")
1562
+ shape = { kind: "string" };
1563
+ }
1564
+ shape ??= unknownJsonShape;
1565
+ return value.nullable ? unionJsonShapes([shape, nullJsonShape]) : shape;
1566
+ }
1567
+ #jsonDocument(value, expression) {
1568
+ if (value.json !== undefined)
1569
+ return value.json.shape;
1570
+ if (expression?.kind === "literal" && typeof expression.value === "string") {
1571
+ try {
1572
+ const parsed = JSON.parse(expression.value);
1573
+ // JSON.parse discards duplicate object labels; SQLite path lookup keeps the first.
1574
+ // Only canonical containers establish literal structure without another JSON parser.
1575
+ const trimmed = expression.value.trim();
1576
+ if ((trimmed.startsWith("{") || trimmed.startsWith("[")) && JSON.stringify(parsed) !== trimmed)
1577
+ return unknownJsonShape;
1578
+ return jsonShapeFromValue(parsed);
1579
+ }
1580
+ catch {
1581
+ /* JSON5 and malformed/dynamic input stay unknown. */
1582
+ }
1583
+ }
1584
+ return unknownJsonShape;
1585
+ }
1586
+ #jsonPath(text) {
1587
+ if (!text.startsWith("$"))
1588
+ return undefined;
1589
+ let rest = text.slice(1);
1590
+ const path = [];
1591
+ while (rest.length > 0 && path.length < 32) {
1592
+ const quoted = /^\.("(?:[^"\\]|\\.)*")/u.exec(rest);
1593
+ const member = /^\.([^.[\]"]+)/u.exec(rest);
1594
+ const index = /^\[(\d+|#(?:-\d+)?)\]/u.exec(rest);
1595
+ if (quoted !== null) {
1596
+ try {
1597
+ path.push(JSON.parse(quoted[1]));
1598
+ }
1599
+ catch {
1600
+ return undefined;
1601
+ }
1602
+ rest = rest.slice(quoted[0].length);
1603
+ }
1604
+ else if (member !== null) {
1605
+ path.push(member[1]);
1606
+ rest = rest.slice(member[0].length);
1607
+ }
1608
+ else if (index !== null) {
1609
+ path.push(index[1] === "#" || index[1] === "#-0"
1610
+ ? Number.POSITIVE_INFINITY
1611
+ : index[1].startsWith("#-")
1612
+ ? -Number(index[1].slice(2))
1613
+ : Number(index[1]));
1614
+ rest = rest.slice(index[0].length);
1615
+ }
1616
+ else
1617
+ return undefined;
1618
+ }
1619
+ return rest.length === 0 ? path : undefined;
1620
+ }
1621
+ #jsonExtract(shape, path, serialized, binary, nullable) {
1622
+ const selected = path === undefined ? { shape: unknownJsonShape, presence: "maybe" } : jsonShapeAtPath(shape, path);
1623
+ nullable ||= selected.presence !== "always";
1624
+ if (selected.presence === "never")
1625
+ return { tsType: "never", nullable: true, scalarJsonShape: nullJsonShape };
1626
+ if (serialized)
1627
+ return this.#jsonResult(selected.shape, nullable, binary);
1628
+ const variants = selected.shape.kind === "union" ? selected.shape.variants : [selected.shape];
1629
+ const structured = (item) => ["object", "array", "tuple"].includes(item.kind);
1630
+ if (variants.every(structured))
1631
+ return this.#jsonResult(selected.shape, nullable, binary);
1632
+ const types = [];
1633
+ const runtime = [];
1634
+ const scalars = [];
1635
+ for (const item of variants) {
1636
+ if (item.kind === "null") {
1637
+ nullable = true;
1638
+ continue;
1639
+ }
1640
+ if (structured(item)) {
1641
+ types.push(binary ? "Uint8Array" : "string");
1642
+ runtime.push(binary ? "bytes" : "string");
1643
+ continue;
1644
+ }
1645
+ const kind = item.kind === "literal" ? typeof item.value : item.kind;
1646
+ if (kind === "boolean") {
1647
+ types.push(this.#policy.integer === "bigint" ? "0n | 1n" : "0 | 1");
1648
+ runtime.push(this.#policy.integer);
1649
+ scalars.push(unionJsonShapes([
1650
+ { kind: "literal", value: 0 },
1651
+ { kind: "literal", value: 1 },
1652
+ ]));
1653
+ }
1654
+ else if (kind === "number") {
1655
+ types.push(this.#policy.integer, "number");
1656
+ runtime.push(this.#policy.integer, "number");
1657
+ scalars.push({ kind: "number" });
1658
+ }
1659
+ else if (kind === "string") {
1660
+ types.push("string");
1661
+ runtime.push("string");
1662
+ scalars.push(item);
1663
+ }
1664
+ else {
1665
+ types.push(this.#policy.integer, "number", "string", ...(binary ? ["Uint8Array"] : []));
1666
+ runtime.push(this.#policy.integer, "number", "string", ...(binary ? ["bytes"] : []));
1667
+ nullable = true;
1668
+ }
1669
+ }
1670
+ const members = [...new Set(runtime)];
1671
+ return {
1672
+ tsType: types.length === 0 ? "never" : unionSqliteTypes(types),
1673
+ nullable,
1674
+ runtimeType: members.length === 0 ? "unknown" : members.length === 1 ? members[0] : { union: members },
1675
+ jsonEmbedding: unionJsonShapes(variants.map((item) => {
1676
+ const kind = item.kind === "literal" ? typeof item.value : item.kind;
1677
+ return kind === "boolean"
1678
+ ? unionJsonShapes([
1679
+ { kind: "literal", value: 0 },
1680
+ { kind: "literal", value: 1 },
1681
+ ])
1682
+ : kind === "number"
1683
+ ? { kind: "number" }
1684
+ : item;
1685
+ })),
1686
+ ...(scalars.length === variants.length ? { scalarJsonShape: unionJsonShapes(scalars) } : {}),
1687
+ };
1688
+ }
1689
+ #jsonUpdate(shape, path, value, mode, arrayInsert = false, depth = 0, budget = { nodes: 0 }) {
1690
+ if (++budget.nodes > 1_024 || depth > 32)
1691
+ return unknownJsonShape;
1692
+ if (path.length === 0)
1693
+ return mode === "insert" ? shape : (value ?? nullJsonShape);
1694
+ if (shape.kind === "unknown")
1695
+ return shape;
1696
+ if (shape.kind === "union")
1697
+ return unionJsonShapes(shape.variants.map((item) => this.#jsonUpdate(item, path, value, mode, arrayInsert, depth + 1, budget)));
1698
+ let key = path[0];
1699
+ if (key === Number.POSITIVE_INFINITY && shape.kind === "tuple")
1700
+ key = shape.elements.length;
1701
+ if (typeof key === "string" && shape.kind === "object") {
1702
+ const selected = jsonShapeAtPath(shape, [key]);
1703
+ if (path.length === 1)
1704
+ return updateJsonShapePath(shape, [key], value, mode);
1705
+ if (selected.presence === "never" && mode === "replace")
1706
+ return shape;
1707
+ const child = selected.presence === "never"
1708
+ ? typeof path[1] === "number"
1709
+ ? jsonTupleShape([])
1710
+ : jsonObjectShape({})
1711
+ : selected.shape;
1712
+ let next = this.#jsonUpdate(child, path.slice(1), value, mode, arrayInsert, depth + 1, budget);
1713
+ if (selected.presence === "never" && next === child)
1714
+ return shape;
1715
+ if (selected.presence === "maybe" && mode !== "replace" && value !== undefined) {
1716
+ const missing = typeof path[1] === "number" ? jsonTupleShape([]) : jsonObjectShape({});
1717
+ next = unionJsonShapes([
1718
+ next,
1719
+ this.#jsonUpdate(missing, path.slice(1), value, mode, arrayInsert, depth + 1, budget),
1720
+ ]);
1721
+ }
1722
+ return updateJsonShapePath(shape, [key], next, selected.presence === "never" ? "set" : "replace");
1723
+ }
1724
+ if (typeof key === "number" && shape.kind === "tuple") {
1725
+ const index = key < 0 ? shape.elements.length + key : key;
1726
+ if (!Number.isSafeInteger(index) || index < 0 || index > shape.elements.length)
1727
+ return shape;
1728
+ if (path.length === 1) {
1729
+ if (arrayInsert && value !== undefined) {
1730
+ const elements = [...shape.elements];
1731
+ elements.splice(index, 0, value);
1732
+ return jsonTupleShape(elements);
1733
+ }
1734
+ return updateJsonShapePath(shape, [index], value, mode);
1735
+ }
1736
+ if (index === shape.elements.length && mode === "replace")
1737
+ return shape;
1738
+ const child = shape.elements[index] ?? (typeof path[1] === "number" ? jsonTupleShape([]) : jsonObjectShape({}));
1739
+ return updateJsonShapePath(shape, [index], this.#jsonUpdate(child, path.slice(1), value, mode, arrayInsert, depth + 1, budget), index === shape.elements.length ? "set" : "replace");
1740
+ }
1741
+ if (typeof key === "number" && shape.kind === "array") {
1742
+ if (key === Number.POSITIVE_INFINITY && path.length > 1)
1743
+ return unknownJsonShape;
1744
+ return value === undefined
1745
+ ? shape
1746
+ : jsonArrayShape(unionJsonShapes([
1747
+ shape.element,
1748
+ path.length === 1
1749
+ ? value
1750
+ : this.#jsonUpdate(shape.element, path.slice(1), value, mode, arrayInsert, depth + 1, budget),
1751
+ ...(path.length > 1 && mode !== "replace"
1752
+ ? [
1753
+ this.#jsonUpdate(typeof path[1] === "number" ? jsonTupleShape([]) : jsonObjectShape({}), path.slice(1), value, mode, arrayInsert, depth + 1, budget),
1754
+ ]
1755
+ : []),
1756
+ ]));
1757
+ }
1758
+ return shape;
1759
+ }
1760
+ #jsonPatch(left, right, budget = { nodes: 0 }) {
1761
+ if (++budget.nodes > 1_024)
1762
+ return unknownJsonShape;
1763
+ if (left.kind === "unknown" || right.kind === "unknown")
1764
+ return unknownJsonShape;
1765
+ if (left.kind === "union")
1766
+ return unionJsonShapes(left.variants.map((item) => this.#jsonPatch(item, right, budget)));
1767
+ if (right.kind === "union")
1768
+ return unionJsonShapes(right.variants.map((item) => this.#jsonPatch(left, item, budget)));
1769
+ if (right.kind !== "object")
1770
+ return right;
1771
+ if (right.additionalProperties !== undefined ||
1772
+ Object.values(right.properties).some((property) => property.optional))
1773
+ return unknownJsonShape;
1774
+ let result = left.kind === "object" ? left : jsonObjectShape({});
1775
+ for (const [key, property] of Object.entries(right.properties)) {
1776
+ const previous = jsonShapeAtPath(result, [key]);
1777
+ const apply = (patch) => {
1778
+ if (++budget.nodes > 1_024 || patch.kind === "unknown")
1779
+ return unknownJsonShape;
1780
+ if (patch.kind === "union")
1781
+ return unionJsonShapes(patch.variants.map(apply));
1782
+ return updateJsonShapePath(result, [key], patch.kind === "null"
1783
+ ? undefined
1784
+ : this.#jsonPatch(previous.presence === "never" ? jsonObjectShape({}) : previous.shape, patch, budget));
1785
+ };
1786
+ result = apply(property.shape);
1787
+ }
1788
+ return result;
1789
+ }
1790
+ #jsonCall(name, values, args) {
1791
+ if (!name.startsWith("JSON"))
1792
+ return undefined;
1793
+ const binary = name.startsWith("JSONB");
1794
+ const plain = name.replace(/^JSONB/u, "JSON");
1795
+ const result = (shape, nullable = false) => this.#jsonResult(shape, nullable, binary);
1796
+ if (plain === "JSON" || plain === "JSON_PRETTY")
1797
+ return result(this.#jsonDocument(values[0], args[0]), values[0].nullable);
1798
+ if (plain === "JSON_ARRAY")
1799
+ return result(jsonTupleShape(values.map((value, index) => this.#jsonValue(value, args[index]))));
1800
+ if (plain === "JSON_OBJECT") {
1801
+ const properties = {};
1802
+ for (let index = 0; index < args.length; index += 2) {
1803
+ const key = args[index];
1804
+ if (key.kind !== "literal")
1805
+ return result(unknownJsonShape);
1806
+ if (typeof key.value !== "string") {
1807
+ this.#diagnostic("TSQ202", "SQLite JSON object labels must be text", key.range);
1808
+ return { tsType: "unknown", nullable: true };
1809
+ }
1810
+ Object.defineProperty(properties, key.value, {
1811
+ value: {
1812
+ shape: Object.hasOwn(properties, key.value)
1813
+ ? unionJsonShapes([properties[key.value].shape, this.#jsonValue(values[index + 1], args[index + 1])])
1814
+ : this.#jsonValue(values[index + 1], args[index + 1]),
1815
+ },
1816
+ enumerable: true,
1817
+ configurable: true,
1818
+ });
1819
+ }
1820
+ return result(jsonObjectShape(properties));
1821
+ }
1822
+ if (plain === "JSON_GROUP_ARRAY")
1823
+ return result(jsonArrayShape(this.#jsonValue(values[0], args[0])));
1824
+ if (plain === "JSON_GROUP_OBJECT")
1825
+ return result(jsonObjectShape({}, this.#jsonValue(values[1], args[1])));
1826
+ if (plain === "JSON_QUOTE")
1827
+ return result(this.#jsonValue(values[0], args[0]));
1828
+ if (plain === "JSON_EXTRACT") {
1829
+ const shape = this.#jsonDocument(values[0], args[0]);
1830
+ const paths = args
1831
+ .slice(1)
1832
+ .map((argument) => argument.kind === "literal" && typeof argument.value === "string"
1833
+ ? this.#jsonPath(argument.value)
1834
+ : undefined);
1835
+ if (paths.length === 1)
1836
+ return this.#jsonExtract(shape, paths[0], false, binary, values[0].nullable || values[1].nullable);
1837
+ const selected = paths.map((path) => path === undefined
1838
+ ? unknownJsonShape
1839
+ : (() => {
1840
+ const item = jsonShapeAtPath(shape, path);
1841
+ return item.presence === "never"
1842
+ ? nullJsonShape
1843
+ : item.presence === "maybe"
1844
+ ? unionJsonShapes([item.shape, nullJsonShape])
1845
+ : item.shape;
1846
+ })());
1847
+ return result(jsonTupleShape(selected), values.some((value) => value.nullable));
1848
+ }
1849
+ if (["JSON_SET", "JSON_INSERT", "JSON_REPLACE", "JSON_ARRAY_INSERT", "JSON_REMOVE"].includes(plain)) {
1850
+ let shape = this.#jsonDocument(values[0], args[0]);
1851
+ for (let index = 1; index < args.length; index += plain === "JSON_REMOVE" ? 1 : 2) {
1852
+ const argument = args[index];
1853
+ const path = argument.kind === "literal" && typeof argument.value === "string"
1854
+ ? this.#jsonPath(argument.value)
1855
+ : undefined;
1856
+ if (path === undefined)
1857
+ return result(unknownJsonShape, true);
1858
+ if (plain === "JSON_REMOVE" && path.length === 0)
1859
+ return { tsType: "never", nullable: true, scalarJsonShape: nullJsonShape };
1860
+ shape = this.#jsonUpdate(shape, path, plain === "JSON_REMOVE" ? undefined : this.#jsonValue(values[index + 1], args[index + 1]), plain === "JSON_INSERT" ? "insert" : plain === "JSON_REPLACE" ? "replace" : "set", plain === "JSON_ARRAY_INSERT");
1861
+ }
1862
+ return result(shape, plain === "JSON_REMOVE"
1863
+ ? values.some((value) => value.nullable)
1864
+ : values[0].nullable || values.some((value, index) => index % 2 === 1 && value.nullable));
1865
+ }
1866
+ if (plain === "JSON_PATCH")
1867
+ return result(this.#jsonPatch(this.#jsonDocument(values[0], args[0]), this.#jsonDocument(values[1], args[1])), values.some((value) => value.nullable));
1868
+ return undefined;
1869
+ }
1460
1870
  #columnType(relation, column) {
1461
- return { tsType: column.tsType, nullable: column.nullable || relation.nullable, databaseType: column.databaseType };
1871
+ return { ...this.#snapshotType(column), nullable: column.nullable || relation.nullable };
1462
1872
  }
1463
1873
  #hasImplicitRowid(table, columnName) {
1464
1874
  if (!["rowid", "oid", "_rowid_"].includes(columnName.toLowerCase()))
@@ -1489,6 +1899,21 @@ class Resolver {
1489
1899
  #snapshotType(column) {
1490
1900
  if (column === undefined)
1491
1901
  return undefined;
1902
+ const projected = this.#projections.get(column);
1903
+ if (projected !== undefined)
1904
+ return projected;
1905
+ const declaration = column.jsonDeclaration;
1906
+ if (declaration !== undefined && (declaration.source === "database" || this.#runtimeValidation)) {
1907
+ if (declaration.source === "application")
1908
+ this.#requiresJsonValidation = true;
1909
+ const binary = column.databaseType.toLowerCase() === "blob";
1910
+ const result = this.#jsonResult(declaration.shape, column.nullable, binary);
1911
+ const { jsonSubtype: _subtype, ...stored } = result;
1912
+ return {
1913
+ ...stored,
1914
+ ...(declaration.source === "application" ? { json: { ...result.json, requiresRuntimeValidation: true } } : {}),
1915
+ };
1916
+ }
1492
1917
  return { tsType: column.tsType, nullable: column.nullable, databaseType: column.databaseType };
1493
1918
  }
1494
1919
  #generated(column) {