@volter/world-core 3.0.37 → 3.0.39

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 +108 -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 +108 -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
@@ -0,0 +1,123 @@
1
+ import { exactInteger } from "../exact-json.js";
2
+ // RESP frame parsing and RESP2 replies, shared by the Redis stream and journey tooling.
3
+ // https://redis.io/docs/latest/develop/reference/protocol-spec/
4
+ import { RedisInteger, RedisStatus } from "./engine.js";
5
+ export const binaryText = (b) => Array.from(b, (n) => String.fromCharCode(n)).join('');
6
+ export const binaryBytes = (s) => Uint8Array.from(s, (c) => c.charCodeAt(0));
7
+ export function readResp(s, start = 0) {
8
+ const e = s.indexOf('\r\n', start);
9
+ if (e < 0)
10
+ return undefined;
11
+ const tag = s[start], head = s.slice(start + 1, e);
12
+ let at = e + 2;
13
+ if (tag === '+' || tag === '-')
14
+ return { type: tag, value: head, end: at, ...(tag === '-' ? { error: true } : {}) };
15
+ if (tag === ':')
16
+ return { type: tag, value: exactInteger(head), end: at };
17
+ if (tag === '_')
18
+ return { type: tag, value: null, end: at };
19
+ if (tag === '#')
20
+ return { type: tag, value: head === 't', end: at };
21
+ if (tag === ',')
22
+ return { type: tag, value: Number(head), end: at };
23
+ const n = Number(head);
24
+ if (!Number.isSafeInteger(n) || n < -1)
25
+ throw new Error('Protocol error: invalid length');
26
+ if (tag === '$' || tag === '!') {
27
+ if (n === -1)
28
+ return { type: tag, value: null, end: at };
29
+ if (s.length < at + n + 2)
30
+ return undefined;
31
+ if (s.slice(at + n, at + n + 2) !== '\r\n')
32
+ throw new Error('Protocol error: invalid bulk terminator');
33
+ return { type: tag, value: s.slice(at, at + n), end: at + n + 2, ...(tag === '!' ? { error: true } : {}) };
34
+ }
35
+ if (['*', '%', '~', '>'].includes(tag)) {
36
+ if (n === -1)
37
+ return { type: tag, value: null, end: at };
38
+ const out = [];
39
+ for (let i = 0; i < n * (tag === '%' ? 2 : 1); i++) {
40
+ const f = readResp(s, at);
41
+ if (!f)
42
+ return undefined;
43
+ out.push(f.error ? { error: f.value } : f.value);
44
+ at = f.end;
45
+ }
46
+ return { type: tag, value: tag === '%' ? Object.fromEntries(Array.from({ length: n }, (_, i) => [out[i * 2], out[i * 2 + 1]])) : out, end: at };
47
+ }
48
+ throw new Error('Protocol error: invalid type');
49
+ }
50
+ export function respReply(value) {
51
+ if (value instanceof RedisInteger || typeof value === 'object' && value !== null && 'integer' in value)
52
+ return `:${value.integer}\r\n`;
53
+ if (value === null)
54
+ return '$-1\r\n';
55
+ if (value instanceof RedisStatus || typeof value === 'object' && !Array.isArray(value) && 'value' in value)
56
+ return `+${String(value.value).replace(/[\r\n]/g, ' ')}\r\n`;
57
+ if (typeof value === 'object' && !Array.isArray(value) && 'error' in value)
58
+ return `-${String(value.error).trim().replace(/[\r\n]/g, ' ')}\r\n`;
59
+ if (Array.isArray(value))
60
+ return `*${value.length}\r\n${value.map((x) => respReply(x)).join('')}`;
61
+ if (typeof value === 'object')
62
+ return respReply(Object.entries(value).flat());
63
+ if (typeof value === 'number')
64
+ return Number.isInteger(value) ? `:${value}\r\n` : respReply(String(value));
65
+ const s = String(value);
66
+ return `$${s.length}\r\n${s}\r\n`;
67
+ }
68
+ export const commandFrame = (command) => {
69
+ const args = command.map((s) => binaryText(new TextEncoder().encode(s)));
70
+ return binaryBytes(`*${args.length}\r\n${args.map((s) => `$${s.length}\r\n${s}\r\n`).join('')}`);
71
+ };
72
+ export function inlineCommand(line) {
73
+ const words = [];
74
+ let word = '', quote = '', active = false;
75
+ for (let i = 0; i < line.length; i++) {
76
+ const c = line[i];
77
+ if (c === '\\' && quote) {
78
+ const next = line[++i];
79
+ if (next === 'x' && /^[0-9a-f]{2}$/i.test(line.slice(i + 1, i + 3))) {
80
+ word += String.fromCharCode(parseInt(line.slice(i + 1, i + 3), 16));
81
+ i += 2;
82
+ }
83
+ else
84
+ word += { n: '\n', r: '\r', t: '\t' }[next] ?? next;
85
+ }
86
+ else if (quote) {
87
+ if (c === quote)
88
+ quote = '';
89
+ else
90
+ word += c;
91
+ }
92
+ else if (c === '"' || c === "'") {
93
+ quote = c;
94
+ active = true;
95
+ }
96
+ else if (/\s/.test(c)) {
97
+ if (active) {
98
+ words.push(word);
99
+ word = '';
100
+ active = false;
101
+ }
102
+ }
103
+ else {
104
+ word += c;
105
+ active = true;
106
+ }
107
+ }
108
+ if (quote)
109
+ throw new Error('Protocol error: unbalanced quotes in request');
110
+ if (active)
111
+ words.push(word);
112
+ return words;
113
+ }
114
+ /** Carry RESP integer/status tags across an internal JSON request without losing int64 bytes. */
115
+ export function wireValue(value) {
116
+ if (value instanceof RedisInteger)
117
+ return { integer: value.integer };
118
+ if (Array.isArray(value))
119
+ return value.map(wireValue);
120
+ if (value && typeof value === 'object')
121
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, wireValue(item)]));
122
+ return value;
123
+ }
@@ -1,2 +1,8 @@
1
1
  export * from './engine.js';
