@sublime-ui/desktop 0.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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/dist/bridge/main-router.d.ts +30 -0
  4. package/dist/bridge/main-router.js +22 -0
  5. package/dist/bridge/preload.d.ts +32 -0
  6. package/dist/bridge/preload.js +9 -0
  7. package/dist/bridge/proxy.d.ts +24 -0
  8. package/dist/bridge/proxy.js +10 -0
  9. package/dist/client.d.ts +5 -0
  10. package/dist/client.js +16 -0
  11. package/dist/define-native.d.ts +18 -0
  12. package/dist/define-native.js +6 -0
  13. package/dist/errors.d.ts +24 -0
  14. package/dist/errors.js +29 -0
  15. package/dist/index.d.ts +16 -0
  16. package/dist/index.js +46 -0
  17. package/dist/registry.d.ts +22 -0
  18. package/dist/registry.js +21 -0
  19. package/dist/services/clipboard.d.ts +16 -0
  20. package/dist/services/clipboard.js +15 -0
  21. package/dist/services/dialog.d.ts +19 -0
  22. package/dist/services/dialog.js +23 -0
  23. package/dist/services/fs.d.ts +20 -0
  24. package/dist/services/fs.js +26 -0
  25. package/dist/services/get-electron.d.ts +14 -0
  26. package/dist/services/get-electron.js +6 -0
  27. package/dist/services/index.d.ts +7 -0
  28. package/dist/services/index.js +12 -0
  29. package/dist/services/notifications.d.ts +21 -0
  30. package/dist/services/notifications.js +11 -0
  31. package/dist/services/shell.d.ts +16 -0
  32. package/dist/services/shell.js +19 -0
  33. package/dist/shell/create-window.d.ts +48 -0
  34. package/dist/shell/create-window.js +28 -0
  35. package/dist/shell/main.d.ts +53 -0
  36. package/dist/shell/main.js +18 -0
  37. package/dist/types.d.ts +16 -0
  38. package/dist/types.js +0 -0
  39. package/dist/use-native.d.ts +24 -0
  40. package/dist/use-native.js +21 -0
  41. package/package.json +35 -0
  42. package/src/bridge/main-router.ts +52 -0
  43. package/src/bridge/preload.ts +43 -0
  44. package/src/bridge/proxy.ts +31 -0
  45. package/src/client.ts +33 -0
  46. package/src/define-native.ts +21 -0
  47. package/src/errors.ts +45 -0
  48. package/src/index.ts +68 -0
  49. package/src/registry.ts +44 -0
  50. package/src/services/clipboard.ts +21 -0
  51. package/src/services/dialog.ts +31 -0
  52. package/src/services/fs.ts +32 -0
  53. package/src/services/get-electron.ts +14 -0
  54. package/src/services/index.ts +13 -0
  55. package/src/services/notifications.ts +24 -0
  56. package/src/services/shell.ts +24 -0
  57. package/src/shell/create-window.ts +75 -0
  58. package/src/shell/main.ts +70 -0
  59. package/src/types.ts +16 -0
  60. package/src/use-native.ts +57 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aaron Mkandawire and Sublime UI contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # @sublime-ui/desktop
