@endo/compartment-mapper 1.6.3 → 2.1.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 (95) hide show
  1. package/package.json +24 -14
  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 +81 -30
  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 +243 -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 +738 -254
  19. package/src/digest.d.ts +22 -2
  20. package/src/digest.d.ts.map +1 -1
  21. package/src/digest.js +180 -57
  22. package/src/generic-graph.d.ts +7 -25
  23. package/src/generic-graph.d.ts.map +1 -1
  24. package/src/generic-graph.js +83 -108
  25. package/src/guards.d.ts +18 -0
  26. package/src/guards.d.ts.map +1 -0
  27. package/src/guards.js +109 -0
  28. package/src/hooks.md +124 -0
  29. package/src/import-archive-lite.d.ts.map +1 -1
  30. package/src/import-archive-lite.js +15 -11
  31. package/src/import-archive.d.ts +5 -19
  32. package/src/import-archive.d.ts.map +1 -1
  33. package/src/import-archive.js +7 -27
  34. package/src/import-hook.d.ts +4 -3
  35. package/src/import-hook.d.ts.map +1 -1
  36. package/src/import-hook.js +140 -70
  37. package/src/import-lite.d.ts +6 -6
  38. package/src/import-lite.d.ts.map +1 -1
  39. package/src/import-lite.js +8 -5
  40. package/src/import.d.ts +3 -3
  41. package/src/import.d.ts.map +1 -1
  42. package/src/import.js +16 -6
  43. package/src/infer-exports.d.ts +4 -2
  44. package/src/infer-exports.d.ts.map +1 -1
  45. package/src/infer-exports.js +172 -23
  46. package/src/link.d.ts +4 -3
  47. package/src/link.d.ts.map +1 -1
  48. package/src/link.js +122 -52
  49. package/src/node-modules.d.ts +4 -3
  50. package/src/node-modules.d.ts.map +1 -1
  51. package/src/node-modules.js +513 -151
  52. package/src/parse-cjs-shared-export-wrapper.d.ts.map +1 -1
  53. package/src/parse-cjs-shared-export-wrapper.js +3 -1
  54. package/src/pattern-replacement.d.ts +6 -0
  55. package/src/pattern-replacement.d.ts.map +1 -0
  56. package/src/pattern-replacement.js +198 -0
  57. package/src/policy-format.d.ts +22 -5
  58. package/src/policy-format.d.ts.map +1 -1
  59. package/src/policy-format.js +342 -108
  60. package/src/policy.d.ts +13 -28
  61. package/src/policy.d.ts.map +1 -1
  62. package/src/policy.js +161 -106
  63. package/src/types/canonical-name.d.ts +97 -0
  64. package/src/types/canonical-name.d.ts.map +1 -0
  65. package/src/types/canonical-name.ts +151 -0
  66. package/src/types/compartment-map-schema.d.ts +121 -35
  67. package/src/types/compartment-map-schema.d.ts.map +1 -1
  68. package/src/types/compartment-map-schema.ts +211 -37
  69. package/src/types/external.d.ts +240 -76
  70. package/src/types/external.d.ts.map +1 -1
  71. package/src/types/external.ts +305 -74
  72. package/src/types/generic-graph.d.ts +8 -2
  73. package/src/types/generic-graph.d.ts.map +1 -1
  74. package/src/types/generic-graph.ts +7 -2
  75. package/src/types/internal.d.ts +31 -50
  76. package/src/types/internal.d.ts.map +1 -1
  77. package/src/types/internal.ts +60 -58
  78. package/src/types/node-modules.d.ts +112 -14
  79. package/src/types/node-modules.d.ts.map +1 -1
  80. package/src/types/node-modules.ts +152 -13
  81. package/src/types/pattern-replacement.d.ts +62 -0
  82. package/src/types/pattern-replacement.d.ts.map +1 -0
  83. package/src/types/pattern-replacement.ts +70 -0
  84. package/src/types/policy-schema.d.ts +26 -11
  85. package/src/types/policy-schema.d.ts.map +1 -1
  86. package/src/types/policy-schema.ts +29 -16
  87. package/src/types/policy.d.ts +6 -2
  88. package/src/types/policy.d.ts.map +1 -1
  89. package/src/types/policy.ts +7 -2
  90. package/src/types/powers.d.ts +11 -9
  91. package/src/types/powers.d.ts.map +1 -1
  92. package/src/types/powers.ts +11 -10
  93. package/src/types/typescript.d.ts +28 -0
  94. package/src/types/typescript.d.ts.map +1 -1
  95. package/src/types/typescript.ts +37 -1
