@omriashke/dynamico-core 0.1.4 → 0.1.5

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.
@@ -4,12 +4,18 @@ export interface PackageScopeOptions {
4
4
  components: readonly string[];
5
5
  /** Synchronous exports merged into the package (Colors, useTheme, theme tokens, …). */
6
6
  values?: Record<string, unknown>;
7
+ /**
8
+ * After a component loads, copy named exports onto the package root.
9
+ * e.g. `{ ThemeProvider: ['useTheme', 'useAppTheme'] }`
10
+ */
11
+ reexports?: Record<string, readonly string[]>;
7
12
  }
8
13
  /**
9
14
  * Synthetic npm-like scope module backed by the Dynamico registry.
10
15
  *
11
16
  * - `values` are available synchronously (data + hooks shared with ThemeProvider).
12
17
  * - `components` are lazy React wrappers that load/render registry entries.
18
+ * - Subscribes to the source WebSocket so registry pushes hot-swap live in the host.
13
19
  */
14
20
  export declare function createPackageScope(source: Source, getScope: () => Scope, options: PackageScopeOptions): Record<string, unknown>;
15
21
  export declare function createPackageScopeFromNames(source: Source, getScope: () => Scope, componentNames: readonly string[], values?: Record<string, unknown>): Record<string, unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"packageScope.d.ts","sourceRoot":"","sources":["../src/packageScope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGhD,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AASD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,KAAK,EACrB,OAAO,EAAE,mBAAmB,GAC3B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmDzB;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,KAAK,EACrB,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB"}