2
2
  export * from './lua.js';
3
+ export * from './resp2.js';
4
+ export * from './json.js';
5
+ export * from './member-storage.js';
6
+ export * from './lua-libs.js';
7
+ export * from './frames.js';
8
+ export * from './stream.js';
@@ -4,3 +4,9 @@
4
4
  // docs/contributing/architecture.md, "Other wires: lanes, headers and line protocols".
5
5
  export * from "./engine.js";
6
6
  export * from "./lua.js";
7
+ export * from "./resp2.js";
8
+ export * from "./json.js";
9
+ export * from "./member-storage.js";
10
+ export * from "./lua-libs.js";
11
+ export * from "./frames.js";
12
+ export * from "./stream.js";
@@ -0,0 +1,2 @@
1
+ /** Serialize a JSON reply envelope, including nested exact Redis integer numeric tokens. */
2
+ export declare function jsonReply(value: unknown): string;
@@ -0,0 +1,14 @@
1
+ // JSON envelopes for Redis replies. Integer replies carry decimal digits until the wire writes them;
2
+ // converting them to a Number here would round values outside JavaScript's safe integer range.
3
+ import { RedisInteger } from "./engine.js";
4
+ /** Serialize a JSON reply envelope, including nested exact Redis integer numeric tokens. */
5
+ export function jsonReply(value) {
6
+ if (value instanceof RedisInteger)
7
+ return value.integer;
8
+ if (Array.isArray(value))
9
+ return `[${value.map((item) => jsonReply(item)).join(',')}]`;
10
+ if (value !== null && typeof value === 'object') {
11
+ return `{${Object.entries(value).filter(([, item]) => item !== undefined).map(([key, item]) => `${JSON.stringify(key)}:${jsonReply(item)}`).join(',')}}`;
12
+ }
13
+ return JSON.stringify(value) ?? 'null';
14
+ }
@@ -0,0 +1,4 @@
1
+ import { type LuaValue, type LuaHost } from './lua.js';
2
+ export declare function unpackMessage(s: string): LuaValue;
3
+ export declare function packMessage(value: LuaValue): string;
4
+ export declare const standardLuaLibraries: NonNullable<LuaHost['extend']>;
@@ -0,0 +1,173 @@
1
+ // Redis's Lua JSON/MessagePack runtime libraries. Values only; no state or vendor names.
2
+ // https://redis.io/docs/latest/develop/programmability/lua-api/ (cjson, cmsgpack, bit)
3
+ import { LuaTable, LuaError, LUA_NULL, luaPattern, numberToLuaString, luaTypeName } from "./lua.js";
4
+ const bytes = (s) => Uint8Array.from(s, (c) => c.charCodeAt(0));
5
+ const text = (b) => Array.from(b, (v) => String.fromCharCode(v)).join('');
6
+ const objectTable = (v) => { const t = new LuaTable(); for (const [k, x] of Object.entries(v))
7
+ t.set(k, from(x)); return t; };
8
+ const from = (v) => v === null ? LUA_NULL : Array.isArray(v) ? LuaTable.fromArray(v.map(from)) : typeof v === 'object' ? objectTable(v) : v;
9
+ const to = (v) => v === LUA_NULL || v === null ? null : v instanceof LuaTable ? v.length > 0 ? v.toArray().map(to) : Object.fromEntries([...v.map.entries()].map(([k, x]) => [String(k), to(x)])) : v;
10
+ export function unpackMessage(s) {
11
+ const b = bytes(s), d = new DataView(b.buffer);
12
+ let i = 0;
13
+ const n = (width, signed = false) => { let v; if (i + width > b.length)
14
+ throw new LuaError('Bad data format in input.'); if (width === 1)
15
+ v = signed ? d.getInt8(i) : d.getUint8(i);
16
+ else if (width === 2)
17
+ v = signed ? d.getInt16(i) : d.getUint16(i);
18
+ else if (width === 4)
19
+ v = signed ? d.getInt32(i) : d.getUint32(i);
20
+ else
21
+ v = Number(signed ? d.getBigInt64(i) : d.getBigUint64(i)); i += width; return v; };
22
+ const string = (len) => { const out = text(b.subarray(i, i + len)); i += len; if (i > b.length)
23
+ throw new LuaError('Bad data format in input.'); return out; };
24
+ const array = (len) => LuaTable.fromArray(Array.from({ length: len }, read));
25
+ const map = (len) => { const t = new LuaTable(); for (let j = 0; j < len; j++)
26
+ t.set(read(), read()); return t; };
27
+ function read() {
28
+ const c = n(1);
29
+ if (c < 128)
30
+ return c;
31
+ if (c >= 224)
32
+ return c - 256;
33
+ if ((c & 0xe0) === 0xa0)
34
+ return string(c & 31);
35
+ if ((c & 0xf0) === 0x90)
36
+ return array(c & 15);
37
+ if ((c & 0xf0) === 0x80)
38
+ return map(c & 15);
39
+ switch (c) {
40
+ case 0xc0: return null;
41
+ case 0xc2: return false;
42
+ case 0xc3: return true;
43
+ case 0xc4:
44
+ case 0xd9: return string(n(1));
45
+ case 0xc5:
46
+ case 0xda: return string(n(2));
47
+ case 0xc6:
48
+ case 0xdb: return string(n(4));
49
+ case 0xcc: return n(1);
50
+ case 0xcd: return n(2);
51
+ case 0xce: return n(4);
52
+ case 0xcf: return n(8);
53
+ case 0xd0: return n(1, true);
54
+ case 0xd1: return n(2, true);
55
+ case 0xd2: return n(4, true);
56
+ case 0xd3: return n(8, true);
57
+ case 0xca: {
58
+ const v = d.getFloat32(i);
59
+ i += 4;
60
+ return v;
61
+ }
62
+ case 0xcb: {
63
+ const v = d.getFloat64(i);
64
+ i += 8;
65
+ return v;
66
+ }
67
+ case 0xdc: return array(n(2));
68
+ case 0xdd: return array(n(4));
69
+ case 0xde: return map(n(2));
70
+ case 0xdf: return map(n(4));
71
+ default: throw new LuaError('Bad data format in input.');
72
+ }
73
+ }
74
+ const value = read();
75
+ if (i !== b.length)
76
+ throw new LuaError('Extra bytes in input.');
77
+ return value;
78
+ }
79
+ export function packMessage(value) {
80
+ const out = [];
81
+ const len = (tag, size, n) => { out.push(tag); for (let j = size - 1; j >= 0; j--)
82
+ out.push(Math.floor(n / 2 ** (j * 8)) & 255); };
83
+ const put = (v) => {
84
+ if (v === null || v === LUA_NULL) {
85
+ out.push(0xc0);
86
+ return;
87
+ }
88
+ if (typeof v === 'boolean') {
89
+ out.push(v ? 0xc3 : 0xc2);
90
+ return;
91
+ }
92
+ if (typeof v === 'number') {
93
+ if (Number.isInteger(v) && v >= 0 && v < 128 || v >= -32 && v < 0 && Number.isInteger(v))
94
+ out.push(v & 255);
95
+ else {
96
+ const b = new Uint8Array(8);
97
+ new DataView(b.buffer).setFloat64(0, v);
98
+ out.push(0xcb, ...b);
99
+ }
100
+ return;
101
+ }
102
+ if (typeof v === 'string') {
103
+ if (v.length < 32)
104
+ out.push(0xa0 | v.length);
105
+ else
106
+ len(v.length < 256 ? 0xd9 : 0xda, v.length < 256 ? 1 : 2, v.length);
107
+ out.push(...bytes(v));
108
+ return;
109
+ }
110
+ if (!(v instanceof LuaTable))
111
+ throw new LuaError('Unsupported type');
112
+ if (v.length > 0) {
113
+ const a = v.toArray();
114
+ if (a.length < 16)
115
+ out.push(0x90 | a.length);
116
+ else
117
+ len(0xdc, 2, a.length);
118
+ a.forEach(put);
119
+ }
120
+ else {
121
+ if (v.map.size < 16)
122
+ out.push(0x80 | v.map.size);
123
+ else
124
+ len(0xde, 2, v.map.size);
125
+ for (const [k, x] of v.map) {
126
+ put(k);
127
+ put(x);
128
+ }
129
+ }
130
+ };
131
+ put(value);
132
+ return text(Uint8Array.from(out));
133
+ }
134
+ export const standardLuaLibraries = ({ get, set, commandApi }) => {
135
+ // Lua 5.1 string.match is a selected script runtime capability, not a base-library change.
136
+ // source: https://www.lua.org/manual/5.1/manual.html "Returns the captures from the pattern"
137
+ const string = get('string');
138
+ string.set('match', (a) => {
139
+ const str = (i) => { const v = a[i] ?? null; if (typeof v === 'string')
140
+ return v; if (typeof v === 'number')
141
+ return numberToLuaString(v); throw new LuaError(`bad argument #${i + 1} to 'string.match' (string expected, got ${luaTypeName(v)})`); };
142
+ const value = str(0), pattern = str(1);
143
+ const start = a[2] === undefined ? 0 : Math.max(0, Number(a[2]) - 1);
144
+ const match = new RegExp(luaPattern(pattern)).exec(value.slice(start));
145
+ return match ? match.length > 1 ? match.slice(1) : [match[0]] : [null];
146
+ });
147
+ set('redis', commandApi);
148
+ set('server', commandApi);
149
+ const json = new LuaTable();
150
+ json.set('null', LUA_NULL);
151
+ json.set('encode', (a) => [text(new TextEncoder().encode(JSON.stringify(to(a[0] ?? null))))]);
152
+ json.set('decode', (a) => { try {
153
+ return [from(JSON.parse(new TextDecoder().decode(bytes(String(a[0])))))];
154
+ }
155
+ catch {
156
+ throw new LuaError('Expected value but found invalid token');
157
+ } });
158
+ set('cjson', json);
159
+ const msg = new LuaTable();
160
+ msg.set('unpack', (a) => [unpackMessage(String(a[0]))]);
161
+ msg.set('pack', (a) => [packMessage(a[0] ?? null)]);
162
+ set('cmsgpack', msg);
163
+ const bit = new LuaTable();
164
+ const ns = (a) => a.map(Number);
165
+ bit.set('band', (a) => [ns(a).reduce((x, y) => x & y, -1)]);
166
+ bit.set('bor', (a) => [ns(a).reduce((x, y) => x | y, 0)]);
167
+ bit.set('bxor', (a) => [ns(a).reduce((x, y) => x ^ y, 0)]);
168
+ bit.set('bnot', (a) => [~Number(a[0])]);
169
+ bit.set('lshift', (a) => [Number(a[0]) << Number(a[1])]);
170
+ bit.set('rshift', (a) => [Number(a[0]) >>> Number(a[1])]);
171
+ bit.set('arshift', (a) => [Number(a[0]) >> Number(a[1])]);
172
+ set('bit', bit);
173
+ };
@@ -121,6 +121,7 @@ type Stat = {
121
121
  };
