@inkandswitch/patchwork-bootloader 0.6.3 → 0.7.1

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.
@@ -0,0 +1,242 @@
1
+ import debug from "debug";
2
+ export const lifecycleLog = debug("patchwork:lifecycle");
3
+ function describeErrorEvent(event) {
4
+ const error = event;
5
+ const where = error.filename
6
+ ? ` (${error.filename}:${error.lineno}:${error.colno})`
7
+ : "";
8
+ return `${error.message || String(event)}${where}`;
9
+ }
10
+ // A silent port is not proof of death: the worker may still be evaluating its
11
+ // module graph, or be busy with wasm/sync work. In both cases every queued
12
+ // message is delivered once it catches up, and tearing the port down would lose
13
+ // them. So silence only starts a non-destructive probe: a second connection to
14
+ // the same instance. Only if the probe gets a `hello` while this port stays
15
+ // silent do we know the instance is alive but our port is stranded, and
16
+ // recover.
17
+ const HEARTBEAT_MS = 5_000;
18
+ const HEARTBEAT_TIMEOUT_MS = 25_000;
19
+ // An idle worker hellos within milliseconds of connecting, so before first
20
+ // contact the budget is tighter — probing early rescues stranded boots fast.
21
+ const FIRST_CONTACT_TIMEOUT_MS = 4_000;
22
+ // After a slow boot both connections hello at roughly the same moment and
23
+ // cross-port delivery order isn't guaranteed, so give the suspect this long to
24
+ // also speak before concluding it's stranded.
25
+ const PROBE_GRACE_MS = 500;
26
+ // Below this spacing, skip: if the fresh worker is dead too, its own heartbeat
27
+ // re-triggers recovery later rather than spinning in a tight loop.
28
+ const RECOVERY_MIN_INTERVAL_MS = 15_000;
29
+ /**
30
+ * A SharedWorker a tab keeps alive: spawned on demand, heartbeated, and rebuilt
31
+ * if the browser kills it (which it may, under memory pressure).
32
+ */
33
+ export function sharedWorkerHandle(name,
34
+ /** Read on every spawn, so a site can set the path after this is built. */
35
+ path, { debugging, onMessage, onSpawn, }) {
36
+ let current;
37
+ let disposeDeathDetection;
38
+ let recovering = false;
39
+ let lastRecoveryAt = 0;
40
+ const recreatedListeners = new Set();
41
+ const get = () => {
42
+ if (current)
43
+ return current;
44
+ const worker = new SharedWorker(path(), { name, type: "module" });
45
+ current = worker;
46
+ // Fires when a message can't be structured-deserialized. Silent otherwise:
47
+ // the message is dropped, which looks identical to a worker that never
48
+ // replied.
49
+ worker.port.addEventListener("messageerror", (event) => {
50
+ console.error(`[${name}] undeserializable message from worker:`, event);
51
+ });
52
+ // Replies come back on this port, and we listen with addEventListener
53
+ // rather than onmessage, so it needs start().
54
+ worker.port.start();
55
+ worker.port.addEventListener("message", onMessage);
56
+ worker.port.postMessage({ type: "debug", debug: debugging });
57
+ onSpawn?.(worker);
58
+ disposeDeathDetection = installDeathDetection(worker);
59
+ return worker;
60
+ };
61
+ async function recover(reason, dead) {
62
+ if (dead !== current)
63
+ return;
64
+ if (recovering)
65
+ return;
66
+ const now = Date.now();
67
+ if (now - lastRecoveryAt < RECOVERY_MIN_INTERVAL_MS)
68
+ return;
69
+ recovering = true;
70
+ lastRecoveryAt = now;
71
+ lifecycleLog("recreating the %s SharedWorker (%s)", name, reason);
72
+ try {
73
+ disposeDeathDetection?.();
74
+ disposeDeathDetection = undefined;
75
+ current = undefined;
76
+ try {
77
+ dead.port.close();
78
+ }
79
+ catch { }
80
+ get();
81
+ for (const listener of recreatedListeners) {
82
+ try {
83
+ listener();
84
+ }
85
+ catch (error) {
86
+ console.error(`[${name}] recreated listener threw`, error);
87
+ }
88
+ }
89
+ }
90
+ finally {
91
+ recovering = false;
92
+ }
93
+ }
94
+ function installDeathDetection(worker) {
95
+ let instanceId;
96
+ let lastHeardAt = Date.now();
97
+ let warnedUnresponsive = false;
98
+ let warnedSendFailed = false;
99
+ let disposed = false;
100
+ let probe;
101
+ let seq = 0;
102
+ const closeProbe = () => {
103
+ if (!probe)
104
+ return;
105
+ try {
106
+ probe.port.close();
107
+ }
108
+ catch { }
109
+ probe = undefined;
110
+ };
111
+ worker.port.addEventListener("message", (event) => {
112
+ const data = event.data;
113
+ if (data?.type !== "hello" && data?.type !== "pong")
114
+ return;
115
+ lastHeardAt = Date.now();
116
+ warnedUnresponsive = false;
117
+ closeProbe();
118
+ if (instanceId === undefined) {
119
+ instanceId = data.instanceId;
120
+ lifecycleLog("%s SharedWorker instance %s (via %s)", name, data.instanceId, data.type);
121
+ }
122
+ else if (data.instanceId && data.instanceId !== instanceId) {
123
+ lifecycleLog("%s SharedWorker instance changed (instance %s, was %s)", name, data.instanceId, instanceId);
124
+ instanceId = data.instanceId;
125
+ }
126
+ });
127
+ worker.port.addEventListener("close", () => {
128
+ if (disposed)
129
+ return;
130
+ lifecycleLog("%s SharedWorker control port closed", name);
131
+ void recover("control port closed", worker);
132
+ });
133
+ // Not gated on the debug namespace: a worker that fails to load never
134
+ // replies to anything, and this is the only signal that says so.
135
+ worker.addEventListener("error", (event) => {
136
+ console.error(`${name} SharedWorker error:`, describeErrorEvent(event));
137
+ });
138
+ const startProbe = (reason) => {
139
+ if (probe || disposed)
140
+ return;
141
+ lifecycleLog("%s SharedWorker %s; probing with a second connection", name, reason);
142
+ const startedAt = Date.now();
143
+ const p = new SharedWorker(path(), { name, type: "module" });
144
+ probe = p;
145
+ p.port.start();
146
+ p.port.addEventListener("message", (event) => {
147
+ if (event.data?.type !== "hello")
148
+ return;
149
+ setTimeout(() => {
150
+ if (disposed || probe !== p)
151
+ return;
152
+ closeProbe();
153
+ // The suspect spoke while the probe ran: it was merely busy, and
154
+ // everything queued on it has been delivered.
155
+ if (lastHeardAt >= startedAt)
156
+ return;
157
+ void recover(`port unresponsive on a live worker (${reason}; probe confirmed)`, worker);
158
+ }, PROBE_GRACE_MS);
159
+ });
160
+ // No hello on the probe means the instance is loading or busy. The probe
161
+ // waits indefinitely rather than tearing anything down on a timer.
162
+ };
163
+ const heartbeat = setInterval(() => {
164
+ try {
165
+ worker.port.postMessage({ type: "ping", id: ++seq });
166
+ }
167
+ catch (error) {
168
+ // Without this a failed send is indistinguishable from a dead worker.
169
+ if (!warnedSendFailed) {
170
+ warnedSendFailed = true;
171
+ console.error(`${name} SharedWorker ping send threw`, error);
172
+ }
173
+ }
174
+ const neverHeard = instanceId === undefined;
175
+ const silentMs = Date.now() - lastHeardAt;
176
+ const timeoutMs = neverHeard
177
+ ? FIRST_CONTACT_TIMEOUT_MS
178
+ : HEARTBEAT_TIMEOUT_MS;
179
+ if (silentMs <= timeoutMs)
180
+ return;
181
+ // First contact probes regardless of visibility: SharedWorkers don't
182
+ // suspend with the tab, and the probe destroys nothing. Post-contact
183
+ // silence defers to visibility, since a hidden page's throttling can fake
184
+ // it.
185
+ const visible = typeof document === "undefined" ||
186
+ document.visibilityState === "visible";
187
+ if (!neverHeard && !visible)
188
+ return;
189
+ const seconds = Math.round(silentMs / 1000);
190
+ const reason = neverHeard
191
+ ? `no hello ~${seconds}s after connecting`
192
+ : `no pong for ~${seconds}s`;
193
+ if (!warnedUnresponsive) {
194
+ warnedUnresponsive = true;
195
+ lifecycleLog("%s SharedWorker %s (tab visible)", name, reason);
196
+ }
197
+ startProbe(reason);
198
+ }, HEARTBEAT_MS);
199
+ return () => {
200
+ disposed = true;
201
+ clearInterval(heartbeat);
202
+ closeProbe();
203
+ };
204
+ }
205
+ return {
206
+ name,
207
+ get,
208
+ post(message, transfer) {
209
+ get().port.postMessage(message, transfer ?? []);
210
+ },
211
+ onRecreated(listener) {
212
+ recreatedListeners.add(listener);
213
+ return () => {
214
+ recreatedListeners.delete(listener);
215
+ };
216
+ },
217
+ };
218
+ }
219
+ /**
220
+ * Mirror a worker's forwarded console output into this tab's console, since a
221
+ * SharedWorker's own console is only visible in chrome://inspect.
222
+ */
223
+ export function forwardWorkerConsole(name, data) {
224
+ if (data?.type !== "console")
225
+ return false;
226
+ const { level, args } = data;
227
+ if (!lifecycleLog.enabled &&
228
+ typeof args?.[0] === "string" &&
229
+ args[0].includes("[lifecycle]")) {
230
+ return true;
231
+ }
232
+ const write = console[level] ?? console.log;
233
+ // The worker's logs carry %c directives in args[0] with CSS in the following
234
+ // args, so the tag has to go inside the format string or the CSS prints raw.
235
+ if (typeof args[0] === "string") {
236
+ write(`[${name}] ${args[0]}`, ...args.slice(1));
237
+ }
238
+ else {
239
+ write(`[${name}]`, ...args);
240
+ }
241
+ return true;
242
+ }
@@ -0,0 +1,20 @@
1
+ import type { RepoConfig } from "@automerge/automerge-repo/slim";
2
+ type SubductionAdapters = NonNullable<RepoConfig["subductionAdapters"]>;
3
+ /**
4
+ * Every Repo on this origin — each tab's, and the automerge protocol handler
5
+ * worker's — is a full Subduction node with its own storage and its own
6
+ * sync-server socket. Siblings would still meet through the server,
7
+ * eventually; this meets them over a BroadcastChannel so an edit in one tab
8
+ * lands in the others in the time it takes to post a message, online or not.
9
+ *
10
+ * What crosses the channel is Subduction: transport frames between two nodes,
11
+ * authenticated by each one's signer. No classic automerge sync runs here, so
12
+ * the adapters go to `new Repo({ subductionAdapters })` rather than to the
13
+ * network subsystem.
14
+ *
15
+ * A BroadcastChannel is a mesh in which every node sees every other one, and
16
+ * Subduction's handshake has an initiator and a responder, so the role is
17
+ * "mesh": for each pair, the node whose peer id sorts lower speaks first.
18
+ */
19
+ export declare function siblingAdapters(): SubductionAdapters;
20
+ export {};
@@ -0,0 +1,28 @@
1
+ import { BroadcastChannelNetworkAdapter } from "@automerge/automerge-repo-network-broadcastchannel";
2
+ import { storagePrefix } from "./storage.js";
3
+ /**
4
+ * Every Repo on this origin — each tab's, and the automerge protocol handler
5
+ * worker's — is a full Subduction node with its own storage and its own
6
+ * sync-server socket. Siblings would still meet through the server,
7
+ * eventually; this meets them over a BroadcastChannel so an edit in one tab
8
+ * lands in the others in the time it takes to post a message, online or not.
9
+ *
10
+ * What crosses the channel is Subduction: transport frames between two nodes,
11
+ * authenticated by each one's signer. No classic automerge sync runs here, so
12
+ * the adapters go to `new Repo({ subductionAdapters })` rather than to the
13
+ * network subsystem.
14
+ *
15
+ * A BroadcastChannel is a mesh in which every node sees every other one, and
16
+ * Subduction's handshake has an initiator and a responder, so the role is
17
+ * "mesh": for each pair, the node whose peer id sorts lower speaks first.
18
+ */
19
+ export function siblingAdapters() {
20
+ const serviceName = `${storagePrefix}-siblings`;
21
+ return [
22
+ {
23
+ adapter: new BroadcastChannelNetworkAdapter({ channelName: serviceName }),
24
+ serviceName,
25
+ role: "mesh",
26
+ },
27
+ ];
28
+ }
@@ -0,0 +1,17 @@
1
+ import type { StorageAdapterInterface } from "@automerge/automerge-repo/slim";
2
+ import { MemorySigner } from "@automerge/automerge-subduction/slim";
3
+ /**
4
+ * The Subduction identity every Repo on this origin presents: each tab and the
5
+ * automerge protocol handler worker sign as the same peer. The seed is kept in
6
+ * the same IndexedDB they already share, so it survives a reload and a new tab
7
+ * picks it up rather than minting its own.
8
+ *
9
+ * Keyhive sites don't come through here — their signer is derived from the
10
+ * keyhive active keypair, which ARK keeps in the keyhive storage those same
11
+ * contexts share.
12
+ *
13
+ * The lock is for a cold profile. Without it two contexts both find nothing,
14
+ * both generate, and both go on using the seed they made while only one of
15
+ * them is the seed in storage.
16
+ */
17
+ export declare function loadOrCreateSigner(storage: StorageAdapterInterface): Promise<MemorySigner>;
package/dist/signer.js ADDED
@@ -0,0 +1,27 @@
1
+ import { MemorySigner } from "@automerge/automerge-subduction/slim";
2
+ const SIGNER_KEY = ["patchwork", "subduction-signer"];
3
+ const SIGNER_LOCK = "patchwork-subduction-signer";
4
+ /**
5
+ * The Subduction identity every Repo on this origin presents: each tab and the
6
+ * automerge protocol handler worker sign as the same peer. The seed is kept in
7
+ * the same IndexedDB they already share, so it survives a reload and a new tab
8
+ * picks it up rather than minting its own.
9
+ *
10
+ * Keyhive sites don't come through here — their signer is derived from the
11
+ * keyhive active keypair, which ARK keeps in the keyhive storage those same
12
+ * contexts share.
13
+ *
14
+ * The lock is for a cold profile. Without it two contexts both find nothing,
15
+ * both generate, and both go on using the seed they made while only one of
16
+ * them is the seed in storage.
17
+ */
18
+ export async function loadOrCreateSigner(storage) {
19
+ return navigator.locks.request(SIGNER_LOCK, async () => {
20
+ const stored = await storage.load(SIGNER_KEY);
21
+ if (stored)
22
+ return MemorySigner.fromBytes(stored);
23
+ const seed = crypto.getRandomValues(new Uint8Array(32));
24
+ await storage.save(SIGNER_KEY, seed);
25
+ return MemorySigner.fromBytes(seed);
26
+ });
27
+ }
package/dist/types.d.ts CHANGED
@@ -5,68 +5,6 @@
5
5
  * reintroduced when either of them restarts — and so tabs can listen in.
