wenay-common2 1.0.61 → 1.0.63

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 (65) hide show
  1. package/README.md +8 -8
  2. package/lib/Common/Observe/index.d.ts +5 -0
  3. package/lib/Common/{ObserveAll2 → Observe}/index.js +3 -1
  4. package/lib/Common/Observe/reactive.d.ts +21 -0
  5. package/lib/Common/{ObserveAll2/reactive2.js → Observe/reactive.js} +7 -7
  6. package/lib/Common/Observe/reactive2.d.ts +1 -0
  7. package/lib/Common/{events/Listen2.js → Observe/reactive2.js} +1 -1
  8. package/lib/Common/Observe/store-manager.d.ts +123 -0
  9. package/lib/Common/Observe/store-manager.js +227 -0
  10. package/lib/Common/Observe/store-offline.d.ts +88 -0
  11. package/lib/Common/Observe/store-offline.js +278 -0
  12. package/lib/Common/{ObserveAll2 → Observe}/store-replay.d.ts +9 -38
  13. package/lib/Common/{ObserveAll2 → Observe}/store-replay.js +1 -1
  14. package/lib/Common/{ObserveAll2 → Observe}/store.d.ts +3 -3
  15. package/lib/Common/{ObserveAll2 → Observe}/store.js +25 -31
  16. package/lib/Common/async/promiseProgress.d.ts +12 -0
  17. package/lib/Common/async/promiseProgress.js +29 -0
  18. package/lib/Common/events/Listen.d.ts +129 -1
  19. package/lib/Common/events/Listen.js +243 -15
  20. package/lib/Common/events/Listen3.d.ts +1 -210
  21. package/lib/Common/events/Listen3.js +15 -253
  22. package/lib/Common/events/SocketBuffer.d.ts +13 -33
  23. package/lib/Common/events/SocketBuffer.js +8 -8
  24. package/lib/Common/events/SocketServerHook.d.ts +5 -45
  25. package/lib/Common/events/SocketServerHook.js +1 -1
  26. package/lib/Common/events/joinListens.d.ts +6 -7
  27. package/lib/Common/events/joinListens.js +3 -4
  28. package/lib/Common/events/mapListen.d.ts +4 -0
  29. package/lib/Common/events/mapListen.js +25 -0
  30. package/lib/Common/events/replay-conflate.d.ts +2 -22
  31. package/lib/Common/events/replay-conflate.js +5 -5
  32. package/lib/Common/events/replay-history.d.ts +1 -1
  33. package/lib/Common/events/replay-listen.d.ts +20 -66
  34. package/lib/Common/events/replay-listen.js +10 -13
  35. package/lib/Common/events/replay-wire.d.ts +1 -1
  36. package/lib/Common/rcp/createRpcServerAutoWithProtocolDetection.d.ts +3 -3
  37. package/lib/Common/rcp/createRpcServerAutoWithProtocolDetection.js +10 -10
  38. package/lib/Common/rcp/listen-deep.d.ts +10 -10
  39. package/lib/Common/rcp/listen-deep.js +1 -1
  40. package/lib/Common/rcp/listen-socket.d.ts +13 -13
  41. package/lib/Common/rcp/listen-socket.js +4 -3
  42. package/lib/Common/rcp/rpc-client-auto.d.ts +2 -2
  43. package/lib/Common/rcp/rpc-client-auto.js +3 -2
  44. package/lib/Common/rcp/rpc-client.js +6 -4
  45. package/lib/Common/rcp/rpc-index.d.ts +2 -1
  46. package/lib/Common/rcp/rpc-index.js +3 -2
  47. package/lib/Common/rcp/rpc-path.d.ts +1 -0
  48. package/lib/Common/rcp/rpc-path.js +6 -0
  49. package/lib/Common/rcp/rpc-server-auto.d.ts +2 -2
  50. package/lib/Common/rcp/rpc-server-auto.js +7 -11
  51. package/lib/Common/rcp/rpc-server.js +11 -7
  52. package/lib/client.d.ts +1 -1
  53. package/lib/client.js +1 -1
  54. package/lib/index.d.ts +3 -4
  55. package/lib/index.js +4 -5
  56. package/package.json +5 -3
  57. package/lib/Common/ObserveAll2/index.d.ts +0 -3
  58. package/lib/Common/ObserveAll2/reactive2.d.ts +0 -61
  59. package/lib/Common/async/PromiseArrayListen.d.ts +0 -14
  60. package/lib/Common/async/PromiseArrayListen.js +0 -37
  61. package/lib/Common/events/Listen2.d.ts +0 -1
  62. package/lib/Common/events/UseListenTransform.d.ts +0 -24
  63. package/lib/Common/events/UseListenTransform.js +0 -28
  64. package/lib/Common/rcp/test.d.ts +0 -1
  65. package/lib/Common/rcp/test.js +0 -79