2
+
3
+ Electron desktop packaging and a secure **native bridge** for
4
+ [Sublime UI](https://sublime-ui.github.io/sublime-ui/) apps. The desktop target
5
+ renders your existing **web** UI and adds access to native capabilities.
6
+
7
+ Define a native service in the main process and call it from the renderer with a
8
+ typed hook. Everything travels over one generic, `contextIsolation`-safe IPC
9
+ channel.
10
+
11
+ ```ts
12
+ // main process — define + register
13
+ import { defineNative, registerNative } from '@sublime-ui/desktop';
14
+ export const greeter = defineNative('greeter', {
15
+ async hello(name: string) { return `Hello, ${name}!`; },
16
+ });
17
+ registerNative([greeter]);
18
+ ```
19
+
20
+ ```ts
21
+ // renderer — renderer-safe entry, returns null on web/mobile
22
+ import { useNative } from '@sublime-ui/desktop/client';
23
+ const greeter = useNative<typeof import('./greeter').greeter>('greeter');
24
+ await greeter?.hello('world');
25
+ ```
26
+
27
+ Built-in services cover `fs`, `dialog`, `shell`, `clipboard`, and
28
+ `notifications`. Packaging uses Electron Forge via
29
+ `sublime desktop:dev` / `sublime desktop:build`.
30
+
31
+ > Import renderer code from `@sublime-ui/desktop/client` — it contains **no**
32
+ > node/electron and is safe to bundle into the web build.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ npm install @sublime-ui/desktop
38
+ ```
39
+
40
+ ## Documentation
41
+
42
+ The native bridge and packaging:
43
+ **https://sublime-ui.github.io/sublime-ui/docs/desktop/overview**
44
+
45
+ ## License
46
+
47
+ MIT
@@ -0,0 +1,30 @@
1
+ import { SerializedError } from '../errors.js';
2
+
3
+ /**
4
+ * Main-process IPC router for the single `native:invoke` channel.
5
+ *
6
+ * Registers one handler that resolves `(module, method)` against the native
7
+ * registry and dispatches. Unknown pairs throw a {@link NativeError}; any
8
+ * throw (unknown or from the impl) is caught and returned as a
9
+ * `{ __nativeError: SerializedError }` envelope, which the renderer proxy
10
+ * (`useNative` / Task 8) revives into a {@link NativeError} and rethrows.
11
+ * Keeping the error convention as a returned envelope — rather than a rejected
12
+ * promise — gives the proxy a single, structured-clone-safe shape to detect.
13
+ */
14
+
15
+ /** Envelope returned over IPC when a native call fails. */
16
+ interface NativeErrorEnvelope {
17
+ __nativeError: SerializedError;
18
+ }
19
+ /** Minimal `ipcMain` surface needed to register the channel. Injectable. */
20
+ interface IpcMainLike {
21
+ handle(channel: string, listener: (e: unknown, ...args: any[]) => any): void;
22
+ }
23
+ /**
24
+ * Install the `native:invoke` router onto the given `ipcMain`.
25
+ *
26
+ * @param ipcMain Electron's `ipcMain` (or a compatible fake for tests).
27
+ */
28
+ declare function installNativeRouter(ipcMain: IpcMainLike): void;
29
+
30
+ export { type IpcMainLike, type NativeErrorEnvelope, installNativeRouter };
@@ -0,0 +1,22 @@
1
+ import { resolve } from "../registry";
2
+ import { NativeError, serializeError } from "../errors";
3
+ function installNativeRouter(ipcMain) {
4
+ ipcMain.handle(
5
+ "native:invoke",
6
+ async (_event, mod, method, args = []) => {
7
+ try {
8
+ const fn = resolve(mod, method);
9
+ if (fn === void 0) {
10
+ throw new NativeError(`Unknown native method ${mod}:${method}`);
11
+ }
12
+ return await fn(...args);
13
+ } catch (e) {
14
+ const envelope = { __nativeError: serializeError(e) };
15
+ return envelope;
16
+ }
17
+ }
18
+ );
19
+ }
20
+ export {
21
+ installNativeRouter
22
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Preload bridge: exposes the typed native surface to the renderer.
3
+ *
4
+ * Runs in Electron's isolated preload context (the only place with access to
5
+ * `contextBridge`). It exposes a single object, `window.sublimeNative`, whose
6
+ * `invoke` forwards every call over the one generic `native:invoke` channel to
7
+ * the main-process router (Task 6). Nothing else is exposed — keeping
8
+ * `contextIsolation: true` / `nodeIntegration: false` meaningful — so the
9
+ * renderer can only reach native functionality through the registry-validated
10
+ * router. `useNative` (Task 8) reads this surface and revives error envelopes.
11
+ */
12
+ /** Minimal `contextBridge` surface needed to expose the bridge. Injectable. */
13
+ interface ContextBridgeLike {
14
+ exposeInMainWorld(key: string, api: unknown): void;
15
+ }
16
+ /** Minimal `ipcRenderer` surface needed to forward invocations. Injectable. */
17
+ interface IpcRendererLike {
18
+ invoke(channel: string, ...args: any[]): Promise<unknown>;
19
+ }
20
+ /** Shape exposed at `window.sublimeNative`. */
21
+ interface SublimeNativeBridge {
22
+ invoke(mod: string, method: string, args: unknown[]): Promise<unknown>;
23
+ }
24
+ /**
25
+ * Expose the `sublimeNative` bridge on the main world.
26
+ *
27
+ * @param contextBridge Electron's `contextBridge` (or a compatible fake).
28
+ * @param ipcRenderer Electron's `ipcRenderer` (or a compatible fake).
29
+ */
30
+ declare function exposeNativeBridge(contextBridge: ContextBridgeLike, ipcRenderer: IpcRendererLike): void;
31
+
32
+ export { type ContextBridgeLike, type IpcRendererLike, type SublimeNativeBridge, exposeNativeBridge };
@@ -0,0 +1,9 @@
1
+ function exposeNativeBridge(contextBridge, ipcRenderer) {
2
+ const bridge = {
3
+ invoke: (mod, method, args) => ipcRenderer.invoke("native:invoke", mod, method, args)
4
+ };
5
+ contextBridge.exposeInMainWorld("sublimeNative", bridge);
6
+ }
7
+ export {
8
+ exposeNativeBridge
9
+ };
@@ -0,0 +1,24 @@
1
+ import { NativeMethods } from '../types.js';
2
+
3
+ /**
4
+ * Build the renderer-side typed proxy for a native service.
5
+ *
6
+ * Every property access on the returned object yields a function that forwards
7
+ * its call to `invoke(mod, method, args)` — the single `native:invoke` IPC
8
+ * channel. No node dependencies enter the renderer: the proxy only knows the
9
+ * module name and forwards arguments, while the `M` type parameter (derived
10
+ * from the service authored with `defineNative`) gives end-to-end type safety.
11
+ *
12
+ * @param mod the native service name (the registry key).
13
+ * @param invoke the transport: forwards `(mod, method, args)` over IPC.
14
+ *
15
+ * @example
16
+ * const printer = createProxy<{ print: (copies: number) => Promise<string> }>(
17
+ * 'printer',
18
+ * invoke,
19
+ * );
20
+ * await printer.print(7); // invoke('printer', 'print', [7])
21
+ */
22
+ declare function createProxy<M extends NativeMethods>(mod: string, invoke: (mod: string, method: string, args: unknown[]) => Promise<unknown>): M;
23
+
24
+ export { createProxy };
@@ -0,0 +1,10 @@
1
+ function createProxy(mod, invoke) {
2
+ return new Proxy({}, {
3
+ get(_target, prop) {
4
+ return (...args) => invoke(mod, String(prop), args);
5
+ }
6
+ });
7
+ }
8
+ export {
9
+ createProxy
10
+ };
@@ -0,0 +1,5 @@
1
+ export { defineNative } from './define-native.js';
2
+ export { NativeMethods, NativeService } from './types.js';
3
+ export { NativeError, SerializedError, deserializeError, serializeError } from './errors.js';
4
+ export { useNative } from './use-native.js';
5
+ export { createProxy } from './bridge/proxy.js';
package/dist/client.js ADDED
@@ -0,0 +1,16 @@
1
+ import { defineNative } from "./define-native";
2
+ import {
3
+ NativeError,
4
+ serializeError,
5
+ deserializeError
6
+ } from "./errors";
7
+ import { useNative } from "./use-native";
8
+ import { createProxy } from "./bridge/proxy";
9
+ export {
10
+ NativeError,
11
+ createProxy,
12
+ defineNative,
13
+ deserializeError,
14
+ serializeError,
15
+ useNative
16
+ };
@@ -0,0 +1,18 @@
1
+ import { NativeMethods, NativeService } from './types.js';
2
+
3
+ /**
4
+ * Author a native service from the main process.
5
+ *
6
+ * A thin, fully-typed wrapper: it pairs a service `name` with its async
7
+ * `methods` so that `typeof service` preserves each method's signature.
8
+ * That preserved type is what the renderer contract is derived from, giving
9
+ * end-to-end type safety across the `native:invoke` IPC boundary.
10
+ *
11
+ * @example
12
+ * const printer = defineNative('printer', {
13
+ * print: async (copies: number): Promise<string> => `printed ${copies}`,
14
+ * });
15
+ */
16
+ declare function defineNative<M extends NativeMethods>(name: string, methods: M): NativeService<M>;
17
+
18
+ export { defineNative };
@@ -0,0 +1,6 @@
1
+ function defineNative(name, methods) {
2
+ return { name, methods };
3
+ }
4
+ export {
5
+ defineNative
6
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Typed error transport for the native bridge.
3
+ *
4
+ * Errors thrown by a native service in the main process are serialized to a
5
+ * plain {@link SerializedError} so they can cross the `native:invoke` IPC
6
+ * boundary, then revived into a {@link NativeError} on the renderer side.
7
+ */
8
+ /** Plain, structured-clone-safe representation of a native error. */
9
+ interface SerializedError {
10
+ name: string;
11
+ message: string;
12
+ code?: string;
13
+ }
14
+ /** Error type rethrown on the renderer when a native call fails. */
15
+ declare class NativeError extends Error {
16
+ code?: string;
17
+ constructor(message: string, code?: string);
18
+ }
19
+ /** Coerce any throwable into a transport-safe {@link SerializedError}. */
20
+ declare function serializeError(e: unknown): SerializedError;
21
+ /** Revive a {@link SerializedError} into a {@link NativeError}. */
22
+ declare function deserializeError(s: SerializedError): NativeError;
23
+
24
+ export { NativeError, type SerializedError, deserializeError, serializeError };
package/dist/errors.js ADDED
@@ -0,0 +1,29 @@
1
+ class NativeError extends Error {
2
+ code;
3
+ constructor(message, code) {
4
+ super(message);
5
+ this.name = "NativeError";
6
+ if (code !== void 0) {
7
+ this.code = code;
8
+ }
9
+ }
10
+ }
11
+ function serializeError(e) {
12
+ if (e instanceof Error) {
13
+ const code = e.code;
14
+ const out = { name: e.name, message: e.message };
15
+ if (typeof code === "string") {
16
+ out.code = code;
17
+ }
18
+ return out;
19
+ }
20
+ return { name: "Error", message: String(e) };
21
+ }
22
+ function deserializeError(s) {
23
+ return new NativeError(s.message, s.code);
24
+ }
25
+ export {
26
+ NativeError,
27
+ deserializeError,
28
+ serializeError
29
+ };
@@ -0,0 +1,16 @@
1
+ export { defineNative } from './define-native.js';
2
+ export { registerNative } from './registry.js';
3
+ export { NativeMethods, NativeService } from './types.js';
4
+ export { NativeError, SerializedError, deserializeError, serializeError } from './errors.js';
5
+ export { useNative } from './use-native.js';
6
+ export { createProxy } from './bridge/proxy.js';
7
+ export { IpcMainLike, NativeErrorEnvelope, installNativeRouter } from './bridge/main-router.js';
8
+ export { ContextBridgeLike, IpcRendererLike, SublimeNativeBridge, exposeNativeBridge } from './bridge/preload.js';
9
+ export { BrowserWindowCtor, BrowserWindowLike, CreateWindowOptions, createWindow } from './shell/create-window.js';
10
+ export { AppLike, StartDesktopOptions, startDesktop } from './shell/main.js';
11
+ export { fs } from './services/fs.js';
12
+ export { dialog } from './services/dialog.js';
13
+ export { shell } from './services/shell.js';
14
+ export { clipboard } from './services/clipboard.js';
15
+ export { NotifyOptions, notifications } from './services/notifications.js';
16
+ import 'electron';
package/dist/index.js ADDED
@@ -0,0 +1,46 @@
1
+ import { defineNative } from "./define-native";
2
+ import { registerNative } from "./registry";
3
+ import {
4
+ NativeError,
5
+ serializeError,
6
+ deserializeError
7
+ } from "./errors";
8
+ import { useNative } from "./use-native";
9
+ import { createProxy } from "./bridge/proxy";
10
+ import {
11
+ installNativeRouter
12
+ } from "./bridge/main-router";
13
+ import {
14
+ exposeNativeBridge
15
+ } from "./bridge/preload";
16
+ import {
17
+ createWindow
18
+ } from "./shell/create-window";
19
+ import {
20
+ startDesktop
21
+ } from "./shell/main";
22
+ import {
23
+ fs,
24
+ dialog,
25
+ shell,
26
+ clipboard,
27
+ notifications
28
+ } from "./services/index";
29
+ export {
30
+ NativeError,
31
+ clipboard,
32
+ createProxy,
33
+ createWindow,
34
+ defineNative,
35
+ deserializeError,
36
+ dialog,
37
+ exposeNativeBridge,
38
+ fs,
39
+ installNativeRouter,
40
+ notifications,
41
+ registerNative,
42
+ serializeError,
43
+ shell,
44
+ startDesktop,
45
+ useNative
46
+ };
@@ -0,0 +1,22 @@
1
+ import { NativeService } from './types.js';
2
+
3
+ /**
4
+ * Native service registry.
5
+ *
6
+ * Services authored in the main process are registered here; the
7
+ * `native:invoke` router resolves `(module, method)` pairs against this
8
+ * registry and dispatches. Anything not registered resolves to `undefined`,
9
+ * which the router treats as an unknown-method rejection.
10
+ */
11
+
12
+ /** Register one or more native services, keyed by their `name`. */
13
+ declare function registerNative(toRegister: NativeService[]): void;
14
+ /**
15
+ * Resolve a `(module, method)` pair to its implementation, or `undefined`
16
+ * when the module is unknown or the method is not exposed by it.
17
+ */
18
+ declare function resolve(mod: string, method: string): ((...args: any[]) => Promise<any>) | undefined;
19
+ /** Clear all registered services. Test seam. */
20
+ declare function clearRegistry(): void;
21
+
22
+ export { clearRegistry, registerNative, resolve };
@@ -0,0 +1,21 @@
1
+ const services = /* @__PURE__ */ new Map();
2
+ function registerNative(toRegister) {
3
+ for (const service of toRegister) {
4
+ services.set(service.name, service);
5
+ }
6
+ }
7
+ function resolve(mod, method) {
8
+ const methods = services.get(mod)?.methods;
9
+ if (methods === void 0) {
10
+ return void 0;
11
+ }
12
+ return Object.prototype.hasOwnProperty.call(methods, method) ? methods[method] : void 0;
13
+ }
14
+ function clearRegistry() {
15
+ services.clear();
16
+ }
17
+ export {
18
+ clearRegistry,
19
+ registerNative,
20
+ resolve
21
+ };
@@ -0,0 +1,16 @@
1
+ import { NativeService } from '../types.js';
2
+
3
+ /**
4
+ * Built-in `clipboard` native service.
5
+ *
6
+ * Thin async wrappers over Electron's synchronous `clipboard` module so the
7
+ * surface stays uniform across the native bridge (every method is a Promise).
8
+ * The Electron module is resolved lazily so the package loads without Electron
9
+ * and unit tests can mock it.
10
+ */
11
+ declare const clipboard: NativeService<{
12
+ readText: () => Promise<string>;
13
+ writeText: (text: string) => Promise<void>;
14
+ }>;
15
+
16
+ export { clipboard };
@@ -0,0 +1,15 @@
1
+ import { defineNative } from "../define-native";
2
+ import { getElectron } from "./get-electron";
3
+ const clipboard = defineNative("clipboard", {
4
+ readText: async () => {
5
+ const { clipboard: c } = await getElectron();
6
+ return c.readText();
7
+ },
8
+ writeText: async (text) => {
9
+ const { clipboard: c } = await getElectron();
10
+ c.writeText(text);
11
+ }
12
+ });
13
+ export {
14
+ clipboard
15
+ };
@@ -0,0 +1,19 @@
1
+ import { NativeService } from '../types.js';
2
+ import { MessageBoxOptions } from 'electron';
3
+
4
+ /**
5
+ * Built-in `dialog` native service.
6
+ *
7
+ * Thin async wrappers over Electron's `dialog` module. File pickers collapse
8
+ * Electron's `{ canceled, filePaths }` / `{ canceled, filePath }` shapes down
9
+ * to a single selected path or `null` when the user cancels (or selects
10
+ * nothing). The Electron module is resolved lazily so the package loads
11
+ * without Electron and unit tests can mock it.
12
+ */
13
+ declare const dialog: NativeService<{
14
+ openFile: () => Promise<string | null>;
15
+ saveFile: () => Promise<string | null>;
16
+ message: (opts: MessageBoxOptions) => Promise<void>;
17
+ }>;
18
+
19
+ export { dialog };
@@ -0,0 +1,23 @@
1
+ import { defineNative } from "../define-native";
2
+ import { getElectron } from "./get-electron";
3
+ const dialog = defineNative("dialog", {
4
+ openFile: async () => {
5
+ const { dialog: d } = await getElectron();
6
+ const result = await d.showOpenDialog({ properties: ["openFile"] });
7
+ if (result.canceled) return null;
8
+ return result.filePaths[0] ?? null;
9
+ },
10
+ saveFile: async () => {
11
+ const { dialog: d } = await getElectron();
12
+ const result = await d.showSaveDialog({});
13
+ if (result.canceled) return null;
14
+ return result.filePath ?? null;
15
+ },
16
+ message: async (opts) => {
17
+ const { dialog: d } = await getElectron();
18
+ await d.showMessageBox(opts);
19
+ }
20
+ });
21
+ export {
22
+ dialog
23
+ };
@@ -0,0 +1,20 @@
1
+ import { NativeService } from '../types.js';
2
+
3
+ /**
4
+ * Built-in `fs` native service.
5
+ *
6
+ * A thin, async wrapper over `node:fs/promises` exposed through the native
7
+ * bridge. Text files are read/written as UTF-8 strings; `mkdir` is recursive
8
+ * and `remove` maps to `rm` with `recursive` + `force` so it never throws on
9
+ * a missing path.
10
+ */
11
+ declare const fs: NativeService<{
12
+ readFile: (path: string) => Promise<string>;
13
+ writeFile: (path: string, data: string) => Promise<void>;
14
+ exists: (path: string) => Promise<boolean>;
15
+ readDir: (path: string) => Promise<string[]>;
16
+ mkdir: (path: string) => Promise<void>;
17
+ remove: (path: string) => Promise<void>;
18
+ }>;
19
+
20
+ export { fs };
@@ -0,0 +1,26 @@
1
+ import { readFile, writeFile, readdir, mkdir as mkdirFs, rm, access } from "node:fs/promises";
2
+ import { defineNative } from "../define-native";
3
+ const fs = defineNative("fs", {
4
+ readFile: (path) => readFile(path, "utf8"),
5
+ writeFile: async (path, data) => {
6
+ await writeFile(path, data, "utf8");
7
+ },
8
+ exists: async (path) => {
9
+ try {
10
+ await access(path);
11
+ return true;
12
+ } catch {
13
+ return false;
14
+ }
15
+ },
16
+ readDir: (path) => readdir(path),
17
+ mkdir: async (path) => {
18
+ await mkdirFs(path, { recursive: true });
19
+ },
20
+ remove: async (path) => {
21
+ await rm(path, { recursive: true, force: true });
22
+ }
23
+ });
24
+ export {
25
+ fs
26
+ };
@@ -0,0 +1,14 @@
1
+ import * as Electron from 'electron';
2
+
3
+ /**
4
+ * Lazy, mockable accessor for the `electron` runtime module.
5
+ *
6
+ * The built-in native services run in the main process, where importing
7
+ * `electron` is permitted. Going through this single indirection keeps the
8
+ * import out of module-evaluation time (so the package can be loaded in
9
+ * environments without Electron) and gives unit tests one seam to mock via
10
+ * `vi.mock('electron', …)`.
11
+ */
12
+ declare function getElectron(): Promise<typeof Electron>;
13
+
14
+ export { getElectron };
@@ -0,0 +1,6 @@
1
+ async function getElectron() {
2
+ return import("electron");
3
+ }
4
+ export {
5
+ getElectron
6
+ };
@@ -0,0 +1,7 @@
1
+ export { fs } from './fs.js';
2
+ export { dialog } from './dialog.js';
3
+ export { shell } from './shell.js';
4
+ export { clipboard } from './clipboard.js';
5
+ export { NotifyOptions, notifications } from './notifications.js';
6
+ import '../types.js';
7
+ import 'electron';
@@ -0,0 +1,12 @@
1
+ import { fs } from "./fs";
2
+ import { dialog } from "./dialog";
3
+ import { shell } from "./shell";
4
+ import { clipboard } from "./clipboard";
5
+ import { notifications } from "./notifications";
6
+ export {
7
+ clipboard,
8
+ dialog,
9
+ fs,
10
+ notifications,
11
+ shell
12
+ };
@@ -0,0 +1,21 @@
1
+ import { NativeService } from '../types.js';
2
+
3
+ /** Options for {@link notifications}'s `notify` method. */
4
+ interface NotifyOptions {
5
+ /** Title line of the notification. */
6
+ title: string;
7
+ /** Body text of the notification. */
8
+ body: string;
9
+ }
10
+ /**
11
+ * Built-in `notifications` native service.
12
+ *
13
+ * Constructs and shows a native OS notification via Electron's `Notification`
14
+ * class. The Electron module is resolved lazily so the package loads without
15
+ * Electron and unit tests can mock it.
16
+ */
17
+ declare const notifications: NativeService<{
18
+ notify: ({ title, body }: NotifyOptions) => Promise<void>;
19
+ }>;
20
+
21
+ export { type NotifyOptions, notifications };
@@ -0,0 +1,11 @@
1
+ import { defineNative } from "../define-native";
2
+ import { getElectron } from "./get-electron";
3
+ const notifications = defineNative("notifications", {
4
+ notify: async ({ title, body }) => {
5
+ const { Notification } = await getElectron();
6
+ new Notification({ title, body }).show();
7
+ }
8
+ });
9
+ export {
10
+ notifications
11
+ };
@@ -0,0 +1,16 @@
1
+ import { NativeService } from '../types.js';
2
+
3
+ /**
4
+ * Built-in `shell` native service.
5
+ *
6
+ * Thin async wrappers over Electron's `shell` module for opening URLs, paths,
7
+ * and revealing items in the OS file manager. The Electron module is resolved
8
+ * lazily so the package loads without Electron and unit tests can mock it.
9
+ */
10
+ declare const shell: NativeService<{
11
+ openExternal: (url: string) => Promise<void>;
12
+ openPath: (path: string) => Promise<void>;
13
+ showItemInFolder: (path: string) => Promise<void>;
14
+ }>;
15
+
16
+ export { shell };