@object-ui/core 3.3.1 → 3.3.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @object-ui/core
2
2
 
3
+ ## 3.3.2
4
+
5
+ ### Patch Changes
6
+
7
+ - @object-ui/types@3.3.2
8
+
3
9
  ## 3.3.1
4
10
 
5
11
  ### Patch Changes
@@ -55,8 +55,23 @@ export type ComponentConfig<T = any> = ComponentMeta & {
55
55
  type: string;
56
56
  component: ComponentRenderer<T>;
57
57
  };
58
+ /**
59
+ * Lazy loader function used by `Registry.registerLazy`. The loader is invoked
60
+ * the first time a missing component type is requested through `getAsync`/the
61
+ * SchemaRenderer fallback path, and is expected to perform a dynamic
62
+ * `import()` of a plugin module whose top-level side-effects call
63
+ * `register()` for that type.
64
+ */
65
+ export type LazyComponentLoader = () => Promise<unknown>;
58
66
  export declare class Registry<T = any> {
59
67
  private components;
68
+ private lazyEntries;
69
+ /**
70
+ * Notifies subscribers that the registry has changed (new components
71
+ * registered). Used by SchemaRenderer to re-render after a lazy plugin
72
+ * load completes.
73
+ */
74
+ private listeners;
60
75
  /**
61
76
  * Register a component with optional namespace support.
62
77
  * If namespace is provided in meta, the component will be registered as "namespace:type".
@@ -76,6 +91,38 @@ export declare class Registry<T = any> {
76
91
  * // Accessible as 'button'
77
92
  */
78
93
  register(type: string, component: ComponentRenderer<T>, meta?: ComponentMeta): void;
94
+ /**
95
+ * Register a lazy-loaded component. The `loader` is a function returning a
96
+ * dynamic `import()` whose target module performs `register()` calls for
97
+ * the given `type` as a top-level side effect.
98
+ *
99
+ * The loader will be invoked the first time `loadLazy(type)` is called (or
100
+ * the first time the SchemaRenderer encounters an unknown component that
101
+ * matches a registered lazy type). Subsequent registrations are idempotent.
102
+ *
103
+ * @example
104
+ * ComponentRegistry.registerLazy('object-map', () => import('@object-ui/plugin-map'), { namespace: 'plugin-map' });
105
+ */
106
+ registerLazy(type: string, loader: LazyComponentLoader, meta?: ComponentMeta): void;
107
+ /**
108
+ * Returns true if `type` (or its namespaced form) has a registered lazy
109
+ * loader awaiting first use.
110
+ */
111
+ hasLazy(type: string, namespace?: string): boolean;
112
+ /**
113
+ * Trigger the lazy loader for `type`, if any. Resolves once the loader
114
+ * completes (whether or not the loaded module actually registered the
115
+ * expected type — caller should re-check the registry afterwards).
116
+ * Returns `undefined` if no lazy entry matches.
117
+ */
118
+ loadLazy(type: string, namespace?: string): Promise<unknown> | undefined;
119
+ /**
120
+ * Subscribe to registry changes (component registrations). Returns an
121
+ * unsubscribe function. Used by React renderers to re-render when a
122
+ * lazy-loaded plugin finishes registering its components.
123
+ */
124
+ subscribe(listener: () => void): () => void;
125
+ private notify;
79
126
  /**
80
127
  * Get a component by type. Supports both namespaced and non-namespaced lookups.
81
128
  *
@@ -13,6 +13,23 @@ export class Registry {
13
13
  writable: true,
14
14
  value: new Map()
15
15
  });
16
+ Object.defineProperty(this, "lazyEntries", {
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true,
20
+ value: new Map()
21
+ });
22
+ /**
23
+ * Notifies subscribers that the registry has changed (new components
24
+ * registered). Used by SchemaRenderer to re-render after a lazy plugin
25
+ * load completes.
26
+ */
27
+ Object.defineProperty(this, "listeners", {
28
+ enumerable: true,
29
+ configurable: true,
30
+ writable: true,
31
+ value: new Set()
32
+ });
16
33
  }
