@preact-mvc/atoll 0.0.0-stage → 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.
package/package.json CHANGED
@@ -1,6 +1,34 @@
1
1
  {
2
2
  "name": "@preact-mvc/atoll",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Framework-free worker-rendered islands: op protocol, proxy DOM, worker runtime, main-thread driver",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/index.ts",
8
+ "./worker": "./src/worker.ts",
9
+ "./package.json": "./package.json",
10
+ "./*": "./src/*"
11
+ },
12
+ "files": [
13
+ "src",
14
+ "!src/**/*.test.ts",
15
+ "!src/**/*.test.tsx"
16
+ ],
17
+ "scripts": {
18
+ "typecheck": "tsc --noEmit"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public",
22
+ "registry": "https://registry.npmjs.org"
23
+ },
24
+ "dependencies": {
25
+ "@atolljs/core": "0.1.11",
26
+ "@atolljs/islands": "0.1.11"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/jwhenry3/preact-mvc",
31
+ "directory": "packages/atoll"
32
+ },
33
+ "license": "MIT"
34
+ }
package/src/driver.ts ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * `@preact-mvc/atoll` shell surface — a thin facade over `@atolljs/islands`
3
+ * (docs/atoll/islands.md). The op protocol, proxy DOM, instance lifecycle,
4
+ * and op-replay driver are the real package now; this file only normalizes
5
+ * the worker option (an already-created WorkerLike counts as an entry, the
6
+ * real package wants a bundler-detectable factory or URL) and defaults the
7
+ * transport to message-only so mounts work without COOP/COEP headers —
8
+ * GitHub Pages can't serve them.
9
+ *
10
+ * Push mode still works: pass `mode: 'push'` on a `worker:` mount (builds a
11
+ * doorbell client) or `doorbell: true` on `connectIslandWorker`, and serve
12
+ * Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy:
13
+ * require-corp.
14
+ */
15
+ import {
16
+ callbackProp,
17
+ connectIslandWorker as atollConnectIslandWorker,
18
+ islandAppNameOf,
19
+ mountIsland as atollMountIsland,
20
+ } from '@atolljs/islands';
21
+ import type {
22
+ IslandClient,
23
+ IslandHandle,
24
+ IslandWorkerOptions,
25
+ Mode,
26
+ Op,
27
+ } from '@atolljs/islands';
28
+
29
+ export { callbackProp };
30
+ export type { IslandClient, IslandHandle, Mode, Op };
31
+
32
+ /**
33
+ * What a `worker:` option accepts: a bundler-detectable factory
34
+ * `() => new Worker(new URL(...))`, a URL, or an already-spawned worker
35
+ * (test doubles like `InProcessWorker` count — the real `Worker` type would
36
+ * reject them, so the surface is structural).
37
+ */
38
+ export type WorkerLike = Pick<
39
+ Worker,
40
+ 'postMessage' | 'addEventListener' | 'removeEventListener' | 'terminate'
41
+ >;
42
+
43
+ const asWorkerEntry = (
44
+ w: WorkerLike | (() => WorkerLike) | URL,
45
+ ): (() => Worker) | URL =>
46
+ w instanceof URL ? w : typeof w === 'function' ? (w as () => Worker) : () => w as Worker;
47
+
48
+ export interface ConnectOptions extends IslandWorkerOptions {
49
+ /** Bundler-detectable factory / URL, or an already-spawned worker. */
50
+ worker: WorkerLike | (() => WorkerLike) | URL;
51
+ /**
52
+ * Shared-memory doorbell for push-mode flush — requires cross-origin
53
+ * isolation. Default false (message-only): mounts fall back to the 50ms
54
+ * poll, which needs no special headers.
55
+ */
56
+ doorbell?: boolean;
57
+ /**
58
+ * Keep the worker thread resident (but idle) when its last island
59
+ * destroys: the instance still unmounts — dispose hooks run, ops stop —
60
+ * but the thread's module graph and worker-side caches survive, so the
61
+ * next mount on this client is a warm start. Explicit `client.terminate()`
62
+ * still kills it.
63
+ */
64
+ keepAlive?: boolean;
65
+ }
66
+
67
+ /** One island client = one dedicated worker. Islands mounted with `client:`
68
+ * share it; each `worker:` mount builds its own. */
69
+ export function connectIslandWorker({
70
+ worker,
71
+ doorbell = false,
72
+ ...options
73
+ }: ConnectOptions): IslandClient {
74
+ return atollConnectIslandWorker({ ...options, doorbell, worker: asWorkerEntry(worker) });
75
+ }
76
+
77
+ export interface MountOptions extends IslandWorkerOptions {
78
+ /** Container the island's ops replay into. */
79
+ el: HTMLElement;
80
+ /**
81
+ * Which app to mount: the `apps` registry key, a stamped app
82
+ * (`islandApp`/`viewIsland` result), or a contract. Resolved through
83
+ * `islandAppNameOf` — bare components fall back to the function name.
84
+ */
85
+ app?: unknown;
86
+ props?: Record<string, unknown>;
87
+ /** Island → shell channel: receives every worker-side `emit(name, payload)`. */
88
+ onEvent?: (name: string, payload: unknown) => void;
89
+ /** `data-atoll-slot` transclusion holes — shell mounts real DOM inside. */
90
+ slots?: Record<string, (el: HTMLElement | null) => void>;
91
+ onActivity?: () => void;
92
+ onOps?: (ops: readonly Op[], elapsedMs: number) => void;
93
+ /**
94
+ * 'push' uses the doorbell SAB (cross-origin isolation required for
95
+ * `worker:` mounts); 'poll' (the default here) needs none.
96
+ */
97
+ mode?: Mode;
98
+ /** Devtools renderer tag ('preact-mvc'); the adapter sets it. */
99
+ framework?: string;
100
+ mountTimeout?: number;
101
+ /** This mount's destroy() leaves the client resident-but-idle. */
102
+ keepAlive?: boolean;
103
+ /** A shared `connectIslandWorker` client, OR an entry this mount owns. */
104
+ client?: IslandClient;
105
+ worker?: WorkerLike | (() => WorkerLike) | URL;
106
+ }
107
+
108
+ export function mountIsland(opts: MountOptions): Promise<IslandHandle> {
109
+ const { worker, client, app, mode = 'poll', ...rest } = opts;
110
+ const name = islandAppNameOf(app);
111
+ if (app !== undefined && name === undefined) {
112
+ return Promise.reject(
113
+ new Error(
114
+ 'mountIsland: could not resolve an app name — pass the registry key ' +
115
+ 'or stamp the app with islandApp(name, app)',
116
+ ),
117
+ );
118
+ }
119
+ const base = { ...rest, app: name, mode };
120
+ return client !== undefined
121
+ ? atollMountIsland({ ...base, client })
122
+ : atollMountIsland({ ...base, worker: asWorkerEntry(worker!) });
123
+ }
package/src/index.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `@preact-mvc/atoll` — the shell-facing facade over the real Atoll
3
+ * packages (`@atolljs/islands` + `@atolljs/core`). Worker islands render
4
+ * inside Web Workers against a proxy DOM; commits stream back as ops the
5
+ * driver replays onto real DOM.
6
+ *
7
+ * Shell side (this entry): `mountIsland`, `connectIslandWorker`,
8
+ * `callbackProp`, `islandApp`. Worker side (import in worker entries):
9
+ * the `./worker` subpath re-exports `@atolljs/islands/worker` —
10
+ * `definePolyWorker`/`defineMonoWorker`, `emit`, instance scope. The
11
+ * preact-mvc adapter and `<Island/>` shell components live in
12
+ * `@preact-mvc/atoll-island`.
13
+ */
14
+ export { islandApp, islandAppNameOf } from '@atolljs/islands';
15
+ export type { IslandAppProps } from '@atolljs/islands';
16
+ export {
17
+ defineIslandContract,
18
+ isIslandContract,
19
+ withContract,
20
+ contractOf,
21
+ contractWorkerOf,
22
+ } from '@atolljs/islands';
23
+ export type {
24
+ IslandContract,
25
+ IslandContractEventHandler,
26
+ IslandContractEvents,
27
+ IslandContractProps,
28
+ IslandWorkerOptions,
29
+ } from '@atolljs/islands';
30
+ export type { EventPayload, Op, WireProps } from '@atolljs/islands';
31
+ export { z } from '@atolljs/core';
32
+ export type { Schema } from '@atolljs/core';
33
+
34
+ export { mountIsland, connectIslandWorker, callbackProp } from './driver';
35
+ export type { ConnectOptions, MountOptions, WorkerLike } from './driver';
36
+ export type { IslandClient, IslandHandle, Mode } from './driver';
package/src/worker.ts ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * `@preact-mvc/atoll/worker` — the worker-side entry, re-exported from
3
+ * `@atolljs/islands/worker`. Import in worker entries only:
4
+ *
5
+ * import { definePolyWorker } from '@preact-mvc/atoll/worker';
6
+ * export const worker = definePolyWorker({ apps: { ... } });
7
+ */
8
+ export {
9
+ definePolyWorker,
10
+ defineMonoWorker,
11
+ islandApp,
12
+ islandAppNameOf,
13
+ emit,
14
+ runInInstance,
15
+ getActiveInstance,
16
+ getLastActiveInstance,
17
+ getLastTouchedInstance,
18
+ setActiveInstance,
19
+ installDomShim,
20
+ ProxyElement,
21
+ } from '@atolljs/islands/worker';
22
+ /**
23
+ * Nested-island surfaces: `mountIsland` is the ONE mount API on both
24
+ * threads — a ProxyElement target (inside a worker-rendered tree) routes
25
+ * to the registered sub-mounter and spawns a real sub-worker; a real
26
+ * element drives the main-thread applier. `connectIslandWorker` is
27
+ * thread-agnostic and spawns from whichever thread calls it. Re-exported
28
+ * through our driver facade so the same worker-option normalization and
29
+ * message-only defaults apply on nested mounts. Platform note: nested
30
+ * `new Worker` needs an engine that supports it — Chrome/Firefox/Node,
31
+ * NOT Safari workers.
32
+ */
33
+ export {
34
+ connectIslandWorker,
35
+ mountIsland,
36
+ callbackProp,
37
+ } from './driver';
38
+ export type { ConnectIslandWorkerConfig, MountIslandOptions } from '@atolljs/islands/worker';
39
+ export {
40
+ defineIslandContract,
41
+ isIslandContract,
42
+ withContract,
43
+ contractOf,
44
+ } from '@atolljs/islands/worker';
45
+ export type {
46
+ ImperativeIslandApp,
47
+ IslandApp,
48
+ IslandContract,
49
+ PolyWorkerRegistry,
50
+ ProxyDocument,
51
+ RenderContext,
52
+ RenderedHandle,
53
+ RenderedIslandApp,
54
+ } from '@atolljs/islands/worker';
55
+ export type { EventPayload, Op, WireProps } from '@atolljs/islands/worker';
56
+ export type { IslandWorkerDefinition } from '@atolljs/islands';
57
+
58
+ import { setActiveInstance } from '@atolljs/islands/worker';
59
+ import type { IslandWorkerDefinition } from '@atolljs/islands';
60
+
61
+ /** What `definePolyWorker`/`defineMonoWorker` return — the worker def type
62
+ * island clients type-check against. */
63
+ export type IslandWorkerDef = IslandWorkerDefinition;
64
+
65
+ /**
66
+ * Enter `key`'s instance scope, returning the previous key for restore —
67
+ * a named pair around `setActiveInstance` (which returns the prev itself),
68
+ * used by the preact-mvc adapter's out-of-task render scoping.
69
+ */
70
+ export const enterInstance = (key: string): string => setActiveInstance(key);
71
+ /** Restore the scope `enterInstance` returned (`''` when there was none). */
72
+ export const exitInstance = (prev: string | null): void => {
73
+ setActiveInstance(prev ?? '');
74
+ };
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.