@olivervea/pipelines 2.0.0-beta.18 → 2.0.0-beta.19

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 (51) hide show
  1. package/package.json +4 -2
  2. package/src/assets.d.ts +2 -2
  3. package/src/build/build.ts +111 -43
  4. package/src/build/cli.ts +28 -21
  5. package/src/build/elf.ts +8 -7
  6. package/src/build/hashes.ts +61 -28
  7. package/src/build/project.ts +69 -30
  8. package/src/builder/pipeline.ts +274 -102
  9. package/src/builder/refs.ts +12 -6
  10. package/src/core/brands.ts +6 -6
  11. package/src/core/config-refs.ts +46 -20
  12. package/src/core/config.ts +110 -33
  13. package/src/core/external.ts +30 -12
  14. package/src/core/handlers.ts +54 -14
  15. package/src/core/models.ts +29 -14
  16. package/src/core/origin.ts +7 -4
  17. package/src/core/secrets.ts +2 -2
  18. package/src/core/steps.ts +39 -23
  19. package/src/core/types.ts +5 -1
  20. package/src/core/webhooks.ts +21 -7
  21. package/src/ctx/files.ts +111 -40
  22. package/src/ctx/index.ts +75 -34
  23. package/src/ctx/sh.ts +104 -44
  24. package/src/ctx/types.ts +4 -4
  25. package/src/describe/describe.ts +550 -196
  26. package/src/describe/manifest-schema.ts +210 -80
  27. package/src/describe/mapper-source.ts +249 -104
  28. package/src/describe/record.ts +67 -37
  29. package/src/dev/cli.ts +64 -34
  30. package/src/harness/env.ts +33 -27
  31. package/src/harness/execute.ts +55 -29
  32. package/src/harness/index.ts +21 -8
  33. package/src/harness/main.ts +197 -74
  34. package/src/harness/result.ts +38 -22
  35. package/src/harness/validate.ts +62 -19
  36. package/src/index.ts +123 -14
  37. package/src/library/checks.ts +11 -6
  38. package/src/library/compose-deploy.ts +40 -23
  39. package/src/library/github-source.ts +69 -39
  40. package/src/library/helm-deploy-ssh.ts +213 -76
  41. package/src/library/helm-deploy.ts +263 -85
  42. package/src/library/http-health.ts +7 -5
  43. package/src/library/image-build.ts +293 -117
  44. package/src/library/index.ts +47 -15
  45. package/src/library/models.ts +17 -9
  46. package/src/library/ssh.ts +31 -14
  47. package/src/testing/index.ts +63 -24
  48. package/src/util/canonical.ts +7 -4
  49. package/src/util/handler-hash.ts +15 -5
  50. package/src/util/hash.ts +5 -5
  51. package/src/util/tar.ts +74 -37
@@ -12,34 +12,103 @@
12
12
  // function (from) { return { … }; }
13
13
  // Free identifiers (`RELEASE`, `CONFIG.url`) are allowed: they are module constants, recorded as
14
14
  // `{const}` by the proxy. This requires the harness bundle not to be minified.
15
- import { MapperError } from "./record.ts";
15
+ import { MapperError } from './record.ts';
16
16
 
17
17
  type Tok =
18
- | { t: "ident"; v: string; pos: number }
19
- | { t: "str"; v: string; pos: number }
20
- | { t: "num"; v: string; pos: number }
21
- | { t: "punc"; v: string; pos: number }
22
- | { t: "op"; v: string; pos: number }
23
- | { t: "eof"; v: ""; pos: number };
18
+ | { t: 'ident'; v: string; pos: number }
19
+ | { t: 'str'; v: string; pos: number }
20
+ | { t: 'num'; v: string; pos: number }
21
+ | { t: 'punc'; v: string; pos: number }
22
+ | { t: 'op'; v: string; pos: number }
23
+ | { t: 'eof'; v: ''; pos: number };
24
24
 
