@harborclient/sdk 0.7.0 → 1.0.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 (81) hide show
  1. package/dist/build/index.d.ts +59 -0
  2. package/dist/build/index.d.ts.map +1 -0
  3. package/dist/build/index.js +116 -0
  4. package/dist/client.d.ts +2 -2
  5. package/dist/components/index.d.ts +1 -0
  6. package/dist/components/index.d.ts.map +1 -1
  7. package/dist/components/index.js +1 -0
  8. package/dist/components/portalToBody.d.ts +11 -0
  9. package/dist/components/portalToBody.d.ts.map +1 -0
  10. package/dist/components/portalToBody.js +15 -0
  11. package/dist/eslint/index.d.ts +9 -0
  12. package/dist/eslint/index.d.ts.map +1 -0
  13. package/dist/eslint/index.js +18 -0
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/main.d.ts +1 -1
  17. package/dist/main.d.ts.map +1 -1
  18. package/dist/runtime/index.d.ts +1 -1
  19. package/dist/runtime/jsx-dev-runtime.d.ts +1 -1
  20. package/dist/runtime/jsx-runtime-host.d.ts +44 -0
  21. package/dist/runtime/jsx-runtime-host.js +42 -0
  22. package/dist/runtime/react-dom.d.ts +1 -0
  23. package/dist/runtime/react-dom.js +16 -0
  24. package/dist/runtime/react.d.ts +6 -0
  25. package/dist/runtime/react.js +70 -2
  26. package/dist/runtime/reactHost.js +52 -0
  27. package/dist/runtime/store.d.ts +106 -0
  28. package/dist/runtime/store.d.ts.map +1 -1
  29. package/dist/runtime/store.js +100 -0
  30. package/dist/runtime/store.ts +211 -0
  31. package/dist/runtime/viewHost.js +2 -1
  32. package/dist/runtime-utils.d.ts +59 -0
  33. package/dist/runtime-utils.d.ts.map +1 -1
  34. package/dist/runtime-utils.js +66 -0
  35. package/dist/storage/index.d.ts +1 -0
  36. package/dist/storage/index.d.ts.map +1 -1
  37. package/dist/storage/index.js +1 -0
  38. package/dist/storage/validate.d.ts +71 -0
  39. package/dist/storage/validate.d.ts.map +1 -0
  40. package/dist/storage/validate.js +111 -0
  41. package/dist/types.d.ts +14 -0
  42. package/dist/types.d.ts.map +1 -1
  43. package/package.json +37 -8
  44. package/tsconfig.base.json +15 -0
  45. package/dist/components/enhanceControl.test.d.ts +0 -2
  46. package/dist/components/enhanceControl.test.d.ts.map +0 -1
  47. package/dist/components/enhanceControl.test.js +0 -83
  48. package/dist/components/utils.test.d.ts +0 -2
  49. package/dist/components/utils.test.d.ts.map +0 -1
  50. package/dist/components/utils.test.js +0 -44
  51. package/dist/http/resolveRequest.test.d.ts +0 -2
  52. package/dist/http/resolveRequest.test.d.ts.map +0 -1
  53. package/dist/http/resolveRequest.test.js +0 -40
  54. package/dist/http/substitute.test.d.ts +0 -2
  55. package/dist/http/substitute.test.d.ts.map +0 -1
  56. package/dist/http/substitute.test.js +0 -93
  57. package/dist/pluginDatabaseApi.test.d.ts +0 -2
  58. package/dist/pluginDatabaseApi.test.d.ts.map +0 -1
  59. package/dist/pluginDatabaseApi.test.js +0 -65
  60. package/dist/runtime/index.test.d.ts +0 -2
  61. package/dist/runtime/index.test.d.ts.map +0 -1
  62. package/dist/runtime/index.test.js +0 -41
  63. package/dist/runtime/index.test.ts +0 -53
  64. package/dist/runtime-utils.test.d.ts +0 -2
  65. package/dist/runtime-utils.test.d.ts.map +0 -1
  66. package/dist/runtime-utils.test.js +0 -104
  67. package/dist/signing/signing.test.d.ts +0 -2
  68. package/dist/signing/signing.test.d.ts.map +0 -1
  69. package/dist/signing/signing.test.js +0 -100
  70. package/dist/storage/cappedList.test.d.ts +0 -2
  71. package/dist/storage/cappedList.test.d.ts.map +0 -1
  72. package/dist/storage/cappedList.test.js +0 -11
  73. package/dist/utilities.test.d.ts +0 -2
  74. package/dist/utilities.test.d.ts.map +0 -1
  75. package/dist/utilities.test.js +0 -16
  76. package/dist/variables/dynamic.test.d.ts +0 -2
  77. package/dist/variables/dynamic.test.d.ts.map +0 -1
  78. package/dist/variables/dynamic.test.js +0 -60
  79. package/dist/variables/tokens.test.d.ts +0 -2
  80. package/dist/variables/tokens.test.d.ts.map +0 -1
  81. package/dist/variables/tokens.test.js +0 -160
