@endo/compartment-mapper 1.2.2 → 1.3.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 (67) hide show
  1. package/archive-lite.d.ts.map +1 -0
  2. package/archive-parsers.d.ts.map +1 -0
  3. package/archive.d.ts.map +1 -0
  4. package/bundle.d.ts.map +1 -0
  5. package/capture-lite.d.ts.map +1 -0
  6. package/import-archive-lite.d.ts.map +1 -0
  7. package/import-archive-parsers.d.ts.map +1 -0
  8. package/import-archive.d.ts.map +1 -0
  9. package/import-lite.d.ts.map +1 -0
  10. package/import-parsers.d.ts.map +1 -0
  11. package/import.d.ts.map +1 -0
  12. package/index.d.ts.map +1 -0
  13. package/node-modules.d.ts.map +1 -0
  14. package/node-powers.d.ts +1 -1
  15. package/node-powers.d.ts.map +1 -0
  16. package/node-powers.js +5 -1
  17. package/package.json +15 -11
  18. package/src/compartment-map.d.ts +1 -1
  19. package/src/compartment-map.d.ts.map +1 -1
  20. package/src/compartment-map.js +1 -1
  21. package/src/import-hook.d.ts +14 -1
  22. package/src/import-hook.d.ts.map +1 -1
  23. package/src/import-hook.js +493 -144
  24. package/src/import-lite.d.ts +20 -3
  25. package/src/import-lite.d.ts.map +1 -1
  26. package/src/import-lite.js +137 -15
  27. package/src/import.d.ts +45 -5
  28. package/src/import.d.ts.map +1 -1
  29. package/src/import.js +52 -6
  30. package/src/link.d.ts +2 -11
  31. package/src/link.d.ts.map +1 -1
  32. package/src/link.js +76 -154
  33. package/src/map-parser.d.ts +4 -0
  34. package/src/map-parser.d.ts.map +1 -0
  35. package/src/map-parser.js +339 -0
  36. package/src/node-modules.d.ts +2 -5
  37. package/src/node-modules.d.ts.map +1 -1
  38. package/src/node-modules.js +12 -5
  39. package/src/node-powers.d.ts +29 -23
  40. package/src/node-powers.d.ts.map +1 -1
  41. package/src/node-powers.js +102 -25
  42. package/src/parse-archive-cjs.js +2 -1
  43. package/src/parse-archive-mjs.js +2 -1
  44. package/src/parse-bytes.d.ts.map +1 -1
  45. package/src/parse-bytes.js +2 -6
  46. package/src/parse-cjs-shared-export-wrapper.d.ts.map +1 -1
  47. package/src/parse-cjs-shared-export-wrapper.js +23 -6
  48. package/src/parse-cjs.js +3 -2
  49. package/src/parse-json.d.ts +5 -3
  50. package/src/parse-json.d.ts.map +1 -1
  51. package/src/parse-json.js +9 -9
  52. package/src/parse-mjs.js +2 -1
  53. package/src/parse-pre-cjs.js +3 -2
  54. package/src/parse-pre-mjs.js +2 -1
  55. package/src/parse-text.d.ts.map +1 -1
  56. package/src/parse-text.js +2 -6
  57. package/src/policy.d.ts +21 -14
  58. package/src/policy.d.ts.map +1 -1
  59. package/src/policy.js +53 -43
  60. package/src/powers.d.ts +8 -1
  61. package/src/powers.d.ts.map +1 -1
  62. package/src/powers.js +60 -10
  63. package/src/search.d.ts.map +1 -1
  64. package/src/search.js +1 -2
  65. package/src/types.d.ts +343 -21
  66. package/src/types.d.ts.map +1 -1
  67. package/src/types.js +369 -22
@@ -7,20 +7,41 @@
7
7
  */
8
8
 
9
9
  // @ts-check
