@volter/world-core 3.0.38 → 3.0.40

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 (179) hide show
  1. package/app-route.cjs +26 -25
  2. package/app-route.d.cts +1 -0
  3. package/dist/app-route.cjs +26 -25
  4. package/dist/app-route.d.cts +1 -0
  5. package/dist/generated/pack-facts.json +409 -352
  6. package/dist/inject.cjs +474 -95
  7. package/dist/network-policy.cjs +110 -13
  8. package/dist/network-policy.d.cts +8 -3
  9. package/dist/src/actions.js +2 -1
  10. package/dist/src/anthropic-wire.d.ts +2 -0
  11. package/dist/src/anthropic-wire.js +5 -0
  12. package/dist/src/attestation.d.ts +6 -0
  13. package/dist/src/attestation.js +27 -0
  14. package/dist/src/bytes.d.ts +3 -0
  15. package/dist/src/bytes.js +17 -0
  16. package/dist/src/changeset.js +2 -1
  17. package/dist/src/clickhouse/sql.js +73 -14
  18. package/dist/src/derived-core.d.ts +210 -32
  19. package/dist/src/derived-core.js +510 -244
  20. package/dist/src/derived-real.d.ts +1 -1
  21. package/dist/src/derived-real.js +704 -93
  22. package/dist/src/derived.d.ts +18 -2
  23. package/dist/src/derived.js +21 -2
  24. package/dist/src/events.d.ts +28 -45
  25. package/dist/src/events.js +40 -58
  26. package/dist/src/exact-json.d.ts +10 -0
  27. package/dist/src/exact-json.js +98 -0
  28. package/dist/src/executor.d.ts +17 -0
  29. package/dist/src/executor.js +91 -30
  30. package/dist/src/file-response.js +1 -1
  31. package/dist/src/form.d.ts +8 -0
  32. package/dist/src/form.js +76 -0
  33. package/dist/src/git/format.d.ts +16 -0
  34. package/dist/src/git/format.js +110 -0
  35. package/dist/src/git/index.d.ts +1 -0
  36. package/dist/src/git/index.js +1 -0
  37. package/dist/src/git/lfs.d.ts +1 -1
  38. package/dist/src/git/lfs.js +4 -3
  39. package/dist/src/graphql-wire.d.ts +6 -0
  40. package/dist/src/graphql-wire.js +6 -3
  41. package/dist/src/grpc-wire.js +2 -1
  42. package/dist/src/head.d.ts +1 -0
  43. package/dist/src/head.js +15 -4
  44. package/dist/src/held-reads.d.ts +6 -0
  45. package/dist/src/held-reads.js +60 -0
  46. package/dist/src/history.js +5 -4
  47. package/dist/src/host-port.d.ts +5 -0
  48. package/dist/src/host-port.js +14 -0
  49. package/dist/src/index.d.ts +11 -3
  50. package/dist/src/index.js +9 -4
  51. package/dist/src/log.js +12 -7
  52. package/dist/src/machines.d.ts +81 -5
  53. package/dist/src/machines.js +125 -9
  54. package/dist/src/managed-database.d.ts +15 -11
  55. package/dist/src/managed-database.js +38 -37
  56. package/dist/src/multipart.d.ts +21 -3
  57. package/dist/src/multipart.js +112 -38
  58. package/dist/src/observe.d.ts +3 -0
  59. package/dist/src/observe.js +6 -2
  60. package/dist/src/openai-wire.d.ts +16 -0
  61. package/dist/src/openai-wire.js +43 -12
  62. package/dist/src/pack-fetch.d.ts +4 -6
  63. package/dist/src/pack-fetch.js +189 -57
  64. package/dist/src/packRegistry.d.ts +7 -1
  65. package/dist/src/packRegistry.js +20 -3
  66. package/dist/src/private-transport.d.ts +1 -0
  67. package/dist/src/private-transport.js +11 -0
  68. package/dist/src/protobuf.js +10 -4
  69. package/dist/src/rateBudget.d.ts +8 -1
  70. package/dist/src/rateBudget.js +1 -0
  71. package/dist/src/redis/engine.d.ts +14 -5
  72. package/dist/src/redis/engine.js +271 -72
  73. package/dist/src/redis/frames.d.ts +17 -0
  74. package/dist/src/redis/frames.js +123 -0
  75. package/dist/src/redis/index.d.ts +6 -0
  76. package/dist/src/redis/index.js +6 -0
  77. package/dist/src/redis/json.d.ts +2 -0
  78. package/dist/src/redis/json.js +14 -0
  79. package/dist/src/redis/lua-libs.d.ts +4 -0
  80. package/dist/src/redis/lua-libs.js +173 -0
  81. package/dist/src/redis/lua.d.ts +1 -0
  82. package/dist/src/redis/lua.js +36 -0
  83. package/dist/src/redis/member-storage.d.ts +5 -0
  84. package/dist/src/redis/member-storage.js +75 -0
  85. package/dist/src/redis/resp2.d.ts +4 -0
  86. package/dist/src/redis/resp2.js +29 -0
  87. package/dist/src/redis/stream.d.ts +14 -0
  88. package/dist/src/redis/stream.js +335 -0
  89. package/dist/src/remote-execute.d.ts +2 -2
  90. package/dist/src/request-body.d.ts +11 -0
  91. package/dist/src/request-body.js +11 -0
  92. package/dist/src/runtime.d.ts +6 -5
  93. package/dist/src/runtime.js +5 -5
  94. package/dist/src/s3/wire.d.ts +4 -4
  95. package/dist/src/s3/wire.js +5 -3
  96. package/dist/src/scenario.d.ts +22 -0
  97. package/dist/src/scenario.js +35 -31
  98. package/dist/src/serve-http.d.ts +2 -1
  99. package/dist/src/serve-http.js +10 -1
  100. package/dist/src/serve.js +7 -4
  101. package/dist/src/signing.d.ts +23 -5
  102. package/dist/src/signing.js +74 -11
  103. package/dist/src/sigv4.d.ts +3 -1
  104. package/dist/src/sigv4.js +4 -2
  105. package/dist/src/smtp.js +2 -1
  106. package/dist/src/sockets.js +8 -3
  107. package/dist/src/state-system.d.ts +3 -0
  108. package/dist/src/storage.js +5 -2
  109. package/dist/src/twin-fetch.d.ts +3 -1
  110. package/dist/src/twin-fetch.js +3 -2
  111. package/dist/src/vendor-call.d.ts +10 -3
  112. package/dist/src/vendor-call.js +58 -14
  113. package/dist/test-fixtures/attestation.SOURCE.md +1 -0
  114. package/dist/test-fixtures/attestation.json +50 -0
  115. package/generated/pack-facts.json +409 -352
  116. package/inject.cjs +474 -95
  117. package/network-policy.cjs +110 -13
  118. package/network-policy.d.cts +8 -3
  119. package/package.json +11 -4
  120. package/src/actions.ts +2 -1
  121. package/src/anthropic-wire.ts +6 -0
  122. package/src/attestation.ts +23 -0
  123. package/src/bytes.ts +21 -0
  124. package/src/changeset.ts +2 -1
  125. package/src/clickhouse/sql.ts +50 -16
  126. package/src/derived-core.ts +573 -249
  127. package/src/derived-real.ts +552 -42
  128. package/src/derived.ts +23 -4
  129. package/src/events.ts +54 -85
  130. package/src/exact-json.ts +71 -0
  131. package/src/executor.ts +86 -27
  132. package/src/file-response.ts +1 -1
  133. package/src/form.ts +66 -0
  134. package/src/git/format.ts +89 -0
  135. package/src/git/index.ts +1 -0
  136. package/src/git/lfs.ts +5 -4
  137. package/src/graphql-wire.ts +8 -3
  138. package/src/grpc-wire.ts +2 -1
  139. package/src/head.ts +15 -5
  140. package/src/held-reads.ts +54 -0
  141. package/src/history.ts +5 -4
  142. package/src/host-port.ts +9 -0
  143. package/src/index.ts +10 -4
  144. package/src/log.ts +11 -7
  145. package/src/machines.ts +135 -13
  146. package/src/managed-database.ts +48 -40
  147. package/src/multipart.ts +110 -35
  148. package/src/observe.ts +6 -3
  149. package/src/openai-wire.ts +45 -14
  150. package/src/pack-fetch.ts +173 -50
  151. package/src/packRegistry.ts +27 -4
  152. package/src/private-transport.ts +8 -0
  153. package/src/protobuf.ts +8 -4
  154. package/src/rateBudget.ts +6 -1
  155. package/src/redis/engine.ts +168 -63
  156. package/src/redis/frames.ts +53 -0
  157. package/src/redis/index.ts +6 -0
  158. package/src/redis/json.ts +13 -0
  159. package/src/redis/lua-libs.ts +62 -0
  160. package/src/redis/lua.ts +20 -0
  161. package/src/redis/member-storage.ts +58 -0
  162. package/src/redis/resp2.ts +25 -0
  163. package/src/redis/stream.ts +102 -0
  164. package/src/remote-execute.ts +2 -2
  165. package/src/request-body.ts +12 -0
  166. package/src/runtime.ts +6 -5
  167. package/src/s3/wire.ts +8 -5
  168. package/src/scenario.ts +34 -13
  169. package/src/serve-http.ts +11 -2
  170. package/src/serve.ts +4 -3
  171. package/src/signing.ts +71 -17
  172. package/src/sigv4.ts +5 -3
  173. package/src/smtp.ts +3 -2
  174. package/src/sockets.ts +8 -3
  175. package/src/state-system.ts +3 -0
  176. package/src/storage.ts +5 -2
  177. package/src/twin-fetch.ts +4 -3
  178. package/src/vendor-call.ts +59 -12
  179. package/test-fixtures/attestation.json +50 -0