@@ -1,4 +1,86 @@
1
1
  import type { Disposable } from '../types.js';
2
+ /**
3
+ * Minimal storage surface used by {@link createStorageStore}.
4
+ */
5
+ export interface StorageLike {
6
+ /**
7
+ * Returns the stored value for a key.
8
+ *
9
+ * @param key - Storage key within the plugin namespace.
10
+ */
11
+ get<T>(key: string): Promise<T | undefined>;
12
+ /**
13
+ * Persists a JSON-serializable value.
14
+ *
15
+ * @param key - Storage key within the plugin namespace.
16
+ * @param value - Value to persist.
17
+ */
18
+ set<T>(key: string, value: T): Promise<void>;
19
+ }
20
+ /**
21
+ * Reactive store backed by plugin storage and compatible with React
22
+ * `useSyncExternalStore`.
23
+ */
24
+ export interface StorageStore<T> {
25
+ /**
26
+ * Subscribes to snapshot changes for `useSyncExternalStore`.
27
+ *
28
+ * @param listener - Callback invoked when the snapshot changes.
29
+ */
30
+ subscribe(listener: () => void): () => void;
31
+ /**
32
+ * Returns the current in-memory snapshot.
33
+ */
34
+ getSnapshot(): T;
35
+ /**
36
+ * React hook returning the current snapshot.
37
+ */
38
+ useValue(): T;
39
+ /**
40
+ * Reloads from storage and notifies subscribers when the parsed value changed.
41
+ */
42
+ reloadFromStorage(): Promise<void>;
43
+ /**
44
+ * Persists to storage, then updates the in-memory snapshot and notifies subscribers.
45
+ * Rejects when persistence fails; the snapshot is left unchanged.
46
+ *
47
+ * @param next - New snapshot value.
48
+ */
49
+ set(next: T): Promise<void>;
50
+ }
51
+ /**
52
+ * Options for {@link createStorageStore}.
53
+ */
54
+ export interface CreateStorageStoreOptions<T> {
55
+ /** Plugin storage API from `hc.storage`. */
56
+ storage: StorageLike;
57
+ /** Storage key within the plugin namespace. */
58
+ key: string;
59
+ /**
60
+ * Validates and hydrates a raw storage value into a typed snapshot.
61
+ * Called with `undefined` when the key has never been set.
62
+ *
63
+ * @param raw - Raw value from storage, or `undefined` when absent.
64
+ */
65
+ parse: (raw: unknown) => T;
66
+ /**
67
+ * Compares two snapshots to skip no-op reloads and writes.
68
+ * Defaults to `JSON.stringify` equality.
69
+ */
70
+ equals?: (a: T, b: T) => boolean;
71
+ /**
72
+ * When true, {@link StorageStore.reloadFromStorage} leaves the snapshot
73
+ * unchanged if the storage key is absent. Default false (apply `parse(undefined)`).
74
+ */
75
+ keepCurrentWhenMissing?: boolean;
76
+ }
77
+ /**
78
+ * Options for {@link syncOnWindowFocus}.
79
+ */
80
+ export interface SyncOnWindowFocusOptions {
81
+ /** Optional polling interval in milliseconds in addition to focus/visibility reloads. */
82
+ intervalMs?: number;
83
+ }
2
84
  /**
3
85
  * Creates a module-level external store compatible with React `useSyncExternalStore`.
4
86
  *
@@ -9,6 +91,19 @@ export declare function createExternalStore<T>(initial: T): {
9
91
  getSnapshot: () => T;
10
92
  setState: (next: T) => void;
11
93
  };
94
+ /**
95
+ * Creates a storage-backed external store for sharing state across plugin webviews.
96
+ *
97
+ * Hydrates from storage asynchronously on creation. Synchronous {@link StorageStore.getSnapshot}
98
+ * may return `parse(undefined)` until hydration completes; use {@link StorageStore.useValue}
99
+ * or await {@link StorageStore.reloadFromStorage} when you need the persisted value before reading.
100
+ *
101
+ * Separate plugin webviews do not share memory; use {@link syncOnWindowFocus} to
102
+ * reload when another surface writes storage.
103
+ *
104
+ * @param options - Storage key, parse/equals helpers, and the plugin storage API.
105
+ */
106
+ export declare function createStorageStore<T>(options: CreateStorageStoreOptions<T>): StorageStore<T>;
12
107
  /**
13
108
  * Starts an interval and returns a disposable that clears it on deactivation.
14
109
  *
@@ -16,4 +111,15 @@ export declare function createExternalStore<T>(initial: T): {
16
111
  * @param intervalMs - Interval in milliseconds.
17
112
  */
