@travetto/registry 3.3.4 → 3.4.0-rc.1
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.
- package/README.md +1 -1
- package/package.json +4 -4
- package/src/decorator.ts +4 -1
- package/src/internal/commonjs-loader.ts +86 -0
- package/src/internal/file-loader.ts +72 -0
- package/src/source/class-source.ts +22 -17
- package/support/transformer.register.ts +4 -5
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ The registry is a [MetadataRegistry](https://github.com/travetto/travetto/tree/m
|
|
|
74
74
|
### Live Flow
|
|
75
75
|
At runtime, the registry is designed to listen for changes and to propagate the changes as necessary. In many cases the same file is handled by multiple registries.
|
|
76
76
|
|
|
77
|
-
As the [DynamicFileLoader](https://github.com/travetto/travetto/tree/main/module/
|
|
77
|
+
As the [DynamicFileLoader](https://github.com/travetto/travetto/tree/main/module/registry/src/internal/file-loader.ts#L17) notifies that a file has been changed, the [RootRegistry](https://github.com/travetto/travetto/tree/main/module/registry/src/service/root.ts#L10) will pick it up, and process it accordingly.
|
|
78
78
|
|
|
79
79
|
## Supporting Metadata
|
|
80
80
|
As mentioned in [Manifest](https://github.com/travetto/travetto/tree/main/module/manifest#readme "Support for project indexing, manifesting, along with file watching")'s readme, the framework produces hashes of methods, classes, and functions, to allow for detecting changes to individual parts of the codebase. During the live flow, various registries will inspect this information to determine if action should be taken.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/registry",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0-rc.1",
|
|
4
4
|
"description": "Patterns and utilities for handling registration of metadata and functionality for run-time use",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ast-transformations",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"directory": "module/registry"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/base": "^3.
|
|
30
|
+
"@travetto/base": "^3.4.0-rc.1"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@travetto/cli": "^3.
|
|
34
|
-
"@travetto/transformer": "^3.
|
|
33
|
+
"@travetto/cli": "^3.4.0-rc.2",
|
|
34
|
+
"@travetto/transformer": "^3.4.0-rc.1"
|
|
35
35
|
},
|
|
36
36
|
"peerDependenciesMeta": {
|
|
37
37
|
"@travetto/transformer": {
|
package/src/decorator.ts
CHANGED
|
@@ -24,7 +24,10 @@ class $PendingRegister {
|
|
|
24
24
|
/**
|
|
25
25
|
* Clear pending classes
|
|
26
26
|
*/
|
|
27
|
-
flush(): [string, Class[]][] {
|
|
27
|
+
flush(log?: boolean): [string, Class[]][] {
|
|
28
|
+
if (log) {
|
|
29
|
+
console.debug('Pending changes', { changes: this.ordered.map(([, x]) => x.map(y => y.Ⲑid)) });
|
|
30
|
+
}
|
|
28
31
|
const out = this.ordered.slice(0);
|
|
29
32
|
this.map.clear();
|
|
30
33
|
this.ordered = [];
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Module } from 'module';
|
|
2
|
+
|
|
3
|
+
import { RootIndex, path } from '@travetto/manifest';
|
|
4
|
+
import { RetargettingProxy, GlobalEnv } from '@travetto/base';
|
|
5
|
+
|
|
6
|
+
declare module 'module' {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
8
|
+
function _resolveFilename(req: string, parent: typeof Module): string;
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
10
|
+
function _load(req: string, parent: typeof Module): unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type ModuleLoader = typeof Module['_load'];
|
|
14
|
+
type ModuleProxy = <T>(file: string, mod?: T) => (T | undefined);
|
|
15
|
+
|
|
16
|
+
const moduleLoad: ModuleLoader = Module._load.bind(Module);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Dynamic commonjs module loader. Hooks into module loading
|
|
20
|
+
*/
|
|
21
|
+
export class DynamicCommonjsLoader {
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build a module loader
|
|
25
|
+
*/
|
|
26
|
+
static buildModuleLoader(proxyModuleLoad?: ModuleProxy): ModuleLoader {
|
|
27
|
+
return (request: string, parent: typeof Module): unknown => {
|
|
28
|
+
let mod: unknown;
|
|
29
|
+
try {
|
|
30
|
+
mod = moduleLoad.apply(null, [request, parent]);
|
|
31
|
+
} catch (err: unknown) {
|
|
32
|
+
const name = Module._resolveFilename!(request, parent);
|
|
33
|
+
if (err instanceof Error && GlobalEnv.dynamic && !name.startsWith('test/')) {
|
|
34
|
+
const errMsg = err.message;
|
|
35
|
+
console.debug(`Unable to load ${name}: stubbing out with error proxy.`, errMsg);
|
|
36
|
+
const e = (): never => { throw new Error(errMsg); };
|
|
37
|
+
mod = new Proxy({}, { getOwnPropertyDescriptor: e, get: e, has: e });
|
|
38
|
+
} else {
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const fileName = Module._resolveFilename!(request, parent);
|
|
44
|
+
// Only proxy local modules
|
|
45
|
+
if (RootIndex.getModuleFromSource(fileName)?.local) {
|
|
46
|
+
return proxyModuleLoad ? proxyModuleLoad(fileName, mod) : mod;
|
|
47
|
+
} else {
|
|
48
|
+
return mod;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#loader: ModuleLoader;
|
|
54
|
+
#modules = new Map<string, RetargettingProxy<unknown>>();
|
|
55
|
+
|
|
56
|
+
#proxyModule<T>(file: string, mod?: T): T | undefined {
|
|
57
|
+
if (!this.#modules.has(file)) {
|
|
58
|
+
this.#modules.set(file, new RetargettingProxy<T>(mod!));
|
|
59
|
+
} else {
|
|
60
|
+
this.#modules.get(file)!.setTarget(mod);
|
|
61
|
+
}
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
63
|
+
return this.#modules.get(file)!.get() as T;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async init(): Promise<void> {
|
|
67
|
+
this.#loader = DynamicCommonjsLoader.buildModuleLoader((file, mod) => this.#proxyModule(file, mod));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async unload(file: string): Promise<void> {
|
|
71
|
+
const native = path.toNative(file);
|
|
72
|
+
if (native in require.cache) {
|
|
73
|
+
delete require.cache[native]; // Remove require cached element
|
|
74
|
+
}
|
|
75
|
+
this.#proxyModule(file, null);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async load(file: string): Promise<void> {
|
|
79
|
+
try {
|
|
80
|
+
Module._load = this.#loader;
|
|
81
|
+
require(file);
|
|
82
|
+
} finally {
|
|
83
|
+
Module._load = moduleLoad;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ManifestModuleUtil, RootIndex } from '@travetto/manifest';
|
|
2
|
+
import { ShutdownManager, CompilerClient } from '@travetto/base';
|
|
3
|
+
|
|
4
|
+
type WatchHandler = Parameters<CompilerClient['onFileChange']>[0];
|
|
5
|
+
type CompilerWatchEvent = Parameters<WatchHandler>[0];
|
|
6
|
+
interface ModuleLoader {
|
|
7
|
+
init?(): Promise<void>;
|
|
8
|
+
load(file: string): Promise<void>;
|
|
9
|
+
unload(file: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const VALID_FILE_TYPES = new Set(['js', 'ts']);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Listens to file changes, and provides a unified interface for watching file changes, reloading files as needed
|
|
16
|
+
*/
|
|
17
|
+
class $DynamicFileLoader {
|
|
18
|
+
#handlers: WatchHandler[] = [];
|
|
19
|
+
#loader: ModuleLoader;
|
|
20
|
+
#initialized = false;
|
|
21
|
+
|
|
22
|
+
async dispatch(ev: CompilerWatchEvent): Promise<void> {
|
|
23
|
+
if (ev.action === 'update' || ev.action === 'delete') {
|
|
24
|
+
await this.#loader.unload(ev.output);
|
|
25
|
+
}
|
|
26
|
+
if (ev.action === 'create' || ev.action === 'delete') {
|
|
27
|
+
RootIndex.reinitForModule(RootIndex.mainModuleName);
|
|
28
|
+
}
|
|
29
|
+
if (ev.action === 'create' || ev.action === 'update') {
|
|
30
|
+
await this.#loader.load(ev.output);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const handler of this.#handlers) {
|
|
34
|
+
await handler(ev);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
onLoadEvent(handler: WatchHandler): void {
|
|
39
|
+
this.#handlers.push(handler);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async init(): Promise<void> {
|
|
43
|
+
if (this.#initialized) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.#initialized = true;
|
|
48
|
+
|
|
49
|
+
// TODO: ESM Support?
|
|
50
|
+
const { DynamicCommonjsLoader } = await import('./commonjs-loader.js');
|
|
51
|
+
this.#loader = new DynamicCommonjsLoader();
|
|
52
|
+
|
|
53
|
+
await this.#loader.init?.();
|
|
54
|
+
|
|
55
|
+
ShutdownManager.onUnhandled(err => {
|
|
56
|
+
if (err && (err.message ?? '').includes('Cannot find module')) { // Handle module reloading
|
|
57
|
+
console.error('Cannot find module', { error: err });
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}, 0);
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
// Fire off, and let it run in the bg, restart on exit
|
|
64
|
+
await new CompilerClient().onFileChange(async ev => {
|
|
65
|
+
if (ev.file && RootIndex.hasModule(ev.module) && VALID_FILE_TYPES.has(ManifestModuleUtil.getFileType(ev.file))) {
|
|
66
|
+
await this.dispatch(ev);
|
|
67
|
+
}
|
|
68
|
+
}, true);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const DynamicFileLoader = new $DynamicFileLoader();
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { FindConfig, RootIndex } from '@travetto/manifest';
|
|
4
4
|
import { Class, GlobalEnv } from '@travetto/base';
|
|
5
|
-
import { DynamicFileLoader } from '@travetto/base/src/internal/file-loader';
|
|
6
5
|
|
|
6
|
+
import { DynamicFileLoader } from '../internal/file-loader';
|
|
7
7
|
import { ChangeSource, ChangeEvent, ChangeHandler } from '../types';
|
|
8
8
|
import { PendingRegister } from '../decorator';
|
|
9
9
|
|
|
10
|
+
const moduleFindConfig: FindConfig = {
|
|
11
|
+
module: (m) =>
|
|
12
|
+
m.roles.includes('std') && (
|
|
13
|
+
GlobalEnv.devMode || m.prod ||
|
|
14
|
+
(GlobalEnv.envName === 'doc' || GlobalEnv.envName === 'test') && m.roles.includes(GlobalEnv.envName)
|
|
15
|
+
),
|
|
16
|
+
folder: f => f === 'src' || f === '$index'
|
|
17
|
+
};
|
|
18
|
+
|
|
10
19
|
/**
|
|
11
20
|
* A class change source. Meant to be hooked into the
|
|
12
21
|
* compiler as a way to listen to changes via the compiler
|
|
@@ -81,19 +90,6 @@ export class ClassSource implements ChangeSource<Class> {
|
|
|
81
90
|
}
|
|
82
91
|
}
|
|
83
92
|
|
|
84
|
-
/**
|
|
85
|
-
* Handle when a file watch event happens
|
|
86
|
-
*/
|
|
87
|
-
async onLoadEvent(ev: WatchEvent): Promise<void> {
|
|
88
|
-
console.debug('Pending changes', { changes: PendingRegister.ordered.map(([, x]) => x.map(y => y.Ⲑid)) });
|
|
89
|
-
for (const [file, classes] of PendingRegister.flush()) {
|
|
90
|
-
this.#handleFileChanges(file, classes);
|
|
91
|
-
}
|
|
92
|
-
if (ev.action === 'create') {
|
|
93
|
-
this.#flush();
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
93
|
/**
|
|
98
94
|
* Emit a change event
|
|
99
95
|
*/
|
|
@@ -107,12 +103,21 @@ export class ClassSource implements ChangeSource<Class> {
|
|
|
107
103
|
*/
|
|
108
104
|
async init(): Promise<void> {
|
|
109
105
|
if (GlobalEnv.dynamic) {
|
|
110
|
-
DynamicFileLoader.onLoadEvent(ev =>
|
|
106
|
+
DynamicFileLoader.onLoadEvent(ev => {
|
|
107
|
+
for (const [file, classes] of PendingRegister.flush(true)) {
|
|
108
|
+
this.#handleFileChanges(file, classes);
|
|
109
|
+
}
|
|
110
|
+
if (ev.action === 'create') {
|
|
111
|
+
this.#flush();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
111
114
|
await DynamicFileLoader.init();
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
// Ensure everything is loaded
|
|
115
|
-
|
|
118
|
+
for (const mod of RootIndex.find(moduleFindConfig)) {
|
|
119
|
+
await import(mod.import);
|
|
120
|
+
}
|
|
116
121
|
|
|
117
122
|
// Flush all load events
|
|
118
123
|
this.#flush();
|
|
@@ -16,11 +16,10 @@ export class RegisterTransformer {
|
|
|
16
16
|
*/
|
|
17
17
|
@AfterClass()
|
|
18
18
|
static registerClass(state: TransformerState, node: ts.ClassDeclaration): ts.ClassDeclaration {
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
)
|
|
19
|
+
if (
|
|
20
|
+
state.importName === REGISTER_MOD ||
|
|
21
|
+
state.importName.startsWith(BASE_MOD_SRC) ||
|
|
22
|
+
state.importName.startsWith(MANIFEST_MOD)
|
|
24
23
|
) { // Cannot process self
|
|
25
24
|
return node;
|
|
26
25
|
}
|