10
+ /**
11
+ * @import {
12
+ * ImportHook,
13
+ * ImportNowHook,
14
+ * RedirectStaticModuleInterface,
15
+ * StaticModuleType
16
+ * } from 'ses'
17
+ * @import {
18
+ * CompartmentDescriptor,
19
+ * ChooseModuleDescriptorOperators,
20
+ * ChooseModuleDescriptorOptions,
21
+ * ChooseModuleDescriptorYieldables,
22
+ * ExitModuleImportHook,
23
+ * FindRedirectParams,
24
+ * HashFn,
25
+ * ImportHookMaker,
26
+ * ImportNowHookMaker,
27
+ * MakeImportNowHookMakerOptions,
28
+ * ModuleDescriptor,
29
+ * ParseResult,
30
+ * ReadFn,
31
+ * ReadPowers,
32
+ * SourceMapHook,
33
+ * Sources,
34
+ * ReadNowPowers
35
+ * } from './types.js'
36
+ */
10
37
 
11
- /** @import {ImportHook} from 'ses' */
12
- /** @import {StaticModuleType} from 'ses' */
13
- /** @import {RedirectStaticModuleInterface} from 'ses' */
14
- /** @import {ReadFn} from './types.js' */
15
- /** @import {ReadPowers} from './types.js' */
16
- /** @import {HashFn} from './types.js' */
17
- /** @import {Sources} from './types.js' */
18
- /** @import {CompartmentDescriptor} from './types.js' */
19
- /** @import {ImportHookMaker} from './types.js' */
20
- /** @import {ExitModuleImportHook} from './types.js' */
21
-
22
- import { attenuateModuleHook, enforceModulePolicy } from './policy.js';
38
+ import { asyncTrampoline, syncTrampoline } from '@endo/trampoline';
23
39
  import { resolve } from './node-module-specifier.js';
40
+ import {
41
+ attenuateModuleHook,
42
+ ATTENUATORS_COMPARTMENT,
43
+ enforceModulePolicy,
44
+ } from './policy.js';
24
45
  import { unpackReadPowers } from './powers.js';
25
46
 
26
47
  // q, as in quote, for quoting strings in error messages.
@@ -36,6 +57,8 @@ const { apply } = Reflect;
36
57
  */
37
58
  const freeze = Object.freeze;
38
59
 
60
+ const { entries, keys, assign, create } = Object;
61
+
39
62
  const { hasOwnProperty } = Object.prototype;