18
113
  export declare function setIntervalDisposable(callback: () => void, intervalMs: number): Disposable;
114
+ /**
115
+ * Reloads one or more storage-backed stores when the window regains focus or
116
+ * becomes visible, with optional polling for live cross-webview updates.
117
+ *
118
+ * Push the returned disposable onto `hc.subscriptions`, or dispose it from a
119
+ * React effect cleanup.
120
+ *
121
+ * @param stores - One store or an array of stores to reload together.
122
+ * @param options - Optional polling interval in milliseconds.
123
+ */
124
+ export declare function syncOnWindowFocus(stores: StorageStore<unknown> | StorageStore<unknown>[], options?: SyncOnWindowFocusOptions): Disposable;
19
125
  //# sourceMappingURL=store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/runtime/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG;IAClD,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IAChD,WAAW,EAAE,MAAM,CAAC,CAAC;IACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;CAC7B,CAkBA;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,CAO1F"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/runtime/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAE5C;;;;;OAKG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAE5C;;OAEG;IACH,WAAW,IAAI,CAAC,CAAC;IAEjB;;OAEG;IACH,QAAQ,IAAI,CAAC,CAAC;IAEd;;OAEG;IACH,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC;;;;;OAKG;IACH,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB,CAAC,CAAC;IAC1C,4CAA4C;IAC5C,OAAO,EAAE,WAAW,CAAC;IAErB,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;;OAKG;IACH,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,CAAC,CAAC;IAE3B;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC;IAEjC;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG;IAClD,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IAChD,WAAW,EAAE,MAAM,CAAC,CAAC;IACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;CAC7B,CAkBA;AAYD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,EAAE,yBAAyB,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAkD5F;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,CAO1F;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,YAAY,CAAC,OAAO,CAAC,EAAE,EACvD,OAAO,CAAC,EAAE,wBAAwB,GACjC,UAAU,CA0BZ"}
