@endo/compartment-mapper 1.6.3 → 2.0.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.
Files changed (81) hide show
  1. package/package.json +12 -16
  2. package/src/archive-lite.d.ts +7 -7
  3. package/src/archive-lite.d.ts.map +1 -1
  4. package/src/archive-lite.js +78 -27
  5. package/src/archive.d.ts.map +1 -1
  6. package/src/archive.js +7 -0
  7. package/src/bundle-lite.d.ts +3 -3
  8. package/src/bundle-lite.d.ts.map +1 -1
  9. package/src/bundle-lite.js +19 -24
  10. package/src/bundle.d.ts +3 -3
  11. package/src/bundle.d.ts.map +1 -1
  12. package/src/bundle.js +19 -24
  13. package/src/capture-lite.d.ts +2 -2
  14. package/src/capture-lite.d.ts.map +1 -1
  15. package/src/capture-lite.js +217 -25
  16. package/src/compartment-map.d.ts +9 -2
  17. package/src/compartment-map.d.ts.map +1 -1
  18. package/src/compartment-map.js +737 -254
  19. package/src/digest.d.ts +22 -2
  20. package/src/digest.d.ts.map +1 -1
  21. package/src/digest.js +179 -56
  22. package/src/generic-graph.d.ts.map +1 -1
  23. package/src/generic-graph.js +8 -3
  24. package/src/guards.d.ts +18 -0
  25. package/src/guards.d.ts.map +1 -0
  26. package/src/guards.js +109 -0
  27. package/src/hooks.md +124 -0
  28. package/src/import-archive-lite.d.ts.map +1 -1
  29. package/src/import-archive-lite.js +15 -11
  30. package/src/import-archive.d.ts +5 -19
  31. package/src/import-archive.d.ts.map +1 -1
  32. package/src/import-archive.js +7 -27
  33. package/src/import-hook.d.ts +4 -3
  34. package/src/import-hook.d.ts.map +1 -1
  35. package/src/import-hook.js +138 -69
  36. package/src/import-lite.d.ts +6 -6
  37. package/src/import-lite.d.ts.map +1 -1
  38. package/src/import-lite.js +8 -5
  39. package/src/import.d.ts +3 -3
  40. package/src/import.d.ts.map +1 -1
  41. package/src/import.js +16 -6
  42. package/src/infer-exports.d.ts.map +1 -1
  43. package/src/infer-exports.js +16 -6
  44. package/src/link.d.ts +4 -3
  45. package/src/link.d.ts.map +1 -1
  46. package/src/link.js +70 -58
  47. package/src/node-modules.d.ts +4 -3
  48. package/src/node-modules.d.ts.map +1 -1
  49. package/src/node-modules.js +482 -114
  50. package/src/parse-cjs-shared-export-wrapper.d.ts.map +1 -1
  51. package/src/parse-cjs-shared-export-wrapper.js +3 -1
  52. package/src/policy-format.d.ts +22 -5
  53. package/src/policy-format.d.ts.map +1 -1
  54. package/src/policy-format.js +342 -108
  55. package/src/policy.d.ts +13 -28
  56. package/src/policy.d.ts.map +1 -1
  57. package/src/policy.js +161 -106
  58. package/src/types/canonical-name.d.ts +97 -0
  59. package/src/types/canonical-name.d.ts.map +1 -0
  60. package/src/types/canonical-name.ts +151 -0
  61. package/src/types/compartment-map-schema.d.ts +114 -35
  62. package/src/types/compartment-map-schema.d.ts.map +1 -1
  63. package/src/types/compartment-map-schema.ts +202 -37
  64. package/src/types/external.d.ts +168 -28
  65. package/src/types/external.d.ts.map +1 -1
  66. package/src/types/external.ts +215 -26
  67. package/src/types/internal.d.ts +23 -42
  68. package/src/types/internal.d.ts.map +1 -1
  69. package/src/types/internal.ts +51 -50
  70. package/src/types/node-modules.d.ts +71 -10
  71. package/src/types/node-modules.d.ts.map +1 -1
  72. package/src/types/node-modules.ts +107 -9
  73. package/src/types/policy-schema.d.ts +26 -11
  74. package/src/types/policy-schema.d.ts.map +1 -1
  75. package/src/types/policy-schema.ts +29 -16
  76. package/src/types/policy.d.ts +6 -2
  77. package/src/types/policy.d.ts.map +1 -1
  78. package/src/types/policy.ts +7 -2
  79. package/src/types/typescript.d.ts +28 -0
  80. package/src/types/typescript.d.ts.map +1 -1
  81. package/src/types/typescript.ts +37 -1
@@ -1,14 +1,43 @@
1
1
  /* Validates a compartment map against its schema. */
2
2
 
3
- import { assertPackagePolicy } from './policy-format.js';
3
+ import {
4
+ assertPackagePolicy,
5
+ ATTENUATORS_COMPARTMENT,
6
+ ENTRY_COMPARTMENT,
7
+ } from './policy-format.js';
4
8
 
5
- /** @import {CompartmentMapDescriptor} from './types.js' */
9
+ /**
10
+ * @import {
11
+ * FileCompartmentDescriptor,
12
+ * FileCompartmentMapDescriptor,
13
+ * FileModuleConfiguration,
14
+ * CompartmentMapDescriptor,
15
+ * EntryDescriptor,
16
+ * ModuleConfiguration,
17
+ * ExitModuleConfiguration,
18
+ * CompartmentModuleConfiguration,
19
+ * CompartmentDescriptor,
20
+ * ScopeDescriptor,
21
+ * BaseModuleConfiguration,
22
+ * DigestedCompartmentMapDescriptor,
23
+ * PackageCompartmentMapDescriptor,
24
+ * PackageCompartmentDescriptor,
25
+ * FileUrlString,
26
+ * LanguageForExtension,
27
+ * LanguageForModuleSpecifier,
28
+ * ModuleConfigurationKind,
29
+ * ModuleConfigurationKindToType,
30
+ * ErrorModuleConfiguration,
31
+ * DigestedCompartmentDescriptor} from './types.js'
32
+ */
6
33
 
7
34
  // TODO convert to the new `||` assert style.
8
35
  // Deferred because this file pervasively uses simple template strings rather than
9
36
  // template strings tagged with `assert.details` (aka `X`), and uses
10
37
  // this definition of `q` rather than `assert.quote`