122
122
  /** Parse Lua source into a chunk. Exported so a test can prove a bad script fails at PARSE time. */
123
123
  export declare function parseLua(src: string): Stat[];
124
+ export declare function luaPattern(pattern: string): string;
124
125
  export declare function luaTruthy(v: LuaValue): boolean;
125
126
  export declare function luaTypeName(v: LuaValue): string;
126
127
  /** Lua 5.1's `%.14g` number formatting — `3` prints as "3", not "3.0". */
@@ -604,6 +604,42 @@ class Scope {
604
604
  }
605
605
  }
606
606
  const BREAK = Symbol('lua-break');
607
+ // Lua pattern subset used by command-language scripts; unsupported balanced/frontier patterns refuse explicitly.
608
+ export function luaPattern(pattern) {
609
+ const classes = { a: 'A-Za-z', d: '0-9', l: 'a-z', u: 'A-Z', w: 'A-Za-z0-9', s: '\\s', x: 'A-Fa-f0-9', p: '!-/:-@[-`{-~', c: '\\x00-\\x1f\\x7f', z: '\\x00' };
610
+ let out = '', inClass = false;
611
+ for (let i = 0; i < pattern.length; i++) {
612
+ const c = pattern[i];
613
+ if (c === '%') {
614
+ const next = pattern[++i];
615
+ if (next === 'b' || next === 'f')
616
+ throw new LuaError('unsupported Lua balanced/frontier pattern');
617
+ if (classes[next.toLowerCase()]) {
618
+ const content = classes[next.toLowerCase()];
619
+ out += inClass ? content : `[${next === next.toUpperCase() ? '^' : ''}${content}]`;
620
+ }
621
+ else if (/\d/.test(next))
622
+ out += `\\${next}`;
623
+ else
624
+ out += next.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
625
+ }
626
+ else if (c === '[') {
627
+ inClass = true;
628
+ out += c;
629
+ }
630
+ else if (c === ']') {
631
+ inClass = false;
632
+ out += c;
633
+ }
634
+ else if (!inClass && c === '-')
635
+ out += '*?';
636
+ else if ('\\{}|'.includes(c))
637
+ out += `\\${c}`;
638
+ else
639
+ out += c;
640
+ }
641
+ return out;
642
+ }
607
643
  export function luaTruthy(v) {
608
644
  return v !== null && v !== false;
609
645
  }