@@ -1,3 +1,4 @@
1
+ import { useSyncExternalStore } from './react.js';
1
2
  /**
2
3
  * Creates a module-level external store compatible with React `useSyncExternalStore`.
3
4
  *
@@ -22,6 +23,73 @@ export function createExternalStore(initial) {
22
23
  }
23
24
  };
24
25
  }
26
+ /**
27
+ * Compares two JSON-serializable values using `JSON.stringify`.
28
+ *
29
+ * @param a - First value.
30
+ * @param b - Second value.
31
+ */
32
+ function defaultEquals(a, b) {
33
+ return JSON.stringify(a) === JSON.stringify(b);
34
+ }
35
+ /**
36
+ * Creates a storage-backed external store for sharing state across plugin webviews.
37
+ *
38
+ * Hydrates from storage asynchronously on creation. Synchronous {@link StorageStore.getSnapshot}
39
+ * may return `parse(undefined)` until hydration completes; use {@link StorageStore.useValue}
40
+ * or await {@link StorageStore.reloadFromStorage} when you need the persisted value before reading.
41
+ *
42
+ * Separate plugin webviews do not share memory; use {@link syncOnWindowFocus} to
43
+ * reload when another surface writes storage.
44
+ *
45
+ * @param options - Storage key, parse/equals helpers, and the plugin storage API.
46
+ */
47
+ export function createStorageStore(options) {
48
+ const { storage, key, parse, equals = defaultEquals, keepCurrentWhenMissing = false } = options;
49
+ const external = createExternalStore(parse(undefined));
50
+ /**
51
+ * Reloads from storage and updates subscribers when the parsed snapshot changed.
52
+ */
53
+ async function reloadFromStorage() {
54
+ const raw = await storage.get(key);
55
+ if (raw === undefined && keepCurrentWhenMissing) {
56
+ return;
57
+ }
58
+ const next = parse(raw);
59
+ const current = external.getSnapshot();
60
+ if (!equals(current, next)) {
61
+ external.setState(next);
62
+ }
63
+ }
64
+ /**
65
+ * Persists when the value changed, then updates the snapshot and notifies subscribers.
66
+ * Rejects when persistence fails; the snapshot is left unchanged.
67
+ *
68
+ * @param next - New snapshot value.
69
+ */
70
+ async function set(next) {
71
+ const current = external.getSnapshot();
72
+ if (equals(current, next)) {
73
+ return;
74
+ }
75
+ await storage.set(key, next);
76
+ external.setState(next);
77
+ }
78
+ /**
79
+ * React hook returning the current storage-backed snapshot.
80
+ */
81
+ function useValue() {
82
+ return useSyncExternalStore(external.subscribe, external.getSnapshot, external.getSnapshot);
83
+ }
84
+ void reloadFromStorage();
85
+ return {
86
+ subscribe: external.subscribe,
87
+ getSnapshot: external.getSnapshot,
88
+ useValue,
89
+ reloadFromStorage,
90
+ set
91
+ };
92
+ }
25
93
  /**
26
94
  * Starts an interval and returns a disposable that clears it on deactivation.
27
95
  *
@@ -36,3 +104,35 @@ export function setIntervalDisposable(callback, intervalMs) {
36
104
  }
37
105
  };
38
106
  }
107
+ /**
108
+ * Reloads one or more storage-backed stores when the window regains focus or
109
+ * becomes visible, with optional polling for live cross-webview updates.
110
+ *
111
+ * Push the returned disposable onto `hc.subscriptions`, or dispose it from a
112
+ * React effect cleanup.
113
+ *
114
+ * @param stores - One store or an array of stores to reload together.
115
+ * @param options - Optional polling interval in milliseconds.
116
+ */
117
+ export function syncOnWindowFocus(stores, options) {
118
+ const list = Array.isArray(stores) ? stores : [stores];
119
+ /**
120
+ * Reloads every registered store from plugin storage.
121
+ */
122
+ const reload = () => {
123
+ for (const store of list) {
124
+ void store.reloadFromStorage();
125
+ }
126
+ };
127
+ window.addEventListener('focus', reload);
128
+ document.addEventListener('visibilitychange', reload);
129
+ reload();
130
+ const intervalDisposable = options?.intervalMs !== undefined ? setIntervalDisposable(reload, options.intervalMs) : null;
131
+ return {
132
+ dispose: () => {
133
+ window.removeEventListener('focus', reload);
134
+ document.removeEventListener('visibilitychange', reload);
135
+ intervalDisposable?.dispose();
136
+ }
137
+ };
138
+ }
@@ -1,4 +1,100 @@
1
1
  import type { Disposable } from '../types.js';