6
6
  */
7
7
  export declare const HANDOFF_CHANNEL = "@patchwork/handoff";
8
- /**
9
- * BroadcastChannel on which the automerge shared worker announces remote
10
- * heads it learns about from the sync server. Any tab can listen to stay
11
- * informed of sync progress without repo-to-repo gossiping.
12
- */
13
- export declare const SYNCSTATE_CHANNEL = "@patchwork/syncstate";
14
- /**
15
- * Worker → tabs: the worker's Subduction link to the sync server flipped.
16
- * `serverPeerIds` are the directly-connected sync-server peer ids (their
17
- * verifying keys), so a tab can tell which peer rows are *the server* and
18
- * judge "synced" against them specifically.
19
- */
20
- export interface SyncStateConnectionMessage {
21
- type: "connection";
22
- connected: boolean;
23
- serverPeerIds: string[];
24
- }
25
- /**
26
- * Worker → tabs: the shared worker's own Subduction identity, so a tab can
27
- * tell which peer rows are "us". `peerId` is `signer.peerId().toString()` (the
28
- * value that shows up as a peer id); `verifyingKey` is its hex Ed25519 key.
29
- */
30
- export interface SyncStateWhoAmIMessage {
31
- type: "whoami";
32
- peerId: string;
33
- verifyingKey: string;
34
- }
35
- export type SyncStateBroadcast = SyncStateConnectionMessage | SyncStateWhoAmIMessage;
36
- /**
37
- * Tab → worker: please replay the current global sync signals (whoami +
38
- * connection) so a freshly-opened tab can orient immediately. Per-document
39
- * heads are no longer replayed here — a tab subscribes to the specific docs it
40
- * cares about over its control port instead (see {@link SyncSubscribeMessage}).
41
- */
42
- export interface SyncStateRequestMessage {
43
- type: "request";
44
- /** @deprecated ignored — per-doc state is delivered via sync-sub now. */
45
- documentId?: string;
46
- }
47
- /** Tab → worker: start pushing me this document's heads (replays current state). */
48
- export interface SyncSubscribeMessage {
49
- type: "sync-sub";
50
- documentId: string;
51
- }
52
- /** Tab → worker: stop pushing me this document's heads. */
53
- export interface SyncUnsubscribeMessage {
54
- type: "sync-unsub";
55
- documentId: string;
56
- }
57
- /**
58
- * Worker → tab (control port): a peer's heads for a subscribed document — the
59
- * worker's own (keyed by its peerId) or a Subduction peer's (keyed by its
60
- * verifying-key storageId). Same payload as the old broadcast remote-heads
61
- * message, but delivered only to the tabs that asked for this document.
62
- */
63
- export interface SyncStateDocMessage {
64
- type: "sync-state";
65
- documentId: string;
66
- storageId: string;
67
- heads: string[];
68
- timestamp: number;
69
- }
70
8
  /**
71
9
  * The special URL to resolve, plus enough of the {@link Request} the service
72
10
  * worker is holding that the automerge worker can construct one that
@@ -166,25 +104,13 @@ export type SetupServiceWorkerOptions = {
166
104
  path?: string;
167
105
  /**
168
106
  * The public path to the automerge shared worker file.
169
- * Defaults to `/automerge-worker.js`
107
+ * Defaults to `/automerge-protocol-handler-worker.js`
170
108
  */