25
- const PUNCS = ["...", "=>", "?.", "(", ")", "{", "}", "[", "]", ",", ":", ".", ";"];
26
- const OPS = ["===", "!==", "**=", "??=", "&&=", "||=", "==", "!=", "<=", ">=", "&&", "||", "??", "++", "--", "**", "<<", ">>", "+=", "-=", "*=", "/=", "?", "!", "+", "-", "*", "/", "%", "<", ">", "=", "&", "|", "^", "~", "@", "#"];
25
+ const PUNCS = ['...', '=>', '?.', '(', ')', '{', '}', '[', ']', ',', ':', '.', ';'];
26
+ const OPS = [
27
+ '===',
28
+ '!==',
29
+ '**=',
30
+ '??=',
31
+ '&&=',
32
+ '||=',
33
+ '==',
34
+ '!=',
35
+ '<=',
36
+ '>=',
37
+ '&&',
38
+ '||',
39
+ '??',
40
+ '++',
41
+ '--',
42
+ '**',
43
+ '<<',
44
+ '>>',
45
+ '+=',
46
+ '-=',
47
+ '*=',
48
+ '/=',
49
+ '?',
50
+ '!',
51
+ '+',
52
+ '-',
53
+ '*',
54
+ '/',
55
+ '%',
56
+ '<',
57
+ '>',
58
+ '=',
59
+ '&',
60
+ '|',
61
+ '^',
62
+ '~',
63
+ '@',
64
+ '#',
65
+ ];
27
66
 
28
67
  function describeOp(op: string): string {
29
- if (op === "?") return "a ternary (`? :`)";
30
- if (op === "??") return "nullish coalescing (`??`)";
31
- if (op === "&&" || op === "||") return `a logical operator (\`${op}\`)`;
32
- if (op === "!") return "negation (`!`)";
33
- if (["===", "!==", "==", "!=", "<", ">", "<=", ">="].includes(op)) return `a comparison (\`${op}\`)`;
34
- if (["+", "-", "*", "/", "%", "**"].includes(op)) return `arithmetic/concatenation (\`${op}\`)`;
35
- if (op.endsWith("=") || op === "++" || op === "--") return `an assignment (\`${op}\`)`;
68
+ if (op === '?') return 'a ternary (`? :`)';
69
+ if (op === '??') return 'nullish coalescing (`??`)';
70
+ if (op === '&&' || op === '||') return `a logical operator (\`${op}\`)`;
71
+ if (op === '!') return 'negation (`!`)';
72
+ if (['===', '!==', '==', '!=', '<', '>', '<=', '>='].includes(op))
73
+ return `a comparison (\`${op}\`)`;
74
+ if (['+', '-', '*', '/', '%', '**'].includes(op))
75
+ return `arithmetic/concatenation (\`${op}\`)`;
76
+ if (op.endsWith('=') || op === '++' || op === '--') return `an assignment (\`${op}\`)`;
36
77
  return `the operator \`${op}\``;
37
78
  }
38
79
 
39
80
  const KEYWORDS = new Set([
40
- "typeof", "void", "delete", "new", "await", "yield", "this", "super", "class", "function", "async",
41
- "if", "else", "for", "while", "do", "switch", "case", "try", "catch", "throw", "var", "let", "const",
42
- "in", "instanceof", "import", "export", "return", "arguments", "eval",
81
+ 'typeof',
82
+ 'void',
83
+ 'delete',
84
+ 'new',
85
+ 'await',
86
+ 'yield',
87
+ 'this',
88
+ 'super',
89
+ 'class',
90
+ 'function',
91
+ 'async',
92
+ 'if',
93
+ 'else',
94
+ 'for',
95
+ 'while',
96
+ 'do',
97
+ 'switch',
98
+ 'case',
99
+ 'try',
100
+ 'catch',
101
+ 'throw',
102
+ 'var',
103
+ 'let',
104
+ 'const',
105
+ 'in',
106
+ 'instanceof',
107
+ 'import',
108
+ 'export',
109
+ 'return',
110
+ 'arguments',
111
+ 'eval',
43
112
  ]);
44
113
 