2
+ import { useSyncExternalStore } from './react.js';
3
+
4
+ /**
5
+ * Minimal storage surface used by {@link createStorageStore}.
6
+ */
7
+ export interface StorageLike {
8
+ /**
9
+ * Returns the stored value for a key.
10
+ *
11
+ * @param key - Storage key within the plugin namespace.
12
+ */
13
+ get<T>(key: string): Promise<T | undefined>;
14
+
15
+ /**
16
+ * Persists a JSON-serializable value.
17
+ *
18
+ * @param key - Storage key within the plugin namespace.
19
+ * @param value - Value to persist.
20
+ */
21
+ set<T>(key: string, value: T): Promise<void>;
22
+ }
23
+
24
+ /**
25
+ * Reactive store backed by plugin storage and compatible with React
26
+ * `useSyncExternalStore`.
27
+ */
28
+ export interface StorageStore<T> {
29
+ /**
30
+ * Subscribes to snapshot changes for `useSyncExternalStore`.
31
+ *
32
+ * @param listener - Callback invoked when the snapshot changes.
33
+ */
34
+ subscribe(listener: () => void): () => void;
35
+
36
+ /**
37
+ * Returns the current in-memory snapshot.
38
+ */
39
+ getSnapshot(): T;
40
+
41
+ /**
42
+ * React hook returning the current snapshot.
43
+ */
44
+ useValue(): T;
45
+
46
+ /**
47
+ * Reloads from storage and notifies subscribers when the parsed value changed.
48
+ */
49
+ reloadFromStorage(): Promise<void>;
50
+
51
+ /**
52
+ * Persists to storage, then updates the in-memory snapshot and notifies subscribers.
53
+ * Rejects when persistence fails; the snapshot is left unchanged.
54
+ *
55
+ * @param next - New snapshot value.
56
+ */
57
+ set(next: T): Promise<void>;
58
+ }
59
+
60
+ /**
61
+ * Options for {@link createStorageStore}.
62
+ */
63
+ export interface CreateStorageStoreOptions<T> {
64
+ /** Plugin storage API from `hc.storage`. */
65
+ storage: StorageLike;
66
+
67
+ /** Storage key within the plugin namespace. */
68
+ key: string;
69
+
70
+ /**
71
+ * Validates and hydrates a raw storage value into a typed snapshot.
72
+ * Called with `undefined` when the key has never been set.
73
+ *
74
+ * @param raw - Raw value from storage, or `undefined` when absent.
75
+ */
76
+ parse: (raw: unknown) => T;
77
+
78
+ /**
79
+ * Compares two snapshots to skip no-op reloads and writes.
80
+ * Defaults to `JSON.stringify` equality.
81
+ */
82
+ equals?: (a: T, b: T) => boolean;
83
+
84
+ /**
85
+ * When true, {@link StorageStore.reloadFromStorage} leaves the snapshot
86
+ * unchanged if the storage key is absent. Default false (apply `parse(undefined)`).
87
+ */
88
+ keepCurrentWhenMissing?: boolean;
89
+ }
90
+
91
+ /**
92
+ * Options for {@link syncOnWindowFocus}.
93
+ */
94
+ export interface SyncOnWindowFocusOptions {
95
+ /** Optional polling interval in milliseconds in addition to focus/visibility reloads. */
96
+ intervalMs?: number;
97
+ }
2
98
 
