feat(api): update to latest README specs
MODIFY
README.md
README.md
@@ -12,7 +12,7 @@ When an importer is created, it has the following API:
```js
/**
* spec - Module specifier string provided to import
- * url - referrer URL TODO: referrer?
+ * url - referrer URL
*/
function importer({ spec, url }): Promise<ModuleNamespace>;
```
@@ -58,9 +58,3 @@ Multiple Linkers per Evaluator. One Evaluator per Linker.
Multiple Importers per Linker.
A Compartment has one Importer.
-
-## Known Issues
-
-* The word `moduleId` in the sources actually should be a `scopedRef` or `moduleLocation`.
-
-* `export * from 'foo'` is not yet implemented.

ADD
src/analyze.js
+export const makeTypeAnalyzer = typeMap => async (resourceStream) => {
+ const { type } = resourceStream;
+ const handler = typeMap[type];
+ if (handler === undefined) {
+ throw TypeError(`Type analyzer for '${type}' not specified`);
+ }
+ return handler(resourceStream);
+};

MODIFY
src/index.js
src/index.js
export { makeRootedResolver } from './resolve';
export { makeSuffixLocator } from './locate';
export { makeProtocolRetriever } from './retrieve';
-export { makeTransformRewriter } from './rewrite';
+export { makeTypeAnalyzer } from './analyze';
export { makeEvaluateLinker } from './link';
export { makeImporter as default } from './pipeline';

MODIFY
src/link.js
src/link.js
@@ -22,12 +22,12 @@ export const makeEvaluateLinker = (evaluator, instanceCache = new Map()) => {
tolerantEvaluator,
preEndowments,
);
- instanceCache.set(linkageRecord.moduleId, linkedInstance);
+ instanceCache.set(linkageRecord.moduleLocation, linkedInstance);
for (const specifier of keys(linkageRecord.imports)) {
- const moduleId = linkageRecord.moduleIds[specifier];
- const importedInstance = recursiveLink(moduleId, linker, preEndowments);
- linkedImportNS.set(moduleId, importedInstance);
+ const moduleLocation = linkageRecord.moduleLocations[specifier];
+ const importedInstance = recursiveLink(moduleLocation, linker, preEndowments);
+ linkedImportNS.set(moduleLocation, importedInstance);
}
return linkedInstance;