45
114
  function tokenize(src: string): Tok[] {
@@ -47,57 +116,72 @@ function tokenize(src: string): Tok[] {
47
116
  let i = 0;
48
117
  while (i < src.length) {
49
118
  const c = src[i]!;
50
- if (/\s/.test(c)) { i++; continue; }
51
- if (src.startsWith("//", i)) { const e = src.indexOf("\n", i); i = e < 0 ? src.length : e + 1; continue; }
52
- if (src.startsWith("/*", i)) {
53
- const e = src.indexOf("*/", i + 2);
54
- if (e < 0) throw new MapperError("mapper source has an unterminated comment");
119
+ if (/\s/.test(c)) {
120
+ i++;
121
+ continue;
122
+ }
123
+ if (src.startsWith('//', i)) {
124
+ const e = src.indexOf('\n', i);
125
+ i = e < 0 ? src.length : e + 1;
126
+ continue;
127
+ }
128
+ if (src.startsWith('/*', i)) {
129
+ const e = src.indexOf('*/', i + 2);
130
+ if (e < 0) throw new MapperError('mapper source has an unterminated comment');
55
131
  i = e + 2;
56
132
  continue;
57
133
  }
58
- if (c === "`") throw new MapperError("mapper uses a template string");
134
+ if (c === '`') throw new MapperError('mapper uses a template string');
59
135
  if (c === '"' || c === "'") {
60
136
  let j = i + 1;
61
- let v = "";
137
+ let v = '';
62
138
  for (;;) {
63
- if (j >= src.length) throw new MapperError("mapper source has an unterminated string");
139
+ if (j >= src.length)
140
+ throw new MapperError('mapper source has an unterminated string');
64
141
  const d = src[j]!;
65
142
  if (d === c) break;
66
- if (d === "\\") { v += src.slice(j, j + 2); j += 2; continue; }
143
+ if (d === '\\') {
144
+ v += src.slice(j, j + 2);
145
+ j += 2;
146
+ continue;
147
+ }
67
148
  v += d;
68
149
  j++;
69
150
  }
70
- toks.push({ t: "str", v, pos: i });
151
+ toks.push({ t: 'str', v, pos: i });
71
152
  i = j + 1;
72
153
  continue;
73
154
  }
74
- const num = /^(?:0[xXoObB][0-9a-fA-F_]+|(?:\d[\d_]*)?\.?\d[\d_]*(?:[eE][+-]?\d+)?)n?/.exec(src.slice(i));
75
- if (num && /[\d.]/.test(c) && num[0] !== ".") {
76
- toks.push({ t: "num", v: num[0], pos: i });
155
+ const num =
156
+ /^(?:0[xXoObB][0-9a-fA-F_]+|(?:\d[\d_]*)?\.?\d[\d_]*(?:[eE][+-]?\d+)?)n?/.exec(
157
+ src.slice(i),
158
+ );
159
+ if (num && /[\d.]/.test(c) && num[0] !== '.') {
160
+ toks.push({ t: 'num', v: num[0], pos: i });
77
161
  i += num[0].length;
78
162
  continue;
79
163
  }
80
164
  const id = /^[\p{ID_Start}$_][\p{ID_Continue}$‌‍]*/u.exec(src.slice(i));
81
165
  if (id) {
82
- toks.push({ t: "ident", v: id[0], pos: i });
166
+ toks.push({ t: 'ident', v: id[0], pos: i });
83
167
  i += id[0].length;
84
168
  continue;
85
169
  }
86
170
  const p = PUNCS.find((x) => src.startsWith(x, i));
87
- if (p && !(p === "?." && /\d/.test(src[i + 2] ?? ""))) {
88
- toks.push({ t: "punc", v: p, pos: i });
171
+ if (p && !(p === '?.' && /\d/.test(src[i + 2] ?? ''))) {
172
+ toks.push({ t: 'punc', v: p, pos: i });
89
173
  i += p.length;
90
174
  continue;
91
175
  }
92
176
  const o = OPS.find((x) => src.startsWith(x, i));
93
177
  if (o) {
94
- toks.push({ t: "op", v: o, pos: i });
178
+ toks.push({ t: 'op', v: o, pos: i });
95
179
  i += o.length;
96
180
  continue;
97
181
  }
98
182
  throw new MapperError(`mapper source has an unexpected character '${c}'`);
99
183
  }
100
- toks.push({ t: "eof", v: "", pos: src.length });
184
+ toks.push({ t: 'eof', v: '', pos: src.length });
101
185
  return toks;
102
186
  }
103
187
 
@@ -107,132 +191,193 @@ export function analyzeMapperSource(src: string): void {
107
191
  let k = 0;
108
192
  const peek = (o = 0) => toks[Math.min(k + o, toks.length - 1)]!;
109
193
  const next = () => toks[k++]!;
110
- const is = (t: Tok["t"], v?: string, o = 0) => peek(o).t === t && (v === undefined || peek(o).v === v);
111
- const unexpected = (what = "here"): never => {
194
+ const is = (t: Tok['t'], v?: string, o = 0) =>
195
+ peek(o).t === t && (v === undefined || peek(o).v === v);
196
+ const unexpected = (what = 'here'): never => {
112
197
  const tok = peek();
113
- if (tok.t === "op") throw new MapperError(`mapper uses ${describeOp(tok.v)}; mappers may only contain property access and literals — move logic into the handler or a when block`);
114
- if (tok.t === "punc" && tok.v === "?.") throw new MapperError("mapper uses optional chaining (`?.`); declare the input optional(...) and map the whole ref instead");
115
- if (tok.t === "punc" && tok.v === "...") throw new MapperError("mapper uses spread (`...`); list the fields explicitly");
116
- if (tok.t === "punc" && tok.v === "(") throw new MapperError("mapper calls a function or method; mappers may only contain property access and literals");
117
- if (tok.t === "ident" && KEYWORDS.has(tok.v)) throw new MapperError(`mapper uses \`${tok.v}\`; mappers may only contain property access and literals`);
118
- throw new MapperError(`mapper source is not an object literal of property accesses and literals (unexpected ${tok.t === "eof" ? "end" : `'${tok.v}'`} ${what})`);
198
+ if (tok.t === 'op')
199
+ throw new MapperError(
200
+ `mapper uses ${describeOp(tok.v)}; mappers may only contain property access and literals — move logic into the handler or a when block`,
201
+ );
202
+ if (tok.t === 'punc' && tok.v === '?.')
203
+ throw new MapperError(
204
+ 'mapper uses optional chaining (`?.`); declare the input optional(...) and map the whole ref instead',
205
+ );
206
+ if (tok.t === 'punc' && tok.v === '...')
207
+ throw new MapperError('mapper uses spread (`...`); list the fields explicitly');
208
+ if (tok.t === 'punc' && tok.v === '(')
209
+ throw new MapperError(
210
+ 'mapper calls a function or method; mappers may only contain property access and literals',
211
+ );
212
+ if (tok.t === 'ident' && KEYWORDS.has(tok.v))
213
+ throw new MapperError(
214
+ `mapper uses \`${tok.v}\`; mappers may only contain property access and literals`,
215
+ );
216
+ throw new MapperError(
217
+ `mapper source is not an object literal of property accesses and literals (unexpected ${tok.t === 'eof' ? 'end' : `'${tok.v}'`} ${what})`,
218
+ );
119
219
  };
120
- const expect = (t: Tok["t"], v?: string) => (is(t, v) ? next() : unexpected(`(expected ${v ?? t})`));
220
+ const expect = (t: Tok['t'], v?: string) =>
221
+ is(t, v) ? next() : unexpected(`(expected ${v ?? t})`);
121
222
 
122
223
  const identifier = () => {
123
- if (!is("ident") || KEYWORDS.has(peek().v)) unexpected("(expected an identifier)");
224
+ if (!is('ident') || KEYWORDS.has(peek().v)) unexpected('(expected an identifier)');
124
225
  return next();
125
226
  };
126
227
 
127
228
  // ── parameters ──
128
229
  const params = () => {
129
- if (is("ident")) { identifier(); return; }
130
- expect("punc", "(");
131
- if (is("punc", ")")) { next(); return; }
132
- if (is("punc", "{")) destructuring();
230
+ if (is('ident')) {
231
+ identifier();
232
+ return;
233
+ }
234
+ expect('punc', '(');
235
+ if (is('punc', ')')) {
236
+ next();
237
+ return;
238
+ }
239
+ if (is('punc', '{')) destructuring();
133
240
  else identifier();
134
- if (is("punc", ",")) next();
135
- if (is("op", "=")) throw new MapperError("mapper parameter has a default value");
136
- expect("punc", ")");
241
+ if (is('punc', ',')) next();
242
+ if (is('op', '=')) throw new MapperError('mapper parameter has a default value');
243
+ expect('punc', ')');
137
244
  };
138
245
  const destructuring = () => {
139
- expect("punc", "{");
140
- while (!is("punc", "}")) {
141
- if (is("str")) { next(); expect("punc", ":"); identifier(); }
142
- else {
246
+ expect('punc', '{');
247
+ while (!is('punc', '}')) {
248
+ if (is('str')) {
249
+ next();
250
+ expect('punc', ':');
251
+ identifier();
252
+ } else {
143
253
  identifier();
144
- if (is("punc", ":")) { next(); identifier(); }
254
+ if (is('punc', ':')) {
255
+ next();
256
+ identifier();
257
+ }
145
258
  }
146
- if (is("op", "=")) throw new MapperError("mapper parameter destructuring has a default value");
147
- if (!is("punc", ",")) break;
259
+ if (is('op', '='))
260
+ throw new MapperError('mapper parameter destructuring has a default value');
261
+ if (!is('punc', ',')) break;
148
262
  next();
149
263
  }
150
- expect("punc", "}");
264
+ expect('punc', '}');
151
265
  };
152
266
 
153
267
  // ── values ──
154
268
  const value = (): void => {
155
- if (is("punc", "(")) { next(); value(); expect("punc", ")"); return; }
156
- if (is("punc", "{")) return object();
157
- if (is("punc", "[")) return array();
158
- if (is("str") || is("num")) { next(); return; }
159
- if (is("op", "-") && is("num", undefined, 1)) { next(); next(); return; }
160
- if (is("ident", "true") || is("ident", "false") || is("ident", "null")) { next(); return; }
161
- if (is("ident", "undefined")) throw new MapperError("mapper uses `undefined`; leave optional fields out instead");
269
+ if (is('punc', '(')) {
270
+ next();
271
+ value();
272
+ expect('punc', ')');
273
+ return;
274
+ }
275
+ if (is('punc', '{')) return object();
276
+ if (is('punc', '[')) return array();
277
+ if (is('str') || is('num')) {
278
+ next();
279
+ return;
280
+ }
281
+ if (is('op', '-') && is('num', undefined, 1)) {
282
+ next();
283
+ next();
284
+ return;
285
+ }
286
+ if (is('ident', 'true') || is('ident', 'false') || is('ident', 'null')) {
287
+ next();
288
+ return;
289
+ }
290
+ if (is('ident', 'undefined'))
291
+ throw new MapperError('mapper uses `undefined`; leave optional fields out instead');
162
292
  chain();
163
293
  };
164
294
  const chain = () => {
165
295
  identifier();
166
296
  for (;;) {
167
- if (is("punc", ".")) { next(); if (!is("ident")) unexpected("(expected a property name)"); next(); continue; }
168
- if (is("punc", "[")) {
297
+ if (is('punc', '.')) {
298
+ next();
299
+ if (!is('ident')) unexpected('(expected a property name)');
300
+ next();
301
+ continue;
302
+ }
303
+ if (is('punc', '[')) {
169
304
  next();
170
- if (!is("str") && !is("num")) throw new MapperError("mapper uses a computed property access; only literal keys (from[\"step-name\"]) are allowed");
305
+ if (!is('str') && !is('num'))
306
+ throw new MapperError(
307
+ 'mapper uses a computed property access; only literal keys (from["step-name"]) are allowed',
308
+ );
171
309
  next();
172
- expect("punc", "]");
310
+ expect('punc', ']');
173
311
  continue;
174
312
  }
175
313
  break;
176
314
  }
177
315
  };
178
316
  const array = () => {
179
- expect("punc", "[");
180
- while (!is("punc", "]")) {
181
- if (is("punc", ",")) unexpected("(array holes are not allowed)");
317
+ expect('punc', '[');
318
+ while (!is('punc', ']')) {
319
+ if (is('punc', ',')) unexpected('(array holes are not allowed)');
182
320
  value();
183
- if (!is("punc", ",")) break;
321
+ if (!is('punc', ',')) break;
184
322
  next();
185
323
  }
186
- expect("punc", "]");
324
+ expect('punc', ']');
187
325
  };
188
326
  const object = () => {
189
- expect("punc", "{");
190
- while (!is("punc", "}")) {
191
- if (is("punc", "...")) unexpected();
192
- if (is("punc", "[")) throw new MapperError("mapper uses a computed key; only literal keys are allowed");
193
- if (is("ident") && (is("punc", ",", 1) || is("punc", "}", 1))) {
327
+ expect('punc', '{');
328
+ while (!is('punc', '}')) {
329
+ if (is('punc', '...')) unexpected();
330
+ if (is('punc', '['))
331
+ throw new MapperError(
332
+ 'mapper uses a computed key; only literal keys are allowed',
333
+ );
334
+ if (is('ident') && (is('punc', ',', 1) || is('punc', '}', 1))) {
194
335
  // shorthand `{ input }` — a destructured root or a module constant
195
336
  identifier();
196
337
  } else {
197
- if (is("ident") || is("str") || is("num")) next();
198
- else unexpected("(expected a property name)");
199
- if (is("punc", "(")) throw new MapperError("mapper defines a method; mappers may only contain property access and literals");
200
- expect("punc", ":");
338
+ if (is('ident') || is('str') || is('num')) next();
339
+ else unexpected('(expected a property name)');
340
+ if (is('punc', '('))
341
+ throw new MapperError(
342
+ 'mapper defines a method; mappers may only contain property access and literals',
343
+ );
344
+ expect('punc', ':');
201
345
  value();
202
346
  }
203
- if (!is("punc", ",")) break;
347
+ if (!is('punc', ',')) break;
204
348
  next();
205
349
  }
206
- expect("punc", "}");
350
+ expect('punc', '}');
207
351
  };
208
352
  const returnBody = () => {
209
- expect("punc", "{");
210
- if (!is("ident", "return")) unexpected("(a block-bodied mapper must consist of a single `return { … }`)");
353
+ expect('punc', '{');
354
+ if (!is('ident', 'return'))
355
+ unexpected('(a block-bodied mapper must consist of a single `return { … }`)');
211
356
  next();
212
357
  object();
213
- if (is("punc", ";")) next();
214
- expect("punc", "}");
358
+ if (is('punc', ';')) next();
359
+ expect('punc', '}');
215
360
  };
216
361
 
217
362
  // ── function ──
218
- if (is("ident", "async")) throw new MapperError("mapper must not be async");
219
- if (is("ident", "function")) {
363
+ if (is('ident', 'async')) throw new MapperError('mapper must not be async');
364
+ if (is('ident', 'function')) {
220
365
  next();
221
- if (is("op", "*")) throw new MapperError("mapper must not be a generator");
222
- if (is("ident")) next();
366
+ if (is('op', '*')) throw new MapperError('mapper must not be a generator');
367
+ if (is('ident')) next();
223
368
  params();
224
369
  returnBody();
225
370
  } else {
226
371
  params();
227
- expect("punc", "=>");
228
- if (is("punc", "(")) {
372
+ expect('punc', '=>');
373
+ if (is('punc', '(')) {
229
374
  next();
230
375
  object();
231
- expect("punc", ")");
232
- } else if (is("punc", "{")) {
376
+ expect('punc', ')');
377
+ } else if (is('punc', '{')) {
233
378
  returnBody();
234
- } else unexpected("(the mapper body must be an object literal: from => ({ … }))");
379
+ } else unexpected('(the mapper body must be an object literal: from => ({ … }))');
235
380
  }
236
- if (is("punc", ";")) next();
237
- if (!is("eof")) unexpected("after the mapper body");
381
+ if (is('punc', ';')) next();
382
+ if (!is('eof')) unexpected('after the mapper body');
238
383
  }
@@ -4,10 +4,10 @@
4
4
  // `in`, assignment and JSON.stringify. It CANNOT see truthiness, `!`, `??`, `||`, ternaries or
5
5
  // `===` against a value — those are rejected by the static source analysis (mapper-source.ts),
6
6
  // and primarily by the `Ref<T>` types.
7
- import { REF_PATH } from "../core/brands.ts";
7
+ import { REF_PATH } from '../core/brands.ts';
8
8
 
9
9
  export class MapperError extends Error {
10
- override name = "MapperError";
10
+ override name = 'MapperError';
11
11
  }
12
12
 
13
13
  /** Manifest mapping node: `{ref}` | `{const}` | a nested object of mappings. */
@@ -24,22 +24,28 @@ function refProxy(path: string): unknown {
24
24
  return new Proxy(target, {
25
25
  get(_t, prop) {
26
26
  if (prop === REF_PATH) return path;
27
- if (prop === "then") return undefined; // `await ref` must not recurse
28
- if (prop === Symbol.toPrimitive || prop === "toString" || prop === "valueOf")
29
- return fail("coerces a value (template string / concatenation / arithmetic / comparison)");
30
- if (prop === "toJSON") return fail("serializes a reference (JSON.stringify)");
31
- if (typeof prop === "symbol") return fail(`uses ${String(prop)} (iteration/spread/inspection)`);
32
- if (prop.includes(".")) return fail(`accesses field '${prop}' (field names containing '.' cannot be mapped)`);
27
+ if (prop === 'then') return undefined; // `await ref` must not recurse
28
+ if (prop === Symbol.toPrimitive || prop === 'toString' || prop === 'valueOf')
29
+ return fail(
30
+ 'coerces a value (template string / concatenation / arithmetic / comparison)',
31
+ );
32
+ if (prop === 'toJSON') return fail('serializes a reference (JSON.stringify)');
33
+ if (typeof prop === 'symbol')
34
+ return fail(`uses ${String(prop)} (iteration/spread/inspection)`);
35
+ if (prop.includes('.'))
36
+ return fail(
37
+ `accesses field '${prop}' (field names containing '.' cannot be mapped)`,
38
+ );
33
39
  return refProxy(`${path}.${prop}`);
34
40
  },
35
- apply: () => fail("calls a function/method"),
36
- construct: () => fail("constructs"),
37
- set: () => fail("assigns"),
38
- deleteProperty: () => fail("deletes"),
39
- has: () => fail("uses the `in` operator"),
40
- ownKeys: () => fail("enumerates/spreads"),
41
- defineProperty: () => fail("defines a property"),
42
- getPrototypeOf: () => fail("inspects the prototype (instanceof)"),
41
+ apply: () => fail('calls a function/method'),
42
+ construct: () => fail('constructs'),
43
+ set: () => fail('assigns'),
44
+ deleteProperty: () => fail('deletes'),
45
+ has: () => fail('uses the `in` operator'),
46
+ ownKeys: () => fail('enumerates/spreads'),
47
+ defineProperty: () => fail('defines a property'),
48
+ getPrototypeOf: () => fail('inspects the prototype (instanceof)'),
43
49
  });
44
50
  }
45
51
 
@@ -49,59 +55,83 @@ export function rootProxy(available: ReadonlySet<string>): unknown {
49
55
  {},
50
56
  {
51
57
  get(_t, prop) {
52
- if (typeof prop === "symbol" || prop === "then") return undefined;
58
+ if (typeof prop === 'symbol' || prop === 'then') return undefined;
53
59
  if (!available.has(prop))
54
- throw new MapperError(`mapper references '${prop}', which is not available at this stage (available: ${[...available].sort().join(", ") || "none"})`);
60
+ throw new MapperError(
61
+ `mapper references '${prop}', which is not available at this stage (available: ${[...available].sort().join(', ') || 'none'})`,
62
+ );
55
63
  return refProxy(prop);
56
64
  },
57
65
  has: () => {
58
- throw new MapperError("mapper uses the `in` operator on its argument");
66
+ throw new MapperError('mapper uses the `in` operator on its argument');
59
67
  },
60
68
  ownKeys: () => {
61
- throw new MapperError("mapper enumerates/spreads its argument");
69
+ throw new MapperError('mapper enumerates/spreads its argument');
62
70
  },
63
71
  },
64
72
  );
65
73
  }
66
74
 
67
75
  export const refPathOf = (v: unknown): string | undefined =>
68
- typeof v === "function" ? ((v as unknown as Record<symbol, unknown>)[REF_PATH] as string | undefined) : undefined;
76
+ typeof v === 'function'
77
+ ? ((v as unknown as Record<symbol, unknown>)[REF_PATH] as string | undefined)
78
+ : undefined;
69
79
 
70
80
  function toMapping(v: unknown, at: string): Mapping {
71
81
  const path = refPathOf(v);
72
82
  if (path !== undefined) return { ref: path };
73
- if (typeof v === "function") throw new MapperError(`mapper returns a function at '${at}'`);
83
+ if (typeof v === 'function')
84
+ throw new MapperError(`mapper returns a function at '${at}'`);
74
85
  if (v === undefined) throw new MapperError(`mapper returns undefined at '${at}'`);
75
- if (v === null || typeof v === "string" || typeof v === "boolean") return { const: v };
76
- if (typeof v === "number") {
77
- if (!Number.isFinite(v)) throw new MapperError(`mapper returns a non-finite number at '${at}'`);
86
+ if (v === null || typeof v === 'string' || typeof v === 'boolean') return { const: v };
87
+ if (typeof v === 'number') {
88
+ if (!Number.isFinite(v))
89
+ throw new MapperError(`mapper returns a non-finite number at '${at}'`);
78
90
  return { const: v };
79
91
  }
80
92
  if (Array.isArray(v)) {
81
93
  const items = v.map((x, i) => toMapping(x, `${at}[${i}]`));
82
- if (items.every((e) => "const" in e && Object.keys(e).length === 1)) return { const: items.map((e) => (e as { const: unknown }).const) };
83
- throw new MapperError(`mapper puts references inside an array at '${at}' (only constant arrays are supported)`);
94
+ if (items.every((e) => 'const' in e && Object.keys(e).length === 1))
95
+ return { const: items.map((e) => (e as { const: unknown }).const) };
96
+ throw new MapperError(
97
+ `mapper puts references inside an array at '${at}' (only constant arrays are supported)`,
98
+ );
84
99
  }
85
- if (typeof v === "object") {
86
- if (Object.getPrototypeOf(v) !== Object.prototype && Object.getPrototypeOf(v) !== null)
100
+ if (typeof v === 'object') {
101
+ if (
102
+ Object.getPrototypeOf(v) !== Object.prototype &&
103
+ Object.getPrototypeOf(v) !== null
104
+ )
87
105
  throw new MapperError(`mapper returns a non-plain object at '${at}'`);
88
106
  const keys = Object.keys(v);
89
- if (keys.length === 1 && (keys[0] === "ref" || keys[0] === "const"))
90
- throw new MapperError(`mapper builds an object whose only field is '${keys[0]}' at '${at}'; that is reserved by the manifest's mapping encoding — map the whole object with a ref instead`);
107
+ if (keys.length === 1 && (keys[0] === 'ref' || keys[0] === 'const'))
108
+ throw new MapperError(
109
+ `mapper builds an object whose only field is '${keys[0]}' at '${at}'; that is reserved by the manifest's mapping encoding — map the whole object with a ref instead`,
110
+ );
91
111
  const out: Record<string, Mapping> = {};
92
- for (const k of keys) out[k] = toMapping((v as Record<string, unknown>)[k], at ? `${at}.${k}` : k);
112
+ for (const k of keys)
113
+ out[k] = toMapping((v as Record<string, unknown>)[k], at ? `${at}.${k}` : k);
93
114
  return out;
94
115
  }
95
116
  throw new MapperError(`mapper returns an unsupported value (${typeof v}) at '${at}'`);
96
117
  }
97
118
 
98
119
  /** Calls `fn` with recording proxies and returns `{ inputKey: Mapping }`. */
99
- export function recordMapper(fn: (from: unknown) => unknown, available: ReadonlySet<string>): Record<string, Mapping> {
120
+ export function recordMapper(
121
+ fn: (from: unknown) => unknown,
122
+ available: ReadonlySet<string>,
123
+ ): Record<string, Mapping> {
100
124
  const result = fn(rootProxy(available));
101
- if (refPathOf(result) !== undefined || typeof result !== "object" || result === null || Array.isArray(result))
102
- throw new MapperError("mapper must return an object literal");
103
- if (result instanceof Promise) throw new MapperError("mapper must be synchronous");
125
+ if (
126
+ refPathOf(result) !== undefined ||
127
+ typeof result !== 'object' ||
128
+ result === null ||
129
+ Array.isArray(result)
130
+ )
131
+ throw new MapperError('mapper must return an object literal');
132
+ if (result instanceof Promise) throw new MapperError('mapper must be synchronous');
104
133
  const out: Record<string, Mapping> = {};
105
- for (const k of Object.keys(result)) out[k] = toMapping((result as Record<string, unknown>)[k], k);
134
+ for (const k of Object.keys(result))
135
+ out[k] = toMapping((result as Record<string, unknown>)[k], k);
106
136
  return out;
107
137
  }