@endo/compartment-mapper 1.6.2 → 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 (94) 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 +84 -0
  23. package/src/generic-graph.d.ts.map +1 -0
  24. package/src/generic-graph.js +356 -0
  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 +156 -71
  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.map +1 -1
  44. package/src/infer-exports.js +16 -6
  45. package/src/json.d.ts +1 -1
  46. package/src/json.d.ts.map +1 -1
  47. package/src/json.js +10 -3
  48. package/src/link.d.ts +4 -3
  49. package/src/link.d.ts.map +1 -1
  50. package/src/link.js +70 -58
  51. package/src/node-modules.d.ts +5 -3
  52. package/src/node-modules.d.ts.map +1 -1
  53. package/src/node-modules.js +648 -245
  54. package/src/node-powers.d.ts +6 -5
  55. package/src/node-powers.d.ts.map +1 -1
  56. package/src/node-powers.js +11 -8
  57. package/src/parse-cjs-shared-export-wrapper.d.ts.map +1 -1
  58. package/src/parse-cjs-shared-export-wrapper.js +3 -1
  59. package/src/policy-format.d.ts +22 -5
  60. package/src/policy-format.d.ts.map +1 -1
  61. package/src/policy-format.js +342 -108
  62. package/src/policy.d.ts +13 -28
  63. package/src/policy.d.ts.map +1 -1
  64. package/src/policy.js +161 -106
  65. package/src/types/canonical-name.d.ts +97 -0
  66. package/src/types/canonical-name.d.ts.map +1 -0
  67. package/src/types/canonical-name.ts +151 -0
  68. package/src/types/compartment-map-schema.d.ts +114 -35
  69. package/src/types/compartment-map-schema.d.ts.map +1 -1
  70. package/src/types/compartment-map-schema.ts +202 -37
  71. package/src/types/external.d.ts +173 -29
  72. package/src/types/external.d.ts.map +1 -1
  73. package/src/types/external.ts +221 -27
  74. package/src/types/generic-graph.d.ts +17 -0
  75. package/src/types/generic-graph.d.ts.map +1 -0
  76. package/src/types/generic-graph.ts +17 -0
  77. package/src/types/internal.d.ts +24 -42
  78. package/src/types/internal.d.ts.map +1 -1
  79. package/src/types/internal.ts +52 -50
  80. package/src/types/node-modules.d.ts +101 -17
  81. package/src/types/node-modules.d.ts.map +1 -1
  82. package/src/types/node-modules.ts +142 -17
  83. package/src/types/policy-schema.d.ts +26 -11
  84. package/src/types/policy-schema.d.ts.map +1 -1
  85. package/src/types/policy-schema.ts +29 -16
  86. package/src/types/policy.d.ts +6 -2
  87. package/src/types/policy.d.ts.map +1 -1
  88. package/src/types/policy.ts +7 -2
  89. package/src/types/powers.d.ts +38 -11
  90. package/src/types/powers.d.ts.map +1 -1
  91. package/src/types/powers.ts +50 -17
  92. package/src/types/typescript.d.ts +28 -0
  93. package/src/types/typescript.d.ts.map +1 -1
  94. 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,166 @@ 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