MODIFY
src/moduleInstance.js
src/moduleInstance.js
@@ -216,15 +216,15 @@ export function makeModuleInstance(
const candidateAll = create(null);
candidateAll.default = false;
for (const [specifier, importUpdaters] of updateRecord.entries()) {
- const moduleId = linkageRecord.moduleIds[specifier];
- const instance = importNS.get(moduleId);
- instance.initialize(); // bottom up cycle tolerant
+ const moduleLocation = linkageRecord.moduleLocations[specifier];
+ const instance = importNS.get(moduleLocation);
+ instance.getNamespace(); // bottom up cycle tolerant
const { notifiers: modNotifiers } = instance;
for (const [importName, updaters] of importUpdaters.entries()) {
const notify = modNotifiers[importName];
if (!notify) {
throw SyntaxError(
- `The requested module '${moduleId}' does not provide an export named '${importName}'`,
+ `The requested module '${moduleLocation}' does not provide an export named '${importName}'`,
);
}
for (const updater of updaters) {
@@ -284,7 +284,7 @@ export function makeModuleInstance(
let optFunctor = evaluator(functorSource, endowments);
let didThrow = false;
let thrownError;
- function initialize() {
+ function getNamespace() {
if (optFunctor) {
// uninitialized
const functor = optFunctor;
@@ -301,12 +301,12 @@ export function makeModuleInstance(
if (didThrow) {
throw thrownError;
}
+ return moduleNS;
}
return harden({
linkageRecord,
- moduleNS,
notifiers,
- initialize,
+ getNamespace,
});
}

MODIFY
src/pipeline.js
src/pipeline.js
@@ -4,54 +4,54 @@ export const makeImporter = (importHooks, moduleCache = new Map()) => {
resolve,
locate,
retrieve,
- rewrite,
+ analyze,
rootLinker,
} = importHooks;
- function recursiveLink(moduleId, linker, preEndowments) {
+ function recursiveLink(moduleLocation, linker, preEndowments) {
const subLinker =
- (linker.linkerFor && linker.linkerFor(moduleId)) || linker;
+ (linker.linkerFor && linker.linkerFor(moduleLocation)) || linker;
- let moduleInstance = subLinker.instanceCache.get(moduleId);
+ let moduleInstance = subLinker.instanceCache.get(moduleLocation);
if (moduleInstance) {
return moduleInstance;
}
// Translate the linkage record into a module instance, and cache it.
- const linkageRecord = moduleCache.get(moduleId);
+ const linkageRecord = moduleCache.get(moduleLocation);
moduleInstance = subLinker.link(
linkageRecord, // has url
recursiveLink,
preEndowments,
);
- subLinker.instanceCache.set(moduleId, moduleInstance);
+ subLinker.instanceCache.set(moduleLocation, moduleInstance);
return moduleInstance;
}
async function loadOne(specifier, staticRecord, referrer) {
- let moduleId;
+ let moduleLocation;
if (staticRecord === undefined) {
- const scopedRef = resolve(specifier, referrer);
- moduleId = await locate(scopedRef);
+ const absoluteSpecifier = resolve(specifier, referrer);
+ moduleLocation = await locate(absoluteSpecifier);
} else {
// The static record is already prepared, so bypass the rewriter.
// This fresh object ensures we don't clash in the cache.
- moduleId = {
+ moduleLocation = {
toString() {
return `${referrer}`;
},
};
}
- let loadedP = moduleCache.get(moduleId);
+ let loadedP = moduleCache.get(moduleLocation);
if (!loadedP) {
let getStaticRecordP;
if (staticRecord === undefined) {
- // Begin the recursive load.
- getStaticRecordP = retrieve(moduleId).then(body =>
- rewrite(body, moduleId),
- );
+ // Begin the recursive analysis.
+ getStaticRecordP = retrieve(moduleLocation)
+ .then(analyze)
+ .then(sr => ({ staticRecord: sr }));
} else {
// We are injecting a pre-created static record.
getStaticRecordP = Promise.resolve({ staticRecord });
@@ -60,36 +60,35 @@ export const makeImporter = (importHooks, moduleCache = new Map()) => {
loadedP = getStaticRecordP.then(rs => {
// Prevent circularity.
// console.log(`rs`, rs);
- const linkageRecord = { ...rs.staticRecord, moduleIds: {}, moduleId };
+ const linkageRecord = { ...rs.staticRecord, moduleLocations: {}, moduleLocation };
// console.log(`linkageRecord`, linkageRecord);
- moduleCache.set(moduleId, linkageRecord);
+ moduleCache.set(moduleLocation, linkageRecord);
return Promise.all(
- Object.keys(linkageRecord.imports).map(spec =>
+ Object.keys(linkageRecord.imports || {}).map(spec =>
// eslint-disable-next-line no-use-before-define
- loadOne(spec, undefined, moduleId).then(
- // Populate the record from the specifier to the moduleId.
- subModuleId => (linkageRecord.moduleIds[spec] = subModuleId),
+ loadOne(spec, undefined, moduleLocation).then(
+ // Populate the record from the specifier to the moduleLocation.
+ subModuleLocation => (linkageRecord.moduleLocations[spec] = subModuleLocation),
),
),
);
});
- moduleCache.set(moduleId, loadedP);
+ moduleCache.set(moduleLocation, loadedP);
}
- // Loading in progress, or already a moduleId with linkageRecord.
- // Our caller really just wants to know the moduleId.
- return Promise.resolve(loadedP).then(() => moduleId);
+ // Loading in progress, or already a moduleLocation with linkageRecord.
+ // Our caller really just wants to know the moduleLocation.
+ return Promise.resolve(loadedP).then(() => moduleLocation);
}
const importer = async (srcSpec, preEndowments) => {
const { spec, staticRecord, url } = srcSpec;
- const moduleId = await loadOne(spec, staticRecord, url);
+ const moduleLocation = await loadOne(spec, staticRecord, url);
// Begin initialization of the linked modules.
- const moduleInstance = recursiveLink(moduleId, rootLinker, preEndowments);
- await moduleInstance.initialize();
- return moduleInstance.moduleNS;
+ const moduleInstance = recursiveLink(moduleLocation, rootLinker, preEndowments);
+ return moduleInstance.getNamespace();
};
return importer;

MODIFY
src/retrieve.js
src/retrieve.js
-// TODO: Make protoHandlers a Map, to avoid attacker-controlled keys.
export const makeProtocolRetriever = protoHandlers => {
- return async moduleId => {
- const url = new URL(moduleId);
- const bareProtocol = url.protocol.slice(0, -1);
- const handler = protoHandlers.get(bareProtocol);
+ return async absoluteSpecifier => {
+ const url = new URL(absoluteSpecifier);
+ const protocol = url.protocol;
+ const handler = protoHandlers[protocol];
if (handler === undefined) {
- throw TypeError(`Protocol retriever for ${url.protocol} not specified`);
+ throw TypeError(`Protocol retriever for ${absoluteSpecifier} not specified`);
}
return handler(url);
};

DELETE
src/rewrite.js
-// This rewriter accepts a boxed transform-module because
-// we need to tie the knot between make-importer and
-// transform-module.
-export const makeTransformRewriter = boxedTransformModule => {
- return (moduleSource, moduleId) => {
- const [{ rewrite }] = boxedTransformModule;
- const rs = rewrite({
- endowments: {},
- sourceType: 'module',
- src: moduleSource,
- url: moduleId,
- });
- return rs;
- };
-};

ADD
test/test-analyze.js
+/* eslint-disable import/no-extraneous-dependencies */
+import { test } from 'tape-promise/tape';
+
+import { makeTypeAnalyzer } from '../src';
+
+test('type analyzer', async t => {
+ try {
+ const imports = Object.create(null);
+ const liveExportMap = Object.create(null);
+ const fixedExportMap = Object.create(null);
+ const exportAlls = [];
+ const moduleAnalyzer = ({string}) => {
+ const functorSource = string.replace(/import/g, 'hImport');
+ return {
+ imports,
+ liveExportMap,
+ fixedExportMap,
+ exportAlls,
+ functorSource,
+ };
+ };
+ const analyze = makeTypeAnalyzer({ module: moduleAnalyzer });
+ await t.rejects(analyze('foo'), TypeError, 'missing type rejects');
+ await t.rejects(analyze({ type: 'unknown' }), TypeError, 'unknown type rejects');
+ const moduleStaticRecord = await analyze({ string: `import('foo')`, type: 'module' });
+ t.deepEqual(
+ moduleStaticRecord,
+ {
+ functorSource: `hImport('foo')`,
+ exportAlls,
+ fixedExportMap,
+ liveExportMap,
+ imports,
+ },
+ `analyze works`,
+ );
+ } catch (e) {
+ t.isNot(e, e, 'unexpected exception');
+ } finally {
+ t.end();
+ }
+});

MODIFY
test/test-link.js
test/test-link.js
@@ -27,8 +27,8 @@ ${async ({ liveVar, imports }) => {
imports: { './def': ['def'] },
fixedExportMap: {},
liveExportMap: { abc: ['abc', false] },
- moduleIds: { './def': 'https://www.example.com/foo/def' },
- moduleId: 'https://www.example.com/foo/abc',
+ moduleLocations: { './def': 'https://www.example.com/foo/def' },
+ moduleLocation: 'https://www.example.com/foo/abc',
},
],
[
@@ -40,18 +40,17 @@ async ({ imports, liveVar }) => { await imports(new Map()); liveVar.lo(456); lo
imports: {},
fixedExportMap: {},
liveExportMap: { def: ['lo', true] },
- moduleIds: {},
- moduleId: 'https://www.example.com/foo/def',
+ moduleLocations: {},
+ moduleLocation: 'https://www.example.com/foo/def',
},
],
]);
- const recursiveLink = (moduleId, linker, preEndowments) => {
- const linkageRecord = linkageMap.get(moduleId);
+ const recursiveLink = (moduleLocation, linker, preEndowments) => {
+ const linkageRecord = linkageMap.get(moduleLocation);
return linker.link(linkageRecord, recursiveLink, preEndowments);
};
const mi = recursiveLink('https://www.example.com/foo/abc', rootLinker, {});
- await mi.initialize();
- const { moduleNS } = mi;
+ const moduleNS = await mi.getNamespace();
t.deepEqual(moduleNS, { abc: 457 }, 'linkage success');
} catch (e) {
t.isNot(e, e, 'unexpected exception');

MODIFY
test/test-moddir.js
test/test-moddir.js
/* eslint-disable import/no-extraneous-dependencies */
import { test } from 'tape-promise/tape';
import { evaluateProgram as evaluate } from '@agoric/evaluate';
-import makeModuleTransformer from '@agoric/transform-module';
+import { makeModuleAnalyzer } from '@agoric/transform-module';
import * as babelCore from '@babel/core';
import fs from 'fs';
import path from 'path';
import makeImporter, * as mi from '../src';
-const readFile = ({ pathname }) => fs.promises.readFile(pathname, 'utf-8');
+const readFile = ({ pathname }) =>
+ fs.promises.readFile(pathname, 'utf-8')
+ .then(str => ({type: 'module', string: str}))
-const protoHandlers = new Map([['file', readFile]]);
+const protoHandlers = {'file:': readFile};
+const typeAnalyzers = {module: makeModuleAnalyzer(babelCore)};
const setup = rootUrl => {
- const boxedTransform = [];
const importer = makeImporter({
resolve: mi.makeRootedResolver(rootUrl),
locate: mi.makeSuffixLocator('.js'),
retrieve: mi.makeProtocolRetriever(protoHandlers),
- rewrite: mi.makeTransformRewriter(boxedTransform),
+ analyze: mi.makeTypeAnalyzer(typeAnalyzers),
rootLinker: mi.makeEvaluateLinker(evaluate),
});
- boxedTransform[0] = makeModuleTransformer(babelCore, importer);
const endowments = {
insist(assertion, description) {
if (!assertion) {

MODIFY
test/test-pipeline.js
test/test-pipeline.js
@@ -4,7 +4,6 @@ import makeImporter from '../src';
test('end-to-end import', async t => {
try {
- let transformModule;
const indexSource = `\
// index.js
export { default as abc } from './abc';
@@ -13,35 +12,34 @@ export { default as abc } from './abc';
resolve(specifier, referrer) {
return new URL(specifier, referrer).href;
},
- async locate(scopedRef) {
- if (scopedRef === 'https://example.com/hello/') {
+ async locate(absSpecifier) {
+ if (absSpecifier === 'https://example.com/hello/') {
return 'https://example.com/hello/index.js';
}
- if (scopedRef === 'https://example.com/hello/abc') {
+ if (absSpecifier === 'https://example.com/hello/abc') {
return 'https://example.com/hello/abc.js';
}
- throw TypeError(`Unrecognized scopedRef`);
+ throw TypeError(`Unrecognized absolute specifier ${absSpecifier}`);
},
- async retrieve(moduleId) {
- if (moduleId === 'https://example.com/hello/index.js') {
- return indexSource;
+ async retrieve(moduleLocation) {
+ if (moduleLocation === 'https://example.com/hello/index.js') {
+ return { string: indexSource, type: 'module' };
}
- if (moduleId === 'https://example.com/hello/abc.js') {
- return `\
+ if (moduleLocation === 'https://example.com/hello/abc.js') {
+ return { string: `\
// abc.js
export default 123;
-`;
+`, type: 'module' };
}
- throw TypeError(`Unrecognized retrieve ${moduleId}`);
- },
- rewrite(moduleSource, moduleId) {
- const rs = transformModule.rewrite({
- endowments: {},
- sourceType: 'module',
- src: moduleSource,
- url: moduleId,
- });
- return rs;
+ throw TypeError(`Unrecognized retrieve ${moduleLocation}`);
+ },
+ async analyze({ string }) {
+ return {
+ functorSource: `({ onceVar }) => onceVar.default(${JSON.stringify(
+ string,
+ )});`,
+ imports: {},
+ };
},
rootLinker: {
link(linkageRecord, _recursiveLink, _preEndowments) {
@@ -49,52 +47,28 @@ export default 123;
let functor = (1, eval)(linkageRecord.functorSource);
const moduleNS = {};
const functorArg = {
- constVar: {
+ onceVar: {
default(val) {
moduleNS.default = val;
},
},
};
return {
- async initialize() {
+ getNamespace() {
if (functor) {
// console.log(`have functor`, String(functor));
const f = functor;
functor = null;
f(functorArg);
}
+ return moduleNS;
},
- moduleNS,
};
},
instanceCache: new Map(),
},
});
- transformModule = {
- rewrite(rs) {
- if (rs.sourceType === 'module') {
- const staticRecord = {
- exportAlls: [],
- functorSource: `({ constVar }) => constVar.default(${JSON.stringify(
- rs.src,
- )});`,
- imports: {},
- moduleId: rs.url,
- };
- return {
- staticRecord,
- endowments: { hImport: 'something' },
- sourceType: 'script',
- };
- }
- return {
- src: '987',
- sourceType: 'script',
- };
- },
- };
-
t.deepEquals(
await importer({ spec: './hello/', url: 'https://example.com/' }),
{ default: indexSource },
@@ -125,11 +99,11 @@ test('import cached specifier', async t => {
}
throw TypeError(`Don't know how to resolve ${specifier}`);
},
- locate(scopedRef) {
- if (moduleCache.has(scopedRef)) {
- return scopedRef;
+ locate(absSpecifier) {
+ if (moduleCache.has(absSpecifier)) {
+ return absSpecifier;
}
- throw TypeError(`Don't know how to locate ${scopedRef}`);
+ throw TypeError(`Don't know how to locate ${absSpecifier}`);
},
rootLinker: {
link(mlr, _recursiveLink, _preEndowments) {
@@ -138,11 +112,10 @@ test('import cached specifier', async t => {
throw TypeError(`Don't know how to link non-source mlr`);
}
return {
- initialize() {
+ async getNamespace() {
// eslint-disable-next-line no-eval
- this.moduleNS = (1, eval)(mlr.source);
+ return (1, eval)(mlr.source);
},
- moduleNS: {},
};
},
instanceCache: new Map(),

MODIFY
test/test-retrieve.js
test/test-retrieve.js
@@ -5,11 +5,12 @@ import path from 'path';
import { makeProtocolRetriever } from '../src';
-const readFile = ({ pathname }) => fs.promises.readFile(pathname, 'utf-8');
+const readFile = ({ pathname }) => fs.promises.readFile(pathname, 'utf-8')
+ .then(val => ({ type: 'module', string: val }));
test('filesystem retriever', async t => {
try {
- const retrieve = makeProtocolRetriever(new Map([['file', readFile]]));
+ const retrieve = makeProtocolRetriever({'file:': readFile});
const sr = `file://${path.join(__dirname, 'simple-retrieve')}`;
await t.rejects(
retrieve('http://www.example.com'),
@@ -21,9 +22,9 @@ test('filesystem retriever', async t => {
Error,
'cannot retrieve nonexistent',
);
- t.equal(
+ t.deepEqual(
await retrieve(`${sr}/hello.txt`),
- `Hello, world!\n`,
+ { string: `Hello, world!\n`, type: 'module' },
`retrieve existing file`,
);
} catch (e) {

DELETE
test/test-rewrite.js
-/* eslint-disable import/no-extraneous-dependencies */
-import { test } from 'tape-promise/tape';
-
-import { makeTransformRewriter } from '../src';
-
-test('transform rewriter', async t => {
- try {
- const boxedTransform = [];
- const rewrite = makeTransformRewriter(boxedTransform);
- boxedTransform[0] = {
- rewrite(rs) {
- t.equal(typeof rewrite, 'function', 'rewrite is a function');
- t.equal(rs.sourceType, 'module', 'sourceType is a module');
- rs.endowments.hImport = async mod => `imported:${mod}`;
- rs.src = rs.src.replace(/import/g, 'hImport');
- return rs;
- },
- };
- const rs = rewrite(`import('foo')`, '@example/foo');
- t.equal(
- await rs.endowments.hImport('something'),
- 'imported:something',
- `endowments work`,
- );
- t.equal(rs.src, `hImport('foo')`, 'rewrite works');
- // eslint-disable-next-line no-eval
- const ret = (1, eval)(`({ hImport }) => (${rs.src}\n)`)(rs.endowments);
- t.equal(await ret, `imported:foo`, `combination works`);
- } catch (e) {
- t.isNot(e, e, 'unexpected exception');
- } finally {
- t.end();
- }
-});

feat(api): update to latest README specs