@hypequery/datasets 0.23.1 → 0.25.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.
Files changed (255) hide show
  1. package/README.md +14 -0
  2. package/dist/aggregations.d.ts +3 -0
  3. package/dist/aggregations.d.ts.map +1 -1
  4. package/dist/aggregations.js +6 -0
  5. package/dist/cache/definition-identity.d.ts +9 -6
  6. package/dist/cache/definition-identity.d.ts.map +1 -1
  7. package/dist/cache/definition-identity.js +14 -8
  8. package/dist/cache/protocol-cache-keys.d.ts.map +1 -1
  9. package/dist/cache/protocol-cache-keys.js +29 -6
  10. package/dist/cache/query-signature.d.ts.map +1 -1
  11. package/dist/cache/query-signature.js +12 -1
  12. package/dist/catalog.d.ts +7 -0
  13. package/dist/catalog.d.ts.map +1 -1
  14. package/dist/catalog.js +5 -2
  15. package/dist/client-contract/query-schema.d.ts +19 -0
  16. package/dist/client-contract/query-schema.d.ts.map +1 -0
  17. package/dist/client-contract/query-schema.js +85 -0
  18. package/dist/constants.d.ts +5 -4
  19. package/dist/constants.d.ts.map +1 -1
  20. package/dist/constants.js +7 -6
  21. package/dist/contract.d.ts +5 -0
  22. package/dist/contract.d.ts.map +1 -1
  23. package/dist/contract.js +6 -3
  24. package/dist/dataset-compilation.d.ts +2 -0
  25. package/dist/dataset-compilation.d.ts.map +1 -1
  26. package/dist/dataset-sql-dialect.d.ts +119 -0
  27. package/dist/dataset-sql-dialect.d.ts.map +1 -1
  28. package/dist/dataset.d.ts +4 -2
  29. package/dist/dataset.d.ts.map +1 -1
  30. package/dist/dataset.js +23 -31
  31. package/dist/in-memory-backend.d.ts.map +1 -1
  32. package/dist/in-memory-backend.js +2 -0
  33. package/dist/index.d.ts +49 -6
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +34 -6
  36. package/dist/internal.d.ts +5 -1
  37. package/dist/internal.d.ts.map +1 -1
  38. package/dist/internal.js +5 -1
  39. package/dist/measure.d.ts +9 -1
  40. package/dist/measure.d.ts.map +1 -1
  41. package/dist/measure.js +9 -1
  42. package/dist/metric-query-engine.d.ts +0 -1
  43. package/dist/metric-query-engine.d.ts.map +1 -1
  44. package/dist/metric-query-engine.js +11 -3
  45. package/dist/protocol-adapter.d.ts.map +1 -1
  46. package/dist/protocol-adapter.js +2 -3
  47. package/dist/protocol-deployment-adapter.d.ts.map +1 -1
  48. package/dist/protocol-deployment-adapter.js +7 -1
  49. package/dist/protocol-rehydrate.d.ts.map +1 -1
  50. package/dist/protocol-rehydrate.js +1 -0
  51. package/dist/protocol-schema-adapter.d.ts +7 -7
  52. package/dist/protocol-schema-adapter.d.ts.map +1 -1
  53. package/dist/protocol-schema-adapter.js +12 -245
  54. package/dist/query-builder-protocol.d.ts +34 -1
  55. package/dist/query-builder-protocol.d.ts.map +1 -1
  56. package/dist/query-planner.d.ts +14 -5
  57. package/dist/query-planner.d.ts.map +1 -1
  58. package/dist/query-planner.js +53 -34
  59. package/dist/relationship-check.d.ts.map +1 -1
  60. package/dist/relationship-check.js +8 -8
  61. package/dist/schemas.d.ts +17 -0
  62. package/dist/schemas.d.ts.map +1 -0
  63. package/dist/schemas.js +16 -0
  64. package/dist/semantic-planner.d.ts.map +1 -1
  65. package/dist/semantic-planner.js +49 -13
  66. package/dist/semantic-query-schema.d.ts +8 -1
  67. package/dist/semantic-query-schema.d.ts.map +1 -1
  68. package/dist/semantic-query-schema.js +18 -12
  69. package/dist/tools.d.ts.map +1 -1
  70. package/dist/tools.js +4 -3
  71. package/dist/types.d.ts +105 -7
  72. package/dist/types.d.ts.map +1 -1
  73. package/dist/utils/aggregate-state-functions.d.ts +24 -0
  74. package/dist/utils/aggregate-state-functions.d.ts.map +1 -0
  75. package/dist/utils/aggregate-state-functions.js +60 -0
  76. package/dist/utils/approximate-measures.d.ts +3 -1
  77. package/dist/utils/approximate-measures.d.ts.map +1 -1
  78. package/dist/utils/approximate-measures.js +8 -2
  79. package/dist/utils/base-measure-definition.d.ts +16 -0
  80. package/dist/utils/base-measure-definition.d.ts.map +1 -1
  81. package/dist/utils/base-measure-definition.js +31 -0
  82. package/dist/utils/build-dataset-query-builder.d.ts +1 -1
  83. package/dist/utils/build-dataset-query-builder.d.ts.map +1 -1
  84. package/dist/utils/build-dataset-query-builder.js +26 -21
  85. package/dist/utils/by-name.d.ts +8 -0
  86. package/dist/utils/by-name.d.ts.map +1 -0
  87. package/dist/utils/by-name.js +44 -0
  88. package/dist/utils/calendar-shift-sql.d.ts +5 -4
  89. package/dist/utils/calendar-shift-sql.d.ts.map +1 -1
  90. package/dist/utils/calendar-shift-sql.js +8 -8
  91. package/dist/utils/clickhouse-dataset-sql-dialect.d.ts +2 -2
  92. package/dist/utils/clickhouse-dataset-sql-dialect.d.ts.map +1 -1
  93. package/dist/utils/clickhouse-dataset-sql-dialect.js +143 -1
  94. package/dist/utils/compile-dataset-query.d.ts +1 -1
  95. package/dist/utils/compile-dataset-query.d.ts.map +1 -1
  96. package/dist/utils/compile-dataset-query.js +22 -8
  97. package/dist/utils/composite-time-measure-sql.d.ts +3 -5
  98. package/dist/utils/composite-time-measure-sql.d.ts.map +1 -1
  99. package/dist/utils/composite-time-measure-sql.js +15 -16
  100. package/dist/utils/count-aggregations.d.ts +4 -0
  101. package/dist/utils/count-aggregations.d.ts.map +1 -0
  102. package/dist/utils/count-aggregations.js +6 -0
  103. package/dist/utils/dataset-definition-validation.d.ts +1 -1
  104. package/dist/utils/dataset-definition-validation.d.ts.map +1 -1
  105. package/dist/utils/dataset-definition-validation.js +55 -26
  106. package/dist/utils/dataset-derived-query.d.ts +2 -4
  107. package/dist/utils/dataset-derived-query.d.ts.map +1 -1
  108. package/dist/utils/dataset-derived-query.js +11 -14
  109. package/dist/utils/dataset-having.d.ts +5 -7
  110. package/dist/utils/dataset-having.d.ts.map +1 -1
  111. package/dist/utils/dataset-having.js +9 -12
  112. package/dist/utils/dataset-normalization.d.ts.map +1 -1
  113. package/dist/utils/dataset-normalization.js +1 -0
  114. package/dist/utils/dataset-query-validation.d.ts +1 -1
  115. package/dist/utils/dataset-query-validation.d.ts.map +1 -1
  116. package/dist/utils/dataset-query-validation.js +19 -2
  117. package/dist/utils/dataset-source.d.ts +42 -0
  118. package/dist/utils/dataset-source.d.ts.map +1 -0
  119. package/dist/utils/dataset-source.js +59 -0
  120. package/dist/utils/dataset-sql-dialect.d.ts +13 -2
  121. package/dist/utils/dataset-sql-dialect.d.ts.map +1 -1
  122. package/dist/utils/dataset-sql-dialect.js +74 -1
  123. package/dist/utils/dataset-time-keys.d.ts +32 -0
  124. package/dist/utils/dataset-time-keys.d.ts.map +1 -0
  125. package/dist/utils/dataset-time-keys.js +55 -0
  126. package/dist/utils/dataset-validation.d.ts.map +1 -1
  127. package/dist/utils/dataset-validation.js +7 -0
  128. package/dist/utils/deployable-formulas.d.ts +33 -0
  129. package/dist/utils/deployable-formulas.d.ts.map +1 -0
  130. package/dist/utils/deployable-formulas.js +102 -0
  131. package/dist/utils/derived-cte-validation.d.ts +5 -0
  132. package/dist/utils/derived-cte-validation.d.ts.map +1 -1
  133. package/dist/utils/derived-cte-validation.js +77 -8
  134. package/dist/utils/derived-measure-sql.d.ts +10 -2
  135. package/dist/utils/derived-measure-sql.d.ts.map +1 -1
  136. package/dist/utils/derived-measure-sql.js +23 -43
  137. package/dist/utils/enforced-constraints.d.ts +14 -0
  138. package/dist/utils/enforced-constraints.d.ts.map +1 -0
  139. package/dist/utils/enforced-constraints.js +57 -0
  140. package/dist/utils/filter-field-type.d.ts +14 -0
  141. package/dist/utils/filter-field-type.d.ts.map +1 -0
  142. package/dist/utils/filter-field-type.js +21 -0
  143. package/dist/utils/filtered-aggregation-sql.d.ts +2 -1
  144. package/dist/utils/filtered-aggregation-sql.d.ts.map +1 -1
  145. package/dist/utils/filtered-aggregation-sql.js +18 -35
  146. package/dist/utils/groupable.d.ts +7 -0
  147. package/dist/utils/groupable.d.ts.map +1 -0
  148. package/dist/utils/groupable.js +8 -0
  149. package/dist/utils/metric-derived-query.d.ts +2 -4
  150. package/dist/utils/metric-derived-query.d.ts.map +1 -1
  151. package/dist/utils/metric-derived-query.js +58 -51
  152. package/dist/utils/metric-query-builder.d.ts +1 -1
  153. package/dist/utils/metric-query-builder.d.ts.map +1 -1
  154. package/dist/utils/metric-query-builder.js +22 -20
  155. package/dist/utils/metric-query-validation.d.ts.map +1 -1
  156. package/dist/utils/metric-query-validation.js +14 -1
  157. package/dist/utils/portable-aggregations.d.ts +1 -0
  158. package/dist/utils/portable-aggregations.d.ts.map +1 -1
  159. package/dist/utils/portable-aggregations.js +5 -0
  160. package/dist/utils/protocol-schema-conversion.d.ts +12 -0
  161. package/dist/utils/protocol-schema-conversion.d.ts.map +1 -0
  162. package/dist/utils/protocol-schema-conversion.js +61 -0
  163. package/dist/utils/query-field-references.d.ts +10 -0
  164. package/dist/utils/query-field-references.d.ts.map +1 -0
  165. package/dist/utils/query-field-references.js +38 -0
  166. package/dist/utils/query-timezone.d.ts +41 -3
  167. package/dist/utils/query-timezone.d.ts.map +1 -1
  168. package/dist/utils/query-timezone.js +76 -9
  169. package/dist/utils/raw-sql-dialect-check.d.ts +12 -0
  170. package/dist/utils/raw-sql-dialect-check.d.ts.map +1 -0
  171. package/dist/utils/raw-sql-dialect-check.js +37 -0
  172. package/dist/utils/relationship-builder-plan.d.ts +37 -18
  173. package/dist/utils/relationship-builder-plan.d.ts.map +1 -1
  174. package/dist/utils/relationship-builder-plan.js +43 -28
  175. package/dist/utils/relationship-measure-sql.d.ts +14 -4
  176. package/dist/utils/relationship-measure-sql.d.ts.map +1 -1
  177. package/dist/utils/relationship-measure-sql.js +36 -10
  178. package/dist/utils/relationship-measures.d.ts.map +1 -1
  179. package/dist/utils/relationship-measures.js +3 -0
  180. package/dist/utils/relationship-validation.d.ts.map +1 -1
  181. package/dist/utils/relationship-validation.js +2 -0
  182. package/dist/utils/row-scope.d.ts +23 -0
  183. package/dist/utils/row-scope.d.ts.map +1 -0
  184. package/dist/utils/row-scope.js +26 -0
  185. package/dist/utils/scoped-dataset-source.d.ts +23 -0
  186. package/dist/utils/scoped-dataset-source.d.ts.map +1 -0
  187. package/dist/utils/scoped-dataset-source.js +34 -0
  188. package/dist/utils/semantic-tool.d.ts +2 -2
  189. package/dist/utils/semantic-tool.d.ts.map +1 -1
  190. package/dist/utils/semantic-tool.js +1 -1
  191. package/dist/utils/shift-measure-sql.d.ts +5 -4
  192. package/dist/utils/shift-measure-sql.d.ts.map +1 -1
  193. package/dist/utils/shift-measure-sql.js +25 -26
  194. package/dist/utils/shift-wall-time-sql.d.ts +2 -1
  195. package/dist/utils/shift-wall-time-sql.d.ts.map +1 -1
  196. package/dist/utils/shift-wall-time-sql.js +7 -7
  197. package/dist/utils/snapshot-execution-context.d.ts.map +1 -1
  198. package/dist/utils/snapshot-execution-context.js +3 -0
  199. package/dist/utils/statement-parameters.d.ts +58 -0
  200. package/dist/utils/statement-parameters.d.ts.map +1 -0
  201. package/dist/utils/statement-parameters.js +101 -0
  202. package/dist/utils/time-axis-calendar-guard.d.ts +2 -1
  203. package/dist/utils/time-axis-calendar-guard.d.ts.map +1 -1
  204. package/dist/utils/time-axis-calendar-guard.js +5 -7
  205. package/dist/utils/time-measure-axis-sql.d.ts +3 -2
  206. package/dist/utils/time-measure-axis-sql.d.ts.map +1 -1
  207. package/dist/utils/time-measure-axis-sql.js +20 -22
  208. package/dist/utils/time-measure-context-sql.d.ts +3 -2
  209. package/dist/utils/time-measure-context-sql.d.ts.map +1 -1
  210. package/dist/utils/time-measure-context-sql.js +11 -11
  211. package/dist/utils/time-measure-dataset-sql.d.ts +7 -5
  212. package/dist/utils/time-measure-dataset-sql.d.ts.map +1 -1
  213. package/dist/utils/time-measure-dataset-sql.js +17 -12
  214. package/dist/utils/time-measure-leaf-sql.d.ts +3 -2
  215. package/dist/utils/time-measure-leaf-sql.d.ts.map +1 -1
  216. package/dist/utils/time-measure-leaf-sql.js +19 -18
  217. package/dist/utils/time-measure-result-sql.d.ts.map +1 -1
  218. package/dist/utils/time-measure-result-sql.js +10 -11
  219. package/dist/utils/time-measure-source-sql.d.ts +5 -2
  220. package/dist/utils/time-measure-source-sql.d.ts.map +1 -1
  221. package/dist/utils/time-measure-source-sql.js +33 -23
  222. package/dist/utils/time-measure-values-sql.d.ts +4 -3
  223. package/dist/utils/time-measure-values-sql.d.ts.map +1 -1
  224. package/dist/utils/time-measure-values-sql.js +26 -29
  225. package/dist/utils/timestamp-instants.d.ts +17 -0
  226. package/dist/utils/timestamp-instants.d.ts.map +1 -0
  227. package/dist/utils/timestamp-instants.js +34 -0
  228. package/dist/utils/timestamp-text.d.ts +12 -0
  229. package/dist/utils/timestamp-text.d.ts.map +1 -0
  230. package/dist/utils/timestamp-text.js +18 -0
  231. package/dist/utils/zod-json-schema.d.ts +8 -0
  232. package/dist/utils/zod-json-schema.d.ts.map +1 -0
  233. package/dist/utils/zod-json-schema.js +18 -0
  234. package/dist/utils/zod-schema-version.d.ts +19 -0
  235. package/dist/utils/zod-schema-version.d.ts.map +1 -0
  236. package/dist/utils/zod-schema-version.js +6 -0
  237. package/dist/utils/zod-v3-protocol-schema.d.ts +10 -0
  238. package/dist/utils/zod-v3-protocol-schema.d.ts.map +1 -0
  239. package/dist/utils/zod-v3-protocol-schema.js +164 -0
  240. package/dist/utils/zod-v4-protocol-schema.d.ts +12 -0
  241. package/dist/utils/zod-v4-protocol-schema.d.ts.map +1 -0
  242. package/dist/utils/zod-v4-protocol-schema.js +171 -0
  243. package/dist/validation.d.ts +7 -0
  244. package/dist/validation.d.ts.map +1 -1
  245. package/dist/validation.js +42 -8
  246. package/package.json +21 -12
  247. package/dist/api.type-test.d.ts +0 -2
  248. package/dist/api.type-test.d.ts.map +0 -1
  249. package/dist/api.type-test.js +0 -251
  250. package/dist/utils/time-arithmetic-sql.d.ts +0 -4
  251. package/dist/utils/time-arithmetic-sql.d.ts.map +0 -1
  252. package/dist/utils/time-arithmetic-sql.js +0 -9
  253. package/dist/utils/window-aggregation-sql.d.ts +0 -5
  254. package/dist/utils/window-aggregation-sql.d.ts.map +0 -1
  255. package/dist/utils/window-aggregation-sql.js +0 -17
