@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.
- package/app-route.cjs +26 -25
- package/app-route.d.cts +1 -0
- package/dist/app-route.cjs +26 -25
- package/dist/app-route.d.cts +1 -0
- package/dist/generated/pack-facts.json +409 -352
- package/dist/inject.cjs +474 -95
- package/dist/network-policy.cjs +110 -13
- package/dist/network-policy.d.cts +8 -3
- package/dist/src/actions.js +2 -1
- package/dist/src/anthropic-wire.d.ts +2 -0
- package/dist/src/anthropic-wire.js +5 -0
- package/dist/src/attestation.d.ts +6 -0
- package/dist/src/attestation.js +27 -0
- package/dist/src/bytes.d.ts +3 -0
- package/dist/src/bytes.js +17 -0
- package/dist/src/changeset.js +2 -1
- package/dist/src/clickhouse/sql.js +73 -14
- package/dist/src/derived-core.d.ts +210 -32
- package/dist/src/derived-core.js +510 -244
- package/dist/src/derived-real.d.ts +1 -1
- package/dist/src/derived-real.js +704 -93
- package/dist/src/derived.d.ts +18 -2
- package/dist/src/derived.js +21 -2
- package/dist/src/events.d.ts +28 -45
- package/dist/src/events.js +40 -58
- package/dist/src/exact-json.d.ts +10 -0
- package/dist/src/exact-json.js +98 -0
- package/dist/src/executor.d.ts +17 -0
- package/dist/src/executor.js +91 -30
- package/dist/src/file-response.js +1 -1
- package/dist/src/form.d.ts +8 -0
- package/dist/src/form.js +76 -0
- package/dist/src/git/format.d.ts +16 -0
- package/dist/src/git/format.js +110 -0
- package/dist/src/git/index.d.ts +1 -0
- package/dist/src/git/index.js +1 -0
- package/dist/src/git/lfs.d.ts +1 -1
- package/dist/src/git/lfs.js +4 -3
- package/dist/src/graphql-wire.d.ts +6 -0
- package/dist/src/graphql-wire.js +6 -3
- package/dist/src/grpc-wire.js +2 -1
- package/dist/src/head.d.ts +1 -0
- package/dist/src/head.js +15 -4
- package/dist/src/held-reads.d.ts +6 -0
- package/dist/src/held-reads.js +60 -0
- package/dist/src/history.js +5 -4
- package/dist/src/host-port.d.ts +5 -0
- package/dist/src/host-port.js +14 -0
- package/dist/src/index.d.ts +11 -3
- package/dist/src/index.js +9 -4
- package/dist/src/log.js +12 -7
- package/dist/src/machines.d.ts +81 -5
- package/dist/src/machines.js +125 -9
- package/dist/src/managed-database.d.ts +15 -11
- package/dist/src/managed-database.js +38 -37
- package/dist/src/multipart.d.ts +21 -3
- package/dist/src/multipart.js +112 -38
- package/dist/src/observe.d.ts +3 -0
- package/dist/src/observe.js +6 -2
- package/dist/src/openai-wire.d.ts +16 -0
- package/dist/src/openai-wire.js +43 -12
- package/dist/src/pack-fetch.d.ts +4 -6
- package/dist/src/pack-fetch.js +189 -57
- package/dist/src/packRegistry.d.ts +7 -1
- package/dist/src/packRegistry.js +20 -3
- package/dist/src/private-transport.d.ts +1 -0
- package/dist/src/private-transport.js +11 -0
- package/dist/src/protobuf.js +10 -4
- package/dist/src/rateBudget.d.ts +8 -1
- package/dist/src/rateBudget.js +1 -0
- package/dist/src/redis/engine.d.ts +14 -5
- package/dist/src/redis/engine.js +271 -72
- package/dist/src/redis/frames.d.ts +17 -0
- package/dist/src/redis/frames.js +123 -0
- package/dist/src/redis/index.d.ts +6 -0
- package/dist/src/redis/index.js +6 -0
- package/dist/src/redis/json.d.ts +2 -0
- package/dist/src/redis/json.js +14 -0
- package/dist/src/redis/lua-libs.d.ts +4 -0
- package/dist/src/redis/lua-libs.js +173 -0
- package/dist/src/redis/lua.d.ts +1 -0
- package/dist/src/redis/lua.js +36 -0
- package/dist/src/redis/member-storage.d.ts +5 -0
- package/dist/src/redis/member-storage.js +75 -0
- package/dist/src/redis/resp2.d.ts +4 -0
- package/dist/src/redis/resp2.js +29 -0
- package/dist/src/redis/stream.d.ts +14 -0
- package/dist/src/redis/stream.js +335 -0
- package/dist/src/remote-execute.d.ts +2 -2
- package/dist/src/request-body.d.ts +11 -0
- package/dist/src/request-body.js +11 -0
- package/dist/src/runtime.d.ts +6 -5
- package/dist/src/runtime.js +5 -5
- package/dist/src/s3/wire.d.ts +4 -4
- package/dist/src/s3/wire.js +5 -3
- package/dist/src/scenario.d.ts +22 -0
- package/dist/src/scenario.js +35 -31
- package/dist/src/serve-http.d.ts +2 -1
- package/dist/src/serve-http.js +10 -1
- package/dist/src/serve.js +7 -4
- package/dist/src/signing.d.ts +23 -5
- package/dist/src/signing.js +74 -11
- package/dist/src/sigv4.d.ts +3 -1
- package/dist/src/sigv4.js +4 -2
- package/dist/src/smtp.js +2 -1
- package/dist/src/sockets.js +8 -3
- package/dist/src/state-system.d.ts +3 -0
- package/dist/src/storage.js +5 -2
- package/dist/src/twin-fetch.d.ts +3 -1
- package/dist/src/twin-fetch.js +3 -2
- package/dist/src/vendor-call.d.ts +10 -3
- package/dist/src/vendor-call.js +58 -14
- package/dist/test-fixtures/attestation.SOURCE.md +1 -0
- package/dist/test-fixtures/attestation.json +50 -0
- package/generated/pack-facts.json +409 -352
- package/inject.cjs +474 -95
- package/network-policy.cjs +110 -13
- package/network-policy.d.cts +8 -3
- package/package.json +11 -4
- package/src/actions.ts +2 -1
- package/src/anthropic-wire.ts +6 -0
- package/src/attestation.ts +23 -0
- package/src/bytes.ts +21 -0
- package/src/changeset.ts +2 -1
- package/src/clickhouse/sql.ts +50 -16
- package/src/derived-core.ts +573 -249
- package/src/derived-real.ts +552 -42
- package/src/derived.ts +23 -4
- package/src/events.ts +54 -85
- package/src/exact-json.ts +71 -0
- package/src/executor.ts +86 -27
- package/src/file-response.ts +1 -1
- package/src/form.ts +66 -0
- package/src/git/format.ts +89 -0
- package/src/git/index.ts +1 -0
- package/src/git/lfs.ts +5 -4
- package/src/graphql-wire.ts +8 -3
- package/src/grpc-wire.ts +2 -1
- package/src/head.ts +15 -5
- package/src/held-reads.ts +54 -0
- package/src/history.ts +5 -4
- package/src/host-port.ts +9 -0
- package/src/index.ts +10 -4
- package/src/log.ts +11 -7
- package/src/machines.ts +135 -13
- package/src/managed-database.ts +48 -40
- package/src/multipart.ts +110 -35
- package/src/observe.ts +6 -3
- package/src/openai-wire.ts +45 -14
- package/src/pack-fetch.ts +173 -50
- package/src/packRegistry.ts +27 -4
- package/src/private-transport.ts +8 -0
- package/src/protobuf.ts +8 -4
- package/src/rateBudget.ts +6 -1
- package/src/redis/engine.ts +168 -63
- package/src/redis/frames.ts +53 -0
- package/src/redis/index.ts +6 -0
- package/src/redis/json.ts +13 -0
- package/src/redis/lua-libs.ts +62 -0
- package/src/redis/lua.ts +20 -0
- package/src/redis/member-storage.ts +58 -0
- package/src/redis/resp2.ts +25 -0
- package/src/redis/stream.ts +102 -0
- package/src/remote-execute.ts +2 -2
- package/src/request-body.ts +12 -0
- package/src/runtime.ts +6 -5
- package/src/s3/wire.ts +8 -5
- package/src/scenario.ts +34 -13
- package/src/serve-http.ts +11 -2
- package/src/serve.ts +4 -3
- package/src/signing.ts +71 -17
- package/src/sigv4.ts +5 -3
- package/src/smtp.ts +3 -2
- package/src/sockets.ts +8 -3
- package/src/state-system.ts +3 -0
- package/src/storage.ts +5 -2
- package/src/twin-fetch.ts +4 -3
- package/src/vendor-call.ts +59 -12
- package/test-fixtures/attestation.json +50 -0
package/network-policy.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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:' && !
|
|
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 };
|
package/network-policy.d.cts
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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
|
-
"
|
|
128
|
-
"
|
|
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) =>
|
|
408
|
+
.map((line) => parseExactJson(line) as TwinAction);
|
|
408
409
|
}
|
|
409
410
|
|
|
410
411
|
const META = new Set(['id', 'type', 'updatedAt']);
|
package/src/anthropic-wire.ts
CHANGED
|
@@ -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 =
|
|
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) },
|
package/src/clickhouse/sql.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 :
|
|
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 ? '' :
|
|
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 '
|
|
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,
|
|
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
|
|
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
|
|
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
|
|