@@ -30,45 +30,68 @@
30
30
  */
31
31
 
32
32
  /* eslint no-shadow: 0 */
33
+ /* global globalThis */
33
34
 
34
35
  /**
35
36
  * @import {
36
37
  * CaptureLiteOptions,
37
38
  * CaptureResult,
38
- * CompartmentMapDescriptor,
39
+ * PackageCompartmentMapDescriptor,
40
+ * PreloadOption,
41
+ * MakeLoadCompartmentsOptions,
39
42
  * ReadFn,
40
43
  * ReadPowers,
41
44
  * Sources,
45
+ * CaptureCompartmentMapOptions,
42
46
  * } from './types.js'
43
47
  */
44
48
 
49
+ import { digestCompartmentMap } from './digest.js';
45
50
  import {
46
51
  exitModuleImportHookMaker,
47
52
  makeImportHookMaker,
48
53
  } from './import-hook.js';
49
54
  import { link } from './link.js';
50
55
  import { resolve } from './node-module-specifier.js';
56
+ import { ATTENUATORS_COMPARTMENT, ENTRY_COMPARTMENT } from './policy-format.js';
51
57
  import { detectAttenuators } from './policy.js';
52
58
  import { unpackReadPowers } from './powers.js';
53
- import { digestCompartmentMap } from './digest.js';
54
59
 
55
- const { freeze, assign, create } = Object;
60
+ const { freeze, assign, create, keys, entries } = Object;
61
+ const { quote: q } = assert;
62
+ const noop = () => {};
63
+
64
+ /**
65
+ * The name of the module to preload if no entry is provided in a {@link PreloadOption} array
66
+ */
67
+ const DEFAULT_PRELOAD_ENTRY = '.';
56
68
 
57
- const defaultCompartment = Compartment;
69
+ const DefaultCompartment = /** @type {typeof Compartment} */ (
70
+ // @ts-expect-error globalThis.Compartment is definitely on globalThis.
71
+ globalThis.Compartment
72
+ );
58
73
 
59
74
  /**
60
- * @param {CompartmentMapDescriptor} compartmentMap
75
+ * @param {PackageCompartmentMapDescriptor} compartmentMap
61
76
  * @param {Sources} sources
77
+ * @param {CaptureCompartmentMapOptions} options
62
78
  * @returns {CaptureResult}
63
79
  */