11
38
  const q = JSON.stringify;
39
+ const { keys, entries } = Object;
40
+ const { isArray } = Array;
12
41
 
13
42
  /** @type {(a: string, b: string) => number} */
14
43
  // eslint-disable-next-line no-nested-ternary
@@ -26,432 +55,886 @@ function* enumerate(iterable) {
26
55
  }
27
56
  }
28
57
 
58
+ /**
59
+ * Type guard for a string value.
60
+ *
61
+ * @overload
62
+ * @param {unknown} value
63
+ * @param {string} keypath
64
+ * @param {string} url
65
+ * @returns {asserts value is string}
66
+ */
67
+
68
+ /**
69
+ * Type guard for a string value with a custom assertion failure message.
70
+ *
71
+ * @overload
72
+ * @param {unknown} value
73
+ * @param {string} message
74
+ * @returns {asserts value is string}
75
+ */
76
+
77
+ /**
78
+ * Type guard for a string value.
79
+ *
80
+ * @param {unknown} value
81
+ * @param {string} pathOrMessage
82
+ * @param {string} url
83
+ * @returns {asserts value is string}
84
+ */
85
+ const assertString = (value, pathOrMessage, url) => {
86
+ const keypath = pathOrMessage;
87
+ assert.typeof(
88
+ value,
89
+ 'string',
90
+ `${keypath} in ${q(url)} must be a string; got ${q(value)}`,
91
+ );
92
+ };
93
+
94
+ /**
95
+ * Asserts the `label` field valid
96
+ *
97
+ * @param {unknown} allegedLabel
98
+ * @param {string} keypath
99
+ * @param {string} url
100
+ * @returns {asserts alleged is string}
101
+ */
102
+ const assertLabel = (allegedLabel, keypath, url) => {
103
+ assertString(allegedLabel, keypath, url);
104
+ if (allegedLabel === ATTENUATORS_COMPARTMENT) {
105
+ return;
106
+ }
107
+ if (allegedLabel === ENTRY_COMPARTMENT) {
108
+ return;
109
+ }
110
+ assert(
111
+ /^(?:@[a-z][a-z0-9-.]*\/)?[a-z][a-z0-9-.]*(?:>(?:@[a-z][a-z0-9-.]*\/)?[a-z][a-z0-9-.]*)*$/.test(
112
+ allegedLabel,
113
+ ),
114
+ `${keypath} must be a canonical name in ${q(url)}; got ${q(allegedLabel)}`,
115
+ );
116
+ };
117
+
118
+ /**
119
+ * @param {unknown} allegedObject
120
+ * @param {string} keypath
121
+ * @param {string} url
122
+ * @returns {asserts allegedObject is Record<PropertyKey, unknown>}
123
+ */
124
+ const assertPlainObject = (allegedObject, keypath, url) => {
125
+ const object = Object(allegedObject);
126
+ assert(
127
+ object === allegedObject &&
128
+ !isArray(object) &&
129
+ !(typeof object === 'function'),
130
+ `${keypath} must be an object; got ${q(allegedObject)} of type ${q(typeof allegedObject)} in ${q(url)}`,
131
+ );
132
+ };
133
+
134
+ /**
135
+ *
136
+ * @param {unknown} value
137
+ * @param {string} keypath
138
+ * @param {string} url
139
+ * @returns {asserts value is boolean}
140
+ */
141
+ const assertBoolean = (value, keypath, url) => {
142
+ assert.typeof(
143
+ value,
144
+ 'boolean',
145
+ `${keypath} in ${q(url)} must be a boolean; got ${q(value)}`,
146
+ );
147
+ };
148
+
29
149
  /**
30
150
  * @param {Record<string, unknown>} object
31
151
  * @param {string} message
32
152
  */
33
153
  const assertEmptyObject = (object, message) => {
34
- assert(Object.keys(object).length === 0, message);
154
+ assert(keys(object).length === 0, message);
35
155
  };
36
156
 
37
157
  /**
38
158
  * @param {unknown} conditions
39
159
  * @param {string} url
160
+ * @returns {asserts conditions is CompartmentMapDescriptor['tags']}
40
161
  */
41
162
  const assertConditions = (conditions, url) => {
42
163
  if (conditions === undefined) return;
43
164
  assert(
44
- Array.isArray(conditions),
45
- `conditions must be an array, got ${conditions} in ${q(url)}`,
165
+ isArray(conditions),
166
+ `conditions must be an array; got ${conditions} in ${q(url)}`,
46
167
  );
47
168
  for (const [index, value] of enumerate(conditions)) {
48
- assert.typeof(
49
- value,
50
- 'string',
51
- `conditions[${index}] must be a string, got ${value} in ${q(url)}`,
52
- );
169
+ assertString(value, `conditions[${index}]`, url);
53
170
  }
54
171
  };
55
172
 
56
173
  /**
57
- * @param {Record<string, unknown>} allegedModule
58
- * @param {string} path
174
+ * @template {Partial<ModuleConfiguration>} T
175
+ * @param {T} allegedModule
176
+ * @returns {Omit<T, keyof BaseModuleConfiguration>}
177
+ */
178
+ const getModuleConfigurationSpecificProperties = allegedModule => {
179
+ const {
180
+ __createdBy: _createdBy,
181
+ retained: _retained,
182
+ deferredError: _deferredError,
183
+ ...other
184
+ } = allegedModule;
185
+ return other;
186
+ };
187
+
188
+ /**
189
+ *
190
+ * @param {Record<PropertyKey, unknown>} allegedModule
191
+ * @param {string} keypath
59
192
  * @param {string} url
193
+ * @returns {asserts allegedModule is ModuleConfiguration}
60
194
  */