3
99
  /**
4
100
  * Creates a module-level external store compatible with React `useSyncExternalStore`.
@@ -29,6 +125,80 @@ export function createExternalStore<T>(initial: T): {
29
125
  };
30
126
  }
31
127
 
128
+ /**
129
+ * Compares two JSON-serializable values using `JSON.stringify`.
130
+ *
131
+ * @param a - First value.
132
+ * @param b - Second value.
133
+ */
134
+ function defaultEquals<T>(a: T, b: T): boolean {
135
+ return JSON.stringify(a) === JSON.stringify(b);
136
+ }
137
+
138
+ /**
139
+ * Creates a storage-backed external store for sharing state across plugin webviews.
140
+ *
141
+ * Hydrates from storage asynchronously on creation. Synchronous {@link StorageStore.getSnapshot}
142
+ * may return `parse(undefined)` until hydration completes; use {@link StorageStore.useValue}
143
+ * or await {@link StorageStore.reloadFromStorage} when you need the persisted value before reading.
144
+ *
145
+ * Separate plugin webviews do not share memory; use {@link syncOnWindowFocus} to
146
+ * reload when another surface writes storage.
147
+ *
148
+ * @param options - Storage key, parse/equals helpers, and the plugin storage API.
149
+ */
150
+ export function createStorageStore<T>(options: CreateStorageStoreOptions<T>): StorageStore<T> {
151
+ const { storage, key, parse, equals = defaultEquals, keepCurrentWhenMissing = false } = options;
152
+ const external = createExternalStore(parse(undefined));
153
+
154
+ /**
155
+ * Reloads from storage and updates subscribers when the parsed snapshot changed.
156
+ */
157
+ async function reloadFromStorage(): Promise<void> {
158
+ const raw = await storage.get(key);
159
+ if (raw === undefined && keepCurrentWhenMissing) {
160
+ return;
161
+ }
162
+ const next = parse(raw);
163
+ const current = external.getSnapshot();
164
+ if (!equals(current, next)) {
165
+ external.setState(next);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Persists when the value changed, then updates the snapshot and notifies subscribers.
171
+ * Rejects when persistence fails; the snapshot is left unchanged.
172
+ *
173
+ * @param next - New snapshot value.
174
+ */
175
+ async function set(next: T): Promise<void> {
176
+ const current = external.getSnapshot();
177
+ if (equals(current, next)) {
178
+ return;
179
+ }
180
+ await storage.set(key, next);
181
+ external.setState(next);
182
+ }
183
+
184
+ /**
185
+ * React hook returning the current storage-backed snapshot.
186
+ */
187
+ function useValue(): T {
188
+ return useSyncExternalStore(external.subscribe, external.getSnapshot, external.getSnapshot);
189
+ }
190
+
191
+ void reloadFromStorage();
192
+
193
+ return {
194
+ subscribe: external.subscribe,
195
+ getSnapshot: external.getSnapshot,
196
+ useValue,
197
+ reloadFromStorage,
198
+ set
199
+ };
200
+ }
201
+
32
202
  /**
33
203
  * Starts an interval and returns a disposable that clears it on deactivation.
34
204
  *
@@ -43,3 +213,44 @@ export function setIntervalDisposable(callback: () => void, intervalMs: number):
43
213
  }
44
214
  };
45
215
  }
216
+
217
+ /**
218
+ * Reloads one or more storage-backed stores when the window regains focus or
219
+ * becomes visible, with optional polling for live cross-webview updates.
220
+ *
221
+ * Push the returned disposable onto `hc.subscriptions`, or dispose it from a
222
+ * React effect cleanup.
223
+ *
224
+ * @param stores - One store or an array of stores to reload together.
225
+ * @param options - Optional polling interval in milliseconds.
226
+ */
227
+ export function syncOnWindowFocus(
228
+ stores: StorageStore<unknown> | StorageStore<unknown>[],
229
+ options?: SyncOnWindowFocusOptions
230
+ ): Disposable {
231
+ const list = Array.isArray(stores) ? stores : [stores];
232
+
233
+ /**
234
+ * Reloads every registered store from plugin storage.
235
+ */
236
+ const reload = (): void => {
237
+ for (const store of list) {
238
+ void store.reloadFromStorage();
239
+ }
240
+ };
241
+
242
+ window.addEventListener('focus', reload);
243
+ document.addEventListener('visibilitychange', reload);
244
+ reload();
245
+
246
+ const intervalDisposable =
247
+ options?.intervalMs !== undefined ? setIntervalDisposable(reload, options.intervalMs) : null;
248
+
249
+ return {
250
+ dispose: () => {
251
+ window.removeEventListener('focus', reload);
252
+ document.removeEventListener('visibilitychange', reload);
253
+ intervalDisposable?.dispose();
254
+ }
255
+ };
256
+ }
@@ -7,7 +7,7 @@ import {
7
7
  parseViewHostRole,
8
8
  resolveContributionKindFromUrl
9
9
  } from './createBridgedPluginContext.js';
10
- import { setHostReact } from './reactHost.js';
10
+ import { setHostReact, setHostReactDom } from './reactHost.js';
11
11
 
12
12
  /**
13
13
  * Bootstraps an isolated plugin webview shell.
@@ -47,6 +47,7 @@ export async function bootstrapViewHost(options = {}) {
47
47
  ]);
48
48
 
49
49
  setHostReact(React);
50
+ setHostReactDom(ReactDOM);
50
51
 
51
52
  const manifest = await fetch(`harbor-plugin://${pluginId}/manifest.json`).then((response) =>
52
53
  response.json()
@@ -33,4 +33,63 @@ export declare function truncateBody(body: string, maxBytes: number, suffix?: st
33
33
  body: string;
34
34
  truncated: boolean;
35
35
  };
36
+ /**
37
+ * Minimum log level emitted by {@link createLogger}.
38
+ *
39
+ * Messages below the active level are suppressed. `silent` suppresses all output.
40
+ */
41
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
42
+ /**
43
+ * Plugin-scoped logger with consistent `[pluginId]` prefixing and level control.
44
+ *
45
+ * Safe in the SES plugin main runtime where only `console`, `Date`, and `Math`
46
+ * globals are available.
47
+ */
48
+ export interface Logger {
49
+ /**
50
+ * Logs a debug message when the active level is `debug`.
51
+ *
52
+ * @param args - Values forwarded to the console after the prefix.
53
+ */
54
+ debug(...args: unknown[]): void;
55
+ /**
56
+ * Logs an informational message when the active level is `debug` or `info`.
57
+ *
58
+ * @param args - Values forwarded to the console after the prefix.
59
+ */
60
+ info(...args: unknown[]): void;
61
+ /**
62
+ * Logs a warning when the active level is below `error`.
63
+ *
64
+ * @param args - Values forwarded to the console after the prefix.
65
+ */
66
+ warn(...args: unknown[]): void;
67
+ /**
68
+ * Logs an error when the active level is not `silent`.
69
+ *
70
+ * @param args - Values forwarded to the console after the prefix.
71
+ */
72
+ error(...args: unknown[]): void;
73
+ /**
74
+ * Changes the minimum log level for subsequent calls.
75
+ *
76
+ * @param level - New minimum level.
77
+ */
78
+ setLevel(level: LogLevel): void;
79
+ }
80
+ /**
81
+ * Creates a plugin-scoped logger that prefixes every message with `[pluginId]`.
82
+ *
83
+ * Routes through `console` only so the logger works in both the renderer and
84
+ * the SES-hardened main runtime. When `console.warn` or `console.error` are
85
+ * unavailable, falls back to `console.log`.
86
+ *
87
+ * @param pluginId - Manifest id used as the log prefix.
88
+ * @param options - Optional initial configuration.
89
+ * @param options.level - Minimum level to emit. Defaults to `info`.
90
+ * @returns Logger with level-filtered `debug`, `info`, `warn`, and `error` methods.
91
+ */
92
+ export declare function createLogger(pluginId: string, options?: {
93
+ level?: LogLevel;
94
+ }): Logger;
36
95
  //# sourceMappingURL=runtime-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-utils.d.ts","sourceRoot":"","sources":["../src/runtime-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,MAAM,SAAO,GAAG,MAAM,CAK9C;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAmBhD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CA+BvE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,SAAsD,GAC3D;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAQtC"}
1
+ {"version":3,"file":"runtime-utils.d.ts","sourceRoot":"","sources":["../src/runtime-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,MAAM,SAAO,GAAG,MAAM,CAK9C;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAmBhD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CA+BvE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,SAAsD,GAC3D;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAQtC;AAED;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB;;;;OAIG;IACH,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAEhC;;;;OAIG;IACH,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAE/B;;;;OAIG;IACH,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAE/B;;;;OAIG;IACH,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAEhC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;CACjC;AAUD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,QAAQ,CAAA;CAAE,GAAG,MAAM,CAuDrF"}
@@ -99,3 +99,69 @@ export function truncateBody(body, maxBytes, suffix = `\n\n[truncated — body e
99
99
  truncated: true
100
100
  };
101
101
  }
102
+ const LOG_LEVEL_RANK = {
103
+ debug: 0,
104
+ info: 1,
105
+ warn: 2,
106
+ error: 3,
107
+ silent: 4
108
+ };
109
+ /**
110
+ * Creates a plugin-scoped logger that prefixes every message with `[pluginId]`.
111
+ *
112
+ * Routes through `console` only so the logger works in both the renderer and
113
+ * the SES-hardened main runtime. When `console.warn` or `console.error` are
114
+ * unavailable, falls back to `console.log`.
115
+ *
116
+ * @param pluginId - Manifest id used as the log prefix.
117
+ * @param options - Optional initial configuration.
118
+ * @param options.level - Minimum level to emit. Defaults to `info`.
119
+ * @returns Logger with level-filtered `debug`, `info`, `warn`, and `error` methods.
120
+ */
121
+ export function createLogger(pluginId, options) {
122
+ const prefix = `[${pluginId}]`;
123
+ let level = options?.level ?? 'info';
124
+ /**
125
+ * Emits a message when its severity meets the active level threshold.
126
+ *
127
+ * @param messageLevel - Severity of the message being logged.
128
+ * @param write - Console method to invoke when the message is not suppressed.
129
+ * @param args - Values forwarded after the prefix.
130
+ */
131
+ function log(messageLevel, write, ...args) {
132
+ if (LOG_LEVEL_RANK[messageLevel] < LOG_LEVEL_RANK[level]) {
133
+ return;
134
+ }
135
+ write(prefix, ...args);
136
+ }
137
+ const consoleLog = (...values) => {
138
+ console.log(...values);
139
+ };
140
+ return {
141
+ debug(...args) {
142
+ log('debug', consoleLog, ...args);
143
+ },
144
+ info(...args) {
145
+ log('info', consoleLog, ...args);
146
+ },
147
+ warn(...args) {
148
+ const write = typeof console.warn === 'function'
149
+ ? (...values) => {
150
+ console.warn(...values);
151
+ }
152
+ : consoleLog;
153
+ log('warn', write, ...args);
154
+ },
155
+ error(...args) {
156
+ const write = typeof console.error === 'function'
157
+ ? (...values) => {
158
+ console.error(...values);
159
+ }
160
+ : consoleLog;
161
+ log('error', write, ...args);
162
+ },
163
+ setLevel(nextLevel) {
164
+ level = nextLevel;
165
+ }
166
+ };
167
+ }
@@ -1,2 +1,3 @@
1
1
  export { createCappedList, mergeById, type CreateCappedListOptions, type MergeByIdOptions } from './cappedList.js';
2
+ export { arrayOf, asRecord, bool, isRecord, num, numArray, oneOf, recordOf, str, strArray } from './validate.js';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,OAAO,EACP,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,GAAG,EACH,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,GAAG,EACH,QAAQ,EACT,MAAM,eAAe,CAAC"}
@@ -1 +1,2 @@
1
1
  export { createCappedList, mergeById } from './cappedList.js';
2
+ export { arrayOf, asRecord, bool, isRecord, num, numArray, oneOf, recordOf, str, strArray } from './validate.js';