@fairfox/polly 0.19.0 → 0.20.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.
- package/README.md +131 -943
- package/dist/cli/polly.js +25 -4
- package/dist/cli/polly.js.map +3 -3
- package/dist/src/background/index.js +22 -12
- package/dist/src/background/index.js.map +3 -3
- package/dist/src/background/message-router.js +22 -12
- package/dist/src/background/message-router.js.map +3 -3
- package/dist/src/client/index.js +187 -154
- package/dist/src/client/index.js.map +4 -4
- package/dist/src/elysia/index.js +19 -9
- package/dist/src/elysia/index.js.map +2 -2
- package/dist/src/elysia/plugin.d.ts +3 -3
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +67 -14
- package/dist/src/index.js.map +7 -6
- package/dist/src/shared/adapters/index.js +22 -12
- package/dist/src/shared/adapters/index.js.map +3 -3
- package/dist/src/shared/lib/context-helpers.js +22 -12
- package/dist/src/shared/lib/context-helpers.js.map +3 -3
- package/dist/src/shared/lib/errors.js +19 -9
- package/dist/src/shared/lib/errors.js.map +2 -2
- package/dist/src/shared/lib/message-bus.js +22 -12
- package/dist/src/shared/lib/message-bus.js.map +3 -3
- package/dist/src/shared/lib/resource.d.ts +54 -0
- package/dist/src/shared/lib/resource.js +593 -0
- package/dist/src/shared/lib/resource.js.map +13 -0
- package/dist/src/shared/lib/state.d.ts +1 -0
- package/dist/src/shared/lib/state.js +23 -12
- package/dist/src/shared/lib/state.js.map +4 -4
- package/dist/src/shared/lib/test-helpers.js +19 -9
- package/dist/src/shared/lib/test-helpers.js.map +2 -2
- package/dist/src/shared/state/app-state.js +22 -12
- package/dist/src/shared/state/app-state.js.map +4 -4
- package/dist/src/shared/types/messages.js +19 -9
- package/dist/src/shared/types/messages.js.map +2 -2
- package/dist/tools/init/src/cli.js +6 -2
- package/dist/tools/init/src/cli.js.map +2 -2
- package/dist/tools/init/templates/pwa/package.json.template +1 -2
- package/dist/tools/test/src/adapters/index.d.ts +2 -2
- package/dist/tools/test/src/adapters/index.js +19 -9
- package/dist/tools/test/src/adapters/index.js.map +3 -3
- package/dist/tools/test/src/index.js +19 -9
- package/dist/tools/test/src/index.js.map +3 -3
- package/dist/tools/test/src/test-utils.js +19 -9
- package/dist/tools/test/src/test-utils.js.map +2 -2
- package/dist/tools/verify/specs/docker-compose.yml +1 -1
- package/dist/tools/verify/src/cli.js +185 -14
- package/dist/tools/verify/src/cli.js.map +7 -7
- package/dist/tools/verify/src/config.js +19 -9
- package/dist/tools/verify/src/config.js.map +2 -2
- package/dist/tools/visualize/src/cli.js +144 -5
- package/dist/tools/visualize/src/cli.js.map +3 -3
- package/package.json +12 -14
- package/dist/src/elysia/tla-generator.d.ts +0 -16
- package/dist/tools/verify/specs/verification.config.ts +0 -64
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
function __accessProp(key) {
|
|
6
|
+
return this[key];
|
|
7
|
+
}
|
|
8
|
+
var __toCommonJS = (from) => {
|
|
9
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
10
|
+
if (entry)
|
|
11
|
+
return entry;
|
|
12
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (var key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(entry, key))
|
|
16
|
+
__defProp(entry, key, {
|
|
17
|
+
get: __accessProp.bind(from, key),
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
__moduleCache.set(from, entry);
|
|
22
|
+
return entry;
|
|
23
|
+
};
|
|
24
|
+
var __moduleCache;
|
|
25
|
+
var __returnValue = (v) => v;
|
|
26
|
+
function __exportSetter(name, newValue) {
|
|
27
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
28
|
+
}
|
|
29
|
+
var __export = (target, all) => {
|
|
30
|
+
for (var name in all)
|
|
31
|
+
__defProp(target, name, {
|
|
32
|
+
get: all[name],
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
set: __exportSetter.bind(all, name)
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
39
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
40
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
41
|
+
}) : x)(function(x) {
|
|
42
|
+
if (typeof require !== "undefined")
|
|
43
|
+
return require.apply(this, arguments);
|
|
44
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// src/shared/lib/storage-adapter.ts
|
|
48
|
+
var exports_storage_adapter = {};
|
|
49
|
+
__export(exports_storage_adapter, {
|
|
50
|
+
createStorageAdapter: () => createStorageAdapter,
|
|
51
|
+
MemoryStorageAdapter: () => MemoryStorageAdapter,
|
|
52
|
+
IndexedDBAdapter: () => IndexedDBAdapter,
|
|
53
|
+
ChromeStorageAdapter: () => ChromeStorageAdapter
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
class IndexedDBAdapter {
|
|
57
|
+
dbName;
|
|
58
|
+
storeName = "state";
|
|
59
|
+
dbPromise = null;
|
|
60
|
+
constructor(dbName = "polly-state") {
|
|
61
|
+
this.dbName = dbName;
|
|
62
|
+
}
|
|
63
|
+
getDB() {
|
|
64
|
+
if (this.dbPromise)
|
|
65
|
+
return this.dbPromise;
|
|
66
|
+
this.dbPromise = new Promise((resolve, reject) => {
|
|
67
|
+
const request = indexedDB.open(this.dbName, 1);
|
|
68
|
+
request.onerror = () => reject(request.error);
|
|
69
|
+
request.onsuccess = () => resolve(request.result);
|
|
70
|
+
request.onupgradeneeded = (event) => {
|
|
71
|
+
const db = event.target.result;
|
|
72
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
73
|
+
db.createObjectStore(this.storeName);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
return this.dbPromise;
|
|
78
|
+
}
|
|
79
|
+
async get(keys) {
|
|
80
|
+
try {
|
|
81
|
+
const db = await this.getDB();
|
|
82
|
+
const result = {};
|
|
83
|
+
await Promise.all(keys.map((key) => new Promise((resolve, reject) => {
|
|
84
|
+
const transaction = db.transaction([this.storeName], "readonly");
|
|
85
|
+
const store = transaction.objectStore(this.storeName);
|
|
86
|
+
const request = store.get(key);
|
|
87
|
+
request.onerror = () => reject(request.error);
|
|
88
|
+
request.onsuccess = () => {
|
|
89
|
+
if (request.result !== undefined) {
|
|
90
|
+
result[key] = request.result;
|
|
91
|
+
}
|
|
92
|
+
resolve();
|
|
93
|
+
};
|
|
94
|
+
})));
|
|
95
|
+
return result;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.warn("[Polly] IndexedDB get failed:", error);
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async set(items) {
|
|
102
|
+
try {
|
|
103
|
+
const db = await this.getDB();
|
|
104
|
+
await Promise.all(Object.entries(items).map(([key, value]) => new Promise((resolve, reject) => {
|
|
105
|
+
const transaction = db.transaction([this.storeName], "readwrite");
|
|
106
|
+
const store = transaction.objectStore(this.storeName);
|
|
107
|
+
const request = store.put(value, key);
|
|
108
|
+
request.onerror = () => reject(request.error);
|
|
109
|
+
request.onsuccess = () => resolve();
|
|
110
|
+
})));
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.warn("[Polly] IndexedDB set failed:", error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async remove(keys) {
|
|
116
|
+
try {
|
|
117
|
+
const db = await this.getDB();
|
|
118
|
+
await Promise.all(keys.map((key) => new Promise((resolve, reject) => {
|
|
119
|
+
const transaction = db.transaction([this.storeName], "readwrite");
|
|
120
|
+
const store = transaction.objectStore(this.storeName);
|
|
121
|
+
const request = store.delete(key);
|
|
122
|
+
request.onerror = () => reject(request.error);
|
|
123
|
+
request.onsuccess = () => resolve();
|
|
124
|
+
})));
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.warn("[Polly] IndexedDB remove failed:", error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
class ChromeStorageAdapter {
|
|
132
|
+
async get(keys) {
|
|
133
|
+
if (typeof chrome === "undefined" || !chrome.storage) {
|
|
134
|
+
return {};
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
return await chrome.storage.local.get(keys);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.warn("[Polly] Chrome storage get failed:", error);
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async set(items) {
|
|
144
|
+
if (typeof chrome === "undefined" || !chrome.storage) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
await chrome.storage.local.set(items);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.warn("[Polly] Chrome storage set failed:", error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async remove(keys) {
|
|
154
|
+
if (typeof chrome === "undefined" || !chrome.storage) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
await chrome.storage.local.remove(keys);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.warn("[Polly] Chrome storage remove failed:", error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
class MemoryStorageAdapter {
|
|
166
|
+
storage = new Map;
|
|
167
|
+
async get(keys) {
|
|
168
|
+
const result = {};
|
|
169
|
+
for (const key of keys) {
|
|
170
|
+
const value = this.storage.get(key);
|
|
171
|
+
if (value !== undefined) {
|
|
172
|
+
result[key] = value;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
async set(items) {
|
|
178
|
+
for (const [key, value] of Object.entries(items)) {
|
|
179
|
+
this.storage.set(key, value);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async remove(keys) {
|
|
183
|
+
for (const key of keys) {
|
|
184
|
+
this.storage.delete(key);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function createStorageAdapter() {
|
|
189
|
+
if (typeof chrome !== "undefined" && chrome.storage && chrome.runtime) {
|
|
190
|
+
return new ChromeStorageAdapter;
|
|
191
|
+
}
|
|
192
|
+
if (typeof indexedDB !== "undefined") {
|
|
193
|
+
return new IndexedDBAdapter;
|
|
194
|
+
}
|
|
195
|
+
return new MemoryStorageAdapter;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/shared/lib/sync-adapter.ts
|
|
199
|
+
var exports_sync_adapter = {};
|
|
200
|
+
__export(exports_sync_adapter, {
|
|
201
|
+
createSyncAdapter: () => createSyncAdapter,
|
|
202
|
+
NoOpSyncAdapter: () => NoOpSyncAdapter,
|
|
203
|
+
ChromeRuntimeSyncAdapter: () => ChromeRuntimeSyncAdapter,
|
|
204
|
+
BroadcastChannelSyncAdapter: () => BroadcastChannelSyncAdapter
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
class NoOpSyncAdapter {
|
|
208
|
+
broadcast(_message) {}
|
|
209
|
+
onMessage(_callback) {
|
|
210
|
+
return () => {};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
class ChromeRuntimeSyncAdapter {
|
|
215
|
+
listeners = [];
|
|
216
|
+
port = null;
|
|
217
|
+
constructor() {
|
|
218
|
+
if (typeof chrome !== "undefined" && chrome.runtime) {
|
|
219
|
+
chrome.runtime.onMessage.addListener((message, _sender, _sendResponse) => {
|
|
220
|
+
if (message.type === "STATE_SYNC") {
|
|
221
|
+
this.listeners.forEach((listener) => {
|
|
222
|
+
listener(message);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
broadcast(message) {
|
|
229
|
+
if (typeof chrome === "undefined" || !chrome.runtime) {
|
|
230
|
+
console.warn("[SyncAdapter] chrome.runtime not available");
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
chrome.runtime.sendMessage({
|
|
235
|
+
type: "STATE_SYNC",
|
|
236
|
+
key: message.key,
|
|
237
|
+
value: message.value,
|
|
238
|
+
clock: message.clock
|
|
239
|
+
});
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.warn("[SyncAdapter] Failed to broadcast state update:", error);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
onMessage(callback) {
|
|
245
|
+
this.listeners.push(callback);
|
|
246
|
+
return () => {
|
|
247
|
+
const index = this.listeners.indexOf(callback);
|
|
248
|
+
if (index > -1) {
|
|
249
|
+
this.listeners.splice(index, 1);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
connect() {
|
|
254
|
+
return Promise.resolve();
|
|
255
|
+
}
|
|
256
|
+
disconnect() {
|
|
257
|
+
this.listeners = [];
|
|
258
|
+
if (this.port) {
|
|
259
|
+
this.port.disconnect();
|
|
260
|
+
this.port = null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
isConnected() {
|
|
264
|
+
return typeof chrome !== "undefined" && !!chrome.runtime;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
class BroadcastChannelSyncAdapter {
|
|
269
|
+
channel = null;
|
|
270
|
+
listeners = [];
|
|
271
|
+
constructor(channelName = "polly-sync") {
|
|
272
|
+
if (typeof BroadcastChannel === "undefined") {
|
|
273
|
+
console.warn("[SyncAdapter] BroadcastChannel not available");
|
|
274
|
+
} else {
|
|
275
|
+
this.channel = new BroadcastChannel(channelName);
|
|
276
|
+
this.channel.onmessage = (event) => {
|
|
277
|
+
if (event.data.type === "STATE_SYNC") {
|
|
278
|
+
this.listeners.forEach((listener) => {
|
|
279
|
+
listener(event.data);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
broadcast(message) {
|
|
286
|
+
if (!this.channel) {
|
|
287
|
+
console.warn("[SyncAdapter] BroadcastChannel not initialized");
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
this.channel.postMessage({
|
|
292
|
+
type: "STATE_SYNC",
|
|
293
|
+
key: message.key,
|
|
294
|
+
value: message.value,
|
|
295
|
+
clock: message.clock
|
|
296
|
+
});
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.warn("[SyncAdapter] Failed to broadcast state update:", error);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
onMessage(callback) {
|
|
302
|
+
this.listeners.push(callback);
|
|
303
|
+
return () => {
|
|
304
|
+
const index = this.listeners.indexOf(callback);
|
|
305
|
+
if (index > -1) {
|
|
306
|
+
this.listeners.splice(index, 1);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
connect() {
|
|
311
|
+
return Promise.resolve();
|
|
312
|
+
}
|
|
313
|
+
disconnect() {
|
|
314
|
+
this.listeners = [];
|
|
315
|
+
if (this.channel) {
|
|
316
|
+
this.channel.close();
|
|
317
|
+
this.channel = null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
isConnected() {
|
|
321
|
+
return this.channel !== null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function createSyncAdapter() {
|
|
325
|
+
if (typeof chrome !== "undefined" && chrome.runtime) {
|
|
326
|
+
return new ChromeRuntimeSyncAdapter;
|
|
327
|
+
}
|
|
328
|
+
if (typeof BroadcastChannel !== "undefined") {
|
|
329
|
+
return new BroadcastChannelSyncAdapter;
|
|
330
|
+
}
|
|
331
|
+
return new NoOpSyncAdapter;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/shared/lib/state.ts
|
|
335
|
+
import { effect, signal } from "@preact/signals";
|
|
336
|
+
var stateRegistry = new Map;
|
|
337
|
+
function $sharedState(key, initialValue, options = {}) {
|
|
338
|
+
const sig = createState(key, initialValue, {
|
|
339
|
+
...options,
|
|
340
|
+
enableSync: true,
|
|
341
|
+
enablePersist: true
|
|
342
|
+
});
|
|
343
|
+
const entry = stateRegistry.get(key);
|
|
344
|
+
if (entry) {
|
|
345
|
+
sig.loaded = entry.loaded;
|
|
346
|
+
}
|
|
347
|
+
return sig;
|
|
348
|
+
}
|
|
349
|
+
function $syncedState(key, initialValue, options = {}) {
|
|
350
|
+
return createState(key, initialValue, {
|
|
351
|
+
...options,
|
|
352
|
+
enableSync: true,
|
|
353
|
+
enablePersist: false
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
function $persistedState(key, initialValue, options = {}) {
|
|
357
|
+
const sig = createState(key, initialValue, {
|
|
358
|
+
...options,
|
|
359
|
+
enableSync: false,
|
|
360
|
+
enablePersist: true
|
|
361
|
+
});
|
|
362
|
+
const entry = stateRegistry.get(key);
|
|
363
|
+
if (entry) {
|
|
364
|
+
sig.loaded = entry.loaded;
|
|
365
|
+
}
|
|
366
|
+
return sig;
|
|
367
|
+
}
|
|
368
|
+
function $state(initialValue) {
|
|
369
|
+
return signal(initialValue);
|
|
370
|
+
}
|
|
371
|
+
function deepEqual(a, b) {
|
|
372
|
+
if (a === b)
|
|
373
|
+
return true;
|
|
374
|
+
if (a == null || b == null)
|
|
375
|
+
return false;
|
|
376
|
+
if (typeof a !== "object" || typeof b !== "object")
|
|
377
|
+
return false;
|
|
378
|
+
const keysA = Object.keys(a);
|
|
379
|
+
const keysB = Object.keys(b);
|
|
380
|
+
if (keysA.length !== keysB.length)
|
|
381
|
+
return false;
|
|
382
|
+
for (const key of keysA) {
|
|
383
|
+
if (!keysB.includes(key))
|
|
384
|
+
return false;
|
|
385
|
+
if (!deepEqual(a[key], b[key]))
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
function resolveAdapters(options) {
|
|
391
|
+
if (options.storage || options.sync) {
|
|
392
|
+
return {
|
|
393
|
+
storage: options.storage || (options.enablePersist ? createStorageAdapter() : null),
|
|
394
|
+
sync: options.sync || (options.enableSync ? createSyncAdapter() : null)
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
if (options.bus) {
|
|
398
|
+
return {
|
|
399
|
+
storage: options.bus.adapters.storage,
|
|
400
|
+
sync: options.enableSync ? createSyncAdapter() : null
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
storage: options.enablePersist ? createStorageAdapter() : null,
|
|
405
|
+
sync: options.enableSync ? createSyncAdapter() : null
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function createState(key, initialValue, options) {
|
|
409
|
+
if (stateRegistry.has(key)) {
|
|
410
|
+
return stateRegistry.get(key)?.signal;
|
|
411
|
+
}
|
|
412
|
+
const sig = signal(initialValue);
|
|
413
|
+
if (options.verify) {
|
|
414
|
+
const mirror = JSON.parse(JSON.stringify(initialValue));
|
|
415
|
+
sig.verify = mirror;
|
|
416
|
+
}
|
|
417
|
+
const entry = {
|
|
418
|
+
signal: sig,
|
|
419
|
+
clock: 0,
|
|
420
|
+
loaded: Promise.resolve(),
|
|
421
|
+
updating: false
|
|
422
|
+
};
|
|
423
|
+
const adapters = resolveAdapters(options);
|
|
424
|
+
if (options.enablePersist && adapters.storage) {
|
|
425
|
+
entry.loaded = loadFromStorage(key, sig, entry, adapters.storage, options.validator);
|
|
426
|
+
}
|
|
427
|
+
entry.loaded.then(() => {
|
|
428
|
+
let debounceTimer = null;
|
|
429
|
+
let previousValue = sig.value;
|
|
430
|
+
let isFirstRun = true;
|
|
431
|
+
effect(() => {
|
|
432
|
+
if (entry.updating)
|
|
433
|
+
return;
|
|
434
|
+
const value = sig.value;
|
|
435
|
+
if (isFirstRun) {
|
|
436
|
+
isFirstRun = false;
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (deepEqual(value, previousValue)) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
previousValue = value;
|
|
443
|
+
if (options.verify) {
|
|
444
|
+
const verifySignal = sig;
|
|
445
|
+
if (verifySignal.verify) {
|
|
446
|
+
Object.assign(verifySignal.verify, JSON.parse(JSON.stringify(value)));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
entry.clock++;
|
|
450
|
+
const doUpdate = () => {
|
|
451
|
+
if (options.enablePersist && adapters.storage) {
|
|
452
|
+
persistToStorage(key, value, entry.clock, adapters.storage);
|
|
453
|
+
}
|
|
454
|
+
if (options.enableSync && adapters.sync) {
|
|
455
|
+
broadcastUpdate(key, value, entry.clock, adapters.sync);
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
if (options.debounceMs) {
|
|
459
|
+
if (debounceTimer)
|
|
460
|
+
clearTimeout(debounceTimer);
|
|
461
|
+
debounceTimer = setTimeout(doUpdate, options.debounceMs);
|
|
462
|
+
} else {
|
|
463
|
+
doUpdate();
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
if (options.enableSync && adapters.sync) {
|
|
468
|
+
if (adapters.sync.connect) {
|
|
469
|
+
adapters.sync.connect();
|
|
470
|
+
}
|
|
471
|
+
adapters.sync.onMessage((message) => {
|
|
472
|
+
if (message.key !== key)
|
|
473
|
+
return;
|
|
474
|
+
const oldClock = entry.clock;
|
|
475
|
+
entry.clock = Math.max(entry.clock, message.clock);
|
|
476
|
+
if (message.clock > oldClock) {
|
|
477
|
+
if (options.validator && !options.validator(message.value)) {
|
|
478
|
+
console.warn(`[Polly] State "${key}": Received invalid value from sync (clock: ${message.clock})`, message.value);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (deepEqual(entry.signal.value, message.value)) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
applyUpdate(entry, message.value, message.clock);
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
stateRegistry.set(key, entry);
|
|
489
|
+
return sig;
|
|
490
|
+
}
|
|
491
|
+
async function loadFromStorage(key, sig, entry, storage, validator) {
|
|
492
|
+
try {
|
|
493
|
+
const result = await storage.get([key, `${key}:clock`]);
|
|
494
|
+
if (result[key] !== undefined) {
|
|
495
|
+
const storedValue = result[key];
|
|
496
|
+
if (validator) {
|
|
497
|
+
if (validator(storedValue)) {
|
|
498
|
+
sig.value = storedValue;
|
|
499
|
+
} else {
|
|
500
|
+
console.warn(`[Polly] State "${key}": Stored value failed validation, using initial value`, storedValue);
|
|
501
|
+
}
|
|
502
|
+
} else {
|
|
503
|
+
sig.value = storedValue;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (result[`${key}:clock`] !== undefined) {
|
|
507
|
+
entry.clock = result[`${key}:clock`];
|
|
508
|
+
}
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.warn(`[Polly] Failed to load state from storage: ${key}`, error);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
function persistToStorage(key, value, clock, storage) {
|
|
514
|
+
try {
|
|
515
|
+
storage.set({
|
|
516
|
+
[key]: value,
|
|
517
|
+
[`${key}:clock`]: clock
|
|
518
|
+
});
|
|
519
|
+
} catch (error) {
|
|
520
|
+
console.warn(`[Polly] Failed to persist state to storage: ${key}`, error);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function broadcastUpdate(key, value, clock, sync) {
|
|
524
|
+
try {
|
|
525
|
+
sync.broadcast({
|
|
526
|
+
key,
|
|
527
|
+
value,
|
|
528
|
+
clock
|
|
529
|
+
});
|
|
530
|
+
} catch (error) {
|
|
531
|
+
console.warn(`[Polly] Failed to broadcast state update: ${key}`, error);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function applyUpdate(entry, value, clock) {
|
|
535
|
+
entry.updating = true;
|
|
536
|
+
entry.signal.value = value;
|
|
537
|
+
entry.clock = clock;
|
|
538
|
+
entry.updating = false;
|
|
539
|
+
}
|
|
540
|
+
function getStateByKey(key) {
|
|
541
|
+
const entry = stateRegistry.get(key);
|
|
542
|
+
return entry?.signal;
|
|
543
|
+
}
|
|
544
|
+
function clearStateRegistry() {
|
|
545
|
+
stateRegistry.clear();
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/shared/lib/resource.ts
|
|
549
|
+
import { effect as effect2, signal as signal2 } from "@preact/signals";
|
|
550
|
+
function $resource(_name, options) {
|
|
551
|
+
const { source, fetcher, initialValue } = options;
|
|
552
|
+
const data = signal2(initialValue);
|
|
553
|
+
const status = signal2("idle");
|
|
554
|
+
const error = signal2(undefined);
|
|
555
|
+
let generation = 0;
|
|
556
|
+
let lastSource;
|
|
557
|
+
function runFetch(sourceValue) {
|
|
558
|
+
const thisGeneration = ++generation;
|
|
559
|
+
status.value = "loading";
|
|
560
|
+
error.value = undefined;
|
|
561
|
+
fetcher(sourceValue).then((result) => {
|
|
562
|
+
if (thisGeneration !== generation)
|
|
563
|
+
return;
|
|
564
|
+
data.value = result;
|
|
565
|
+
status.value = "success";
|
|
566
|
+
error.value = undefined;
|
|
567
|
+
}, (err) => {
|
|
568
|
+
if (thisGeneration !== generation)
|
|
569
|
+
return;
|
|
570
|
+
status.value = "error";
|
|
571
|
+
error.value = err instanceof Error ? err : new Error(String(err));
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
effect2(() => {
|
|
575
|
+
const sourceValue = source();
|
|
576
|
+
if (lastSource !== undefined && deepEqual(lastSource, sourceValue)) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
lastSource = sourceValue;
|
|
580
|
+
runFetch(sourceValue);
|
|
581
|
+
});
|
|
582
|
+
function refetch() {
|
|
583
|
+
if (lastSource !== undefined) {
|
|
584
|
+
runFetch(lastSource);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return { data, status, error, refetch };
|
|
588
|
+
}
|
|
589
|
+
export {
|
|
590
|
+
$resource
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
//# debugId=CA95E0087865A45C64756E2164756E21
|