@@ -1,6 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  const WORLD_NETWORK_POLICY_ENV = 'VOLTER_WORLD_NETWORK_POLICY';
4
+ /** Reads only: an `egress` grant admits these; any other method needs `writes`. */
5
+ const READ_METHODS = Object.freeze(['GET', 'HEAD', 'OPTIONS']);
6
+ /** The public build-artifact origins a build-phase World reads without listing them (architecture, "Reads are granted generously"). */
7
+ const PUBLIC_BUILD_READS = Object.freeze([
8
+ 'https://binaries.prisma.sh', 'https://fonts.googleapis.com', 'https://fonts.gstatic.com',
9
+ 'https://index.crates.io', 'https://registry.npmjs.org', 'https://static.crates.io',
10
+ ]);
4
11
 
5
12
  function object(value, keys, label) {
6
13
  if (!value || typeof value !== 'object' || Array.isArray(value)
@@ -10,17 +17,37 @@ function object(value, keys, label) {
10
17
  return value;
11
18
  }
12
19
 
20
+ // Decode once, then resolve dot segments. Separators must have been literal on the wire.
21
+ function normalizedPath(path) {
22
+ if (/\\|%2f|%5c/i.test(path)) return null;
23
+ let decoded;
24
+ try { decoded = decodeURIComponent(path); } catch { return null; }
25
+ if (/[\\\x00-\x1f\x7f]/.test(decoded) || /%[0-9a-f]{2}/i.test(decoded)) return null;
26
+ const parts = [];
27
+ for (const segment of decoded.split('/')) {
28
+ if (segment === '.') continue;
29
+ if (segment === '..') { if (parts.length > 1) parts.pop(); }
30
+ else parts.push(segment);
31
+ }
32
+ let result = parts.join('/');
33
+ if (!result.startsWith('/')) result = '/' + result;
34
+ if ((decoded.endsWith('/.') || decoded.endsWith('/..')) && !result.endsWith('/')) result += '/';
35
+ return result;
36
+ }
37
+
13
38
  function origins(value) {
14
- if (!Array.isArray(value) || value.length > 256) throw new Error('World network egress must be an array of at most 256 exact HTTPS origins');
39
+ if (!Array.isArray(value) || value.length > 256) throw new Error('World network egress must be an array of at most 256 exact HTTPS origins, exact paths or trailing-slash subtrees');
15
40
  const normalized = value.map(origin => {
16
41
  if (typeof origin !== 'string' || origin.length > 2048) throw new Error('World network egress origins must be strings');
17
42
  let url;
18
43
  try { url = new URL(origin); } catch { throw new Error(`Invalid World network origin: ${origin}`); }
19
- if (url.protocol !== 'https:' || url.origin !== origin || url.username || url.password
44
+ const canonical = url.pathname === '/' ? url.origin : url.origin + url.pathname;
45
+ if (url.protocol !== 'https:' || url.port === '0' || canonical !== origin || url.username || url.password || url.search || url.hash
46
+ || /[\\*]/.test(origin) || normalizedPath(url.pathname) === null
20
47
  || !/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/.test(url.hostname)
21
48
  || url.hostname.split('.').some(label => !label || label.length > 63 || label.startsWith('-') || label.endsWith('-'))
22
49
  || /^[\d.]+$/.test(url.hostname) || url.hostname === 'localhost' || url.hostname.endsWith('.localhost')) {
23
- throw new Error(`World network requires an exact canonical HTTPS origin: ${origin}`);
50
+ throw new Error(`World network requires an exact canonical HTTPS origin, path or subtree: ${origin}`);
24
51
  }
25
52
  return origin;
26
53
  });
@@ -28,19 +55,32 @@ function origins(value) {
28
55
  }
29
56
 
30
57
  function normalizeWorldNetwork(value) {
31
- const network = object(value, ['egress'], 'World network');
32
- return Object.freeze({ egress: origins(network.egress) });
58
+ const network = object(value, ['egress', 'writes', 'phase', 'passthrough', 'publicReads'], 'World network');
59
+ const egress = origins(network.egress);
60
+ const writes = origins(network.writes ?? []);
61
+ const phase = network.phase ?? 'runtime';
62
+ if (phase !== 'build' && phase !== 'runtime') throw new Error('World network phase must be build or runtime');
63
+ if (network.publicReads !== undefined && typeof network.publicReads !== 'boolean') throw new Error('World network publicReads must be a boolean');
64
+ const publicReads = phase === 'build' && network.publicReads !== false;
65
+ const passthrough = origins(network.passthrough ?? []);
66
+ if (passthrough.length && phase !== 'build') throw new Error('World TLS passthrough requires phase: build');
67
+ for (const origin of passthrough) {
68
+ if (origin !== new URL(origin).origin || !egress.includes(origin)) throw new Error(`TLS passthrough requires an exact-origin egress grant: ${origin}`);
69
+ // an opaque tunnel cannot show its method, so it cannot carry a write
70
+ if (writes.some(grant => new URL(grant).origin === origin)) throw new Error(`TLS passthrough cannot carry a write grant: ${origin}`);
71
+ }
72
+ return Object.freeze({ egress, writes, phase, passthrough, publicReads });
33
73
  }
34
74
 
35
75
  function createWorldNetworkPolicy(world, network) {
36
76
  if (typeof world !== 'string' || !world || world.length > 256) throw new Error('World network policy requires its World identity');
37
- return Object.freeze({ version: 1, world, egress: normalizeWorldNetwork(network).egress });
77
+ return Object.freeze({ version: 1, world, ...normalizeWorldNetwork(network) });
38
78
  }
39
79
 
40
80
  function parseWorldNetworkPolicy(value) {
41
- const policy = object(value, ['version', 'world', 'egress'], 'World network policy');
81
+ const policy = object(value, ['version', 'world', 'egress', 'writes', 'phase', 'passthrough', 'publicReads'], 'World network policy');
42
82
  if (policy.version !== 1) throw new Error('Unsupported World network policy version');
43
- return createWorldNetworkPolicy(policy.world, { egress: policy.egress });
83
+ return createWorldNetworkPolicy(policy.world, { egress: policy.egress, writes: policy.writes, phase: policy.phase, passthrough: policy.passthrough, publicReads: policy.publicReads });
44
84
  }
45
85
 
46
86
  function worldNetworkPolicyFromEnv(env) {
@@ -52,10 +92,67 @@ function worldNetworkPolicyFromEnv(env) {
52
92
  return policy;
53
93
  }
54
94
 
55
- function allowsWorldNetworkEgress(policy, value) {
95
+ function destination(value, originOnly = false) {
96
+ if (/\\/.test(String(value).split(/[?#]/, 1)[0])) return null;
97
+ let url;
98
+ try { url = new URL(String(value)); } catch { return null; }
99
+ const path = normalizedPath(url.pathname);
100
+ return url.protocol === 'https:' && url.port !== '0' && !url.username && !url.password && (path !== null || originOnly) ? { url, path } : null;
101
+ }
102
+
103
+ function containsGrant(broad, narrow) {
104
+ const a = destination(broad); const b = destination(narrow);
105
+ if (!a || !b || a.url.origin !== b.url.origin) return false;
106
+ if (broad === a.url.origin) return true;
107
+ if (narrow === b.url.origin) return false;
108
+ return a.path.endsWith('/') ? b.path.startsWith(a.path) : !b.path.endsWith('/') && b.path === a.path;
109
+ }
110
+
111
+ function granted(grants, to) {
112
+ return grants.some(grant => {
113
+ const allowed = destination(grant);
114
+ return allowed && to.url.origin === allowed.url.origin && (grant === allowed.url.origin
115
+ || (to.path !== null && (allowed.path.endsWith('/') ? to.path.startsWith(allowed.path) : to.path === allowed.path)));
116
+ });
117
+ }
118
+
119
+ /** Whether `method` to `value` is allowed: reads by `egress`, `writes` or the build phase's public reads; writes only by `writes`. */
120
+ function allowsWorldNetworkEgress(policy, value, method = 'GET') {
121
+ const to = destination(value, true);
122
+ if (!to) return false;
123
+ if (granted(policy.writes ?? [], to)) return true;
124
+ if (!READ_METHODS.includes(String(method).toUpperCase())) return false;
125
+ return granted(policy.egress, to) || (policy.phase === 'build' && policy.publicReads === true && PUBLIC_BUILD_READS.includes(to.url.origin));
126
+ }
127
+
128
+ function worldNetworkUsesPassthrough(policy, value) {
129
+ const to = destination(value, true);
130
+ if (!to || policy.phase !== 'build') return false;
131
+ // public build artifacts tunnel unopened: native clients (Turbopack's next/font on macOS) trust only the OS store
132
+ if (policy.publicReads === true && PUBLIC_BUILD_READS.includes(to.url.origin) && !granted(policy.writes ?? [], to)) return true;
133
+ return policy.passthrough?.includes(to.url.origin) === true && policy.egress.includes(to.url.origin);
134
+ }
135
+
136
+ /** This authority must be mediated per request; CONNECT is only a path to the inspector. */
137
+ function worldNetworkNeedsPathInspection(policy, value) {
56
138
  let url;
57
139
  try { url = new URL(String(value)); } catch { return false; }
58
- return url.protocol === 'https:' && !url.username && !url.password && policy.egress.includes(url.origin);
140
+ return url.protocol === 'https:' && !worldNetworkUsesPassthrough(policy, url)
141
+ && ([...policy.egress, ...(policy.writes ?? [])].some(grant => new URL(grant).origin === url.origin)
142
+ || (policy.phase === 'build' && policy.publicReads === true && PUBLIC_BUILD_READS.includes(url.origin)));
143
+ }
144
+
145
+ /** Parent ceilings keep the narrower path and cannot create opaque transport permission. */
146
+ function intersectWorldNetwork(own, ceiling) {
147
+ const left = normalizeWorldNetwork(own); const right = normalizeWorldNetwork(ceiling);
148
+ const meet = (a, b) => [...new Set(a.flatMap(x => b.flatMap(y => containsGrant(y, x) ? [x] : containsGrant(x, y) ? [y] : [])))].sort();
149
+ const egress = meet(left.egress, right.egress);
150
+ const writes = meet(left.writes, right.writes);
151
+ const passthrough = left.phase === 'build' && right.phase === 'build'
152
+ ? left.passthrough.filter(origin => right.passthrough.includes(origin) && egress.includes(origin)) : [];
153
+ // public reads are permission too: only a build ceiling that reads them passes them on
154
+ const publicReads = left.publicReads && right.publicReads;
155
+ return normalizeWorldNetwork({ egress, writes, phase: left.phase, passthrough, publicReads });
59
156
  }
60
157
 
61
158
  /** Loopback and private addresses stay inside the machine a World runs on: never refused. */
@@ -80,15 +177,15 @@ function isWorldInternalHost(hostname) {
80
177
  * injector does not attach to. Internal addresses pass; strict egress refuses everything else; a
81
178
  * published network policy refuses what it does not list. An unreadable policy refuses.
82
179
  */
83
- function worldEgressRefusal(value, env = process.env) {
180
+ function worldEgressRefusal(value, env = process.env, method = 'GET') {
84
181
  let url;
85
182
  try { url = new URL(String(value)); } catch { return `unparseable destination ${String(value)}`; }
86
183
  if (isWorldInternalHost(url.hostname)) return null;
87
184
  if (env.VOLTER_TWIN_STRICT_EGRESS === '1' || env.VOLTER_TWIN_STRICT_EGRESS === 'true') return `sealed World refuses untwinned host ${url.hostname}`;
88
185
  let policy;
89
186
  try { policy = worldNetworkPolicyFromEnv(env); } catch (error) { return `World network policy unreadable (${error.message}); refusing ${url.hostname}`; }
90
- if (policy !== undefined && !allowsWorldNetworkEgress(policy, url)) return `World network policy does not allow ${url.origin}`;
187
+ if (policy !== undefined && !allowsWorldNetworkEgress(policy, url, method)) return `World network policy does not allow ${String(method).toUpperCase()} ${url.origin}`;
91
188
  return null;
92
189
  }
93
190
 
94
- module.exports = { isWorldInternalHost, worldEgressRefusal, WORLD_NETWORK_POLICY_ENV, normalizeWorldNetwork, createWorldNetworkPolicy, parseWorldNetworkPolicy, worldNetworkPolicyFromEnv, allowsWorldNetworkEgress };
191
+ module.exports = { PUBLIC_BUILD_READS, READ_METHODS, intersectWorldNetwork, worldNetworkNeedsPathInspection, worldNetworkUsesPassthrough, isWorldInternalHost, worldEgressRefusal, WORLD_NETWORK_POLICY_ENV, normalizeWorldNetwork, createWorldNetworkPolicy, parseWorldNetworkPolicy, worldNetworkPolicyFromEnv, allowsWorldNetworkEgress };
@@ -1,10 +1,15 @@
1
- export interface WorldNetwork { readonly egress: readonly string[] }
1
+ export interface WorldNetwork { readonly egress: readonly string[]; readonly writes?: readonly string[]; readonly phase?: 'build' | 'runtime'; readonly passthrough?: readonly string[]; readonly publicReads?: boolean }
2
2
  export interface WorldNetworkPolicy extends WorldNetwork { readonly version: 1; readonly world: string }
3
3
  export const WORLD_NETWORK_POLICY_ENV: 'VOLTER_WORLD_NETWORK_POLICY';
4
4
  export function normalizeWorldNetwork(value: unknown): WorldNetwork;
5
5
  export function createWorldNetworkPolicy(world: string, network: WorldNetwork): WorldNetworkPolicy;
6
6
  export function parseWorldNetworkPolicy(value: unknown): WorldNetworkPolicy;
7
7
  export function worldNetworkPolicyFromEnv(env: Readonly<Record<string, string | undefined>>): WorldNetworkPolicy | undefined;
8
- export function allowsWorldNetworkEgress(policy: WorldNetworkPolicy, value: string | URL): boolean;
8
+ export const READ_METHODS: readonly string[];
9
+ export const PUBLIC_BUILD_READS: readonly string[];
10
+ export function allowsWorldNetworkEgress(policy: WorldNetworkPolicy, value: string | URL, method?: string): boolean;
9
11
  export function isWorldInternalHost(hostname: string): boolean;
10
- export function worldEgressRefusal(value: string | URL, env?: Readonly<Record<string, string | undefined>>): string | null;
12
+ export function worldEgressRefusal(value: string | URL, env?: Readonly<Record<string, string | undefined>>, method?: string): string | null;
13
+ export function worldNetworkNeedsPathInspection(policy: WorldNetworkPolicy, value: string | URL): boolean;
14
+ export function worldNetworkUsesPassthrough(policy: WorldNetworkPolicy, value: string | URL): boolean;
15
+ export function intersectWorldNetwork(own: WorldNetwork, ceiling: WorldNetwork): WorldNetwork;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-core",
3
- "version": "3.0.38",
3
+ "version": "3.0.40",
4
4
  "description": "The kernel of Volter World: one log per twin, branches as pointers, checkpoints, the fold that keeps a twin current, the head that performs a write against the vendor, references, and the git plane. A twin package builds on it; the runtime serves it.",
5
5
  "keywords": [
6
6
  "twin",
@@ -79,6 +79,10 @@
79
79
  "types": "./dist/src/schemas.d.ts",
80
80
  "default": "./dist/src/schemas.js"
81
81
  },
82
+ "./exact-json": {
83
+ "types": "./dist/src/exact-json.d.ts",
84
+ "default": "./dist/src/exact-json.js"
85
+ },
82
86
  "./inject": "./inject.cjs",
83
87
  "./attach": "./attach.cjs",
84
88
  "./stream-bridge": "./stream-bridge.cjs",
@@ -124,14 +128,17 @@
124
128
  "postpack": "node ../../scripts/publish/prepare-publish.mjs postpack"
125
129
  },
126
130
  "dependencies": {
127
- "zod": "^4.4.2",
128
- "ws": "^8.18.0"
131
+ "ws": "^8.18.0",
132
+ "zod": "^4.4.2"
129
133
  },
130
134
  "devDependencies": {
131
135
  "@types/bun": "^1.2.20",
132
136
  "@types/node": "^24.0.0",
133
137
  "typescript": "^5.9.0",
134
- "@types/ws": "^8.5.0"
138
+ "@types/ws": "^8.5.0",
139
+ "@sigstore/bundle": "4.0.0",
140
+ "@sigstore/protobuf-specs": "0.5.2",
141
+ "@sigstore/verify": "3.1.1"
135
142
  },
136
143
  "engines": {
137
144
  "node": ">=22.3"
package/src/actions.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { parseExactJson } from './exact-json.ts';
1
2
  // Transaction/action log (scorecard R18) — the semantic correction that separates
2
3
  // OBSERVED facts from LOCAL transaction commits.
3
4
  //
@@ -404,7 +405,7 @@ export function listActions(service: string, root?: string): TwinAction[] {
404
405
  return getActiveWorldStore()
405
406
  .readLines(path)
406
407
  .filter((line) => line.trim())
407
- .map((line) => JSON.parse(line) as TwinAction);
408
+ .map((line) => parseExactJson(line) as TwinAction);
408
409
  }
409
410
 
410
411
  const META = new Set(['id', 'type', 'updatedAt']);
@@ -135,3 +135,9 @@ export function messageSSE(m: Row, extra: Row = {}, options: { ping?: boolean; c
135
135
  send('message_stop', { ...extra });
136
136
  return events.join('');
137
137
  }
138
+
139
+ /** An admitted Messages generation failure is an error SSE event, or the native JSON envelope. */
140
+ export function messageFailure(error: Row, stream: boolean, requestId?: string): Row | string {
141
+ const data = { type: 'error', error, ...(stream ? {} : { request_id: requestId ?? null }) };
142
+ return stream ? `event: error\ndata: ${JSON.stringify(data)}\n\n` : data;
143
+ }
@@ -0,0 +1,23 @@
1
+ // Offline bundle verification against explicit World trust anchors. No network or persistent TUF state.
2
+ import { certificateOf, digest, publicKeyOf } from './signing.ts';
3
+
4
+ /** Verify simulated keyless attestations using the World's certificate and transparency roots.
5
+ * World certificate authorities do not run CT; signing time must still be authenticated by a log witness. */
6
+ export async function verifyAttestationBundle(bundle: unknown, trust: { certificates: readonly string[]; logKeys: readonly string[] }): Promise<void> {
7
+ if (!trust.certificates.length || !trust.logKeys.length) throw new Error('Attestation trust anchors are required');
8
+ // B1: protocol libraries are supplied by the pack and load only when the wire is used.
9
+ const [{ bundleFromJSON }, { TrustedRoot }, { toSignedEntity, toTrustMaterial, Verifier }] = await Promise.all([import('@sigstore/bundle'), import('@sigstore/protobuf-specs'), import('@sigstore/verify')]);
10
+ const root = TrustedRoot.fromJSON({ mediaType: 'application/vnd.dev.sigstore.trustedroot+json;version=0.1',
11
+ certificateAuthorities: trust.certificates.map((pem) => {
12
+ const cert = certificateOf(pem);
13
+ if (!cert) throw new Error('Invalid attestation certificate root');
14
+ return { certChain: { certificates: [{ rawBytes: Buffer.from(cert.der).toString('base64') }] }, validFor: { start: cert.notBefore, end: cert.notAfter } };
15
+ }),
16
+ tlogs: trust.logKeys.map((pem) => { const key = publicKeyOf(pem); return {
17
+ hashAlgorithm: 'SHA2_256', logId: { keyId: Buffer.from(digest('sha256', key.der), 'hex').toString('base64') },
18
+ publicKey: { rawBytes: Buffer.from(key.der).toString('base64'), keyDetails: 'PUBLIC_KEY_DETAILS_UNSPECIFIED' },
19
+ }; }), ctlogs: [], timestampAuthorities: [],
20
+ });
21
+ const entity = toSignedEntity(bundleFromJSON(bundle));
22
+ new Verifier(toTrustMaterial(root), { tlogThreshold: 1, ctlogThreshold: 0 }).verify(entity);
23
+ }
package/src/bytes.ts CHANGED
@@ -5,9 +5,16 @@
5
5
  // reached the way the kernel reaches every builtin: `process.getBuiltinModule`, at call time, never at import (the seam
6
6
  // serve-http.ts's `nodeBuiltin` uses).
7
7
 
8
+ import { DEFAULT_MAX_DECODED_REQUEST_BODY_SIZE, RequestBodyTooLargeError } from './request-body.ts';
9
+
10
+ type Decode = (b: Uint8Array, options: { maxOutputLength: number }, callback: (error: (Error & { code?: string }) | null, bytes: Uint8Array) => void) => void;
8
11
  type Zlib = {
9
12
  gzipSync(b: Uint8Array): Uint8Array;
10
13
  gunzipSync(b: Uint8Array): Uint8Array;
14
+ gunzip: Decode;
15
+ inflate: Decode;
16
+ brotliDecompress: Decode;
17
+ zstdDecompress: Decode;
11
18
  deflateSync(b: Uint8Array): Uint8Array;
12
19
  };
13
20
 
@@ -28,6 +35,20 @@ export async function gunzip(bytes: Uint8Array): Promise<Uint8Array> {
28
35
  return new Uint8Array(zlib('gunzip').gunzipSync(bytes));
29
36
  }
30
37
 
38
+ /** Compressed HTTP REQUESTS only: async decoding stops at maxOutputLength during inflation.
39
+ * Return the decoder's own buffer; the request consumes it as a stream without copying it again. */
40
+ export async function decompress(bytes: Uint8Array, coding: 'gzip' | 'deflate' | 'br' | 'zstd', limit = DEFAULT_MAX_DECODED_REQUEST_BODY_SIZE): Promise<Uint8Array> {
41
+ const builtin = zlib('decompress');
42
+ const decode = { gzip: builtin.gunzip, deflate: builtin.inflate, br: builtin.brotliDecompress, zstd: builtin.zstdDecompress }[coding];
43
+ return new Promise((resolve, reject) => {
44
+ decode(bytes, { maxOutputLength: limit }, (error, body) => {
45
+ if (error) reject(error.code === 'ERR_BUFFER_TOO_LARGE' ? new RequestBodyTooLargeError() : error);
46
+ else if (body.byteLength > limit) reject(new RequestBodyTooLargeError());
47
+ else resolve(body);
48
+ });
49
+ });
50
+ }
51
+
31
52
  /** Bytes as a gzip member. */
32
53
  export async function gzip(bytes: Uint8Array): Promise<Uint8Array> {
33
54
  if (typeof CompressionStream === 'function') return streamed(bytes, new CompressionStream('gzip'));
package/src/changeset.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { parseExactJson } from './exact-json.ts';
1
2
  import { resolveReferences } from './references.ts';
2
3
  import { withHistoryLocks } from './ancestry.ts';
3
4
  import { captureParentHistory, historyChanges, historyEntries, historyPrefix, readHistoryView, type HistoryReference } from './history.ts';
@@ -451,7 +452,7 @@ export function parseVerifierExpression(expression: string, id: string): Changes
451
452
  const raw = parts.slice(4).join(' ');
452
453
  let value: unknown;
453
454
  if (raw) {
454
- try { value = JSON.parse(raw); } catch { value = raw; }
455
+ try { value = parseExactJson(raw); } catch { value = raw; }
455
456
  }
456
457
  const assert: TwinActionPrecondition = {
457
458
  subject: { type: subject.slice(0, colon), id: subject.slice(colon + 1) },
@@ -1,3 +1,4 @@
1
+ import { parseExactJson } from '../exact-json.ts';
1
2
  // The kernel's ClickHouse® SQL engine — deterministic, over rows a pack supplies (Tinybird's Data Sources and pipe nodes,
2
3
  // PostHog's events and persons).
3
4
  //
@@ -1174,7 +1175,7 @@ function fnType(e: Extract<Expr, { k: 'fn' }>, scope: Scope, seen: Set<string>,
1174
1175
  case 'tolowcardinality': { const t = a0 ? T(a0) : 'String'; return /^LowCardinality/.test(t) ? t : `LowCardinality(${t})`; }
1175
1176
  case 'tostring': case 'concat': case 'replace': case 'replaceall': case 'replaceone': case 'replaceregexpall': case 'replaceregexpone': case 'lower': case 'upper': case 'lowerutf8': case 'upperutf8': case 'substring': case 'substr':
1176
1177
  case 'trim': case 'trimboth': case 'trimleft': case 'trimright': case 'encodeurlformcomponent': case 'decodeurlformcomponent': case 'encodeurlcomponent': case 'decodeurlcomponent':
1177
- case 'extracturlparameter': case 'jsonextractstring': case 'jsonextractraw': case 'formatdatetime': case 'domain': case 'domainwithoutwww': case 'path': case 'cutquerystring': case 'leftpad': case 'rightpad': case 'reverse': case 'base64encode': case 'base64decode': case 'hex':
1178
+ case 'extract': case 'extracturlparameter': case 'jsonextractstring': case 'jsonextractraw': case 'formatdatetime': case 'domain': case 'domainwithoutwww': case 'path': case 'cutquerystring': case 'leftpad': case 'rightpad': case 'reverse': case 'base64encode': case 'base64decode': case 'hex':
1178
1179
  return 'String';
1179
1180
  case 'splitbystring': case 'splitbychar': case 'extracturlparameters': case 'extracturlparameternames': return 'Array(String)';
1180
1181
  case 'mapfilter': return e.args[1] ? T(e.args[1]) : 'Map(String, String)';
@@ -1216,15 +1217,17 @@ function fnType(e: Extract<Expr, { k: 'fn' }>, scope: Scope, seen: Set<string>,
1216
1217
  case 'toyyyymm': return 'UInt32';
1217
1218
  case 'toyear': return 'UInt16';
1218
1219
  case 'tomonth': case 'todayofmonth': case 'tohour': case 'tominute': case 'tosecond': case 'todayofweek': return 'UInt8';
1219
- case 'todatetime64': case 'todatetime64orzero': {
1220
+ case 'todatetime64': case 'todatetime64orzero': case 'parsedatetime64besteffort': case 'parsedatetime64besteffortornull': case 'parsedatetime64besteffortorzero': {
1220
1221
  const scale = e.args[1]?.k === 'lit' ? Number(e.args[1].v) : 3;
1221
1222
  const tz = e.args[2]?.k === 'lit' ? String(e.args[2].v) : undefined;
1222
- return tz ? `DateTime64(${scale}, '${tz}')` : `DateTime64(${scale})`;
1223
+ const type = tz ? `DateTime64(${scale}, '${tz}')` : `DateTime64(${scale})`;
1224
+ return name.endsWith('ornull') ? `Nullable(${type})` : type;
1223
1225
  }
1224
1226
  case 'todatetime': case 'now': case 'tostartofday': case 'tostartofhour': case 'tostartofminute': case 'tostartoffiveminutes': case 'tostartoffifteenminutes': case 'tostartofsecond': case 'parsedatetimebesteffort': case 'parsedatetimebesteffortornull': {
1225
1227
  const tzArg = name === 'now' ? a0 : e.args[1];
1226
1228
  const tz = tzArg?.k === 'lit' ? String(tzArg.v) : undefined;
1227
- return tz ? `DateTime('${tz}')` : 'DateTime';
1229
+ const type = tz ? `DateTime('${tz}')` : 'DateTime';
1230
+ return name.endsWith('ornull') ? `Nullable(${type})` : type;
1228
1231
  }
1229
1232
  case 'now64': return 'DateTime64(3)';
1230
1233
  case 'todate': case 'today': case 'tostartofmonth': case 'tostartofweek': case 'tostartofyear': case 'tostartofquarter': case 'tomonday': case 'yesterday': return 'Date';
@@ -1702,7 +1705,7 @@ function relativeNumber(v: DT | DDate, unit: string, tz: string | undefined): nu
1702
1705
  function jsonPathValue(json: unknown, keys: unknown[]): unknown {
1703
1706
  let cur: unknown;
1704
1707
  if (typeof json === 'string') {
1705
- try { cur = JSON.parse(json); } catch { return undefined; }
1708
+ try { cur = parseExactJson(json, (digits) => BigInt(digits)); } catch { return undefined; }
1706
1709
  } else cur = json;
1707
1710
  for (const k of keys) {
1708
1711
  if (cur === null || typeof cur !== 'object') return undefined;
@@ -1854,6 +1857,17 @@ function evalFunction(e: Extract<Expr, { k: 'fn' }>, env: Env): unknown {
1854
1857
  const pad = fill.repeat(Math.ceil((n - s.length) / Math.max(1, fill.length))).slice(0, n - s.length);
1855
1858
  return name === 'leftpad' ? pad + s : s + pad;
1856
1859
  }
1860
+ // ClickHouse string-search-functions#extract: first capturing group, otherwise the whole first match.
1861
+ case 'extract': {
1862
+ argc(2);
1863
+ const haystack = A(0), pattern = A(1);
1864
+ if (haystack === null || pattern === null) return null;
1865
+ if (typeof haystack !== 'string' || typeof pattern !== 'string') return fail('Illegal type of argument of function extract: expected String');
1866
+ let match: RegExpExecArray | null;
1867
+ try { match = new RegExp(valueToText(pattern)).exec(valueToText(haystack)); }
1868
+ catch { return fail('Cannot compile re2: ' + valueToText(pattern)); }
1869
+ return match ? (match.length > 1 ? match[1] ?? '' : match[0]) : '';
1870
+ }
1857
1871
  case 'match': { argc(2); const s = A(0); const p = A(1); if (s === null || p === null) return null; return new RegExp(valueToText(p)).test(valueToText(s)) ? 1 : 0; }
1858
1872
  case 'replaceregexpall': case 'replaceregexpone': {
1859
1873
  argc(3); const s = A(0); const p = A(1); const r = A(2); if (s === null || p === null || r === null) return null;
@@ -1911,11 +1925,12 @@ function evalFunction(e: Extract<Expr, { k: 'fn' }>, env: Env): unknown {
1911
1925
  if (e.args.length < 1) fail(`Number of arguments for function ${e.name} doesn't match`);
1912
1926
  const j = A(0); if (j === null) return null;
1913
1927
  const keys = e.args.slice(1).map((_, i) => A(i + 1));
1914
- const v = jsonPathValue(typeof j === 'string' ? j : JSON.stringify(j), keys);
1928
+ const v = jsonPathValue(typeof j === 'string' ? j : stringifyJson(j), keys);
1915
1929
  if (name === 'jsonhas') return v === undefined ? 0 : 1;
1916
1930
  if (name === 'jsonextractstring') return typeof v === 'string' ? v : '';
1917
- if (name === 'jsonextractraw') return v === undefined ? '' : JSON.stringify(v);
1931
+ if (name === 'jsonextractraw') return v === undefined ? '' : stringifyJson(v);
1918
1932
  if (name === 'jsonextractbool') return v === true ? 1 : 0;
1933
+ if (typeof v === 'bigint') return name === 'jsonextractint' ? normInt(v) : Number(v);
1919
1934
  if (typeof v === 'number') return name === 'jsonextractint' ? Math.trunc(v) : v;
1920
1935
  if (typeof v === 'boolean') return v ? 1 : 0;
1921
1936
  return 0;
@@ -1939,8 +1954,8 @@ function evalFunction(e: Extract<Expr, { k: 'fn' }>, env: Env): unknown {
1939
1954
  case 'round': { argc(1, 2); const v = A(0); if (v === null) return null; const d = e.args.length > 1 ? asNumber(A(1))! : 0; const f = 10 ** d; return roundHalfEven(asNumber(v)! * f) / f; }
1940
1955
  case 'floor': { argc(1, 2); const v = A(0); if (v === null) return null; const d = e.args.length > 1 ? asNumber(A(1))! : 0; const f = 10 ** d; return Math.floor(asNumber(v)! * f) / f; }
1941
1956
  case 'ceil': case 'ceiling': { argc(1, 2); const v = A(0); if (v === null) return null; const d = e.args.length > 1 ? asNumber(A(1))! : 0; const f = 10 ** d; return Math.ceil(asNumber(v)! * f) / f; }
1942
- case 'abs': { argc(1); const v = A(0); return v === null ? null : Math.abs(asNumber(v)!); }
1943
- case 'intdiv': { argc(2); const a = A(0); const b = A(1); if (a === null || b === null) return null; if (asNumber(b) === 0) fail('Division by zero'); return Math.trunc(asNumber(a)! / asNumber(b)!); }
1957
+ case 'abs': { argc(1); const v = A(0); return v === null ? null : typeof v === 'bigint' ? normInt(v < 0n ? -v : v) : Math.abs(asNumber(v)!); }
1958
+ case 'intdiv': { argc(2); const a = A(0); const b = A(1); if (a === null || b === null) return null; if (isExactInt(a) && isExactInt(b)) { const divisor = toBigInt(b); if (divisor === 0n) fail('Division by zero'); return normInt(toBigInt(a) / divisor); } if (asNumber(b) === 0) fail('Division by zero'); return Math.trunc(asNumber(a)! / asNumber(b)!); }
1944
1959
  case 'divide': case 'plus': case 'minus': case 'multiply': case 'modulo': {
1945
1960
  argc(2); const ops: Record<string, string> = { divide: '/', plus: '+', minus: '-', multiply: '*', modulo: '%' };
1946
1961
  return arithmetic(ops[name]!, A(0), A(1));
@@ -1952,11 +1967,24 @@ function evalFunction(e: Extract<Expr, { k: 'fn' }>, env: Env): unknown {
1952
1967
  case 'now64': { argc(0, 2); return new DT(env.scope.exec.nowMs, e.args.length ? asNumber(A(0))! : 3, e.args.length > 1 ? tzArg(e, 1, env) : undefined); }
1953
1968
  case 'today': { argc(0); return toDateValue(new DT(env.scope.exec.nowMs, 0)); }
1954
1969
  case 'yesterday': { argc(0); return new DDate(toDateValue(new DT(env.scope.exec.nowMs, 0)).days - 1); }
1955
- case 'todatetime': case 'parsedatetimebesteffort': case 'parsedatetime64besteffort': case 'parsedatetimebesteffortornull': {
1970
+ case 'todatetime': case 'parsedatetimebesteffort': case 'parsedatetimebesteffortornull': {
1956
1971
  argc(1, 2); const v = A(0); if (v === null) return null;
1957
1972
  const tz = e.args.length > 1 ? tzArg(e, 1, env) : undefined;
1958
1973
  if (name === 'parsedatetimebesteffortornull') { try { return castDateTime(v, 0, tz); } catch { return null; } }
1959
- return castDateTime(v, name === 'parsedatetime64besteffort' ? 3 : 0, tz);
1974
+ return castDateTime(v, 0, tz);
1975
+ }
1976
+ // https://clickhouse.com/docs/en/sql-reference/functions/type-conversion-functions#parseDateTime64BestEffort
1977
+ case 'parsedatetime64besteffort': case 'parsedatetime64besteffortornull': case 'parsedatetime64besteffortorzero': {
1978
+ argc(1, 3); const v = A(0); if (v === null) return null;
1979
+ if (typeof v !== 'string') fail('Illegal type of argument of function ' + e.name + ': expected String');
1980
+ const scale = e.args.length > 1 ? asNumber(A(1))! : 3;
1981
+ const tz = e.args.length > 2 ? tzArg(e, 2, env) : undefined;
1982
+ try { return castDateTime(v, scale, tz); }
1983
+ catch (error) {
1984
+ if (name === 'parsedatetime64besteffortornull') return null;
1985
+ if (name === 'parsedatetime64besteffortorzero') return new DT(0, scale, tz);
1986
+ throw error;
1987
+ }
1960
1988
  }
1961
1989
  case 'todatetime64': case 'todatetime64orzero': {
1962
1990
  argc(2, 3); const v = A(0); if (v === null) return null;
@@ -2125,7 +2153,7 @@ function evalFunction(e: Extract<Expr, { k: 'fn' }>, env: Env): unknown {
2125
2153
  if (e.args[0]?.k !== 'lambda') {
2126
2154
  if (name === 'arraysum' || name === 'arraycount') {
2127
2155
  argc(1); const arr = A(0) as unknown[] | null; if (arr === null) return null;
2128
- return name === 'arraysum' ? arr.reduce<number>((s, x) => s + (asNumber(x) ?? 0), 0) : arr.filter((x) => truth(x) === true).length;
2156
+ return name === 'arraysum' ? sumArray(arr) : arr.filter((x) => truth(x) === true).length;
2129
2157
  }
2130
2158
  return fail(`${e.name}: first argument must be a lambda`);
2131
2159
  }
@@ -2136,7 +2164,7 @@ function evalFunction(e: Extract<Expr, { k: 'fn' }>, env: Env): unknown {
2136
2164
  if (name === 'arraycount') return results.filter((r) => truth(r) === true).length;
2137
2165
  if (name === 'arrayexists') return results.some((r) => truth(r) === true) ? 1 : 0;
2138
2166
  if (name === 'arrayall') return results.every((r) => truth(r) === true) ? 1 : 0;
2139
- if (name === 'arraysum') return results.reduce<number>((s, x) => s + (asNumber(x) ?? 0), 0);
2167
+ if (name === 'arraysum') return sumArray(results);
2140
2168
  const i = results.findIndex((r) => truth(r) === true);
2141
2169
  return i < 0 ? arrayAt(arrays[0]!, 0) : arrays[0]![i];
2142
2170
  }
@@ -2153,6 +2181,12 @@ function evalFunction(e: Extract<Expr, { k: 'fn' }>, env: Env): unknown {
2153
2181
  }
2154
2182
  }
2155
2183
 
2184
+ function sumArray(values: unknown[]): number | bigint {
2185
+ const held = values.filter((value) => value !== null && value !== undefined);
2186
+ return held.every(isExactInt) ? normInt(held.reduce<bigint>((sum, value) => sum + toBigInt(value), 0n))
2187
+ : held.reduce<number>((sum, value) => sum + (asNumber(value) ?? 0), 0);
2188
+ }
2189
+
2156
2190
  function roundHalfEven(x: number): number {
2157
2191
  const r = Math.round(x);
2158
2192
  return Math.abs(x % 1) === 0.5 && r % 2 !== 0 ? r - 1 : r;
@@ -2367,13 +2401,13 @@ const SCALAR_FUNCTIONS = new Set([
2367
2401
  'arraypushback', 'arraypushfront', 'arrayreverse', 'arrayreversesort', 'arrayslice', 'arraysort', 'arraystringconcat', 'arraysum',
2368
2402
  'assumenotnull', 'base64decode', 'base64encode', 'ceil', 'ceiling', 'char_length', 'coalesce', 'concat', 'cutquerystring', 'date_bin',
2369
2403
  'date_diff', 'datediff', 'decodeurlcomponent', 'decodeurlformcomponent', 'divide', 'domain', 'domainwithoutwww', 'empty',
2370
- 'encodeurlcomponent', 'encodeurlformcomponent', 'endswith', 'equals', 'extracturlparameter', 'extracturlparameternames', 'floor', 'formatdatetime',
2404
+ 'encodeurlcomponent', 'encodeurlformcomponent', 'endswith', 'equals', 'extract', 'extracturlparameter', 'extracturlparameternames', 'floor', 'formatdatetime',
2371
2405
  'mapcontains', 'mapcontainskey', 'mapfilter', 'mapkeys', 'mapvalues',
2372
2406
  'fromunixtimestamp', 'fromunixtimestamp64micro', 'fromunixtimestamp64milli', 'fromunixtimestamp64nano', 'greatest', 'has', 'hasall',
2373
2407
  'hasany', 'hex', 'identity', 'if', 'ifnull', 'indexof', 'intdiv', 'isnotnull', 'isnull', 'jsonextractbool', 'jsonextractfloat',
2374
2408
  'jsonextractint', 'jsonextractraw', 'jsonextractstring', 'jsonhas', 'lcase', 'least', 'leftpad', 'length', 'lengthutf8', 'locate',
2375
2409
  'lower', 'lowerutf8', 'ltrim', 'match', 'materialize', 'mid', 'minus', 'modulo', 'multiif', 'multiply', 'notempty', 'notequals', 'now',
2376
- 'now64', 'nullif', 'parsedatetime64besteffort', 'parsedatetimebesteffort', 'parsedatetimebesteffortornull', 'path', 'plus',
2410
+ 'now64', 'nullif', 'parsedatetime64besteffort', 'parsedatetime64besteffortornull', 'parsedatetime64besteffortorzero', 'parsedatetimebesteffort', 'parsedatetimebesteffortornull', 'path', 'plus',
2377
2411
  'position', 'pow', 'power', 'range', 'replace', 'replaceall', 'replaceone', 'replaceregexpall', 'replaceregexpone', 'reverse',
2378
2412
  'rightpad', 'round', 'rtrim', 'splitbychar', 'splitbystring', 'sqrt', 'startswith', 'substr', 'substring', 'subtractdays',
2379
2413
  'subtracthours', 'subtractminutes', 'subtractmonths', 'subtractseconds', 'subtractweeks', 'subtractyears', 'time_bucket',
@@ -2390,7 +2424,7 @@ const INTERVAL_FUNCTIONS = new Set(['millisecond', 'second', 'minute', 'hour', '
2390
2424
  const STRING_FUNCTIONS = new Set([
2391
2425
  'tostring', 'concat', 'replace', 'replaceall', 'replaceone', 'replaceregexpall', 'replaceregexpone', 'lower', 'upper', 'lowerutf8',
2392
2426
  'upperutf8', 'substring', 'substr', 'trim', 'trimboth', 'trimleft', 'trimright', 'encodeurlformcomponent', 'decodeurlformcomponent',
2393
- 'encodeurlcomponent', 'decodeurlcomponent', 'extracturlparameter', 'jsonextractstring', 'jsonextractraw', 'formatdatetime', 'domain',
2427
+ 'encodeurlcomponent', 'decodeurlcomponent', 'extract', 'extracturlparameter', 'jsonextractstring', 'jsonextractraw', 'formatdatetime', 'domain',
2394
2428
  'domainwithoutwww', 'path', 'cutquerystring', 'leftpad', 'rightpad', 'base64encode', 'base64decode', 'hex', 'totypename',
2395
2429
  ]);
2396
2430