171
109
  workerPath?: string;
172
110
  };
173
- export type ServiceWorkerRepoChannelListener = (port: MessagePort) => void | Promise<void>;
174
111
  export type SetupServiceWorkerResult = {
175
112
  shared?: SharedWorker;
176
113
  kill?: () => void;
177
114
  /** Open a classic Automerge sync WebSocket from the automerge worker. */
178
115
  connectClassicSync: (server?: string) => Promise<void>;
179
- subscribeToRepoChannel: (listener: ServiceWorkerRepoChannelListener) => Promise<() => void>;
180
- /** Open a fresh repo sync port to the automerge worker (dev console). */
181
- getRepoChannel: () => MessagePort;
182
- /**
183
- * Watch one document's sync heads (this tab's own and each Subduction peer's,
184
- * as the worker learns them). Calls `listener` on every update for that doc,
185
- * replaying the current state on subscribe. Returns an unsubscribe function;
186
- * the worker stops pushing the doc once the last local watcher drops it (and
187
- * automatically if this tab goes away).
188
- */
189
- subscribeSyncState: (documentId: string, listener: (update: SyncStateDocMessage) => void) => () => void;
190
116
  };
package/dist/types.js CHANGED
@@ -5,9 +5,3 @@
5
5
  * reintroduced when either of them restarts — and so tabs can listen in.