+ { log = noop, policy, _preload: preload = [] } = {},
116
+ ) => {
117
+ const {
118
+ entry: { module: entryModuleSpecifier },
119
+ } = compartmentMap;
120
+
121
+ /**
122
+ * Iterates over canonical names in the {@link preload preload array}
123
+ * and loads those which have not yet been loaded.
124
+ *
125
+ * Will not load the "attenuators" `Compartment`, nor will it load any
126
+ * `Compartment` having a non-empty value in `sources` (since it is presumed
127
+ * it has already been loaded).
128
+ *
129
+ * @param {Record<string, Compartment>} compartments
130
+ * @returns {Promise<void>} Resolves when all appropriate compartments are
131
+ * loaded.
132
+ */
133
+ const preloader = async compartments => {
134
+ /** @type {[compartmentName: string, compartment: Compartment, moduleSpecifier: string][]} */
135
+ const compartmentsToLoad = [];
136
+
137
+ for (const preloadValue of preload) {
138
+ /** @type {string} */
139
+ let canonicalName;
140
+ /** @type {string} */
141
+ let entry;
142
+ if (typeof preloadValue === 'string') {
143
+ canonicalName = preloadValue;
144
+ entry = DEFAULT_PRELOAD_ENTRY;
145
+ } else {
146
+ canonicalName = preloadValue.compartment;
147
+ entry = preloadValue.entry;
148
+ }
149
+
150
+ // skip; should already be loaded
151
+ if (
152
+ canonicalName !== ATTENUATORS_COMPARTMENT &&
153
+ canonicalName !== ENTRY_COMPARTMENT
154
+ ) {
155
+ // TODO: A mapping of canonical name to compartment name is generated by
156
+ // mapNodeModules(), but it is not exposed. Expose it as an option on
157
+ // mapNodeModules() to be mutated and allow it as an option to
158
+ // captureFromMap() so we do not have to do this extra work. The data we
159
+ // need is _also_ generated by digestCompartmentMap() as the
160
+ // newToOldCompartmentNames property, but we cannot time-travel.
161
+ const [compartmentName, compartmentDescriptor] = entries(
162
+ compartmentMap.compartments,
163
+ ).find(([, compartment]) => compartment.label === canonicalName) ?? [
164
+ canonicalName,
165
+ compartmentMap.compartments[canonicalName],
166
+ ];
167
+
168
+ if (!compartmentDescriptor) {
169
+ throw new ReferenceError(
170
+ `Failed attempting to preload unknown compartment ${q(canonicalName)}`,
171
+ );
172
+ }
173
+
174
+ const compartmentSources = sources[compartmentName];
175
+
176
+ if (keys(compartmentSources).length) {
177
+ log(
178
+ `Refusing to preload Compartment ${q(canonicalName)}; already loaded`,
179
+ );
180
+ } else {
181
+ const compartment = compartments[compartmentName];
182
+ if (!compartment) {
183
+ throw new ReferenceError(
184
+ `No compartment found for ${q(canonicalName)}`,
185
+ );
186
+ }
187
+
188
+ compartmentsToLoad.push([canonicalName, compartment, entry]);
189
+ }
190
+ }
191
+ }
192
+
193
+ const { length: compartmentsToLoadCount } = compartmentsToLoad;
194
+
195
+ /**
196
+ * This index increments in the order in which compartments finish
197
+ * loading—_not_ the order in which they began loading.
198
+ */
199
+ let loadedCompartmentIndex = 0;
200
+ await Promise.all(
201
+ compartmentsToLoad.map(
202
+ async ([compartmentName, compartment, moduleSpecifier]) => {
203
+ await compartment.load(moduleSpecifier);
204
+ loadedCompartmentIndex += 1;
205
+ log(
206
+ `Force-loaded Compartment: ${q(compartmentName)} (${loadedCompartmentIndex}/${compartmentsToLoadCount})`,
207
+ );
208
+ },
209
+ ),
210
+ );
211
+ };
212
+
213
+ /**
214
+ * Loads, in order:
215
+ *
216
+ * 1. The entry compartment
217
+ * 2. The attenuators compartment (_if and only if_ `policy` was provided)
218
+ * 3. All modules scheduled for preloading
219
+ *
220
+ * @param {Record<string, Compartment>} linkedCompartments
221
+ * @param {Compartment} entryCompartment
222
+ * @param {Compartment} attenuatorsCompartment
223
+ * @returns {Promise<void>} Resolves when all compartments are loaded.
224
+ */
225
+ const loadCompartments = async (
226
+ linkedCompartments,
227
+ entryCompartment,
228
+ attenuatorsCompartment,
229
+ ) => {
230
+ await entryCompartment.load(entryModuleSpecifier);
231
+
232
+ if (policy) {
233
+ // retain all attenuators.
234
+ await Promise.all(
235
+ detectAttenuators(policy).map(attenuatorSpecifier =>
236
+ attenuatorsCompartment.load(attenuatorSpecifier),
237
+ ),
238
+ );
239
+ }
240
+
241
+ await preloader(linkedCompartments);
242
+ };
243
+
244
+ return loadCompartments;
245
+ };
246
+
247
+ /**
248
+ * "Captures" the compartment map descriptors and sources from a partially
249
+ * completed compartment map—_without_ creating an archive.
250
+ *
251
+ * The resulting compartment map represents a well-formed dependency graph,
252
+ * laden with useful metadata. This, for example, could be used for automatic
253
+ * policy generation.
254
+ *
255
+ * @param {ReadFn | ReadPowers} readPowers Powers
256
+ * @param {PackageCompartmentMapDescriptor} compartmentMap
84
257
  * @param {CaptureLiteOptions} [options]
85
258
  * @returns {Promise<CaptureResult>}
86
259
  */
