@thalesfp/snapstate 0.1.0
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/LICENSE +21 -0
- package/README.md +439 -0
- package/dist/form/index.cjs +1177 -0
- package/dist/form/index.cjs.map +1 -0
- package/dist/form/index.d.cts +197 -0
- package/dist/form/index.d.ts +197 -0
- package/dist/form/index.js +1153 -0
- package/dist/form/index.js.map +1 -0
- package/dist/index.cjs +528 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +109 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +498 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.cjs +693 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +130 -0
- package/dist/react/index.d.ts +130 -0
- package/dist/react/index.js +671 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/react/index.ts
|
|
21
|
+
var react_exports = {};
|
|
22
|
+
__export(react_exports, {
|
|
23
|
+
SnapStore: () => ReactSnapStore,
|
|
24
|
+
asyncStatus: () => asyncStatus,
|
|
25
|
+
setDefaultHeaders: () => setDefaultHeaders,
|
|
26
|
+
setHttpClient: () => setHttpClient
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(react_exports);
|
|
29
|
+
|
|
30
|
+
// src/react/store.ts
|
|
31
|
+
var import_react = require("react");
|
|
32
|
+
|
|
33
|
+
// src/core/types.ts
|
|
34
|
+
var _statuses = {
|
|
35
|
+
idle: Object.freeze({ value: "idle", isIdle: true, isLoading: false, isReady: false, isError: false }),
|
|
36
|
+
loading: Object.freeze({ value: "loading", isIdle: false, isLoading: true, isReady: false, isError: false }),
|
|
37
|
+
ready: Object.freeze({ value: "ready", isIdle: false, isLoading: false, isReady: true, isError: false }),
|
|
38
|
+
error: Object.freeze({ value: "error", isIdle: false, isLoading: false, isReady: false, isError: true })
|
|
39
|
+
};
|
|
40
|
+
function asyncStatus(value) {
|
|
41
|
+
return _statuses[value];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/core/trie.ts
|
|
45
|
+
function invokeAll(listeners) {
|
|
46
|
+
let firstError;
|
|
47
|
+
for (const l of listeners) {
|
|
48
|
+
try {
|
|
49
|
+
l();
|
|
50
|
+
} catch (e) {
|
|
51
|
+
firstError ??= e;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (firstError !== void 0) {
|
|
55
|
+
throw firstError;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function createNode() {
|
|
59
|
+
return { listeners: /* @__PURE__ */ new Set(), children: /* @__PURE__ */ new Map() };
|
|
60
|
+
}
|
|
61
|
+
function parsePath(path) {
|
|
62
|
+
if (path === "") {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
return path.split(".");
|
|
66
|
+
}
|
|
67
|
+
var SubscriptionTrie = class {
|
|
68
|
+
root = createNode();
|
|
69
|
+
globalListeners = /* @__PURE__ */ new Set();
|
|
70
|
+
/** Subscribe to a specific path. Returns unsubscribe function. */
|
|
71
|
+
add(path, listener) {
|
|
72
|
+
const segments = parsePath(path);
|
|
73
|
+
const parents = [];
|
|
74
|
+
let node = this.root;
|
|
75
|
+
for (const seg of segments) {
|
|
76
|
+
if (!node.children.has(seg)) {
|
|
77
|
+
node.children.set(seg, createNode());
|
|
78
|
+
}
|
|
79
|
+
parents.push({ parent: node, segment: seg });
|
|
80
|
+
node = node.children.get(seg);
|
|
81
|
+
}
|
|
82
|
+
node.listeners.add(listener);
|
|
83
|
+
return () => {
|
|
84
|
+
node.listeners.delete(listener);
|
|
85
|
+
for (let i = parents.length - 1; i >= 0; i--) {
|
|
86
|
+
const { parent, segment } = parents[i];
|
|
87
|
+
const child = parent.children.get(segment);
|
|
88
|
+
if (child.listeners.size === 0 && child.children.size === 0) {
|
|
89
|
+
parent.children.delete(segment);
|
|
90
|
+
} else {
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Subscribe to all changes (no path filter). */
|
|
97
|
+
addGlobal(listener) {
|
|
98
|
+
this.globalListeners.add(listener);
|
|
99
|
+
return () => {
|
|
100
|
+
this.globalListeners.delete(listener);
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Notify listeners for exact path, all ancestors, and all descendants. */
|
|
104
|
+
notify(path) {
|
|
105
|
+
const segments = parsePath(path);
|
|
106
|
+
const collected = /* @__PURE__ */ new Set();
|
|
107
|
+
for (const l of this.globalListeners) collected.add(l);
|
|
108
|
+
let node = this.root;
|
|
109
|
+
for (const l of node.listeners) collected.add(l);
|
|
110
|
+
let matched = true;
|
|
111
|
+
for (const seg of segments) {
|
|
112
|
+
const wildcard = node.children.get("*");
|
|
113
|
+
if (wildcard) {
|
|
114
|
+
for (const l of wildcard.listeners) collected.add(l);
|
|
115
|
+
this.collectDescendants(wildcard, collected);
|
|
116
|
+
}
|
|
117
|
+
const child = node.children.get(seg);
|
|
118
|
+
if (!child) {
|
|
119
|
+
matched = false;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
node = child;
|
|
123
|
+
for (const l of node.listeners) collected.add(l);
|
|
124
|
+
}
|
|
125
|
+
if (matched) {
|
|
126
|
+
this.collectDescendants(node, collected);
|
|
127
|
+
}
|
|
128
|
+
invokeAll(collected);
|
|
129
|
+
}
|
|
130
|
+
/** Notify all listeners in the trie. */
|
|
131
|
+
notifyAll() {
|
|
132
|
+
const collected = /* @__PURE__ */ new Set();
|
|
133
|
+
for (const l of this.globalListeners) collected.add(l);
|
|
134
|
+
this.collectDescendants(this.root, collected);
|
|
135
|
+
for (const l of this.root.listeners) collected.add(l);
|
|
136
|
+
invokeAll(collected);
|
|
137
|
+
}
|
|
138
|
+
collectDescendants(node, out) {
|
|
139
|
+
for (const child of node.children.values()) {
|
|
140
|
+
for (const l of child.listeners) out.add(l);
|
|
141
|
+
this.collectDescendants(child, out);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
clear() {
|
|
145
|
+
this.root = createNode();
|
|
146
|
+
this.globalListeners.clear();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// src/core/structural.ts
|
|
151
|
+
function applyUpdate(state, path, value) {
|
|
152
|
+
const segments = path.split(".");
|
|
153
|
+
return updateAtPath(state, segments, 0, value);
|
|
154
|
+
}
|
|
155
|
+
function updateAtPath(current, segments, index, value) {
|
|
156
|
+
if (index === segments.length) {
|
|
157
|
+
if (typeof value === "function") {
|
|
158
|
+
return value(current);
|
|
159
|
+
}
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
const key = segments[index];
|
|
163
|
+
if (Array.isArray(current)) {
|
|
164
|
+
const i = Number(key);
|
|
165
|
+
const next2 = updateAtPath(current[i], segments, index + 1, value);
|
|
166
|
+
if (Object.is(next2, current[i])) {
|
|
167
|
+
return current;
|
|
168
|
+
}
|
|
169
|
+
const copy = current.slice();
|
|
170
|
+
copy[i] = next2;
|
|
171
|
+
return copy;
|
|
172
|
+
}
|
|
173
|
+
if (current !== null && typeof current === "object") {
|
|
174
|
+
const obj = current;
|
|
175
|
+
const next2 = updateAtPath(obj[key], segments, index + 1, value);
|
|
176
|
+
if (Object.is(next2, obj[key])) {
|
|
177
|
+
return current;
|
|
178
|
+
}
|
|
179
|
+
return { ...obj, [key]: next2 };
|
|
180
|
+
}
|
|
181
|
+
const next = updateAtPath(void 0, segments, index + 1, value);
|
|
182
|
+
return { [key]: next };
|
|
183
|
+
}
|
|
184
|
+
function getAtPath(state, path) {
|
|
185
|
+
if (path === "") {
|
|
186
|
+
return state;
|
|
187
|
+
}
|
|
188
|
+
const segments = path.split(".");
|
|
189
|
+
let current = state;
|
|
190
|
+
for (const seg of segments) {
|
|
191
|
+
if (current === null || current === void 0) {
|
|
192
|
+
return void 0;
|
|
193
|
+
}
|
|
194
|
+
current = current[seg];
|
|
195
|
+
}
|
|
196
|
+
return current;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/core/computed.ts
|
|
200
|
+
function createComputed(host, deps, fn) {
|
|
201
|
+
let cachedValue;
|
|
202
|
+
let dirty = true;
|
|
203
|
+
const unsubs = [];
|
|
204
|
+
const markDirty = () => {
|
|
205
|
+
dirty = true;
|
|
206
|
+
};
|
|
207
|
+
for (const dep of deps) {
|
|
208
|
+
unsubs.push(host.subscribe(dep, markDirty));
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
cachedValue = fn(host.getSnapshot());
|
|
212
|
+
} catch (e) {
|
|
213
|
+
for (const unsub of unsubs) unsub();
|
|
214
|
+
unsubs.length = 0;
|
|
215
|
+
throw e;
|
|
216
|
+
}
|
|
217
|
+
dirty = false;
|
|
218
|
+
return {
|
|
219
|
+
get() {
|
|
220
|
+
if (dirty) {
|
|
221
|
+
cachedValue = fn(host.getSnapshot());
|
|
222
|
+
dirty = false;
|
|
223
|
+
}
|
|
224
|
+
return cachedValue;
|
|
225
|
+
},
|
|
226
|
+
destroy() {
|
|
227
|
+
for (const unsub of unsubs) unsub();
|
|
228
|
+
unsubs.length = 0;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/core/store.ts
|
|
234
|
+
function createStore(initialState, options = {}) {
|
|
235
|
+
const { autoBatch = true } = options;
|
|
236
|
+
let state = initialState;
|
|
237
|
+
const trie = new SubscriptionTrie();
|
|
238
|
+
let batchDepth = 0;
|
|
239
|
+
let pendingPaths = /* @__PURE__ */ new Set();
|
|
240
|
+
let microtaskScheduled = false;
|
|
241
|
+
function flushNotifications() {
|
|
242
|
+
const paths = pendingPaths;
|
|
243
|
+
pendingPaths = /* @__PURE__ */ new Set();
|
|
244
|
+
microtaskScheduled = false;
|
|
245
|
+
if (paths.size === 0) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const sorted = [...paths].sort();
|
|
249
|
+
const deduped = [];
|
|
250
|
+
for (const p of sorted) {
|
|
251
|
+
const last = deduped[deduped.length - 1];
|
|
252
|
+
if (last !== void 0 && p.startsWith(last + ".")) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
deduped.push(p);
|
|
256
|
+
}
|
|
257
|
+
for (const path of deduped) {
|
|
258
|
+
trie.notify(path);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function scheduleFlush() {
|
|
262
|
+
if (batchDepth > 0) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (autoBatch && !microtaskScheduled) {
|
|
266
|
+
microtaskScheduled = true;
|
|
267
|
+
queueMicrotask(flushNotifications);
|
|
268
|
+
} else if (!autoBatch) {
|
|
269
|
+
flushNotifications();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function get(path) {
|
|
273
|
+
if (path === void 0 || path === "") {
|
|
274
|
+
return state;
|
|
275
|
+
}
|
|
276
|
+
return getAtPath(state, path);
|
|
277
|
+
}
|
|
278
|
+
function set(path, value) {
|
|
279
|
+
if (path === "") {
|
|
280
|
+
throw new Error("Cannot set with an empty path. Use a specific path to update state.");
|
|
281
|
+
}
|
|
282
|
+
const prev = state;
|
|
283
|
+
state = applyUpdate(state, path, value);
|
|
284
|
+
if (state !== prev) {
|
|
285
|
+
pendingPaths.add(path);
|
|
286
|
+
scheduleFlush();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function batch(fn) {
|
|
290
|
+
batchDepth++;
|
|
291
|
+
try {
|
|
292
|
+
fn();
|
|
293
|
+
} finally {
|
|
294
|
+
batchDepth--;
|
|
295
|
+
if (batchDepth === 0) {
|
|
296
|
+
flushNotifications();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function subscribe(pathOrCallback, callback) {
|
|
301
|
+
if (typeof pathOrCallback === "function") {
|
|
302
|
+
return trie.addGlobal(pathOrCallback);
|
|
303
|
+
}
|
|
304
|
+
return trie.add(pathOrCallback, callback);
|
|
305
|
+
}
|
|
306
|
+
function getSnapshot() {
|
|
307
|
+
return state;
|
|
308
|
+
}
|
|
309
|
+
function computed(deps, fn) {
|
|
310
|
+
return createComputed({ getSnapshot, subscribe }, deps, fn);
|
|
311
|
+
}
|
|
312
|
+
function notify() {
|
|
313
|
+
trie.notifyAll();
|
|
314
|
+
}
|
|
315
|
+
function destroy() {
|
|
316
|
+
trie.clear();
|
|
317
|
+
pendingPaths.clear();
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
get,
|
|
321
|
+
set,
|
|
322
|
+
batch,
|
|
323
|
+
subscribe,
|
|
324
|
+
getSnapshot,
|
|
325
|
+
computed,
|
|
326
|
+
notify,
|
|
327
|
+
destroy
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/core/base.ts
|
|
332
|
+
var IDLE_STATE = { status: asyncStatus("idle"), error: null };
|
|
333
|
+
var defaultHttpClient = {
|
|
334
|
+
async request(url, init) {
|
|
335
|
+
const fetchInit = { method: init?.method ?? "GET" };
|
|
336
|
+
const merged = { ...defaultHeaders, ...init?.headers };
|
|
337
|
+
if (Object.keys(merged).length) {
|
|
338
|
+
fetchInit.headers = merged;
|
|
339
|
+
}
|
|
340
|
+
if (init?.body !== void 0) {
|
|
341
|
+
fetchInit.body = JSON.stringify(init.body);
|
|
342
|
+
fetchInit.headers = { "Content-Type": "application/json", ...merged };
|
|
343
|
+
}
|
|
344
|
+
const res = await fetch(url, fetchInit);
|
|
345
|
+
if (!res.ok) {
|
|
346
|
+
let message = `HTTP ${res.status}`;
|
|
347
|
+
try {
|
|
348
|
+
const text2 = await res.text();
|
|
349
|
+
if (text2) {
|
|
350
|
+
const json = JSON.parse(text2);
|
|
351
|
+
message = json.error ?? json.message ?? message;
|
|
352
|
+
}
|
|
353
|
+
} catch {
|
|
354
|
+
}
|
|
355
|
+
throw new Error(message);
|
|
356
|
+
}
|
|
357
|
+
const text = await res.text();
|
|
358
|
+
return text ? JSON.parse(text) : void 0;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
var httpClient = defaultHttpClient;
|
|
362
|
+
var defaultHeaders = {};
|
|
363
|
+
function setHttpClient(client) {
|
|
364
|
+
httpClient = client;
|
|
365
|
+
}
|
|
366
|
+
function setDefaultHeaders(headers) {
|
|
367
|
+
defaultHeaders = headers;
|
|
368
|
+
}
|
|
369
|
+
var SnapStore = class {
|
|
370
|
+
_store;
|
|
371
|
+
_operations = /* @__PURE__ */ new Map();
|
|
372
|
+
_generations = /* @__PURE__ */ new Map();
|
|
373
|
+
state;
|
|
374
|
+
api;
|
|
375
|
+
constructor(initialState, options) {
|
|
376
|
+
this._store = createStore(initialState, options);
|
|
377
|
+
const store = this._store;
|
|
378
|
+
const operations = this._operations;
|
|
379
|
+
const generations = this._generations;
|
|
380
|
+
const doFetch = async (key, fn) => {
|
|
381
|
+
const gen = (generations.get(key) ?? 0) + 1;
|
|
382
|
+
generations.set(key, gen);
|
|
383
|
+
operations.set(key, { status: asyncStatus("loading"), error: null });
|
|
384
|
+
store.notify();
|
|
385
|
+
try {
|
|
386
|
+
await fn();
|
|
387
|
+
if (generations.get(key) !== gen) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
operations.set(key, { status: asyncStatus("ready"), error: null });
|
|
391
|
+
} catch (e) {
|
|
392
|
+
if (generations.get(key) !== gen) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
operations.set(key, {
|
|
396
|
+
status: asyncStatus("error"),
|
|
397
|
+
error: e instanceof Error ? e.message : "Unknown error"
|
|
398
|
+
});
|
|
399
|
+
store.notify();
|
|
400
|
+
throw e;
|
|
401
|
+
}
|
|
402
|
+
store.notify();
|
|
403
|
+
};
|
|
404
|
+
const doSend = async (key, method, url, options2) => {
|
|
405
|
+
await doFetch(key, async () => {
|
|
406
|
+
try {
|
|
407
|
+
const data = await httpClient.request(url, {
|
|
408
|
+
method,
|
|
409
|
+
body: options2?.body,
|
|
410
|
+
headers: options2?.headers
|
|
411
|
+
});
|
|
412
|
+
options2?.onSuccess?.(data);
|
|
413
|
+
} catch (e) {
|
|
414
|
+
options2?.onError?.(e instanceof Error ? e : new Error("Unknown error"));
|
|
415
|
+
throw e;
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
};
|
|
419
|
+
this.state = {
|
|
420
|
+
get: ((path) => {
|
|
421
|
+
if (path === void 0) {
|
|
422
|
+
return store.get();
|
|
423
|
+
}
|
|
424
|
+
return store.get(path);
|
|
425
|
+
}),
|
|
426
|
+
set: (path, value) => store.set(path, value),
|
|
427
|
+
batch: (fn) => store.batch(fn),
|
|
428
|
+
computed: (deps, fn) => store.computed(deps, fn),
|
|
429
|
+
append: (path, ...items) => {
|
|
430
|
+
store.set(path, ((prev) => [...prev, ...items]));
|
|
431
|
+
},
|
|
432
|
+
prepend: (path, ...items) => {
|
|
433
|
+
store.set(path, ((prev) => [...items, ...prev]));
|
|
434
|
+
},
|
|
435
|
+
insertAt: (path, index, ...items) => {
|
|
436
|
+
store.set(path, ((prev) => {
|
|
437
|
+
const arr = prev;
|
|
438
|
+
return [...arr.slice(0, index), ...items, ...arr.slice(index)];
|
|
439
|
+
}));
|
|
440
|
+
},
|
|
441
|
+
patch: (path, predicate, updates) => {
|
|
442
|
+
store.set(path, ((prev) => {
|
|
443
|
+
const arr = prev;
|
|
444
|
+
let changed = false;
|
|
445
|
+
const result = arr.map((item) => {
|
|
446
|
+
if (item == null) {
|
|
447
|
+
return item;
|
|
448
|
+
}
|
|
449
|
+
if (predicate(item)) {
|
|
450
|
+
changed = true;
|
|
451
|
+
return Object.assign(Object.create(Object.getPrototypeOf(item)), item, updates);
|
|
452
|
+
}
|
|
453
|
+
return item;
|
|
454
|
+
});
|
|
455
|
+
return changed ? result : arr;
|
|
456
|
+
}));
|
|
457
|
+
},
|
|
458
|
+
remove: (path, predicate) => {
|
|
459
|
+
store.set(path, ((prev) => {
|
|
460
|
+
const arr = prev;
|
|
461
|
+
const result = arr.filter((item) => !predicate(item));
|
|
462
|
+
return result.length === arr.length ? arr : result;
|
|
463
|
+
}));
|
|
464
|
+
},
|
|
465
|
+
removeAt: (path, index) => {
|
|
466
|
+
store.set(path, ((prev) => {
|
|
467
|
+
const arr = prev;
|
|
468
|
+
const i = index < 0 ? arr.length + index : index;
|
|
469
|
+
if (i < 0 || i >= arr.length) {
|
|
470
|
+
throw new RangeError(`Index ${index} out of bounds for array of length ${arr.length}`);
|
|
471
|
+
}
|
|
472
|
+
return [...arr.slice(0, i), ...arr.slice(i + 1)];
|
|
473
|
+
}));
|
|
474
|
+
},
|
|
475
|
+
at: (path, index) => {
|
|
476
|
+
return store.get(path).at(index);
|
|
477
|
+
},
|
|
478
|
+
filter: (path, predicate) => {
|
|
479
|
+
return store.get(path).filter(predicate);
|
|
480
|
+
},
|
|
481
|
+
find: (path, predicate) => {
|
|
482
|
+
return store.get(path).find(predicate);
|
|
483
|
+
},
|
|
484
|
+
findIndexOf: (path, predicate) => {
|
|
485
|
+
return store.get(path).findIndex(predicate);
|
|
486
|
+
},
|
|
487
|
+
count: (path, predicate) => {
|
|
488
|
+
return store.get(path).filter(predicate).length;
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
this.api = {
|
|
492
|
+
fetch: doFetch,
|
|
493
|
+
get: async (key, url, onSuccess) => {
|
|
494
|
+
await doFetch(key, async () => {
|
|
495
|
+
const data = await httpClient.request(url);
|
|
496
|
+
onSuccess?.(data);
|
|
497
|
+
});
|
|
498
|
+
},
|
|
499
|
+
post: (key, url, options2) => doSend(key, "POST", url, options2),
|
|
500
|
+
put: (key, url, options2) => doSend(key, "PUT", url, options2),
|
|
501
|
+
patch: (key, url, options2) => doSend(key, "PATCH", url, options2),
|
|
502
|
+
delete: (key, url, options2) => doSend(key, "DELETE", url, options2)
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
subscribe(pathOrCallback, callback) {
|
|
506
|
+
if (typeof pathOrCallback === "function") {
|
|
507
|
+
return this._store.subscribe(pathOrCallback);
|
|
508
|
+
}
|
|
509
|
+
return this._store.subscribe(pathOrCallback, callback);
|
|
510
|
+
}
|
|
511
|
+
/** Return a snapshot of the current state. Compatible with React's `useSyncExternalStore`. */
|
|
512
|
+
getSnapshot = () => {
|
|
513
|
+
return this._store.getSnapshot();
|
|
514
|
+
};
|
|
515
|
+
/** Get the async status of an operation by key. Returns `idle` if never started. */
|
|
516
|
+
getStatus(key) {
|
|
517
|
+
return { ...this._operations.get(key) ?? IDLE_STATE };
|
|
518
|
+
}
|
|
519
|
+
/** Tear down subscriptions and cleanup. */
|
|
520
|
+
destroy() {
|
|
521
|
+
this._store.destroy();
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
// src/react/store.ts
|
|
526
|
+
function shallowEqual(a, b) {
|
|
527
|
+
const keysA = Object.keys(a);
|
|
528
|
+
const keysB = Object.keys(b);
|
|
529
|
+
if (keysA.length !== keysB.length) {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
for (const key of keysA) {
|
|
533
|
+
if (a[key] !== b[key]) {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
var ReactSnapStore = class extends SnapStore {
|
|
540
|
+
constructor(initialState, options) {
|
|
541
|
+
super(initialState, options);
|
|
542
|
+
}
|
|
543
|
+
connect(Component, configOrMapper) {
|
|
544
|
+
const store = this;
|
|
545
|
+
if (typeof configOrMapper === "object" && "select" in configOrMapper) {
|
|
546
|
+
return this._connectWithSelect(Component, configOrMapper.select);
|
|
547
|
+
}
|
|
548
|
+
const mapToProps = typeof configOrMapper === "function" ? configOrMapper : configOrMapper.props;
|
|
549
|
+
const fetchFn = typeof configOrMapper === "function" ? void 0 : configOrMapper.fetch;
|
|
550
|
+
const loadingComponent = typeof configOrMapper === "function" ? void 0 : configOrMapper.loading;
|
|
551
|
+
const errorComponent = typeof configOrMapper === "function" ? void 0 : configOrMapper.error;
|
|
552
|
+
const Connected = (0, import_react.forwardRef)(function Connected2(ownProps, ref) {
|
|
553
|
+
const cachedRef = (0, import_react.useRef)(null);
|
|
554
|
+
const revisionRef = (0, import_react.useRef)(0);
|
|
555
|
+
const subscribe = (0, import_react.useCallback)(
|
|
556
|
+
(cb) => store.subscribe(() => {
|
|
557
|
+
revisionRef.current++;
|
|
558
|
+
cb();
|
|
559
|
+
}),
|
|
560
|
+
[store]
|
|
561
|
+
);
|
|
562
|
+
const getSnapshot = (0, import_react.useCallback)(() => {
|
|
563
|
+
const currentRevision = revisionRef.current;
|
|
564
|
+
if (cachedRef.current && cachedRef.current.revision === currentRevision) {
|
|
565
|
+
return cachedRef.current.props;
|
|
566
|
+
}
|
|
567
|
+
const next = mapToProps(store);
|
|
568
|
+
if (cachedRef.current && shallowEqual(cachedRef.current.props, next)) {
|
|
569
|
+
cachedRef.current = { revision: currentRevision, props: cachedRef.current.props };
|
|
570
|
+
return cachedRef.current.props;
|
|
571
|
+
}
|
|
572
|
+
cachedRef.current = { revision: currentRevision, props: next };
|
|
573
|
+
return next;
|
|
574
|
+
}, [store]);
|
|
575
|
+
const mappedProps = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
576
|
+
const [asyncState, setAsyncState] = (0, import_react.useState)({ status: asyncStatus("idle"), error: null });
|
|
577
|
+
const fetchGenRef = (0, import_react.useRef)(0);
|
|
578
|
+
(0, import_react.useEffect)(() => {
|
|
579
|
+
if (!fetchFn) {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
let cancelled = false;
|
|
583
|
+
const gen = ++fetchGenRef.current;
|
|
584
|
+
setAsyncState({ status: asyncStatus("loading"), error: null });
|
|
585
|
+
Promise.resolve().then(() => {
|
|
586
|
+
if (cancelled) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
return fetchFn(store);
|
|
590
|
+
}).then(() => {
|
|
591
|
+
if (gen === fetchGenRef.current) {
|
|
592
|
+
setAsyncState({ status: asyncStatus("ready"), error: null });
|
|
593
|
+
}
|
|
594
|
+
}).catch((e) => {
|
|
595
|
+
if (gen === fetchGenRef.current) {
|
|
596
|
+
setAsyncState({
|
|
597
|
+
status: asyncStatus("error"),
|
|
598
|
+
error: e instanceof Error ? e.message : "Unknown error"
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
return () => {
|
|
603
|
+
cancelled = true;
|
|
604
|
+
};
|
|
605
|
+
}, []);
|
|
606
|
+
if (fetchFn) {
|
|
607
|
+
if (loadingComponent && (asyncState.status.isIdle || asyncState.status.isLoading)) {
|
|
608
|
+
return (0, import_react.createElement)(loadingComponent);
|
|
609
|
+
}
|
|
610
|
+
if (errorComponent && asyncState.status.isError) {
|
|
611
|
+
return (0, import_react.createElement)(errorComponent, { error: asyncState.error });
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return (0, import_react.createElement)(Component, {
|
|
615
|
+
...ownProps,
|
|
616
|
+
...mappedProps,
|
|
617
|
+
...fetchFn ? asyncState : {},
|
|
618
|
+
ref
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
Connected.displayName = `Connect(${Component.displayName || Component.name || "Component"})`;
|
|
622
|
+
return Connected;
|
|
623
|
+
}
|
|
624
|
+
_connectWithSelect(Component, selectFn) {
|
|
625
|
+
const store = this;
|
|
626
|
+
const resolvePathValue = (path) => {
|
|
627
|
+
const segments = path.split(".");
|
|
628
|
+
let val = store.getSnapshot();
|
|
629
|
+
for (const seg of segments) {
|
|
630
|
+
if (val == null) {
|
|
631
|
+
return void 0;
|
|
632
|
+
}
|
|
633
|
+
val = val[seg];
|
|
634
|
+
}
|
|
635
|
+
return val;
|
|
636
|
+
};
|
|
637
|
+
const trackedPaths = [];
|
|
638
|
+
const trackingPick = ((path) => {
|
|
639
|
+
trackedPaths.push(path);
|
|
640
|
+
return resolvePathValue(path);
|
|
641
|
+
});
|
|
642
|
+
selectFn(trackingPick);
|
|
643
|
+
const paths = [...trackedPaths];
|
|
644
|
+
const readPick = ((path) => {
|
|
645
|
+
return resolvePathValue(path);
|
|
646
|
+
});
|
|
647
|
+
const Connected = (0, import_react.forwardRef)(function Connected2(ownProps, ref) {
|
|
648
|
+
const cachedRef = (0, import_react.useRef)(null);
|
|
649
|
+
const revisionRef = (0, import_react.useRef)(0);
|
|
650
|
+
const subscribe = (0, import_react.useCallback)(
|
|
651
|
+
(cb) => {
|
|
652
|
+
const unsubs = paths.map(
|
|
653
|
+
(p) => store.subscribe(p, () => {
|
|
654
|
+
revisionRef.current++;
|
|
655
|
+
cb();
|
|
656
|
+
})
|
|
657
|
+
);
|
|
658
|
+
return () => unsubs.forEach((u) => u());
|
|
659
|
+
},
|
|
660
|
+
[store]
|
|
661
|
+
);
|
|
662
|
+
const getSnapshot = (0, import_react.useCallback)(() => {
|
|
663
|
+
const currentRevision = revisionRef.current;
|
|
664
|
+
if (cachedRef.current && cachedRef.current.revision === currentRevision) {
|
|
665
|
+
return cachedRef.current.props;
|
|
666
|
+
}
|
|
667
|
+
const next = selectFn(readPick);
|
|
668
|
+
if (cachedRef.current && shallowEqual(cachedRef.current.props, next)) {
|
|
669
|
+
cachedRef.current = { revision: currentRevision, props: cachedRef.current.props };
|
|
670
|
+
return cachedRef.current.props;
|
|
671
|
+
}
|
|
672
|
+
cachedRef.current = { revision: currentRevision, props: next };
|
|
673
|
+
return next;
|
|
674
|
+
}, [store]);
|
|
675
|
+
const mappedProps = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
676
|
+
return (0, import_react.createElement)(Component, {
|
|
677
|
+
...ownProps,
|
|
678
|
+
...mappedProps,
|
|
679
|
+
ref
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
Connected.displayName = `Connect(${Component.displayName || Component.name || "Component"})`;
|
|
683
|
+
return Connected;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
687
|
+
0 && (module.exports = {
|
|
688
|
+
SnapStore,
|
|
689
|
+
asyncStatus,
|
|
690
|
+
setDefaultHeaders,
|
|
691
|
+
setHttpClient
|
|
692
|
+
});
|
|
693
|
+
//# sourceMappingURL=index.cjs.map
|