@cordy/electro 1.0.8 → 1.1.0

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.mts CHANGED
@@ -1,16 +1,10 @@
1
- import { BaseWindow } from "electron";
1
+ import { BaseWindow, WebContentsView } from "electron";
2
2
  import { UserConfig } from "vite";
3
3
 
4
4
  //#region src/config/types.d.ts
5
5
  declare const RUNTIME_BRAND: unique symbol;
6
- declare const WINDOW_BRAND: unique symbol;
6
+ declare const VIEW_BRAND: unique symbol;
7
7
  declare const CONFIG_BRAND: unique symbol;
8
- type WindowLifecycle = "singleton" | "multi";
9
- type WindowCloseBehavior = "hide" | "destroy";
10
- type WindowType = "base-window" | "browser-window";
11
- interface WindowBehavior {
12
- close: WindowCloseBehavior;
13
- }
14
8
  interface RuntimeDefinition {
15
9
  readonly [RUNTIME_BRAND]: true;
16
10
  readonly entry: string;
@@ -18,45 +12,37 @@ interface RuntimeDefinition {
18
12
  /** @internal Caller path captured by defineRuntime(). */
19
13
  readonly __source: string;
20
14
  }
21
- interface WindowDefinition {
22
- readonly [WINDOW_BRAND]: true;
15
+ interface ViewDefinition {
16
+ readonly [VIEW_BRAND]: true;
23
17
  readonly name: string;
24
18
  readonly entry: string;
25
- readonly type?: WindowType;
26
19
  readonly features?: readonly string[];
27
20
  readonly vite?: UserConfig;
28
21
  readonly preload?: string;
29
- readonly lifecycle?: WindowLifecycle;
30
- readonly autoShow?: boolean;
31
- readonly behavior?: WindowBehavior;
32
- readonly window?: Record<string, unknown>;
33
- /** @internal Caller path captured by defineWindow(). */
22
+ readonly webPreferences?: Record<string, unknown>;
23
+ /** @internal Caller path captured by defineView(). */
34
24
  readonly __source: string;
35
25
  }
36
26
  interface ElectroConfig {
37
27
  readonly [CONFIG_BRAND]: true;
38
28
  readonly runtime: RuntimeDefinition;
39
- readonly windows?: readonly WindowDefinition[];
29
+ readonly views?: readonly ViewDefinition[];
40
30
  }
41
31
  interface DefineRuntimeInput {
42
32
  entry: string;
43
33
  vite?: UserConfig;
44
34
  }
45
- interface DefineWindowInput {
35
+ interface DefineViewInput {
46
36
  name: string;
47
37
  entry: string;
48
- type?: WindowType;
49
38
  features?: readonly string[];
50
39
  vite?: UserConfig;
51
40
  preload?: string;
52
- lifecycle?: WindowLifecycle;
53
- autoShow?: boolean;
54
- behavior?: WindowBehavior;
55
- window?: Record<string, unknown>;
41
+ webPreferences?: Record<string, unknown>;
56
42
  }
57
43
  interface DefineConfigInput {
58
44
  runtime: RuntimeDefinition;
59
- windows?: readonly WindowDefinition[];
45
+ views?: readonly ViewDefinition[];
60
46
  }
61
47
  //#endregion
62
48
  //#region src/config/define-config.d.ts
@@ -65,8 +51,8 @@ declare function defineConfig(input: DefineConfigInput): ElectroConfig;
65
51
  //#region src/config/define-runtime.d.ts
66
52
  declare function defineRuntime(input: DefineRuntimeInput): RuntimeDefinition;
67
53
  //#endregion
68
- //#region src/config/define-window.d.ts
69
- declare function defineWindow(input: DefineWindowInput): WindowDefinition;
54
+ //#region src/config/define-view.d.ts
55
+ declare function defineView(input: DefineViewInput): ViewDefinition;
70
56
  //#endregion
71
57
  //#region src/core/event-bus/types.d.ts
72
58
  type EventHandler = (payload: unknown) => void;
@@ -146,60 +132,96 @@ declare enum FeatureStatus {
146
132
  ERROR = "error"
147
133
  }
148
134
  //#endregion
149
- //#region src/window/types.d.ts
150
- /** A window with an auto-loading `load()` method that resolves dev/prod URLs. */
151
- type ElectroWindow<T extends BaseWindow = BaseWindow> = T & {
135
+ //#region src/core/view/types.d.ts
136
+ type ViewId = string;
137
+ /** Config for renderer-linked views (built by Vite). */
138
+ interface RendererViewConfig {
139
+ id: ViewId;
140
+ /** Link to a defineView() name. `true` = use `id` as the renderer name. */
141
+ renderer: true | string;
142
+ webPreferences?: Record<string, unknown>;
143
+ }
144
+ /** Config for dynamic views (no renderer entry, programmatic). */
145
+ interface DynamicViewConfig {
146
+ id: ViewId;
147
+ webPreferences?: Record<string, unknown>;
148
+ }
149
+ type ViewConfig = RendererViewConfig | DynamicViewConfig;
150
+ /** A WebContentsView augmented with `load()` for renderer-linked views. */
151
+ type ElectroView = WebContentsView & {
152
152
  load(): Promise<void>;
153
153
  };
154
- /** Factory interface for creating platform windows from definitions. */
155
- interface WindowFactory {
156
- create(definition: WindowDefinition): BaseWindow;
154
+ /** Public interface hides the View class. */
155
+ interface CreatedView {
156
+ readonly id: ViewId;
157
+ /** Create the WebContentsView. Idempotent: returns existing if alive. */
158
+ create(): ElectroView;
159
+ /** Return the live ElectroView, or null if not yet created / destroyed. */
160
+ view(): ElectroView | null;
161
+ /** Destroy the WebContentsView and clear refs. */
162
+ destroy(): void;
157
163
  }
158
- /** Snapshot of a tracked window. */
159
- interface WindowInfo {
160
- name: string;
161
- windowId: number;
162
- destroyed: boolean;
164
+ /** Type-erased interface for managers. */
165
+ interface ViewInstance {
166
+ readonly id: ViewId;
167
+ create(): ElectroView;
168
+ view(): ElectroView | null;
169
+ destroy(): void;
170
+ }
171
+ //#endregion
172
+ //#region src/core/view/manager.d.ts
173
+ /**
174
+ * ViewManager — global registry for view instances.
175
+ *
176
+ * Views are registered at runtime startup and accessible from any feature.
177
+ * Views are idempotent: `get(id).create()` returns the same instance if alive.
178
+ */
179
+ declare class ViewManager {
180
+ private readonly instances;
181
+ register(view: ViewInstance): void;
182
+ get(id: ViewId): ViewInstance | null;
183
+ destroyAll(): void;
184
+ }
185
+ //#endregion
186
+ //#region src/core/window/types.d.ts
187
+ type WindowId = string;
188
+ interface WindowConfig<TApi = void> {
189
+ id: WindowId;
190
+ options?: Record<string, unknown>;
191
+ api?: (window: BaseWindow) => TApi;
192
+ }
193
+ /** Public interface — hides the Window class. */
194
+ interface CreatedWindow<TApi = void> {
195
+ readonly id: WindowId;
196
+ /** Create the BaseWindow. Idempotent: returns existing if alive. */
197
+ create(): BaseWindow;
198
+ /** The live BaseWindow, or null if not yet created / destroyed. */
199
+ readonly window: BaseWindow | null;
200
+ /** The typed API, or null if not yet created / destroyed. */
201
+ readonly api: TApi | null;
202
+ /** Destroy the BaseWindow and clear refs. */
203
+ destroy(): void;
204
+ }
205
+ /** Type-erased interface for managers. */
206
+ interface WindowInstance {
207
+ readonly id: WindowId;
208
+ create(): BaseWindow;
209
+ readonly window: BaseWindow | null;
210
+ readonly api: unknown;
211
+ destroy(): void;
163
212
  }
164
213
  //#endregion
165
- //#region src/window/manager.d.ts
214
+ //#region src/core/window/manager.d.ts
166
215
  /**
167
- * WindowManager — registry, creation, and lifecycle coordinator for windows.
216
+ * WindowManager — global registry for window instances.
168
217
  *
169
- * Manages window definitions and their instances through a WindowFactory
170
- * abstraction. Enforces singleton/multi lifecycle semantics and provides
171
- * querying and bulk destruction capabilities.
218
+ * Windows are registered at runtime startup and accessible from any feature.
172
219
  */
173
220
  declare class WindowManager {
174
- private readonly factory;
175
- private readonly definitions;
176
221
  private readonly instances;
177
- constructor(factory: WindowFactory);
178
- /** Register a window definition. Throws on duplicate name. */
179
- registerDefinition(definition: WindowDefinition): void;
180
- /** Check whether a definition is registered for the given name. */
181
- hasDefinition(name: string): boolean;
182
- /**
183
- * Create a window from a registered definition.
184
- *
185
- * - Throws if no definition exists for `name`.
186
- * - For singleton lifecycle (the default), throws if a live instance already exists.
187
- * - For multi lifecycle, always creates a new instance.
188
- */
189
- createWindow(name: string): ElectroWindow;
190
- /**
191
- * Get the first alive window for the given name, or null.
192
- * Cleans up destroyed instances as a side effect.
193
- */
194
- getWindow(name: string): ElectroWindow | null;
195
- /** Destroy all instances for the given name. No-op if name is unknown. */
196
- destroyWindow(name: string): void;
197
- /** Destroy all tracked windows across all names. */
222
+ register(window: WindowInstance): void;
223
+ get(id: WindowId): WindowInstance | null;
198
224
  destroyAll(): void;
199
- /** Returns a snapshot of all tracked windows (including destroyed ones). */
200
- list(): WindowInfo[];
201
- /** Return alive (non-destroyed) instances for a given name. */
202
- private getAliveInstances;
203
225
  }
204
226
  //#endregion
205
227
  //#region src/core/task/enums.d.ts
@@ -331,14 +353,17 @@ interface FeatureMap {}
331
353
  interface ServiceOwnerMap {}
332
354
  /** Maps task ID → owning feature ID. Populated by codegen. */
333
355
  interface TaskOwnerMap {}
334
- /** Declaration-merging registry for window types. Codegen populates this. */
335
- interface WindowMap {}
356
+ /** Declaration-merging registry for view types. Codegen populates this. */
357
+ interface ViewMap {}
358
+ /** Declaration-merging registry for window API types. Codegen populates this. */
359
+ interface WindowApiMap {}
336
360
  /** Resolve the owning feature ID for a service. Falls back to `string` (→ BaseContext). */
337
361
  type _ServiceOwner<TId extends string> = TId extends keyof ServiceOwnerMap ? ServiceOwnerMap[TId] : string;
338
362
  /** Resolve the owning feature ID for a task. Falls back to `string` (→ BaseContext). */
339
363
  type _TaskOwner<TId extends string> = TId extends keyof TaskOwnerMap ? TaskOwnerMap[TId] : string;
340
- type _SuggestWindowKeys = (keyof WindowMap & string) | (string & {});
341
- type _ResolveWindow<K extends string> = K extends keyof WindowMap ? ElectroWindow<WindowMap[K]> : ElectroWindow;
364
+ type _SuggestViewKeys = (keyof ViewMap & string) | (string & {});
365
+ type _SuggestWindowKeys = (keyof WindowApiMap & string) | (string & {});
366
+ type _ResolveWindowApi<K extends string> = K extends keyof WindowApiMap ? WindowApiMap[K] : unknown;
342
367
  /** Extract the dependency IDs union for a feature. */
343
368
  type _DepIds<FId> = FId extends keyof FeatureMap ? FeatureMap[FId]["dependencies"] : never;
344
369
  /** Own service keys for a feature. */
@@ -395,8 +420,9 @@ type TypedContext<FId extends keyof FeatureMap, ExcludeSvc extends string = neve
395
420
  publish<K extends _OwnEventKeys<FId>>(event: K, ...args: undefined extends _ResolveOwnEvent<FId, K> ? [payload?: _ResolveOwnEvent<FId, K>] : [payload: _ResolveOwnEvent<FId, K>]): void;
396
421
  on<K extends _OwnEventKeys<FId> | _DepEventKeys<FId>>(event: K, handler: (payload: _ResolveEvent<FId, K>) => void): () => void;
397
422
  };
398
- createWindow: <K extends _SuggestWindowKeys>(name: K) => _ResolveWindow<K & string>;
399
- getWindow: <K extends _SuggestWindowKeys>(name: K) => _ResolveWindow<K & string> | null;
423
+ getWindow: <K extends _SuggestWindowKeys>(id: K) => CreatedWindow<_ResolveWindowApi<K & string>> | null;
424
+ createView: <K extends _SuggestViewKeys>(id: K) => ElectroView;
425
+ getView: <K extends _SuggestViewKeys>(id: K) => ElectroView | null;
400
426
  };
401
427
  /** Unscoped context — flat suggestions from all features. */
402
428
  type BaseContext = {
@@ -409,8 +435,9 @@ type BaseContext = {
409
435
  publish(event: _SuggestAllEvent, payload?: unknown): void;
410
436
  on(event: string, handler: (payload: unknown) => void): () => void;
411
437
  };
412
- createWindow: <K extends _SuggestWindowKeys>(name: K) => _ResolveWindow<K & string>;
413
- getWindow: <K extends _SuggestWindowKeys>(name: K) => _ResolveWindow<K & string> | null;
438
+ getWindow: <K extends _SuggestWindowKeys>(id: K) => CreatedWindow<_ResolveWindowApi<K & string>> | null;
439
+ createView: <K extends _SuggestViewKeys>(id: K) => ElectroView;
440
+ getView: <K extends _SuggestViewKeys>(id: K) => ElectroView | null;
414
441
  };
415
442
  interface LoggerContext {
416
443
  debug(code: string, message: string, details?: Record<string, unknown>): void;
@@ -580,9 +607,12 @@ declare class FeatureManager {
580
607
  /** Global task ownership: taskId → featureId. */
581
608
  private readonly taskOwners;
582
609
  private windowManager;
610
+ private viewManager;
583
611
  constructor(logger: LoggerContext, eventBus?: EventBus | undefined);
584
- /** @internal Set the window manager (called by Electron layer before bootstrap). */
612
+ /** @internal Set the window manager (called by Runtime before bootstrap). */
585
613
  setWindowManager(windowManager: WindowManager): void;
614
+ /** @internal Set the view manager (called by Runtime before bootstrap). */
615
+ setViewManager(viewManager: ViewManager): void;
586
616
  /**
587
617
  * Register a feature or a list of features.
588
618
  * If a feature with the same ID is already registered, a warning is logged and the feature is skipped.
@@ -641,7 +671,7 @@ declare class Feature<FId extends FeatureId> {
641
671
  * @throws If the transition is not allowed from the current state.
642
672
  */
643
673
  transition(target: FeatureStatus): void;
644
- initialize(features: Feature<FeatureId>[], manager: FeatureManager, eventBus?: EventBus, windowManager?: WindowManager): Promise<void>;
674
+ initialize(features: Feature<FeatureId>[], manager: FeatureManager, eventBus?: EventBus, windowManager?: WindowManager, viewManager?: ViewManager): Promise<void>;
645
675
  activate(): Promise<void>;
646
676
  deactivate(): Promise<void>;
647
677
  destroy(): Promise<void>;
@@ -710,6 +740,8 @@ declare class Logger implements LoggerContext {
710
740
  //#region src/core/runtime/types.d.ts
711
741
  type RuntimeConfig = {
712
742
  features?: FeatureConfig<any>[];
743
+ windows?: WindowInstance[];
744
+ views?: ViewInstance[];
713
745
  logger?: {
714
746
  handlers?: LogHandler[];
715
747
  };
@@ -721,10 +753,9 @@ declare class Runtime {
721
753
  readonly logger: Logger;
722
754
  private readonly featureManager;
723
755
  private readonly eventBus;
724
- private windowManager;
756
+ private readonly windowManager;
757
+ private readonly viewManager;
725
758
  constructor(config?: RuntimeConfig);
726
- /** @internal Inject window manager (called by Electron layer before start). */
727
- _injectWindowManager(windowManager: WindowManager): void;
728
759
  register(features: FeatureConfig<any> | FeatureConfig<any>[]): void;
729
760
  start(): Promise<void>;
730
761
  shutdown(): Promise<void>;
@@ -750,25 +781,45 @@ declare function createService<Scope extends ServiceScope, TApi, TId extends Ser
750
781
  /** Create a {@link Task} from a config object. Throws if `id` is empty. */
751
782
  declare function createTask<TId extends TaskId, TPayload = void>(config: TaskConfig<TId, TPayload>): CreatedTask<TId, TPayload>;
752
783
  //#endregion
784
+ //#region src/core/window/helpers.d.ts
785
+ /**
786
+ * Creates a {@link Window} instance from a configuration object.
787
+ *
788
+ * @param config - Window configuration with `id`, optional `options`, and optional `api`.
789
+ * @returns A new `Window` instance ready for registration.
790
+ * @throws If `config.id` is empty.
791
+ */
792
+ declare function createWindow<TApi = void>(config: WindowConfig<TApi>): CreatedWindow<TApi>;
793
+ //#endregion
794
+ //#region src/core/view/helpers.d.ts
795
+ /**
796
+ * Creates a {@link View} instance from a configuration object.
797
+ *
798
+ * @param config - View configuration with `id`, and either `renderer` (for Vite-built views) or `webPreferences` (for dynamic views).
799
+ * @returns A new `View` instance ready for registration.
800
+ * @throws If `config.id` is empty.
801
+ */
802
+ declare function createView(config: ViewConfig): CreatedView;
803
+ //#endregion
753
804
  //#region src/policy/types.d.ts
754
805
  /** Outcome of a policy check. */
755
806
  declare enum PolicyDecision {
756
807
  ALLOWED = "ALLOWED",
757
808
  ACCESS_DENIED = "ACCESS_DENIED",
758
- WINDOW_NOT_FOUND = "WINDOW_NOT_FOUND"
809
+ VIEW_NOT_FOUND = "VIEW_NOT_FOUND"
759
810
  }
760
811
  /** Result of a policy check with context. */
761
812
  interface PolicyResult {
762
813
  readonly decision: PolicyDecision;
763
- readonly windowName: string;
814
+ readonly viewName: string;
764
815
  readonly featureId: string;
765
816
  }
766
817
  //#endregion
767
818
  //#region src/policy/engine.d.ts
768
819
  /**
769
- * Deny-by-default policy engine for window–feature access control.
820
+ * Deny-by-default policy engine for view–feature access control.
770
821
  *
771
- * A window's renderer can only access exposed services of features
822
+ * A view's renderer can only access exposed services of features
772
823
  * listed in its `features: []` config. Everything else is denied.
773
824
  *
774
825
  * Used at:
@@ -777,15 +828,15 @@ interface PolicyResult {
777
828
  */
778
829
  declare class PolicyEngine {
779
830
  private readonly policies;
780
- constructor(windows: readonly WindowDefinition[]);
831
+ constructor(views: readonly ViewDefinition[]);
781
832
  /** Full policy check with decision code and context. */
782
- check(windowName: string, featureId: string): PolicyResult;
833
+ check(viewName: string, featureId: string): PolicyResult;
783
834
  /** Convenience: returns true only when access is ALLOWED. */
784
- canAccess(windowName: string, featureId: string): boolean;
785
- /** Returns the allowed feature IDs for a window. Throws if window unknown. */
786
- getAllowedFeatures(windowName: string): readonly string[];
787
- /** Returns all registered window names. */
788
- getWindowNames(): string[];
835
+ canAccess(viewName: string, featureId: string): boolean;
836
+ /** Returns the allowed feature IDs for a view. Throws if view unknown. */
837
+ getAllowedFeatures(viewName: string): readonly string[];
838
+ /** Returns all registered view names. */
839
+ getViewNames(): string[];
789
840
  }
790
841
  //#endregion
791
- export { type BaseContext, type CreatedEvent, type CreatedService, type CreatedTask, type DefineConfigInput, type DefineRuntimeInput, type DefineWindowInput, type ElectroConfig, type ElectroWindow, EventAccessor, type EventHandler, type EventId, type EventInstance, type EventSubscription, type FeatureConfig, type FeatureContext, type FeatureHandle, type FeatureId, type FeatureMap, FeatureStatus, type LogEntry, type LogHandler, type LoggerContext, PolicyDecision, PolicyEngine, type PolicyResult, Runtime, type RuntimeConfig, type RuntimeDefinition, RuntimeState, type ServiceConfig, type ServiceId, type ServiceInfo, type ServiceOwnerMap, ServiceScope, ServiceStatus, type StopMode, type TaskConfig, type TaskExecutionContext, type TaskHandle, type TaskId, TaskOverlapStrategy, type TaskOwnerMap, TaskRetryStrategy, TaskStatus, type TaskStatusInfo, type TypedContext, type WindowBehavior, type WindowCloseBehavior, type WindowDefinition, type WindowFactory, type WindowInfo, type WindowLifecycle, type WindowMap, type WindowType, createEvent, createFeature, createRuntime, createService, createTask, defineConfig, defineRuntime, defineWindow };
842
+ export { type BaseContext, type CreatedEvent, type CreatedService, type CreatedTask, type CreatedView, type CreatedWindow, type DefineConfigInput, type DefineRuntimeInput, type DefineViewInput, type ElectroConfig, type ElectroView, EventAccessor, type EventHandler, type EventId, type EventInstance, type EventSubscription, type FeatureConfig, type FeatureContext, type FeatureHandle, type FeatureId, type FeatureMap, FeatureStatus, type LogEntry, type LogHandler, type LoggerContext, PolicyDecision, PolicyEngine, type PolicyResult, Runtime, type RuntimeConfig, type RuntimeDefinition, RuntimeState, type ServiceConfig, type ServiceId, type ServiceInfo, type ServiceOwnerMap, ServiceScope, ServiceStatus, type StopMode, type TaskConfig, type TaskExecutionContext, type TaskHandle, type TaskId, TaskOverlapStrategy, type TaskOwnerMap, TaskRetryStrategy, TaskStatus, type TaskStatusInfo, type TypedContext, type ViewConfig, type ViewDefinition, type ViewId, type ViewInstance, type ViewMap, type WindowApiMap, type WindowConfig, type WindowId, type WindowInstance, createEvent, createFeature, createRuntime, createService, createTask, createView, createWindow, defineConfig, defineRuntime, defineView };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
- import { join } from "node:path";
3
2
  import { Cron } from "croner";
3
+ import { join } from "node:path";
4
4
 
5
5
  //#region \0rolldown/runtime.js
6
6
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
@@ -8,15 +8,15 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
8
8
  //#endregion
9
9
  //#region src/config/define-config.ts
10
10
  function defineConfig(input) {
11
- const windows = input.windows ?? [];
11
+ const views = input.views ?? [];
12
12
  const seen = /* @__PURE__ */ new Set();
13
- for (const win of windows) {
14
- if (seen.has(win.name)) throw new Error(`[electro] defineConfig: duplicate window name "${win.name}"`);
15
- seen.add(win.name);
13
+ for (const view of views) {
14
+ if (seen.has(view.name)) throw new Error(`[electro] defineConfig: duplicate view name "${view.name}"`);
15
+ seen.add(view.name);
16
16
  }
17
17
  return {
18
18
  runtime: input.runtime,
19
- windows
19
+ views
20
20
  };
21
21
  }
22
22
 
@@ -53,26 +53,19 @@ function defineRuntime(input) {
53
53
  }
54
54
 
55
55
  //#endregion
56
- //#region src/config/define-window.ts
57
- const WINDOW_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
58
- function defineWindow(input) {
59
- if (!input.name || input.name.trim().length === 0) throw new Error("[electro] defineWindow: name must be a non-empty string");
60
- if (!WINDOW_NAME_PATTERN.test(input.name)) throw new Error(`[electro] defineWindow: name "${input.name}" is invalid. Must match ${WINDOW_NAME_PATTERN.toString()}`);
61
- if (!input.entry || input.entry.trim().length === 0) throw new Error("[electro] defineWindow: entry must be a non-empty string");
62
- const lifecycle = input.lifecycle ?? "singleton";
63
- const close = input.behavior?.close ?? (lifecycle === "multi" ? "destroy" : "hide");
64
- if (lifecycle === "multi" && close === "hide") throw new Error("[electro] defineWindow: behavior.close \"hide\" is only allowed with lifecycle \"singleton\". Multi-instance windows must use \"destroy\".");
56
+ //#region src/config/define-view.ts
57
+ const VIEW_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
58
+ function defineView(input) {
59
+ if (!input.name || input.name.trim().length === 0) throw new Error("[electro] defineView: name must be a non-empty string");
60
+ if (!VIEW_NAME_PATTERN.test(input.name)) throw new Error(`[electro] defineView: name "${input.name}" is invalid. Must match ${VIEW_NAME_PATTERN.toString()}`);
61
+ if (!input.entry || input.entry.trim().length === 0) throw new Error("[electro] defineView: entry must be a non-empty string");
65
62
  return {
66
63
  name: input.name,
67
64
  entry: input.entry,
68
- type: input.type,
69
65
  features: input.features,
70
66
  vite: input.vite,
71
67
  preload: input.preload,
72
- lifecycle,
73
- autoShow: input.autoShow ?? false,
74
- behavior: { close },
75
- window: input.window,
68
+ webPreferences: input.webPreferences,
76
69
  __source: getCallerPath() ?? ""
77
70
  };
78
71
  }
@@ -158,125 +151,6 @@ let RuntimeState = /* @__PURE__ */ function(RuntimeState) {
158
151
  return RuntimeState;
159
152
  }({});
160
153
 
161
- //#endregion
162
- //#region src/window/default-factory.ts
163
- /**
164
- * Default WindowFactory — creates Electron BrowserWindow or BaseWindow
165
- * from a WindowDefinition at runtime.
166
- */
167
- function createDefaultWindowFactory() {
168
- return { create(definition) {
169
- if (definition.type === "browser-window") {
170
- const { BrowserWindow } = __require("electron");
171
- return new BrowserWindow({
172
- show: definition.autoShow ?? false,
173
- ...definition.window ?? {}
174
- });
175
- }
176
- const { BaseWindow: BW } = __require("electron");
177
- return new BW({
178
- show: definition.autoShow ?? false,
179
- ...definition.window ?? {}
180
- });
181
- } };
182
- }
183
-
184
- //#endregion
185
- //#region src/window/manager.ts
186
- /**
187
- * WindowManager — registry, creation, and lifecycle coordinator for windows.
188
- *
189
- * Manages window definitions and their instances through a WindowFactory
190
- * abstraction. Enforces singleton/multi lifecycle semantics and provides
191
- * querying and bulk destruction capabilities.
192
- */
193
- var WindowManager = class {
194
- factory;
195
- definitions = /* @__PURE__ */ new Map();
196
- instances = /* @__PURE__ */ new Map();
197
- constructor(factory) {
198
- this.factory = factory;
199
- }
200
- /** Register a window definition. Throws on duplicate name. */
201
- registerDefinition(definition) {
202
- if (this.definitions.has(definition.name)) throw new Error(`Window definition duplicate: "${definition.name}" is already registered`);
203
- this.definitions.set(definition.name, definition);
204
- }
205
- /** Check whether a definition is registered for the given name. */
206
- hasDefinition(name) {
207
- return this.definitions.has(name);
208
- }
209
- /**
210
- * Create a window from a registered definition.
211
- *
212
- * - Throws if no definition exists for `name`.
213
- * - For singleton lifecycle (the default), throws if a live instance already exists.
214
- * - For multi lifecycle, always creates a new instance.
215
- */
216
- createWindow(name) {
217
- const definition = this.definitions.get(name);
218
- if (!definition) throw new Error(`Cannot create window: no definition found for "${name}"`);
219
- if ((definition.lifecycle ?? "singleton") === "singleton") {
220
- if (this.getAliveInstances(name).length > 0) throw new Error(`Cannot create window "${name}": singleton instance already exists`);
221
- }
222
- const window = this.factory.create(definition);
223
- const electrified = window;
224
- electrified.load = async () => {
225
- if (typeof window.loadURL !== "function") throw new Error(`Cannot load window "${name}": BaseWindow does not support loadURL/loadFile. Set type: "browser-window" in your window definition.`);
226
- const bw = window;
227
- const devUrl = process.env[`ELECTRO_DEV_URL_${name}`];
228
- if (devUrl) {
229
- await bw.loadURL(devUrl);
230
- return;
231
- }
232
- const { app } = await import("electron");
233
- await bw.loadFile(join(app.getAppPath(), ".electro", "out", "renderer", name, "index.html"));
234
- };
235
- const list = this.instances.get(name);
236
- if (list) list.push(electrified);
237
- else this.instances.set(name, [electrified]);
238
- return electrified;
239
- }
240
- /**
241
- * Get the first alive window for the given name, or null.
242
- * Cleans up destroyed instances as a side effect.
243
- */
244
- getWindow(name) {
245
- const list = this.instances.get(name);
246
- if (!list) return null;
247
- const alive = list.filter((w) => !w.isDestroyed());
248
- this.instances.set(name, alive);
249
- return alive.length > 0 ? alive[0] : null;
250
- }
251
- /** Destroy all instances for the given name. No-op if name is unknown. */
252
- destroyWindow(name) {
253
- const list = this.instances.get(name);
254
- if (!list) return;
255
- for (const window of list) if (!window.isDestroyed()) window.destroy();
256
- this.instances.set(name, []);
257
- }
258
- /** Destroy all tracked windows across all names. */
259
- destroyAll() {
260
- for (const [name] of this.instances) this.destroyWindow(name);
261
- }
262
- /** Returns a snapshot of all tracked windows (including destroyed ones). */
263
- list() {
264
- const result = [];
265
- for (const [name, windows] of this.instances) for (const window of windows) result.push({
266
- name,
267
- windowId: window.id,
268
- destroyed: window.isDestroyed()
269
- });
270
- return result;
271
- }
272
- /** Return alive (non-destroyed) instances for a given name. */
273
- getAliveInstances(name) {
274
- const list = this.instances.get(name);
275
- if (!list) return [];
276
- return list.filter((w) => !w.isDestroyed());
277
- }
278
- };
279
-
280
154
  //#endregion
281
155
  //#region src/core/event-bus/event-bus.ts
282
156
  var EventBus = class {
@@ -311,26 +185,6 @@ var EventBus = class {
311
185
  }
312
186
  };
313
187
 
314
- //#endregion
315
- //#region src/window/accessor.ts
316
- /**
317
- * Thin accessor bound to FeatureContext.
318
- *
319
- * Provides `createWindow(name)` and `getWindow(name)` for features.
320
- * Delegates all work to WindowManager.
321
- */
322
- var WindowAccessor = class {
323
- constructor(manager) {
324
- this.manager = manager;
325
- }
326
- createWindow(name) {
327
- return this.manager.createWindow(name);
328
- }
329
- getWindow(name) {
330
- return this.manager.getWindow(name);
331
- }
332
- };
333
-
334
188
  //#endregion
335
189
  //#region src/core/service/enums.ts
336
190
  let ServiceScope = /* @__PURE__ */ function(ServiceScope) {
@@ -676,12 +530,15 @@ var Feature = class {
676
530
  throw new Error("Events not yet initialized");
677
531
  }
678
532
  },
679
- createWindow: () => {
680
- throw new Error("Window manager not available");
681
- },
682
533
  getWindow: () => {
683
534
  throw new Error("Window manager not available");
684
535
  },
536
+ createView: () => {
537
+ throw new Error("View manager not available");
538
+ },
539
+ getView: () => {
540
+ throw new Error("View manager not available");
541
+ },
685
542
  logger: this.logger,
686
543
  signal: this.controller.signal
687
544
  };
@@ -699,8 +556,8 @@ var Feature = class {
699
556
  transition(target) {
700
557
  this.state.transition(target);
701
558
  }
702
- async initialize(features, manager, eventBus, windowManager) {
703
- this.buildContext(features, manager, eventBus, windowManager);
559
+ async initialize(features, manager, eventBus, windowManager, viewManager) {
560
+ this.buildContext(features, manager, eventBus, windowManager, viewManager);
704
561
  await this.config.onInitialize?.(this.context);
705
562
  }
706
563
  async activate() {
@@ -716,7 +573,7 @@ var Feature = class {
716
573
  async destroy() {
717
574
  await this.config.onDestroy?.(this.context);
718
575
  }
719
- buildContext(features, manager, eventBus, windowManager) {
576
+ buildContext(features, manager, eventBus, windowManager, viewManager) {
720
577
  this.context.signal = this.controller.signal;
721
578
  this.context.logger = this.logger;
722
579
  this.serviceManager = new ServiceManager(this.context);
@@ -753,10 +610,20 @@ var Feature = class {
753
610
  }
754
611
  };
755
612
  }
756
- if (windowManager) {
757
- const windowAccessor = new WindowAccessor(windowManager);
758
- this.context.createWindow = (name) => windowAccessor.createWindow(name);
759
- this.context.getWindow = (name) => windowAccessor.getWindow(name);
613
+ if (windowManager) this.context.getWindow = (id) => {
614
+ return windowManager.get(id);
615
+ };
616
+ if (viewManager) {
617
+ this.context.createView = (id) => {
618
+ const inst = viewManager.get(id);
619
+ if (!inst) throw new Error(`View "${id}" not registered`);
620
+ return inst.create();
621
+ };
622
+ this.context.getView = (id) => {
623
+ const inst = viewManager.get(id);
624
+ if (!inst) return null;
625
+ return inst.view();
626
+ };
760
627
  }
761
628
  }
762
629
  };
@@ -770,14 +637,19 @@ var FeatureManager = class {
770
637
  /** Global task ownership: taskId → featureId. */
771
638
  taskOwners = /* @__PURE__ */ new Map();
772
639
  windowManager = null;
640
+ viewManager = null;
773
641
  constructor(logger, eventBus = void 0) {
774
642
  this.logger = logger;
775
643
  this.eventBus = eventBus;
776
644
  }
777
- /** @internal Set the window manager (called by Electron layer before bootstrap). */
645
+ /** @internal Set the window manager (called by Runtime before bootstrap). */
778
646
  setWindowManager(windowManager) {
779
647
  this.windowManager = windowManager;
780
648
  }
649
+ /** @internal Set the view manager (called by Runtime before bootstrap). */
650
+ setViewManager(viewManager) {
651
+ this.viewManager = viewManager;
652
+ }
781
653
  /**
782
654
  * Register a feature or a list of features.
783
655
  * If a feature with the same ID is already registered, a warning is logged and the feature is skipped.
@@ -837,7 +709,7 @@ var FeatureManager = class {
837
709
  const features = [];
838
710
  const deps = feature.config.dependencies ?? [];
839
711
  for (const dep of deps) features.push(this.registry.get(dep));
840
- await feature.initialize(features, this, this.eventBus, this.windowManager ?? void 0);
712
+ await feature.initialize(features, this, this.eventBus, this.windowManager ?? void 0, this.viewManager ?? void 0);
841
713
  feature.transition(FeatureStatus.READY);
842
714
  } catch (err) {
843
715
  feature.transition(FeatureStatus.ERROR);
@@ -1006,6 +878,49 @@ var Logger = class {
1006
878
  }
1007
879
  };
1008
880
 
881
+ //#endregion
882
+ //#region src/core/view/manager.ts
883
+ /**
884
+ * ViewManager — global registry for view instances.
885
+ *
886
+ * Views are registered at runtime startup and accessible from any feature.
887
+ * Views are idempotent: `get(id).create()` returns the same instance if alive.
888
+ */
889
+ var ViewManager = class {
890
+ instances = /* @__PURE__ */ new Map();
891
+ register(view) {
892
+ if (this.instances.has(view.id)) throw new Error(`View "${view.id}" is already registered`);
893
+ this.instances.set(view.id, view);
894
+ }
895
+ get(id) {
896
+ return this.instances.get(id) ?? null;
897
+ }
898
+ destroyAll() {
899
+ for (const inst of this.instances.values()) inst.destroy();
900
+ }
901
+ };
902
+
903
+ //#endregion
904
+ //#region src/core/window/manager.ts
905
+ /**
906
+ * WindowManager — global registry for window instances.
907
+ *
908
+ * Windows are registered at runtime startup and accessible from any feature.
909
+ */
910
+ var WindowManager = class {
911
+ instances = /* @__PURE__ */ new Map();
912
+ register(window) {
913
+ if (this.instances.has(window.id)) throw new Error(`Window "${window.id}" is already registered`);
914
+ this.instances.set(window.id, window);
915
+ }
916
+ get(id) {
917
+ return this.instances.get(id) ?? null;
918
+ }
919
+ destroyAll() {
920
+ for (const inst of this.instances.values()) inst.destroy();
921
+ }
922
+ };
923
+
1009
924
  //#endregion
1010
925
  //#region src/core/runtime/runtime.ts
1011
926
  const RUNTIME_TRANSITIONS = {
@@ -1021,7 +936,8 @@ var Runtime = class {
1021
936
  logger;
1022
937
  featureManager;
1023
938
  eventBus;
1024
- windowManager = null;
939
+ windowManager;
940
+ viewManager;
1025
941
  constructor(config) {
1026
942
  this.state = new StateMachine({
1027
943
  transitions: RUNTIME_TRANSITIONS,
@@ -1033,24 +949,19 @@ var Runtime = class {
1033
949
  this.featureManager = new FeatureManager(this.logger, this.eventBus);
1034
950
  this.logger.addHandler(createConsoleHandler());
1035
951
  if (config?.logger?.handlers) for (const handler of config.logger.handlers) this.logger.addHandler(handler);
952
+ this.windowManager = new WindowManager();
953
+ for (const win of config?.windows ?? []) this.windowManager.register(win);
954
+ this.viewManager = new ViewManager();
955
+ for (const view of config?.views ?? []) this.viewManager.register(view);
956
+ this.featureManager.setWindowManager(this.windowManager);
957
+ this.featureManager.setViewManager(this.viewManager);
1036
958
  if (config?.features) this.featureManager.register(config.features);
1037
959
  }
1038
- /** @internal Inject window manager (called by Electron layer before start). */
1039
- _injectWindowManager(windowManager) {
1040
- this.state.assertState(RuntimeState.CREATED);
1041
- this.windowManager = windowManager;
1042
- this.featureManager.setWindowManager(windowManager);
1043
- }
1044
960
  register(features) {
1045
961
  this.state.assertState(RuntimeState.CREATED);
1046
962
  this.featureManager.register(features);
1047
963
  }
1048
964
  async start() {
1049
- if (!this.windowManager && typeof __ELECTRO_WINDOW_DEFINITIONS__ !== "undefined") {
1050
- const wm = new WindowManager(createDefaultWindowFactory());
1051
- for (const def of __ELECTRO_WINDOW_DEFINITIONS__) wm.registerDefinition(def);
1052
- this._injectWindowManager(wm);
1053
- }
1054
965
  this.state.transition(RuntimeState.STARTING);
1055
966
  try {
1056
967
  await this.featureManager.bootstrap();
@@ -1064,7 +975,8 @@ var Runtime = class {
1064
975
  this.state.assertState(RuntimeState.RUNNING);
1065
976
  this.state.transition(RuntimeState.STOPPING);
1066
977
  await this.featureManager.shutdown();
1067
- this.windowManager?.destroyAll();
978
+ this.viewManager.destroyAll();
979
+ this.windowManager.destroyAll();
1068
980
  this.state.transition(RuntimeState.STOPPED);
1069
981
  }
1070
982
  async enable(id) {
@@ -1531,22 +1443,154 @@ function createTask(config) {
1531
1443
  return new Task(config);
1532
1444
  }
1533
1445
 
1446
+ //#endregion
1447
+ //#region src/core/window/window.ts
1448
+ /**
1449
+ * Window — manages a single BaseWindow instance with an optional typed API.
1450
+ *
1451
+ * `create()` is idempotent: calling it while an alive window exists returns
1452
+ * the existing instance. After the window is destroyed (user close, etc.),
1453
+ * calling `create()` again spawns a fresh one.
1454
+ */
1455
+ var Window = class {
1456
+ _window = null;
1457
+ _api = null;
1458
+ constructor(config) {
1459
+ this.config = config;
1460
+ }
1461
+ get id() {
1462
+ return this.config.id;
1463
+ }
1464
+ create() {
1465
+ if (this._window && !this._window.isDestroyed()) return this._window;
1466
+ const { BaseWindow: BW } = __require("electron");
1467
+ this._window = new BW({
1468
+ show: false,
1469
+ ...this.config.options ?? {}
1470
+ });
1471
+ if (this.config.api) this._api = this.config.api(this._window);
1472
+ return this._window;
1473
+ }
1474
+ get window() {
1475
+ if (this._window?.isDestroyed()) {
1476
+ this._window = null;
1477
+ this._api = null;
1478
+ }
1479
+ return this._window;
1480
+ }
1481
+ get api() {
1482
+ if (this._window?.isDestroyed()) {
1483
+ this._window = null;
1484
+ this._api = null;
1485
+ }
1486
+ return this._api;
1487
+ }
1488
+ destroy() {
1489
+ if (this._window && !this._window.isDestroyed()) this._window.destroy();
1490
+ this._window = null;
1491
+ this._api = null;
1492
+ }
1493
+ };
1494
+
1495
+ //#endregion
1496
+ //#region src/core/window/helpers.ts
1497
+ /**
1498
+ * Creates a {@link Window} instance from a configuration object.
1499
+ *
1500
+ * @param config - Window configuration with `id`, optional `options`, and optional `api`.
1501
+ * @returns A new `Window` instance ready for registration.
1502
+ * @throws If `config.id` is empty.
1503
+ */
1504
+ function createWindow(config) {
1505
+ if (!config.id) throw new Error("Window must have an id");
1506
+ return new Window(config);
1507
+ }
1508
+
1509
+ //#endregion
1510
+ //#region src/core/view/view.ts
1511
+ function isRendererConfig(config) {
1512
+ return "renderer" in config;
1513
+ }
1514
+ function resolveRendererName(config) {
1515
+ return config.renderer === true ? config.id : config.renderer;
1516
+ }
1517
+ /**
1518
+ * View — manages a single WebContentsView instance.
1519
+ *
1520
+ * `create()` is idempotent: calling it while an alive view exists returns
1521
+ * the existing instance. After the view is destroyed, calling `create()`
1522
+ * spawns a fresh one.
1523
+ *
1524
+ * For renderer-linked views, `load()` resolves the dev URL or production
1525
+ * file path automatically.
1526
+ */
1527
+ var View = class {
1528
+ _view = null;
1529
+ constructor(config) {
1530
+ this.config = config;
1531
+ }
1532
+ get id() {
1533
+ return this.config.id;
1534
+ }
1535
+ create() {
1536
+ if (this._view && !this._view.webContents.isDestroyed()) return this._view;
1537
+ const { WebContentsView: WCV } = __require("electron");
1538
+ const view = new WCV({ webPreferences: { ...this.config.webPreferences ?? {} } });
1539
+ if (isRendererConfig(this.config)) {
1540
+ const rendererName = resolveRendererName(this.config);
1541
+ view.load = async () => {
1542
+ const devUrl = process.env[`ELECTRO_DEV_URL_${rendererName}`];
1543
+ if (devUrl) {
1544
+ await view.webContents.loadURL(devUrl);
1545
+ return;
1546
+ }
1547
+ const { app } = await import("electron");
1548
+ await view.webContents.loadFile(join(app.getAppPath(), ".electro", "out", "renderer", rendererName, "index.html"));
1549
+ };
1550
+ } else view.load = async () => {};
1551
+ this._view = view;
1552
+ return view;
1553
+ }
1554
+ view() {
1555
+ if (this._view?.webContents.isDestroyed()) this._view = null;
1556
+ return this._view;
1557
+ }
1558
+ destroy() {
1559
+ if (this._view && !this._view.webContents.isDestroyed()) this._view.webContents.close();
1560
+ this._view = null;
1561
+ }
1562
+ };
1563
+
1564
+ //#endregion
1565
+ //#region src/core/view/helpers.ts
1566
+ /**
1567
+ * Creates a {@link View} instance from a configuration object.
1568
+ *
1569
+ * @param config - View configuration with `id`, and either `renderer` (for Vite-built views) or `webPreferences` (for dynamic views).
1570
+ * @returns A new `View` instance ready for registration.
1571
+ * @throws If `config.id` is empty.
1572
+ */
1573
+ function createView(config) {
1574
+ if (!config.id) throw new Error("View must have an id");
1575
+ return new View(config);
1576
+ }
1577
+
1534
1578
  //#endregion
1535
1579
  //#region src/policy/types.ts
1536
1580
  /** Outcome of a policy check. */
1537
1581
  let PolicyDecision = /* @__PURE__ */ function(PolicyDecision) {
1538
1582
  PolicyDecision["ALLOWED"] = "ALLOWED";
1539
1583
  PolicyDecision["ACCESS_DENIED"] = "ACCESS_DENIED";
1540
- PolicyDecision["WINDOW_NOT_FOUND"] = "WINDOW_NOT_FOUND";
1584
+ PolicyDecision["VIEW_NOT_FOUND"] = "VIEW_NOT_FOUND";
1541
1585
  return PolicyDecision;
1542
1586
  }({});
1543
1587
 
1544
1588
  //#endregion
1545
1589
  //#region src/policy/engine.ts
1546
1590
  /**
1547
- * Deny-by-default policy engine for window–feature access control.
1591
+ * Deny-by-default policy engine for view–feature access control.
1548
1592
  *
1549
- * A window's renderer can only access exposed services of features
1593
+ * A view's renderer can only access exposed services of features
1550
1594
  * listed in its `features: []` config. Everything else is denied.
1551
1595
  *
1552
1596
  * Used at:
@@ -1555,38 +1599,38 @@ let PolicyDecision = /* @__PURE__ */ function(PolicyDecision) {
1555
1599
  */
1556
1600
  var PolicyEngine = class {
1557
1601
  policies = /* @__PURE__ */ new Map();
1558
- constructor(windows) {
1559
- for (const win of windows) this.policies.set(win.name, new Set(win.features ?? []));
1602
+ constructor(views) {
1603
+ for (const view of views) this.policies.set(view.name, new Set(view.features ?? []));
1560
1604
  }
1561
1605
  /** Full policy check with decision code and context. */
1562
- check(windowName, featureId) {
1563
- const allowed = this.policies.get(windowName);
1606
+ check(viewName, featureId) {
1607
+ const allowed = this.policies.get(viewName);
1564
1608
  if (!allowed) return {
1565
- decision: PolicyDecision.WINDOW_NOT_FOUND,
1566
- windowName,
1609
+ decision: PolicyDecision.VIEW_NOT_FOUND,
1610
+ viewName,
1567
1611
  featureId
1568
1612
  };
1569
1613
  return {
1570
1614
  decision: allowed.has(featureId) ? PolicyDecision.ALLOWED : PolicyDecision.ACCESS_DENIED,
1571
- windowName,
1615
+ viewName,
1572
1616
  featureId
1573
1617
  };
1574
1618
  }
1575
1619
  /** Convenience: returns true only when access is ALLOWED. */
1576
- canAccess(windowName, featureId) {
1577
- return this.check(windowName, featureId).decision === PolicyDecision.ALLOWED;
1620
+ canAccess(viewName, featureId) {
1621
+ return this.check(viewName, featureId).decision === PolicyDecision.ALLOWED;
1578
1622
  }
1579
- /** Returns the allowed feature IDs for a window. Throws if window unknown. */
1580
- getAllowedFeatures(windowName) {
1581
- const allowed = this.policies.get(windowName);
1582
- if (!allowed) throw new Error(`Window "${windowName}" is not registered in the policy engine`);
1623
+ /** Returns the allowed feature IDs for a view. Throws if view unknown. */
1624
+ getAllowedFeatures(viewName) {
1625
+ const allowed = this.policies.get(viewName);
1626
+ if (!allowed) throw new Error(`View "${viewName}" is not registered in the policy engine`);
1583
1627
  return [...allowed];
1584
1628
  }
1585
- /** Returns all registered window names. */
1586
- getWindowNames() {
1629
+ /** Returns all registered view names. */
1630
+ getViewNames() {
1587
1631
  return [...this.policies.keys()];
1588
1632
  }
1589
1633
  };
1590
1634
 
1591
1635
  //#endregion
1592
- export { EventAccessor, FeatureStatus, PolicyDecision, PolicyEngine, Runtime, RuntimeState, ServiceScope, ServiceStatus, TaskOverlapStrategy, TaskRetryStrategy, TaskStatus, createEvent, createFeature, createRuntime, createService, createTask, defineConfig, defineRuntime, defineWindow };
1636
+ export { EventAccessor, FeatureStatus, PolicyDecision, PolicyEngine, Runtime, RuntimeState, ServiceScope, ServiceStatus, TaskOverlapStrategy, TaskRetryStrategy, TaskStatus, createEvent, createFeature, createRuntime, createService, createTask, createView, createWindow, defineConfig, defineRuntime, defineView };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cordy/electro",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "Electron framework — feature-first runtime, codegen, and Vite-based build system",
5
5
  "license": "MIT",
6
6
  "type": "module",