40
63
  /**
41
64
  * @param {Record<string, any>} haystack
@@ -68,10 +91,97 @@ const nodejsConventionSearchSuffixes = [
68
91
  '/index.node',
69
92
  ];
70
93
 
94
+ /**
95
+ * Given a module specifier which is an absolute path, attempt to match it with
96
+ * an existing compartment; return a {@link RedirectStaticModuleInterface} if found.
97
+ *
98
+ * @throws If we determine `absoluteModuleSpecifier` is unknown
99
+ * @param {FindRedirectParams} params Parameters
100
+ * @returns {RedirectStaticModuleInterface|undefined} A redirect or nothing
101
+ */
102
+ const findRedirect = ({
103
+ compartmentDescriptor,
104
+ compartmentDescriptors,
105
+ compartments,
106
+ absoluteModuleSpecifier,
107
+ packageLocation,
108
+ }) => {
109
+ const moduleSpecifierLocation = new URL(
110
+ absoluteModuleSpecifier,
111
+ packageLocation,
112
+ ).href;
113
+
114
+ // a file:// URL string
115
+ let someLocation = new URL('./', moduleSpecifierLocation).href;
116
+
117
+ // we are guaranteed an absolute path, so we can search "up" for the compartment
118
+ // due to the structure of `node_modules`
119
+
120
+ // n === count of path components to the fs root
121
+ for (;;) {
122
+ if (
123
+ someLocation !== ATTENUATORS_COMPARTMENT &&
124
+ someLocation in compartments
125
+ ) {
126
+ const location = someLocation;
127
+ const someCompartmentDescriptor = compartmentDescriptors[location];
128
+ if (compartmentDescriptor === someCompartmentDescriptor) {
129
+ // this compartmentDescriptor wants to dynamically load its own module
130
+ // using an absolute path
131
+ return undefined;
132
+ }
133
+
134
+ // this tests the compartment referred to by the absolute path
135
+ // is a dependency of the compartment descriptor
136
+ if (compartmentDescriptor.compartments.has(location)) {
137
+ return {
138
+ specifier: absoluteModuleSpecifier,
139
+ compartment: compartments[location],
140
+ };
141
+ }
142
+
143
+ // this tests if the compartment descriptor is a dependency of the
144
+ // compartment referred to by the absolute path.
145
+ // it may be in scope, but disallowed by policy.
146
+ if (
147
+ someCompartmentDescriptor.compartments.has(
148
+ compartmentDescriptor.location,
149
+ )
150
+ ) {
151
+ enforceModulePolicy(
152
+ compartmentDescriptor.name,
153
+ someCompartmentDescriptor,
154
+ {
155
+ errorHint: `Blocked in import hook. ${q(absoluteModuleSpecifier)} is part of the compartment map and resolves to ${location}`,
156
+ },
157
+ );
158
+ return {
159
+ specifier: absoluteModuleSpecifier,
160
+ compartment: compartments[location],
161
+ };
162
+ }
163
+
164
+ throw new Error(`Could not import module: ${q(absoluteModuleSpecifier)}`);
165
+ } else {
166
+ // go up a directory
167
+ const parentLocation = new URL('../', someLocation).href;
168
+
169
+ // afaict this behavior is consistent across both windows and posix
170
+ if (parentLocation === someLocation) {
171
+ throw new Error(
172
+ `Could not import unknown module: ${q(absoluteModuleSpecifier)}`,
173
+ );
174
+ }
175
+
176
+ someLocation = parentLocation;
177
+ }
178
+ }
179
+ };
180
+
71
181
  /**
72
182
  * @param {object} params
73
183
  * @param {Record<string, any>=} params.modules
74
- * @param {ExitModuleImportHook=} params.exitModuleImportHook
184
+ * @param {ExitModuleImportHook} [params.exitModuleImportHook]
75
185
  * @returns {ExitModuleImportHook|undefined}
76
186
  */