87
- export const captureFromMap = async (powers, compartmentMap, options = {}) => {
260
+ export const captureFromMap = async (
261
+ readPowers,
262
+ compartmentMap,
263
+ options = {},
264
+ ) => {
88
265
  const {
89
266
  moduleTransforms,
90
267
  syncModuleTransforms,
@@ -94,14 +271,17 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
94
271
  policy = undefined,
95
272
  sourceMapHook = undefined,
96
273
  parserForLanguage: parserForLanguageOption = {},
97
- Compartment = defaultCompartment,
274
+ Compartment: CompartmentOption = DefaultCompartment,
275
+ log = noop,
276
+ _preload: preload = [],
277
+ packageConnectionsHook,
278
+ moduleSourceHook,
98
279
  } = options;
99
-
100
280
  const parserForLanguage = freeze(
101
281
  assign(create(null), parserForLanguageOption),
102
282
  );
103
283
 
104
- const { read, computeSha512 } = unpackReadPowers(powers);
284
+ const { read, computeSha512 } = unpackReadPowers(readPowers);
105
285
 
106
286
  const {
107
287
  compartments,
@@ -111,6 +291,12 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
111
291
  /** @type {Sources} */
112
292
  const sources = Object.create(null);
113
293
 
294
+ const loadCompartments = makePreloader(compartmentMap, sources, {
295
+ log,
296
+ policy,
297
+ _preload: preload,
298
+ });
299
+
114
300
  const consolidatedExitModuleImportHook = exitModuleImportHookMaker({
115
301
  modules: exitModules,
116
302
  exitModuleImportHook,
@@ -127,26 +313,32 @@ export const captureFromMap = async (powers, compartmentMap, options = {}) => {
127
313
  entryModuleSpecifier,
128
314
  importHook: consolidatedExitModuleImportHook,
129
315
  sourceMapHook,
316
+ moduleSourceHook,
130
317
  });
318
+
131
319
  // Induce importHook to record all the necessary modules to import the given module specifier.
132
- const { compartment, attenuatorsCompartment } = link(compartmentMap, {
320
+ const {
321
+ compartment: entryCompartment,
322
+ compartments: linkedCompartments,
323
+ attenuatorsCompartment,
324
+ } = link(compartmentMap, {
133
325
  resolve,
134
326
  makeImportHook,
135
327
  moduleTransforms,
136
328
  syncModuleTransforms,
137
329
  parserForLanguage,
138
330
  archiveOnly: true,
139
- Compartment,
331
+ Compartment: CompartmentOption,
140
332
  });
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
333
 
151
- return captureCompartmentMap(compartmentMap, sources);
334
+ await loadCompartments(
335
+ linkedCompartments,
336
+ entryCompartment,
337
+ attenuatorsCompartment,
338
+ );
339
+
340
+ return captureCompartmentMap(compartmentMap, sources, {
341
+ packageConnectionsHook,
342
+ log,
343
+ });
152
344
  };
@@ -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;AA40B/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;kDA1c1B,YAAY;mDAAZ,YAAY;sDAAZ,YAAY;qDAAZ,YAAY"}