64
- const captureCompartmentMap = (compartmentMap, sources) => {
80
+ const captureCompartmentMap = (
81
+ compartmentMap,
82
+ sources,
83
+ { packageConnectionsHook, log = noop } = {},
84
+ ) => {
65
85
  const {
66
86
  compartmentMap: captureCompartmentMap,
67
87
  sources: captureSources,
68
88
  newToOldCompartmentNames,
69
89
  compartmentRenames,
70
90
  oldToNewCompartmentNames,
71
- } = digestCompartmentMap(compartmentMap, sources);
91
+ } = digestCompartmentMap(compartmentMap, sources, {
92
+ packageConnectionsHook,
93
+ log,
94
+ });
72
95
  return {
73
96
  captureCompartmentMap,
74
97
  captureSources,
@@ -79,12 +102,190 @@ const captureCompartmentMap = (compartmentMap, sources) => {
79
102
  };
80
103
 
81
104
  /**
82
- * @param {ReadFn | ReadPowers} powers
83
- * @param {CompartmentMapDescriptor} compartmentMap
105
+ * Factory for a function that loads compartments.
106
+ *
107
+ * @param {PackageCompartmentMapDescriptor} compartmentMap Compartment map
108
+ * @param {Sources} sources Sources
109
+ * @param {MakeLoadCompartmentsOptions} [options]
110
+ * @returns {(linkedCompartments: Record<string, Compartment>, entryCompartment: Compartment, attenuatorsCompartment: Compartment) => Promise<void>}
111
+ */
112
+ const makePreloader = (
113
+ compartmentMap,
114
+ sources,
115
+ {
116
+ log = noop,
117
+ policy,
118
+ _preload: preload = [],
119
+ _redundantPreloadHook: redundantPreloadHook = undefined,
120
+ } = {},
121
+ ) => {
122
+ const {
123
+ entry: { module: entryModuleSpecifier },
124
+ } = compartmentMap;
125
+
126
+ /**
127
+ * Iterates over canonical names in the {@link preload preload array}
128
+ * and loads those which have not yet been loaded.
129
+ *
130
+ * Will not load the "attenuators" `Compartment`, nor will it load any
131
+ * `Compartment` having a non-empty value in `sources` (since it is presumed
132
+ * it has already been loaded).
133
+ *
134
+ * @param {Record<string, Compartment>} compartments
135
+ * @returns {Promise<void>} Resolves when all appropriate compartments are
136
+ * loaded.
137
+ */
138
+ const preloader = async compartments => {
139
+ /** @type {[compartmentName: string, compartment: Compartment, moduleSpecifier: string][]} */
140
+ const compartmentsToLoad = [];
141
+
142
+ for (const preloadValue of preload) {
143
+ /** @type {string} */
144
+ let canonicalName;
145
+ /** @type {string} */
146
+ let entry;
147
+ if (typeof preloadValue === 'string') {
148
+ canonicalName = preloadValue;
149
+ entry = DEFAULT_PRELOAD_ENTRY;
150
+ } else {
151
+ canonicalName = preloadValue.compartment;
152
+ entry = preloadValue.entry;
153
+ }
154
+
155
+ // skip; should already be loaded
156
+ if (
157
+ canonicalName !== ATTENUATORS_COMPARTMENT &&
158
+ canonicalName !== ENTRY_COMPARTMENT
159
+ ) {
160
+ // TODO: A mapping of canonical name to compartment name is generated by
161
+ // mapNodeModules(), but it is not exposed. Expose it as an option on
162
+ // mapNodeModules() to be mutated and allow it as an option to
163
+ // captureFromMap() so we do not have to do this extra work. The data we
164
+ // need is _also_ generated by digestCompartmentMap() as the
165
+ // newToOldCompartmentNames property, but we cannot time-travel.
166
+ const [compartmentName, compartmentDescriptor] = entries(
167
+ compartmentMap.compartments,
168
+ ).find(([, compartment]) => compartment.label === canonicalName) ?? [
169
+ canonicalName,
170
+ compartmentMap.compartments[canonicalName],
171
+ ];
172
+
173
+ if (!compartmentDescriptor) {
174
+ throw new ReferenceError(
175
+ `Failed attempting to preload unknown compartment ${q(canonicalName)}`,
176
+ );
177
+ }
178
+
179
+ const compartmentSources = sources[compartmentName];
180
+
181
+ // The default preload entry is the entry module as defined by the
182
+ // package itself. This corresponds to the `ModuleConfiguration` of `.`
183
+ // in the `CompartmentDescriptor`'s `modules` object. Since the key in
184
+ // `compartmentSources` is presumably a resolved relative path, we don't
185
+ // actually know which key to look for! Thus, we are assuming that the
186
+ // `Compartment`'s entry module has been loaded if _any_ sources are
187
+ // present.
188
+ const entryIsLoaded =
189
+ entry === DEFAULT_PRELOAD_ENTRY
190
+ ? keys(compartmentSources).length > 0
191
+ : entry in compartmentSources;
192
+
193
+ if (entryIsLoaded) {
194
+ log(
195
+ `Refusing to preload Compartment ${q(canonicalName)} entry ${q(entry)}; already loaded`,
196
+ );
197
+ if (redundantPreloadHook) {
198
+ redundantPreloadHook({
199
+ canonicalName,
200
+ entry,
201
+ log,
202
+ });
203
+ }
204
+ } else {
205
+ const compartment = compartments[compartmentName];
206
+ if (!compartment) {
207
+ throw new ReferenceError(
208
+ `No compartment found for ${q(canonicalName)}`,
209
+ );
210
+ }
211
+
212
+ compartmentsToLoad.push([canonicalName, compartment, entry]);
213
+ }
214
+ }
215
+ }
216
+
217
+ const { length: compartmentsToLoadCount } = compartmentsToLoad;
218
+
219
+ /**
220
+ * This index increments in the order in which compartments finish
221
+ * loading—_not_ the order in which they began loading.
222
+ */
223
+ let loadedCompartmentIndex = 0;
224
+ await Promise.all(
225
+ compartmentsToLoad.map(
226
+ async ([compartmentName, compartment, moduleSpecifier]) => {
227
+ await compartment.load(moduleSpecifier);
228
+ loadedCompartmentIndex += 1;
229
+ log(
230
+ `Force-loaded Compartment: ${q(compartmentName)} (${loadedCompartmentIndex}/${compartmentsToLoadCount})`,
231
+ );
232
+ },
233
+ ),
234
+ );
235
+ };
236
+
237
+ /**
238
+ * Loads, in order:
239
+ *
240
+ * 1. The entry compartment
241
+ * 2. The attenuators compartment (_if and only if_ `policy` was provided)
242
+ * 3. All modules scheduled for preloading
243
+ *
244
+ * @param {Record<string, Compartment>} linkedCompartments
245
+ * @param {Compartment} entryCompartment
246
+ * @param {Compartment} attenuatorsCompartment
247
+ * @returns {Promise<void>} Resolves when all compartments are loaded.
248
+ */
249
+ const loadCompartments = async (
250
+ linkedCompartments,
251
+ entryCompartment,
252
+ attenuatorsCompartment,
253
+ ) => {
254
+ await entryCompartment.load(entryModuleSpecifier);
255
+
256
+ if (policy) {
257
+ // retain all attenuators.
258
+ await Promise.all(
259
+ detectAttenuators(policy).map(attenuatorSpecifier =>
260
+ attenuatorsCompartment.load(attenuatorSpecifier),
261
+ ),
262
+ );
263
+ }
264
+
265
+ await preloader(linkedCompartments);
266
+ };
267
+
268
+ return loadCompartments;
269
+ };
270
+
271
+ /**
272
+ * "Captures" the compartment map descriptors and sources from a partially
273
+ * completed compartment map—_without_ creating an archive.
274
+ *
275
+ * The resulting compartment map represents a well-formed dependency graph,
276
+ * laden with useful metadata. This, for example, could be used for automatic
277
+ * policy generation.
278
+ *
279
+ * @param {ReadFn | ReadPowers} readPowers Powers
280
+ * @param {PackageCompartmentMapDescriptor} compartmentMap
84
281
  * @param {CaptureLiteOptions} [options]
85
282
  * @returns {Promise<CaptureResult>}
86
283
  */
87
- export const captureFromMap = async (powers, compartmentMap, options = {}) => {
284
+ export const captureFromMap = async (
285
+ readPowers,
286
+ compartmentMap,
287
+ options = {},
288
+ ) => {
88
289
  const {
89
290
  moduleTransforms,
90
291
  syncModuleTransforms,
@@ -94,14 +295,18 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
94
295
  policy = undefined,
95
296
  sourceMapHook = undefined,
96
297
  parserForLanguage: parserForLanguageOption = {},
97
- Compartment = defaultCompartment,
298
+ Compartment: CompartmentOption = DefaultCompartment,
299
+ log = noop,
300
+ _preload: preload = [],
301
+ _redundantPreloadHook: redundantPreloadHook = undefined,
302
+ packageConnectionsHook,
303
+ moduleSourceHook,
98
304
  } = options;
99
-
100
305
  const parserForLanguage = freeze(
101
306
  assign(create(null), parserForLanguageOption),
102
307
  );
103
308
 
104
- const { read, computeSha512 } = unpackReadPowers(powers);
309
+ const { read, computeSha512 } = unpackReadPowers(readPowers);
105
310
 
106
311
  const {
107
312
  compartments,
@@ -111,6 +316,13 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
111
316
  /** @type {Sources} */
112
317
  const sources = Object.create(null);
113
318
 
319
+ const loadCompartments = makePreloader(compartmentMap, sources, {
320
+ log,
321
+ policy,
322
+ _preload: preload,
323
+ _redundantPreloadHook: redundantPreloadHook,
324
+ });
325
+
114
326
  const consolidatedExitModuleImportHook = exitModuleImportHookMaker({
115
327
  modules: exitModules,
116
328
  exitModuleImportHook,
@@ -127,26 +339,32 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
127
339
  entryModuleSpecifier,
128
340
  importHook: consolidatedExitModuleImportHook,
129
341
  sourceMapHook,
342
+ moduleSourceHook,
130
343
  });
344
+
131
345
  // Induce importHook to record all the necessary modules to import the given module specifier.
132
- const { compartment, attenuatorsCompartment } = link(compartmentMap, {
346
+ const {
347
+ compartment: entryCompartment,
348
+ compartments: linkedCompartments,
349
+ attenuatorsCompartment,
350
+ } = link(compartmentMap, {
133
351
  resolve,
134
352
  makeImportHook,
135
353
  moduleTransforms,
136
354
  syncModuleTransforms,
137
355
  parserForLanguage,
138
356
  archiveOnly: true,
139
- Compartment,
357
+ Compartment: CompartmentOption,
140
358
  });
141
- await compartment.load(entryModuleSpecifier);
142
- if (policy) {
143
- // retain all attenuators.
144
- await Promise.all(
145
- detectAttenuators(policy).map(attenuatorSpecifier =>
146
- attenuatorsCompartment.load(attenuatorSpecifier),
147
- ),
148
- );
149
- }
150
359
 
151
- return captureCompartmentMap(compartmentMap, sources);
360
+ await loadCompartments(
361
+ linkedCompartments,
362
+ entryCompartment,
363
+ attenuatorsCompartment,
364
+ );
365
+
366
+ return captureCompartmentMap(compartmentMap, sources, {
367
+ packageConnectionsHook,
368
+ log,
369
+ });
152
370
  };
@@ -1,5 +1,12 @@
1
1
  /** @type {(a: string, b: string) => number} */
2
2
  export const stringCompare: (a: string, b: string) => number;
3
- export function assertCompartmentMap(allegedCompartmentMap: unknown, url?: string): asserts allegedCompartmentMap is CompartmentMapDescriptor;
4
- import type { CompartmentMapDescriptor } from './types.js';
3
+ export function assertFileCompartmentMap(allegedCompartmentMap: unknown, url?: string): asserts allegedCompartmentMap is FileCompartmentMapDescriptor;
4
+ export function assertDigestedCompartmentDescriptors(allegedCompartments: unknown, url?: string): asserts allegedCompartments is Record<string, DigestedCompartmentDescriptor>;
5
+ export function assertDigestedCompartmentMap(allegedCompartmentMap: unknown, url?: string): asserts allegedCompartmentMap is DigestedCompartmentMapDescriptor;
6
+ export function assertPackageCompartmentMap(allegedCompartmentMap: unknown, url?: string): asserts allegedCompartmentMap is PackageCompartmentMapDescriptor;
7
+ export type AssertFn<T = string> = (value: unknown, keypath: string, url: string) => void;
8
+ import type { FileCompartmentMapDescriptor } from './types.js';
9
+ import type { DigestedCompartmentDescriptor } from './types.js';
10
+ import type { DigestedCompartmentMapDescriptor } from './types.js';
11
+ import type { PackageCompartmentMapDescriptor } from './types.js';
5
12
  //# sourceMappingURL=compartment-map.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compartment-map.d.ts","sourceRoot":"","sources":["compartment-map.js"],"names":[],"mappings":"AAYA,+CAA+C;AAE/C,4BAFW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,CAE2B;AA8Z/D,4DALI,OAAO,QACP,MAAM,GACJ,QAAQ,qBAAqB,IAAI,wBAAwB,CA+BrE;8CApc2C,YAAY"}
1
+ {"version":3,"file":"compartment-map.d.ts","sourceRoot":"","sources":["compartment-map.js"],"names":[],"mappings":"AAyCA,+CAA+C;AAE/C,4BAFW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,CAE2B;AA60B/D,gEAJI,OAAO,QACP,MAAM,GACJ,QAAQ,qBAAqB,IAAI,4BAA4B,CASzE;AAQM,0EAJI,OAAO,QACP,MAAM,GACJ,QAAQ,mBAAmB,IAAI,MAAM,CAAC,MAAM,EAAE,6BAA6B,CAAC,CAUxF;AAQM,oEAJI,OAAO,QACP,MAAM,GACJ,QAAQ,qBAAqB,IAAI,gCAAgC,CAS7E;AAOM,mEAJI,OAAO,QACP,MAAM,GACJ,QAAQ,qBAAqB,IAAI,+BAA+B,CAS5E;qBApca,CAAC,aACF,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI;kDA3c1B,YAAY;mDAAZ,YAAY;sDAAZ,YAAY;qDAAZ,YAAY"}