1
+ {"version":3,"file":"packageScope.d.ts","sourceRoot":"","sources":["../src/packageScope.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAkB,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGhE,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;CAC/C;AAYD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,KAAK,EACrB,OAAO,EAAE,mBAAmB,GAC3B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyHzB;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,KAAK,EACrB,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB"}
@@ -1,60 +1,122 @@
1
+ import { createElement, useSyncExternalStore } from "react";
1
2
  import { loadModule } from "./loader.js";
2
3
  /**
3
4
  * Synthetic npm-like scope module backed by the Dynamico registry.
4
5
  *
5
6
  * - `values` are available synchronously (data + hooks shared with ThemeProvider).
6
7
  * - `components` are lazy React wrappers that load/render registry entries.
8
+ * - Subscribes to the source WebSocket so registry pushes hot-swap live in the host.
7
9
  */
8
10
  export function createPackageScope(source, getScope, options) {
9
- const { components, values = {} } = options;
11
+ const { components, values = {}, reexports = {} } = options;
12
+ const componentSet = new Set(components);
10
13
  const modules = new Map();
11
- const ensureLoaded = (name) => {
14
+ const pkg = { ...values, __esModule: true };
15
+ const getState = (name) => {
12
16
  let state = modules.get(name);
13
17
  if (!state) {
14
- state = {};
18
+ state = { revision: 0, listeners: new Set() };
15
19
  modules.set(name, state);
16
20
  }
17
- if (state.factory || state.loading)
21
+ return state;
22
+ };
23
+ const subscribe = (name, listener) => {
24
+ const state = getState(name);
25
+ state.listeners.add(listener);
26
+ return () => {
27
+ state.listeners.delete(listener);
28
+ };
29
+ };
30
+ const getRevision = (name) => getState(name).revision;
31
+ const notify = (name) => {
32
+ const state = modules.get(name);
33
+ if (state)
34
+ for (const listener of state.listeners)
35
+ listener();
36
+ // A dependency update may change what an already-mounted parent renders.
37
+ for (const [otherName, other] of modules) {
38
+ if (otherName === name)
39
+ continue;
40
+ if (other.listeners.size === 0)
41
+ continue;
42
+ for (const listener of other.listeners)
43
+ listener();
44
+ }
45
+ };
46
+ let makeLazy;
47
+ const requireRelative = (specifier) => {
48
+ const base = specifier
49
+ .replace(/^\.+\//, "")
50
+ .replace(/\.[tj]sx?$/, "")
51
+ .split("/")
52
+ .pop();
53
+ if (!base)
54
+ throw new Error(`dynamico: cannot resolve '${specifier}'`);
55
+ ensureLoaded(base);
56
+ const dep = modules.get(base)?.factory;
57
+ return dep ?? { default: makeLazy(base) };
58
+ };
59
+ const ingest = (name, module) => {
60
+ const state = getState(name);
61
+ if (module.error || !module.code) {
62
+ state.factory = undefined;
63
+ state.revision += 1;
64
+ notify(name);
18
65
  return;
19
- state.loading = source.fetch(name).then((mod) => {
20
- if (mod.error || !mod.code)
21
- return;
22
- try {
23
- const scope = getScope();
24
- const factory = loadModule(mod.code, scope, (rel) => {
25
- const base = rel
26
- .replace(/^\.+\//, "")
27
- .replace(/\.[tj]sx?$/, "")
28
- .split("/")
29
- .pop();
30
- if (!base)
31
- throw new Error(`dynamico: cannot resolve '${rel}'`);
32
- ensureLoaded(base);
33
- const dep = modules.get(base)?.factory;
34
- return dep ?? { default: makeLazy(base) };
35
- });
36
- state.factory = factory;
37
- }
38
- catch {
39
- state.factory = undefined;
66
+ }
67
+ try {
68
+ const factory = loadModule(module.code, getScope(), requireRelative);
69
+ state.factory = factory;
70
+ state.revision += 1;
71
+ const extra = reexports[name];
72
+ if (extra) {
73
+ for (const key of extra) {
74
+ const exported = factory[key];
75
+ if (exported !== undefined)
76
+ pkg[key] = exported;
77
+ }
40
78
  }
79
+ }
80
+ catch {
81
+ state.factory = undefined;
82
+ state.revision += 1;
83
+ }
84
+ notify(name);
85
+ };
86
+ const ensureLoaded = (name) => {
87
+ const state = getState(name);
88
+ if (state.factory !== undefined || state.loading)
89
+ return;
90
+ state.loading = source
91
+ .fetch(name)
92
+ .then((module) => {
93
+ ingest(name, module);
94
+ })
95
+ .finally(() => {
96
+ state.loading = undefined;
41
97
  });
42
98
  };
43
- const makeLazy = (name) => {
99
+ source.subscribe(({ module }) => {
100
+ if (componentSet.has(module.name)) {
101
+ ingest(module.name, module);
102
+ }
103
+ });
104
+ makeLazy = (name) => {
44
105
  ensureLoaded(name);
45
106
  const Lazy = (props) => {
46
- const factory = modules.get(name)?.factory;
107
+ const revision = useSyncExternalStore((cb) => subscribe(name, cb), () => getRevision(name), () => getRevision(name));
108
+ void revision;
109
+ const factory = getState(name).factory;
47
110
  if (!factory)
48
111
  return null;
49
112
  const target = factory.default ?? factory[name];
50
113
  if (typeof target !== "function")
51
114
  return null;
52
- return target(props);
115
+ return createElement(target, props);
53
116
  };
54
117
  Object.defineProperty(Lazy, "name", { value: `PackageScope(${name})` });
55
118
  return Lazy;
56
119
  };
57
- const pkg = { ...values, __esModule: true };
58
120
  for (const name of components) {
59
121
  pkg[name] = makeLazy(name);
60
122
  }
@@ -1 +1 @@
1
- {"version":3,"file":"packageScope.js","sourceRoot":"","sources":["../src/packageScope.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"packageScope.js","sourceRoot":"","sources":["../src/packageScope.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omriashke/dynamico-core",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Renderer-agnostic core for dynamico: source adapters, module loader, versioned component registry.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,4 +1,5 @@
1
- import type { Scope, Source } from "./types.js";
1
+ import { createElement, useSyncExternalStore, type ComponentType } from "react";
2
+ import type { CompiledModule, Scope, Source } from "./types.js";
2
3
  import { loadModule } from "./loader.js";
3
4
 
4
5
  export interface PackageScopeOptions {
@@ -6,6 +7,11 @@ export interface PackageScopeOptions {
6
7
  components: readonly string[];
7
8
  /** Synchronous exports merged into the package (Colors, useTheme, theme tokens, …). */
8
9
  values?: Record<string, unknown>;
10
+ /**
11
+ * After a component loads, copy named exports onto the package root.
12
+ * e.g. `{ ThemeProvider: ['useTheme', 'useAppTheme'] }`
13
+ */
14
+ reexports?: Record<string, readonly string[]>;
9
15
  }
10
16
 
11
17
  type LazyComponent = (props: Record<string, unknown>) => unknown;
@@ -13,6 +19,9 @@ type LazyComponent = (props: Record<string, unknown>) => unknown;
13
19
  interface ModuleState {
14
20
  factory?: Record<string, unknown>;
15
21
  loading?: Promise<void>;
22
+ /** Bumped on every ingest so useSyncExternalStore subscribers re-render. */
23
+ revision: number;
24
+ listeners: Set<() => void>;
16
25
  }
17
26
 
18
27
  /**
@@ -20,58 +29,129 @@ interface ModuleState {
20
29
  *
21
30
  * - `values` are available synchronously (data + hooks shared with ThemeProvider).
22
31
  * - `components` are lazy React wrappers that load/render registry entries.
32
+ * - Subscribes to the source WebSocket so registry pushes hot-swap live in the host.
23
33
  */
24
34
  export function createPackageScope(
25
35
  source: Source,
26
36
  getScope: () => Scope,
27
37
  options: PackageScopeOptions,
28
38
  ): Record<string, unknown> {
29
- const { components, values = {} } = options;
39
+ const { components, values = {}, reexports = {} } = options;
40
+ const componentSet = new Set(components);
30
41
  const modules = new Map<string, ModuleState>();
42
+ const pkg: Record<string, unknown> = { ...values, __esModule: true };
31
43
 
32
- const ensureLoaded = (name: string): void => {
44
+ const getState = (name: string): ModuleState => {
33
45
  let state = modules.get(name);
34
46
  if (!state) {
35
- state = {};
47
+ state = { revision: 0, listeners: new Set() };
36
48
  modules.set(name, state);
37
49
  }
38
- if (state.factory || state.loading) return;
39
- state.loading = source.fetch(name).then((mod) => {
40
- if (mod.error || !mod.code) return;
41
- try {
42
- const scope = getScope();
43
- const factory = loadModule(mod.code, scope, (rel) => {
44
- const base = rel
45
- .replace(/^\.+\//, "")
46
- .replace(/\.[tj]sx?$/, "")
47
- .split("/")
48
- .pop();
49
- if (!base) throw new Error(`dynamico: cannot resolve '${rel}'`);
50
- ensureLoaded(base);
51
- const dep = modules.get(base)?.factory;
52
- return dep ?? { default: makeLazy(base) };
53
- }) as Record<string, unknown>;
54
- state.factory = factory;
55
- } catch {
56
- state.factory = undefined;
50
+ return state;
51
+ };
52
+
53
+ const subscribe = (name: string, listener: () => void): (() => void) => {
54
+ const state = getState(name);
55
+ state.listeners.add(listener);
56
+ return () => {
57
+ state.listeners.delete(listener);
58
+ };
59
+ };
60
+
61
+ const getRevision = (name: string): number => getState(name).revision;
62
+
63
+ const notify = (name: string): void => {
64
+ const state = modules.get(name);
65
+ if (state) for (const listener of state.listeners) listener();
66
+ // A dependency update may change what an already-mounted parent renders.
67
+ for (const [otherName, other] of modules) {
68
+ if (otherName === name) continue;
69
+ if (other.listeners.size === 0) continue;
70
+ for (const listener of other.listeners) listener();
71
+ }
72
+ };
73
+
74
+ let makeLazy: (name: string) => LazyComponent;
75
+
76
+ const requireRelative = (specifier: string): unknown => {
77
+ const base = specifier
78
+ .replace(/^\.+\//, "")
79
+ .replace(/\.[tj]sx?$/, "")
80
+ .split("/")
81
+ .pop();
82
+ if (!base) throw new Error(`dynamico: cannot resolve '${specifier}'`);
83
+ ensureLoaded(base);
84
+ const dep = modules.get(base)?.factory;
85
+ return dep ?? { default: makeLazy(base) };
86
+ };
87
+
88
+ const ingest = (name: string, module: CompiledModule): void => {
89
+ const state = getState(name);
90
+ if (module.error || !module.code) {
91
+ state.factory = undefined;
92
+ state.revision += 1;
93
+ notify(name);
94
+ return;
95
+ }
96
+ try {
97
+ const factory = loadModule(module.code, getScope(), requireRelative) as Record<
98
+ string,
99
+ unknown
100
+ >;
101
+ state.factory = factory;
102
+ state.revision += 1;
103
+ const extra = reexports[name];
104
+ if (extra) {
105
+ for (const key of extra) {
106
+ const exported = factory[key];
107
+ if (exported !== undefined) pkg[key] = exported;
108
+ }
57
109
  }
58
- });
110
+ } catch {
111
+ state.factory = undefined;
112
+ state.revision += 1;
113
+ }
114
+ notify(name);
59
115
  };
60
116
 
61
- const makeLazy = (name: string): LazyComponent => {
117
+ const ensureLoaded = (name: string): void => {
118
+ const state = getState(name);
119
+ if (state.factory !== undefined || state.loading) return;
120
+ state.loading = source
121
+ .fetch(name)
122
+ .then((module) => {
123
+ ingest(name, module);
124
+ })
125
+ .finally(() => {
126
+ state.loading = undefined;
127
+ });
128
+ };
129
+
130
+ source.subscribe(({ module }) => {
131
+ if (componentSet.has(module.name)) {
132
+ ingest(module.name, module);
133
+ }
134
+ });
135
+
136
+ makeLazy = (name: string): LazyComponent => {
62
137
  ensureLoaded(name);
63
138
  const Lazy: LazyComponent = (props) => {
64
- const factory = modules.get(name)?.factory;
139
+ const revision = useSyncExternalStore(
140
+ (cb) => subscribe(name, cb),
141
+ () => getRevision(name),
142
+ () => getRevision(name),
143
+ );
144
+ void revision;
145
+ const factory = getState(name).factory;
65
146
  if (!factory) return null;
66
147
  const target = factory.default ?? factory[name];
67
148
  if (typeof target !== "function") return null;
68
- return (target as (p: Record<string, unknown>) => unknown)(props);
149
+ return createElement(target as ComponentType<Record<string, unknown>>, props);
69
150
  };
70
151
  Object.defineProperty(Lazy, "name", { value: `PackageScope(${name})` });
71
152
  return Lazy;
72
153
  };
73
154
 
74
- const pkg: Record<string, unknown> = { ...values, __esModule: true };
75
155
  for (const name of components) {
76
156
  pkg[name] = makeLazy(name);
77
157
  }