@fraczak/k 6.8.0 → 6.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/repl-codecs.mjs CHANGED
@@ -6,27 +6,7 @@ import { encodeToWire, decodeWire } from "./codecs/runtime/prefix-codec.mjs";
6
6
  import { deriveClosedPattern } from "./codecs/runtime/codec.mjs";
7
7
  import { patternToPropertyList } from "./codecs/runtime/pattern-json.mjs";
8
8
  import { finalize } from "./codes.mjs";
9
- import * as intCodec from "./codecs/int.mjs";
10
- import * as utf8Codec from "./codecs/utf8.mjs";
11
- import * as jsonCodec from "./codecs/json.mjs";
12
- import * as ieeeCodec from "./codecs/ieee.mjs";
13
- import * as unitCodec from "./codecs/unit.mjs";
14
- import { patternFromJsonValue } from "./codecs/json-codec.mjs";
15
9
 
16
- const BUILTIN_CODECS = {
17
- int: intCodec,
18
- utf8: utf8Codec,
19
- json: jsonCodec,
20
- ieee: ieeeCodec,
21
- unit: unitCodec
22
- };
23
-
24
- const UNIVERSAL_CODE = "*";
25
- const NAME_RE = /^[a-zA-Z0-9_+-][a-zA-Z0-9_?!+-]*$/;
26
-
27
- function isCanonicalCodeName(name) {
28
- return typeof name === "string" && name.startsWith("@");
29
- }
30
10
 