17
34
  /**
18
35
  * Register a component with optional namespace support.
@@ -65,6 +82,81 @@ export class Registry {
65
82
  ...meta
66
83
  });
67
84
  }
85
+ // A real component is now available — clear any matching lazy stub so we
86
+ // don't keep holding the loader reference, and notify subscribers.
87
+ this.lazyEntries.delete(fullType);
88
+ this.lazyEntries.delete(type);
89
+ this.notify();
90
+ }
91
+ /**
92
+ * Register a lazy-loaded component. The `loader` is a function returning a
93
+ * dynamic `import()` whose target module performs `register()` calls for
94
+ * the given `type` as a top-level side effect.
95
+ *
96
+ * The loader will be invoked the first time `loadLazy(type)` is called (or
97
+ * the first time the SchemaRenderer encounters an unknown component that
98
+ * matches a registered lazy type). Subsequent registrations are idempotent.
99
+ *
100
+ * @example
101
+ * ComponentRegistry.registerLazy('object-map', () => import('@object-ui/plugin-map'), { namespace: 'plugin-map' });
102
+ */
103
+ registerLazy(type, loader, meta) {
104
+ const fullType = meta?.namespace ? `${meta.namespace}:${type}` : type;
105
+ const entry = { loader, meta };
106
+ this.lazyEntries.set(fullType, entry);
107
+ if (meta?.namespace && !meta?.skipFallback) {
108
+ this.lazyEntries.set(type, entry);
109
+ }
110
+ }
111
+ /**
112
+ * Returns true if `type` (or its namespaced form) has a registered lazy
113
+ * loader awaiting first use.
114
+ */
115
+ hasLazy(type, namespace) {
116
+ if (namespace)
117
+ return this.lazyEntries.has(`${namespace}:${type}`);
118
+ return this.lazyEntries.has(type);
119
+ }
120
+ /**
121
+ * Trigger the lazy loader for `type`, if any. Resolves once the loader
122
+ * completes (whether or not the loaded module actually registered the
123
+ * expected type — caller should re-check the registry afterwards).
124
+ * Returns `undefined` if no lazy entry matches.
125
+ */
126
+ loadLazy(type, namespace) {
127
+ const key = namespace ? `${namespace}:${type}` : type;
128
+ const entry = this.lazyEntries.get(key);
129
+ if (!entry)
130
+ return undefined;
131
+ if (!entry.pending) {
132
+ entry.pending = entry.loader().catch((err) => {
133
+ // Allow retries on failure by clearing the cached promise.
134
+ entry.pending = undefined;
135
+ throw err;
136
+ });
137
+ }
138
+ return entry.pending;
139
+ }
140
+ /**
141
+ * Subscribe to registry changes (component registrations). Returns an
142
+ * unsubscribe function. Used by React renderers to re-render when a
143
+ * lazy-loaded plugin finishes registering its components.
144
+ */
145
+ subscribe(listener) {
146
+ this.listeners.add(listener);
147
+ return () => {
148
+ this.listeners.delete(listener);
149
+ };
150
+ }
151
+ notify() {
152
+ for (const listener of this.listeners) {
153
+ try {
154
+ listener();
155
+ }
156
+ catch (err) {
157
+ console.error('[Registry] listener error', err);
158
+ }
159
+ }
68
160
  }
69
161
  /**
70
162
  * Get a component by type. Supports both namespaced and non-namespaced lookups.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/core",
3
- "version": "3.3.1",
3
+ "version": "3.3.2",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",
@@ -28,7 +28,7 @@
28
28
  "@objectstack/spec": "^4.0.4",
29
29
  "lodash": "^4.18.1",
30
30
  "zod": "^4.4.3",
31
- "@object-ui/types": "3.3.1"
31
+ "@object-ui/types": "3.3.2"
32
32
  },
33
33
  "devDependencies": {
34
34
  "typescript": "^6.0.3",