package/README.md CHANGED
@@ -18,13 +18,13 @@ A set of common utilities and components for TypeScript/Node.js projects.
18
18
 
19
19
  ### ⏱️ **async/** — Async Utilities
20
20
  - **`waitRun.ts`** — throttle/debounce (`enhancedWaitRun`), async queues (`createAsyncQueue`, `queueRun`), task queue with readiness control (`createTaskQueue`)
21
- - **`PromiseArrayListen.ts`** — processing an array of promises with success/error subscriptions
21
+ - **`promiseProgress.ts`** — processing an array of promises with success/error subscriptions
22
22
  - **`createIterableObject.ts`** — proxy for iterable objects (readonly/read-write)
23
23
 
24
24
  ### 🔔 **events/** — Events and Subscriptions
25
- - **`Listen.ts`** — subscription system (`UseListen`, `funcListenCallback`), `isListenCallback` check
25
+ - **`Listen.ts`** — subscription system (`listen`, `createListen`), `isListenCallback` check
26
26
  - **`event.ts`** — event handler collections (`CObjectEventsArr`, `CObjectEventsList`)
27
- - **`SocketBuffer.ts`** — socket callback buffering (`socketBuffer3`, `funcListenCallbackSnapshot`)
27
+ - **`SocketBuffer.ts`** — socket callback buffering (`socketBuffer`, `listenSnapshot`)
28
28
  - **`SocketServerHook.ts`** — tag-based subscription wrappers for socket communication (`SocketServerHook`, `WebSocketServerHook`)
29
29
  - **`joinListens.ts`** — merging multiple subscription streams into one (`joinListens`)
30
30
  - **`listen-socket.ts`** — bridge between event system and RPC (`listenSocket`, `listenSocketFirst`, `listenSocketAll`, `listenSocketSmart`)
@@ -92,11 +92,11 @@ Bidirectional, strongly-typed RPC protocol over sockets (Socket.IO or similar).
92
92
 
93
93
  ### 2.1 Socket Connection
94
94
  ```typescript
95
- import { createRpcServerAuto, UseListen } from "wenay-common2";
95
+ import { createRpcServerAuto, listen } from "wenay-common2";
96
96
 
97
97
  io.sockets.on('connection', (socket) => {
98
98
  // 1. Create unsubscribe trigger for memory cleanup
99
- const [stop, listenStop] = UseListen<[]>();
99
+ const [stop, listenStop] = listen<[]>();
100
100
  socket.on('disconnect', stop);
101
101
 
102
102
  // 2. Initialize RPC channel on this socket
@@ -113,10 +113,10 @@ io.sockets.on('connection', (socket) => {
113
113
  ### 2.2 Building API Object (Facade)
114
114
  The object is traversed by the server to build a "Schema" that is sent to the client.
115
115
  ```typescript
116
- import { noStrict, UseListen } from "wenay-common2";
116
+ import { noStrict, listen } from "wenay-common2";
117
117
 
118
118
  // Create pub/sub event system
119
- const [sendEvent, listenEvent] = UseListen<[string]>();
119
+ const [sendEvent, listenEvent] = listen<[string]>();
120
120
 
