@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.
Files changed (55) hide show
  1. package/README.md +131 -943
  2. package/dist/cli/polly.js +25 -4
  3. package/dist/cli/polly.js.map +3 -3
  4. package/dist/src/background/index.js +22 -12
  5. package/dist/src/background/index.js.map +3 -3
  6. package/dist/src/background/message-router.js +22 -12
  7. package/dist/src/background/message-router.js.map +3 -3
  8. package/dist/src/client/index.js +187 -154
  9. package/dist/src/client/index.js.map +4 -4
  10. package/dist/src/elysia/index.js +19 -9
  11. package/dist/src/elysia/index.js.map +2 -2
  12. package/dist/src/elysia/plugin.d.ts +3 -3
  13. package/dist/src/index.d.ts +2 -0
  14. package/dist/src/index.js +67 -14
  15. package/dist/src/index.js.map +7 -6
  16. package/dist/src/shared/adapters/index.js +22 -12
  17. package/dist/src/shared/adapters/index.js.map +3 -3
  18. package/dist/src/shared/lib/context-helpers.js +22 -12
  19. package/dist/src/shared/lib/context-helpers.js.map +3 -3
  20. package/dist/src/shared/lib/errors.js +19 -9
  21. package/dist/src/shared/lib/errors.js.map +2 -2
  22. package/dist/src/shared/lib/message-bus.js +22 -12
  23. package/dist/src/shared/lib/message-bus.js.map +3 -3
  24. package/dist/src/shared/lib/resource.d.ts +54 -0
  25. package/dist/src/shared/lib/resource.js +593 -0
  26. package/dist/src/shared/lib/resource.js.map +13 -0
  27. package/dist/src/shared/lib/state.d.ts +1 -0
  28. package/dist/src/shared/lib/state.js +23 -12
  29. package/dist/src/shared/lib/state.js.map +4 -4
  30. package/dist/src/shared/lib/test-helpers.js +19 -9
  31. package/dist/src/shared/lib/test-helpers.js.map +2 -2
  32. package/dist/src/shared/state/app-state.js +22 -12
  33. package/dist/src/shared/state/app-state.js.map +4 -4
  34. package/dist/src/shared/types/messages.js +19 -9
  35. package/dist/src/shared/types/messages.js.map +2 -2
  36. package/dist/tools/init/src/cli.js +6 -2
  37. package/dist/tools/init/src/cli.js.map +2 -2
  38. package/dist/tools/init/templates/pwa/package.json.template +1 -2
  39. package/dist/tools/test/src/adapters/index.d.ts +2 -2
  40. package/dist/tools/test/src/adapters/index.js +19 -9
  41. package/dist/tools/test/src/adapters/index.js.map +3 -3
  42. package/dist/tools/test/src/index.js +19 -9
  43. package/dist/tools/test/src/index.js.map +3 -3
  44. package/dist/tools/test/src/test-utils.js +19 -9
  45. package/dist/tools/test/src/test-utils.js.map +2 -2
  46. package/dist/tools/verify/specs/docker-compose.yml +1 -1
  47. package/dist/tools/verify/src/cli.js +185 -14
  48. package/dist/tools/verify/src/cli.js.map +7 -7
  49. package/dist/tools/verify/src/config.js +19 -9
  50. package/dist/tools/verify/src/config.js.map +2 -2
  51. package/dist/tools/visualize/src/cli.js +144 -5
  52. package/dist/tools/visualize/src/cli.js.map +3 -3
  53. package/package.json +12 -14
  54. package/dist/src/elysia/tla-generator.d.ts +0 -16
  55. 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