@lark.js/mvc 0.0.14 → 0.0.16

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/dist/index.d.ts CHANGED
@@ -26,16 +26,6 @@ declare const CALL_BREAK_TIME = 48;
26
26
  /** Increment global counter and return new value */
27
27
  declare function nextCounter(): number;
28
28
 
29
- /**
30
- * Dynamically inject CSS styles into the document head.
31
- * Returns a cleanup function to remove the injected styles.
32
- *
33
- * @param styleIdOrPairs - Style ID string or array of [id, content] pairs
34
- * @param css - CSS content string (only used when first arg is string)
35
- * @returns Cleanup function to remove the styles
36
- */
37
- declare function applyStyle(styleIdOrPairs: string | string[], css?: string): () => void;
38
-
39
29
  /**
40
30
  * Mark/Unmark: signature-based lifecycle tracking for async callbacks.
41
31
  *
@@ -64,444 +54,77 @@ declare function mark(host: object, key: string): () => boolean;
64
54
  declare function unmark(host: object): void;
65
55
 
66
56
  /**
67
- * Multi-cast event emitter class.
68
- *
69
- * @example
70
- * const emitter = new EventEmitter();
71
- * emitter.on('change', (data) => console.log(data));
72
- * emitter.fire('change', { key: 'value' });
73
- */
74
- declare class EventEmitter<T = unknown> implements EventEmitterInterface<T> {
75
- /** Event listeners: prefixed key -> listener array */
76
- listeners: Map<string, EventListenerEntry[]>;
77
- /** Number of `fire()` calls currently on the stack (re-entrancy depth). */
78
- private firingDepth;
79
- /** Keys whose listener list needs compaction after firing settles. */
80
- private pendingCompaction;
81
- /**
82
- * Bind event listener.
83
- */
84
- on(event: string, handler: (this: T, e: ChangeEvent) => void): this;
85
- /**
86
- * Unbind event listener.
87
- * If handler is provided, removes only that handler.
88
- * If no handler, removes all handlers for the event.
89
- */
90
- off(event: string, handler?: AnyFunc): this;
91
- /**
92
- * Fire event, execute all bound handlers. Safe for re-entrant `off()` calls
93
- * during dispatch: removed handlers are replaced with noop and compacted
94
- * after the outermost fire returns.
95
- *
96
- * @param event - Event name
97
- * @param data - Event data (type property added automatically)
98
- * @param remove - Whether to remove all handlers after firing
99
- * @param lastToFirst - Whether to execute handlers in reverse order
100
- */
101
- fire(event: string, data?: Record<string, unknown>, remove?: boolean, lastToFirst?: boolean): this;
102
- }
103
-
104
- /**
105
- * Minimal HMR context interface.
106
- * Compatible with Vite's `import.meta.hot` and webpack's `module.hot`.
107
- * Defined here to avoid depending on bundler-specific type packages.
57
+ * Register a View setup function for a given view path.
58
+ * Called after module loading completes (or up front during boot).
108
59
  */
109
- interface HotContext {
110
- /** Accept a self-update. The callback receives the new module namespace. */
111
- accept(cb?: (mod: {
112
- default?: unknown;
113
- } | undefined) => void): void;
114
- /** Register a cleanup callback that runs before this module is replaced. */
115
- dispose(cb: (data: unknown) => void): void;
116
- /** Force a full page reload (fallback when HMR cannot handle the update). */
117
- invalidate(): void;
118
- }
60
+ declare function registerViewClass(viewPath: string, setup: ViewSetup): void;
119
61
  /**
120
- * Find all currently mounted frames whose viewPath matches the given path,
121
- * and re-mount them.
122
- *
123
- * After a new View class is registered via `registerViewClass`, calling this
124
- * function triggers `frame.mountView()` on each matching frame. Since the new
125
- * class is already in the registry, `Frame.mountView` takes the synchronous
126
- * path (`getViewClass` returns the class immediately).
127
- *
128
- * @param viewPath - The view path to match (e.g. 'home', 'components/list')
62
+ * Invalidate a View setup from the registry.
63
+ * Used by HMR to force re-loading of a view module.
129
64
  */
130
- declare function reloadViews(viewPath: string): void;
65
+ declare function invalidateViewClass(viewPath: string): void;
131
66
 
132
67
  /**
133
- * Base View class.
134
- * Views are created via View.extend() and mounted by Frame.
68
+ * Create a frame object. Called internally by mountFrame / createRoot.
69
+ * Not intended for direct user use use `Frame.createRoot()` or
70
+ * `frame.mountFrame()` instead.
71
+ *
72
+ * @internal
135
73
  */
136
- declare class View implements ViewInterface {
137
- /** View ID (same as owner frame ID) */
138
- id: string;
139
- /** Owner frame */
140
- owner: FrameInterface | number;
141
- /** Updater instance */
142
- updater: UpdaterInterface;
143
- /** Signature: > 0 means active, incremented on render, 0 = destroyed */
144
- signature: number;
145
- /** Whether rendered at least once */
146
- rendered?: boolean;
147
- /** Whether view has template */
148
- template?: ViewTemplate;
149
- /** Location observation config */
150
- locationObserved: ViewLocationObserved;
151
- /** Observed state keys */
152
- observedStateKeys?: string[];
153
- /** Resource map */
154
- resources: Record<string, ViewResourceEntry>;
155
- /** Whether endUpdate pending */
156
- endUpdatePending?: number;
157
- /** Internal event storage */
158
- private _events;
159
- /** Prototype-stored event maps shape (set by View.prepare). */
160
- private get protoEventState();
161
- /**
162
- * Event bitmask map: eventType -> bitmask (1=root, 2=selector).
163
- * Read from prototype ($evtObjMap) set by View.prepare.
164
- * Using a getter avoids ES6 class field shadowing the prototype value.
165
- */
166
- get eventObjectMap(): Record<string, number>;
167
- /**
168
- * Selector event map: eventType -> selector list.
169
- * Read from prototype ($selMap) set by View.prepare.
170
- */
171
- get eventSelectorMap(): Record<string, ViewEventSelectorEntry>;
172
- /**
173
- * Global event list: [{handler, element, eventName, modifiers}].
174
- * Read from prototype ($globalEvtList) set by View.prepare.
175
- */
176
- get globalEventList(): ViewGlobalEventEntry[];
177
- /**
178
- * Initialize view (called by Frame when mounting).
179
- */
180
- init(): void;
181
- /**
182
- * Render view template (called by Frame after init).
183
- * Wrapped by View.wrapMethod to manage signature + resources.
184
- */
185
- render(): void;
186
- on(event: string, handler: AnyFunc): this;
187
- off(event: string, handler?: AnyFunc): this;
188
- fire(event: string, data?: Record<string, unknown>, remove?: boolean, lastToFirst?: boolean): this;
189
- /** Get the owning frame, asserting it has been bound. */
190
- private get ownerFrame();
191
- /**
192
- * Notify view that HTML update is about to begin.
193
- * Unmounts child frames in the update zone.
194
- */
195
- beginUpdate(id?: string): void;
196
- /**
197
- * Notify view that HTML update has ended.
198
- * Mounts child frames in the update zone and runs deferred invokes.
199
- */
200
- endUpdate(id?: string, inner?: boolean): void;
201
- /**
202
- * Wrap an async callback to check view signature before executing.
203
- * If the view has been re-rendered or destroyed, the callback is skipped.
204
- */
205
- wrapAsync<Fn extends AnyFunc>(fn: Fn, context?: unknown): (...args: Parameters<Fn>) => ReturnType<Fn> | undefined;
206
- /**
207
- * Observe location parameters or path changes.
208
- * When observed keys change, render() is called automatically.
209
- */
210
- observeLocation(params: string | string[] | Record<string, unknown>, observePath?: boolean): void;
211
- /**
212
- * Observe State data keys for changes.
213
- * When observed keys change via State.digest(), render() is called.
214
- */
215
- observeState(observedKeys: string | string[]): void;
216
- /**
217
- * Capture (register) a resource under a key.
218
- * If a resource already exists at that key, it's destroyed first.
219
- * When destroyOnRender=true, the resource is destroyed on next render call.
220
- */
221
- capture(key: string, resource?: unknown, destroyOnRender?: boolean): unknown;
222
- /**
223
- * Release a captured resource.
224
- * If destroy=true, calls the resource's destroy() method.
225
- */
226
- release(key: string, destroy?: boolean): unknown;
227
- /**
228
- * Set up a leave confirmation for route changes and page unload.
229
- */
230
- leaveTip(message: string, condition: () => boolean): void;
231
- /** Collected makes from mixins */
232
- static makes?: AnyFunc[];
233
- /**
234
- * Prepare a View subclass by scanning its prototype for event method patterns.
235
- * Pattern: `$?name<eventType1,eventType2>(&modifiers)`
236
- *
237
- * Only runs once per View subclass (guarded by makes marker).
238
- * Called from Frame.mountView before creating the view instance.
239
- */
240
- static prepare(oView: typeof View): AnyFunc[];
241
- /**
242
- * Bind or unbind event delegation for a view instance.
243
- * Called from Frame during mount/unmount.
244
- */
245
- static delegateEvents(view: ViewInterface, destroy?: boolean): void;
246
- /**
247
- * Destroy all resources managed by a view.
248
- * If lastly=true, destroy ALL resources; otherwise only destroyOnRender ones.
249
- */
250
- static destroyAllResources(view: ViewInterface, lastly: boolean): void;
251
- /**
252
- * Process deferred invoke calls on a frame.
253
- */
254
- static runInvokes(frame: FrameInterface): void;
255
- /**
256
- * Wrap a method on the prototype to add signature checking and resource cleanup.
257
- */
258
- private static wrapMethod;
259
- /**
260
- * When two mixins define the same event method, merge them into
261
- * a single function that calls both in sequence.
262
- */
263
- private static processMixinsSameEvent;
264
- /**
265
- * Merge an array of mixin objects into the view prototype.
266
- */
267
- private static mergeMixins;
268
- /**
269
- * Destroy a single resource entry.
270
- */
271
- private static destroyResource;
272
- /**
273
- * Extend View to create a new View subclass.
274
- *
275
- * Supports:
276
- * - props.make: constructor-like init (called with initParams + {node, deep})
277
- * - props.mixins: array of mixin objects
278
- * - Event method patterns: `'name<click>'` etc.
279
- */
280
- static extend(props?: ThisType<ViewInterface> & Record<string, unknown>, statics?: Record<string, unknown>): typeof View;
281
- /**
282
- * Merge mixins into View prototype.
283
- */
284
- static merge(this: typeof View, ...mixins: Record<string, unknown>[]): typeof View;
285
- /**
286
- * Set up HMR accept handler for this view module.
287
- *
288
- * When the module is hot-replaced, the new View class is extracted from
289
- * the new module, registered in the view registry, and all currently
290
- * mounted frames using this viewPath are re-mounted.
291
- *
292
- * No-op when `hot` is undefined (production / non-HMR environment).
293
- *
294
- * ```ts
295
- * if (import.meta.hot) {
296
- * HomeView.accept(import.meta.hot, 'home');
297
- * }
298
- * ```
299
- */
300
- static accept(hot: HotContext | undefined, viewPath: string): void;
301
- /**
302
- * Set up HMR dispose handler for this view module.
303
- *
304
- * When the module is about to be replaced, the old View class is removed
305
- * from the registry so subsequent lookups don't return the stale class.
306
- *
307
- * No-op when `hot` is undefined (production / non-HMR environment).
308
- *
309
- * ```ts
310
- * if (import.meta.hot) {
311
- * HomeView.dispose(import.meta.hot, 'home');
312
- * }
313
- * ```
314
- */
315
- static dispose(hot: HotContext | undefined, viewPath: string): void;
74
+ declare function createFrame(id: string, parentId?: string): FrameObj;
75
+ interface FrameApi {
76
+ get(id: string): FrameObj | undefined;
77
+ getAll(): Map<string, FrameObj>;
78
+ getRoot(): FrameObj | undefined;
79
+ createRoot(rootId?: string): FrameObj;
80
+ on(event: string, handler: AnyFunc): FrameApi;
81
+ off(event: string, handler?: AnyFunc): FrameApi;
82
+ fire(event: string, data?: Record<string, unknown>): void;
316
83
  }
84
+ declare const Frame: FrameApi;
85
+
317
86
  /**
318
- * Type-safe wrapper around `View.extend()`.
87
+ * Define a view via a setup function (hooks style).
319
88
  *
320
- * `View.extend({...})` accepts any object literal, and inside its methods
321
- * `this` is typed only as the base `ViewInterface` — so any custom state
322
- * field or helper method requires a `(this as MyView).foo` strong-cast at
323
- * every call site.
89
+ * The setup function runs once on mount, receives a `ViewCtx`, and returns
90
+ * `{ template, events, assign? }`. Hooks (`useState`, `useEffect`, etc.)
91
+ * can be called inside setup to manage state and side effects.
324
92
  *
325
- * `defineView()` threads the literal's own shape back into `this` via
326
- * `ThisType<P & ViewInterface>`, so `this.foo` is typed automatically:
327
- *
328
- * ```ts
329
- * const HomeView = defineView({
330
- * $title: "Home",
331
- * init() {
332
- * this.updater.set({ title: this.$title }); // both typed
333
- * },
334
- * greet() {
335
- * return `hello ${this.$title}`;
336
- * },
93
+ * @example
94
+ * const HomeView = defineView((ctx, params) => {
95
+ * const [getCount, setCount] = useState('count', 0);
96
+ * return {
97
+ * template,
98
+ * events: { "incr<click>": (e) => setCount(getCount() + 1) },
99
+ * };
337
100
  * });
338
- * ```
339
- *
340
- * Runtime semantics are identical to `View.extend(props, statics)` — this is
341
- * a zero-cost type-only wrapper.
342
- */
343
- declare function defineView<P extends Record<string, unknown>>(props: P & ThisType<P & ViewInterface>, statics?: Record<string, unknown>): typeof View;
344
-
345
- /**
346
- * Register a View class for a given view path.
347
- * Called after module loading completes (or up front during boot).
348
- */
349
- declare function registerViewClass(viewPath: string, ViewClass: typeof View): void;
350
- /**
351
- * Invalidate a View class from the registry.
352
- * Used by HMR to force re-loading of a view module.
353
101
  */
354
- declare function invalidateViewClass(viewPath: string): void;
102
+ declare function defineView(setup: ViewSetup): ViewSetup;
355
103
 
356
104
  /**
357
- * Frame (View Frame) class for view lifecycle management.
358
- * Each frame owns a view and manages child frames.
105
+ * Create a multi-cast event emitter.
106
+ *
107
+ * @returns An emitter API object with `on`, `off`, `fire` methods.
108
+ * The object also supports the `on{EventName}` convention: setting
109
+ * `emitter.onDestroy = fn` causes `fire("destroy", ...)` to call `fn`.
359
110
  *
111
+ * @example
112
+ * const emitter = createEmitter();
113
+ * emitter.on('change', (data) => console.log(data));
114
+ * emitter.fire('change', { key: 'value' });
360
115
  */
361
- declare class Frame extends EventEmitter implements FrameInterface {
362
- /** Frame ID (same as owner DOM element ID) */
363
- readonly id: string;
364
- /** Parent Frame ID */
365
- private _parentId;
366
- get parentId(): string | undefined;
367
- /** Children map: id -> id */
368
- childrenMap: Record<string, string>;
369
- /** Children count */
370
- childrenCount: number;
371
- /** Ready count (children that have fired 'created') */
372
- readyCount: number;
373
- /** Set of child frame IDs that have fired 'created' */
374
- readyMap: Set<string>;
375
- /** View instance */
376
- viewInstance?: ViewInterface;
377
- /** Get view instance (read-only) */
378
- get view(): ViewInterface | undefined;
379
- /** Invoke list for deferred method calls */
380
- invokeList: FrameInvokeEntry[];
381
- /** Signature for async operation tracking */
382
- signature: number;
383
- /** Whether view has altered */
384
- hasAltered: number;
385
- /** Whether view is destroyed */
386
- destroyed: number;
387
- /** View path (v-lark attribute value) */
388
- viewPath?: string;
389
- /** Original template before mount */
390
- originalTemplate?: string;
391
- /** Hold fire created flag */
392
- holdFireCreated: number;
393
- /** Children created flag */
394
- childrenCreated: number;
395
- /** Children alter flag */
396
- childrenAlter: number;
397
- constructor(id: string, parentId?: string);
398
- /**
399
- * Mount a view to this frame.
400
- *
401
- * Complete flow:
402
- * 1. Parse viewPath, translate query params from parent
403
- * 2. Unmount current view
404
- * 3. Load View class (via require or provided ViewClass)
405
- * 4. View_Prepare (scan event methods)
406
- * 5. Create View instance
407
- * 6. View_DelegateEvents (bind DOM events)
408
- * 7. Call view.init()
409
- * 8. If view has template, call render via Updater
410
- * 9. If no template, call endUpdate directly
411
- */
412
- mountView(viewPath: string, viewInitParams?: Record<string, unknown>): void;
413
- /**
414
- * Internal: actually mount the view after class is loaded.
415
- */
416
- doMountView(ViewClass: typeof View, params: Record<string, unknown>, node: HTMLElement, sign: number): void;
417
- /**
418
- * Unmount current view.
419
- */
420
- unmountView(): void;
421
- /**
422
- * Mount a child frame.
423
- */
424
- mountFrame(frameId: string, viewPath: string, viewInitParams?: Record<string, unknown>): FrameInterface;
425
- /**
426
- * Unmount a child frame.
427
- */
428
- unmountFrame(id?: string): void;
429
- /**
430
- * Mount all views in a zone.
431
- */
432
- mountZone(zoneId?: string): void;
433
- /**
434
- * Unmount all views in a zone.
435
- */
436
- unmountZone(zoneId?: string): void;
437
- /**
438
- * Get all child frame IDs.
439
- */
440
- children(): string[];
441
- /**
442
- * Get parent frame at given level.
443
- * @param level - How many levels up (default 1)
444
- */
445
- parent(level?: number): Frame | undefined;
446
- /**
447
- * Invoke a method on the view.
448
- */
449
- invoke(name: string, args?: unknown[]): unknown;
450
- /**
451
- * Type-safe variant of `invoke`.
452
- *
453
- * `invoke()` accepts any string and any args, which silently hides
454
- * mismatched call sites when a method gets renamed. `invokeTyped` carries
455
- * the view's method signature through TypeScript so the compiler catches
456
- * those mistakes:
457
- *
458
- * ```ts
459
- * type Home = View & { loadData(id: string): Promise<void> };
460
- * frame.invokeTyped<Home, "loadData">("loadData", ["user-1"]);
461
- * ```
462
- *
463
- * Behavior is identical to `invoke` at runtime — same defer / direct-call
464
- * paths — so it's a drop-in safer overload.
465
- */
466
- invokeTyped<V extends Record<string, unknown>, K extends keyof V & string>(name: K, args: V[K] extends (...a: infer A) => unknown ? A : never[]): V[K] extends (...a: never[]) => infer R ? R | undefined : unknown;
467
- /** Get frame by ID */
468
- static get(id: string): Frame | undefined;
469
- /** Get all frames */
470
- static getAll(): Map<string, Frame>;
471
- /**
472
- * Returns the existing root frame, or `undefined` if none has been created.
473
- * Pure getter — never creates a Frame, never touches the DOM.
474
- *
475
- * Use `Frame.createRoot(id)` to create the root explicitly during framework
476
- * boot. For Micro-Frontend hosts that own multiple independent containers,
477
- * use `new Frame(containerId)` directly so each MF mount has its own root.
478
- */
479
- static getRoot(): Frame | undefined;
480
- /**
481
- * Create (or return) the singleton root frame for this app.
482
- *
483
- * Idempotent: subsequent calls always return the original root regardless
484
- * of `rootId` — so passing a different id later is silently ignored.
485
- * `Framework.boot()` is the canonical caller; user code rarely needs this.
486
- */
487
- static createRoot(rootId?: string): Frame;
488
- /** Bind event listener (static) */
489
- static on(event: string, handler: AnyFunc): typeof Frame;
490
- /** Unbind event listener (static) */
491
- static off(event: string, handler?: AnyFunc): typeof Frame;
492
- /** Fire event (static) */
493
- static fire(event: string, data?: Record<string, unknown>): void;
494
- }
116
+ declare function createEmitter<T = unknown>(): EmitterApi<T>;
495
117
 
496
118
  /**
497
119
  * Lark framework type definitions.
498
- * All shared types are defined here to eliminate type cheats across modules.
120
+ * All shared types are defined here to provide a single source of truth
121
+ * for module interfaces and to enforce type safety across the framework.
499
122
  *
500
123
  * Lark is a lightweight MVC frontend framework that provides:
501
- * - View: base view class with extend/merge inheritance and mixin support
502
- * - Router: hash-based two-phase route confirmation
124
+ * - View: functional view system via defineView() + ViewCtx + Hooks
125
+ * - Router: history/hash two-phase route confirmation
503
126
  * - State: simple cross-view observable singleton (recommended for simple cases)
504
- * - Store: zustand-aligned state management with create/getState/setState/subscribe
127
+ * - Store: zustand-aligned state management with createStore/getState/setState/subscribe
505
128
  * (recommended for complex cases)
506
129
  * - Service: API request management with caching, queuing, and deduplication
507
130
  * - Frame: view frame managing view mount/unmount lifecycle
@@ -536,48 +159,6 @@ interface CacheOptions<T> {
536
159
  /** Comparator for sorting entries */
537
160
  sortComparator?: (a: CacheEntry<T>, b: CacheEntry<T>) => number;
538
161
  }