6
6
  */
7
7
  export const HANDOFF_CHANNEL = "@patchwork/handoff";
8
- /**
9
- * BroadcastChannel on which the automerge shared worker announces remote
10
- * heads it learns about from the sync server. Any tab can listen to stay
11
- * informed of sync progress without repo-to-repo gossiping.
12
- */
13
- export const SYNCSTATE_CHANNEL = "@patchwork/syncstate";
@@ -0,0 +1,8 @@
1
+ export declare function postToPort(port: MessagePort, message: unknown): void;
2
+ export declare function startWorkerControl(name: string, handlers?: {
3
+ onConnect?: (port: MessagePort) => void;
4
+ onMessage?: (data: any, port: MessagePort, event: MessageEvent) => void;
5
+ onClose?: (port: MessagePort) => void;
6
+ }): {
7
+ log: (...args: unknown[]) => void;
8
+ };
@@ -0,0 +1,111 @@
1
+ // The control protocol every patchwork SharedWorker speaks with its tabs:
2
+ // console forwarding, a `hello` on connect, ping/pong for the tab's death
3
+ // detection, and a debug toggle. Everything else is the worker's own business
4
+ // and arrives through `onMessage`.
5
+ /** A fresh instance means cold in-memory state, so tabs watch this. */
6
+ const WORKER_INSTANCE_ID = Math.random().toString(36).slice(2);
7
+ const WORKER_BOOT_TIME = Date.now();
8
+ const MAX_BUFFER = 200;
9
+ export function postToPort(port, message) {
10
+ try {
11
+ port.postMessage(message);
12
+ }
13
+ catch (error) {
14
+ console.warn("sending failed", error);
15
+ }
16
+ }
17
+ function serializeArg(arg) {
18
+ if (typeof arg === "string")
19
+ return arg;
20
+ if (arg instanceof Error)
21
+ return arg.stack || `${arg.name}: ${arg.message}`;
22
+ try {
23
+ return JSON.stringify(arg);
24
+ }
25
+ catch {
26
+ return String(arg);
27
+ }
28
+ }
29
+ export function startWorkerControl(name, handlers = {}) {
30
+ const ports = new Set();
31
+ // Logs emitted before any tab connects (wasm boot) would otherwise be lost.
32
+ const preConnect = [];
33
+ // `debug` reads localStorage, which a SharedWorker doesn't have, so this is
34
+ // toggled by a control message from a tab instead.
35
+ let debugging = false;
36
+ // A SharedWorker's own console is buried in chrome://inspect, so mirror
37
+ // everything over each connected tab's control port.
38
+ const forward = (level, rawArgs) => {
39
+ const args = rawArgs.map(serializeArg);
40
+ if (!ports.size) {
41
+ if (preConnect.length < MAX_BUFFER)
42
+ preConnect.push({ level, args });
43
+ return;
44
+ }
45
+ for (const port of ports)
46
+ postToPort(port, { type: "console", level, args });
47
+ };
48
+ for (const level of ["log", "info", "warn", "error", "debug"]) {
49
+ const original = console[level].bind(console);
50
+ console[level] = (...args) => {
51
+ original(...args);
52
+ forward(level, args);
53
+ };
54
+ }
55
+ self.addEventListener("error", (event) => {
56
+ const e = event;
57
+ forward("error", [
58
+ `uncaught error: ${e.message}`,
59
+ e.error instanceof Error ? e.error.stack : undefined,
60
+ ]);
61
+ });
62
+ self.addEventListener("unhandledrejection", (event) => {
63
+ const reason = event.reason;
64
+ forward("error", [
65
+ "unhandled rejection:",
66
+ reason instanceof Error ? reason.stack || reason.message : reason,
67
+ ]);
68
+ });
69
+ self.addEventListener("connect", (event) => {
70
+ const port = event.ports[0];
71
+ handlers.onConnect?.(port);
72
+ port.addEventListener("message", (messageEvent) => {
73
+ const data = messageEvent.data;
74
+ if (data?.type === "ping") {
75
+ postToPort(port, {
76
+ type: "pong",
77
+ id: data.id,
78
+ instanceId: WORKER_INSTANCE_ID,
79
+ });
80
+ return;
81
+ }
82
+ if (data?.type === "debug") {
83
+ debugging = data.debug;
84
+ return;
85
+ }
86
+ handlers.onMessage?.(data, port, messageEvent);
87
+ });
88
+ // Fires when the owning page is destroyed.
89
+ port.addEventListener("close", () => {
90
+ ports.delete(port);
91
+ handlers.onClose?.(port);
92
+ });
93
+ port.start();
94
+ postToPort(port, {
95
+ type: "hello",
96
+ instanceId: WORKER_INSTANCE_ID,
97
+ bootTime: WORKER_BOOT_TIME,
98
+ });
99
+ ports.add(port);
100
+ for (const { level, args } of preConnect.splice(0)) {
101
+ postToPort(port, { type: "console", level, args });
102
+ }
103
+ });
104
+ console.warn(`[lifecycle] ${name} SharedWorker started (instance ${WORKER_INSTANCE_ID})`);
105
+ return {
106
+ log: (...args) => {
107
+ if (debugging)
108
+ console.log(`[${name}]`, ...args);
109
+ },
110
+ };
111
+ }
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "git+https://github.com/inkandswitch/patchwork-system.git",
6
6
  "directory": "core/bootloader"