@@ -0,0 +1,5 @@
1
+ import type { RedisContext, KeyspaceImage, KeySpace } from './engine.js';
2
+ export declare const memberStorage: {
3
+ image(ctx: RedisContext): KeyspaceImage;
4
+ flush(space: KeySpace): Promise<void>;
5
+ };
@@ -0,0 +1,75 @@
1
+ // Redis's collection layout over the World tree. First need: the managed Redis queue's growing events stream.
2
+ // Key metadata and each hash field/set member/sorted-set member/stream entry are separate subjects.
3
+ import { createHash } from 'node:crypto';
4
+ import { applyTwinWrite } from "../serve.js";
5
+ import { projectResources } from "../actions.js";
6
+ import { treeStamp, treeChangesSince } from "../log.js";
7
+ import { getActiveWorldStore } from "../world-store.js";
8
+ const scope = (c) => c.database === undefined ? '' : `db:${c.database}:`;
9
+ const collections = new Set(['hash', 'set', 'zset', 'stream', 'list']);
10
+ const held = new WeakMap();
11
+ const members = (r) => !r || !collections.has(r.kind) ? new Map() : new Map(r.v.map((x, i) => r.kind === 'list' ? [String(i), x] : r.kind === 'set' ? [x, x] : [x[0], x[1]]));
12
+ const memberId = (c, name, field) => `${scope(c)}member:${createHash('sha256').update(JSON.stringify([name, field])).digest('hex')}`;
13
+ export const memberStorage = {
14
+ image(ctx) {
15
+ const cache = held.get(getActiveWorldStore()) ?? new Map();
16
+ held.set(getActiveWorldStore(), cache);
17
+ const key = `${ctx.service}\0${ctx.root ?? ''}\0${scope(ctx)}`;
18
+ let h = cache.get(key);
19
+ const stamp = treeStamp(ctx.service, ctx.root);
20
+ if (h?.stamp === stamp)
21
+ return h.image;
22
+ const delta = h && treeChangesSince(ctx.service, ctx.root, h.stamp);
23
+ if (!h || !delta || delta.removed.length)
24
+ h = { stamp, subjects: new Map(), image: { rows: new Map(), scripts: new Map(), revs: new Map() } };
25
+ for (const r of delta && !delta.removed.length ? delta.changed : projectResources(ctx.service, ctx.root))
26
+ h.subjects.set(r.id, r);
27
+ // Reconstruct only keys affected by the tree delta, not every member on every read.
28
+ const affected = new Set();
29
+ const changed = delta && !delta.removed.length ? delta.changed : [...h.subjects.values()];
30
+ for (const r of changed) {
31
+ if (!r.id.startsWith(scope(ctx)))
32
+ continue;
33
+ if (r.type === 'key')
34
+ affected.add(r.name);
35
+ if (r.type === 'member')
36
+ affected.add(r.key);
37
+ if (r.type === 'script') {
38
+ if (r.gone)
39
+ h.image.scripts.delete(r.sha);
40
+ else
41
+ h.image.scripts.set(r.sha, r.body);
42
+ }
43
+ }
44
+ for (const name of affected) {
45
+ const meta = h.subjects.get(`${scope(ctx)}key:${name}`);
46
+ if (!meta)
47
+ continue;
48
+ h.image.revs.set(name, meta._rev ?? 0);
49
+ if (meta.gone) {
50
+ h.image.rows.delete(name);
51
+ continue;
52
+ }
53
+ const v = collections.has(meta.kind) ? [...h.subjects.values()].filter((r) => r.type === 'member' && r.id.startsWith(scope(ctx)) && r.key === name && r.gone !== true).sort((a, b) => meta.kind === 'list' ? Number(a.field) - Number(b.field) : 0).map((r) => meta.kind === 'list' ? r.value : meta.kind === 'set' ? r.field : [r.field, r.value]) : meta.v;
54
+ h.image.rows.set(name, { name, kind: meta.kind, v, pexpireAt: meta.pexpire_at == null ? null : String(meta.pexpire_at), lastId: meta.last_id, _rev: meta._rev });
55
+ }
56
+ h.stamp = stamp;
57
+ cache.set(key, h);
58
+ return h.image;
59
+ },
60
+ async flush(space) {
61
+ const ctx = space.ctx;
62
+ const write = async (type, id, operation, fields) => applyTwinWrite(ctx.service, { operation, subjectType: type, subjectId: id, fields, occurredAt: ctx.occurredAt, actor: { kind: 'agent' } }, ctx.root);
63
+ for (const { name, row, operation } of space.pendingWrites()) {
64
+ const before = members(space.image.rows.get(name) ?? null), after = members(row);
65
+ for (const field of new Set([...before.keys(), ...after.keys()])) {
66
+ if (before.has(field) && after.has(field) && JSON.stringify(before.get(field)) === JSON.stringify(after.get(field)))
67
+ continue;
68
+ await write('member', memberId(ctx, name, field), operation, { key: name, field, value: after.get(field) ?? null, gone: !after.has(field) });
69
+ }
70
+ await write('key', `${scope(ctx)}key:${name}`, operation, { name, kind: row?.kind ?? 'string', v: row && !collections.has(row.kind) ? row.v : null, pexpire_at: row?.pexpireAt ?? null, last_id: row?.lastId ?? '', gone: row === null, _rev: space.nextRev(name) });
71
+ }
72
+ for (const { sha, body, operation } of space.pendingScriptWrites())
73
+ await write('script', `${scope(ctx)}script:${sha}`, operation, { sha, body: body ?? '', gone: body === null });
74
+ },
75
+ };
@@ -0,0 +1,4 @@
1
+ import { type RedisValue } from './engine.js';
2
+ export declare function resp2(value: RedisValue | {
3
+ error: string;
4
+ }): string;
@@ -0,0 +1,29 @@
1
+ // A reply as RESP2 writes it (https://redis.io/docs/latest/develop/reference/protocol-spec/): a status as a simple string,
2
+ // an error as a simple error, an integer as an integer, a string as a bulk string (its length in bytes), a null as the
3
+ // null bulk string, an array as an array of its elements' replies. RESP2 has no float or double: a number that is not an
4
+ // integer is its bulk string, as Redis sends INCRBYFLOAT's. "Simple strings never contain carriage return ( \r ) or line
5
+ // feed ( \n ) characters" (the page), and an error is a simple string's form: Redis writes a status's text with each CR
6
+ // and LF mapped to a space (script_lua.c, `sdsmapchars(ok,"\r\n"," ",2)` before `addReplyStatusLength`), and an
7
+ // error's with the CRs and LFs at its ends trimmed and each one left inside mapped to a space (networking.c,
8
+ // `addReplyErrorFormatInternal`: `sdstrim(s, "\r\n")`, then `sdsmapchars(s, "\r\n", " ", 2)`), so a message holding
9
+ // one never ends the reply early. Source: https://raw.githubusercontent.com/redis/redis/7.4.0/src/networking.c and
10
+ // https://raw.githubusercontent.com/redis/redis/7.4.0/src/script_lua.c.
11
+ // Shared by every wire that answers a Redis reply as RESP2 bytes.
12
+ import { RedisInteger, RedisStatus } from "./engine.js";
13
+ const spaced = (text) => text.replace(/[\r\n]/g, ' ');
14
+ const trimmed = (text) => text.replace(/^[\r\n]+|[\r\n]+$/g, '');
15
+ export function resp2(value) {
16
+ if (value instanceof RedisInteger)
17
+ return `:${value.integer}\r\n`;
18
+ if (value === null)
19
+ return '$-1\r\n';
20
+ if (value instanceof RedisStatus)
21
+ return `+${spaced(value.value)}\r\n`;
22
+ if (Array.isArray(value))
23
+ return `*${value.length}\r\n${value.map((v) => resp2(v)).join('')}`;
24
+ if (typeof value === 'number')
25
+ return Number.isInteger(value) ? `:${value}\r\n` : resp2(String(value));
26
+ if (typeof value === 'string')
27
+ return `$${new TextEncoder().encode(value).length}\r\n${value}\r\n`;
28
+ return `-${spaced(trimmed(value.error))}\r\n`;
29
+ }
@@ -0,0 +1,14 @@
1
+ import type { TwinStream } from '../twin-fetch.js';
2
+ import { type RedisDialect, type RunItem } from './engine.js';
3
+ export type RedisStreamOptions = {
4
+ dialect: RedisDialect;
5
+ version: string;
6
+ password?: string;
7
+ databases: number;
8
+ run(commands: string[][], database: string): Promise<{
9
+ items: RunItem[];
10
+ wrote: boolean;
11
+ instant?: number;
12
+ }>;
13
+ };
14
+ export declare function createRedisStream(options: RedisStreamOptions): TwinStream;