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