7
7
  },
8
- "version": "0.6.3",
8
+ "version": "0.7.1",
9
9
  "author": "chee",
10
10
  "type": "module",
11
11
  "license": "MIT",
@@ -26,6 +26,14 @@
26
26
  "import": "./dist/externals-list.js",
27
27
  "types": "./dist/externals-list.d.ts"
28
28
  },
29
+ "./siblings": {
30
+ "import": "./dist/siblings.js",
31
+ "types": "./dist/siblings.d.ts"
32
+ },
33
+ "./signer": {
34
+ "import": "./dist/signer.js",
35
+ "types": "./dist/signer.d.ts"
36
+ },
29
37
  "./storage": {
30
38
  "import": "./dist/storage.js",
31
39
  "types": "./dist/storage.d.ts"
@@ -34,9 +42,9 @@
34
42
  "import": "./dist/service-worker.js",
35
43
  "types": "./dist/service-worker.d.ts"
36
44
  },
37
- "./automerge-worker": {
38
- "import": "./dist/automerge-worker.js",
39
- "types": "./dist/automerge-worker.d.ts"
45
+ "./automerge-protocol-handler-worker": {
46
+ "import": "./dist/automerge-protocol-handler-worker.js",
47
+ "types": "./dist/automerge-protocol-handler-worker.d.ts"
40
48
  },
41
49
  "./module-loader": {
42
50
  "import": "./dist/module-loader.js",
@@ -53,9 +61,10 @@
53
61
  "./global.css": "./dist/global.css"
54
62
  },
55
63
  "dependencies": {
56
- "@automerge/automerge": "3.3.2",
64
+ "@automerge/automerge": "3.4.1",
57
65
  "@automerge/automerge-repo": "2.6.0-subduction.48",
58
66
  "@automerge/automerge-repo-keyhive": "0.5.0-alpha.7",
67
+ "@automerge/automerge-repo-network-broadcastchannel": "2.6.0-subduction.48",
59
68
  "@automerge/automerge-repo-network-messagechannel": "2.6.0-subduction.48",
60
69
  "@automerge/automerge-repo-network-websocket": "2.6.0-subduction.48",
61
70
  "@automerge/automerge-repo-storage-indexeddb": "2.6.0-subduction.48",
@@ -73,12 +82,12 @@
73
82
  "solid-js": "^1.9.13",
74
83
  "tinyargs": "^0.1.4",
75
84
  "@inkandswitch/patchwork-elements": "^6.0.2",
76
- "@inkandswitch/patchwork-filesystem": "^0.2.8",
77
- "@inkandswitch/patchwork-plugins": "^1.2.3",
85
+ "@inkandswitch/patchwork-plugins": "^1.2.4",
86
+ "@inkandswitch/patchwork-filesystem": "^0.2.9",
78
87
  "@inkandswitch/patchwork-providers": "^0.5.2"
79
88
  },
80
89
  "peerDependencies": {
81
- "@automerge/automerge": "3.3.2",
90
+ "@automerge/automerge": "3.4.1",
82
91
  "@automerge/automerge-repo": "2.6.0-subduction.48",
83
92
  "@automerge/automerge-repo-keyhive": "0.5.0-alpha.7",
84
93
  "@automerge/vanillajs": "2.6.0-subduction.48"