@firsthandjs/data 0.9.1 → 0.10.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/dist/action.d.ts +30 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/document.d.ts.map +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.dev.js +444 -399
- package/dist/index.js +2 -2
- package/dist/resource.d.ts +0 -20
- package/dist/resource.d.ts.map +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/vite.dev.js +41 -35
- package/dist/vite.js +41 -35
- package/package.json +3 -3
package/dist/index.dev.js
CHANGED
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
effect,
|
|
5
5
|
isRendering,
|
|
6
6
|
onCleanup,
|
|
7
|
-
untrack,
|
|
8
7
|
useContext
|
|
9
8
|
} from "@firsthandjs/core";
|
|
10
9
|
|
|
@@ -57,85 +56,91 @@ function readable(value) {
|
|
|
57
56
|
|
|
58
57
|
// packages/data/src/store.ts
|
|
59
58
|
function createData(options = {}) {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
const state = {
|
|
60
|
+
options,
|
|
61
|
+
held: /* @__PURE__ */ new Set(),
|
|
62
|
+
recent: [],
|
|
63
|
+
remember: options.remember ?? 6e4
|
|
64
|
+
};
|
|
64
65
|
return {
|
|
65
66
|
storage: options.storage,
|
|
66
|
-
missed: (entry, tags) =>
|
|
67
|
-
if (remember === 0 || recent.length === 0) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
const since = now() - remember;
|
|
71
|
-
recent = recent.filter((one) => one.at >= since);
|
|
72
|
-
return recent.some(
|
|
73
|
-
// Younger than this resource's last answer, and about what it has just
|
|
74
|
-
// said it is about. A resource that has answered *since* the
|
|
75
|
-
// invalidation has already taken it into account.
|
|
76
|
-
(one) => one.at > entry.answeredAt && anyTagMatches(one.patterns, tags)
|
|
77
|
-
);
|
|
78
|
-
},
|
|
67
|
+
missed: (entry, tags) => missed(state, entry, tags),
|
|
79
68
|
settled: (tags) => {
|
|
80
|
-
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
recent = recent.filter((one) => !anyTagMatches(one.patterns, tags));
|
|
84
|
-
},
|
|
85
|
-
settle: async (passes = 10) => {
|
|
86
|
-
for (let pass = 0; pass < passes; pass++) {
|
|
87
|
-
const waiting = [];
|
|
88
|
-
for (const entry of held) {
|
|
89
|
-
if (entry.inflight !== null) {
|
|
90
|
-
waiting.push(entry.inflight);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
if (waiting.length === 0) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
await Promise.all(waiting);
|
|
97
|
-
}
|
|
69
|
+
settled(state, tags);
|
|
98
70
|
},
|
|
71
|
+
settle: (passes = 10) => settle(state, passes),
|
|
99
72
|
hold: (entry) => {
|
|
100
|
-
held.add(entry);
|
|
101
|
-
return () => held.delete(entry);
|
|
102
|
-
},
|
|
103
|
-
invalidate: async (...patterns) => {
|
|
104
|
-
const waiting = [];
|
|
105
|
-
for (const cache of options.caches ?? []) {
|
|
106
|
-
cache.forgetTagged(patterns);
|
|
107
|
-
}
|
|
108
|
-
if (remember > 0) {
|
|
109
|
-
recent.push({ patterns, at: now() });
|
|
110
|
-
}
|
|
111
|
-
for (const entry of [...held]) {
|
|
112
|
-
if (entry.controller !== null) {
|
|
113
|
-
entry.pending.push(...patterns);
|
|
114
|
-
}
|
|
115
|
-
if (!anyTagMatches(patterns, entry.tags)) {
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
devQuery("invalidated", entry.name ?? "(call site)", entry.tags);
|
|
119
|
-
waiting.push(entry.run(true));
|
|
120
|
-
}
|
|
121
|
-
await Promise.all(waiting);
|
|
73
|
+
state.held.add(entry);
|
|
74
|
+
return () => state.held.delete(entry);
|
|
122
75
|
},
|
|
76
|
+
invalidate: (...patterns) => invalidate(state, patterns),
|
|
123
77
|
clear: () => {
|
|
124
|
-
|
|
125
|
-
for (const entry of [...held]) {
|
|
126
|
-
entry.controller?.abort();
|
|
127
|
-
held.delete(entry);
|
|
128
|
-
}
|
|
129
|
-
try {
|
|
130
|
-
options.storage?.clear?.();
|
|
131
|
-
} catch {
|
|
132
|
-
}
|
|
78
|
+
clear(state);
|
|
133
79
|
},
|
|
134
80
|
get size() {
|
|
135
|
-
return held.size;
|
|
81
|
+
return state.held.size;
|
|
136
82
|
}
|
|
137
83
|
};
|
|
138
84
|
}
|
|
85
|
+
function missed(state, entry, tags) {
|
|
86
|
+
if (state.remember === 0 || state.recent.length === 0) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const since = Date.now() - state.remember;
|
|
90
|
+
state.recent = state.recent.filter((one) => one.at >= since);
|
|
91
|
+
return state.recent.some((one) => one.at > entry.answeredAt && anyTagMatches(one.patterns, tags));
|
|
92
|
+
}
|
|
93
|
+
function settled(state, tags) {
|
|
94
|
+
if (state.recent.length === 0) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
state.recent = state.recent.filter((one) => !anyTagMatches(one.patterns, tags));
|
|
98
|
+
}
|
|
99
|
+
async function settle(state, passes) {
|
|
100
|
+
for (let pass = 0; pass < passes; pass++) {
|
|
101
|
+
const waiting = [];
|
|
102
|
+
for (const entry of state.held) {
|
|
103
|
+
if (entry.inflight !== null) {
|
|
104
|
+
waiting.push(entry.inflight);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (waiting.length === 0) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
await Promise.all(waiting);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function invalidate(state, patterns) {
|
|
114
|
+
const waiting = [];
|
|
115
|
+
for (const cache of state.options.caches ?? []) {
|
|
116
|
+
cache.forgetTagged(patterns);
|
|
117
|
+
}
|
|
118
|
+
if (state.remember > 0) {
|
|
119
|
+
state.recent.push({ patterns, at: Date.now() });
|
|
120
|
+
}
|
|
121
|
+
for (const entry of [...state.held]) {
|
|
122
|
+
if (entry.controller !== null) {
|
|
123
|
+
entry.pending.push(...patterns);
|
|
124
|
+
}
|
|
125
|
+
if (!anyTagMatches(patterns, entry.tags)) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
devQuery("invalidated", entry.name ?? "(call site)", entry.tags);
|
|
129
|
+
waiting.push(entry.run(true));
|
|
130
|
+
}
|
|
131
|
+
await Promise.all(waiting);
|
|
132
|
+
}
|
|
133
|
+
function clear(state) {
|
|
134
|
+
state.recent = [];
|
|
135
|
+
for (const entry of [...state.held]) {
|
|
136
|
+
entry.controller?.abort();
|
|
137
|
+
state.held.delete(entry);
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
state.options.storage?.clear?.();
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
}
|
|
139
144
|
function createHeld(store, name) {
|
|
140
145
|
const entry = {
|
|
141
146
|
data: signal(void 0),
|
|
@@ -208,187 +213,143 @@ function useResource(load, options = {}) {
|
|
|
208
213
|
const entry = createHeld(store, options.persist);
|
|
209
214
|
const release = store.hold(entry);
|
|
210
215
|
devQuery("created", options.persist ?? "(call site)", []);
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
216
|
+
const loading = { entry, store, persist: options.persist };
|
|
217
|
+
entry.run = (force) => runOnce(loading, load, force);
|
|
218
|
+
seedFromStorage(entry, store, options.persist);
|
|
219
|
+
startLoading(entry);
|
|
220
|
+
onCleanup(() => {
|
|
221
|
+
entry.disposed = true;
|
|
215
222
|
entry.controller?.abort();
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
};
|
|
238
|
-
const request = {
|
|
239
|
-
signal: controller.signal,
|
|
240
|
-
get force() {
|
|
241
|
-
asked = true;
|
|
242
|
-
return forced;
|
|
243
|
-
},
|
|
244
|
-
// Read after `tags()` by a cache that keeps them, which is every client
|
|
245
|
-
// in this project: they declare first and look in their cache second.
|
|
246
|
-
get declared() {
|
|
247
|
-
return entry.tags;
|
|
248
|
-
},
|
|
249
|
-
tags: declare
|
|
250
|
-
};
|
|
251
|
-
const context = {
|
|
252
|
-
signal: controller.signal,
|
|
253
|
-
get force() {
|
|
254
|
-
asked = true;
|
|
255
|
-
return forced;
|
|
256
|
-
},
|
|
257
|
-
request,
|
|
258
|
-
tags: declare
|
|
259
|
-
};
|
|
260
|
-
const running = load(context).then(async (value) => {
|
|
261
|
-
if (controller.signal.aborted || entry.disposed) {
|
|
262
|
-
return void 0;
|
|
263
|
-
}
|
|
264
|
-
entry.controller = null;
|
|
265
|
-
succeed(entry, value);
|
|
266
|
-
if (!entry.superseded) {
|
|
267
|
-
store.settled(entry.tags);
|
|
268
|
-
}
|
|
269
|
-
if (options.persist !== void 0) {
|
|
270
|
-
try {
|
|
271
|
-
store.storage?.write?.(options.persist, value);
|
|
272
|
-
} catch {
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
if (entry.superseded) {
|
|
276
|
-
entry.superseded = false;
|
|
277
|
-
return entry.run(true);
|
|
278
|
-
}
|
|
279
|
-
return value;
|
|
280
|
-
}).catch((error) => {
|
|
281
|
-
if (controller.signal.aborted || entry.disposed) {
|
|
282
|
-
return void 0;
|
|
283
|
-
}
|
|
284
|
-
entry.controller = null;
|
|
285
|
-
fail(entry, error);
|
|
223
|
+
devQuery("dropped", options.persist ?? "(call site)", entry.tags);
|
|
224
|
+
release();
|
|
225
|
+
});
|
|
226
|
+
return expose(entry, release);
|
|
227
|
+
}
|
|
228
|
+
function runOnce(loading, load, force) {
|
|
229
|
+
const { entry, store } = loading;
|
|
230
|
+
if (entry.disposed) {
|
|
231
|
+
return Promise.resolve(void 0);
|
|
232
|
+
}
|
|
233
|
+
entry.controller?.abort();
|
|
234
|
+
const controller = new AbortController();
|
|
235
|
+
entry.controller = controller;
|
|
236
|
+
entry.pending = [];
|
|
237
|
+
entry.superseded = false;
|
|
238
|
+
entry.loading.value = true;
|
|
239
|
+
if (entry.data.peek() === void 0) {
|
|
240
|
+
entry.status.value = "loading";
|
|
241
|
+
}
|
|
242
|
+
const running = load(contextFor(entry, store, controller, force)).then(async (value) => answered(loading, controller, value)).catch((error) => {
|
|
243
|
+
if (controller.signal.aborted || entry.disposed) {
|
|
286
244
|
return void 0;
|
|
287
|
-
}
|
|
288
|
-
entry.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
245
|
+
}
|
|
246
|
+
entry.controller = null;
|
|
247
|
+
fail(entry, error);
|
|
248
|
+
return void 0;
|
|
249
|
+
});
|
|
250
|
+
entry.inflight = running;
|
|
251
|
+
void running.then(() => {
|
|
252
|
+
if (entry.inflight === running) {
|
|
253
|
+
entry.inflight = null;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
return running;
|
|
257
|
+
}
|
|
258
|
+
async function answered(loading, controller, value) {
|
|
259
|
+
const { entry, store, persist } = loading;
|
|
260
|
+
if (controller.signal.aborted || entry.disposed) {
|
|
261
|
+
return void 0;
|
|
262
|
+
}
|
|
263
|
+
entry.controller = null;
|
|
264
|
+
succeed(entry, value);
|
|
265
|
+
if (!entry.superseded) {
|
|
266
|
+
store.settled(entry.tags);
|
|
267
|
+
}
|
|
268
|
+
if (persist !== void 0) {
|
|
300
269
|
try {
|
|
301
|
-
|
|
270
|
+
store.storage?.write?.(persist, value);
|
|
302
271
|
} catch {
|
|
303
|
-
stored = void 0;
|
|
304
|
-
}
|
|
305
|
-
if (isThenable(stored)) {
|
|
306
|
-
void (async () => {
|
|
307
|
-
try {
|
|
308
|
-
seed(entry, await stored);
|
|
309
|
-
} catch {
|
|
310
|
-
}
|
|
311
|
-
})();
|
|
312
|
-
} else {
|
|
313
|
-
seed(entry, stored);
|
|
314
272
|
}
|
|
315
273
|
}
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
274
|
+
if (entry.superseded) {
|
|
275
|
+
entry.superseded = false;
|
|
276
|
+
return entry.run(true);
|
|
277
|
+
}
|
|
278
|
+
return value;
|
|
279
|
+
}
|
|
280
|
+
function seedFromStorage(entry, store, persist) {
|
|
281
|
+
const storage = store.storage;
|
|
282
|
+
if (persist === void 0 || storage?.read === void 0) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
let stored;
|
|
286
|
+
try {
|
|
287
|
+
stored = storage.read(persist);
|
|
288
|
+
} catch {
|
|
289
|
+
stored = void 0;
|
|
290
|
+
}
|
|
291
|
+
if (!isThenable(stored)) {
|
|
292
|
+
seed(entry, stored);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
void (async () => {
|
|
296
|
+
try {
|
|
297
|
+
seed(entry, await stored);
|
|
298
|
+
} catch {
|
|
321
299
|
}
|
|
322
|
-
}
|
|
300
|
+
})();
|
|
301
|
+
}
|
|
302
|
+
function startLoading(entry) {
|
|
303
|
+
if (!isRendering()) {
|
|
323
304
|
effect(() => {
|
|
324
305
|
void entry.run(false);
|
|
325
306
|
});
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (entry.data.peek() === void 0) {
|
|
310
|
+
void entry.run(false);
|
|
311
|
+
} else {
|
|
312
|
+
entry.loading.value = false;
|
|
326
313
|
}
|
|
327
|
-
onCleanup(() => {
|
|
328
|
-
entry.disposed = true;
|
|
329
|
-
entry.controller?.abort();
|
|
330
|
-
devQuery("dropped", options.persist ?? "(call site)", entry.tags);
|
|
331
|
-
release();
|
|
332
|
-
});
|
|
333
|
-
return expose(entry, release);
|
|
334
314
|
}
|
|
335
|
-
function
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
entry
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
data: entry.data,
|
|
345
|
-
error: entry.error,
|
|
346
|
-
status: entry.status,
|
|
347
|
-
running: entry.loading,
|
|
348
|
-
run: async (input) => {
|
|
349
|
-
controller?.abort();
|
|
350
|
-
controller = new AbortController();
|
|
351
|
-
const current = controller;
|
|
352
|
-
let invalidating = [];
|
|
353
|
-
entry.loading.value = true;
|
|
354
|
-
entry.status.value = "loading";
|
|
355
|
-
try {
|
|
356
|
-
const invalidates = (...tags) => {
|
|
357
|
-
invalidating = tags;
|
|
358
|
-
};
|
|
359
|
-
const result = await untrack(
|
|
360
|
-
() => run(input, {
|
|
361
|
-
signal: current.signal,
|
|
362
|
-
invalidates,
|
|
363
|
-
// An action changes something, so nothing it sends may be
|
|
364
|
-
// answered out of a cache (`force`) or kept in one (`mutating`).
|
|
365
|
-
// `tags` is the store's invalidation, so a client that knows what
|
|
366
|
-
// a mutation changed — a document with `@invalidates` — reports it
|
|
367
|
-
// without the call site repeating it.
|
|
368
|
-
request: {
|
|
369
|
-
signal: current.signal,
|
|
370
|
-
force: true,
|
|
371
|
-
mutating: true,
|
|
372
|
-
tags: invalidates
|
|
373
|
-
}
|
|
374
|
-
})
|
|
375
|
-
);
|
|
376
|
-
if (current.signal.aborted || entry.disposed) {
|
|
377
|
-
return void 0;
|
|
378
|
-
}
|
|
379
|
-
succeed(entry, result);
|
|
380
|
-
if (invalidating.length > 0) {
|
|
381
|
-
await store.invalidate(...invalidating);
|
|
382
|
-
}
|
|
383
|
-
return result;
|
|
384
|
-
} catch (error) {
|
|
385
|
-
if (current.signal.aborted || entry.disposed) {
|
|
386
|
-
return void 0;
|
|
387
|
-
}
|
|
388
|
-
fail(entry, error);
|
|
389
|
-
return void 0;
|
|
315
|
+
function contextFor(entry, store, controller, force) {
|
|
316
|
+
let forced = force;
|
|
317
|
+
let asked = false;
|
|
318
|
+
const declare = (...next) => {
|
|
319
|
+
entry.tags = next;
|
|
320
|
+
if (!forced && store.missed(entry, next)) {
|
|
321
|
+
forced = true;
|
|
322
|
+
if (asked) {
|
|
323
|
+
entry.superseded = true;
|
|
390
324
|
}
|
|
391
325
|
}
|
|
326
|
+
if (entry.pending.length > 0 && anyTagMatches(entry.pending, next)) {
|
|
327
|
+
entry.superseded = true;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
const why = () => {
|
|
331
|
+
asked = true;
|
|
332
|
+
return forced;
|
|
333
|
+
};
|
|
334
|
+
const request = {
|
|
335
|
+
signal: controller.signal,
|
|
336
|
+
get force() {
|
|
337
|
+
return why();
|
|
338
|
+
},
|
|
339
|
+
// Read after `tags()` by a cache that keeps them, which is every client in
|
|
340
|
+
// this project: they declare first and look in their cache second.
|
|
341
|
+
get declared() {
|
|
342
|
+
return entry.tags;
|
|
343
|
+
},
|
|
344
|
+
tags: declare
|
|
345
|
+
};
|
|
346
|
+
return {
|
|
347
|
+
signal: controller.signal,
|
|
348
|
+
get force() {
|
|
349
|
+
return why();
|
|
350
|
+
},
|
|
351
|
+
request,
|
|
352
|
+
tags: declare
|
|
392
353
|
};
|
|
393
354
|
}
|
|
394
355
|
function fromObservable(source, options = {}) {
|
|
@@ -429,6 +390,66 @@ async function void_(promise) {
|
|
|
429
390
|
return void 0;
|
|
430
391
|
}
|
|
431
392
|
|
|
393
|
+
// packages/data/src/action.ts
|
|
394
|
+
import { onCleanup as onCleanup2, untrack } from "@firsthandjs/core";
|
|
395
|
+
function useAction(run) {
|
|
396
|
+
const store = useData();
|
|
397
|
+
const entry = createHeld(store, void 0);
|
|
398
|
+
const holder = { controller: null, invalidating: [] };
|
|
399
|
+
onCleanup2(() => {
|
|
400
|
+
entry.disposed = true;
|
|
401
|
+
holder.controller?.abort();
|
|
402
|
+
});
|
|
403
|
+
return {
|
|
404
|
+
data: entry.data,
|
|
405
|
+
error: entry.error,
|
|
406
|
+
status: entry.status,
|
|
407
|
+
running: entry.loading,
|
|
408
|
+
run: (input) => once(entry, store, holder, () => run(input, contextFor2(holder)))
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function contextFor2(holder) {
|
|
412
|
+
const current = holder.controller;
|
|
413
|
+
const invalidates = (...tags) => {
|
|
414
|
+
holder.invalidating = tags;
|
|
415
|
+
};
|
|
416
|
+
return {
|
|
417
|
+
signal: current.signal,
|
|
418
|
+
invalidates,
|
|
419
|
+
request: {
|
|
420
|
+
signal: current.signal,
|
|
421
|
+
force: true,
|
|
422
|
+
mutating: true,
|
|
423
|
+
tags: invalidates
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
async function once(entry, store, holder, body) {
|
|
428
|
+
holder.controller?.abort();
|
|
429
|
+
holder.controller = new AbortController();
|
|
430
|
+
const current = holder.controller;
|
|
431
|
+
holder.invalidating = [];
|
|
432
|
+
entry.loading.value = true;
|
|
433
|
+
entry.status.value = "loading";
|
|
434
|
+
try {
|
|
435
|
+
const result = await untrack(body);
|
|
436
|
+
if (current.signal.aborted || entry.disposed) {
|
|
437
|
+
return void 0;
|
|
438
|
+
}
|
|
439
|
+
succeed(entry, result);
|
|
440
|
+
if (holder.invalidating.length > 0) {
|
|
441
|
+
await store.invalidate(...holder.invalidating);
|
|
442
|
+
}
|
|
443
|
+
return result;
|
|
444
|
+
} catch (error) {
|
|
445
|
+
if (current.signal.aborted || entry.disposed) {
|
|
446
|
+
return void 0;
|
|
447
|
+
}
|
|
448
|
+
fail(entry, error);
|
|
449
|
+
return void 0;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
432
453
|
// packages/data/src/cache.ts
|
|
433
454
|
function stableKey(value) {
|
|
434
455
|
if (value === void 0) {
|
|
@@ -447,142 +468,151 @@ function stableKey(value) {
|
|
|
447
468
|
return `{${entries.map(([name, held]) => `${name}:${stableKey(held)}`).join(",")}}`;
|
|
448
469
|
}
|
|
449
470
|
function createCacheClient(options = {}) {
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
entries.delete(key);
|
|
458
|
-
};
|
|
459
|
-
const put = (key, value) => {
|
|
460
|
-
keep(key, {
|
|
461
|
-
value,
|
|
462
|
-
tags: [],
|
|
463
|
-
expires: ttl === 0 ? Infinity : now() + ttl,
|
|
464
|
-
inflight: null,
|
|
465
|
-
controller: null,
|
|
466
|
-
waiting: 0
|
|
467
|
-
});
|
|
471
|
+
const store = {
|
|
472
|
+
ttl: options.ttl ?? 0,
|
|
473
|
+
max: options.max ?? 100,
|
|
474
|
+
now: options.now ?? (() => Date.now()),
|
|
475
|
+
// Insertion order is the eviction order, and a read moves an entry to the
|
|
476
|
+
// end: a `Map` already keeps that order, so there is no list to maintain.
|
|
477
|
+
entries: /* @__PURE__ */ new Map()
|
|
468
478
|
};
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
entries.set(key, entry);
|
|
472
|
-
if (entries.size > max) {
|
|
473
|
-
for (const oldest of entries.keys()) {
|
|
474
|
-
drop(oldest);
|
|
475
|
-
break;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
479
|
+
const write = (key, value) => {
|
|
480
|
+
put(store, key, value);
|
|
478
481
|
};
|
|
479
482
|
return {
|
|
480
483
|
get size() {
|
|
481
|
-
return entries.size;
|
|
482
|
-
},
|
|
483
|
-
dump: () => {
|
|
484
|
-
const out = {};
|
|
485
|
-
for (const [key, entry] of entries) {
|
|
486
|
-
if (entry.inflight === null) {
|
|
487
|
-
out[key] = entry.value;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
return out;
|
|
484
|
+
return store.entries.size;
|
|
491
485
|
},
|
|
486
|
+
dump: () => dump(store),
|
|
492
487
|
seed: (values) => {
|
|
493
488
|
for (const key in values) {
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
},
|
|
497
|
-
settle: async (passes = 10) => {
|
|
498
|
-
for (let pass = 0; pass < passes; pass++) {
|
|
499
|
-
const waiting = [];
|
|
500
|
-
for (const entry of entries.values()) {
|
|
501
|
-
if (entry.inflight !== null) {
|
|
502
|
-
waiting.push(entry.inflight);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
if (waiting.length === 0) {
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
await Promise.all(waiting.map(async (one) => one.catch(() => void 0)));
|
|
489
|
+
write(key, values[key]);
|
|
509
490
|
}
|
|
510
491
|
},
|
|
492
|
+
settle: (passes = 10) => settle2(store, passes),
|
|
511
493
|
peek: (key) => {
|
|
512
|
-
const entry = entries.get(key);
|
|
513
|
-
|
|
514
|
-
return void 0;
|
|
515
|
-
}
|
|
516
|
-
return entry.value;
|
|
494
|
+
const entry = store.entries.get(key);
|
|
495
|
+
return entry === void 0 || entry.expires <= store.now() ? void 0 : entry.value;
|
|
517
496
|
},
|
|
518
|
-
write
|
|
497
|
+
write,
|
|
519
498
|
forget: (key) => {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
drop(held);
|
|
523
|
-
}
|
|
524
|
-
return;
|
|
499
|
+
for (const held of key === void 0 ? [...store.entries.keys()] : [key]) {
|
|
500
|
+
drop(store, held);
|
|
525
501
|
}
|
|
526
|
-
drop(key);
|
|
527
502
|
},
|
|
528
503
|
forgetTagged: (patterns) => {
|
|
529
|
-
for (const [key, entry] of [...entries]) {
|
|
504
|
+
for (const [key, entry] of [...store.entries]) {
|
|
530
505
|
if (entry.tags.length > 0 && anyTagMatches(patterns, entry.tags)) {
|
|
531
|
-
drop(key);
|
|
506
|
+
drop(store, key);
|
|
532
507
|
}
|
|
533
508
|
}
|
|
534
509
|
},
|
|
535
|
-
read: (key, produce) => async (request) =>
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
510
|
+
read: (key, produce) => async (request) => read(store, key, produce, request)
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
function drop(store, key) {
|
|
514
|
+
const entry = store.entries.get(key);
|
|
515
|
+
entry?.controller?.abort();
|
|
516
|
+
store.entries.delete(key);
|
|
517
|
+
}
|
|
518
|
+
function keep(store, key, entry) {
|
|
519
|
+
store.entries.delete(key);
|
|
520
|
+
store.entries.set(key, entry);
|
|
521
|
+
if (store.entries.size > store.max) {
|
|
522
|
+
for (const oldest of store.entries.keys()) {
|
|
523
|
+
drop(store, oldest);
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function put(store, key, value) {
|
|
529
|
+
keep(store, key, {
|
|
530
|
+
value,
|
|
531
|
+
tags: [],
|
|
532
|
+
expires: store.ttl === 0 ? Infinity : store.now() + store.ttl,
|
|
533
|
+
inflight: null,
|
|
534
|
+
controller: null,
|
|
535
|
+
waiting: 0
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
function dump(store) {
|
|
539
|
+
const out = {};
|
|
540
|
+
for (const [key, entry] of store.entries) {
|
|
541
|
+
if (entry.inflight === null) {
|
|
542
|
+
out[key] = entry.value;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return out;
|
|
546
|
+
}
|
|
547
|
+
async function settle2(store, passes) {
|
|
548
|
+
for (let pass = 0; pass < passes; pass++) {
|
|
549
|
+
const waiting = [];
|
|
550
|
+
for (const entry of store.entries.values()) {
|
|
551
|
+
if (entry.inflight !== null) {
|
|
552
|
+
waiting.push(entry.inflight);
|
|
551
553
|
}
|
|
552
|
-
const controller = new AbortController();
|
|
553
|
-
const entry = {
|
|
554
|
-
value: void 0,
|
|
555
|
-
// What the request has been declared to be about by now. A client
|
|
556
|
-
// declares before it looks here, which is what makes this possible.
|
|
557
|
-
tags: request.declared ?? [],
|
|
558
|
-
expires: 0,
|
|
559
|
-
inflight: null,
|
|
560
|
-
controller,
|
|
561
|
-
waiting: 0
|
|
562
|
-
};
|
|
563
|
-
const run = produce({ signal: controller.signal, force: request.force }).then((value) => {
|
|
564
|
-
if (entries.get(key) === entry) {
|
|
565
|
-
if (ttl === 0) {
|
|
566
|
-
entries.delete(key);
|
|
567
|
-
} else {
|
|
568
|
-
entry.value = value;
|
|
569
|
-
entry.expires = now() + ttl;
|
|
570
|
-
entry.inflight = null;
|
|
571
|
-
entry.controller = null;
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
return value;
|
|
575
|
-
}).catch((error) => {
|
|
576
|
-
if (entries.get(key) === entry) {
|
|
577
|
-
entries.delete(key);
|
|
578
|
-
}
|
|
579
|
-
throw error;
|
|
580
|
-
});
|
|
581
|
-
entry.inflight = run;
|
|
582
|
-
keep(key, entry);
|
|
583
|
-
return await share(entry, request);
|
|
584
554
|
}
|
|
555
|
+
if (waiting.length === 0) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
await Promise.all(waiting.map(async (one) => one.catch(() => void 0)));
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
async function read(store, key, produce, request) {
|
|
562
|
+
if (request.mutating === true) {
|
|
563
|
+
return await produce(request);
|
|
564
|
+
}
|
|
565
|
+
const held = store.entries.get(key);
|
|
566
|
+
if (request.force) {
|
|
567
|
+
drop(store, key);
|
|
568
|
+
} else if (held !== void 0) {
|
|
569
|
+
if (held.inflight !== null) {
|
|
570
|
+
return await share(held, request);
|
|
571
|
+
}
|
|
572
|
+
if (held.expires > store.now()) {
|
|
573
|
+
keep(store, key, held);
|
|
574
|
+
return held.value;
|
|
575
|
+
}
|
|
576
|
+
store.entries.delete(key);
|
|
577
|
+
}
|
|
578
|
+
return await start(store, key, produce, request);
|
|
579
|
+
}
|
|
580
|
+
async function start(store, key, produce, request) {
|
|
581
|
+
const controller = new AbortController();
|
|
582
|
+
const entry = {
|
|
583
|
+
value: void 0,
|
|
584
|
+
// What the request has been declared to be about by now. A client declares
|
|
585
|
+
// before it looks here, which is what makes this possible.
|
|
586
|
+
tags: request.declared ?? [],
|
|
587
|
+
expires: 0,
|
|
588
|
+
inflight: null,
|
|
589
|
+
controller,
|
|
590
|
+
waiting: 0
|
|
585
591
|
};
|
|
592
|
+
const run = produce({ signal: controller.signal, force: request.force }).then((value) => {
|
|
593
|
+
if (store.entries.get(key) === entry) {
|
|
594
|
+
settleEntry(store, key, entry, value);
|
|
595
|
+
}
|
|
596
|
+
return value;
|
|
597
|
+
}).catch((error) => {
|
|
598
|
+
if (store.entries.get(key) === entry) {
|
|
599
|
+
store.entries.delete(key);
|
|
600
|
+
}
|
|
601
|
+
throw error;
|
|
602
|
+
});
|
|
603
|
+
entry.inflight = run;
|
|
604
|
+
keep(store, key, entry);
|
|
605
|
+
return await share(entry, request);
|
|
606
|
+
}
|
|
607
|
+
function settleEntry(store, key, entry, value) {
|
|
608
|
+
if (store.ttl === 0) {
|
|
609
|
+
store.entries.delete(key);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
entry.value = value;
|
|
613
|
+
entry.expires = store.now() + store.ttl;
|
|
614
|
+
entry.inflight = null;
|
|
615
|
+
entry.controller = null;
|
|
586
616
|
}
|
|
587
617
|
async function share(entry, request) {
|
|
588
618
|
entry.waiting += 1;
|
|
@@ -642,20 +672,20 @@ var FirsthandDirectiveError = class extends Error {
|
|
|
642
672
|
this.name = "FirsthandDirectiveError";
|
|
643
673
|
}
|
|
644
674
|
};
|
|
645
|
-
function endOfString(source,
|
|
646
|
-
if (source.startsWith('"""',
|
|
647
|
-
const close = source.indexOf('"""',
|
|
675
|
+
function endOfString(source, start2) {
|
|
676
|
+
if (source.startsWith('"""', start2)) {
|
|
677
|
+
const close = source.indexOf('"""', start2 + 3);
|
|
648
678
|
return close === -1 ? source.length : close + 3;
|
|
649
679
|
}
|
|
650
|
-
let at =
|
|
680
|
+
let at = start2 + 1;
|
|
651
681
|
while (at < source.length && source[at] !== '"') {
|
|
652
682
|
at += source[at] === "\\" ? 2 : 1;
|
|
653
683
|
}
|
|
654
684
|
return at + 1;
|
|
655
685
|
}
|
|
656
|
-
function endOfArguments(source,
|
|
686
|
+
function endOfArguments(source, start2) {
|
|
657
687
|
let depth = 0;
|
|
658
|
-
let at =
|
|
688
|
+
let at = start2;
|
|
659
689
|
while (at < source.length) {
|
|
660
690
|
const character = source[at];
|
|
661
691
|
if (character === '"') {
|
|
@@ -672,7 +702,7 @@ function endOfArguments(source, start) {
|
|
|
672
702
|
}
|
|
673
703
|
at++;
|
|
674
704
|
}
|
|
675
|
-
throw new FirsthandDirectiveError(`unclosed arguments in ${source.slice(
|
|
705
|
+
throw new FirsthandDirectiveError(`unclosed arguments in ${source.slice(start2, start2 + 40)}`);
|
|
676
706
|
}
|
|
677
707
|
function unquote(quoted, directive) {
|
|
678
708
|
try {
|
|
@@ -704,9 +734,9 @@ function parseArguments(raw, directive) {
|
|
|
704
734
|
}
|
|
705
735
|
at++;
|
|
706
736
|
skipIgnored();
|
|
707
|
-
const
|
|
708
|
-
vars[name[0]] =
|
|
709
|
-
at =
|
|
737
|
+
const read2 = readValue(raw, directive, at);
|
|
738
|
+
vars[name[0]] = read2.value;
|
|
739
|
+
at = read2.next;
|
|
710
740
|
skipIgnored();
|
|
711
741
|
}
|
|
712
742
|
return vars;
|
|
@@ -747,24 +777,14 @@ function readValue(raw, directive, at) {
|
|
|
747
777
|
return { value: { literal: Number.isNaN(asNumber) ? text : asNumber }, next };
|
|
748
778
|
}
|
|
749
779
|
function scan(source) {
|
|
750
|
-
const
|
|
751
|
-
const invalidates = [];
|
|
780
|
+
const into = { tags: [], invalidates: [] };
|
|
752
781
|
let stripped = "";
|
|
753
782
|
let at = 0;
|
|
754
783
|
let kept = 0;
|
|
755
784
|
while (at < source.length) {
|
|
756
|
-
const
|
|
757
|
-
if (
|
|
758
|
-
at =
|
|
759
|
-
continue;
|
|
760
|
-
}
|
|
761
|
-
if (character === "#") {
|
|
762
|
-
const newline = source.indexOf("\n", at);
|
|
763
|
-
at = newline === -1 ? source.length : newline;
|
|
764
|
-
continue;
|
|
765
|
-
}
|
|
766
|
-
if (character !== "@") {
|
|
767
|
-
at++;
|
|
785
|
+
const skipped = skipPast(source, at);
|
|
786
|
+
if (skipped !== null) {
|
|
787
|
+
at = skipped;
|
|
768
788
|
continue;
|
|
769
789
|
}
|
|
770
790
|
const directive = DIRECTIVE.exec(source.slice(at));
|
|
@@ -772,26 +792,7 @@ function scan(source) {
|
|
|
772
792
|
at++;
|
|
773
793
|
continue;
|
|
774
794
|
}
|
|
775
|
-
|
|
776
|
-
let args = "";
|
|
777
|
-
let probe = end;
|
|
778
|
-
while (probe < source.length && /\s/.test(source[probe])) {
|
|
779
|
-
probe++;
|
|
780
|
-
}
|
|
781
|
-
if (source[probe] === "(") {
|
|
782
|
-
const close = endOfArguments(source, probe);
|
|
783
|
-
args = source.slice(probe + 1, close - 1);
|
|
784
|
-
end = close;
|
|
785
|
-
}
|
|
786
|
-
const vars = parseArguments(args, directive[1]);
|
|
787
|
-
const named = vars["name"];
|
|
788
|
-
if (named === void 0 || !("literal" in named) || typeof named.literal !== "string") {
|
|
789
|
-
throw new FirsthandDirectiveError(
|
|
790
|
-
`@${directive[1]} needs a literal name, as in @${directive[1]}(name: "user", id: $id)`
|
|
791
|
-
);
|
|
792
|
-
}
|
|
793
|
-
delete vars["name"];
|
|
794
|
-
(directive[1] === "tag" ? tags : invalidates).push({ name: named.literal, vars });
|
|
795
|
+
const end = takeDirective(source, at, directive, into);
|
|
795
796
|
let from = at;
|
|
796
797
|
while (from > kept && /\s/.test(source[from - 1])) {
|
|
797
798
|
from--;
|
|
@@ -800,7 +801,42 @@ function scan(source) {
|
|
|
800
801
|
kept = end;
|
|
801
802
|
at = end;
|
|
802
803
|
}
|
|
803
|
-
return {
|
|
804
|
+
return { ...into, stripped: stripped + source.slice(kept) };
|
|
805
|
+
}
|
|
806
|
+
function skipPast(source, at) {
|
|
807
|
+
const character = source[at];
|
|
808
|
+
if (character === '"') {
|
|
809
|
+
return endOfString(source, at);
|
|
810
|
+
}
|
|
811
|
+
if (character === "#") {
|
|
812
|
+
const newline = source.indexOf("\n", at);
|
|
813
|
+
return newline === -1 ? source.length : newline;
|
|
814
|
+
}
|
|
815
|
+
return character === "@" ? null : at + 1;
|
|
816
|
+
}
|
|
817
|
+
function takeDirective(source, at, directive, into) {
|
|
818
|
+
const kind = directive[1];
|
|
819
|
+
let end = at + directive[0].length;
|
|
820
|
+
let args = "";
|
|
821
|
+
let probe = end;
|
|
822
|
+
while (probe < source.length && /\s/.test(source[probe])) {
|
|
823
|
+
probe++;
|
|
824
|
+
}
|
|
825
|
+
if (source[probe] === "(") {
|
|
826
|
+
const close = endOfArguments(source, probe);
|
|
827
|
+
args = source.slice(probe + 1, close - 1);
|
|
828
|
+
end = close;
|
|
829
|
+
}
|
|
830
|
+
const vars = parseArguments(args, kind);
|
|
831
|
+
const named = vars["name"];
|
|
832
|
+
if (named === void 0 || !("literal" in named) || typeof named.literal !== "string") {
|
|
833
|
+
throw new FirsthandDirectiveError(
|
|
834
|
+
`@${kind} needs a literal name, as in @${kind}(name: "user", id: $id)`
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
delete vars["name"];
|
|
838
|
+
(kind === "tag" ? into.tags : into.invalidates).push({ name: named.literal, vars });
|
|
839
|
+
return end;
|
|
804
840
|
}
|
|
805
841
|
function parseGraphQL(source) {
|
|
806
842
|
const { tags, invalidates, stripped } = scan(source);
|
|
@@ -876,17 +912,26 @@ async function send(options, url, init, request) {
|
|
|
876
912
|
}
|
|
877
913
|
return parsed;
|
|
878
914
|
}
|
|
915
|
+
function cacheKeyFor(options, init, target, merged) {
|
|
916
|
+
if (init.cacheKey === false) {
|
|
917
|
+
return void 0;
|
|
918
|
+
}
|
|
919
|
+
const method = (merged.method ?? "GET").toUpperCase();
|
|
920
|
+
const named = typeof init.cacheKey === "string" ? init.cacheKey : method === "GET" || method === "HEAD" ? `${method} ${target}` : void 0;
|
|
921
|
+
if (named === void 0) {
|
|
922
|
+
return void 0;
|
|
923
|
+
}
|
|
924
|
+
const scope = options.scope === void 0 ? merged.headers.get("authorization") ?? "" : untrack2(options.scope);
|
|
925
|
+
return `${scope}\0${named}`;
|
|
926
|
+
}
|
|
879
927
|
function createFetchClient(options = {}) {
|
|
880
928
|
const cache = options.cache === void 0 || options.cache === false ? void 0 : "read" in options.cache ? options.cache : createCacheClient(options.cache);
|
|
881
929
|
const client = {
|
|
882
930
|
cache,
|
|
883
931
|
request: (url, init = {}) => async (request) => {
|
|
884
932
|
const [target, merged] = resolve(options, url, init);
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
const named = typeof init.cacheKey === "string" ? init.cacheKey : method === "GET" || method === "HEAD" ? `${method} ${target}` : void 0;
|
|
888
|
-
const key = named === void 0 ? void 0 : `${scope}\0${named}`;
|
|
889
|
-
if (cache === void 0 || init.cacheKey === false || key === void 0 || // An action. Even a `cacheKey` does not put its answer in here: the
|
|
933
|
+
const key = cacheKeyFor(options, init, target, merged);
|
|
934
|
+
if (cache === void 0 || key === void 0 || // An action. Even a `cacheKey` does not put its answer in here: the
|
|
890
935
|
// call site asked for a key, not for its writes to be remembered.
|
|
891
936
|
request.mutating === true) {
|
|
892
937
|
return await send(options, target, merged, request);
|