539
- /**
540
- * Cache interface providing LFU (Least Frequently Used) cache management.
541
- * Cache keys use a special prefix internally for namespace isolation.
542
- */
543
- interface CacheInterface<T = unknown> {
544
- /**
545
- * Set a cache resource. If the key exists, updates the value and increments frequency.
546
- * Triggers LFU eviction when cache entries exceed capacity (maxSize + bufferSize).
547
- * @param key Unique identifier for the cached resource
548
- * @param resource The resource to cache
549
- */
550
- set(key: string, resource: T): void;
551
- /**
552
- * Get a cached resource. Access increments frequency count and timestamp for LFU ranking.
553
- * Returns undefined if the key does not exist.
554
- * @param key Cache resource key
555
- */
556
- get(key: string): T | undefined;
557
- /**
558
- * Remove a resource from cache by key. Triggers onRemove callback on deletion.
559
- * @param key Cache resource key to remove
560
- */
561
- del(key: string): void;
562
- /**
563
- * Check if cache contains a resource for the given key.
564
- * @param key Cache resource key
565
- */
566
- has(key: string): boolean;
567
- /**
568
- * Iterate over all cached resource values.
569
- * @param callback Iteration callback receiving the cached value (may be undefined)
570
- */
571
- forEach(callback: (value: T | undefined) => void): void;
572
- /**
573
- * Number of cache entries.
574
- */
575
- readonly size: number;
576
- /**
577
- * Clear all cache entries. Triggers onRemove callback for each deleted entry.
578
- */
579
- clear(): void;
580
- }
581
162
  interface EventListenerEntry {
582
163
  /** Handler function */
583
164
  handler: AnyFunc;
@@ -691,7 +272,7 @@ interface DomRef {
691
272
  /** ID update list: [element, newId][] */
692
273
  idUpdates: [Element, string][];
693
274
  /** Views that need post-processing */
694
- views: ViewInterface[];
275
+ views: ViewCtx[];
695
276
  /** DOM operation list: [opCode, parent, newChild?, oldChild?][] */
696
277
  domOps: DomOp[];
697
278
  /** Whether anything changed */
@@ -748,7 +329,7 @@ interface VDomRef {
748
329
  /** View ID (for placeholder replacement) */
749
330
  viewId: string;
750
331
  /** Sub-views that need re-rendering after diff */
751
- viewRenders: ViewInterface[];
332
+ viewRenders: ViewCtx[];
752
333
  /** Deferred DOM property assignments: [element, propName, value][] */
753
334
  nodeProps: [Element, string, unknown][];
754
335
  /** Pending async operation count */
@@ -776,20 +357,6 @@ interface FrameInvokeEntry {
776
357
  /** Whether removed (args match) */
777
358
  removed?: boolean;
778
359
  }
779
- /** Mixin event handler with internal merge marker and handler list */
780
- interface MixinEventHandler extends AnyFunc {
781
- /** Merged handler list */
782
- handlerList?: AnyFunc[];
783
- /** Mixin marker: 1 = this is a mixin function */
784
- marker?: number;
785
- }
786
- /** View event selector map entry: handler name list with selector presence tracking */
787
- interface ViewEventSelectorEntry {
788
- /** Selector name list */
789
- selectors: string[];
790
- /** Index signature for checking if selector is already registered */
791
- [selector: string]: unknown;
792
- }
793
360
  /**
794
361
  * Compiled template function signature.
795
362
  * `data`/`viewId`/`refData` are required; subsequent encoder args are
@@ -810,18 +377,6 @@ interface ViewResourceEntry {
810
377
  /** Whether to destroy when render() is called */
811
378
  destroyOnRender: boolean;
812
379
  }
813
- interface ViewGlobalEventEntry {
814
- /** Handler function */
815
- handler: AnyFunc;
816
- /** Bound handler wrapper (for removeEventListener) */
817
- boundHandler?: AnyFunc;
818
- /** DOM element (window/document) */
819
- element: EventTarget;
820
- /** Event name */
821
- eventName: string;
822
- /** Modifiers */
823
- modifiers: Record<string, boolean>;
824
- }
825
380
  /**
826
381
  * View configuration for listening to URL changes.
827
382
  * Used as object parameter for `observeLocation()` method.
@@ -841,7 +396,13 @@ interface ViewObserveLocation {
841
396
  * Supports two-phase route confirmation mechanism: change (can reject) → changed.
842
397
  * Hash-based implementation using #! as default hash prefix.
843
398
  */
844
- interface RouterInterface extends EventEmitterInterface<RouterInterface> {
399
+ interface RouterApi {
400
+ /** Bind event listener */
401
+ on(event: string, handler: (e?: ChangeEvent) => void): this;
402
+ /** Unbind event listener */
403
+ off(event: string, handler?: AnyFunc): this;
404
+ /** Fire event */
405
+ fire(event: string, data?: Record<string, unknown>, remove?: boolean, lastToFirst?: boolean): this;
845
406
  /**
846
407
  * Parse href into Location object.
847
408
  * Parses query and hash sections of href, returns structured routing information.
@@ -907,7 +468,7 @@ interface ServiceEvent extends ChangeEvent {
907
468
  /**
908
469
  * Data payload object carrying this request's data.
909
470
  */
910
- readonly payload: PayloadInterface;
471
+ readonly payload: PayloadApi;
911
472
  /**
912
473
  * Error object, present if request throws an error, otherwise null.
913
474
  */
@@ -924,325 +485,178 @@ interface ViewEvent extends ChangeEvent {
924
485
  readonly id: string;
925
486
  }
926
487
  /**
927
- * Frame static event interface carrying associated Frame instance.
488
+ * Frame static event interface carrying associated Frame object.
928
489
  * Carried in Frame's add/remove static events.
929
490
  */
930
491
  interface FrameStaticEvent extends ChangeEvent {
931
492
  /**
932
- * Associated Frame instance object.
493
+ * Associated Frame object.
933
494
  */
934
- readonly frame: FrameInterface;
495
+ readonly frame: FrameObj;
935
496
  }
936
- interface ViewInterface extends EventEmitterInterface<ViewInterface> {
937
- /**
938
- * View ID (same as owner frame ID),
939
- * DOM node ID where current view resides.
940
- */
497
+ /**
498
+ * Functional emitter API.
499
+ *
500
+ * Returned by `createEmitter()`. No `this` binding — handlers are called
501
+ * with `null` context. Methods return the API object for chaining.
502
+ */
503
+ interface EmitterApi<T = unknown> {
504
+ on(name: string, fn: (e?: ChangeEvent) => void): EmitterApi<T>;
505
+ off(name: string, fn?: AnyFunc): EmitterApi<T>;
506
+ fire(name: string, data?: Record<string, unknown>, remove?: boolean, lastToFirst?: boolean): EmitterApi<T>;
507
+ }
508
+ /**
509
+ * Functional cache API.
510
+ *
511
+ * Returned by `createCache()`.
512
+ */
513
+ interface CacheApi<T = unknown> {
514
+ set(key: string, resource: T): void;
515
+ get(key: string): T | undefined;
516
+ del(key: string): void;
517
+ has(key: string): boolean;
518
+ clear(): void;
519
+ forEach(callback: (value: T | undefined) => void): void;
520
+ getSize(): number;
521
+ }
522
+ /**
523
+ * Functional updater API.
524
+ *
525
+ * Returned by `createUpdater()`. `refData` is a property.
526
+ * `set()` returns the API object for chaining.
527
+ */
528
+ interface UpdaterApi {
529
+ get: <T = unknown>(key?: string) => T;
530
+ set: (data: Record<string, unknown>, excludes?: ReadonlySet<string>) => UpdaterApi;
531
+ digest: (data?: Record<string, unknown>, excludes?: ReadonlySet<string>, callback?: () => void) => void;
532
+ forceDigest: () => void;
533
+ snapshot: () => UpdaterApi;
534
+ altered: () => boolean | undefined;
535
+ refData: Record<string, unknown>;
536
+ translate: (data: unknown) => unknown;
537
+ parse: (expr: string) => unknown;
538
+ getChangedKeys: () => ReadonlySet<string>;
539
+ }
540
+ /**
541
+ * Mutable reference cell — used for `signature` and `rendered` on `ViewCtx`.
542
+ * Wraps mutable state in a `Ref<T>` to avoid getter/setter syntax.
543
+ */
544
+ interface Ref<T> {
545
+ value: T;
546
+ }
547
+ /**
548
+ * Functional view context.
549
+ *
550
+ * Passed as the first argument to every view setup function. Provides
551
+ * framework APIs without `this` binding.
552
+ */
553
+ interface ViewCtx {
554
+ /** View ID (same as owner frame ID) */
941
555
  id: string;
942
- /**
943
- * Owner frame,
944
- * Frame instance holding current view.
945
- * May be numeric placeholder 0 before view initialization completes.
946
- * TODO: Migrate numeric placeholder 0 to undefined or null
947
- */
948
- owner: FrameInterface | number;
949
- /**
950
- * Updater instance managing view data binding and DOM rendering.
951
- */
952
- updater: UpdaterInterface;
953
- /**
954
- * Signature: > 0 means active, incremented on render, 0 = destroyed */
955
- signature: number;
556
+ /** Owner frame reference */
557
+ owner: FrameObj;
558
+ /** Updater API for data binding */
559
+ updater: UpdaterApi;
560
+ /** Signature: >0 means active, incremented on render, 0 = destroyed */
561
+ signature: Ref<number>;
956
562
  /** Whether rendered at least once */
957
- rendered?: boolean;
958
- /**
959
- * View template function. Receives data + viewId + refData and a set of
960
- * encoder helpers wired in by the Updater, and returns the rendered HTML.
961
- */
962
- template?: ViewTemplate | VDomTemplate;
963
- /**
964
- * Mixin object array for extending view functionality.
965
- * Framework merges properties and methods from mixins into view prototype.
966
- * Event method conflicts are automatically merged into sequential calls.
967
- */
968
- mixins?: Record<string, unknown>[];
563
+ rendered: Ref<boolean>;
564
+ /** View template function (accessed via getTemplate/setTemplate) */
565
+ getTemplate(): ViewTemplate | VDomTemplate | undefined;
566
+ setTemplate(v: ViewTemplate | VDomTemplate | undefined): void;
969
567
  /** Location observation config */
970
568
  locationObserved: ViewLocationObserved;
971
- /** Observed state keys */
972
- observedStateKeys?: string[];
569
+ /** Observed state keys (accessed via getObservedStateKeys/setObservedStateKeys) */
570
+ getObservedStateKeys(): string[] | undefined;
571
+ setObservedStateKeys(v: string[] | undefined): void;
973
572
  /** Resource map */
974
573
  resources: Record<string, ViewResourceEntry>;
975
- /** Selector event map: eventType -> selector list */
976
- eventSelectorMap: Record<string, ViewEventSelectorEntry>;
977
- /** Event object map: eventType -> bitmask */
978
- eventObjectMap: Record<string, number>;
979
- /** Global event list */
980
- globalEventList: ViewGlobalEventEntry[];
981
- /** Whether endUpdate has been called (1 = pending) */
982
- endUpdatePending?: number;
574
+ /** Internal emitter for lifecycle events ("destroy", "render", etc.) */
575
+ emitter: EmitterApi;
576
+ /** EndUpdate pending flag (accessed via getEndUpdatePending/setEndUpdatePending) */
577
+ getEndUpdatePending(): number | undefined;
578
+ setEndUpdatePending(v: number | undefined): void;
983
579
  /** Last rendered VDOM tree (only used when virtualDom is enabled) */
984
580
  vdom?: VDomNode;
985
- /** Render method (wrapped) */
986
- render(): void;
987
- /**
988
- * Init method called after view is mounted.
989
- * Used for initialization logic.
990
- * Framework passes two arguments during actual invocation:
991
- * - initParams: initialization parameter object
992
- * - options: contains `node: Element` and `deep: boolean`
993
- */
994
- init(): void;
995
581
  /** Wrapped render method */
996
582
  renderMethod?: AnyFunc;
997
- /** endUpdate pending flag */
998
- endUpdatePendingFlag?: number;
999
- /**
1000
- * Notify view that HTML update is about to begin for a specific region.
1001
- * Framework unmounts child Frames in that region to prevent DOM diff from operating on unmounted nodes.
1002
- * @param id Region node ID to update, defaults to current view
1003
- */
1004
- beginUpdate: (id?: string) => void;
1005
- /**
1006
- * Notify view that HTML update has completed for a specific region.
1007
- * Framework mounts child Frames in that region and executes deferred invoke queue.
1008
- * @param id Region node ID that finished updating, defaults to current view
1009
- * @param inner Whether this is an internal framework call
1010
- */
1011
- endUpdate: (id?: string, inner?: boolean) => void;
1012
- /**
1013
- * Wrap async callback to ensure it only executes if view is not destroyed.
1014
- * In SPAs, async callbacks (e.g., setTimeout, AJAX) may execute after view is destroyed,
1015
- * causing errors when manipulating DOM.
1016
- * After wrapping with `wrapAsync`, framework automatically checks view state and only executes callback if view is alive.
1017
- * @param fn Callback function to wrap
1018
- * @param context `this` binding for callback execution, defaults to view itself
1019
- */
1020
- wrapAsync: <Fn extends AnyFunc>(fn: Fn, context?: unknown) => (...args: Parameters<Fn>) => ReturnType<Fn> | undefined;
1021
- /**
1022
- * Listen for URL bar changes, supports two calling modes:
1023
- * - `observeLocation("page,size", true)` pass parameter keys (comma-separated) and whether to observe path
1024
- * - `observeLocation({ params: ["page", "size"], path: true })` pass config object
1025
- * View automatically re-renders when observed parameters or path change.
1026
- * @param params Parameter keys to observe, supports comma-separated string, string array, or config object
1027
- * @param observePath Whether to observe path changes
1028
- */
1029
- observeLocation: (params: string | string[] | Record<string, unknown>, observePath?: boolean) => void;
1030
- /**
1031
- * Observe data changes for specified keys in State.
1032
- * View automatically re-renders when observed keys are updated via `State.digest()`.
1033
- * @param keys Comma-separated key string or string array
1034
- */
1035
- observeState: (keys: string | string[]) => void;
1036
- /**
1037
- * Hand over resource to current view for lifecycle management.
1038
- * Framework automatically calls resource's destroy method at appropriate time when view unmounts or re-renders.
1039
- * @param key Unique key for managed resource; if key already manages different resource, old resource is auto-destroyed
1040
- * @param resource Resource object to manage
1041
- * @param destroyOnRender Whether to auto-destroy resource when render method is called; Service instances typically need auto-destroy on render
1042
- */
1043
- capture: (key: string, resource?: unknown, destroyOnRender?: boolean) => unknown;
1044
- /**
1045
- * Release managed resource, returns the resource object regardless of destruction state.
1046
- * @param key Managed resource key
1047
- * @param destroy Whether to destroy resource (call its destroy method), defaults to true
1048
- */
1049
- release: (key: string, destroy?: boolean) => unknown;
1050
- /**
1051
- * Set leave prompt, e.g., when form has unsaved changes.
1052
- * Can prompt user to choose between leaving directly or saving before leaving.
1053
- * Framework calls condition function during route changes (change phase) and page unloads (beforeunload).
1054
- * Navigation is prevented if condition returns true.
1055
- * @param message Leave prompt message
1056
- * @param condition Function to determine whether to show leave prompt; returns true to prevent navigation
1057
- */
1058
- leaveTip: (message: string, condition: () => boolean) => void;
1059
- /**
1060
- * Assign method for incremental DOM updates.
1061
- * Framework uses DOM diff (in-memory real DOM diff) to update only changed portions,
1062
- * automatically handling child view mounting and unmounting.
1063
- * Returns true if DOM changed, undefined if no change.
1064
- * @param options Incremental update config, used internally by framework
1065
- */
1066
- assign?: (options?: unknown) => boolean | undefined;
1067
- /**
1068
- * Triggered when view is destroyed.
1069
- */
1070
- onDestroy?: (e?: ChangeEvent) => void;
1071
- /**
1072
- * Triggered when render method is called.
1073
- */
1074
- onRender?: (e?: ChangeEvent) => void;
1075
- /**
1076
- * Inherit View to create new view subclass.
1077
- * Supports props.make constructor, props.mixins, and event methods (e.g., `'name<click>'`).
1078
- * @param props Prototype object containing init, render, and other methods
1079
- * @param statics Object of static methods or properties
1080
- */
1081
- extend?<TProps = object, TStatics = object>(props?: ExtendThisType<TProps & ViewInterface>, statics?: TStatics): ViewInterface & TStatics;
1082
- /**
1083
- * Merge multiple mixin objects into View prototype.
1084
- * Existing properties are not overwritten; event method conflicts are automatically merged into sequential calls.
1085
- * @param args Mixin object list
1086
- */
1087
- merge?(...args: ExtendThisType<ViewInterface>[]): ViewInterface;
1088
- navigate?: (path: string, params?: Record<string, unknown>) => void;
583
+ /** Event handlers returned by setup (accessed via getEvents/setEvents) */
584
+ getEvents(): Record<string, AnyFunc> | undefined;
585
+ setEvents(v: Record<string, AnyFunc> | undefined): void;
586
+ /** Cleanup functions registered by useEffect */
587
+ cleanups: Array<() => void>;
588
+ /** assign function returned by setup (accessed via getAssign/setAssign) */
589
+ getAssign(): ((options?: unknown) => boolean | undefined) | undefined;
590
+ setAssign(v: ((options?: unknown) => boolean | undefined) | undefined): void;
591
+ render(): void;
592
+ init(params?: unknown): void;
593
+ beginUpdate(id?: string): void;
594
+ endUpdate(id?: string, inner?: boolean): void;
595
+ wrapAsync<Fn extends AnyFunc>(fn: Fn, context?: unknown): (...args: Parameters<Fn>) => ReturnType<Fn> | undefined;
596
+ observeLocation(params: string | string[] | Record<string, unknown>, observePath?: boolean): void;
597
+ observeState(keys: string | string[]): void;
598
+ capture(key: string, resource?: unknown, destroyOnRender?: boolean): unknown;
599
+ release(key: string, destroy?: boolean): unknown;
600
+ leaveTip(message: string, condition: () => boolean): void;
601
+ fire(event: string, data?: Record<string, unknown>, remove?: boolean, lastToFirst?: boolean): void;
602
+ on(event: string, handler: AnyFunc): () => void;
603
+ off(event: string, handler?: AnyFunc): void;
1089
604
  }
1090
- type ExtendThisType<T> = Record<string, unknown> & ThisType<T>;
1091
605
  /**
1092
- * Minimal Frame interface needed by View.
1093
- * Frame (View Frame) is a view container managing view mount, unmount, and parent-child hierarchy.
1094
- * Each Frame corresponds to one DOM node, associated with view via v-lark attribute.
606
+ * Functional frame object.
607
+ *
608
+ * Created by `createFrame()`. Uses `ViewCtx` for its view reference.
1095
609
  */
1096
- interface FrameInterface extends EventEmitterInterface<FrameInterface> {
1097
- /**
1098
- * DOM node ID where Frame resides.
1099
- */
610
+ interface FrameObj {
1100
611
  id: string;
1101
- /**
1102
- * View module path currently rendered by this Frame, e.g., "app/views/default".
1103
- */
1104
- readonly viewPath?: string;
1105
- /**
1106
- * Parent Frame ID, undefined if this is a top-level Frame.
1107
- */
612
+ /** View path (accessed via getViewPath) */
613
+ getViewPath(): string | undefined;
1108
614
  readonly parentId: string | undefined;
1109
- /**
1110
- * Mount view to current Frame.
1111
- * Framework loads view class, creates instance, and renders view.
1112
- * @param viewPath View module path, e.g., "app/views/default"
1113
- * @param viewInitParams Parameters passed during view initialization, accessible in view's init method
1114
- */
615
+ view: ViewCtx | undefined;
616
+ invokeList: FrameInvokeEntry[];
617
+ signature: number;
618
+ destroyed: number;
619
+ hasAltered: number;
620
+ originalTemplate?: string;
621
+ holdFireCreated: number;
622
+ childrenCreated: number;
623
+ childrenAlter: number;
624
+ childrenMap: Record<string, string>;
625
+ childrenCount: number;
626
+ readyCount: number;
627
+ readyMap: Set<string>;
628
+ emitter: EmitterApi;
629
+ /** Dispatcher visit tag (set during dispatcherUpdate walk) */
630
+ dispatcherUpdateTag?: number;
1115
631
  mountView(viewPath: string, viewInitParams?: Record<string, unknown>): void;
1116
- /**
1117
- * Unmount view from current Frame, triggers view's destroy event and cleans up resources.
1118
- */
1119
632
  unmountView(): void;
1120
- /**
1121
- * Mount child Frame on specified DOM node and render view.
1122
- * @param frameId DOM node ID for rendering
1123
- * @param viewPath View path
1124
- * @param viewInitParams Parameters passed during view initialization
1125
- */
1126
- mountFrame: (frameId: string, viewPath: string, viewInitParams?: Record<string, unknown>) => FrameInterface;
1127
- /**
1128
- * Unmount child Frame from specified DOM node.
1129
- * @param id DOM node ID, defaults to current Frame if omitted
1130
- */
1131
- unmountFrame: (id?: string) => void;
1132
- /**
1133
- * Render all child views under specified node (scans v-lark attributes and mounts).
1134
- * @param zoneId DOM node ID, defaults to current Frame
1135
- */
1136
- mountZone: (zoneId?: string) => void;
1137
- /**
1138
- * Unmount all child views under specified node.
1139
- * @param zoneId DOM node ID, defaults to current Frame
1140
- */
1141
- unmountZone: (zoneId?: string) => void;
1142
- /**
1143
- * Get ancestor Frame, defaults to parent Frame (level=1).
1144
- * @param level Levels to traverse upward, defaults to 1
1145
- */
1146
- parent(level?: number): FrameInterface | undefined;
1147
- /**
1148
- * Invoke specified method on view in current Frame.
1149
- * If view is not rendered yet, invocation is deferred until render completes.
1150
- * @param name Method name
1151
- * @param args Arguments array passed to method
1152
- */
1153
- invoke: (name: string, args?: unknown[]) => unknown;
1154
- /**
1155
- * Triggered when all descendant views have been created.
1156
- */
1157
- onCreated?: (e?: ChangeEvent) => void;
1158
- /**
1159
- * Triggered when descendant views change.
1160
- */
1161
- onAlter?: (e?: ChangeEvent) => void;
1162
- /**
1163
- * Get Frame instance by ID, returns undefined if not exists.
1164
- * @param id Frame's DOM node ID
1165
- */
1166
- get?(id: string): FrameInterface | undefined;
1167
- /**
1168
- * Get all Frame instances map for current page.
1169
- */
1170
- getAll?(): Map<string, FrameInterface>;
1171
- /**
1172
- * Triggered when Frame is created and registered.
1173
- */
1174
- onAdd?: (e?: FrameStaticEvent) => void;
1175
- /**
1176
- * Triggered when Frame is destroyed and unregistered.
1177
- */
1178
- onRemove?: (e?: FrameStaticEvent) => void;
1179
- view: ViewInterface | undefined;
1180
- /**
1181
- * Get ID array of all child Frames for current Frame.
1182
- * Note: ID positions in array are not fixed.
1183
- */
1184
- children: () => string[];
1185
- invokeList: FrameInvokeEntry[];
633
+ mountFrame(frameId: string, viewPath: string, viewInitParams?: Record<string, unknown>): FrameObj;
634
+ unmountFrame(id?: string): void;
635
+ mountZone(zoneId?: string): void;
636
+ unmountZone(zoneId?: string): void;
637
+ parent(level?: number): FrameObj | undefined;
638
+ invoke(name: string, args?: unknown[]): unknown;
639
+ children(): string[];
640
+ on(event: string, handler: AnyFunc): FrameObj;
641
+ off(event: string, handler?: AnyFunc): FrameObj;
642
+ fire(event: string, data?: Record<string, unknown>): FrameObj;
1186
643
  }
1187
644
  /**
1188
- * Minimal Updater interface needed by View.
1189
- * View updater responsible for view data binding and data/page updates.
1190
- * Each View instance has an Updater, triggering data/page updates via set/digest.
1191
- * Internally executes complete pipeline: template rendering DOM diff (in-memory real DOM diff) → DOM operations.
645
+ * View setup function the functional API for defining views.
646
+ *
647
+ * Called once on mount with a `ViewCtx` and optional init params.
648
+ * Returns a descriptor with `template`, `events`, and optional `assign`.
1192
649
  */
1193
- interface UpdaterInterface {
1194
- /**
1195
- * Get data that has been set.
1196
- * Returns complete data object if key is omitted, otherwise returns value for specified key.
1197
- * @param key Data key name, omitted returns complete data object
1198
- */
1199
- get: <T = unknown>(key?: string) => T;
1200
- /**
1201
- * Set data and track changed keys.
1202
- * After set, must explicitly call `digest()` to commit changes to page.
1203
- * Returns this for chaining.
1204
- * @param data Data object, e.g., `{ a: 1, b: 2 }`
1205
- * @param excludes Set of keys to exclude from change tracking
1206
- */
1207
- set: (data: Record<string, unknown>, excludes?: ReadonlySet<string>) => UpdaterInterface;
1208
- /**
1209
- * Trigger page re-render.
1210
- * After set, must explicitly call `digest()` to commit changes to page.
1211
- * Internally executes complete pipeline: template rendering → DOM diff (in-memory real DOM diff) → DOM operations.
1212
- * @param data Optional data object, if provided calls `set()` first to set data
1213
- * @param excludes Set of keys to exclude from change tracking
1214
- * @param callback Callback executed after render completes
1215
- */
1216
- digest: (data?: Record<string, unknown>, excludes?: ReadonlySet<string>, callback?: () => void) => void;
1217
- /**
1218
- * Save a snapshot of current data for altered() detection.
1219
- * Works with `altered()` method to detect whether data has changed.
1220
- */
1221
- snapshot: () => UpdaterInterface;
1222
- /**
1223
- * Check if data has changed since last snapshot.
1224
- * Returns undefined if `snapshot()` has not been called.
1225
- */
1226
- altered: () => boolean | undefined;
1227
- /** Ref data for template rendering */
1228
- refData: Record<string, unknown>;
1229
- /**
1230
- * Translate raw reference data starting with @ symbol in template.
1231
- * Replaces `{{@refData}}` with actual value from refData.
1232
- * @param data Reference data to translate
1233
- */
1234
- translate(data: unknown): unknown;
1235
- /**
1236
- * Parse expression string.
1237
- * @param expr Expression string to parse
1238
- */
1239
- parse(expr: string): unknown;
1240
- }
650
+ type ViewSetup = (ctx: ViewCtx, params?: unknown) => {
651
+ template?: ViewTemplate | VDomTemplate;
652
+ events?: Record<string, AnyFunc>;
653
+ assign?: (options?: unknown) => boolean | undefined;
654
+ };
1241
655
  /**
1242
656
  * Data payload interface wrapping API request response data, providing read/write methods.
1243
657
  * Payload instances are created internally by Service, developers access via all/one/save callbacks.
1244
658
  */
1245
- interface PayloadInterface {
659
+ interface PayloadApi {
1246
660
  /**
1247
661
  * Get data from Payload by key.
1248
662
  * @param key Data key name
@@ -1257,7 +671,7 @@ interface PayloadInterface {
1257
671
  * @param keyOrData Key/value string, data object, or endpoint metadata object
1258
672
  * @param value Value when first parameter is a key
1259
673
  */
1260
- set(keyOrData: string | Record<string, unknown> | ServiceMetaEntry, value?: unknown): PayloadInterface;
674
+ set(keyOrData: string | Record<string, unknown> | ServiceMetaEntry, value?: unknown): PayloadApi;
1261
675
  data: Record<string, unknown>;
1262
676
  cacheInfo?: ServiceCacheInfo;
1263
677
  }
@@ -1274,43 +688,22 @@ interface ChangeEvent {
1274
688
  */
1275
689
  readonly keys?: ReadonlySet<string>;
1276
690
  }
1277
- /**
1278
- * Event emitter interface providing on/off/fire methods for publish-subscribe pattern.
1279
- */
1280
- interface EventEmitterInterface<T = unknown> {
1281
- /**
1282
- * Bind event listener, calls handler when event is triggered.
1283
- * @param name Event name
1284
- * @param fn Event handler function
1285
- */
1286
- on(name: string, fn: (this: T, e?: ChangeEvent) => void): EventEmitterInterface<T>;
1287
- /**
1288
- * Unbind event listener, removes all handlers for event if no handler function is provided.
1289
- * @param name Event name
1290
- * @param fn Optional event handler function, if omitted removes all handlers
1291
- */
1292
- off(name: string, fn?: AnyFunc): EventEmitterInterface<T>;
1293
- /**
1294
- * Fire event, executes all bound handlers, automatically adds type property to event data.
1295
- * Supports removing all handlers after firing.
1296
- * Supports executing handler list in reverse order.
1297
- * @param name Event name
1298
- * @param data Event data object
1299
- * @param remove Whether to remove all handlers after firing
1300
- * @param lastToFirst Whether to execute handler list in reverse order
1301
- */
1302
- fire(name: string, data?: Record<string, unknown>, remove?: boolean, lastToFirst?: boolean): EventEmitterInterface<T>;
1303
- }
1304
691
  /**
1305
692
  * Global state interface providing cross-view data sharing and data change notification capabilities.
1306
693
  * State is a singleton object managing app-level state data via get/set/digest.
1307
- * Supports `clean()` method to create a mixin for automatic cleanup on view destruction.
694
+ * Supports `clean()` method for automatic cleanup on view destruction.
1308
695
  *
1309
696
  * Use State for SIMPLE cross-view data (lightweight shared values: counters,
1310
697
  * toggles, page title, session info, etc.). For COMPLEX reactive state —
1311
- * handlers, derived data, or fine-grained subscriptions — use `create` instead.
698
+ * handlers, derived data, or fine-grained subscriptions — use `createStore` instead.
1312
699
  */
1313
- interface StateInterface extends EventEmitterInterface<StateInterface> {
700
+ interface StateApi {
701
+ /** Bind event listener */
702
+ on(event: string, handler: (e?: ChangeEvent) => void): this;
703
+ /** Unbind event listener */
704
+ off(event: string, handler?: AnyFunc): this;
705
+ /** Fire event */
706
+ fire(event: string, data?: Record<string, unknown>, remove?: boolean, lastToFirst?: boolean): this;
1314
707
  /**
1315
708
  * Get data from global state, returns complete state object if key is omitted.
1316
709
  * @param key Data key name, omitted returns complete state object
@@ -1324,16 +717,14 @@ interface StateInterface extends EventEmitterInterface<StateInterface> {
1324
717
  */
1325
718
  set(data: Record<string, unknown>, excludes?: ReadonlySet<string>): this;
1326
719
  /**
1327
- * Clean data for specified keys in State, can only be used in view's mixins.
1328
- * For example `mixins: [State.clean("a,b")]`.
1329
- * Keys registered via this method are automatically cleaned when view is destroyed,
1330
- * and corresponding key reference counts are decremented; data is auto-deleted when count reaches zero.
720
+ * Create a cleanup function for state keys on view destroy.
721
+ * Call inside setup: `State.clean("keys")(ctx)`
1331
722
  * @param keys Comma-separated key string
1332
- * @returns Object with make method, called by mixins mechanism
723
+ * @returns Function that registers destroy cleanup on a ctx
1333
724
  */
1334
- clean(keys: string): {
1335
- make: AnyFunc;
1336
- };
725
+ clean(keys: string): (ctx: {
726
+ on: (event: string, handler: () => void) => void;
727
+ }) => void;
1337
728
  /**
1338
729
  * Detect data changes and dispatch changed event.
1339
730
  * After set, must explicitly call `digest()` to dispatch changed event and notify views to update.
@@ -1384,20 +775,18 @@ interface ServiceMetaEntry {
1384
775
  */
1385
776
  cache?: number;
1386
777
  /**
1387
- * Before-fetch hook,
778
+ * Before-fetch hook.
1388
779
  * Hook function called before request is sent, can process request data.
1389
- * `this` points to current Payload instance.
1390
780
  * @param payload Data carrier for current request
1391
781
  */
1392
- before?: (this: PayloadInterface, payload: PayloadInterface) => void;
782
+ before?: (payload: PayloadApi) => void;
1393
783
  /**
1394
784
  * After-fetch hook.
1395
785
  * Hook function called after request succeeds, before data is passed to view.
1396
786
  * Can process response data in this method.
1397
- * `this` points to current Payload instance.
1398
787
  * @param payload Data carrier for current request
1399
788
  */
1400
- after?: (this: PayloadInterface, payload: PayloadInterface) => void;
789
+ after?: (payload: PayloadApi) => void;
1401
790
  /** Clean keys on destroy,
1402
791
  * Comma-separated endpoint name string for clearing other endpoints' cache.
1403
792
  * For example, if an endpoint creates new data,
@@ -1406,7 +795,7 @@ interface ServiceMetaEntry {
1406
795
  */
1407
796
  cleanKeys?: string;
1408
797
  /** Additional properties */
1409
- [key: string]: unknown;
798
+ [k: string]: unknown;
1410
799
  }
1411
800
  /** Cache info attached to Payload entity */
1412
801
  interface ServiceCacheInfo {
@@ -1421,7 +810,7 @@ interface ServiceCacheInfo {
1421
810
  /** Timestamp when cached */
1422
811
  time: number;
1423
812
  }
1424
- interface FrameworkInterface {
813
+ interface FrameworkApi {
1425
814
  /**
1426
815
  * Read framework configuration.
1427
816
  * - Without arguments: returns the complete config object.
@@ -1443,49 +832,26 @@ interface FrameworkInterface {
1443
832
  * @param cfg Config object
1444
833
  */
1445
834
  boot(cfg: FrameworkConfig): void;
1446
- /**
1447
- * Convert array to hash map object.
1448
- * - Simple array: `Framework.toMap([1,2,3])` => `{1:1, 2:1, 3:1}`
1449
- * - Object array: `Framework.toMap([{id:20},{id:30}], 'id')` => `{20:{id:20}, 30:{id:30}}`
1450
- * @param list Source array
1451
- * @param key Use object's key value from array as map key
1452
- */
1453
- toMap<T>(list: T[] | null | undefined, key?: keyof T): Record<string, T | number>;
1454
- /**
1455
- * Execute methods in try-catch manner, catches exceptions.
1456
- * Returns return value of last successfully executed method.
1457
- * @param fns Function or function array
1458
- * @param args Arguments array passed to functions
1459
- * @param context `this` binding during function execution
1460
- * @param configError Optional error callback, receives the caught exception
1461
- */
1462
- toTry(fns: AnyFunc | AnyFunc[], args?: unknown[], context?: unknown, configError?: (e: unknown) => void): unknown;
1463
835
  /**
1464
836
  * Convert path and params to URL string.
1465
- * Example: `Framework.toUrl('/xxx/', {a:'b',c:'d'})` => `/xxx/?a=b&c=d`
837
+ * Example: `Framework.toUri('/xxx/', {a:'b',c:'d'})` => `/xxx/?a=b&c=d`
1466
838
  * @param path Path string
1467
839
  * @param params Params object
1468
840
  * @param keepEmpty Set of keys whose empty values should be preserved
1469
841
  */
1470
- toUrl(path: string, params?: Record<string, unknown>, keepEmpty?: Set<string>): string;
842
+ toUri(path: string, params?: Record<string, unknown>, keepEmpty?: Set<string>): string;
1471
843
  /**
1472
844
  * Parse URL string to path and params object.
1473
- * Example: `Framework.parseUrl('/xxx/?a=b&c=d')` => `{path:'/xxx/', params:{a:'b',c:'d'}}`
845
+ * Example: `Framework.parseUri('/xxx/?a=b&c=d')` => `{path:'/xxx/', params:{a:'b',c:'d'}}`
1474
846
  * @param url URL string
1475
847
  */
1476
- parseUrl(url: string): ParsedUri;
848
+ parseUri(url: string): ParsedUri;
1477
849
  /**
1478
850
  * Merge source object properties into target object.
1479
851
  * @param target Target object
1480
852
  * @param sources One or more source objects
1481
853
  */
1482
- mix<T extends object>(target: T, ...sources: Record<string, unknown>[]): T;
1483
- /**
1484
- * Check if object has specified own property (safe hasOwnProperty).
1485
- * @param owner Object to check, supports undefined/null
1486
- * @param prop Property key name
1487
- */
1488
- has<T extends object>(owner: T | undefined | null, prop: PropertyKey): boolean;
854
+ assign<T extends object>(target: T, ...sources: Record<string, unknown>[]): T;
1489
855
  /**
1490
856
  * Get enumerable property keys of object as array.
1491
857
  * @param src Source object
@@ -1497,39 +863,24 @@ interface FrameworkInterface {
1497
863
  * @param node Node or node ID
1498
864
  * @param container Container node or node ID
1499
865
  */
1500
- inside(node: HTMLElement | string, container: HTMLElement | string): boolean;
1501
- /**
1502
- * Shorthand for document.getElementById.
1503
- * Returns directly if Element is passed.
1504
- * @param id Node ID or Element object
1505
- */
1506
- node(id: string | Element | null): Element | null;
866
+ nodeInside(node: HTMLElement | string, container: HTMLElement | string): boolean;
1507
867
  /**
1508
868
  * Ensure DOM element has an ID, auto-generates one if missing.
1509
869
  * Returns element's ID.
1510
870
  * @param node DOM element object
1511
871
  */
1512
- nodeId(node: HTMLElement): string;
872
+ ensureNodeId(node: HTMLElement): string;
1513
873
  /**
1514
874
  * Load modules using configured module loader.
1515
875
  * @param names Module names, supports string or string array
1516
876
  * @param callback Callback after modules are loaded
1517
877
  */
1518
878
  use(names: string | string[], callback?: (...modules: unknown[]) => void): void;
1519
- /**
1520
- * Dynamically inject CSS styles into page. Returns cleanup function to remove injected styles.
1521
- * Supports single and batch injection.
1522
- * - `Framework.applyStyle("my-style", "body { color: red; }")` single injection
1523
- * - `Framework.applyStyle(["style1", "css1", "style2", "css2"])` batch injection
1524
- * @param styleIdOrPairs Style unique key or [id1, css1, id2, css2, ...] batch array
1525
- * @param cssText CSS style string (only used when first param is string)
1526
- */
1527
- applyStyle(styleIdOrPairs: string | string[], cssText?: string): () => void;
1528
879
  /**
1529
880
  * Generate globally unique identifier (GUID).
1530
881
  * @param prefix GUID prefix, defaults to "lark-"
1531
882
  */
1532
- guid(prefix?: string): string;
883
+ generateId(prefix?: string): string;
1533
884
  /**
1534
885
  * Create async callback validity marker.
1535
886
  * Returns a check function; if host object is unmarked (e.g., view re-rendered), check function returns false, preventing expired async callbacks from executing.
@@ -1559,7 +910,7 @@ interface FrameworkInterface {
1559
910
  * @param eventType Event type string
1560
911
  * @param eventInit CustomEvent init options
1561
912
  */
1562
- dispatch(target: EventTarget, eventType: string, eventInit?: CustomEventInit): void;
913
+ dispatchEvent(target: EventTarget, eventType: string, eventInit?: CustomEventInit): void;
1563
914
  /**
1564
915
  * Execute a function in try-catch with chunked scheduling.
1565
916
  * @param fn Function to execute
@@ -1579,31 +930,31 @@ interface FrameworkInterface {
1579
930
  /** Wait result: timeout or view not found */
1580
931
  WAIT_TIMEOUT_OR_NOT_FOUND: number;
1581
932
  /**
1582
- * Base class with EventEmitter.
1583
- * Inherits EventEmitter for use as a base class in the framework.
933
+ * Emitter factory function.
934
+ * Use `createEmitter()` to create emitter instances.
1584
935
  */
1585
- Base: typeof EventEmitter;
936
+ createEmitter: typeof createEmitter;
1586
937
  /**
1587
- * View class.
1588
- * Use `View.extend()` to create subclasses.
938
+ * View factory function.
939
+ * Use `defineView()` to create view setups.
1589
940
  */
1590
- View: typeof View;
941
+ defineView: typeof defineView;
1591
942
  /**
1592
- * Cache class.
1593
- * Use `new Cache()` to create cache instances.
943
+ * Cache factory function.
944
+ * Use `createCache()` to create cache instances.
1594
945
  */
1595
- Cache: typeof Cache;
946
+ createCache: typeof createCache;
1596
947
  /**
1597
948
  * Global state object.
1598
949
  */
1599
- State: StateInterface;
950
+ State: StateApi;
1600
951
  /**
1601
952
  * Router object.
1602
953
  */
1603
- Router: RouterInterface;
954
+ Router: RouterApi;
1604
955
  /**
1605
- * Frame class.
1606
- * Frame tree for view lifecycle management. Do not extend or instantiate directly.
956
+ * Frame singleton.
957
+ * Frame tree for view lifecycle management. Use createFrame() to create frames.
1607
958
  */
1608
959
  Frame: typeof Frame;
1609
960
  }
@@ -1687,19 +1038,25 @@ interface FrameworkConfig {
1687
1038
  /**
1688
1039
  * Cross-site (micro-frontend) configuration list.
1689
1040
  * Defines remote projects that can be loaded via Module Federation.
1690
- * Also accessible via `window.crossConfigs` for build-time injection.
1041
+ * Also accessible via `window.crossSites` for build-time injection.
1691
1042
  */
1692
- crossConfigs?: CrossSiteConfig[];
1043
+ crossSites?: CrossSiteConfig[];
1693
1044
  /** Default false. */
1694
1045
  virtualDom?: boolean;
1695
- /** Dynamic config access, custom config items */
1696
- [key: string]: unknown;
1046
+ /**
1047
+ * Enable Frame Devtool Bridge (default: true).
1048
+ * When true, installs a postMessage listener so the Lark DevTool browser
1049
+ * extension can inspect the frame tree. Set to false to suppress the bridge
1050
+ * (and any extension-related errors) in environments where the extension
1051
+ * is not available or causes issues.
1052
+ */
1053
+ devtool?: boolean;
1697
1054
  }
1698
1055
  interface RouteViewConfig {
1699
1056
  /** View path */
1700
1057
  view: string;
1701
1058
  /** Additional properties merged into location */
1702
- [key: string]: unknown;
1059
+ [k: string]: unknown;
1703
1060
  }
1704
1061
  /**
1705
1062
  * Configuration for a remote (cross-site) project in the micro-frontend setup.
@@ -1729,8 +1086,8 @@ interface DomElement extends Element {
1729
1086
  }
1730
1087
  /** Element with frame binding */
1731
1088
  interface FrameBoundElement extends HTMLElement {
1732
- /** Frame instance bound to this element */
1733
- frame?: FrameInterface;
1089
+ /** Frame object bound to this element */
1090
+ frame?: FrameObj;
1734
1091
  /** Whether frame is bound (1 = bound) */
1735
1092
  frameBound?: number;
1736
1093
  /** View rendered flag */
@@ -1753,81 +1110,37 @@ interface CompileOptions {
1753
1110
  }
1754
1111
 
1755
1112
  /**
1756
- * Cache class with LFU-style eviction.
1757
- * Keys are prefixed with SPLITTER for namespace isolation.
1113
+ * Create an LFU-style bounded cache.
1114
+ *
1115
+ * @param options - Cache configuration
1116
+ * @returns A cache API object with `get`, `set`, `del`, `has`, `clear`, `forEach`, `size`.
1758
1117
  *
1759
1118
  * @example
1760
- * const cache = new Cache({ maxSize: 20, bufferSize: 5 });
1119
+ * const cache = createCache({ maxSize: 20, bufferSize: 5 });
1761
1120
  * cache.set('user', { name: 'Alice' });
1762
1121
  * const user = cache.get('user');
1763
1122
  * cache.has('user'); // true
1764
1123
  * cache.del('user');
1765
1124
  */
1766
- declare class Cache<T = unknown> implements CacheInterface<T> {
1767
- /** Cache entries array */
1768
- private entries;
1769
- /** Fast lookup: prefixed key -> entry */
1770
- private lookup;
1771
- /** Buffer size for eviction */
1772
- private readonly bufferSize;
1773
- /** Maximum cache size */
1774
- private readonly maxSize;
1775
- /** Total capacity (maxSize + bufferSize) */
1776
- private readonly capacity;
1777
- /** Callback when entry is removed */
1778
- private readonly onRemove?;
1779
- /** Sort comparator */
1780
- private readonly comparator;
1781
- constructor(options?: CacheOptions<T>);
1782
- /** Prefix a key with SPLITTER for namespace isolation */
1783
- private prefixKey;
1784
- /**
1785
- * Get a cached value by key.
1786
- * Updates frequency and timestamp for cache sorting.
1787
- */
1788
- get(key: string): T | undefined;
1789
- /**
1790
- * Iterate all cached values.
1791
- */
1792
- forEach(callback: (value: T | undefined) => void): void;
1793
- /**
1794
- * Set or update a cached value.
1795
- * If key already exists, updates value and increments frequency.
1796
- * If cache exceeds capacity, triggers eviction.
1797
- */
1798
- set(key: string, value: T): void;
1799
- /**
1800
- * Delete a cached entry. Removes it immediately from both the lookup map
1801
- * and the entries array so the GC can reclaim the value without waiting
1802
- * for the next eviction sweep.
1803
- */
1804
- del(key: string): void;
1805
- /**
1806
- * Check if a key exists in cache.
1807
- */
1808
- has(key: string): boolean;
1809
- /** Get current cache size */
1810
- get size(): number;
1811
- /** Clear all entries */
1812
- clear(): void;
1813
- /**
1814
- * Evict the `bufferSize` worst entries from the cache.
1815
- *
1816
- * Uses single-pass partial selection (O(n·k)) instead of sorting the entire
1817
- * `entries` array (O(n log n)). For the typical `bufferSize = 5` this is
1818
- * effectively a linear scan with at most 5 in-bucket comparisons per
1819
- * iteration — and it avoids mutating the rest of `entries`.
1820
- */
1821
- private evictEntries;
1822
- }
1125
+ declare function createCache<T = unknown>(options?: CacheOptions<T>): CacheApi<T>;
1823
1126
 
1824
1127
  /** Mark framework as booted (called from Framework.boot) */
1825
1128
  declare function markBooted(): void;
1129
+ /**
1130
+ * DEBUG: deduplicate direct-mutation warnings.
1131
+ *
1132
+ * Previously this was delayed by 500ms so multiple writes to the same key
1133
+ * would coalesce — but users complained the warning didn't show up at the
1134
+ * point of the mutation. We now warn synchronously and dedupe by key, so
1135
+ * the first hit shows up immediately at the right place in the stack trace.
1136
+ * `clearNotify(key)` resets the dedup flag once the legitimate
1137
+ * `State.set` + `State.digest` actually runs.
1138
+ */
1826
1139
  /**
1827
1140
  * Observable in-memory data object.
1828
1141
  * Provides get/set/digest/diff/clean methods for cross-view data sharing.
1829
1142
  */
1830
- declare const State: StateInterface;
1143
+ declare const State: StateApi;
1831
1144
 
1832
1145
  /**
1833
1146
  * Router with two-phase change confirmation (supports history and hash modes).
@@ -1837,7 +1150,7 @@ declare const State: StateInterface;
1837
1150
  * const loc = Router.parse();
1838
1151
  * const diff = Router.diff();
1839
1152
  */
1840
- declare const Router: RouterInterface;
1153
+ declare const Router: RouterApi;
1841
1154
  /** Mark framework as booted (called by Framework.boot) */
1842
1155
  declare function markRouterBooted(): void;
1843
1156
  /** Get current routing mode */
@@ -1864,124 +1177,23 @@ declare const config: FrameworkConfig;
1864
1177
  */
1865
1178
  declare function use(names: string | string[], callback?: (...modules: unknown[]) => void): Promise<unknown[]>;
1866
1179
 
1867
- /**
1868
- * CrossSite: Micro-frontend bridge View for cross-project view loading.
1869
- *
1870
- * For Lark + Webpack Module Federation.
1871
- * Provides skeleton rendering, prepare preloading, assign reuse, and remote view mounting.
1872
- *
1873
- * Usage (in host project):
1874
- * 1. Register CrossSite as the bridge view for cross-site paths:
1875
- * registerViewClass('cross-site', CrossSite);
1876
- * 2. Use v-lark="cross-site?view=remote-app/views/home&param=1" in template
1877
- * 3. CrossSite loads the remote project's prepare module, then mounts the actual view
1878
- */
1879
-
1880
- /**
1881
- * Reset the projects map cache (useful when crossConfigs change at runtime).
1882
- */
1883
1180
  declare function resetProjectsMap(): void;
1884
1181
  /**
1885
- * CrossSite bridge View for micro-frontend integration.
1182
+ * CrossSite view — bridge for micro-frontend remote views.
1886
1183
  *
1887
- * Flow:
1888
- * 1. CrossSite is mounted as a regular view (registered as "cross-site")
1889
- * 2. render() shows skeleton template with a child container
1890
- * 3. updateView() loads the remote project's prepare module
1891
- * 4. Once loaded, mounts the actual remote view into the child container
1892
- * 5. On re-assign (same view path), calls assign on the remote view instead of re-mounting
1184
+ * Registered via `registerViewClass("cross-site", CrossSite)`.
1893
1185
  */
1894
- declare const CrossSite: typeof View;
1186
+ declare const CrossSite: ViewSetup;
1895
1187
 
1896
1188
  /**
1897
- * Updater class for view data binding.
1189
+ * Create an Updater for per-view data binding.
1190
+ *
1898
1191
  * Manages view-local data with change detection and DOM diff triggering.
1899
1192
  *
1193
+ * @param viewId - The view (frame) ID this updater belongs to
1194
+ * @returns An updater API object with `get`, `set`, `digest`, `forceDigest`, etc.
1900
1195
  */
1901
- declare class Updater implements UpdaterInterface {
1902
- /** View ID (same as owner frame ID) */
1903
- private viewId;
1904
- /** Current data object */
1905
- private data;
1906
- /** Ref data for template rendering */
1907
- refData: Record<string, unknown>;
1908
- /** Changed keys in current digest cycle */
1909
- private changedKeys;
1910
- /** Whether data has changed since last digest */
1911
- private hasChangedFlag;
1912
- /**
1913
- * Digesting queue: supports re-digest during digest.
1914
- * Holds pending callbacks; `null` is used as a sentinel marking the start
1915
- * of an active digest cycle, so `runDigest` can detect re-entrant calls.
1916
- */
1917
- private digestingQueue;
1918
- /** Monotonically increasing version, bumped each time data actually changes. */
1919
- private version;
1920
- /** Snapshot of `version` taken by `snapshot()`, used by `altered()`. */
1921
- private snapshotVersion;
1922
- /** Last rendered VDOM tree (only used when virtualDom is enabled) */
1923
- private vdom?;
1924
- constructor(viewId: string);
1925
- /**
1926
- * Get data by key.
1927
- * Returns entire data object if key is omitted.
1928
- */
1929
- get<T = unknown>(key?: string): T;
1930
- /**
1931
- * Set data, tracking changed keys.
1932
- * Returns this for chaining.
1933
- */
1934
- set(data: Record<string, unknown>, excludes?: ReadonlySet<string>): this;
1935
- /**
1936
- * Detect changes and trigger DOM re-render.
1937
- *
1938
- * The core rendering pipeline:
1939
- * 1. Set data if provided
1940
- * 2. If changed, run DOM diff (template → new DOM → diff against old DOM)
1941
- * 3. Apply DOM operations
1942
- * 4. Apply ID updates
1943
- * 5. Call endUpdate on views that need re-rendering
1944
- * 6. Support re-digest during digest via queue
1945
- */
1946
- digest(data?: Record<string, unknown>, excludes?: ReadonlySet<string>, callback?: () => void): void;
1947
- /**
1948
- * Core digest execution.
1949
- */
1950
- private runDigest;
1951
- /**
1952
- * Save a snapshot of the current data version for `altered()` detection.
1953
- * Cheap O(1) — records the current monotonic version, no serialization.
1954
- */
1955
- snapshot(): this;
1956
- /**
1957
- * Check whether data has changed since the last snapshot.
1958
- * Returns undefined when no snapshot has been taken yet.
1959
- */
1960
- altered(): boolean | undefined;
1961
- /**
1962
- * Translate a refData reference back to its original value.
1963
- *
1964
- * The ref protocol is `SPLITTER` + ascii decimal digits — the exact format
1965
- * emitted by `refFn`. We require that exact shape so a user-supplied
1966
- * string that merely begins with SPLITTER is never accidentally resolved
1967
- * (or mishandled as a "missing ref").
1968
- */
1969
- translate(data: unknown): unknown;
1970
- /**
1971
- * Resolve a dotted property path against refData.
1972
- *
1973
- * Only safe property-path syntax is supported: `a`, `a.b`, `a.b.c`.
1974
- * Numeric literals (e.g. `1`, `1.5`) are returned as numbers. Anything else
1975
- * returns `undefined` — we no longer evaluate arbitrary JavaScript via
1976
- * `new Function`, so the method is CSP-safe and cannot be used as an
1977
- * injection vector.
1978
- */
1979
- parse(expr: string): unknown;
1980
- /**
1981
- * Get the set of keys changed since the last digest (for external inspection).
1982
- */
1983
- getChangedKeys(): ReadonlySet<string>;
1984
- }
1196
+ declare function createUpdater(viewId: string): UpdaterApi;
1985
1197
 
1986
1198
  /**
1987
1199
  * Create a virtual DOM node.
@@ -1999,174 +1211,212 @@ declare function vdomCreate(tag: string | number, props?: Record<string, unknown
1999
1211
  declare function createVDomRef(viewId: string): VDomRef;
2000
1212
 
2001
1213
  /**
2002
- * Payload wraps API response data with convenient access methods.
1214
+ * @lark.js/mvc Store
1215
+ *
1216
+ * Zustand-aligned state management for Lark MVC.
1217
+ *
1218
+ * Core API:
1219
+ * - create(name, creator): define a store with (set, get) => initialState
1220
+ * - store.getState(): read current state snapshot
1221
+ * - store.setState(partial | updater): shallow-merge state and notify listeners
1222
+ * - store.subscribe(listener): listen for state changes
1223
+ * - store.destroy(): tear down the store
1224
+ * - computed(deps, fn): derived state that auto-recomputes when deps change
1225
+ * - bindStore(view, store, selector?): Lark View lifecycle binding
2003
1226
  */
2004
- declare class Payload implements PayloadInterface {
2005
- /** Payload data */
2006
- data: Record<string, unknown>;
2007
- /** Internal cache info */
2008
- cacheInfo?: ServiceCacheInfo;
2009
- constructor(data?: Record<string, unknown>);
2010
- /** Get a value from payload data */
2011
- get<T = unknown>(key: string): T;
2012
- /** Set a value in payload data */
2013
- set(keyOrData: string | Record<string, unknown> | ServiceMetaEntry, value?: unknown): PayloadInterface;
1227
+ type Listener<T> = (state: T, prevState: T) => void;
1228
+ interface StoreApi<T = object> {
1229
+ getState(): T;
1230
+ setState(partial: Partial<T> | ((prev: T) => Partial<T>)): void;
1231
+ subscribe(listener: Listener<T>): () => void;
1232
+ destroy(): void;
2014
1233
  }
1234
+ type StateCreator<T> = (set: (partial: Partial<T> | ((prev: T) => Partial<T>)) => void, get: () => T) => T;
2015
1235
  /**
2016
- * Minimal interface describing what serviceSend actually uses
2017
- * from a service instance. This avoids coupling to the full
2018
- * ServiceInterface which mixes instance and static methods.
1236
+ * Declare a derived (computed) store property.
1237
+ *
1238
+ * Usage inside a `create` creator:
1239
+ * ```ts
1240
+ * const store = create("counter", (set, get) => ({
1241
+ * count: 0,
1242
+ * doubled: computed(["count"], () => get().count * 2),
1243
+ * }));
1244
+ * ```
1245
+ *
1246
+ * `deps` lists the state keys the computed reads. Whenever any dep changes
1247
+ * via `setState`, the computed re-evaluates before listeners are notified.
1248
+ * Writes to a computed key via `setState` are silently ignored.
2019
1249
  */
2020
- interface ServiceSendTarget {
2021
- destroyed: number;
2022
- busy: number;
2023
- internals: {
2024
- metaList: Record<string, ServiceMetaEntry>;
2025
- payloadCache: Cache<Payload>;
2026
- pendingCacheKeys: Record<string, PendingCacheEntry>;
2027
- syncFn: (payload: Payload, callback: () => void) => void;
2028
- staticEmitter: EventEmitter;
2029
- };
2030
- type: {
2031
- get(attrs: Record<string, unknown>, createNew?: boolean): {
2032
- entity: Payload;
2033
- needsUpdate: boolean;
2034
- };
2035
- };
2036
- enqueue(callback: AnyFunc): unknown;
2037
- }
1250
+ declare function computed<T>(deps: readonly string[], fn: () => T): T;
1251
+ declare function createStore<T extends object>(name: string, creator: StateCreator<T>): StoreApi<T>;
2038
1252
  /**
2039
- * Service: API request management with caching, deduplication, and queue.
1253
+ * Bind a store to a Lark View. Subscribes to state changes and auto-unsubscribes
1254
+ * when the view is destroyed.
2040
1255
  *
2041
- * - Service.extend(syncFn, cacheMax?, cacheBuffer?): creates subclass with sync function
2042
- * - Service.add(attrs): registers API endpoint metadata
2043
- * - new Service().all(attrs, done): fetch all, use cache when available
2044
- * - new Service().one(attrs, done): fetch all, callback on each completion
2045
- * - new Service().save(attrs, done): fetch all, skip cache (always request)
2046
- * - enqueue/dequeue: task queue for sequential async operations
2047
- * - destroy: cancel pending requests
1256
+ * @param view - Lark View instance (must have updater.set/digest and on("destroy"))
1257
+ * @param store - Store created via `create()`
1258
+ * @param selector - Optional function to pick a subset of state for the updater.
1259
+ * If omitted, only non-function state keys are forwarded.
1260
+ * @returns unsubscribe function
2048
1261
  *
2049
- * Per-type state (metaList, payloadCache, pendingCacheKeys, syncFn, staticEmitter)
2050
- * is stored as static class properties. When extend() creates a subclass,
2051
- * each subclass gets its own copies of these static properties, ensuring
2052
- * isolation between different Service types.
1262
+ * @example
1263
+ * ```ts
1264
+ * // Observe all state
1265
+ * bindStore(this, useCountStore);
1266
+ *
1267
+ * // Observe with selector
1268
+ * bindStore(this, useCountStore, (s) => ({ count: s.count }));
1269
+ * ```
1270
+ */
1271
+ declare function bindStore<T>(view: unknown, store: StoreApi<T>, selector?: (state: T) => Record<string, unknown>): () => void;
1272
+
1273
+ /**
1274
+ * Hooks runtime for the functional view system.
1275
+ *
1276
+ * Hooks (`useState`, `useEffect`, `useStore`, etc.) work via a module-level
1277
+ * `currentCtx` that is set during setup function execution. The setup function
1278
+ * runs once on mount (inside `mountCtx`), and hooks register state, effects,
1279
+ * and subscriptions on the ctx.
1280
+ *
1281
+ * Key difference from React hooks: Lark's setup runs ONCE (not on every
1282
+ * render). `useState` returns a `[getter, setter]` pair where the getter
1283
+ * always reads from `ctx.updater.data` — avoiding stale closures. The
1284
+ * template (compiled from `.html`) reads from `updater.data` independently
1285
+ * of the setup function's closures.
1286
+ */
1287
+
1288
+ /**
1289
+ * Declare view-local state backed by `ctx.updater.data`.
1290
+ *
1291
+ * Returns a `[getter, setter]` pair. The getter always reads the latest
1292
+ * value from `ctx.updater.data[key]`, avoiding stale closures in event
1293
+ * handlers. The setter writes to `ctx.updater.data` and triggers a digest.
1294
+ *
1295
+ * @param key - The data key in `updater.data`
1296
+ * @param initial - Initial value (set once on first call)
1297
+ *
1298
+ * @example
1299
+ * const [getCount, setCount] = useState('count', 0);
1300
+ * // In event handler:
1301
+ * "incr<click>": (e) => setCount(getCount() + 1)
1302
+ */
1303
+ declare function useState<T>(key: string, initial: T): [() => T, (v: T) => void];
1304
+ /**
1305
+ * Register a side effect with optional cleanup.
1306
+ *
1307
+ * The effect function runs immediately during setup. If it returns a cleanup
1308
+ * function, that cleanup is called on view destroy (or on HMR re-setup).
1309
+ *
1310
+ * Unlike React's `useEffect`, this runs synchronously during setup (not
1311
+ * deferred to a later tick) and does not re-run on dependency changes
1312
+ * (since setup only runs once).
1313
+ *
1314
+ * @example
1315
+ * useEffect(() => {
1316
+ * const timer = setInterval(tick, 1000);
1317
+ * return () => clearInterval(timer);
1318
+ * });
1319
+ */
1320
+ declare function useEffect(fn: () => (() => void) | void, _deps?: unknown[]): void;
1321
+ /**
1322
+ * Bind a store to the view's updater. The store's state is synced to
1323
+ * `ctx.updater.data` automatically. Auto-unsubscribes on view destroy.
1324
+ *
1325
+ * @param store - The store created by `create()`
1326
+ * @param selector - Optional selector to pick which keys to sync
1327
+ * @returns A getter that reads the selected state from updater.data
1328
+ *
1329
+ * @example
1330
+ * const getCount = useStore(useCountStore, (s) => ({ count: s.count }));
1331
+ * // In event handler:
1332
+ * "incr<click>": (e) => useCountStore.getState().increment()
1333
+ */
1334
+ declare function useStore<T extends Record<string, unknown>>(store: StoreApi<T>, selector?: (s: T) => Partial<T>): () => Partial<T>;
1335
+ /**
1336
+ * Set up an interval that is automatically cleared on view destroy.
1337
+ *
1338
+ * @param fn - Function to call on each interval
1339
+ * @param delay - Interval delay in milliseconds
1340
+ *
1341
+ * @example
1342
+ * useInterval(() => {
1343
+ * ctx.updater.set({ time: Date.now() }).digest();
1344
+ * }, 1000);
1345
+ */
1346
+ declare function useInterval(fn: () => void, delay: number): void;
1347
+ /**
1348
+ * Set up a timeout that is automatically cleared on view destroy.
1349
+ *
1350
+ * @param fn - Function to call after delay
1351
+ * @param delay - Timeout delay in milliseconds
1352
+ */
1353
+ declare function useTimeout(fn: () => void, delay: number): void;
1354
+ /**
1355
+ * Capture a resource (e.g., a Service instance, observer, etc.) that is
1356
+ * automatically destroyed on view destroy or render (if destroyOnRender).
1357
+ *
1358
+ * @param key - Unique key for the resource
1359
+ * @param resource - The resource object (must have a `destroy()` method)
1360
+ * @param destroyOnRender - If true, destroyed on next render call
1361
+ *
1362
+ * @example
1363
+ * const service = createService(syncFn);
1364
+ * useResource('myService', service.instance(), true);
1365
+ */
1366
+ declare function useResource(key: string, resource: unknown, destroyOnRender?: boolean): void;
1367
+ /**
1368
+ * Register an event handler on the view's internal emitter.
1369
+ * Automatically unregistered on view destroy.
1370
+ *
1371
+ * @param event - Event name (e.g., "destroy", "render")
1372
+ * @param handler - Event handler function
1373
+ *
1374
+ * @example
1375
+ * useEvent("destroy", () => console.log("View destroyed"));
1376
+ */
1377
+ declare function useEvent(event: string, handler: AnyFunc): void;
1378
+
1379
+ /**
1380
+ * Create a Payload wrapping API response data.
2053
1381
  */
2054
- declare class Service {
2055
- /** Service instance ID */
1382
+ declare function createPayload(data?: Record<string, unknown>): PayloadApi;
1383
+ interface ServiceInstance {
2056
1384
  id: string;
2057
- /** Whether service is busy (1 = busy) */
2058
1385
  busy: number;
2059
- /** Whether service is destroyed (1 = destroyed) */
2060
1386
  destroyed: number;
2061
- /** Task queue for sequential operations */
2062
- taskQueue: AnyFunc[];
2063
- /** Previous dequeue arguments */
2064
- prevArgs: unknown[];
2065
- /** Instance event emitter */
2066
- private _emitter;
2067
- constructor();
2068
- /** Instance event emitter (public accessor) */
2069
- get emitter(): EventEmitterInterface;
2070
- /**
2071
- * Get internals object for serviceSend compatibility.
2072
- * References per-type static state from the current class.
2073
- */
2074
- get internals(): ServiceSendTarget["internals"];
2075
- /**
2076
- * Get type reference (the constructor) for serviceSend compatibility.
2077
- * Static methods like get/create are accessible via the constructor.
2078
- */
2079
- get type(): ServiceSendTarget["type"];
2080
- /**
2081
- * Fetch all endpoints, callback when all complete.
2082
- * Uses cache when available.
2083
- */
2084
- all(attrs: string | Record<string, unknown> | (string | Record<string, unknown>)[], done: AnyFunc): this;
2085
- /**
2086
- * Fetch all endpoints, callback on each completion.
2087
- */
2088
- one(attrs: string | Record<string, unknown> | (string | Record<string, unknown>)[], done: AnyFunc): this;
2089
- /**
2090
- * Fetch all endpoints, skip cache (always request).
2091
- */
2092
- save(attrs: string | Record<string, unknown> | (string | Record<string, unknown>)[], done: AnyFunc): this;
2093
- /**
2094
- * Enqueue a task for sequential execution.
2095
- */
2096
- enqueue(callback: AnyFunc): this;
2097
- /**
2098
- * Dequeue and execute the next task in queue.
2099
- */
1387
+ emitter: EmitterApi;
1388
+ all(attrs: string | Record<string, unknown> | (string | Record<string, unknown>)[], done: AnyFunc): ServiceInstance;
1389
+ one(attrs: string | Record<string, unknown> | (string | Record<string, unknown>)[], done: AnyFunc): ServiceInstance;
1390
+ save(attrs: string | Record<string, unknown> | (string | Record<string, unknown>)[], done: AnyFunc): ServiceInstance;
1391
+ enqueue(callback: AnyFunc): ServiceInstance;
2100
1392
  dequeue(...args: unknown[]): void;
2101
- /**
2102
- * Destroy the service instance.
2103
- * After destruction, no new requests can be sent.
2104
- */
2105
1393
  destroy(): void;
2106
- on(event: string, handler: AnyFunc): this;
2107
- off(event: string, handler?: AnyFunc): this;
2108
- fire(event: string, data?: Record<string, unknown>): this;
2109
- /** Per-type metadata registry */
2110
- static _metaList: Record<string, ServiceMetaEntry>;
2111
- /** Per-type payload cache (LFU with frequency eviction) */
2112
- static _payloadCache: Cache<Payload>;
2113
- /** Per-type pending cache keys for deduplication */
2114
- static _pendingCacheKeys: Record<string, PendingCacheEntry>;
2115
- /** Per-type sync function */
2116
- static _syncFn: (payload: Payload, callback: () => void) => void;
2117
- /** Per-type static event emitter */
2118
- static _staticEmitter: EventEmitter<unknown>;
2119
- /** Per-type cache max size */
2120
- static _cacheMax: number;
2121
- /** Per-type cache buffer size */
2122
- static _cacheBuffer: number;
2123
- /**
2124
- * Register API endpoint metadata.
2125
- */
2126
- static add(attrs: ServiceMetaEntry | ServiceMetaEntry[]): void;
2127
- /**
2128
- * Get metadata for an API endpoint.
2129
- */
2130
- static meta(attrs: string | Record<string, unknown>): ServiceMetaEntry;
2131
- /**
2132
- * Create a Payload for an API request.
2133
- */
2134
- static create(attrs: Record<string, unknown>): Payload;
2135
- /**
2136
- * Get or create a Payload for an API request.
2137
- */
2138
- static get(attrs: Record<string, unknown>, createNew?: boolean): {
2139
- entity: Payload;
1394
+ on(event: string, handler: AnyFunc): ServiceInstance;
1395
+ off(event: string, handler?: AnyFunc): ServiceInstance;
1396
+ fire(event: string, data?: Record<string, unknown>): ServiceInstance;
1397
+ }
1398
+ interface ServiceApi {
1399
+ add(attrs: ServiceMetaEntry | ServiceMetaEntry[]): void;
1400
+ meta(attrs: string | Record<string, unknown>): ServiceMetaEntry;
1401
+ create(attrs: Record<string, unknown>): PayloadApi;
1402
+ get(attrs: Record<string, unknown>, createNew?: boolean): {
1403
+ entity: PayloadApi;
2140
1404
  needsUpdate: boolean;
2141
1405
  };
2142
- /**
2143
- * Get cached Payload if available and not expired.
2144
- */
2145
- static cached(attrs: Record<string, unknown>): Payload | undefined;
2146
- /**
2147
- * Clear cached payloads by endpoint name.
2148
- */
2149
- static clear(names: string | string[]): void;
2150
- static on(event: string, handler: AnyFunc): void;
2151
- static off(event: string, handler?: AnyFunc): void;
2152
- static fire(event: string, data?: Record<string, unknown>): void;
2153
- /**
2154
- * Create a new Service subclass with a custom sync function.
2155
- *
2156
- * Each subclass gets its OWN copies of every per-type static field
2157
- * (`_metaList`, `_payloadCache`, `_pendingCacheKeys`, `_syncFn`,
2158
- * `_staticEmitter`, `_cacheMax`, `_cacheBuffer`) via `static override`.
2159
- * This is intentional: it ensures that endpoint metadata, cache state,
2160
- * in-flight dedup keys, and event subscribers are fully isolated between
2161
- * different Service types, even when one extends another.
2162
- *
2163
- * **Do not refactor these `static override` declarations away** — sharing
2164
- * them through prototype inheritance would let endpoints registered on one
2165
- * subclass leak into another, and the LFU cache evictions of one type
2166
- * would race with those of another.
2167
- */
2168
- static extend(this: typeof Service, newSyncFn: (payload: Payload, callback: () => void) => void, newCacheMax?: number, newCacheBuffer?: number): typeof Service;
1406
+ cached(attrs: Record<string, unknown>): PayloadApi | undefined;
1407
+ clear(names: string | string[]): void;
1408
+ on(event: string, handler: AnyFunc): void;
1409
+ off(event: string, handler?: AnyFunc): void;
1410
+ fire(event: string, data?: Record<string, unknown>): void;
1411
+ instance(): ServiceInstance;
2169
1412
  }
1413
+ /**
1414
+ * Create a Service type with a custom sync function.
1415
+ *
1416
+ * Each call creates independent closure state (metaList, payloadCache, etc.),
1417
+ * ensuring full isolation between different Service types.
1418
+ */
1419
+ declare function createService(syncFn: (payload: PayloadApi, callback: () => void) => void, cacheMax?: number, cacheBuffer?: number): ServiceApi;
2170
1420
 
2171
1421
  /**
2172
1422
  * DOM event delegation system.
@@ -2189,7 +1439,7 @@ declare const EventDelegator: {
2189
1439
  /**
2190
1440
  * Set the frame getter function (called by Framework.boot).
2191
1441
  */
2192
- setFrameGetter(getter: (id: string) => FrameInterface | undefined): void;
1442
+ setFrameGetter(getter: (id: string) => FrameObj | undefined): void;
2193
1443
  /**
2194
1444
  * Get next element GUID.
2195
1445
  */
@@ -2200,7 +1450,7 @@ declare const EventDelegator: {
2200
1450
  * Main framework object.
2201
1451
  * Provides boot, config, and all global utility methods.
2202
1452
  */
2203
- declare const Framework: FrameworkInterface;
1453
+ declare const Framework: FrameworkApi;
2204
1454
 
2205
1455
  /**
2206
1456
  * Sync view state with URL query parameters.
@@ -2215,83 +1465,114 @@ declare const Framework: FrameworkInterface;
2215
1465
  *
2216
1466
  * @example
2217
1467
  * ```ts
2218
- * export default View.extend({
2219
- * template,
2220
- * init() {
2221
- * const [state, setState] = useUrlState(this, { page: "1", size: "20" });
2222
- * this.updater.set({ page: state.page, size: state.size }).digest();
2223
- * this.setState = setState;
2224
- * },
2225
- * assign() {
2226
- * const [state] = useUrlState(this, { page: "1", size: "20" });
2227
- * this.updater.set({ page: state.page, size: state.size });
2228
- * },
2229
- * "nextPage<click>"() {
2230
- * this.setState((prev) => ({ page: String(Number(prev.page) + 1) }));
2231
- * },
1468
+ * export default defineView((ctx) => {
1469
+ * const [state, setState] = useUrlState(ctx, { page: "1", size: "20" });
1470
+ * ctx.updater.set({ page: state.page, size: state.size }).digest();
1471
+ * return {
1472
+ * template,
1473
+ * events: {
1474
+ * "nextPage<click>"() {
1475
+ * setState((prev) => ({ page: String(Number(prev.page) + 1) }));
1476
+ * },
1477
+ * },
1478
+ * };
2232
1479
  * });
2233
1480
  * ```
2234
1481
  */
2235
- declare function useUrlState<S extends Record<string, string>>(view: ViewInterface, initialState?: S): [Readonly<S>, (patch: Partial<S> | ((prev: S) => Partial<S>)) => void];
1482
+ declare function useUrlState<S extends Record<string, string>>(view: ViewCtx, initialState?: S): [Readonly<S>, (patch: Partial<S> | ((prev: S) => Partial<S>)) => void];
1483
+
1484
+ interface HotContext {
1485
+ accept(cb?: (mod: {
1486
+ default?: unknown;
1487
+ } | undefined) => void): void;
1488
+ dispose(cb: (data: unknown) => void): void;
1489
+ invalidate(): void;
1490
+ }
1491
+ declare function reloadViews(viewPath: string): void;
1492
+ declare function hotSwapView(frame: FrameObj, newSetup: ViewSetup): void;
1493
+ declare function hotSwapFrames(viewPath: string, newSetup: ViewSetup): void;
1494
+ declare function hotSwapByTemplate(oldTemplate: ViewTemplate, newTemplate: ViewTemplate): void;
1495
+ declare function hotSwapByView(oldSetup: ViewSetup, newSetup: ViewSetup): void;
2236
1496
 
2237
1497
  /**
2238
- * @lark.js/mvc Store
1498
+ * HMR injection code generator — shared across Vite, Webpack, and Rspack.
2239
1499
  *
2240
- * Zustand-aligned state management for Lark MVC.
1500
+ * ## Why this file exists
2241
1501
  *
2242
- * Core API:
2243
- * - create(name, creator): define a store with (set, get) => initialState
2244
- * - store.getState(): read current state snapshot
2245
- * - store.setState(partial | updater): shallow-merge state and notify listeners
2246
- * - store.subscribe(listener): listen for state changes
2247
- * - store.destroy(): tear down the store
2248
- * - computed(deps, fn): derived state that auto-recomputes when deps change
2249
- * - bindStore(view, store, selector?): Lark View lifecycle binding
1502
+ * React's `@vitejs/plugin-react` and Vue's `@vitejs/plugin-vue` auto-inject
1503
+ * HMR boilerplate at compile time so users never write `import.meta.hot`
1504
+ * themselves. Lark's `larkMvcPlugin` / `larkMvcLoader` previously did NOT
1505
+ * inject any HMR code, forcing users to manually call `acceptView()` /
1506
+ * `disposeView()` in every view file — a poor DX.
1507
+ *
1508
+ * This module generates the HMR snippet strings that the three bundler
1509
+ * integrations (vite.ts, webpack.ts, rspack.ts) append to compiled output.
1510
+ * Extracting the logic here keeps the three plugin files DRY and makes the
1511
+ * cross-bundler differences (Vite's `import.meta.hot` vs Webpack/Rspack's
1512
+ * `module.hot`) explicit and testable.
1513
+ *
1514
+ * ## Two injection targets
1515
+ *
1516
+ * 1. **Template module** (compiled from `.html`): self-accepts. When the
1517
+ * `.html` changes, the accept callback calls `hotSwapByTemplate(old, new)`
1518
+ * to update the template on all mounted views — preserving state.
1519
+ *
1520
+ * 2. **View class module** (`.ts` file that imports `.html`): self-accepts.
1521
+ * When the `.ts` changes, the accept callback calls
1522
+ * `hotSwapByView(old, new)` to swap the setup function on all mounted
1523
+ * instances — preserving state.
1524
+ *
1525
+ * ## Cross-bundler HMR API differences
1526
+ *
1527
+ * | Bundler | HMR context | accept cb receives new module? |
1528
+ * |----------------|------------------------------------------------------|
1529
+ * | Vite | `import.meta.hot` | Yes (`newModule.default`) |
1530
+ * | Webpack (CJS) | `module.hot` | No (module already re-executed)|
1531
+ * | Rspack | `module.hot` | No (module already re-executed)|
1532
+ *
1533
+ * In Vite, the accept callback runs in the OLD module's scope, so local
1534
+ * variables are from the old module. In Webpack/Rspack, the callback runs
1535
+ * in the NEW module's scope (the module has already re-executed), so local
1536
+ * variables are from the new module. The snippets below account for this.
2250
1537
  */
2251
- type Listener<T> = (state: T, prevState: T) => void;
2252
- interface StoreApi<T = Record<string, unknown>> {
2253
- getState(): T;
2254
- setState(partial: Partial<T> | ((prev: T) => Partial<T>)): void;
2255
- subscribe(listener: Listener<T>): () => void;
2256
- destroy(): void;
2257
- }
2258
- type StateCreator<T> = (set: (partial: Partial<T> | ((prev: T) => Partial<T>)) => void, get: () => T) => T;
1538
+ /** Supported bundler identifiers. */
1539
+ type Bundler = "vite" | "webpack" | "rspack";
2259
1540
  /**
2260
- * Declare a derived (computed) store property.
1541
+ * Append HMR code to a compiled template module source.
2261
1542
  *
2262
- * Usage inside a `create` creator:
2263
- * ```ts
2264
- * const store = create("counter", (set, get) => ({
2265
- * count: 0,
2266
- * doubled: computed(["count"], () => get().count * 2),
2267
- * }));
2268
- * ```
1543
+ * Called by the Vite `load` hook and the Webpack/Rspack loader after
1544
+ * `compileTemplate` returns. The `bundler` parameter selects the correct
1545
+ * HMR API (`import.meta.hot` for Vite, `module.hot` for Webpack/Rspack).
2269
1546
  *
2270
- * `deps` lists the state keys the computed reads. Whenever any dep changes
2271
- * via `setState`, the computed re-evaluates before listeners are notified.
2272
- * Writes to a computed key via `setState` are silently ignored.
1547
+ * @param source - The compiled template module source from `compileTemplate`
1548
+ * @param bundler - Which bundler's HMR API to use
1549
+ * @returns The source with HMR accept/dispose code appended
2273
1550
  */
2274
- declare function computed<T>(deps: readonly string[], fn: () => T): T;
2275
- declare function create<T>(name: string, creator: StateCreator<T>): StoreApi<T>;
1551
+ declare function injectTemplateHmrSnippet(source: string, bundler: Bundler): string;
2276
1552
  /**
2277
- * Bind a store to a Lark View. Subscribes to state changes and auto-unsubscribes
2278
- * when the view is destroyed.
1553
+ * Quick check: does this `.ts` source import a `.html` template?
2279
1554
  *
2280
- * @param view - Lark View instance (must have updater.set/digest and on("destroy"))
2281
- * @param store - Store created via `create()`
2282
- * @param selector - Optional function to pick a subset of state for the updater.
2283
- * If omitted, only non-function state keys are forwarded.
2284
- * @returns unsubscribe function
1555
+ * Used by the plugin's `transform` hook to decide whether to inject view
1556
+ * class HMR. Files that don't import `.html` are left untouched.
1557
+ */
1558
+ declare function importsHtmlTemplate(source: string): boolean;
1559
+ /**
1560
+ * Transform a `.ts` view file source to add view class HMR.
2285
1561
  *
2286
- * @example
2287
- * ```ts
2288
- * // Observe all state
2289
- * bindStore(this, useCountStore);
1562
+ * Steps:
1563
+ * 1. Check if the source imports a `.html` template. If not, return as-is.
1564
+ * 2. Find the `export default` declaration (via @babel/parser AST).
1565
+ * 3. Rewrite it to a named const + export, so the HMR snippet can reference
1566
+ * the View class by name (`__larkViewDefault`).
1567
+ * 4. Append the HMR snippet.
2290
1568
  *
2291
- * // Observe with selector
2292
- * bindStore(this, useCountStore, (s) => ({ count: s.count }));
2293
- * ```
1569
+ * If the source has no `export default`, or if parsing fails, the source is
1570
+ * returned unchanged (graceful degradation the file just won't have HMR).
1571
+ *
1572
+ * @param source - The `.ts` source code
1573
+ * @param bundler - Which bundler's HMR API to use
1574
+ * @returns The transformed source with HMR code, or the original if ineligible
2294
1575
  */
2295
- declare function bindStore<T>(view: unknown, store: StoreApi<T>, selector?: (state: T) => Record<string, unknown>): () => void;
1576
+ declare function injectViewHmr(source: string, bundler: Bundler): string;
2296
1577
 
2297
- export { type AnyFunc, CALL_BREAK_TIME, Cache, type CacheEntry, type CacheInterface, type CacheOptions, type ChangeEvent, type CompileOptions, CrossSite, type CrossSiteConfig, type DomElement, type DomOp, type DomRef, EVENT_METHOD_REGEXP, EventDelegator, EventEmitter, type EventEmitterInterface, type EventListenerEntry, Frame, type FrameBoundElement, type FrameInterface, type FrameInvokeEntry, type FrameStaticEvent, Framework, type FrameworkConfig, type FrameworkInterface, type HotContext, LARK_VIEW, type Location, type LocationDiff, type MixinEventHandler, type ParamDiff, type ParsedUri, Payload, type PayloadInterface, type PendingCacheEntry, RouterEvents as ROUTER_EVENTS, type RouteChangeEvent, type RouteChangedEvent, type RouteViewConfig, Router, type RouterInterface, SPLITTER, Service, type ServiceCacheInfo, type ServiceEvent, type ServiceMetaEntry, type ServiceOptions, State, type StateInterface, type StoreApi, TAG_NAME_REGEXP, Updater, type UpdaterInterface, type VDomCreateFn, type VDomNode, type VDomRef, type VDomTemplate, VIEW_EVENT_METHOD_REGEXP, View, type ViewEvent, type ViewEventSelectorEntry, type ViewGlobalEventEntry, type ViewInterface, type ViewLocationObserved, type ViewObserveLocation, type ViewResourceEntry, type ViewTemplate, type VoidFunc, applyStyle, bindStore, computed, create, createVDomRef, defineView, config as frameworkConfig, getRouteMode, invalidateViewClass, mark, markBooted, markRouterBooted, nextCounter, registerViewClass, reloadViews, resetProjectsMap, unmark, use, useUrlState, vdomCreate };
1578
+ export { type AnyFunc, type Bundler, CALL_BREAK_TIME, type CacheApi, type CacheEntry, type CacheOptions, type ChangeEvent, type CompileOptions, CrossSite, type CrossSiteConfig, type DomElement, type DomOp, type DomRef, EVENT_METHOD_REGEXP, type EmitterApi, EventDelegator, type EventListenerEntry, Frame, type FrameApi, type FrameBoundElement, type FrameInvokeEntry, type FrameObj, type FrameStaticEvent, Framework, type FrameworkApi, type FrameworkConfig, type HotContext, LARK_VIEW, type Location, type LocationDiff, type ParamDiff, type ParsedUri, type PayloadApi, type PendingCacheEntry, RouterEvents as ROUTER_EVENTS, type Ref, type RouteChangeEvent, type RouteChangedEvent, type RouteViewConfig, Router, type RouterApi, SPLITTER, type ServiceApi, type ServiceCacheInfo, type ServiceEvent, type ServiceInstance, type ServiceMetaEntry, type ServiceOptions, State, type StateApi, type StoreApi, TAG_NAME_REGEXP, type UpdaterApi, type VDomCreateFn, type VDomNode, type VDomRef, type VDomTemplate, VIEW_EVENT_METHOD_REGEXP, type ViewCtx, type ViewEvent, type ViewLocationObserved, type ViewObserveLocation, type ViewResourceEntry, type ViewSetup, type ViewTemplate, type VoidFunc, bindStore, computed, createCache, createEmitter, createFrame, createPayload, createService, createStore, createUpdater, createVDomRef, defineView, config as frameworkConfig, getRouteMode, hotSwapByTemplate, hotSwapByView, hotSwapFrames, hotSwapView, importsHtmlTemplate, injectTemplateHmrSnippet, injectViewHmr, invalidateViewClass, mark, markBooted, markRouterBooted, nextCounter, registerViewClass, reloadViews, resetProjectsMap, unmark, use, useEffect, useEvent, useInterval, useResource, useState, useStore, useTimeout, useUrlState, vdomCreate };