31
11
  function closedPatternToCodeHash(pattern) {
32
12
  if (!Array.isArray(pattern) || pattern.length === 0) {
@@ -73,17 +53,10 @@ function codeHashToPattern(codeHash, findCode) {
73
53
  return patternToPropertyList(deriveClosedPattern(codeHash, code, resolveType));
74
54
  }
75
55
 
76
- function valueForPattern(value, pattern = null, rawInput = null, codecName = null) {
56
+ function valueForPattern(value, pattern = null) {
77
57
  if (pattern) {
78
58
  return decodeWire(encodeToWire(value, pattern)).value;
79
59
  }
80
- if (codecName === "json" && rawInput != null) {
81
- try {
82
- const json = JSON.parse(rawInput);
83
- const jsonPattern = patternFromJsonValue(json);
84
- return decodeWire(encodeToWire(value, jsonPattern)).value;
85
- } catch {}
86
- }
87
60
  return decodeWire(encodeToWire(value)).value;
88
61
  }
89
62
 
@@ -92,35 +65,27 @@ function valueForCode(value, codeHash, findCode) {
92
65
  return valueForPattern(value, pattern);
93
66
  }
94
67
 
95
- function normalizeCodecModule(mod, fallbackName) {
96
- const codec = mod.replCodec || mod.codec || mod.default || mod;
97
- const name = codec.name || mod.name || fallbackName;
98
- const universal = codec.universal === true;
99
- const patterns = codec.patterns || (codec.pattern ? [codec.pattern] : null);
100
- const codes = codec.codes || codec.codeHashes || (codec.code ? [codec.code] : null) ||
101
- patterns?.map((pattern, index) => {
102
- const codeHash = closedPatternToCodeHash(pattern);
103
- if (!codeHash) throw new Error(`Codec '${name}' pattern ${index} must be a closed property-list pattern`);
104
- return codeHash;
105
- });
106
- if (!name || typeof name !== "string") {
107
- throw new Error("Codec module must export a string name");
108
- }
109
- if (!universal && (!Array.isArray(codes) || codes.length === 0)) {
110
- throw new Error(`Codec '${name}' must export a non-empty codes array`);
111
- }
112
- if (codec.parse != null && typeof codec.parse !== "function") {
113
- throw new Error(`Codec '${name}' parse export must be a function`);
114
- }
115
- if (codec.print != null && typeof codec.print !== "function") {
116
- throw new Error(`Codec '${name}' print export must be a function`);
68
+ function ensureEnveloped(value, codec = null) {
69
+ if (!value || typeof value !== "object") return value;
70
+ if (value.pattern) return value;
71
+ if (codec?.pattern) {
72
+ try {
73
+ return decodeWire(encodeToWire(value, codec.pattern)).value;
74
+ } catch {}
117
75
  }
76
+ try {
77
+ return decodeWire(encodeToWire(value)).value;
78
+ } catch {}
79
+ return value;
80
+ }
81
+
82
+ function normalizeCodecModule(mod, name) {
83
+ const codec = mod.replCodec || mod.codec || mod.default || mod;
118
84
  return {
119
85
  name,
120
- codes: universal ? [UNIVERSAL_CODE] : codes,
121
- universal,
122
86
  parse: codec.parse,
123
- print: codec.print
87
+ print: codec.print,
88
+ pattern: codec.pattern
124
89
  };
125
90
  }
126
91
 
@@ -129,168 +94,77 @@ function codecStore(state) {
129
94
  return state.codecs;
130
95
  }
131
96
 
132
- function resolveCanonicalCodeHash(codeHash, state = null) {
133
- if (codeHash === UNIVERSAL_CODE) return UNIVERSAL_CODE;
134
- if (isCanonicalCodeName(codeHash)) return codeHash;
135
- if (state?.typeAliases) {
136
- const clean = codeHash.startsWith("$") ? codeHash.slice(1) : codeHash;
137
- if (state.typeAliases[clean]) return state.typeAliases[clean];
138
- }
139
- return codeHash;
97
+ function registerCodec(state, rawCodec, filePath) {
98
+ const name = path.basename(filePath);
99
+ const codec = normalizeCodecModule(rawCodec, name);
100
+ const store = codecStore(state);
101
+ store[codec.name] = {
102
+ ...codec,
103
+ source: filePath
104
+ };
105
+ return [{ name: codec.name, source: filePath }];
140
106
  }
141
107
 
142
- function registerCodec(state, codec, source = null) {
143
- const registered = [];
108
+ function unregisterCodec(state, codecName) {
144
109
  const store = codecStore(state);
145
- for (const rawCodeHash of codec.codes) {
146
- const codeHash = resolveCanonicalCodeHash(rawCodeHash, state);
147
- const universal = codeHash === UNIVERSAL_CODE;
148
- if (!universal && !isCanonicalCodeName(codeHash)) {
149
- throw new Error(`Codec '${codec.name}' has non-canonical code '${codeHash}'`);
150
- }
151
- if (!store[codeHash]) store[codeHash] = [];
152
- const entry = {
153
- name: codec.name,
154
- source,
155
- universal,
156
- parse: codec.parse,
157
- print: codec.print
158
- };
159
- const existingIndex = store[codeHash].findIndex((existing) =>
160
- existing.name === entry.name && existing.source === entry.source
161
- );
162
- if (existingIndex === -1) {
163
- store[codeHash].push(entry);
164
- } else {
165
- store[codeHash][existingIndex] = entry;
166
- }
167
- if (!universal && isCanonicalCodeName(codeHash) && state?.typeAliases && codec.name && NAME_RE.test(codec.name)) {
168
- state.typeAliases[codec.name] = codeHash;
169
- }
170
- registered.push({ codeHash, ...entry });
171
- }
172
- return registered;
110
+ if (!store[codecName]) return 0;
111
+ delete store[codecName];
112
+ return 1;
173
113
  }
174
114
 
175
- function unregisterCodec(state, codecName) {
115
+ function listCodecs(state) {
176
116
  const store = codecStore(state);
177
- let removedCount = 0;
178
- for (const [codeHash, codecs] of Object.entries(store)) {
179
- const remaining = codecs.filter((c) => c.name !== codecName);
180
- if (remaining.length !== codecs.length) {
181
- removedCount += codecs.length - remaining.length;
182
- if (remaining.length === 0) {
183
- delete store[codeHash];
184
- } else {
185
- store[codeHash] = remaining;
186
- }
187
- }
188
- }
189
- return removedCount;
117
+ const entries = Object.values(store).sort((a, b) => a.name.localeCompare(b.name));
118
+ if (entries.length === 0) return "(none)";
119
+ return entries.map((c) => `${c.name}${c.source ? ` (${c.source})` : ""}`).join("\n");
190
120
  }
191
121
 
192
- function matchBuiltinCodec(target) {
193
- const normalized = target.replace(/\\/g, "/").replace(/^\.\//, "").toLowerCase();
194
- const base = path.basename(normalized, path.extname(normalized));
195
- if (BUILTIN_CODECS[base]) return { name: base, mod: BUILTIN_CODECS[base] };
196
- return null;
122
+ function codecNames(state) {
123
+ const store = codecStore(state);
124
+ return Object.keys(store).sort();
197
125
  }
198
126
 
199
- async function loadCodecModule(state, filePath) {
200
- const builtin = matchBuiltinCodec(filePath);
201
- if (builtin) {
202
- const codec = normalizeCodecModule(builtin.mod, builtin.name);
203
- return registerCodec(state, codec, "built-in");
127
+ function resolveCodec(state, codecName, capability = null) {
128
+ if (!codecName) {
129
+ throw new Error("Codec name is required");
204
130
  }
131
+ const store = codecStore(state);
132
+ const codec = store[codecName];
133
+ if (!codec) {
134
+ throw new Error(`Codec '${codecName}' is not loaded`);
135
+ }
136
+ if (capability && typeof codec[capability] !== "function") {
137
+ throw new Error(`Codec '${codecName}' does not support ${capability}`);
138
+ }
139
+ return codec;
140
+ }
205
141
 
206
- // Check if file exists on disk or in VFS
207
- let source = null;
208
- try {
209
- if (fs.existsSync(filePath)) {
210
- source = fs.readFileSync(filePath, "utf8");
211
- }
212
- } catch {}
213
-
214
- const resolved = path.resolve(filePath);
215
- if (!source && fs.existsSync(resolved)) {
142
+ async function loadCodecModule(state, filePath) {
143
+ let mod;
144
+ if (typeof process !== "undefined" && process?.versions?.node) {
145
+ const fullPath = path.resolve(filePath);
146
+ const url = pathToFileURL(fullPath);
216
147
  try {
217
- source = fs.readFileSync(resolved, "utf8");
148
+ const stats = fs.statSync(fullPath);
149
+ if (stats?.mtimeMs) url.searchParams.set("mtime", String(stats.mtimeMs));
218
150
  } catch {}
219
- }
220
-
221
- // If in Node environment with a real physical file on disk, try native dynamic import
222
- if (typeof process !== "undefined" && process?.versions?.node && fs.existsSync(resolved)) {
151
+ mod = await import(url.href);
152
+ } else {
153
+ const rawSource = fs.readFileSync(filePath, "utf8");
154
+ const source = rawSource.replace(/^#![^\n]*\n/, "");
155
+ const blob = new Blob([source], { type: "text/javascript" });
156
+ const blobUrl = URL.createObjectURL(blob);
223
157
  try {
224
- const url = pathToFileURL(resolved);
225
- try {
226
- const stats = fs.statSync(resolved);
227
- if (stats?.mtimeMs) url.searchParams.set("mtime", String(stats.mtimeMs));
228
- } catch {}
229
- const mod = await import(url.href);
230
- const codec = normalizeCodecModule(mod, path.basename(resolved, path.extname(resolved)));
231
- return registerCodec(state, codec, resolved);
232
- } catch (nodeErr) {
233
- if (!source) throw nodeErr;
158
+ mod = await import(blobUrl);
159
+ } finally {
160
+ URL.revokeObjectURL(blobUrl);
234
161
  }
235
162
  }
236
163
 
237
- if (!source) {
238
- throw new Error(`Cannot find codec file '${filePath}'`);
239
- }
240
-
241
- // Universal data URL loader (works in browser & Node, and in VFS)
242
- const sanitized = source.replace(
243
- /import\s+[^;]*from\s+['"][^'"]*['"];?/g,
244
- "const { Value, isProduct, isVariant, NODE_KIND } = globalThis;"
245
- );
246
- const dataUrl = "data:text/javascript;charset=utf-8," + encodeURIComponent(sanitized);
247
- const mod = await import(dataUrl);
248
- const codec = normalizeCodecModule(mod, path.basename(filePath, path.extname(filePath)));
249
- return registerCodec(state, codec, filePath);
250
- }
251
-
252
- function listCodecs(state) {
253
- const entries = Object.entries(codecStore(state))
254
- .flatMap(([codeHash, codecs]) => codecs.map((codec) => ({ codeHash, ...codec })))
255
- .sort((a, b) => a.name.localeCompare(b.name) || a.codeHash.localeCompare(b.codeHash));
256
- if (entries.length === 0) return "(none)";
257
- return entries.map((entry) => `${entry.name} ${entry.codeHash === UNIVERSAL_CODE ? "all" : entry.codeHash}${entry.source ? ` (${entry.source})` : ""}`).join("\n");
258
- }
259
-
260
- function codecNames(state, codeHash = null) {
261
- const store = codecStore(state);
262
- const codecs = codeHash
263
- ? [...(store[codeHash] || []), ...(store[UNIVERSAL_CODE] || [])]
264
- : Object.values(store).flat();
265
- return [...new Set(codecs.map((codec) => codec.name))].sort();
266
- }
267
-
268
- function resolveCodec(state, codeHash, codecName = null, capability = null) {
269
- const store = codecStore(state);
270
- const matchesCodec = (codec) =>
271
- (codecName == null || codec.name === codecName) &&
272
- (capability == null || typeof codec[capability] === "function");
273
- const exactMatches = (codeHash ? store[codeHash] || [] : []).filter(matchesCodec);
274
- const universalMatches = (store[UNIVERSAL_CODE] || []).filter(matchesCodec);
275
- const matches = codecName == null && exactMatches.length > 0
276
- ? exactMatches
277
- : [...exactMatches, ...universalMatches];
278
- if (matches.length === 0) {
279
- if (codecName) {
280
- const namedMatches = Object.values(store).flat().filter(matchesCodec);
281
- if (namedMatches.length > 0) return namedMatches[0];
282
- }
283
- const suffix = codecName ? ` named '${codecName}'` : "";
284
- throw new Error(`No ${capability || "usable"} codec${suffix}${codeHash ? ` for ${codeHash}` : ""}`);
285
- }
286
- if (matches.length > 1 && codecName == null) {
287
- throw new Error(`Multiple codecs for ${codeHash || "target"}; specify one of: ${matches.map((codec) => codec.name).join(", ")}`);
288
- }
289
- return matches[0];
164
+ return registerCodec(state, mod, filePath);
290
165
  }
291
166
 
292
167
  export {
293
- BUILTIN_CODECS,
294
168
  codecNames,
295
169
  codeHashToPattern,
296
170
  listCodecs,
@@ -300,8 +174,7 @@ export {
300
174
  resolveCodec,
301
175
  unregisterCodec,
302
176
  closedPatternToCodeHash,
303
- UNIVERSAL_CODE,
304
177
  valueForCode,
305
178
  valueForPattern,
306
- matchBuiltinCodec
179
+ ensureEnveloped
307
180
  };