121
121
  export function buildFacade(client) {
122
122
  const role = (...roles) => hasRole(client, roles) ? true : null;
@@ -233,7 +233,7 @@ api.space.admin.logAction("clicked");
233
233
  ```
234
234
 
235
235
  ### 3.4 Client Subscriptions (Listen)
236
- `createRpcServerAuto` exposes server `UseListen` values as RPC Listen nodes. New code uses `on`/`once` and keeps the returned `off` handle. For TypeScript, project `client.func` to `DeepSocketListen<ServerFacade>`; this mirrors the runtime shape and keeps event argument types.
236
+ `createRpcServerAuto` exposes server `listen` values as RPC Listen nodes. New code uses `on`/`once` and keeps the returned `off` handle. For TypeScript, project `client.func` to `DeepSocketListen<ServerFacade>`; this mirrors the runtime shape and keeps event argument types.
237
237
  ```typescript
238
238
  import type { DeepSocketListen } from "wenay-common2";
239
239
 
@@ -0,0 +1,5 @@
1
+ export * from "./reactive";
2
+ export * from "./store";
3
+ export * from "./store-replay";
4
+ export * from "./store-offline";
5
+ export * from "./store-manager";
@@ -14,6 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./reactive2"), exports);
17
+ __exportStar(require("./reactive"), exports);
18
18
  __exportStar(require("./store"), exports);
19
19
  __exportStar(require("./store-replay"), exports);
20
+ __exportStar(require("./store-offline"), exports);
21
+ __exportStar(require("./store-manager"), exports);
@@ -0,0 +1,21 @@
1
+ type Fn = () => void;
2
+ export type ReactiveChange = {
3
+ paths: PropertyKey[][];
4
+ };
5
+ type PathUpdateFn = (change: ReactiveChange) => void;
6
+ type Drain = 'micro' | 'immediate' | number | ((flush: Fn) => void);
7
+ export type Opts = {
8
+ drain?: Drain;
9
+ depth?: number;
10
+ eager?: boolean;
11
+ };
12
+ export declare function reactive<T extends object>(root: T, opts?: Opts): T;
13
+ export declare function isReactive(p: any): boolean;
14
+ export declare function toRaw<T>(p: T): T;
15
+ export declare function onUpdate(p: any, cb: Fn): () => void;
16
+ export declare function onUpdatePaths(p: any, cb: PathUpdateFn): () => void;
17
+ export declare function flushReactive(p: any): Promise<void>;
18
+ export declare function listenUpdate(p: any): import("../events/Listen").ListenApi<[]>;
19
+ export declare function listenUpdatePaths(p: any): import("../events/Listen").ListenApi<[ReactiveChange]>;
20
+ export type Reactive<T extends object> = T;
21
+ export {};
@@ -9,7 +9,7 @@ exports.flushReactive = flushReactive;
9
9
  exports.listenUpdate = listenUpdate;
10
10
  exports.listenUpdatePaths = listenUpdatePaths;
11
11
  const Listen_1 = require("../events/Listen");
12
- const NODE = Symbol('reactive2.node');
12
+ const NODE = Symbol('reactive.node');
13
13
  const isObj = (v) => v != null && typeof v == 'object';
14
14
  const isReactiveObj = (v) => {
15
15
  if (!isObj(v))
@@ -351,22 +351,22 @@ function flushReactive(p) {
351
351
  return new Promise(resolve => { eng.waiters.add(resolve); });
352
352
  }
353
353
  function listenUpdate(p) {
354
- const listen = (0, Listen_1.funcListenCallbackBase)((emit) => onUpdate(p, () => emit()), {
354
+ const listen = (0, Listen_1.createListen)((emit) => onUpdate(p, () => emit()), {
355
355
  event: (type, count, api) => {
356
- if (type == "add" && count == 1 && !api.isRun())
356
+ if (type == "add" && count == 1 && !api.isRunning())
357
357
  api.run();
358
- if (type == "remove" && count == 0 && api.isRun())
358
+ if (type == "remove" && count == 0 && api.isRunning())
359
359
  api.close();
360
360
  },
361
361
  });
362
362
  return listen;
363
363
  }
364
364
  function listenUpdatePaths(p) {
365
- const listen = (0, Listen_1.funcListenCallbackBase)((emit) => onUpdatePaths(p, change => emit(change)), {
365
+ const listen = (0, Listen_1.createListen)((emit) => onUpdatePaths(p, change => emit(change)), {
366
366
  event: (type, count, api) => {
367
- if (type == "add" && count == 1 && !api.isRun())
367
+ if (type == "add" && count == 1 && !api.isRunning())
368
368
  api.run();
369
- if (type == "remove" && count == 0 && api.isRun())
369
+ if (type == "remove" && count == 0 && api.isRunning())
370
370
  api.close();
371
371
  },
372
372
  });
@@ -0,0 +1 @@
1
+ export * from './reactive';
@@ -14,4 +14,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./Listen3"), exports);
17
+ __exportStar(require("./reactive"), exports);
@@ -0,0 +1,123 @@
1
+ import { ReplayRemote, ReplaySubscribeOpts } from '../events/replay-wire';
2
+ import { createStore, Store, StoreMask, StorePatch, StoreSyncOpts } from './store';
3
+ import { OfflineStorage, OfflineStore } from './store-offline';
4
+ type RemoteStore<T extends object> = {
5
+ get(mask?: any): T | Promise<T>;
6
+ changed: any;
7
+ changedPaths?: any;
8
+ patches?: any;
9
+ changedData?: any;
10
+ };
11
+ export type StoreMirror<T extends object> = Store<T> & {
12
+ sync<M extends StoreMask<T>>(mask: M, opts?: StoreSyncOpts): Promise<() => void>;
13
+ syncPatches<M extends StoreMask<T>>(mask: M, opts?: StoreSyncOpts): Promise<() => void>;
14
+ syncChangedData<M extends StoreMask<T>>(mask: M, opts?: StoreSyncOpts): Promise<() => void>;
15
+ };
16
+ export type ManagedStoreKind = 'mirror' | 'replay' | 'offline';
17
+ export type ManagedStoreState = 'idle' | 'starting' | 'ready' | 'stopped' | 'error';
18
+ export type ManagedStoreSyncMode = 'pull' | 'patches' | 'changedData';
19
+ export type ManagedStoreUsage = {
20
+ count: number;
21
+ weight: number;
22
+ lastUsedAt?: number;
23
+ };
24
+ export type ManagedStorePriorityContext = {
25
+ key: string;
26
+ now: number;
27
+ usage?: ManagedStoreUsage;
28
+ };
29
+ type CommonResource<T extends object> = {
30
+ key?: string;
31
+ initial: T;
32
+ tags?: readonly string[];
33
+ priority?: number | ((ctx: ManagedStorePriorityContext) => number);
34
+ usageKey?: string;
35
+ explicitOnly?: boolean;
36
+ large?: boolean;
37
+ autoStart?: boolean;
38
+ storeOpts?: Parameters<typeof createStore<T>>[1];
39
+ };
40
+ export type ManagedMirrorResource<T extends object> = CommonResource<T> & {
41
+ kind?: 'mirror';
42
+ remote: RemoteStore<T>;
43
+ mask: StoreMask<T>;
44
+ sync?: {
45
+ mode?: ManagedStoreSyncMode;
46
+ opts?: StoreSyncOpts;
47
+ };
48
+ };
49
+ export type ManagedReplayResource<T extends object> = CommonResource<T> & {
50
+ kind?: 'replay';
51
+ remote: ReplayRemote<[StorePatch]>;
52
+ syncOpts?: ReplaySubscribeOpts;
53
+ };
54
+ export type ManagedOfflineResource<T extends object> = CommonResource<T> & {
55
+ kind?: 'offline';
56
+ remote?: ReplayRemote<[StorePatch]>;
57
+ storage: OfflineStorage;
58
+ storageKey?: string;
59
+ version?: number;
60
+ debounceMs?: number;
61
+ syncOpts?: ReplaySubscribeOpts;
62
+ migrate?: (oldSnapshot: unknown, fromVersion: number, toVersion: number) => T | Promise<T>;
63
+ };
64
+ export type ManagedStoreResource<T extends object = any> = ManagedMirrorResource<T> | ManagedReplayResource<T> | ManagedOfflineResource<T>;
65
+ export type ManagedStoreResources = Record<string, ManagedStoreResource<any>>;
66
+ export type ManagedStoreOf<R> = R extends ManagedOfflineResource<infer T> ? OfflineStore<T> : R extends ManagedReplayResource<infer T> ? Store<T> : R extends ManagedMirrorResource<infer T> ? StoreMirror<T> : never;
67
+ export type ManagedStoreStatus = {
68
+ key: string;
69
+ state: ManagedStoreState;
70
+ kind: ManagedStoreKind;
71
+ error?: unknown;
72
+ startedAt?: number;
73
+ stoppedAt?: number;
74
+ };
75
+ export type ManagedStorePlanItem = {
76
+ key: string;
77
+ kind: ManagedStoreKind;
78
+ score: number;
79
+ state: ManagedStoreState;
80
+ large: boolean;
81
+ explicitOnly: boolean;
82
+ tags: readonly string[];
83
+ };
84
+ export type ManagedStoreStartOpts = {
85
+ explicit?: boolean;
86
+ reason?: string;
87
+ };
88
+ export type ManagedStorePlanOpts = {
89
+ keys?: Iterable<string>;
90
+ tags?: readonly string[];
91
+ includeLarge?: boolean;
92
+ includeExplicit?: boolean;
93
+ limit?: number;
94
+ now?: number;
95
+ };
96
+ export type ManagedStoreHandle<TStore> = {
97
+ readonly key: string;
98
+ readonly kind: ManagedStoreKind;
99
+ start(opts?: ManagedStoreStartOpts): Promise<TStore>;
100
+ stop(): void;
101
+ get(): TStore | undefined;
102
+ status(): ManagedStoreStatus;
103
+ touch(weight?: number): void;
104
+ };
105
+ export declare const managedStore: {
106
+ mirror<T extends object>(resource: ManagedMirrorResource<T>): ManagedMirrorResource<T>;
107
+ replay<T extends object>(resource: ManagedReplayResource<T>): ManagedReplayResource<T>;
108
+ offline<T extends object>(resource: ManagedOfflineResource<T>): ManagedOfflineResource<T>;
109
+ };
110
+ export declare function createStoreManager<const R extends ManagedStoreResources>(resources: R): {
111
+ handles: { [K in keyof R]: ManagedStoreHandle<ManagedStoreOf<R[K]>>; };
112
+ statusListen: import("../events/Listen").ListenApi<[ManagedStoreStatus]>;
113
+ plan: (opts?: ManagedStorePlanOpts) => ManagedStorePlanItem[];
114
+ start: <K extends keyof R & string>(key: K, opts?: ManagedStoreStartOpts) => Promise<ManagedStoreOf<R[K]>>;
115
+ startMany: (keys: Iterable<keyof R & string>, opts?: ManagedStoreStartOpts) => Promise<Partial<{ [K in keyof R]: ManagedStoreOf<R[K]>; }>>;
116
+ startPlanned: (opts?: ManagedStorePlanOpts & ManagedStoreStartOpts) => Promise<Partial<{ [K in keyof R]: ManagedStoreOf<R[K]>; }>>;
117
+ stop: <K extends keyof R & string>(key: K) => void;
118
+ stopAll: () => void;
119
+ get: <K extends keyof R & string>(key: K) => ManagedStoreOf<R[K]> | undefined;
120
+ touch: <K extends keyof R & string>(key: K, weight?: number) => void;
121
+ usage: () => Map<string, ManagedStoreUsage>;
122
+ };
123
+ export {};
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.managedStore = void 0;
4
+ exports.createStoreManager = createStoreManager;
5
+ const Listen_1 = require("../events/Listen");
6
+ const store_1 = require("./store");
7
+ const store_offline_1 = require("./store-offline");
8
+ const store_replay_1 = require("./store-replay");
9
+ function kindOf(resource) {
10
+ return resource.kind ?? 'mirror';
11
+ }
12
+ function hasAnyTag(resourceTags, tags) {
13
+ if (!tags?.length)
14
+ return true;
15
+ if (!resourceTags?.length)
16
+ return false;
17
+ return tags.some(tag => resourceTags.includes(tag));
18
+ }
19
+ function resourceScore(key, resource, usage, now) {
20
+ const base = typeof resource.priority == 'function'
21
+ ? resource.priority({ key, usage, now })
22
+ : resource.priority ?? 0;
23
+ const usageScore = usage ? usage.weight + Math.min(usage.count, 50) : 0;
24
+ const recency = usage?.lastUsedAt ? Math.max(0, 1_000_000 - (now - usage.lastUsedAt)) / 1_000_000 : 0;
25
+ return base + usageScore + recency;
26
+ }
27
+ exports.managedStore = {
28
+ mirror(resource) {
29
+ return { ...resource, kind: 'mirror' };
30
+ },
31
+ replay(resource) {
32
+ return { ...resource, kind: 'replay' };
33
+ },
34
+ offline(resource) {
35
+ return { ...resource, kind: 'offline' };
36
+ },
37
+ };
38
+ function createStoreManager(resources) {
39
+ const usages = new Map();
40
+ const [emitStatus, statusListen] = (0, Listen_1.listen)();
41
+ function usageFor(key, resource) {
42
+ const usageKey = resource.usageKey ?? key;
43
+ let usage = usages.get(usageKey);
44
+ if (!usage) {
45
+ usage = { count: 0, weight: 0 };
46
+ usages.set(usageKey, usage);
47
+ }
48
+ return usage;
49
+ }
50
+ function createHandle(key, resource) {
51
+ const kind = kindOf(resource);
52
+ let state = 'idle';
53
+ let store;
54
+ let stopSync;
55
+ let pending;
56
+ let error;
57
+ let startedAt;
58
+ let stoppedAt;
59
+ function setState(next, nextError) {
60
+ state = next;
61
+ error = nextError;
62
+ if (next == 'ready')
63
+ startedAt = Date.now();
64
+ if (next == 'stopped')
65
+ stoppedAt = Date.now();
66
+ emitStatus(status());
67
+ }
68
+ function status() {
69
+ return { key, state, kind, error, startedAt, stoppedAt };
70
+ }
71
+ async function start(opts = {}) {
72
+ if (resource.explicitOnly && !opts.explicit) {
73
+ throw new Error(`store manager: ${key} is explicitOnly`);
74
+ }
75
+ if (state == 'ready' && store)
76
+ return store;
77
+ if (pending)
78
+ return pending;
79
+ pending = (async () => {
80
+ setState('starting');
81
+ try {
82
+ if (kind == 'mirror') {
83
+ const r = resource;
84
+ store ??= (0, store_1.createStoreMirror)(r.remote, r.initial, r.storeOpts);
85
+ const mode = r.sync?.mode ?? 'pull';
86
+ const opts = r.sync?.opts;
87
+ stopSync = mode == 'patches'
88
+ ? await store.syncPatches(r.mask, opts)
89
+ : mode == 'changedData'
90
+ ? await store.syncChangedData(r.mask, opts)
91
+ : await store.sync(r.mask, opts);
92
+ }
93
+ else if (kind == 'replay') {
94
+ const r = resource;
95
+ store ??= (0, store_1.createStore)(r.initial, r.storeOpts);
96
+ const sub = (0, store_replay_1.syncStoreReplay)(store, r.remote, r.syncOpts);
97
+ stopSync = sub;
98
+ await sub.ready;
99
+ }
100
+ else {
101
+ const r = resource;
102
+ store = await (0, store_offline_1.createOfflineStore)({
103
+ key: r.storageKey ?? key,
104
+ remote: r.remote,
105
+ initial: r.initial,
106
+ storage: r.storage,
107
+ version: r.version,
108
+ debounceMs: r.debounceMs,
109
+ storeOpts: r.storeOpts,
110
+ syncOpts: r.syncOpts,
111
+ migrate: r.migrate,
112
+ });
113
+ await store.ready;
114
+ }
115
+ setState('ready');
116
+ return store;
117
+ }
118
+ catch (e) {
119
+ setState('error', e);
120
+ throw e;
121
+ }
122
+ finally {
123
+ pending = undefined;
124
+ }
125
+ })();
126
+ return pending;
127
+ }
128
+ function stop() {
129
+ stopSync?.();
130
+ stopSync = undefined;
131
+ if (kind == 'offline') {
132
+ store?.close?.();
133
+ store = undefined;
134
+ }
135
+ if (state != 'idle')
136
+ setState('stopped');
137
+ }
138
+ function touch(weight = 1) {
139
+ const usage = usageFor(key, resource);
140
+ usage.count++;
141
+ usage.weight += weight;
142
+ usage.lastUsedAt = Date.now();
143
+ }
144
+ return { key, kind, start, stop, get: () => store, status, touch };
145
+ }
146
+ const handles = {};
147
+ for (const key of Object.keys(resources))
148
+ handles[key] = createHandle(key, resources[key]);
149
+ function plan(opts = {}) {
150
+ const now = opts.now ?? Date.now();
151
+ const keySet = opts.keys ? new Set([...opts.keys].map(String)) : null;
152
+ const out = [];
153
+ for (const key of Object.keys(resources)) {
154
+ if (keySet && !keySet.has(key))
155
+ continue;
156
+ const resource = resources[key];
157
+ if (!hasAnyTag(resource.tags, opts.tags))
158
+ continue;
159
+ if (resource.explicitOnly && !opts.includeExplicit)
160
+ continue;
161
+ if (resource.large && !opts.includeLarge)
162
+ continue;
163
+ const usage = usages.get(resource.usageKey ?? key);
164
+ out.push({
165
+ key,
166
+ kind: kindOf(resource),
167
+ score: resourceScore(key, resource, usage, now),
168
+ state: handles[key].status().state,
169
+ large: !!resource.large,
170
+ explicitOnly: !!resource.explicitOnly,
171
+ tags: resource.tags ?? [],
172
+ });
173
+ }
174
+ out.sort((a, b) => b.score - a.score || a.key.localeCompare(b.key));
175
+ return opts.limit == null ? out : out.slice(0, opts.limit);
176
+ }
177
+ async function start(key, opts) {
178
+ const handle = handles[key];
179
+ if (!handle)
180
+ throw new Error(`store manager: unknown resource ${key}`);
181
+ return handle.start(opts);
182
+ }
183
+ async function startMany(keys, opts) {
184
+ const out = {};
185
+ for (const key of keys)
186
+ out[key] = await start(key, opts);
187
+ return out;
188
+ }
189
+ async function startPlanned(opts = {}) {
190
+ const out = {};
191
+ for (const item of plan(opts))
192
+ out[item.key] = await handles[item.key].start(opts);
193
+ return out;
194
+ }
195
+ function stop(key) {
196
+ const handle = handles[key];
197
+ if (!handle)
198
+ throw new Error(`store manager: unknown resource ${key}`);
199
+ handle.stop();
200
+ }
201
+ function stopAll() {
202
+ for (const handle of Object.values(handles))
203
+ handle.stop();
204
+ }
205
+ function get(key) {
206
+ return handles[key]?.get();
207
+ }
208
+ function touch(key, weight) {
209
+ const handle = handles[key];
210
+ if (!handle)
211
+ throw new Error(`store manager: unknown resource ${key}`);
212
+ handle.touch(weight);
213
+ }
214
+ return {
215
+ handles: handles,
216
+ statusListen,
217
+ plan,
218
+ start,
219
+ startMany,
220
+ startPlanned,
221
+ stop,
222
+ stopAll,
223
+ get,
224
+ touch,
225
+ usage: () => new Map(usages),
226
+ };
227
+ }
@@ -0,0 +1,88 @@
1
+ import { ReplayRemote, ReplaySubscribeOpts } from '../events/replay-wire';
2
+ import { createStore, Store, StorePatch } from './store';
3
+ export type OfflineStorage = {
4
+ read<T>(key: string): Promise<T | undefined>;
5
+ write<T>(key: string, value: T): Promise<void>;
6
+ remove(key: string): Promise<void>;
7
+ transaction?<R>(fn: (tx: OfflineStorage) => Promise<R>): Promise<R>;
8
+ };
9
+ export type OfflineStoreRecord<T extends object> = {
10
+ version: number;
11
+ seq: number;
12
+ snapshot: T;
13
+ savedAt: number;
14
+ };
15
+ export type OfflineStoreStatus = {
16
+ ready: boolean;
17
+ syncing: boolean;
18
+ offline: boolean;
19
+ stale: boolean;
20
+ saving: boolean;
21
+ seq: number;
22
+ savedAt?: number;
23
+ error?: unknown;
24
+ };
25
+ export type PersistStoreOpts = {
26
+ key: string;
27
+ storage: OfflineStorage;
28
+ version?: number;
29
+ seq?: number;
30
+ savedAt?: number;
31
+ debounceMs?: number;
32
+ now?: () => number;
33
+ onError?: (error: unknown) => void;
34
+ onStatus?: (status: OfflineStoreStatus) => void;
35
+ };
36
+ export type CreateOfflineStoreOpts<T extends object> = {
37
+ key: string;
38
+ remote?: ReplayRemote<[StorePatch]>;
39
+ initial: T;
40
+ storage: OfflineStorage;
41
+ version?: number;
42
+ debounceMs?: number;
43
+ mode?: 'snapshot' | 'topLevel';
44
+ migrate?: (oldSnapshot: unknown, fromVersion: number, toVersion: number) => T | Promise<T>;
45
+ now?: () => number;
46
+ storeOpts?: Parameters<typeof createStore<T>>[1];
47
+ syncOpts?: ReplaySubscribeOpts;
48
+ onError?: (error: unknown) => void;
49
+ onStatus?: (status: OfflineStoreStatus) => void;
50
+ };
51
+ export type PersistedStoreControl = ReturnType<typeof persistStore>;
52
+ export type OfflineStore<T extends object> = Store<T> & {
53
+ ready: Promise<void>;
54
+ close(): void;
55
+ flush(): Promise<void>;
56
+ status(): OfflineStoreStatus;
57
+ statusListen: PersistedStoreControl['statusListen'];
58
+ reconnect(remote: ReplayRemote<[StorePatch]>, opts?: ReplaySubscribeOpts): Promise<void>;
59
+ };
60
+ export declare function createMemoryOfflineStorage(initial?: Record<string, unknown>): OfflineStorage & {
61
+ dump(): Record<string, unknown>;
62
+ };
63
+ export declare function persistStore<T extends object>(store: Store<T>, opts: PersistStoreOpts): {
64
+ flush: () => Promise<void>;
65
+ forceFlush: () => Promise<void>;
66
+ close: () => void;
67
+ setSeq: (nextSeq: number) => void;
68
+ seq: () => number;
69
+ status: () => OfflineStoreStatus;
70
+ statusListen: {
71
+ on: import("../events/Listen").ListenOnCurrent<[OfflineStoreStatus]>;
72
+ once: (cb: import("../events/Listen").Listener<[OfflineStoreStatus]>, opts?: {
73
+ key?: import("../events/Listen").ListenKey;
74
+ current?: import("../events/Listen").ListenCurrent<[OfflineStoreStatus]> | undefined;
75
+ }) => import("../events/Listen").ListenOff;
76
+ emit: import("../events/Listen").Listener<[OfflineStoreStatus]>;
77
+ has(key: import("../events/Listen").ListenKey): boolean;
78
+ off(keyOrCallback: import("../events/Listen").ListenKey | import("../events/Listen").Listener<[OfflineStoreStatus]> | null): void;
79
+ close(): void;
80
+ count(): number;
81
+ keys(): import("../events/Listen").ListenKey[];
82
+ isRunning(): boolean;
83
+ run(): void;
84
+ onClose(cb: () => void): import("../events/Listen").ListenOff;
85
+ };
86
+ setSyncStatus: (patch: Partial<OfflineStoreStatus>) => void;
87
+ };
88
+ export declare function createOfflineStore<T extends object>(opts: CreateOfflineStoreOpts<T>): Promise<OfflineStore<T>>;