@electrojs/renderer 1.0.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/LICENSE +21 -0
- package/README.md +431 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +1 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anton Ryuben
|
|
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,431 @@
|
|
|
1
|
+
# @electrojs/renderer
|
|
2
|
+
|
|
3
|
+
Typed renderer-side runtime for Electro applications.
|
|
4
|
+
|
|
5
|
+
`@electrojs/renderer` is the only public entrypoint that renderer code uses to talk to the Electro runtime. It exposes:
|
|
6
|
+
|
|
7
|
+
- `ElectroRenderer.initialize(...)` — bootstraps the renderer package
|
|
8
|
+
- `bridge` — typed query/command API
|
|
9
|
+
- `signals` — typed runtime-to-renderer subscriptions
|
|
10
|
+
|
|
11
|
+
This package does **not** know anything about preload implementation details beyond the public preload contract exposed on `window.__ELECTRO_RENDERER__`. The preload side is provided by the runtime layer.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @electrojs/renderer
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Core idea
|
|
24
|
+
|
|
25
|
+
The renderer cannot talk to the runtime directly. All communication goes through:
|
|
26
|
+
|
|
27
|
+
- `bridge.<module>.<method>(...)` for queries and commands
|
|
28
|
+
- `signals.subscribe(...)` / `signals.once(...)` for runtime-published signals
|
|
29
|
+
|
|
30
|
+
Bridge and signal types are provided through declaration merging.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { ElectroRenderer } from "@electrojs/renderer";
|
|
38
|
+
import { createRoot } from "react-dom/client";
|
|
39
|
+
import { App } from "./app";
|
|
40
|
+
|
|
41
|
+
await ElectroRenderer.initialize(async () => {
|
|
42
|
+
createRoot(document.getElementById("root")!).render(<App />);
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
After initialization finishes, `bridge` and `signals` are ready to use.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Public API
|
|
51
|
+
|
|
52
|
+
### `ElectroRenderer.initialize(callback?)`
|
|
53
|
+
|
|
54
|
+
Initializes the renderer package exactly once.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { ElectroRenderer } from "@electrojs/renderer";
|
|
58
|
+
|
|
59
|
+
await ElectroRenderer.initialize(async () => {
|
|
60
|
+
// renderer bootstrap logic
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### Behavior
|
|
65
|
+
|
|
66
|
+
- resolves the preload API from `window.__ELECTRO_RENDERER__`
|
|
67
|
+
- creates the bridge proxy
|
|
68
|
+
- creates the signals client
|
|
69
|
+
- executes the optional callback
|
|
70
|
+
- marks the renderer as initialized only after callback success
|
|
71
|
+
|
|
72
|
+
#### Important rules
|
|
73
|
+
|
|
74
|
+
- `initialize()` can only be called once
|
|
75
|
+
- if initialization fails, internal state is rolled back
|
|
76
|
+
- `bridge` and `signals` must not be used before initialization completes
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### `bridge`
|
|
81
|
+
|
|
82
|
+
`bridge` is a lazily resolved typed proxy.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { bridge } from "@electrojs/renderer";
|
|
86
|
+
|
|
87
|
+
const user = await bridge.auth.getMe();
|
|
88
|
+
await bridge.auth.login("john@example.com", "secret");
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
At runtime, bridge calls are translated into channel invocations:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
bridge.auth.login("a", "b");
|
|
95
|
+
// -> preload.invoke("auth:login", ["a", "b"])
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Method calling rules
|
|
99
|
+
|
|
100
|
+
A bridge method can have one of three shapes depending on generated types:
|
|
101
|
+
|
|
102
|
+
- no input:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
await bridge.auth.getMe();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- tuple input:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
await bridge.auth.login(email, password);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- single object input:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
await bridge.project.create({ name: "Electro" });
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
### `signals`
|
|
123
|
+
|
|
124
|
+
`signals` provides typed subscription helpers.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { signals } from "@electrojs/renderer";
|
|
128
|
+
|
|
129
|
+
const subscription = signals.subscribe("auth:user-logged-in", (payload) => {
|
|
130
|
+
console.log(payload);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
subscription.unsubscribe();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
One-time subscription:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
signals.once("auth:user-logged-in", (payload) => {
|
|
140
|
+
console.log("received once", payload);
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Signal handlers support two shapes:
|
|
145
|
+
|
|
146
|
+
- payload signal:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
signals.subscribe("auth:user-logged-in", (payload) => {
|
|
150
|
+
console.log(payload.userId);
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- no-payload signal:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
signals.subscribe("auth:user-logged-out", () => {
|
|
158
|
+
console.log("logged out");
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Typing bridge and signals
|
|
165
|
+
|
|
166
|
+
This package exposes three empty interfaces intended for declaration merging:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
export interface BridgeQueries {}
|
|
170
|
+
export interface BridgeCommands {}
|
|
171
|
+
export interface BridgeSignals {}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Generated code augments them.
|
|
175
|
+
|
|
176
|
+
Example:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
declare module "@electrojs/renderer" {
|
|
180
|
+
interface BridgeQueries {
|
|
181
|
+
"auth:getMe": {
|
|
182
|
+
input: undefined;
|
|
183
|
+
output: { id: string; email: string } | null;
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
interface BridgeCommands {
|
|
188
|
+
"auth:login": {
|
|
189
|
+
input: [email: string, password: string];
|
|
190
|
+
output: void;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
interface BridgeSignals {
|
|
195
|
+
"auth:user-logged-in": { userId: string };
|
|
196
|
+
"auth:user-logged-out": undefined;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
After augmentation:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
const me = await bridge.auth.getMe();
|
|
205
|
+
await bridge.auth.login("john@example.com", "secret");
|
|
206
|
+
|
|
207
|
+
signals.subscribe("auth:user-logged-in", (payload) => {
|
|
208
|
+
payload.userId;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
signals.subscribe("auth:user-logged-out", () => {});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Runtime preload contract
|
|
217
|
+
|
|
218
|
+
The renderer expects preload to expose this API on `window.__ELECTRO_RENDERER__`:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
interface RendererPreloadApi {
|
|
222
|
+
invoke(channel: string, payload: readonly unknown[]): Promise<unknown>;
|
|
223
|
+
subscribe(signalKey: string, listener: (payload: unknown) => void): () => void;
|
|
224
|
+
once(signalKey: string, listener: (payload: unknown) => void): () => void;
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
This package does not create that object. It only consumes it.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Usage with React
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
import { useEffect, useState } from "react";
|
|
236
|
+
import { bridge, signals } from "@electrojs/renderer";
|
|
237
|
+
|
|
238
|
+
export function App(): JSX.Element {
|
|
239
|
+
const [email, setEmail] = useState<string | null>(null);
|
|
240
|
+
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
let mounted = true;
|
|
243
|
+
|
|
244
|
+
bridge.auth.getMe().then((user) => {
|
|
245
|
+
if (!mounted) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
setEmail(user?.email ?? null);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const subscription = signals.subscribe("auth:user-logged-in", (payload) => {
|
|
253
|
+
setEmail(payload.email);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return () => {
|
|
257
|
+
mounted = false;
|
|
258
|
+
subscription.unsubscribe();
|
|
259
|
+
};
|
|
260
|
+
}, []);
|
|
261
|
+
|
|
262
|
+
return <div>{email ?? "Anonymous"}</div>;
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Errors
|
|
269
|
+
|
|
270
|
+
All package-specific errors inherit from `RendererError`.
|
|
271
|
+
|
|
272
|
+
### `RendererInitializationError`
|
|
273
|
+
|
|
274
|
+
Thrown when initialization fails or is used incorrectly.
|
|
275
|
+
|
|
276
|
+
Examples:
|
|
277
|
+
|
|
278
|
+
- `ELECTRO_RENDERER_ALREADY_INITIALIZED`
|
|
279
|
+
- `ELECTRO_RENDERER_PRELOAD_API_MISSING`
|
|
280
|
+
|
|
281
|
+
### `RendererUsageError`
|
|
282
|
+
|
|
283
|
+
Thrown when renderer APIs are used incorrectly.
|
|
284
|
+
|
|
285
|
+
Examples:
|
|
286
|
+
|
|
287
|
+
- `ELECTRO_RENDERER_NOT_INITIALIZED`
|
|
288
|
+
- `ELECTRO_RENDERER_INVALID_BRIDGE_NAMESPACE_KEY`
|
|
289
|
+
- `ELECTRO_RENDERER_INVALID_BRIDGE_METHOD_KEY`
|
|
290
|
+
- `ELECTRO_RENDERER_INVALID_SIGNAL_KEY`
|
|
291
|
+
- `ELECTRO_RENDERER_INVALID_SIGNAL_HANDLER`
|
|
292
|
+
|
|
293
|
+
### `RendererTransportError`
|
|
294
|
+
|
|
295
|
+
Thrown when preload transport operations fail.
|
|
296
|
+
|
|
297
|
+
Examples:
|
|
298
|
+
|
|
299
|
+
- `ELECTRO_RENDERER_TRANSPORT_INVOKE_FAILED`
|
|
300
|
+
- `ELECTRO_RENDERER_SIGNAL_SUBSCRIBE_FAILED`
|
|
301
|
+
|
|
302
|
+
Example:
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
import { ElectroRenderer, RendererInitializationError } from "@electrojs/renderer";
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
await ElectroRenderer.initialize();
|
|
309
|
+
} catch (error) {
|
|
310
|
+
if (error instanceof RendererInitializationError) {
|
|
311
|
+
console.error(error.code, error.context);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Initialization lifecycle
|
|
319
|
+
|
|
320
|
+
Initialization is transactional.
|
|
321
|
+
|
|
322
|
+
### Successful flow
|
|
323
|
+
|
|
324
|
+
1. validate initialization state
|
|
325
|
+
2. resolve preload API from `window.__ELECTRO_RENDERER__`
|
|
326
|
+
3. create bridge transport
|
|
327
|
+
4. create bridge proxy
|
|
328
|
+
5. create signals client
|
|
329
|
+
6. store bridge and signals
|
|
330
|
+
7. run user callback
|
|
331
|
+
8. mark initialized
|
|
332
|
+
|
|
333
|
+
### Failed flow
|
|
334
|
+
|
|
335
|
+
If any step throws:
|
|
336
|
+
|
|
337
|
+
- bridge storage is cleared
|
|
338
|
+
- signals storage is cleared
|
|
339
|
+
- initialization state is reset
|
|
340
|
+
- original error is rethrown
|
|
341
|
+
|
|
342
|
+
This guarantees the package never stays in a half-initialized state.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Design notes
|
|
347
|
+
|
|
348
|
+
### Why `initialize()` is async
|
|
349
|
+
|
|
350
|
+
Initialization may include async renderer bootstrap logic:
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
await ElectroRenderer.initialize(async () => {
|
|
354
|
+
await loadI18n();
|
|
355
|
+
await warmupCache();
|
|
356
|
+
renderApp();
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
The renderer becomes initialized only after this callback succeeds.
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
### Why `bridge` is a proxy
|
|
365
|
+
|
|
366
|
+
The runtime bridge surface is generated and typed externally. A proxy lets the package:
|
|
367
|
+
|
|
368
|
+
- expose ergonomic `bridge.auth.login(...)` syntax
|
|
369
|
+
- avoid static hardcoded namespaces
|
|
370
|
+
- remain compatible with generated declaration merging
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
### Why `"then"` returns `undefined`
|
|
375
|
+
|
|
376
|
+
Both root and namespace proxies explicitly return `undefined` for `"then"` to avoid accidental promise-like behavior during inspection or framework interop.
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Exports
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
import { bridge, signals, ElectroRenderer, RendererError, RendererInitializationError, RendererTransportError, RendererUsageError } from "@electrojs/renderer";
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Type exports:
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
import type {
|
|
390
|
+
BridgeContractEntry,
|
|
391
|
+
BridgeSignalHandler,
|
|
392
|
+
ElectroRendererApi,
|
|
393
|
+
InitializeCallback,
|
|
394
|
+
RendererPreloadApi,
|
|
395
|
+
RendererSignalListener,
|
|
396
|
+
RendererSignalSubscription,
|
|
397
|
+
} from "@electrojs/renderer";
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## Best practices
|
|
403
|
+
|
|
404
|
+
Always initialize the renderer before using `bridge` or `signals`.
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
await ElectroRenderer.initialize(async () => {
|
|
408
|
+
// safe to render app here
|
|
409
|
+
});
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Always unsubscribe from long-lived signal subscriptions.
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
const subscription = signals.subscribe("project:updated", onProjectUpdated);
|
|
416
|
+
|
|
417
|
+
// later
|
|
418
|
+
subscription.unsubscribe();
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Prefer generated types over handwritten declarations.
|
|
422
|
+
|
|
423
|
+
Keep preload implementation thin and transport-focused. Business logic belongs in the runtime, not in preload.
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## Browser requirement
|
|
428
|
+
|
|
429
|
+
`@electrojs/renderer` expects a browser-like environment with a `window` global.
|
|
430
|
+
|
|
431
|
+
Running tests in plain Node.js without browser mode or DOM emulation will fail for code paths that access `window`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
//#region src/types/transport.d.ts
|
|
2
|
+
type RendererSignalListener = (payload: unknown) => void;
|
|
3
|
+
interface RendererPreloadApi {
|
|
4
|
+
invoke(channel: string, payload: readonly unknown[]): Promise<unknown>;
|
|
5
|
+
subscribe(signalKey: string, listener: RendererSignalListener): () => void;
|
|
6
|
+
once(signalKey: string, listener: RendererSignalListener): () => void;
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/types/window.d.ts
|
|
10
|
+
declare global {
|
|
11
|
+
interface Window {
|
|
12
|
+
readonly __ELECTRO_RENDERER__?: RendererPreloadApi;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/types/public-api.d.ts
|
|
17
|
+
type BridgeNoInput = undefined;
|
|
18
|
+
type BridgeNoPayload = undefined;
|
|
19
|
+
interface BridgeContractEntry<TInput = BridgeNoInput, TOutput = unknown> {
|
|
20
|
+
readonly input: TInput;
|
|
21
|
+
readonly output: TOutput;
|
|
22
|
+
}
|
|
23
|
+
type PromiseBridgeResult<TValue> = Promise<TValue>;
|
|
24
|
+
type BridgeTupleInput = readonly unknown[];
|
|
25
|
+
type IsBridgeNoInput<TValue> = [TValue] extends [BridgeNoInput] ? true : false;
|
|
26
|
+
type IsBridgeNoPayload<TValue> = [TValue] extends [BridgeNoPayload] ? true : false;
|
|
27
|
+
type BridgeMethodFromInput<TInput, TOutput> = IsBridgeNoInput<TInput> extends true ? () => PromiseBridgeResult<TOutput> : [TInput] extends [BridgeTupleInput] ? (...args: TInput) => PromiseBridgeResult<TOutput> : (input: TInput) => PromiseBridgeResult<TOutput>;
|
|
28
|
+
type BridgeMethod<TEntry extends BridgeContractEntry<any, any>> = BridgeMethodFromInput<TEntry["input"], TEntry["output"]>;
|
|
29
|
+
type Simplify<TValue> = { [TKey in keyof TValue]: TValue[TKey] } & {};
|
|
30
|
+
type ContractKey<TContracts extends object> = keyof TContracts & string;
|
|
31
|
+
type ContractNamespace<TKey extends string> = TKey extends `${infer TNamespace}:${infer _TMethod}` ? TNamespace : never;
|
|
32
|
+
type ContractMethodName<TKey extends string> = TKey extends `${string}:${infer TMethod}` ? TMethod : never;
|
|
33
|
+
type NormalizeBridgeEntry<TEntry> = TEntry extends BridgeContractEntry<infer TInput, infer TOutput> ? BridgeContractEntry<TInput, TOutput> : never;
|
|
34
|
+
type ContractMethodShape<TContracts extends object, TNamespace extends string> = Simplify<{ [TKey in Extract<ContractKey<TContracts>, `${TNamespace}:${string}`> as ContractMethodName<TKey>]: BridgeMethod<NormalizeBridgeEntry<TContracts[TKey]>> }>;
|
|
35
|
+
type BuildNamespaceMap<TContracts extends object> = Simplify<{ [TNamespace in ContractNamespace<ContractKey<TContracts>>]: ContractMethodShape<TContracts, TNamespace> }>;
|
|
36
|
+
type BuildBridgeApi<TQueries extends object, TCommands extends object> = Simplify<BuildNamespaceMap<TQueries> & BuildNamespaceMap<TCommands>>;
|
|
37
|
+
type BridgeSignalHandlerFromPayload<TPayload> = IsBridgeNoPayload<TPayload> extends true ? () => void : (payload: TPayload) => void;
|
|
38
|
+
type BridgeSignalHandler<TPayload> = BridgeSignalHandlerFromPayload<TPayload>;
|
|
39
|
+
interface RendererSignalSubscription {
|
|
40
|
+
unsubscribe(): void;
|
|
41
|
+
}
|
|
42
|
+
interface RendererSignalsApi<TSignals extends object> {
|
|
43
|
+
subscribe<TKey extends keyof TSignals & string>(signalKey: TKey, handler: BridgeSignalHandler<TSignals[TKey]>): RendererSignalSubscription;
|
|
44
|
+
once<TKey extends keyof TSignals & string>(signalKey: TKey, handler: BridgeSignalHandler<TSignals[TKey]>): RendererSignalSubscription;
|
|
45
|
+
}
|
|
46
|
+
type InitializeCallback = () => void | Promise<void>;
|
|
47
|
+
interface ElectroRendererApi {
|
|
48
|
+
initialize(callback?: InitializeCallback): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/core/electro-renderer.d.ts
|
|
52
|
+
declare class ElectroRendererManager implements ElectroRendererApi {
|
|
53
|
+
private readonly state;
|
|
54
|
+
initialize(callback?: InitializeCallback): Promise<void>;
|
|
55
|
+
isInitialized(): boolean;
|
|
56
|
+
}
|
|
57
|
+
declare const ElectroRenderer: ElectroRendererManager;
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/errors/renderer.error.d.ts
|
|
60
|
+
declare abstract class RendererError extends Error {
|
|
61
|
+
readonly code: string;
|
|
62
|
+
readonly context?: Readonly<Record<string, unknown>>;
|
|
63
|
+
protected constructor(message: string, code: string, context?: Readonly<Record<string, unknown>>);
|
|
64
|
+
}
|
|
65
|
+
declare class RendererInitializationError extends RendererError {
|
|
66
|
+
static alreadyInitialized(): RendererInitializationError;
|
|
67
|
+
static preloadApiMissing(windowKey: string): RendererInitializationError;
|
|
68
|
+
}
|
|
69
|
+
declare class RendererUsageError extends RendererError {
|
|
70
|
+
static notInitialized(operation: string): RendererUsageError;
|
|
71
|
+
static invalidBridgeNamespaceKey(received: unknown): RendererUsageError;
|
|
72
|
+
static invalidBridgeMethodKey(namespace: string, received: unknown): RendererUsageError;
|
|
73
|
+
static invalidSignalKey(signalKey: unknown): RendererUsageError;
|
|
74
|
+
static invalidSignalHandler(signalKey: unknown, received: unknown): RendererUsageError;
|
|
75
|
+
}
|
|
76
|
+
declare class RendererTransportError extends RendererError {
|
|
77
|
+
static invokeFailed(channel: string, cause: unknown): RendererTransportError;
|
|
78
|
+
static subscribeFailed(signalKey: string, cause: unknown): RendererTransportError;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/index.d.ts
|
|
82
|
+
interface BridgeQueries {}
|
|
83
|
+
interface BridgeCommands {}
|
|
84
|
+
interface BridgeSignals {}
|
|
85
|
+
type BridgeApi = BuildBridgeApi<BridgeQueries, {}>;
|
|
86
|
+
type SignalsApi = RendererSignalsApi<BridgeSignals>;
|
|
87
|
+
declare const bridge: BridgeApi;
|
|
88
|
+
declare const signals: SignalsApi;
|
|
89
|
+
//#endregion
|
|
90
|
+
export { BridgeApi, BridgeCommands, type BridgeContractEntry, BridgeQueries, type BridgeSignalHandler, BridgeSignals, ElectroRenderer, type ElectroRendererApi, type InitializeCallback, RendererError, RendererInitializationError, type RendererPreloadApi, type RendererSignalListener, type RendererSignalSubscription, RendererTransportError, RendererUsageError, SignalsApi, bridge, signals };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=class extends Error{constructor(e,t,n){super(e),this.name=new.target.name,this.code=t,this.context=n,Object.setPrototypeOf(this,new.target.prototype)}},t=class t extends e{static alreadyInitialized(){return new t(`ElectroRenderer.initialize() can only be called once.`,`ELECTRO_RENDERER_ALREADY_INITIALIZED`)}static preloadApiMissing(e){return new t(`Electro renderer preload API is not available on window.${e}.`,`ELECTRO_RENDERER_PRELOAD_API_MISSING`,{windowKey:e})}},n=class t extends e{static notInitialized(e){return new t(`${e} cannot be used before ElectroRenderer.initialize() has completed.`,`ELECTRO_RENDERER_NOT_INITIALIZED`,{operation:e})}static invalidBridgeNamespaceKey(e){return new t(`Bridge namespace key must be a non-empty string.`,`ELECTRO_RENDERER_INVALID_BRIDGE_NAMESPACE_KEY`,{received:e})}static invalidBridgeMethodKey(e,n){return new t(`Bridge method key for namespace "${e}" must be a non-empty string.`,`ELECTRO_RENDERER_INVALID_BRIDGE_METHOD_KEY`,{namespace:e,received:n})}static invalidSignalKey(e){return new t(`Signal key must be a non-empty string.`,`ELECTRO_RENDERER_INVALID_SIGNAL_KEY`,{received:e})}static invalidSignalHandler(e,n){return new t(`Signal handler for "${String(e)}" must be a function.`,`ELECTRO_RENDERER_INVALID_SIGNAL_HANDLER`,{signalKey:e,receivedType:typeof n})}},r=class t extends e{static invokeFailed(e,n){return new t(`Bridge transport invocation failed for channel "${e}".`,`ELECTRO_RENDERER_TRANSPORT_INVOKE_FAILED`,{channel:e,cause:n})}static subscribeFailed(e,n){return new t(`Signal subscription failed for "${e}".`,`ELECTRO_RENDERER_SIGNAL_SUBSCRIBE_FAILED`,{signalKey:e,cause:n})}};let i;function a(e){i=e}function o(){i=void 0}function s(){if(!i)throw n.notInitialized(`bridge`);return i}function c(e,t){return new Proxy({},{get(r,i){if(typeof i!=`string`||i===`then`)return;let a=i.trim();if(a.length===0)throw n.invalidBridgeMethodKey(t,i);return async(...n)=>e.invoke(`${t}:${a}`,n)},has(e,t){return typeof t==`string`&&t.trim().length>0&&t!==`then`},getOwnPropertyDescriptor(e,t){if(!(typeof t!=`string`||t===`then`||t.trim().length===0))return{configurable:!0,enumerable:!0}}})}function l(e){return new Proxy({},{get(t,r){if(typeof r!=`string`||r===`then`)return;let i=r.trim();if(i.length===0)throw n.invalidBridgeNamespaceKey(r);return c(e,i)},has(e,t){return typeof t==`string`&&t.trim().length>0&&t!==`then`},getOwnPropertyDescriptor(e,t){if(!(typeof t!=`string`||t===`then`||t.trim().length===0))return{configurable:!0,enumerable:!0}}})}var u=class{constructor(e){this.preloadApi=e}async invoke(e,t){try{return await this.preloadApi.invoke(e,t)}catch(t){throw r.invokeFailed(e,t)}}};let d;function f(e){d=e}function p(){d=void 0}function m(){if(!d)throw n.notInitialized(`signals`);return d}var h=class{constructor(e){this.dispose=e,this.unsubscribed=!1}unsubscribe(){this.unsubscribed||(this.unsubscribed=!0,this.dispose())}},g=class{constructor(e){this.preloadApi=e}subscribe(e,t){this.ensureValidHandler(e,t);try{return new h(this.preloadApi.subscribe(e,this.createInternalHandler(t)))}catch(t){throw r.subscribeFailed(e,t)}}once(e,t){this.ensureValidHandler(e,t);try{return new h(this.preloadApi.once(e,this.createInternalHandler(t)))}catch(t){throw r.subscribeFailed(e,t)}}createInternalHandler(e){return t=>{if(t===void 0){e();return}e(t)}}ensureValidHandler(e,t){if(typeof e!=`string`||e.trim().length===0)throw n.invalidSignalKey(e);if(typeof t!=`function`)throw n.invalidSignalHandler(e,t)}},_=class{constructor(){this.initialized=!1,this.initializing=!1}isInitialized(){return this.initialized}isInitializing(){return this.initializing}ensureCanInitialize(){if(this.initialized||this.initializing)throw t.alreadyInitialized()}markInitializing(){this.initializing=!0}markInitialized(){this.initializing=!1,this.initialized=!0}reset(){this.initializing=!1,this.initialized=!1}};const v=`__ELECTRO_RENDERER__`;var y=class{static getPreloadApi(e=window){let n=e[v];if(!n)throw t.preloadApiMissing(v);return n}};const b=new class{constructor(){this.state=new _}async initialize(e){this.state.ensureCanInitialize(),this.state.markInitializing();try{let t=y.getPreloadApi(),n=l(new u(t)),r=new g(t);a(n),f(r),e&&await e(),this.state.markInitialized()}catch(e){throw o(),p(),this.state.reset(),e}}isInitialized(){return this.state.isInitialized()}},x=new Proxy({},{get(e,t){return Reflect.get(s(),t)}}),S={subscribe(e,t){return m().subscribe(e,t)},once(e,t){return m().once(e,t)}};export{b as ElectroRenderer,e as RendererError,t as RendererInitializationError,r as RendererTransportError,n as RendererUsageError,x as bridge,S as signals};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@electrojs/renderer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Library for working with the renderer process in @electro",
|
|
5
|
+
"homepage": "https://github.com/MyraxByte/electrojs#readme",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/MyraxByte/electrojs.git",
|
|
10
|
+
"directory": "packages/renderer"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@vitest/browser-playwright": "^4.1.1",
|
|
29
|
+
"@vitest/coverage-v8": "^4.1.1",
|
|
30
|
+
"@vitest/ui": "^4.1.1",
|
|
31
|
+
"jsdom": "^29.0.1",
|
|
32
|
+
"publint": "^0.3.18",
|
|
33
|
+
"tsdown": "^0.21.4",
|
|
34
|
+
"vitest": "^4.1.1",
|
|
35
|
+
"@electrojs/config": "1.0.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@electrojs/config": ">=1.0.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsdown",
|
|
42
|
+
"test": "vitest",
|
|
43
|
+
"test:browser": "vitest --browser"
|
|
44
|
+
}
|
|
45
|
+
}
|