61
- const assertCompartmentModule = (allegedModule, path, url) => {
62
- const { compartment, module, retained, ...extra } = allegedModule;
195
+ const assertBaseModuleConfiguration = (allegedModule, keypath, url) => {
196
+ const { deferredError, retained, createdBy } = allegedModule;
197
+ if (deferredError !== undefined) {
198
+ assertString(deferredError, `${keypath}.deferredError`, url);
199
+ }
200
+ if (retained !== undefined) {
201
+ assertBoolean(retained, `${keypath}.retained`, url);
202
+ }
203
+ if (createdBy !== undefined) {
204
+ assertString(createdBy, `${keypath}.createdBy`, url);
205
+ }
206
+ };
207
+
208
+ /**
209
+ * @param {ModuleConfiguration} moduleDescriptor
210
+ * @param {string} keypath
211
+ * @param {string} url
212
+ * @returns {asserts allegedModule is CompartmentModuleConfiguration}
213
+ */
214
+ const assertCompartmentModuleConfiguration = (
215
+ moduleDescriptor,
216
+ keypath,
217
+ url,
218
+ ) => {
219
+ const { compartment, module, ...extra } =
220
+ getModuleConfigurationSpecificProperties(
221
+ /** @type {CompartmentModuleConfiguration} */ (moduleDescriptor),
222
+ );
63
223
  assertEmptyObject(
64
224
  extra,
65
- `${path} must not have extra properties, got ${q({
66
- extra,
67
- compartment,
68
- })} in ${q(url)}`,
225
+ `${keypath} must not have extra properties; got ${q(extra)} in ${q(url)}`,
69
226
  );
70
- assert.typeof(
71
- compartment,
72
- 'string',
73
- `${path}.compartment must be a string, got ${q(compartment)} in ${q(url)}`,
74
- );
75
- assert.typeof(
76
- module,
77
- 'string',
78
- `${path}.module must be a string, got ${q(module)} in ${q(url)}`,
79
- );
80
- if (retained !== undefined) {
81
- assert.typeof(
82
- retained,
83
- 'boolean',
84
- `${path}.retained must be a boolean, got ${q(retained)} in ${q(url)}`,
85
- );
86
- }
227
+
228
+ assertString(compartment, `${keypath}.compartment`, url);
229
+ assertString(module, `${keypath}.module`, url);
87
230
  };
88
231
 
89
232
  /**
90
- * @param {Record<string, unknown>} allegedModule
91
- * @param {string} path
233
+ * @param {ModuleConfiguration} moduleDescriptor
234
+ * @param {string} keypath
92
235
  * @param {string} url
236
+ * @returns {asserts allegedModule is FileModuleConfiguration}
93
237
  */
94
- const assertFileModule = (allegedModule, path, url) => {
95
- const { location, parser, sha512, ...extra } = allegedModule;
238
+ const assertFileModuleConfiguration = (moduleDescriptor, keypath, url) => {
239
+ const { location, parser, sha512, ...extra } =
240
+ getModuleConfigurationSpecificProperties(
241
+ /** @type {FileModuleConfiguration} */ (moduleDescriptor),
242
+ );
96
243
  assertEmptyObject(
97
244
  extra,
98
- `${path} must not have extra properties, got ${q(
99
- Object.keys(extra),
245
+ `${keypath} must not have extra properties; got ${q(
246
+ keys(extra),
100
247
  )} in ${q(url)}`,
101
248
  );
102
- assert.typeof(
103
- location,
104
- 'string',
105
- `${path}.location must be a string, got ${q(location)} in ${q(url)}`,
106
- );
107
- assert.typeof(
108
- parser,
109
- 'string',
110
- `${path}.parser must be a string, got ${q(parser)} in ${q(url)}`,
111
- );
249
+ if (location !== undefined) {
250
+ assertString(location, `${keypath}.location`, url);
251
+ }
252
+ assertString(parser, `${keypath}.parser`, url);
112
253
 
113
254
  if (sha512 !== undefined) {
114
- assert.typeof(
115
- sha512,
116
- 'string',
117
- `${path}.sha512 must be a string, got ${q(sha512)} in ${q(url)}`,
118
- );
255
+ assertString(sha512, `${keypath}.sha512`, url);
119
256
  }
120
257
  };
121
258
 
122
259
  /**
123
- * @param {Record<string, unknown>} allegedModule
124
- * @param {string} path
260
+ * @param {ModuleConfiguration} moduleDescriptor
261
+ * @param {string} keypath
125
262
  * @param {string} url
263
+ * @returns {asserts allegedModule is ExitModuleConfiguration}
126
264
  */
127
- const assertExitModule = (allegedModule, path, url) => {
128
- const { exit, ...extra } = allegedModule;
265
+ const assertExitModuleConfiguration = (moduleDescriptor, keypath, url) => {
266
+ const { exit, ...extra } = getModuleConfigurationSpecificProperties(
267
+ /** @type {ExitModuleConfiguration} */ (moduleDescriptor),
268
+ );
129
269
  assertEmptyObject(
130
270
  extra,
131
- `${path} must not have extra properties, got ${q(
132
- Object.keys(extra),
271
+ `${keypath} must not have extra properties; got ${q(
272
+ keys(extra),
133
273
  )} in ${q(url)}`,
134
274
  );
135
- assert.typeof(
136
- exit,
137
- 'string',
138
- `${path}.exit must be a string, got ${q(exit)} in ${q(url)}`,
139
- );
275
+ assertString(exit, `${keypath}.exit`, url);
140
276
  };
141
277
 
142
278
  /**
279
+ *
280
+ * @param {ModuleConfiguration} moduleDescriptor
281
+ * @param {string} keypath
282
+ * @param {string} url
283
+ * @returns {asserts moduleDescriptor is ErrorModuleConfiguration}
284
+ */
285
+ const assertErrorModuleConfiguration = (moduleDescriptor, keypath, url) => {
286
+ const { deferredError } = moduleDescriptor;
287
+ if (deferredError) {
288
+ assertString(deferredError, `${keypath}.deferredError`, url);
289
+ }
290
+ };
291
+
292
+ /**
293
+ * @template {ModuleConfigurationKind[]} Kinds
294
+ * @overload
143
295
  * @param {unknown} allegedModule
144
- * @param {string} path
296
+ * @param {string} keypath
145
297
  * @param {string} url
298
+ * @param {Kinds} kinds
299
+ * @returns {asserts allegedModule is ModuleConfigurationKindToType<Kinds>}
146
300
  */
147
- const assertModule = (allegedModule, path, url) => {
148
- const moduleDescriptor = Object(allegedModule);
301
+
302
+ /**
303
+ * @overload
304
+ * @param {unknown} allegedModule
305
+ * @param {string} keypath
306
+ * @param {string} url
307
+ * @returns {asserts allegedModule is ModuleConfiguration}
308
+ */
309
+
310
+ /**
311
+ * @param {unknown} allegedModule
312
+ * @param {string} keypath
313
+ * @param {string} url
314
+ * @param {ModuleConfigurationKind[]} kinds
315
+ */
316
+ function assertModuleConfiguration(allegedModule, keypath, url, kinds) {
317
+ assertPlainObject(allegedModule, keypath, url);
318
+ assertBaseModuleConfiguration(allegedModule, keypath, url);
319
+
320
+ const finalKinds =
321
+ kinds.length > 0
322
+ ? kinds
323
+ : /** @type {ModuleConfigurationKind[]} */ ([
324
+ 'compartment',
325
+ 'file',
326
+ 'exit',
327
+ 'error',
328
+ ]);
329
+ /** @type {Error[]} */
330
+ const errors = [];
331
+ for (const kind of finalKinds) {
332
+ switch (kind) {
333
+ case 'compartment': {
334
+ try {
335
+ assertCompartmentModuleConfiguration(allegedModule, keypath, url);
336
+ } catch (error) {
337
+ errors.push(error);
338
+ }
339
+ break;
340
+ }
341
+ case 'file': {
342
+ try {
343
+ assertFileModuleConfiguration(allegedModule, keypath, url);
344
+ } catch (error) {
345
+ errors.push(error);
346
+ }
347
+ break;
348
+ }
349
+ case 'exit': {
350
+ try {
351
+ assertExitModuleConfiguration(allegedModule, keypath, url);
352
+ } catch (error) {
353
+ errors.push(error);
354
+ }
355
+ break;
356
+ }
357
+ case 'error': {
358
+ try {
359
+ assertErrorModuleConfiguration(allegedModule, keypath, url);
360
+ } catch (error) {
361
+ errors.push(error);
362
+ }
363
+ break;
364
+ }
365
+ default:
366
+ throw new TypeError(
367
+ `Unknown module descriptor kind ${q(kind)} in ${q(url)}`,
368
+ );
369
+ }
370
+ }
371
+
149
372
  assert(
150
- allegedModule === moduleDescriptor && !Array.isArray(moduleDescriptor),
151
- `${path} must be an object, got ${allegedModule} in ${q(url)}`,
373
+ errors.length < finalKinds.length,
374
+ `invalid module descriptor in ${q(url)} at ${q(keypath)}; expected to match one of ${q(kinds)}: ${errors.map(err => err.message).join('; ')}`,
152
375
  );
376
+ }
153
377
 
154
- const { compartment, module, location, parser, exit, deferredError } =
155
- moduleDescriptor;
156
- if (compartment !== undefined || module !== undefined) {
157
- assertCompartmentModule(moduleDescriptor, path, url);
158
- } else if (location !== undefined || parser !== undefined) {
159
- assertFileModule(moduleDescriptor, path, url);
160
- } else if (exit !== undefined) {
161
- assertExitModule(moduleDescriptor, path, url);
162
- } else if (deferredError !== undefined) {
163
- assert.typeof(
164
- deferredError,
165
- 'string',
166
- `${path}.deferredError must be a string contaiing an error message`,
378
+ /**
379
+ * @param {unknown} allegedModules
380
+ * @param {string} keypath
381
+ * @param {string} url
382
+ * @returns {asserts allegedModules is Record<string, ModuleConfiguration>}
383
+ */
384
+ const assertModuleConfigurations = (allegedModules, keypath, url) => {
385
+ assertPlainObject(allegedModules, keypath, url);
386
+ for (const [key, value] of entries(allegedModules)) {
387
+ assertString(
388
+ key,
389
+ `all keys of ${keypath}.modules must be strings; got ${key} in ${q(url)}`,
167
390
  );
168
- } else {
169
- assert.fail(
170
- `${path} is not a valid module descriptor, got ${q(allegedModule)} in ${q(
171
- url,
172
- )}`,
391
+ assertModuleConfiguration(value, `${keypath}.modules[${q(key)}]`, url);
392
+ }
393
+ };
394
+
395
+ /**
396
+ * @param {unknown} allegedModules
397
+ * @param {string} keypath
398
+ * @param {string} url
399
+ * @returns {asserts allegedModules is Record<string, FileModuleConfiguration|CompartmentModuleConfiguration>}
400
+ */
401
+ const assertFileModuleConfigurations = (allegedModules, keypath, url) => {
402
+ assertPlainObject(allegedModules, keypath, url);
403
+ for (const [key, value] of entries(allegedModules)) {
404
+ assertString(
405
+ key,
406
+ `all keys of ${keypath}.modules must be strings; got ${key} in ${q(url)}`,
173
407
  );
408
+ assertModuleConfiguration(value, `${keypath}.modules[${q(key)}]`, url, [
409
+ 'file',
410
+ 'compartment',
411
+ 'error',
412
+ ]);
174
413
  }
175
414
  };
176
415
 
177
416
  /**
178
417
  * @param {unknown} allegedModules
179
- * @param {string} path
418
+ * @param {string} keypath
180
419
  * @param {string} url
420
+ * @returns {asserts allegedModules is Record<string, ModuleConfiguration>}
181
421
  */
182
- const assertModules = (allegedModules, path, url) => {
183
- const modules = Object(allegedModules);
184
- assert(
185
- allegedModules === modules || !Array.isArray(modules),
186
- `modules must be an object, got ${q(allegedModules)} in ${q(url)}`,
187
- );
188
- for (const [key, value] of Object.entries(modules)) {
189
- assertModule(value, `${path}.modules[${q(key)}]`, url);
422
+ const assertDigestedModuleConfigurations = (allegedModules, keypath, url) => {
423
+ assertPlainObject(allegedModules, keypath, url);
424
+ for (const [key, value] of entries(allegedModules)) {
425
+ assertString(
426
+ key,
427
+ `all keys of ${keypath}.modules must be strings; got ${key} in ${q(url)}`,
428
+ );
429
+ assertModuleConfiguration(value, `${keypath}.modules[${q(key)}]`, url, [
430
+ 'file',
431
+ 'exit',
432
+ 'error',
433
+ ]);
190
434
  }
191
435
  };
192
436
 
193
437
  /**
194
438
  * @param {unknown} allegedParsers
195
- * @param {string} path
439
+ * @param {string} keypath
196
440
  * @param {string} url
441
+ * @returns {asserts allegedParsers is LanguageForExtension}
197
442
  */
198
- const assertParsers = (allegedParsers, path, url) => {
199
- if (allegedParsers === undefined) {
200
- return;
201
- }
202
- const parsers = Object(allegedParsers);
203
- assert(
204
- allegedParsers === parsers && !Array.isArray(parsers),
205
- `${path}.parsers must be an object, got ${allegedParsers} in ${q(url)}`,
206
- );
443
+ const assertParsers = (allegedParsers, keypath, url) => {
444
+ assertPlainObject(allegedParsers, `${keypath}.parsers`, url);
207
445
 
208
- for (const [key, value] of Object.entries(parsers)) {
209
- assert.typeof(
446
+ for (const [key, value] of entries(allegedParsers)) {
447
+ assertString(
210
448
  key,
211
- 'string',
212
- `all keys of ${path}.parsers must be strings, got ${key} in ${q(url)}`,
213
- );
214
- assert.typeof(
215
- value,
216
- 'string',
217
- `${path}.parsers[${q(key)}] must be a string, got ${value} in ${q(url)}`,
449
+ `all keys of ${keypath}.parsers must be strings; got ${key} in ${q(url)}`,
218
450
  );
451
+ assertString(value, `${keypath}.parsers[${q(key)}]`, url);
219
452
  }
220
453
  };
221
454
 
222
455
  /**
223
- * @param {unknown} allegedScope
224
- * @param {string} path
456
+ * @overload
457
+ * @param {unknown} allegedTruthyValue
458
+ * @param {string} keypath
225
459
  * @param {string} url
460
+ * @returns {asserts allegedTruthyValue is NonNullable<unknown>}
226
461
  */
227
- const assertScope = (allegedScope, path, url) => {
228
- const scope = Object(allegedScope);
462
+
463
+ /**
464
+ *
465
+ * @overload
466
+ * @param {unknown} allegedTruthyValue
467
+ * @param {string} message
468
+ * @returns {asserts allegedTruthyValue is NonNullable<unknown>}
469
+ */
470
+
471
+ /**
472
+ *
473
+ * @param {unknown} allegedTruthyValue
474
+ * @param {string} keypath
475
+ * @param {string} [url]
476
+ * @returns {asserts allegedTruthyValue is NonNullable<unknown>}
477
+ */
478
+ const assertTruthy = (allegedTruthyValue, keypath, url) => {
229
479
  assert(
230
- allegedScope === scope && !Array.isArray(scope),
231
- `${path} must be an object, got ${allegedScope} in ${q(url)}`,
480
+ allegedTruthyValue,
481
+ url
482
+ ? `${keypath} in ${q(url)} must be truthy; got ${q(allegedTruthyValue)}`
483
+ : url,
232
484
  );
485
+ };
486
+
487
+ /**
488
+ * @template [T=string]
489
+ * @typedef {(value: unknown, keypath: string, url: string) => void} AssertFn
490
+ */
491
+
492
+ /**
493
+ * @template [T=string]
494
+ * @param {unknown} allegedScope
495
+ * @param {string} keypath
496
+ * @param {string} url
497
+ * @param {AssertFn<T>} [assertCompartmentValue]
498
+ * @returns {asserts allegedScope is ScopeDescriptor<T>}
499
+ */
500
+ const assertScope = (allegedScope, keypath, url, assertCompartmentValue) => {
501
+ assertPlainObject(allegedScope, keypath, url);
233
502
 
234
- const { compartment, ...extra } = scope;
503
+ const { compartment, ...extra } = allegedScope;
235
504
  assertEmptyObject(
236
505
  extra,
237
- `${path} must not have extra properties, got ${q(
238
- Object.keys(extra),
506
+ `${keypath} must not have extra properties; got ${q(
507
+ keys(extra),
239
508
  )} in ${q(url)}`,
240
509
  );
241
510
 
242
- assert.typeof(
243
- compartment,
244
- 'string',
245
- `${path}.compartment must be a string, got ${q(compartment)} in ${q(url)}`,
246
- );
511
+ if (assertCompartmentValue) {
512
+ assertCompartmentValue(compartment, `${keypath}.compartment`, url);
513
+ } else {
514
+ assertString(compartment, `${keypath}.compartment`, url);
515
+ }
247
516
  };
248
517
 
249
518
  /**
519
+ * @template [T=string]
250
520
  * @param {unknown} allegedScopes
251
- * @param {string} path
521
+ * @param {string} keypath
252
522
  * @param {string} url
523
+ * @param {AssertFn<T>} [assertCompartmentValue]
524
+ * @returns {asserts allegedScopes is Record<string, ScopeDescriptor<T>>}
253
525
  */
254
- const assertScopes = (allegedScopes, path, url) => {
255
- if (allegedScopes === undefined) {
256
- return;
257
- }
258
- const scopes = Object(allegedScopes);
259
- assert(
260
- allegedScopes === scopes && !Array.isArray(scopes),
261
- `${path}.scopes must be an object, got ${q(allegedScopes)} in ${q(url)}`,
262
- );
526
+ const assertScopes = (
527
+ allegedScopes,
528
+ keypath,
529
+ url,
530
+ assertCompartmentValue = assertString,
531
+ ) => {
532
+ assertPlainObject(allegedScopes, keypath, url);
263
533
 
264
- for (const [key, value] of Object.entries(scopes)) {
265
- assert.typeof(
534
+ for (const [key, value] of entries(allegedScopes)) {
535
+ assertString(
266
536
  key,
267
- 'string',
268
- `all keys of ${path}.scopes must be strings, got ${key} in ${q(url)}`,
537
+ `all keys of ${keypath}.scopes must be strings; got ${key} in ${q(url)}`,
538
+ );
539
+ assertScope(
540
+ value,
541
+ `${keypath}.scopes[${q(key)}]`,
542
+ url,
543
+ assertCompartmentValue,
269
544
  );
270
- assertScope(value, `${path}.scopes[${q(key)}]`, url);
271
545
  }
272
546
  };
273
547
 
274
548
  /**
275
549
  * @param {unknown} allegedTypes
276
- * @param {string} path
550
+ * @param {string} keypath
277
551
  * @param {string} url
552
+ * @returns {asserts allegedTypes is LanguageForModuleSpecifier}
278
553
  */
279
- const assertTypes = (allegedTypes, path, url) => {
280
- if (allegedTypes === undefined) {
281
- return;
554
+ const assertTypes = (allegedTypes, keypath, url) => {
555
+ assertPlainObject(allegedTypes, `${keypath}.types`, url);
556
+
557
+ for (const [key, value] of entries(allegedTypes)) {
558
+ assertString(
559
+ key,
560
+ `all keys of ${keypath}.types must be strings; got ${key} in ${q(url)}`,
561
+ );
562
+ assertString(value, `${keypath}.types[${q(key)}]`, url);
282
563
  }
283
- const types = Object(allegedTypes);
564
+ };
565
+
566
+ /**
567
+ * @template {Record<string, ModuleConfiguration>} [M=Record<string, ModuleConfiguration>]
568
+ * @param {unknown} allegedCompartment
569
+ * @param {string} keypath
570
+ * @param {string} url
571
+ * @param {AssertFn<M>} [moduleConfigurationAssertionFn]
572
+ * @returns {asserts allegedCompartment is CompartmentDescriptor}
573
+ */
574
+ const assertCompartmentDescriptor = (
575
+ allegedCompartment,
576
+ keypath,
577
+ url,
578
+ moduleConfigurationAssertionFn = assertModuleConfigurations,
579
+ ) => {
580
+ assertPlainObject(allegedCompartment, keypath, url);
581
+
582
+ const {
583
+ location,
584
+ name,
585
+ parsers,
586
+ types,
587
+ scopes,
588
+ modules,
589
+ policy,
590
+ sourceDirname,
591
+ retained,
592
+ } = allegedCompartment;
593
+
594
+ assertString(location, `${keypath}.location`, url);
595
+ assertString(name, `${keypath}.name`, url);
596
+
597
+ // TODO: It may be prudent to assert that there exists some module referring
598
+ // to its own compartment
599
+
600
+ moduleConfigurationAssertionFn(modules, keypath, url);
601
+
602
+ if (parsers !== undefined) {
603
+ assertParsers(parsers, keypath, url);
604
+ }
605
+ if (scopes !== undefined) {
606
+ assertScopes(scopes, keypath, url);
607
+ }
608
+ if (types !== undefined) {
609
+ assertTypes(types, keypath, url);
610
+ }
611
+ if (policy !== undefined) {
612
+ assertPackagePolicy(policy, keypath, url);
613
+ }
614
+ if (sourceDirname !== undefined) {
615
+ assertString(sourceDirname, `${keypath}.sourceDirname`, url);
616
+ }
617
+ if (retained !== undefined) {
618
+ assertBoolean(retained, `${keypath}.retained`, url);
619
+ }
620
+ };
621
+
622
+ /**
623
+ * Ensures a string is a file URL (a {@link FileUrlString})
624
+ *
625
+ * @param {unknown} allegedFileUrlString - a package location to assert
626
+ * @param {string} keypath
627
+ * @param {string} url
628
+ * @returns {asserts allegedFileUrlString is FileUrlString}
629
+ */
630
+ const assertFileUrlString = (allegedFileUrlString, keypath, url) => {
631
+ assertString(allegedFileUrlString, keypath, url);
284
632
  assert(
285
- allegedTypes === types && !Array.isArray(types),
286
- `${path}.types must be an object, got ${allegedTypes} in ${q(url)}`,
633
+ allegedFileUrlString.startsWith('file://'),
634
+ `${keypath} must be a file URL in ${q(url)}; got ${q(allegedFileUrlString)}`,
287
635
  );
636
+ assert(
637
+ allegedFileUrlString.length > 7,
638
+ `${keypath} must contain a non-empty path in ${q(url)}; got ${q(allegedFileUrlString)}`,
639
+ );
640
+ };
288
641
 
289
- for (const [key, value] of Object.entries(types)) {
290
- assert.typeof(
642
+ /**
643
+ * @param {unknown} allegedModules
644
+ * @param {string} keypath
645
+ * @param {string} url
646
+ * @returns {asserts allegedModules is Record<string, CompartmentModuleConfiguration>}
647
+ */
648
+ const assertPackageModuleConfigurations = (allegedModules, keypath, url) => {
649
+ assertPlainObject(allegedModules, keypath, url);
650
+ for (const [key, value] of entries(allegedModules)) {
651
+ assertString(
291
652
  key,
292
- 'string',
293
- `all keys of ${path}.types must be strings, got ${key} in ${q(url)}`,
294
- );
295
- assert.typeof(
296
- value,
297
- 'string',
298
- `${path}.types[${q(key)}] must be a string, got ${value} in ${q(url)}`,
653
+ `all keys of ${keypath}.modules must be strings; got ${key} in ${q(url)}`,
299
654
  );
655
+ assertModuleConfiguration(value, `${keypath}.modules[${q(key)}]`, url, [
656
+ 'compartment',
657
+ ]);
300
658
  }
301
659
  };
302
660
 
303
661
  /**
304
- * @param {unknown} allegedPolicy
305
- * @param {string} path
306
- * @param {string} [url]
662
+ *
663
+ * @param {unknown} allegedLocation
664
+ * @param {string} keypath
665
+ * @param {string} url
666
+ * @returns {asserts allegedLocation is PackageCompartmentDescriptor['location']}
307
667
  */
308
-
309
- const assertPolicy = (
310
- allegedPolicy,
311
- path,
312
- url = '<unknown-compartment-map.json>',
313
- ) => {
314
- assertPackagePolicy(allegedPolicy, `${path}.policy`, url);
668
+ const assertPackageLocation = (allegedLocation, keypath, url) => {
669
+ if (allegedLocation === ATTENUATORS_COMPARTMENT) {
670
+ return;
671
+ }
672
+ assertFileUrlString(allegedLocation, keypath, url);
315
673
  };
316
674
 
317
675
  /**
318
676
  * @param {unknown} allegedCompartment
319
- * @param {string} path
677
+ * @param {string} keypath
320
678
  * @param {string} url
679
+ * @returns {asserts allegedCompartment is PackageCompartmentDescriptor}
321
680
  */
322
- const assertCompartment = (allegedCompartment, path, url) => {
323
- const compartment = Object(allegedCompartment);
324
- assert(
325
- allegedCompartment === compartment && !Array.isArray(compartment),
326
- `${path} must be an object, got ${allegedCompartment} in ${q(url)}`,
681
+ const assertPackageCompartmentDescriptor = (
682
+ allegedCompartment,
683
+ keypath,
684
+ url,
685
+ ) => {
686
+ assertCompartmentDescriptor(
687
+ allegedCompartment,
688
+ keypath,
689
+ url,
690
+ assertPackageModuleConfigurations,
327
691
  );
328
692
 
329
693
  const {
330
694
  location,
331
- name,
332
- label,
333
- parsers,
334
- types,
335
695
  scopes,
336
- modules,
337
- policy,
696
+ label,
697
+ // these unused vars already validated by assertPackageModuleConfigurations
698
+ name: _name,
699
+ sourceDirname: _sourceDirname,
700
+ modules: _modules,
701
+ parsers: _parsers,
702
+ types: _types,
703
+ policy: _policy,
704
+ version: _version,
338
705
  ...extra
339
- } = compartment;
706
+ } = /** @type {PackageCompartmentDescriptor} */ (allegedCompartment);
340
707
 
341
708
  assertEmptyObject(
342
709
  extra,
343
- `${path} must not have extra properties, got ${q(
344
- Object.keys(extra),
710
+ `${keypath} must not have extra properties; got ${q(
711
+ keys(extra),
345
712
  )} in ${q(url)}`,
346
713
  );
347
714
 
348
- assert.typeof(
349
- location,
350
- 'string',
351
- `${path}.location in ${q(url)} must be string, got ${q(location)}`,
715
+ assertPackageLocation(location, `${keypath}.location`, url);
716
+ assertLabel(label, `${keypath}.label`, url);
717
+ assertScopes(scopes, `${keypath}.scopes`, url, assertFileUrlString);
718
+ };
719
+
720
+ /**
721
+ *
722
+ * @param {unknown} allegedCompartment
723
+ * @param {string} keypath
724
+ * @param {string} url
725
+ * @returns {asserts allegedCompartment is DigestedCompartmentDescriptor}
726
+ */
727
+ const assertDigestedCompartmentDescriptor = (
728
+ allegedCompartment,
729
+ keypath,
730
+ url,
731
+ ) => {
732
+ assertCompartmentDescriptor(
733
+ allegedCompartment,
734
+ keypath,
735
+ url,
736
+ assertDigestedModuleConfigurations,
352
737
  );
353
- assert.typeof(
354
- name,
355
- 'string',
356
- `${path}.name in ${q(url)} must be string, got ${q(name)}`,
738
+
739
+ const {
740
+ name: _name,
741
+ label: _label,
742
+ modules: _modules,
743
+ policy: _policy,
744
+ location: _location,
745
+ ...extra
746
+ } = allegedCompartment;
747
+
748
+ assertEmptyObject(
749
+ extra,
750
+ `${keypath} must not have extra properties; got ${q(
751
+ keys(extra),
752
+ )} in ${q(url)}`,
357
753
  );
358
- assert.typeof(
754
+ };
755
+
756
+ /**
757
+ * @param {unknown} allegedCompartment
758
+ * @param {string} keypath
759
+ * @param {string} url
760
+ * @returns {asserts allegedCompartment is FileCompartmentDescriptor}
761
+ */
762
+ const assertFileCompartmentDescriptor = (allegedCompartment, keypath, url) => {
763
+ assertCompartmentDescriptor(
764
+ allegedCompartment,
765
+ keypath,
766
+ url,
767
+ assertFileModuleConfigurations,
768
+ );
769
+
770
+ const {
771
+ location: _location,
772
+ name: _name,
359
773
  label,
360
- 'string',
361
- `${path}.label in ${q(url)} must be string, got ${q(label)}`,
774
+ modules: _modules,
775
+ policy: _policy,
776
+ ...extra
777
+ } = /** @type {FileCompartmentDescriptor} */ (allegedCompartment);
778
+
779
+ assertEmptyObject(
780
+ extra,
781
+ `${keypath} must not have extra properties; got ${q(
782
+ keys(extra),
783
+ )} in ${q(url)}`,
362
784
  );
363
785
 
364
- assertModules(modules, path, url);
365
- assertParsers(parsers, path, url);
366
- assertScopes(scopes, path, url);
367
- assertTypes(types, path, url);
368
- assertPolicy(policy, path, url);
786
+ assertString(label, `${keypath}.label`, url);
369
787
  };
370
788
 
371
789
  /**
372
790
  * @param {unknown} allegedCompartments
373
791
  * @param {string} url
792
+ * @returns {asserts allegedCompartments is Record<string, unknown>}
374
793
  */
375
- const assertCompartments = (allegedCompartments, url) => {
376
- const compartments = Object(allegedCompartments);
794
+ const assertCompartmentDescriptors = (allegedCompartments, url) => {
795
+ assertPlainObject(allegedCompartments, 'compartments', url);
796
+ const compartmentNames = keys(allegedCompartments);
377
797
  assert(
378
- allegedCompartments === compartments || !Array.isArray(compartments),
379
- `compartments must be an object, got ${q(allegedCompartments)} in ${q(
380
- url,
381
- )}`,
798
+ compartmentNames.length > 0,
799
+ `compartments must not be empty in ${q(url)}`,
382
800
  );
383
- for (const [key, value] of Object.entries(compartments)) {
384
- assertCompartment(value, `compartments[${q(key)}]`, url);
801
+ for (const key of keys(allegedCompartments)) {
802
+ assertString(
803
+ key,
804
+ `all keys of compartments must be strings; got ${key} in ${q(url)}`,
805
+ );
806
+ }
807
+ assert(
808
+ compartmentNames.every(name => typeof name === 'string'),
809
+ `all keys of compartments must be strings; got ${q(compartmentNames)} in ${q(url)}`,
810
+ );
811
+ };
812
+
813
+ /**
814
+ * @param {unknown} allegedCompartments
815
+ * @param {string} url
816
+ * @returns {asserts allegedCompartments is Record<string, FileCompartmentDescriptor>}
817
+ */
818
+ const assertFileCompartmentDescriptors = (allegedCompartments, url) => {
819
+ assertCompartmentDescriptors(allegedCompartments, url);
820
+ for (const [key, value] of entries(allegedCompartments)) {
821
+ assertFileCompartmentDescriptor(value, `compartments[${q(key)}]`, url);
385
822
  }
386
823
  };
387
824
 
825
+ /**
826
+ * @param {unknown} allegedCompartments
827
+ * @param {string} url
828
+ * @returns {asserts allegedCompartments is Record<string, PackageCompartmentDescriptor>}
829
+ */
830
+ const assertPackageCompartmentDescriptors = (allegedCompartments, url) => {
831
+ assertCompartmentDescriptors(allegedCompartments, url);
832
+ for (const [key, value] of entries(allegedCompartments)) {
833
+ assertPackageCompartmentDescriptor(value, `compartments[${q(key)}]`, url);
834
+ }
835
+ };
388
836
  /**
389
837
  * @param {unknown} allegedEntry
390
838
  * @param {string} url
839
+ * @returns {asserts allegedEntry is EntryDescriptor}
391
840
  */
392
841
  const assertEntry = (allegedEntry, url) => {
393
- const entry = Object(allegedEntry);
394
- assert(
395
- allegedEntry === entry && !Array.isArray(entry),
396
- `"entry" must be an object in compartment map, got ${allegedEntry} in ${q(
397
- url,
398
- )}`,
399
- );
400
- const { compartment, module, ...extra } = entry;
842
+ assertPlainObject(allegedEntry, 'entry', url);
843
+ const { compartment, module, ...extra } = allegedEntry;
401
844
  assertEmptyObject(
402
845
  extra,
403
- `"entry" must not have extra properties in compartment map, got ${q(
404
- Object.keys(extra),
846
+ `"entry" must not have extra properties in compartment map; got ${q(
847
+ keys(extra),
405
848
  )} in ${q(url)}`,
406
849
  );
407
- assert.typeof(
408
- compartment,
409
- 'string',
410
- `entry.compartment must be a string in compartment map, got ${compartment} in ${q(
411
- url,
412
- )}`,
413
- );
414
- assert.typeof(
415
- module,
416
- 'string',
417
- `entry.module must be a string in compartment map, got ${module} in ${q(
418
- url,
419
- )}`,
420
- );
850
+ assertString(compartment, 'entry.compartment', url);
851
+ assertString(module, 'entry.module', url);
421
852
  };
422
853
 
423
854
  /**
424
855
  * @param {unknown} allegedCompartmentMap
425
- * @param {string} [url]
856
+ * @param {string} url
426
857
  * @returns {asserts allegedCompartmentMap is CompartmentMapDescriptor}
427
858
  */
428
-
429
- export const assertCompartmentMap = (
430
- allegedCompartmentMap,
431
- url = '<unknown-compartment-map.json>',
432
- ) => {
433
- const compartmentMap = Object(allegedCompartmentMap);
434
- assert(
435
- allegedCompartmentMap === compartmentMap && !Array.isArray(compartmentMap),
436
- `Compartment map must be an object, got ${allegedCompartmentMap} in ${q(
437
- url,
438
- )}`,
439
- );
859
+ const assertCompartmentMap = (allegedCompartmentMap, url) => {
860
+ assertPlainObject(allegedCompartmentMap, 'compartment map', url);
440
861
  const {
441
862
  // TODO migrate tags to conditions
442
863
  // https://github.com/endojs/endo/issues/2388
443
864
  tags: conditions,
444
865
  entry,
445
- compartments,
866
+ compartments: _compartments,
446
867
  ...extra
447
- } = Object(compartmentMap);
868
+ } = allegedCompartmentMap;
448
869
  assertEmptyObject(
449
870
  extra,
450
- `Compartment map must not have extra properties, got ${q(
451
- Object.keys(extra),
871
+ `Compartment map must not have extra properties; got ${q(
872
+ keys(extra),
452
873
  )} in ${q(url)}`,
453
874
  );
454
875
  assertConditions(conditions, url);
455
876
  assertEntry(entry, url);
456
- assertCompartments(compartments, url);
877
+ assertTruthy(
878
+ allegedCompartmentMap.compartments?.[entry.compartment],
879
+ `compartments must contain entry compartment "${entry.compartment}" in ${q(url)}`,
880
+ );
881
+ };
882
+
883
+ /**
884
+ * @param {unknown} allegedCompartmentMap
885
+ * @param {string} [url]
886
+ * @returns {asserts allegedCompartmentMap is FileCompartmentMapDescriptor}
887
+ */
888
+ export const assertFileCompartmentMap = (
889
+ allegedCompartmentMap,
890
+ url = '<unknown-compartment-map.json>',
891
+ ) => {
892
+ assertCompartmentMap(allegedCompartmentMap, url);
893
+ const { compartments } = allegedCompartmentMap;
894
+ assertFileCompartmentDescriptors(compartments, url);
895
+ };
896
+
897
+ /**
898
+ *
899
+ * @param {unknown} allegedCompartments
900
+ * @param {string} url
901
+ * @returns {asserts allegedCompartments is Record<string, DigestedCompartmentDescriptor>}
902
+ */
903
+ export const assertDigestedCompartmentDescriptors = (
904
+ allegedCompartments,
905
+ url = '<unknown-compartment-map.json>',
906
+ ) => {
907
+ assertCompartmentDescriptors(allegedCompartments, url);
908
+ for (const [key, value] of entries(allegedCompartments)) {
909
+ assertDigestedCompartmentDescriptor(value, `compartments[${q(key)}]`, url);
910
+ }
911
+ };
912
+
913
+ /**
914
+ *
915
+ * @param {unknown} allegedCompartmentMap
916
+ * @param {string} [url]
917
+ * @returns {asserts allegedCompartmentMap is DigestedCompartmentMapDescriptor}
918
+ */
919
+ export const assertDigestedCompartmentMap = (
920
+ allegedCompartmentMap,
921
+ url = '<unknown-compartment-map.json>',
922
+ ) => {
923
+ assertCompartmentMap(allegedCompartmentMap, url);
924
+ const { compartments } = allegedCompartmentMap;
925
+ assertDigestedCompartmentDescriptors(compartments, url);
926
+ };
927
+
928
+ /**
929
+ * @param {unknown} allegedCompartmentMap
930
+ * @param {string} [url]
931
+ * @returns {asserts allegedCompartmentMap is PackageCompartmentMapDescriptor}
932
+ */
933
+ export const assertPackageCompartmentMap = (
934
+ allegedCompartmentMap,
935
+ url = '<unknown-compartment-map.json>',
936
+ ) => {
937
+ assertCompartmentMap(allegedCompartmentMap, url);
938
+ const { compartments } = allegedCompartmentMap;
939
+ assertPackageCompartmentDescriptors(compartments, url);
457
940
  };