@@ -1,54 +1,51 @@
1
- import { GRAIN_FUNCTIONS } from '../constants.js';
2
- import { quoteSQLIdentifier } from '../sql-utils.js';
3
- import { addTimeSql as add, subtractTimeSql as subtract } from './time-arithmetic-sql.js';
4
- import { isCountAggregation, windowAggregateSql } from './window-aggregation-sql.js';
1
+ import { isCountAggregation } from './count-aggregations.js';
5
2
  import { calendarBucketGuardSql } from './time-axis-calendar-guard.js';
6
3
  /** One row is emitted into each bounded window it contributes to. */
7
- function contributionSql(window, grain) {
8
- const bucket = `${GRAIN_FUNCTIONS[grain]}(_hq_time)`;
4
+ function contributionSql(dialect, window, grain) {
5
+ const bucket = dialect.truncateTime('_hq_time', grain);
9
6
  if (!window)
10
7
  return bucket;
11
8
  if (window.cumulative)
12
- return `greatest(${bucket}, _hq_first)`;
9
+ return dialect.greatest(bucket, '_hq_first');
13
10
  let count;
14
11
  if (window.trailing) {
15
- const end = add(bucket, window.trailing.amount, window.trailing.unit);
16
- count = `dateDiff('${grain}', ${bucket}, ${end})`;
12
+ const end = dialect.addTime(bucket, window.trailing.amount, window.trailing.unit);
13
+ count = dialect.timeDiff(grain, bucket, end);
17
14
  }
18
15
  else if (window.toDate) {
19
- const calendarStart = `${GRAIN_FUNCTIONS[window.toDate]}(_hq_time)`;
20
- const end = add(calendarStart, 1, window.toDate);
21
- count = `dateDiff('${grain}', ${bucket}, ${end})`;
16
+ const calendarStart = dialect.truncateTime('_hq_time', window.toDate);
17
+ const end = dialect.addTime(calendarStart, 1, window.toDate);
18
+ count = dialect.timeDiff(grain, bucket, end);
22
19
  }
23
20
  else {
24
21
  throw new Error('Window measure has no supported mode.');
25
22
  }
26
- const fanoutGuard = `throwIf(${count} > 1000, 'A window row contributes to more than 1000 buckets.')`;
27
- const calendarGuard = calendarBucketGuardSql(bucket, `${count} + ${fanoutGuard}`, grain);
28
- const bucketIndices = `arrayJoin(range(toUInt64(${count} + ${fanoutGuard} + ${calendarGuard})))`;
29
- return add(bucket, bucketIndices, grain);
23
+ const fanoutGuard = dialect.guard(`${count} > 1000`, 'A window row contributes to more than 1000 buckets.');
24
+ const calendarGuard = calendarBucketGuardSql(dialect, bucket, `${count} + ${fanoutGuard}`, grain);
25
+ const bucketIndices = dialect.seriesIndex(`${count} + ${fanoutGuard} + ${calendarGuard}`);
26
+ return dialect.addTime(bucket, bucketIndices, grain);
30
27
  }
31
- export function windowScanStartSql(window, grain) {
28
+ export function windowScanStartSql(dialect, window, grain) {
32
29
  if (window.trailing)
33
- return subtract(add('_hq_first', 1, grain), window.trailing.amount, window.trailing.unit);
30
+ return dialect.subtractTime(dialect.addTime('_hq_first', 1, grain), window.trailing.amount, window.trailing.unit);
34
31
  if (window.toDate)
35
- return `${GRAIN_FUNCTIONS[window.toDate]}(_hq_first)`;
32
+ return dialect.truncateTime('_hq_first', window.toDate);
36
33
  throw new Error('Cumulative windows have unbounded history.');
37
34
  }
38
35
  /** Base measures respect partial endpoints; windows use entire contributing buckets. */
39
- export function windowMeasureRowsSql(window, axis) {
36
+ export function windowMeasureRowsSql(dialect, window, axis) {
40
37
  const range = window ? '' : ` WHERE _hq_time ${axis.lowerInclusive ? '>=' : '>'} _hq_lower AND _hq_time ${axis.upperInclusive ? '<=' : '<'} _hq_upper`;
41
- return `SELECT *, ${contributionSql(window, axis.grain)} AS _hq_period FROM _hq_scanned${range}`;
38
+ return `SELECT *, ${contributionSql(dialect, window, axis.grain)} AS _hq_period FROM _hq_scanned${range}`;
42
39
  }
43
40
  /** Fill dimension combinations, aggregate populations, and join measure values onto the series. */
44
- export function buildTimeMeasureValuesSql(dims, inputs, axis) {
41
+ export function buildTimeMeasureValuesSql(dialect, dims, inputs, axis) {
45
42
  const ctes = [];
46
43
  const keys = dims.map(dimension => dimension.alias);
47
44
  const combinations = keys.length
48
45
  ? ` CROSS JOIN (SELECT DISTINCT ${keys.join(', ')} FROM _hq_scanned) AS _hq_combinations`
49
46
  : '';
50
47
  ctes.push(`_hq_skeleton AS (SELECT * FROM _hq_series${combinations})`);
51
- const projections = ['s._hq_period AS period', ...dims.map(dimension => `s.${dimension.alias} AS ${quoteSQLIdentifier(dimension.name)}`)];
48
+ const projections = ['s._hq_period AS period', ...dims.map(dimension => `s.${dimension.alias} AS ${dialect.quoteIdentifier(dimension.name)}`)];
52
49
  const joins = [];
53
50
  let index = 0;
54
51
  for (const input of inputs) {
@@ -58,28 +55,28 @@ export function buildTimeMeasureValuesSql(dims, inputs, axis) {
58
55
  const aggregate = `_hq_agg${index}`;
59
56
  ctes.push(...rowCtes, `${fan} AS (${rowsSql})`);
60
57
  // toNullable makes unmatched LEFT JOIN values NULL regardless of join_use_nulls.
61
- const expression = windowAggregateSql(base.definition, base.value, base.arg);
58
+ const expression = dialect.windowAggregate(base.definition, base.value, base.arg);
62
59
  const groupColumns = `_hq_period${keys.length ? `, ${keys.join(', ')}` : ''}`;
63
60
  const outputRange = restrictToAxis ? ' WHERE _hq_period BETWEEN _hq_first AND _hq_last' : '';
64
61
  const trailingRange = window?.trailing
65
- ? ` AND _hq_time >= ${subtract(add('_hq_period', 1, axis.grain), window.trailing.amount, window.trailing.unit)}`
62
+ ? ` AND _hq_time >= ${dialect.subtractTime(dialect.addTime('_hq_period', 1, axis.grain), window.trailing.amount, window.trailing.unit)}`
66
63
  : '';
67
- const aggregationSql = `SELECT ${groupColumns}, toNullable(${expression}) AS ${output}`
64
+ const aggregationSql = `SELECT ${groupColumns}, ${dialect.toNullable(expression)} AS ${output}`
68
65
  + ` FROM ${fan}${outputRange}${trailingRange} GROUP BY ${groupColumns}`;
69
66
  ctes.push(`${aggregate} AS (${aggregationSql})`);
70
67
  const alias = `m${index}`;
71
- const condition = [`s._hq_period = ${alias}._hq_period`, ...keys.map(key => `isNotDistinctFrom(s.${key}, ${alias}.${key})`)];
68
+ const condition = [`s._hq_period = ${alias}._hq_period`, ...keys.map(key => dialect.isNotDistinctFrom(`s.${key}`, `${alias}.${key}`))];
72
69
  joins.push(`LEFT JOIN ${aggregate} AS ${alias} ON ${condition.join(' AND ')}`);
73
70
  let value = `${alias}.${output}`;
74
71
  if (window?.cumulative) {
75
72
  const operation = base.definition.aggregation === 'count' ? 'sum' : base.definition.aggregation;
76
73
  const partition = keys.length ? `PARTITION BY ${keys.map(key => `s.${key}`).join(', ')} ` : '';
77
74
  const frame = `${partition}ORDER BY s._hq_period ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`;
78
- value = `${operation}OrNull(${value}) OVER (${frame})`;
75
+ value = `${dialect.reaggregate(operation, value)} OVER (${frame})`;
79
76
  }
80
77
  if (isCountAggregation(base.definition))
81
78
  value = `coalesce(${value}, 0)`;
82
- projections.push(`${value} AS ${quoteSQLIdentifier(name)}`);
79
+ projections.push(`${value} AS ${dialect.quoteIdentifier(name)}`);
83
80
  index++;
84
81
  }
85
82
  ctes.push(`_hq_values AS (SELECT ${projections.join(', ')} FROM _hq_skeleton AS s ${joins.join(' ')})`);
@@ -0,0 +1,17 @@
1
+ import { type ParsedTimeBound } from './time-axis-bounds.js';
2
+ /**
3
+ * Parse an RFC 3339 instant: ISO 8601 text carrying `Z` or a numeric offset,
4
+ * the form `Date.prototype.toISOString` produces. Offset-free text is a local
5
+ * reading rather than an instant and yields undefined, as does anything that
6
+ * is not a timestamp.
7
+ */
8
+ export declare function parseTimestampInstant(value: unknown): ParsedTimeBound | undefined;
9
+ /**
10
+ * The wall clock an instant shows in `timezone`, spelled
11
+ * `YYYY-MM-DD hh:mm:ss[.fraction]`: the form ClickHouse reads as local time in
12
+ * the zone of the DateTime64 it is compared against. Offsets are whole
13
+ * minutes, so the authored sub-second digits survive unchanged; a zero
14
+ * fraction is dropped.
15
+ */
16
+ export declare function instantWallClock(instant: ParsedTimeBound, timezone: string): string;
17
+ //# sourceMappingURL=timestamp-instants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timestamp-instants.d.ts","sourceRoot":"","sources":["../../src/utils/timestamp-instants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7E;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAGjF;AAUD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CASnF"}
@@ -0,0 +1,34 @@
1
+ import { parseTimeBound } from './time-axis-bounds.js';
2
+ /**
3
+ * Parse an RFC 3339 instant: ISO 8601 text carrying `Z` or a numeric offset,
4
+ * the form `Date.prototype.toISOString` produces. Offset-free text is a local
5
+ * reading rather than an instant and yields undefined, as does anything that
6
+ * is not a timestamp.
7
+ */
8
+ export function parseTimestampInstant(value) {
9
+ const instant = parseTimeBound(value);
10
+ return instant?.hasOffset ? instant : undefined;
11
+ }
12
+ const NANOSECONDS_PER_SECOND = 1000000000n;
13
+ const CLOCK_PARTS = {
14
+ hourCycle: 'h23',
15
+ year: 'numeric', month: '2-digit', day: '2-digit',
16
+ hour: '2-digit', minute: '2-digit', second: '2-digit',
17
+ };
18
+ /**
19
+ * The wall clock an instant shows in `timezone`, spelled
20
+ * `YYYY-MM-DD hh:mm:ss[.fraction]`: the form ClickHouse reads as local time in
21
+ * the zone of the DateTime64 it is compared against. Offsets are whole
22
+ * minutes, so the authored sub-second digits survive unchanged; a zero
23
+ * fraction is dropped.
24
+ */
25
+ export function instantWallClock(instant, timezone) {
26
+ const parts = new Intl.DateTimeFormat('en-US', { ...CLOCK_PARTS, timeZone: timezone })
27
+ .formatToParts(new Date(instant.milliseconds));
28
+ const part = (type) => parts.find(entry => entry.type === type)?.value ?? '';
29
+ const date = `${part('year').padStart(4, '0')}-${part('month')}-${part('day')}`;
30
+ const clock = `${part('hour')}:${part('minute')}:${part('second')}`;
31
+ const remainder = ((instant.nanoseconds % NANOSECONDS_PER_SECOND) + NANOSECONDS_PER_SECOND) % NANOSECONDS_PER_SECOND;
32
+ const fraction = remainder === 0n ? '' : `.${remainder.toString().padStart(9, '0').replace(/0+$/, '')}`;
33
+ return `${date} ${clock}${fraction}`;
34
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * True for the timestamp text a filter may bind: a real calendar date, or a
3
+ * date and time with seconds, optionally with a fraction and `Z`/offset.
4
+ *
5
+ * ClickHouse reads an invalid `DateTime64` parameter as some other instant
6
+ * instead of refusing it (`2026-13-45` becomes 1900-01-01, `2026-02-30`
7
+ * rolls into March, digits are read as Unix seconds), and refuses a time
8
+ * without seconds, so anything else is rejected before planning. This is
9
+ * the same rule as the Python planner's.
10
+ */
11
+ export declare function isTimestampText(value: unknown): value is string;
12
+ //# sourceMappingURL=timestamp-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timestamp-text.d.ts","sourceRoot":"","sources":["../../src/utils/timestamp-text.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAK/D"}
@@ -0,0 +1,18 @@
1
+ import { parseTimeBound } from './time-axis-bounds.js';
2
+ /**
3
+ * True for the timestamp text a filter may bind: a real calendar date, or a
4
+ * date and time with seconds, optionally with a fraction and `Z`/offset.
5
+ *
6
+ * ClickHouse reads an invalid `DateTime64` parameter as some other instant
7
+ * instead of refusing it (`2026-13-45` becomes 1900-01-01, `2026-02-30`
8
+ * rolls into March, digits are read as Unix seconds), and refuses a time
9
+ * without seconds, so anything else is rejected before planning. This is
10
+ * the same rule as the Python planner's.
11
+ */
12
+ export function isTimestampText(value) {
13
+ const parsed = parseTimeBound(value);
14
+ if (!parsed)
15
+ return false;
16
+ const time = parsed.text.slice(10);
17
+ return time === '' || /^[T ]\d{2}:\d{2}:\d{2}/.test(time);
18
+ }
@@ -0,0 +1,8 @@
1
+ import { type AnyZodSchema } from './zod-schema-version.js';
2
+ /**
3
+ * Draft-07 JSON Schema for a Zod 3 or Zod 4 schema, describing what a caller
4
+ * may send. `zod-to-json-schema` only reads Zod 3 (it returns an empty schema
5
+ * for Zod 4), and Zod 4 ships its own generator.
6
+ */
7
+ export declare function zodToDraft7JsonSchema(schema: AnyZodSchema): Record<string, unknown>;
8
+ //# sourceMappingURL=zod-json-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod-json-schema.d.ts","sourceRoot":"","sources":["../../src/utils/zod-json-schema.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE1E;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASnF"}
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ import { zodToJsonSchema } from 'zod-to-json-schema';
3
+ import { isZod4Schema } from './zod-schema-version.js';
4
+ /**
5
+ * Draft-07 JSON Schema for a Zod 3 or Zod 4 schema, describing what a caller
6
+ * may send. `zod-to-json-schema` only reads Zod 3 (it returns an empty schema
7
+ * for Zod 4), and Zod 4 ships its own generator.
8
+ */
9
+ export function zodToDraft7JsonSchema(schema) {
10
+ if (isZod4Schema(schema)) {
11
+ return z.toJSONSchema(schema, {
12
+ target: 'draft-7',
13
+ io: 'input',
14
+ unrepresentable: 'any',
15
+ });
16
+ }
17
+ return zodToJsonSchema(schema, { target: 'jsonSchema7', $refStrategy: 'none' });
18
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Any Zod schema, from either major version or another installed copy of Zod.
3
+ *
4
+ * Structural on purpose: a caller's schema built with their own `zod@3` is not
5
+ * assignable to this package's `zod@4` types, but both carry their definition
6
+ * on `_def` (v3) or `_zod` (v4).
7
+ */
8
+ export type AnyZodSchema = {
9
+ readonly _def: unknown;
10
+ } | {
11
+ readonly _zod: unknown;
12
+ };
13
+ /** Zod 4 (classic or mini) keeps its internals on `_zod`; Zod 3 has none. */
14
+ export declare function isZod4Schema(schema: unknown): schema is {
15
+ readonly _zod: {
16
+ readonly def: Record<string, any>;
17
+ };
18
+ };
19
+ //# sourceMappingURL=zod-schema-version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod-schema-version.d.ts","sourceRoot":"","sources":["../../src/utils/zod-schema-version.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEnF,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI;IAAE,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAAE,CAAA;CAAE,CAIhH"}
@@ -0,0 +1,6 @@
1
+ /** Zod 4 (classic or mini) keeps its internals on `_zod`; Zod 3 has none. */
2
+ export function isZod4Schema(schema) {
3
+ return typeof schema === 'object'
4
+ && schema !== null
5
+ && typeof schema._zod === 'object';
6
+ }
@@ -0,0 +1,10 @@
1
+ import { type ProtocolSchemaDraft } from './protocol-schema-conversion.js';
2
+ /** A Zod 3 schema, read through its `_def`. Callers may hold any 3.x copy of Zod. */
3
+ type ZodV3Schema = {
4
+ readonly _def: Record<string, any>;
5
+ readonly description?: string;
6
+ };
7
+ /** Converts the portable subset of a Zod 3 schema into an RFC 0004 draft. */
8
+ export declare function convertZodV3Schema(schema: ZodV3Schema, path: string): ProtocolSchemaDraft;
9
+ export {};
10
+ //# sourceMappingURL=zod-v3-protocol-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod-v3-protocol-schema.d.ts","sourceRoot":"","sources":["../../src/utils/zod-v3-protocol-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,mBAAmB,EACzB,MAAM,iCAAiC,CAAC;AAEzC,qFAAqF;AACrF,KAAK,WAAW,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AA8DzF,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAyGzF"}
@@ -0,0 +1,164 @@
1
+ import { ProtocolSchemaAdapterError, annotate, canonicalValue, nativeEnumValues, withDefault, } from './protocol-schema-conversion.js';
2
+ function typeName(schema) {
3
+ return String(schema?._def?.typeName ?? 'Unknown');
4
+ }
5
+ function convertString(schema, path) {
6
+ const result = { kind: 'string' };
7
+ for (const check of schema._def.checks) {
8
+ switch (check.kind) {
9
+ case 'min':
10
+ result.minLength = check.value;
11
+ break;
12
+ case 'max':
13
+ result.maxLength = check.value;
14
+ break;
15
+ case 'length':
16
+ result.minLength = check.value;
17
+ result.maxLength = check.value;
18
+ break;
19
+ default:
20
+ throw new ProtocolSchemaAdapterError(`Unsupported Zod string check "${String(check.kind)}"`, path);
21
+ }
22
+ }
23
+ return annotate(schema.description, result);
24
+ }
25
+ function convertNumber(schema, path) {
26
+ const result = { kind: 'number' };
27
+ for (const check of schema._def.checks) {
28
+ switch (check.kind) {
29
+ case 'int':
30
+ result.kind = 'integer';
31
+ break;
32
+ case 'min':
33
+ result[check.inclusive === false ? 'exclusiveMinimum' : 'minimum'] = check.value;
34
+ break;
35
+ case 'max':
36
+ result[check.inclusive === false ? 'exclusiveMaximum' : 'maximum'] = check.value;
37
+ break;
38
+ case 'finite':
39
+ break;
40
+ default:
41
+ throw new ProtocolSchemaAdapterError(`Unsupported Zod number check "${String(check.kind)}"`, path);
42
+ }
43
+ }
44
+ return annotate(schema.description, result);
45
+ }
46
+ function isOptionalProperty(schema) {
47
+ const name = typeName(schema);
48
+ return name === 'ZodOptional' || name === 'ZodDefault';
49
+ }
50
+ function isUnconstrainedRecordKey(schema) {
51
+ if (schema === undefined || typeName(schema) !== 'ZodString')
52
+ return false;
53
+ const definition = schema._def;
54
+ return Array.isArray(definition.checks)
55
+ && definition.checks.length === 0
56
+ && definition.coerce !== true;
57
+ }
58
+ /** Converts the portable subset of a Zod 3 schema into an RFC 0004 draft. */
59
+ export function convertZodV3Schema(schema, path) {
60
+ const definition = schema._def;
61
+ const done = (result) => annotate(schema.description, result);
62
+ switch (typeName(schema)) {
63
+ case 'ZodAny':
64
+ case 'ZodUnknown':
65
+ return done({ kind: 'any' });
66
+ case 'ZodNever':
67
+ case 'ZodVoid':
68
+ case 'ZodUndefined':
69
+ return done({ kind: 'void' });
70
+ case 'ZodNull':
71
+ return done({ kind: 'null' });
72
+ case 'ZodBoolean':
73
+ return done({ kind: 'boolean' });
74
+ case 'ZodString':
75
+ return convertString(schema, path);
76
+ case 'ZodNumber':
77
+ return convertNumber(schema, path);
78
+ case 'ZodLiteral':
79
+ return done({ kind: 'literal', value: canonicalValue(definition.value, `${path}.value`) });
80
+ case 'ZodEnum':
81
+ return done({ kind: 'enum', values: [...definition.values] });
82
+ case 'ZodNativeEnum':
83
+ return done({ kind: 'enum', values: nativeEnumValues(definition.values) });
84
+ case 'ZodArray': {
85
+ const result = {
86
+ kind: 'array',
87
+ items: convertZodV3Schema(definition.type, `${path}.items`),
88
+ };
89
+ if (definition.minLength?.value !== undefined)
90
+ result.minItems = definition.minLength.value;
91
+ if (definition.maxLength?.value !== undefined)
92
+ result.maxItems = definition.maxLength.value;
93
+ if (definition.exactLength?.value !== undefined) {
94
+ result.minItems = definition.exactLength.value;
95
+ result.maxItems = definition.exactLength.value;
96
+ }
97
+ return done(result);
98
+ }
99
+ case 'ZodObject': {
100
+ if (typeName(definition.catchall) !== 'ZodNever') {
101
+ throw new ProtocolSchemaAdapterError('Unsupported Zod object catchall', path);
102
+ }
103
+ const shape = definition.shape();
104
+ return done({
105
+ kind: 'object',
106
+ properties: Object.fromEntries(Object.entries(shape).map(([name, property]) => [
107
+ name,
108
+ convertZodV3Schema(property, `${path}.properties.${name}`),
109
+ ])),
110
+ required: Object.entries(shape)
111
+ .filter(([, property]) => !isOptionalProperty(property))
112
+ .map(([name]) => name),
113
+ unknownProperties: definition.unknownKeys === 'passthrough'
114
+ ? 'preserve'
115
+ : definition.unknownKeys === 'strict'
116
+ ? 'reject'
117
+ : 'strip',
118
+ });
119
+ }
120
+ case 'ZodRecord':
121
+ if (!isUnconstrainedRecordKey(definition.keyType)) {
122
+ throw new ProtocolSchemaAdapterError('Unsupported constrained Zod record key', path);
123
+ }
124
+ return done({ kind: 'record', values: convertZodV3Schema(definition.valueType, `${path}.values`) });
125
+ case 'ZodUnion':
126
+ return done({
127
+ kind: 'union',
128
+ variants: definition.options.map((option, index) => convertZodV3Schema(option, `${path}.variants[${index}]`)),
129
+ });
130
+ case 'ZodDiscriminatedUnion':
131
+ // RFC 0004 has no separate discriminator index. Converting every option
132
+ // drops only Zod's lookup metadata; each object's literal discriminator
133
+ // property remains part of its protocol variant.
134
+ return done({
135
+ kind: 'union',
136
+ variants: [...definition.options.values()].map((option, index) => convertZodV3Schema(option, `${path}.variants[${index}]`)),
137
+ });
138
+ case 'ZodNullable':
139
+ return done({
140
+ kind: 'union',
141
+ variants: [convertZodV3Schema(definition.innerType, `${path}.variants[0]`), { kind: 'null' }],
142
+ });
143
+ case 'ZodOptional':
144
+ return done(convertZodV3Schema(definition.innerType, path));
145
+ case 'ZodDefault':
146
+ return done(withDefault(convertZodV3Schema(definition.innerType, path), definition.defaultValue(), path));
147
+ case 'ZodBranded':
148
+ return done(convertZodV3Schema(definition.type, path));
149
+ case 'ZodReadonly':
150
+ return done(convertZodV3Schema(definition.innerType, path));
151
+ case 'ZodEffects': {
152
+ const effectType = String(definition.effect?.type ?? 'unknown');
153
+ if (effectType !== 'refinement') {
154
+ throw new ProtocolSchemaAdapterError(`Unsupported Zod effect "${effectType}"`, path);
155
+ }
156
+ // A refinement preserves the wrapped schema's input and output shape.
157
+ // The rule itself remains enforced by Zod, but cannot be advertised by
158
+ // ProtocolSchema (for example, a cross-field selection requirement).
159
+ return done(convertZodV3Schema(definition.schema, path));
160
+ }
161
+ default:
162
+ throw new ProtocolSchemaAdapterError(`Unsupported Zod type "${typeName(schema)}"`, path);
163
+ }
164
+ }
@@ -0,0 +1,12 @@
1
+ import { type ProtocolSchemaDraft } from './protocol-schema-conversion.js';
2
+ /** A Zod 4 schema, read through `_zod.def`. Classic schemas also expose `description`. */
3
+ type ZodV4Schema = {
4
+ readonly _zod: {
5
+ readonly def: Record<string, any>;
6
+ };
7
+ readonly description?: string;
8
+ };
9
+ /** Converts the portable subset of a Zod 4 schema into an RFC 0004 draft. */
10
+ export declare function convertZodV4Schema(schema: ZodV4Schema, path: string): ProtocolSchemaDraft;
11
+ export {};
12
+ //# sourceMappingURL=zod-v4-protocol-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod-v4-protocol-schema.d.ts","sourceRoot":"","sources":["../../src/utils/zod-v4-protocol-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,mBAAmB,EACzB,MAAM,iCAAiC,CAAC;AAEzC,0FAA0F;AAC1F,KAAK,WAAW,GAAG;IACjB,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAAE,CAAC;IACrD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAwHF,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,mBAAmB,CA4EzF"}
@@ -0,0 +1,171 @@
1
+ import { ProtocolSchemaAdapterError, annotate, canonicalValue, nativeEnumValues, withDefault, } from './protocol-schema-conversion.js';
2
+ const INTEGER_FORMATS = new Set(['safeint', 'int32', 'uint32']);
3
+ function typeOf(schema) {
4
+ return String(schema?._zod?.def?.type ?? 'unknown');
5
+ }
6
+ function checksOf(schema) {
7
+ return (schema._zod.def.checks ?? []).map(check => check._zod.def);
8
+ }
9
+ /** Zod 4 stores `.refine()` as a `custom` check on the schema it refines. */
10
+ function isRefinement(check) {
11
+ return check.check === 'custom';
12
+ }
13
+ function applyLengthChecks(schema, result, keys, label, path) {
14
+ for (const check of checksOf(schema)) {
15
+ switch (check.check) {
16
+ case 'min_length':
17
+ result[keys.min] = check.minimum;
18
+ break;
19
+ case 'max_length':
20
+ result[keys.max] = check.maximum;
21
+ break;
22
+ case 'length_equals':
23
+ result[keys.min] = check.length;
24
+ result[keys.max] = check.length;
25
+ break;
26
+ default:
27
+ if (isRefinement(check))
28
+ break;
29
+ throw new ProtocolSchemaAdapterError(`Unsupported Zod ${label} check "${String(check.format ?? check.check)}"`, path);
30
+ }
31
+ }
32
+ return result;
33
+ }
34
+ function convertNumber(schema, path) {
35
+ const result = { kind: 'number' };
36
+ for (const check of checksOf(schema)) {
37
+ switch (check.check) {
38
+ case 'number_format':
39
+ if (!INTEGER_FORMATS.has(check.format)) {
40
+ throw new ProtocolSchemaAdapterError(`Unsupported Zod number check "${String(check.format)}"`, path);
41
+ }
42
+ result.kind = 'integer';
43
+ break;
44
+ case 'greater_than':
45
+ result[check.inclusive ? 'minimum' : 'exclusiveMinimum'] = check.value;
46
+ break;
47
+ case 'less_than':
48
+ result[check.inclusive ? 'maximum' : 'exclusiveMaximum'] = check.value;
49
+ break;
50
+ default:
51
+ if (isRefinement(check))
52
+ break;
53
+ throw new ProtocolSchemaAdapterError(`Unsupported Zod number check "${String(check.check)}"`, path);
54
+ }
55
+ }
56
+ return result;
57
+ }
58
+ function isOptionalProperty(schema) {
59
+ const type = typeOf(schema);
60
+ return type === 'optional' || type === 'default';
61
+ }
62
+ function isUnconstrainedRecordKey(schema) {
63
+ return schema !== undefined
64
+ && typeOf(schema) === 'string'
65
+ && checksOf(schema).length === 0
66
+ && schema._zod.def.coerce !== true;
67
+ }
68
+ function unknownProperties(catchall, path) {
69
+ if (catchall === undefined)
70
+ return 'strip';
71
+ switch (typeOf(catchall)) {
72
+ case 'never':
73
+ return 'reject';
74
+ case 'unknown':
75
+ return 'preserve';
76
+ default:
77
+ throw new ProtocolSchemaAdapterError('Unsupported Zod object catchall', path);
78
+ }
79
+ }
80
+ function literal(values, path) {
81
+ if (values.length === 1) {
82
+ return { kind: 'literal', value: canonicalValue(values[0], `${path}.value`) };
83
+ }
84
+ return {
85
+ kind: 'union',
86
+ variants: values.map((value, index) => ({
87
+ kind: 'literal',
88
+ value: canonicalValue(value, `${path}.variants[${index}].value`),
89
+ })),
90
+ };
91
+ }
92
+ /**
93
+ * Zod 4 has no effects wrapper: a transform is a pipe into a `transform`, and
94
+ * a preprocessor is a pipe out of one. Neither preserves the advertised shape.
95
+ */
96
+ function pipeEffect(definition) {
97
+ if (typeOf(definition.out) === 'transform')
98
+ return 'transform';
99
+ if (typeOf(definition.in) === 'transform')
100
+ return 'preprocess';
101
+ return 'pipe';
102
+ }
103
+ /** Converts the portable subset of a Zod 4 schema into an RFC 0004 draft. */
104
+ export function convertZodV4Schema(schema, path) {
105
+ const definition = schema._zod.def;
106
+ const done = (result) => annotate(schema.description, result);
107
+ switch (typeOf(schema)) {
108
+ case 'any':
109
+ case 'unknown':
110
+ return done({ kind: 'any' });
111
+ case 'never':
112
+ case 'void':
113
+ case 'undefined':
114
+ return done({ kind: 'void' });
115
+ case 'null':
116
+ return done({ kind: 'null' });
117
+ case 'boolean':
118
+ return done({ kind: 'boolean' });
119
+ case 'string':
120
+ return done(applyLengthChecks(schema, { kind: 'string' }, { min: 'minLength', max: 'maxLength' }, 'string', path));
121
+ case 'number':
122
+ return done(convertNumber(schema, path));
123
+ case 'literal':
124
+ return done(literal(definition.values, path));
125
+ case 'enum':
126
+ return done({ kind: 'enum', values: nativeEnumValues(definition.entries) });
127
+ case 'array':
128
+ return done(applyLengthChecks(schema, { kind: 'array', items: convertZodV4Schema(definition.element, `${path}.items`) }, { min: 'minItems', max: 'maxItems' }, 'array', path));
129
+ case 'object': {
130
+ const policy = unknownProperties(definition.catchall, path);
131
+ const shape = definition.shape;
132
+ return done({
133
+ kind: 'object',
134
+ properties: Object.fromEntries(Object.entries(shape).map(([name, property]) => [
135
+ name,
136
+ convertZodV4Schema(property, `${path}.properties.${name}`),
137
+ ])),
138
+ required: Object.entries(shape)
139
+ .filter(([, property]) => !isOptionalProperty(property))
140
+ .map(([name]) => name),
141
+ unknownProperties: policy,
142
+ });
143
+ }
144
+ case 'record':
145
+ if (!isUnconstrainedRecordKey(definition.keyType)) {
146
+ throw new ProtocolSchemaAdapterError('Unsupported constrained Zod record key', path);
147
+ }
148
+ return done({ kind: 'record', values: convertZodV4Schema(definition.valueType, `${path}.values`) });
149
+ case 'union':
150
+ // A discriminated union is a union with a `discriminator`. RFC 0004 has
151
+ // no discriminator index; each variant keeps its literal property.
152
+ return done({
153
+ kind: 'union',
154
+ variants: definition.options.map((option, index) => convertZodV4Schema(option, `${path}.variants[${index}]`)),
155
+ });
156
+ case 'nullable':
157
+ return done({
158
+ kind: 'union',
159
+ variants: [convertZodV4Schema(definition.innerType, `${path}.variants[0]`), { kind: 'null' }],
160
+ });
161
+ case 'optional':
162
+ case 'readonly':
163
+ return done(convertZodV4Schema(definition.innerType, path));
164
+ case 'default':
165
+ return done(withDefault(convertZodV4Schema(definition.innerType, path), definition.defaultValue, path));
166
+ case 'pipe':
167
+ throw new ProtocolSchemaAdapterError(`Unsupported Zod effect "${pipeEffect(definition)}"`, path);
168
+ default:
169
+ throw new ProtocolSchemaAdapterError(`Unsupported Zod type "${typeOf(schema)}"`, path);
170
+ }
171
+ }
@@ -17,6 +17,13 @@ export interface ValidationResult {
17
17
  * @returns true if the value matches the field type
18
18
  */
19
19
  export declare function matchesFieldType(fieldType: FieldType, value: unknown): boolean;
20
+ /** True when a list holds both instants (`Z` or an offset) and offset-free timestamps. */
21
+ export declare function mixesInstantsAndLocalTimes(values: readonly unknown[]): boolean;
22
+ /**
23
+ * An `in` list is one predicate, so every member is read in the same zone:
24
+ * an instant and a local time cannot share one without changing a meaning.
25
+ */
26
+ export declare function mixedInstantListError(field: string): string;
20
27
  /**
21
28
  * Validates a filter value against the expected field type.
22
29
  *