77
187
  export const exitModuleImportHookMaker = ({
@@ -84,12 +194,12 @@ export const exitModuleImportHookMaker = ({
84
194
  return async specifier => {
85
195
  if (modules && has(modules, specifier)) {
86
196
  const ns = modules[specifier];
87
- return Object.freeze({
197
+ return freeze({
88
198
  imports: [],
89
- exports: ns ? Object.keys(ns) : [],
199
+ exports: ns ? keys(ns) : [],
90
200
  execute: moduleExports => {
91
201
  moduleExports.default = ns;
92
- Object.assign(moduleExports, ns);
202
+ assign(moduleExports, ns);
93
203
  },
94
204
  });
95
205
  }
@@ -100,6 +210,185 @@ export const exitModuleImportHookMaker = ({
100
210
  };
101
211
  };
102
212
 
213
+ /**
214
+ * Expands a module specifier into a list of potential candidates based on
215
+ * `searchSuffixes`.
216
+ *
217
+ * @param {string} moduleSpecifier Module specifier
218
+ * @param {string[]} searchSuffixes Suffixes to search if the unmodified
219
+ * specifier is not found
220
+ * @returns {string[]} A list of potential candidates (including
221
+ * `moduleSpecifier` itself)
222
+ */
223
+ const nominateCandidates = (moduleSpecifier, searchSuffixes) => {
224
+ // Collate candidate locations for the moduleSpecifier,
225
+ // to support Node.js conventions and similar.
226
+ const candidates = [moduleSpecifier];
227
+ for (const candidateSuffix of searchSuffixes) {
228
+ candidates.push(`${moduleSpecifier}${candidateSuffix}`);
229
+ }
230
+ return candidates;
231
+ };
232
+
233
+ /**
234
+ * Returns a generator which applies {@link ChooseModuleDescriptorOperators} in
235
+ * `operators` using the options in options to ultimately result in a
236
+ * {@link StaticModuleType} for a particular {@link CompartmentDescriptor} (or
237
+ * `undefined`).
238
+ *
239
+ * Supports both {@link SyncChooseModuleDescriptorOperators sync} and
240
+ * {@link AsyncChooseModuleDescriptorOperators async} operators.
241
+ *
242
+ * Used by both {@link makeImportNowHookMaker} and {@link makeImportHookMaker}.
243
+ *
244
+ * @template {ChooseModuleDescriptorOperators} Operators Type of operators (sync
245
+ * or async)
246
+ * @param {ChooseModuleDescriptorOptions} options Options/context
247
+ * @param {Operators} operators Operators
248
+ * @returns {Generator<ChooseModuleDescriptorYieldables,
249
+ * StaticModuleType|undefined, Awaited<ChooseModuleDescriptorYieldables>>}
250
+ * Generator
251
+ */
252
+ function* chooseModuleDescriptor(
253
+ {
254
+ candidates,
255
+ compartmentDescriptor,
256
+ compartmentDescriptors,
257
+ compartments,
258
+ computeSha512,
259
+ moduleDescriptors,
260
+ moduleSpecifier,
261
+ packageLocation,
262
+ packageSources,
263
+ readPowers,
264
+ sourceMapHook,
265
+ strictlyRequiredForCompartment,
266
+ },
267
+ { maybeRead, parse, shouldDeferError = () => false },
268
+ ) {
269
+ for (const candidateSpecifier of candidates) {
270
+ const candidateModuleDescriptor = moduleDescriptors[candidateSpecifier];
271
+ if (candidateModuleDescriptor !== undefined) {
272
+ const { compartment: candidateCompartmentName = packageLocation } =
273
+ candidateModuleDescriptor;
274
+ const candidateCompartment = compartments[candidateCompartmentName];
275
+ if (candidateCompartment === undefined) {
276
+ throw Error(
277
+ `compartment missing for candidate ${candidateSpecifier} in ${candidateCompartmentName}`,
278
+ );
279
+ }
280
+ // modify compartmentMap to include this redirect
281
+ const candidateCompartmentDescriptor =
282
+ compartmentDescriptors[candidateCompartmentName];
283
+ if (candidateCompartmentDescriptor === undefined) {
284
+ throw Error(
285
+ `compartmentDescriptor missing for candidate ${candidateSpecifier} in ${candidateCompartmentName}`,
286
+ );
287
+ }
288
+ candidateCompartmentDescriptor.modules[moduleSpecifier] =
289
+ candidateModuleDescriptor;
290
+ // return a redirect
291
+ /** @type {RedirectStaticModuleInterface} */
292
+ const record = {
293
+ specifier: candidateSpecifier,
294
+ compartment: candidateCompartment,
295
+ };
296
+ return record;
297
+ }
298
+
299
+ // Using a specifier as a location.
300
+ // This is not always valid.
301
+ // But, for Node.js, when the specifier is relative and not a directory
302
+ // name, they are usable as URL's.
303
+ const moduleLocation = resolveLocation(candidateSpecifier, packageLocation);
304
+
305
+ // "next" values must have type assertions for narrowing because we have
306
+ // multiple yielded types
307
+ const moduleBytes = /** @type {Uint8Array|undefined} */ (
308
+ yield maybeRead(moduleLocation)
309
+ );
310
+
311
+ if (moduleBytes !== undefined) {
312
+ /** @type {string | undefined} */
313
+ let sourceMap;
314
+ // must be narrowed
315
+ const envelope = /** @type {ParseResult} */ (
316
+ yield parse(
317
+ moduleBytes,
318
+ candidateSpecifier,
319
+ moduleLocation,
320
+ packageLocation,
321
+ {
322
+ readPowers,
323
+ sourceMapHook:
324
+ sourceMapHook &&
325
+ (nextSourceMapObject => {
326
+ sourceMap = JSON.stringify(nextSourceMapObject);
327
+ }),
328
+ compartmentDescriptor,
329
+ },
330
+ )
331
+ );
332
+ const {
333
+ parser,
334
+ bytes: transformedBytes,
335
+ record: concreteRecord,
336
+ } = envelope;
337
+
338
+ // Facilitate a redirect if the returned record has a different
339
+ // module specifier than the requested one.
340
+ if (candidateSpecifier !== moduleSpecifier) {
341
+ moduleDescriptors[moduleSpecifier] = {
342
+ module: candidateSpecifier,
343
+ compartment: packageLocation,
344
+ };
345
+ }
346
+ /** @type {StaticModuleType} */
347
+ const record = {
348
+ record: concreteRecord,
349
+ specifier: candidateSpecifier,
350
+ importMeta: { url: moduleLocation },
351
+ };
352
+
353
+ let sha512;
354
+ if (computeSha512 !== undefined) {
355
+ sha512 = computeSha512(transformedBytes);
356
+
357
+ if (sourceMapHook !== undefined && sourceMap !== undefined) {
358
+ sourceMapHook(sourceMap, {
359
+ compartment: packageLocation,
360
+ module: candidateSpecifier,
361
+ location: moduleLocation,
362
+ sha512,
363
+ });
364
+ }
365
+ }
366
+
367
+ const packageRelativeLocation = moduleLocation.slice(
368
+ packageLocation.length,
369
+ );
370
+ packageSources[candidateSpecifier] = {
371
+ location: packageRelativeLocation,
372
+ sourceLocation: moduleLocation,
373
+ parser,
374
+ bytes: transformedBytes,
375
+ record: concreteRecord,
376
+ sha512,
377
+ };
378
+ if (!shouldDeferError(parser)) {
379
+ for (const importSpecifier of getImportsFromRecord(record)) {
380
+ strictlyRequiredForCompartment(packageLocation).add(
381
+ resolve(importSpecifier, moduleSpecifier),
382
+ );
383
+ }
384
+ }
385
+
386
+ return record;
387
+ }
388
+ }
389
+ return undefined;
390
+ }
391
+
103
392
  /**
104
393
  * @param {ReadFn|ReadPowers} readPowers
105
394
  * @param {string} baseLocation
@@ -110,23 +399,23 @@ export const exitModuleImportHookMaker = ({
110
399
  * @param {HashFn} [options.computeSha512]
111
400
  * @param {Array<string>} [options.searchSuffixes] - Suffixes to search if the
112
401
  * unmodified specifier is not found.
113
- * Pass [] to emulate Node.js’s strict behavior.
114
- * The default handles Node.js’s CommonJS behavior.
402
+ * Pass [] to emulate Node.js' strict behavior.
403
+ * The default handles Node.js' CommonJS behavior.
115
404
  * Unlike Node.js, the Compartment Mapper lifts CommonJS up, more like a
116
405
  * bundler, and does not attempt to vary the behavior of resolution depending
117
406
  * on the language of the importing module.
118
407
  * @param {string} options.entryCompartmentName
119
408
  * @param {string} options.entryModuleSpecifier
120
409
  * @param {ExitModuleImportHook} [options.exitModuleImportHook]
121
- * @param {import('./types.js').SourceMapHook} [options.sourceMapHook]
410
+ * @param {SourceMapHook} [options.sourceMapHook]
122
411
  * @returns {ImportHookMaker}
123
412
  */
124
413
  export const makeImportHookMaker = (
125
414
  readPowers,
126
415
  baseLocation,
127
416
  {
128
- sources = Object.create(null),
129
- compartmentDescriptors = Object.create(null),
417
+ sources = create(null),
418
+ compartmentDescriptors = create(null),
130
419
  archiveOnly = false,
131
420
  computeSha512 = undefined,
132
421
  searchSuffixes = nodejsConventionSearchSuffixes,
@@ -168,11 +457,10 @@ export const makeImportHookMaker = (
168
457
  }) => {
169
458
  // per-compartment:
170
459
  packageLocation = resolveLocation(packageLocation, baseLocation);
171
- const packageSources = sources[packageLocation] || Object.create(null);
460
+ const packageSources = sources[packageLocation] || create(null);
172
461
  sources[packageLocation] = packageSources;
173
462
  const compartmentDescriptor = compartmentDescriptors[packageLocation] || {};
174
- const { modules: moduleDescriptors = Object.create(null) } =
175
- compartmentDescriptor;
463
+ const { modules: moduleDescriptors = create(null) } = compartmentDescriptor;
176
464
  compartmentDescriptor.modules = moduleDescriptors;
177
465
 
178
466
  /**
@@ -209,9 +497,11 @@ export const makeImportHookMaker = (
209
497
 
210
498
  /** @type {ImportHook} */
211
499
  const importHook = async moduleSpecifier => {
212
- await null;
213
500
  compartmentDescriptor.retained = true;
214
501
 
502
+ // for lint rule
503
+ await null;
504
+
215
505
  // per-module:
216
506
 
217
507
  // In Node.js, an absolute specifier always indicates a built-in or
@@ -257,130 +547,31 @@ export const makeImportHookMaker = (
257
547
  );
258
548
  }
259
549
 
260
- // Collate candidate locations for the moduleSpecifier,
261
- // to support Node.js conventions and similar.
262
- const candidates = [moduleSpecifier];
263
- for (const candidateSuffix of searchSuffixes) {
264
- candidates.push(`${moduleSpecifier}${candidateSuffix}`);
265
- }
266
-
267
550
  const { maybeRead } = unpackReadPowers(readPowers);
268
551
 
269
- for (const candidateSpecifier of candidates) {
270
- const candidateModuleDescriptor = moduleDescriptors[candidateSpecifier];
271
- if (candidateModuleDescriptor !== undefined) {
272
- const { compartment: candidateCompartmentName = packageLocation } =
273
- candidateModuleDescriptor;
274
- const candidateCompartment = compartments[candidateCompartmentName];
275
- if (candidateCompartment === undefined) {
276
- throw Error(
277
- `compartment missing for candidate ${candidateSpecifier} in ${candidateCompartmentName}`,
278
- );
279
- }
280
- // modify compartmentMap to include this redirect
281
- const candidateCompartmentDescriptor =
282
- compartmentDescriptors[candidateCompartmentName];
283
- if (candidateCompartmentDescriptor === undefined) {
284
- throw Error(
285
- `compartmentDescriptor missing for candidate ${candidateSpecifier} in ${candidateCompartmentName}`,
286
- );
287
- }
288
- candidateCompartmentDescriptor.modules[moduleSpecifier] =
289
- candidateModuleDescriptor;
290
- // return a redirect
291
- /** @type {RedirectStaticModuleInterface} */
292
- const record = {
293
- specifier: candidateSpecifier,
294
- compartment: candidateCompartment,
295
- };
296
- return record;
297
- }
552
+ const candidates = nominateCandidates(moduleSpecifier, searchSuffixes);
298
553
 
299
- // Using a specifier as a location.
300
- // This is not always valid.
301
- // But, for Node.js, when the specifier is relative and not a directory
302
- // name, they are usable as URL's.
303
- const moduleLocation = resolveLocation(
304
- candidateSpecifier,
554
+ const record = await asyncTrampoline(
555
+ chooseModuleDescriptor,
556
+ {
557
+ candidates,
558
+ compartmentDescriptor,
559
+ compartmentDescriptors,
560
+ compartments,
561
+ computeSha512,
562
+ moduleDescriptors,
563
+ moduleSpecifier,
305
564
  packageLocation,
306
- );
307
- // eslint-disable-next-line no-await-in-loop
308
- const moduleBytes = await maybeRead(moduleLocation);
309
- if (moduleBytes !== undefined) {
310
- /** @type {string | undefined} */
311
- let sourceMap;
312
- // eslint-disable-next-line no-await-in-loop
313
- const envelope = await parse(
314
- moduleBytes,
315
- candidateSpecifier,
316
- moduleLocation,
317
- packageLocation,
318
- {
319
- readPowers,
320
- sourceMapHook:
321
- sourceMapHook &&
322
- (nextSourceMapObject => {
323
- sourceMap = JSON.stringify(nextSourceMapObject);
324
- }),
325
- compartmentDescriptor,
326
- },
327
- );
328
- const {
329
- parser,
330
- bytes: transformedBytes,
331
- record: concreteRecord,
332
- } = envelope;
333
-
334
- // Facilitate a redirect if the returned record has a different
335
- // module specifier than the requested one.
336
- if (candidateSpecifier !== moduleSpecifier) {
337
- moduleDescriptors[moduleSpecifier] = {
338
- module: candidateSpecifier,
339
- compartment: packageLocation,
340
- };
341
- }
342
- /** @type {StaticModuleType} */
343
- const record = {
344
- record: concreteRecord,
345
- specifier: candidateSpecifier,
346
- importMeta: { url: moduleLocation },
347
- };
348
-
349
- let sha512;
350
- if (computeSha512 !== undefined) {
351
- sha512 = computeSha512(transformedBytes);
352
-
353
- if (sourceMapHook !== undefined && sourceMap !== undefined) {
354
- sourceMapHook(sourceMap, {
355
- compartment: packageLocation,
356
- module: candidateSpecifier,
357
- location: moduleLocation,
358
- sha512,
359
- });
360
- }
361
- }
362
-
363
- const packageRelativeLocation = moduleLocation.slice(
364
- packageLocation.length,
365
- );
366
- packageSources[candidateSpecifier] = {
367
- location: packageRelativeLocation,
368
- sourceLocation: moduleLocation,
369
- parser,
370
- bytes: transformedBytes,
371
- record: concreteRecord,
372
- sha512,
373
- };
374
- if (!shouldDeferError(parser)) {
375
- for (const importSpecifier of getImportsFromRecord(record)) {
376
- strictlyRequiredForCompartment(packageLocation).add(
377
- resolve(importSpecifier, moduleSpecifier),
378
- );
379
- }
380
- }
565
+ packageSources,
566
+ readPowers,
567
+ sourceMapHook,
568
+ strictlyRequiredForCompartment,
569
+ },
570
+ { maybeRead, parse, shouldDeferError },
571
+ );
381
572
 
382
- return record;
383
- }
573
+ if (record) {
574
+ return record;
384
575
  }
385
576
 
386
577
  return deferError(
@@ -399,3 +590,161 @@ export const makeImportHookMaker = (
399
590
  };
400
591
  return makeImportHook;
401
592
  };
593
+
594
+ /**
595
+ * Synchronous import for dynamic requires.
596
+ *
597
+ * @param {ReadNowPowers} readPowers
598
+ * @param {string} baseLocation
599
+ * @param {MakeImportNowHookMakerOptions} options
600
+ * @returns {ImportNowHookMaker}
601
+ */
602
+ export function makeImportNowHookMaker(
603
+ readPowers,
604
+ baseLocation,
605
+ {
606
+ sources = create(null),
607
+ compartmentDescriptors = create(null),
608
+ computeSha512 = undefined,
609
+ searchSuffixes = nodejsConventionSearchSuffixes,
610
+ sourceMapHook = undefined,
611
+ exitModuleImportNowHook,
612
+ },
613
+ ) {
614
+ // Set of specifiers for modules (scoped to compartment) whose parser is not
615
+ // using heuristics to determine imports.
616
+ /** @type {Map<string, Set<string>>} compartment name ->* module specifier */
617
+ const strictlyRequired = new Map();
618
+
619
+ /**
620
+ * @param {string} compartmentName
621
+ */
622
+ const strictlyRequiredForCompartment = compartmentName => {
623
+ let compartmentStrictlyRequired = strictlyRequired.get(compartmentName);
624
+ if (compartmentStrictlyRequired !== undefined) {
625
+ return compartmentStrictlyRequired;
626
+ }
627
+ compartmentStrictlyRequired = new Set();
628
+ strictlyRequired.set(compartmentName, compartmentStrictlyRequired);
629
+ return compartmentStrictlyRequired;
630
+ };
631
+
632
+ /**
633
+ * @type {ImportNowHookMaker}
634
+ */
635
+ const makeImportNowHook = ({
636
+ packageLocation,
637
+ packageName: _packageName,
638
+ parse,
639
+ compartments,
640
+ }) => {
641
+ if (!('isSyncParser' in parse)) {
642
+ return function impossibleTransformImportNowHook() {
643
+ throw new Error(
644
+ 'Dynamic requires are only possible with synchronous parsers and no asynchronous module transforms in options',
645
+ );
646
+ };
647
+ }
648
+
649
+ const compartmentDescriptor = compartmentDescriptors[packageLocation] || {};
650
+
651
+ packageLocation = resolveLocation(packageLocation, baseLocation);
652
+ const packageSources = sources[packageLocation] || create(null);
653
+ sources[packageLocation] = packageSources;
654
+ const {
655
+ modules:
656
+ moduleDescriptors = /** @type {Record<string, ModuleDescriptor>} */ (
657
+ create(null)
658
+ ),
659
+ } = compartmentDescriptor;
660
+ compartmentDescriptor.modules = moduleDescriptors;
661
+
662
+ let { policy } = compartmentDescriptor;
663
+ policy = policy || create(null);
664
+
665
+ // Associates modules with compartment descriptors based on policy
666
+ // in cases where the association was not made when building the
667
+ // compartment map but is indicated by the policy.
668
+ if ('packages' in policy && typeof policy.packages === 'object') {
669
+ for (const [packageName, packagePolicyItem] of entries(policy.packages)) {
670
+ if (
671
+ !(packageName in compartmentDescriptor.modules) &&
672
+ packageName in compartmentDescriptor.scopes &&
673
+ packagePolicyItem
674
+ ) {
675
+ compartmentDescriptor.modules[packageName] =
676
+ compartmentDescriptor.scopes[packageName];
677
+ }
678
+ }
679
+ }
680
+
681
+ const { maybeReadNow, isAbsolute } = readPowers;
682
+
683
+ /** @type {ImportNowHook} */
684
+ const importNowHook = moduleSpecifier => {
685
+ if (isAbsolute(moduleSpecifier)) {
686
+ const record = findRedirect({
687
+ compartmentDescriptor,
688
+ compartmentDescriptors,
689
+ compartments,
690
+ absoluteModuleSpecifier: moduleSpecifier,
691
+ packageLocation,
692
+ });
693
+ if (record) {
694
+ return record;
695
+ }
696
+ }
697
+
698
+ const candidates = nominateCandidates(moduleSpecifier, searchSuffixes);
699
+
700
+ const record = syncTrampoline(
701
+ chooseModuleDescriptor,
702
+ {
703
+ candidates,
704
+ compartmentDescriptor,
705
+ compartmentDescriptors,
706
+ compartments,
707
+ computeSha512,
708
+ moduleDescriptors,
709
+ moduleSpecifier,
710
+ packageLocation,
711
+ packageSources,
712
+ readPowers,
713
+ sourceMapHook,
714
+ strictlyRequiredForCompartment,
715
+ },
716
+ {
717
+ maybeRead: maybeReadNow,
718
+ parse,
719
+ },
720
+ );
721
+
722
+ if (record) {
723
+ return record;
724
+ }
725
+
726
+ if (exitModuleImportNowHook) {
727
+ // this hook is responsible for ensuring that the moduleSpecifier actually refers to an exit module
728
+ const exitRecord = exitModuleImportNowHook(
729
+ moduleSpecifier,
730
+ packageLocation,
731
+ );
732
+
733
+ if (!exitRecord) {
734
+ throw new Error(`Could not import module: ${q(moduleSpecifier)}`);
735
+ }
736
+
737
+ return exitRecord;
738
+ }
739
+
740
+ throw new Error(
741
+ `Could not import module: ${q(
742
+ moduleSpecifier,
743
+ )}; try providing an importNowHook`,
744
+ );
745
+ };
746
+
747
+ return importNowHook;
748
+ };
749
+ return makeImportNowHook;
750
+ }