@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 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/base/src/internal/file-loader.ts#L24) 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.
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.4",
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.3.4"
30
+ "@travetto/base": "^3.4.0-rc.1"
31
31
  },
32
32
  "peerDependencies": {
33
- "@travetto/cli": "^3.3.6",
34
- "@travetto/transformer": "^3.3.2"
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 { WatchEvent, RootIndex } from '@travetto/manifest';
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 => this.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
- await RootIndex.loadSource();
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 (state.importName === REGISTER_MOD ||
20
- (
21
- state.importName.startsWith(BASE_MOD_SRC) ||
22
- state.importName.startsWith(MANIFEST_MOD)
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
  }