shelving 1.179.0 → 1.179.2
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/api/endpoint/util.d.ts +13 -3
- package/api/endpoint/util.js +15 -8
- package/api/provider/MockAPIProvider.d.ts +2 -7
- package/api/provider/MockAPIProvider.js +1 -1
- package/cloudflare/CloudflareKVProvider.d.ts +13 -31
- package/cloudflare/CloudflareKVProvider.js +21 -59
- package/db/provider/CacheDBProvider.js +11 -11
- package/db/provider/ChangesDBProvider.js +1 -1
- package/db/provider/MemoryDBProvider.d.ts +14 -16
- package/db/provider/MemoryDBProvider.js +67 -108
- package/db/store/ItemStore.js +5 -7
- package/db/store/QueryStore.js +5 -7
- package/package.json +34 -34
package/api/endpoint/util.d.ts
CHANGED
|
@@ -3,8 +3,13 @@ import { type PossibleURL } from "../../util/url.js";
|
|
|
3
3
|
import type { Endpoint } from "./Endpoint.js";
|
|
4
4
|
/**
|
|
5
5
|
* A function that handles an endpoint request, with a payload and returns a result.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* @param payload The payload for the callback combining the `{placeholders}`, `?search` params, and body content (this has been validated against the Endpoint's payload schema).
|
|
8
|
+
* @param request The original incoming request object.
|
|
9
|
+
* @param args Any additional arguments to pass into the endpoint callback
|
|
10
|
+
*
|
|
11
|
+
* @returns {Response} Returning a `Response` object (this will pass back to the client without validation).
|
|
12
|
+
* @returns {R} Returning the return type of the handler (this will be validated against the Endpoint's result schema).
|
|
8
13
|
*/
|
|
9
14
|
export type EndpointCallback<P, R, A extends Arguments = []> = (payload: P, request: Request, ...args: A) => R | Response | Promise<R | Response>;
|
|
10
15
|
/** A typed endpoint definition paired with its implementation callback. */
|
|
@@ -12,6 +17,7 @@ export interface EndpointHandler<P = unknown, R = unknown, A extends Arguments =
|
|
|
12
17
|
readonly endpoint: Endpoint<P, R>;
|
|
13
18
|
readonly callback: EndpointCallback<P, R, A>;
|
|
14
19
|
}
|
|
20
|
+
/** Any endpoint handler. */
|
|
15
21
|
export type AnyEndpointHandler<A extends Arguments = []> = EndpointHandler<any, any, A>;
|
|
16
22
|
/** A collection of endpoint handlers that can be matched and invoked by `handleEndpoints()`. */
|
|
17
23
|
export type EndpointHandlers<A extends Arguments = []> = Iterable<AnyEndpointHandler<A>>;
|
|
@@ -19,6 +25,10 @@ export type EndpointHandlers<A extends Arguments = []> = Iterable<AnyEndpointHan
|
|
|
19
25
|
* Handle a `Request` with the first matching endpoint handler after stripping any base-path prefix from the request pathname.
|
|
20
26
|
* - The original `Request` object is passed through to the callback unchanged.
|
|
21
27
|
* - Path params and query params are merged before payload validation.
|
|
22
|
-
*
|
|
28
|
+
*
|
|
29
|
+
* @param request The input request to handle.
|
|
30
|
+
*
|
|
31
|
+
* @param base The base URL for the API, e.g. `https://myapi.com/a/b`
|
|
32
|
+
* - `pathname` of this URL gets trimmed from `request.path` to form the target path when matching against endpoints, e.g. `/a/b/c/d` will produce `/c/d` for matching.
|
|
23
33
|
*/
|
|
24
34
|
export declare function handleEndpoints<A extends Arguments = []>(request: Request, base: PossibleURL, handlers: EndpointHandlers<A>, ...args: A): Promise<Response>;
|
package/api/endpoint/util.js
CHANGED
|
@@ -8,7 +8,11 @@ import { requireURL } from "../../util/url.js";
|
|
|
8
8
|
* Handle a `Request` with the first matching endpoint handler after stripping any base-path prefix from the request pathname.
|
|
9
9
|
* - The original `Request` object is passed through to the callback unchanged.
|
|
10
10
|
* - Path params and query params are merged before payload validation.
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
|
+
* @param request The input request to handle.
|
|
13
|
+
*
|
|
14
|
+
* @param base The base URL for the API, e.g. `https://myapi.com/a/b`
|
|
15
|
+
* - `pathname` of this URL gets trimmed from `request.path` to form the target path when matching against endpoints, e.g. `/a/b/c/d` will produce `/c/d` for matching.
|
|
12
16
|
*/
|
|
13
17
|
export function handleEndpoints(request, base, handlers, ...args) {
|
|
14
18
|
const caller = handleEndpoints;
|
|
@@ -18,27 +22,31 @@ export function handleEndpoints(request, base, handlers, ...args) {
|
|
|
18
22
|
const { origin: baseOrigin, pathname: basePath } = requireURL(base, undefined, caller);
|
|
19
23
|
const { origin: requestOrigin, pathname: requestPath, searchParams } = requireURL(url, base, caller);
|
|
20
24
|
if (baseOrigin !== requestOrigin)
|
|
21
|
-
throw new NotFoundError("No matching origin", { expected: baseOrigin, received: requestOrigin, caller });
|
|
22
|
-
const targetPath = _stripPathPrefix(requestPath, basePath
|
|
25
|
+
throw new NotFoundError("No matching base origin", { expected: baseOrigin, received: requestOrigin, caller });
|
|
26
|
+
const targetPath = _stripPathPrefix(requestPath, basePath);
|
|
27
|
+
if (!targetPath)
|
|
28
|
+
throw new NotFoundError("No matching base path", { received: requestPath, expected: basePath, caller });
|
|
23
29
|
for (const handler of handlers) {
|
|
24
30
|
const pathParams = handler.endpoint.match(method, targetPath, caller);
|
|
25
31
|
if (!pathParams)
|
|
26
32
|
continue;
|
|
27
33
|
const params = searchParams.size ? { ...getDictionary(searchParams), ...pathParams } : pathParams;
|
|
28
|
-
return
|
|
34
|
+
return _handleEndpoint(handler, params, request, args, handleEndpoints);
|
|
29
35
|
}
|
|
30
36
|
throw new NotFoundError("No matching endpoint", { received: targetPath, caller });
|
|
31
37
|
}
|
|
32
38
|
/**
|
|
33
39
|
* Validate and invoke an endpoint callback after the routing layer has already matched URL params.
|
|
34
40
|
*/
|
|
35
|
-
async function
|
|
41
|
+
async function _handleEndpoint({ endpoint, callback },
|
|
36
42
|
/** Params we already matched/parsed from the URL. */
|
|
37
|
-
params, request, args, caller =
|
|
43
|
+
params, request, args, caller = _handleEndpoint) {
|
|
38
44
|
const content = await getRequestContent(request, caller);
|
|
39
45
|
const unsafePayload = content === undefined ? params : isPlainObject(content) ? { ...content, ...params } : content;
|
|
40
46
|
const payload = endpoint.payload.validate(unsafePayload);
|
|
41
47
|
const unsafeResult = await callback(payload, request, ...args);
|
|
48
|
+
if (unsafeResult instanceof Response)
|
|
49
|
+
return unsafeResult;
|
|
42
50
|
try {
|
|
43
51
|
return getResponse(endpoint.result.validate(unsafeResult));
|
|
44
52
|
}
|
|
@@ -49,7 +57,7 @@ params, request, args, caller = handleEndpoint) {
|
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
59
|
/** Strip a prefix like `/a/b` from a path like `/a/b/c/d` to produce a remainder path like `/c/d`. */
|
|
52
|
-
function _stripPathPrefix(path, prefix
|
|
60
|
+
function _stripPathPrefix(path, prefix) {
|
|
53
61
|
prefix = prefix === "/" ? "/" : prefix.replace(/\/$/, "");
|
|
54
62
|
if (prefix === "/")
|
|
55
63
|
return path;
|
|
@@ -57,5 +65,4 @@ function _stripPathPrefix(path, prefix, caller) {
|
|
|
57
65
|
return "/";
|
|
58
66
|
if (path.startsWith(`${prefix}/`))
|
|
59
67
|
return path.slice(prefix.length);
|
|
60
|
-
throw new NotFoundError("No matching endpoint", { received: path, expected: prefix, caller });
|
|
61
68
|
}
|
|
@@ -12,19 +12,14 @@ export type MockAPICall = {
|
|
|
12
12
|
readonly response: Response;
|
|
13
13
|
readonly result: unknown;
|
|
14
14
|
};
|
|
15
|
-
/**
|
|
16
|
-
* Construction options for a `MockAPIProvider`.
|
|
17
|
-
* - Accepts the normal `APIProvider` options.
|
|
18
|
-
*/
|
|
15
|
+
/** Construction options for a `MockAPIProvider`. */
|
|
19
16
|
export interface MockAPIProviderOptions extends ClientAPIProviderOptions {
|
|
20
|
-
/** Implement this handler to mock the the request/response input/output. */
|
|
21
|
-
readonly handler: RequestHandler;
|
|
22
17
|
}
|
|
23
18
|
/** Provider that logs API calls without sending network requests. */
|
|
24
19
|
export declare class MockAPIProvider extends ClientAPIProvider {
|
|
25
20
|
readonly calls: MockAPICall[];
|
|
26
21
|
readonly handler: RequestHandler;
|
|
27
|
-
constructor(
|
|
22
|
+
constructor(handler: RequestHandler, options?: MockAPIProviderOptions);
|
|
28
23
|
/**
|
|
29
24
|
* Log a `fetch()` call without using the network.
|
|
30
25
|
* - If `getResult` is configured, its return value is returned as-is (no schema validation).
|
|
@@ -4,7 +4,7 @@ import { ClientAPIProvider } from "./ClientAPIProvider.js";
|
|
|
4
4
|
export class MockAPIProvider extends ClientAPIProvider {
|
|
5
5
|
calls = [];
|
|
6
6
|
handler;
|
|
7
|
-
constructor(
|
|
7
|
+
constructor(handler, options = { url: "https://api.mock.com" }) {
|
|
8
8
|
super(options);
|
|
9
9
|
this.handler = handler;
|
|
10
10
|
}
|
|
@@ -11,17 +11,6 @@ export interface KVNamespace {
|
|
|
11
11
|
}): Promise<unknown>;
|
|
12
12
|
put(key: string, value: string): Promise<void>;
|
|
13
13
|
delete(key: string): Promise<void>;
|
|
14
|
-
list(options?: {
|
|
15
|
-
prefix?: string;
|
|
16
|
-
limit?: number;
|
|
17
|
-
cursor?: string;
|
|
18
|
-
}): Promise<{
|
|
19
|
-
keys: readonly {
|
|
20
|
-
name: string;
|
|
21
|
-
}[];
|
|
22
|
-
list_complete: boolean;
|
|
23
|
-
cursor?: string;
|
|
24
|
-
}>;
|
|
25
14
|
}
|
|
26
15
|
/**
|
|
27
16
|
* Cloudflare Workers KV database provider.
|
|
@@ -31,26 +20,21 @@ export interface KVNamespace {
|
|
|
31
20
|
*
|
|
32
21
|
* ### Supported
|
|
33
22
|
* - Single item operations: `getItem`, `setItem`, `addItem`, `updateItem`, `deleteItem`.
|
|
34
|
-
* - Query operations: `getQuery`, `setQuery`, `updateQuery`, `deleteQuery`, `countQuery`.
|
|
35
|
-
* - All filter operators: equality, not, in, out, contains, gt, gte, lt, lte.
|
|
36
|
-
* - Sorting (`$order`) and limiting (`$limit`).
|
|
37
23
|
* - ID generation: `addItem()` generates a UUID v4 identifier automatically.
|
|
38
24
|
*
|
|
39
25
|
* ### Not supported
|
|
40
26
|
* - **Realtime subscriptions:** `getItemSequence()` and `getQuerySequence()` throw `UnimplementedError`.
|
|
41
27
|
* KV has no change feed or push notification mechanism.
|
|
28
|
+
* - **Updates:** `updateItem()` and `updateQuery()` throw `UnimplementedError`.
|
|
29
|
+
* - **Collection queries:** `getQuery()`, `setQuery()`, `deleteQuery()`, and `countQuery()` are not supported.
|
|
30
|
+
* KV does not expose efficient filtering, sorting, or collection scans, so this provider avoids
|
|
31
|
+
* the old "read everything and filter in memory" behavior.
|
|
42
32
|
*
|
|
43
33
|
* ### Performance limitations
|
|
44
|
-
* - **
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* same item may cause one write to be lost.
|
|
49
|
-
* - **Eventual consistency:** KV is eventually consistent — reads may return stale data, particularly
|
|
50
|
-
* shortly after writes. This also affects `updateItem`, `setQuery`, `updateQuery`, and `deleteQuery`
|
|
51
|
-
* since they read before writing.
|
|
52
|
-
* - **No bulk get:** Each item value must be fetched individually — there is no multi-get API.
|
|
53
|
-
* Query operations that match N items require N+1 KV requests (one `list` + N `get` calls per page).
|
|
34
|
+
* - **Single-key store only:** This provider is intentionally limited to direct key reads and writes.
|
|
35
|
+
* If you need collection queries, filtering, sorting, or bulk mutations, use a different backend.
|
|
36
|
+
* - **Eventual consistency:** KV is eventually consistent, so reads may briefly return stale values
|
|
37
|
+
* shortly after writes.
|
|
54
38
|
*/
|
|
55
39
|
export declare class CloudflareKVProvider extends DBProvider<string> {
|
|
56
40
|
private readonly _kv;
|
|
@@ -59,13 +43,11 @@ export declare class CloudflareKVProvider extends DBProvider<string> {
|
|
|
59
43
|
getItemSequence<T extends Data>(_c: Collection<string, string, T>, _id: string): AsyncIterable<OptionalItem<string, T>>;
|
|
60
44
|
addItem<T extends Data>({ name }: Collection<string, string, T>, data: T): Promise<string>;
|
|
61
45
|
setItem<T extends Data>({ name }: Collection<string, string, T>, id: string, data: T): Promise<void>;
|
|
62
|
-
updateItem<T extends Data>(
|
|
46
|
+
updateItem<T extends Data>(_c: Collection<string, string, T>, _id: string, _updates: Updates<T>): Promise<void>;
|
|
63
47
|
deleteItem<T extends Data>({ name }: Collection<string, string, T>, id: string): Promise<void>;
|
|
64
|
-
getQuery<T extends Data>(
|
|
48
|
+
getQuery<T extends Data>(_c: Collection<string, string, T>, _q?: ItemQuery<string, T>): Promise<Items<string, T>>;
|
|
65
49
|
getQuerySequence<T extends Data>(_c: Collection<string, string, T>, _q?: ItemQuery<string, T>): AsyncIterable<Items<string, T>>;
|
|
66
|
-
setQuery<T extends Data>(
|
|
67
|
-
updateQuery<T extends Data>(
|
|
68
|
-
deleteQuery<T extends Data>(c: Collection<string, string, T>,
|
|
69
|
-
/** Fetch all items in a collection, paginating through `kv.list()`. */
|
|
70
|
-
private _getAllItems;
|
|
50
|
+
setQuery<T extends Data>(_c: Collection<string, string, T>, _q: ItemQuery<string, T>, _data: T): Promise<void>;
|
|
51
|
+
updateQuery<T extends Data>(_c: Collection<string, string, T>, _q: ItemQuery<string, T>, _updates: Updates<T>): Promise<void>;
|
|
52
|
+
deleteQuery<T extends Data>(c: Collection<string, string, T>, _q: ItemQuery<string, T>): Promise<void>;
|
|
71
53
|
}
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import { DBProvider } from "../db/provider/DBProvider.js";
|
|
2
2
|
import { UnimplementedError } from "../error/UnimplementedError.js";
|
|
3
|
-
import { requireArray } from "../util/array.js";
|
|
4
3
|
import { getItem } from "../util/item.js";
|
|
5
|
-
import { queryItems } from "../util/query.js";
|
|
6
|
-
import { updateData } from "../util/update.js";
|
|
7
4
|
import { randomUUID } from "../util/uuid.js";
|
|
8
|
-
function _getKey(collection, id) {
|
|
9
|
-
return `${collection}:${id}`;
|
|
10
|
-
}
|
|
11
|
-
function _getPrefix(collection) {
|
|
12
|
-
return `${collection}:`;
|
|
13
|
-
}
|
|
14
5
|
/**
|
|
15
6
|
* Cloudflare Workers KV database provider.
|
|
16
7
|
*
|
|
@@ -19,26 +10,21 @@ function _getPrefix(collection) {
|
|
|
19
10
|
*
|
|
20
11
|
* ### Supported
|
|
21
12
|
* - Single item operations: `getItem`, `setItem`, `addItem`, `updateItem`, `deleteItem`.
|
|
22
|
-
* - Query operations: `getQuery`, `setQuery`, `updateQuery`, `deleteQuery`, `countQuery`.
|
|
23
|
-
* - All filter operators: equality, not, in, out, contains, gt, gte, lt, lte.
|
|
24
|
-
* - Sorting (`$order`) and limiting (`$limit`).
|
|
25
13
|
* - ID generation: `addItem()` generates a UUID v4 identifier automatically.
|
|
26
14
|
*
|
|
27
15
|
* ### Not supported
|
|
28
16
|
* - **Realtime subscriptions:** `getItemSequence()` and `getQuerySequence()` throw `UnimplementedError`.
|
|
29
17
|
* KV has no change feed or push notification mechanism.
|
|
18
|
+
* - **Updates:** `updateItem()` and `updateQuery()` throw `UnimplementedError`.
|
|
19
|
+
* - **Collection queries:** `getQuery()`, `setQuery()`, `deleteQuery()`, and `countQuery()` are not supported.
|
|
20
|
+
* KV does not expose efficient filtering, sorting, or collection scans, so this provider avoids
|
|
21
|
+
* the old "read everything and filter in memory" behavior.
|
|
30
22
|
*
|
|
31
23
|
* ### Performance limitations
|
|
32
|
-
* - **
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* same item may cause one write to be lost.
|
|
37
|
-
* - **Eventual consistency:** KV is eventually consistent — reads may return stale data, particularly
|
|
38
|
-
* shortly after writes. This also affects `updateItem`, `setQuery`, `updateQuery`, and `deleteQuery`
|
|
39
|
-
* since they read before writing.
|
|
40
|
-
* - **No bulk get:** Each item value must be fetched individually — there is no multi-get API.
|
|
41
|
-
* Query operations that match N items require N+1 KV requests (one `list` + N `get` calls per page).
|
|
24
|
+
* - **Single-key store only:** This provider is intentionally limited to direct key reads and writes.
|
|
25
|
+
* If you need collection queries, filtering, sorting, or bulk mutations, use a different backend.
|
|
26
|
+
* - **Eventual consistency:** KV is eventually consistent, so reads may briefly return stale values
|
|
27
|
+
* shortly after writes.
|
|
42
28
|
*/
|
|
43
29
|
export class CloudflareKVProvider extends DBProvider {
|
|
44
30
|
_kv;
|
|
@@ -62,52 +48,28 @@ export class CloudflareKVProvider extends DBProvider {
|
|
|
62
48
|
async setItem({ name }, id, data) {
|
|
63
49
|
await this._kv.put(_getKey(name, id), JSON.stringify(data));
|
|
64
50
|
}
|
|
65
|
-
async updateItem(
|
|
66
|
-
|
|
67
|
-
if (existing)
|
|
68
|
-
await this.setItem(c, id, updateData(existing, updates));
|
|
51
|
+
async updateItem(_c, _id, _updates) {
|
|
52
|
+
throw new UnimplementedError("CloudflareKVProvider does not support updates to items");
|
|
69
53
|
}
|
|
70
54
|
async deleteItem({ name }, id) {
|
|
71
55
|
await this._kv.delete(_getKey(name, id));
|
|
72
56
|
}
|
|
73
|
-
async getQuery(
|
|
74
|
-
|
|
75
|
-
return q ? requireArray(queryItems(all, q)) : all;
|
|
57
|
+
async getQuery(_c, _q) {
|
|
58
|
+
throw new UnimplementedError("CloudflareKVProvider does not support querying items");
|
|
76
59
|
}
|
|
77
60
|
getQuerySequence(_c, _q) {
|
|
78
61
|
throw new UnimplementedError("CloudflareKVProvider does not support realtime subscriptions");
|
|
79
62
|
}
|
|
80
|
-
async setQuery(
|
|
81
|
-
|
|
82
|
-
await Promise.all(items.map(item => this.setItem(c, item.id, data)));
|
|
63
|
+
async setQuery(_c, _q, _data) {
|
|
64
|
+
throw new UnimplementedError("CloudflareKVProvider does not support querying items");
|
|
83
65
|
}
|
|
84
|
-
async updateQuery(
|
|
85
|
-
|
|
86
|
-
await Promise.all(items.map(item => this.setItem(c, item.id, updateData(item, updates))));
|
|
66
|
+
async updateQuery(_c, _q, _updates) {
|
|
67
|
+
throw new UnimplementedError("CloudflareKVProvider does not support updates to items");
|
|
87
68
|
}
|
|
88
|
-
async deleteQuery(c,
|
|
89
|
-
|
|
90
|
-
await Promise.all(items.map(item => this._kv.delete(_getKey(c.name, item.id))));
|
|
91
|
-
}
|
|
92
|
-
/** Fetch all items in a collection, paginating through `kv.list()`. */
|
|
93
|
-
async _getAllItems(collection) {
|
|
94
|
-
const prefix = _getPrefix(collection);
|
|
95
|
-
const items = [];
|
|
96
|
-
let cursor;
|
|
97
|
-
do {
|
|
98
|
-
const result = await this._kv.list(cursor ? { prefix, cursor } : { prefix });
|
|
99
|
-
const values = await Promise.all(result.keys.map(async (key) => {
|
|
100
|
-
const id = key.name.slice(prefix.length);
|
|
101
|
-
const data = (await this._kv.get(key.name, { type: "json" }));
|
|
102
|
-
if (data)
|
|
103
|
-
return getItem(id, data);
|
|
104
|
-
}));
|
|
105
|
-
for (const item of values) {
|
|
106
|
-
if (item)
|
|
107
|
-
items.push(item);
|
|
108
|
-
}
|
|
109
|
-
cursor = result.list_complete ? undefined : result.cursor;
|
|
110
|
-
} while (cursor);
|
|
111
|
-
return items;
|
|
69
|
+
async deleteQuery(c, _q) {
|
|
70
|
+
throw new UnimplementedError("CloudflareKVProvider does not support querying items");
|
|
112
71
|
}
|
|
113
72
|
}
|
|
73
|
+
function _getKey(collection, id) {
|
|
74
|
+
return `${collection}:${id}`;
|
|
75
|
+
}
|
|
@@ -11,51 +11,51 @@ export class CacheDBProvider extends DBProvider {
|
|
|
11
11
|
}
|
|
12
12
|
async getItem(collection, id) {
|
|
13
13
|
const item = await this.source.getItem(collection, id);
|
|
14
|
-
const table = this.memory.getTable(collection
|
|
14
|
+
const table = this.memory.getTable(collection);
|
|
15
15
|
item ? table.setItem(id, item) : table.deleteItem(id);
|
|
16
16
|
return item;
|
|
17
17
|
}
|
|
18
18
|
getItemSequence(collection, id) {
|
|
19
|
-
return this.memory.setItemSequence(
|
|
19
|
+
return this.memory.getTable(collection).setItemSequence(id, this.source.getItemSequence(collection, id));
|
|
20
20
|
}
|
|
21
21
|
async addItem(collection, data) {
|
|
22
22
|
const id = await this.source.addItem(collection, data);
|
|
23
|
-
this.memory.getTable(collection
|
|
23
|
+
this.memory.getTable(collection).setItem(id, data);
|
|
24
24
|
return id;
|
|
25
25
|
}
|
|
26
26
|
async setItem(collection, id, data) {
|
|
27
27
|
await this.source.setItem(collection, id, data);
|
|
28
|
-
this.memory.getTable(collection
|
|
28
|
+
this.memory.getTable(collection).setItem(id, data);
|
|
29
29
|
}
|
|
30
30
|
async updateItem(collection, id, updates) {
|
|
31
31
|
await this.source.updateItem(collection, id, updates);
|
|
32
|
-
this.memory.getTable(collection
|
|
32
|
+
this.memory.getTable(collection).updateItem(id, updates);
|
|
33
33
|
}
|
|
34
34
|
async deleteItem(collection, id) {
|
|
35
35
|
await this.source.deleteItem(collection, id);
|
|
36
|
-
this.memory.getTable(collection
|
|
36
|
+
this.memory.getTable(collection).deleteItem(id);
|
|
37
37
|
}
|
|
38
38
|
countQuery(collection, query) {
|
|
39
39
|
return this.source.countQuery(collection, query);
|
|
40
40
|
}
|
|
41
41
|
async getQuery(collection, query) {
|
|
42
42
|
const items = await this.source.getQuery(collection, query);
|
|
43
|
-
this.memory.getTable(collection
|
|
43
|
+
this.memory.getTable(collection).setItems(items);
|
|
44
44
|
return items;
|
|
45
45
|
}
|
|
46
46
|
getQuerySequence(collection, query) {
|
|
47
|
-
return this.memory.setItemsSequence(
|
|
47
|
+
return this.memory.getTable(collection).setItemsSequence(this.source.getQuerySequence(collection, query));
|
|
48
48
|
}
|
|
49
49
|
async setQuery(collection, query, data) {
|
|
50
50
|
await this.source.setQuery(collection, query, data);
|
|
51
|
-
this.memory.getTable(collection
|
|
51
|
+
this.memory.getTable(collection).setQuery(query, data);
|
|
52
52
|
}
|
|
53
53
|
async updateQuery(collection, query, updates) {
|
|
54
54
|
await this.source.updateQuery(collection, query, updates);
|
|
55
|
-
this.memory.getTable(collection
|
|
55
|
+
this.memory.getTable(collection).updateQuery(query, updates);
|
|
56
56
|
}
|
|
57
57
|
async deleteQuery(collection, query) {
|
|
58
58
|
await this.source.deleteQuery(collection, query);
|
|
59
|
-
this.memory.getTable(collection
|
|
59
|
+
this.memory.getTable(collection).deleteQuery(query);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -7,7 +7,7 @@ export class ChangesDBProvider extends ThroughDBProvider {
|
|
|
7
7
|
_changes = [];
|
|
8
8
|
async addItem(collection, data) {
|
|
9
9
|
const id = await super.addItem(collection, data);
|
|
10
|
-
this._changes.push({ action: "
|
|
10
|
+
this._changes.push({ action: "add", collection: collection.name, id, data });
|
|
11
11
|
return id;
|
|
12
12
|
}
|
|
13
13
|
async setItem(collection, id, data) {
|
|
@@ -14,39 +14,34 @@ export declare class MemoryDBProvider<I extends Identifier = Identifier> extends
|
|
|
14
14
|
/** List of tables in `{ collection: Table }` format. */
|
|
15
15
|
private _tables;
|
|
16
16
|
/** Get a table for a collection. */
|
|
17
|
-
getTable<T extends Data>(
|
|
18
|
-
getItemTime<T extends Data>(collection: Collection<string, I, T>, id: I): number | undefined;
|
|
17
|
+
getTable<T extends Data>({ name }: Collection<string, I, T>): MemoryTable<I, T>;
|
|
19
18
|
getItem<T extends Data>(collection: Collection<string, I, T>, id: I): Promise<OptionalItem<I, T>>;
|
|
20
19
|
getItemSequence<T extends Data>(collection: Collection<string, I, T>, id: I): AsyncIterable<OptionalItem<I, T>>;
|
|
21
|
-
getCachedItemSequence<T extends Data>(collection: Collection<string, I, T>, id: I): AsyncIterable<OptionalItem<I, T>>;
|
|
22
20
|
addItem<T extends Data>(collection: Collection<string, I, T>, data: T): Promise<I>;
|
|
23
21
|
setItem<T extends Data>(collection: Collection<string, I, T>, id: I, data: T): Promise<void>;
|
|
24
|
-
setItemSequence<T extends Data>(collection: Collection<string, I, T>, id: I, sequence: AsyncIterable<OptionalItem<I, T>>): AsyncIterable<OptionalItem<I, T>>;
|
|
25
22
|
updateItem<T extends Data>(collection: Collection<string, I, T>, id: I, updates: Updates<T>): Promise<void>;
|
|
26
23
|
deleteItem<T extends Data>(collection: Collection<string, I, T>, id: I): Promise<void>;
|
|
27
|
-
getQueryTime<T extends Data>(collection: Collection<string, I, T>, query?: ItemQuery<I, T>): number | undefined;
|
|
28
24
|
countQuery<T extends Data>(collection: Collection<string, I, T>, query?: ItemQuery<I, T>): Promise<number>;
|
|
29
25
|
getQuery<T extends Data>(collection: Collection<string, I, T>, query?: ItemQuery<I, T>): Promise<Items<I, T>>;
|
|
30
26
|
getQuerySequence<T extends Data>(collection: Collection<string, I, T>, query?: ItemQuery<I, T>): AsyncIterable<Items<I, T>>;
|
|
31
|
-
getCachedQuerySequence<T extends Data>(collection: Collection<string, I, T>, query?: ItemQuery<I, T>): AsyncIterable<Items<I, T>>;
|
|
32
27
|
setQuery<T extends Data>(collection: Collection<string, I, T>, query: ItemQuery<I, T>, data: T): Promise<void>;
|
|
33
28
|
updateQuery<T extends Data>(collection: Collection<string, I, T>, query: ItemQuery<I, T>, updates: Updates<T>): Promise<void>;
|
|
34
29
|
deleteQuery<T extends Data>(collection: Collection<string, I, T>, query: ItemQuery<I, T>): Promise<void>;
|
|
35
|
-
setItems<T extends Data>(collection: Collection<string, I, T>, items: Items<I, T
|
|
36
|
-
setItemsSequence<T extends Data>(collection: Collection<string, I, T>, sequence: AsyncIterable<Items<I, T>>, query?: ItemQuery<I, T>): AsyncIterable<Items<I, T>>;
|
|
30
|
+
setItems<T extends Data>(collection: Collection<string, I, T>, items: Items<I, T>): void;
|
|
37
31
|
}
|
|
38
32
|
/** An individual table of data. */
|
|
39
33
|
export declare class MemoryTable<I extends Identifier, T extends Data> {
|
|
40
34
|
/** Actual data in this table. */
|
|
41
35
|
protected readonly _data: Map<Identifier, Item<I, T>>;
|
|
42
|
-
/** Times data was last updated. */
|
|
43
|
-
protected readonly _times: Map<Identifier, number>;
|
|
44
36
|
/** Deferred sequence of next values. */
|
|
45
37
|
readonly next: DeferredSequence<void>;
|
|
46
|
-
getItemTime(id: I): number | undefined;
|
|
47
38
|
getItem(id: I): OptionalItem<I, T>;
|
|
39
|
+
/**
|
|
40
|
+
* Subscribe to all changes for this item key.
|
|
41
|
+
* - Emits the current item immediately, including `undefined` when absent.
|
|
42
|
+
* - Wakes on every table change, but only yields when this item's value actually changed.
|
|
43
|
+
*/
|
|
48
44
|
getItemSequence(id: I): AsyncIterable<OptionalItem<I, T>>;
|
|
49
|
-
getCachedItemSequence(id: I): AsyncIterable<OptionalItem<I, T>>;
|
|
50
45
|
/** Function to generate a random ID for this table. */
|
|
51
46
|
generateUniqueID(): I;
|
|
52
47
|
addItem(data: T): I;
|
|
@@ -54,14 +49,17 @@ export declare class MemoryTable<I extends Identifier, T extends Data> {
|
|
|
54
49
|
setItemSequence(id: I, sequence: AsyncIterable<OptionalItem<I, T>>): AsyncIterable<OptionalItem<I, T>>;
|
|
55
50
|
updateItem(id: I, updates: Updates<T>): void;
|
|
56
51
|
deleteItem(id: I): void;
|
|
57
|
-
getQueryTime(query?: ItemQuery<I, T>): number | undefined;
|
|
58
52
|
countQuery(query?: ItemQuery<I, T>): number;
|
|
59
53
|
getQuery(query?: ItemQuery<I, T>): Items<I, T>;
|
|
54
|
+
/**
|
|
55
|
+
* Subscribe to the live result of a query.
|
|
56
|
+
* - Emits the current query result immediately, even if empty.
|
|
57
|
+
* - Wakes on every table change, but only yields when the computed query result changed.
|
|
58
|
+
*/
|
|
60
59
|
getQuerySequence(query?: ItemQuery<I, T>): AsyncIterable<Items<I, T>>;
|
|
61
|
-
getCachedQuerySequence(query?: ItemQuery<I, T>): AsyncIterable<Items<I, T>>;
|
|
62
60
|
setQuery(query: ItemQuery<I, T>, data: T): void;
|
|
63
61
|
updateQuery(query: ItemQuery<I, T>, updates: Updates<T>): void;
|
|
64
62
|
deleteQuery(query: ItemQuery<I, T>): void;
|
|
65
|
-
setItems(items: Items<I, T
|
|
66
|
-
setItemsSequence(sequence: AsyncIterable<Items<I, T
|
|
63
|
+
setItems(items: Items<I, T>): void;
|
|
64
|
+
setItemsSequence(sequence: AsyncIterable<Items<I, T>>): AsyncIterable<Items<I, T>>;
|
|
67
65
|
}
|
|
@@ -17,82 +17,63 @@ export class MemoryDBProvider extends DBProvider {
|
|
|
17
17
|
// biome-ignore lint/suspicious/noExplicitAny: Internal storage erases T; getTable<T> restores it per-call.
|
|
18
18
|
_tables = {};
|
|
19
19
|
/** Get a table for a collection. */
|
|
20
|
-
getTable(
|
|
21
|
-
|
|
22
|
-
return (this._tables[collection] ||= new MemoryTable());
|
|
23
|
-
}
|
|
24
|
-
getItemTime(collection, id) {
|
|
25
|
-
return this.getTable(collection.name).getItemTime(id);
|
|
20
|
+
getTable({ name }) {
|
|
21
|
+
return (this._tables[name] ||= new MemoryTable());
|
|
26
22
|
}
|
|
27
23
|
async getItem(collection, id) {
|
|
28
|
-
return this.getTable(collection
|
|
24
|
+
return this.getTable(collection).getItem(id);
|
|
29
25
|
}
|
|
30
26
|
async *getItemSequence(collection, id) {
|
|
31
|
-
yield* this.getTable(collection
|
|
32
|
-
}
|
|
33
|
-
getCachedItemSequence(collection, id) {
|
|
34
|
-
return this.getTable(collection.name).getCachedItemSequence(id);
|
|
27
|
+
yield* this.getTable(collection).getItemSequence(id);
|
|
35
28
|
}
|
|
36
29
|
async addItem(collection, data) {
|
|
37
|
-
return this.getTable(collection
|
|
30
|
+
return this.getTable(collection).addItem(data);
|
|
38
31
|
}
|
|
39
32
|
async setItem(collection, id, data) {
|
|
40
|
-
this.getTable(collection
|
|
41
|
-
}
|
|
42
|
-
setItemSequence(collection, id, sequence) {
|
|
43
|
-
return this.getTable(collection.name).setItemSequence(id, sequence);
|
|
33
|
+
this.getTable(collection).setItem(id, data);
|
|
44
34
|
}
|
|
45
35
|
async updateItem(collection, id, updates) {
|
|
46
|
-
this.getTable(collection
|
|
36
|
+
this.getTable(collection).updateItem(id, updates);
|
|
47
37
|
}
|
|
48
38
|
async deleteItem(collection, id) {
|
|
49
|
-
this.getTable(collection
|
|
50
|
-
}
|
|
51
|
-
getQueryTime(collection, query) {
|
|
52
|
-
return this.getTable(collection.name).getQueryTime(query);
|
|
39
|
+
this.getTable(collection).deleteItem(id);
|
|
53
40
|
}
|
|
54
41
|
async countQuery(collection, query) {
|
|
55
|
-
return this.getTable(collection
|
|
42
|
+
return this.getTable(collection).countQuery(query);
|
|
56
43
|
}
|
|
57
44
|
async getQuery(collection, query) {
|
|
58
|
-
return this.getTable(collection
|
|
45
|
+
return this.getTable(collection).getQuery(query);
|
|
59
46
|
}
|
|
60
47
|
async *getQuerySequence(collection, query) {
|
|
61
|
-
yield* this.getTable(collection
|
|
62
|
-
}
|
|
63
|
-
getCachedQuerySequence(collection, query) {
|
|
64
|
-
return this.getTable(collection.name).getCachedQuerySequence(query);
|
|
48
|
+
yield* this.getTable(collection).getQuerySequence(query);
|
|
65
49
|
}
|
|
66
50
|
async setQuery(collection, query, data) {
|
|
67
|
-
this.getTable(collection
|
|
51
|
+
this.getTable(collection).setQuery(query, data);
|
|
68
52
|
}
|
|
69
53
|
async updateQuery(collection, query, updates) {
|
|
70
|
-
this.getTable(collection
|
|
54
|
+
this.getTable(collection).updateQuery(query, updates);
|
|
71
55
|
}
|
|
72
56
|
async deleteQuery(collection, query) {
|
|
73
|
-
this.getTable(collection
|
|
74
|
-
}
|
|
75
|
-
setItems(collection, items, query) {
|
|
76
|
-
this.getTable(collection.name).setItems(items, query);
|
|
57
|
+
this.getTable(collection).deleteQuery(query);
|
|
77
58
|
}
|
|
78
|
-
|
|
79
|
-
|
|
59
|
+
setItems(collection, items) {
|
|
60
|
+
this.getTable(collection).setItems(items);
|
|
80
61
|
}
|
|
81
62
|
}
|
|
82
63
|
/** An individual table of data. */
|
|
83
64
|
export class MemoryTable {
|
|
84
65
|
/** Actual data in this table. */
|
|
85
66
|
_data = new Map();
|
|
86
|
-
/** Times data was last updated. */
|
|
87
|
-
_times = new Map();
|
|
88
67
|
/** Deferred sequence of next values. */
|
|
89
68
|
next = new DeferredSequence();
|
|
90
|
-
getItemTime(id) {
|
|
91
|
-
return this._times.get(id);
|
|
92
|
-
}
|
|
93
69
|
getItem(id) {
|
|
94
70
|
return this._data.get(id);
|
|
95
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Subscribe to all changes for this item key.
|
|
74
|
+
* - Emits the current item immediately, including `undefined` when absent.
|
|
75
|
+
* - Wakes on every table change, but only yields when this item's value actually changed.
|
|
76
|
+
*/
|
|
96
77
|
async *getItemSequence(id) {
|
|
97
78
|
let lastValue = this.getItem(id);
|
|
98
79
|
yield lastValue;
|
|
@@ -105,20 +86,6 @@ export class MemoryTable {
|
|
|
105
86
|
}
|
|
106
87
|
}
|
|
107
88
|
}
|
|
108
|
-
async *getCachedItemSequence(id) {
|
|
109
|
-
let lastTime = this._times.get(id);
|
|
110
|
-
if (typeof lastTime === "number")
|
|
111
|
-
yield this.getItem(id);
|
|
112
|
-
while (true) {
|
|
113
|
-
await this.next;
|
|
114
|
-
const nextTime = this._times.get(id);
|
|
115
|
-
if (nextTime !== lastTime) {
|
|
116
|
-
if (typeof nextTime === "number")
|
|
117
|
-
yield this.getItem(id);
|
|
118
|
-
lastTime = nextTime;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
89
|
generateUniqueID() {
|
|
123
90
|
const gen = typeof this._data.keys().next().value === "number" ? getRandom : getRandomKey;
|
|
124
91
|
let id = gen();
|
|
@@ -135,7 +102,6 @@ export class MemoryTable {
|
|
|
135
102
|
const item = getItem(id, data);
|
|
136
103
|
if (this._data.get(id) !== item) {
|
|
137
104
|
this._data.set(id, item);
|
|
138
|
-
this._times.set(id, Date.now());
|
|
139
105
|
this.next.resolve();
|
|
140
106
|
}
|
|
141
107
|
}
|
|
@@ -147,25 +113,31 @@ export class MemoryTable {
|
|
|
147
113
|
}
|
|
148
114
|
updateItem(id, updates) {
|
|
149
115
|
const oldItem = this._data.get(id);
|
|
150
|
-
if (oldItem)
|
|
151
|
-
|
|
116
|
+
if (!oldItem)
|
|
117
|
+
return;
|
|
118
|
+
const nextItem = updateData(oldItem, updates);
|
|
119
|
+
if (this._data.get(id) !== nextItem) {
|
|
120
|
+
this._data.set(id, nextItem);
|
|
121
|
+
this.next.resolve();
|
|
122
|
+
}
|
|
152
123
|
}
|
|
153
124
|
deleteItem(id) {
|
|
154
125
|
if (this._data.has(id)) {
|
|
155
126
|
this._data.delete(id);
|
|
156
|
-
this._times.set(id, Date.now());
|
|
157
127
|
this.next.resolve();
|
|
158
128
|
}
|
|
159
129
|
}
|
|
160
|
-
getQueryTime(query) {
|
|
161
|
-
return this._times.get(_getQueryKey(query));
|
|
162
|
-
}
|
|
163
130
|
countQuery(query) {
|
|
164
131
|
return query ? countItems(queryItems(this._data.values(), query)) : this._data.size;
|
|
165
132
|
}
|
|
166
133
|
getQuery(query) {
|
|
167
134
|
return requireArray(query ? queryItems(this._data.values(), query) : this._data.values());
|
|
168
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Subscribe to the live result of a query.
|
|
138
|
+
* - Emits the current query result immediately, even if empty.
|
|
139
|
+
* - Wakes on every table change, but only yields when the computed query result changed.
|
|
140
|
+
*/
|
|
169
141
|
async *getQuerySequence(query) {
|
|
170
142
|
let lastItems = this.getQuery(query);
|
|
171
143
|
yield lastItems;
|
|
@@ -178,72 +150,59 @@ export class MemoryTable {
|
|
|
178
150
|
}
|
|
179
151
|
}
|
|
180
152
|
}
|
|
181
|
-
async *getCachedQuerySequence(query) {
|
|
182
|
-
const key = _getQueryKey(query);
|
|
183
|
-
let lastTime = this._times.get(key);
|
|
184
|
-
if (typeof lastTime === "number")
|
|
185
|
-
yield this.getQuery(query);
|
|
186
|
-
while (true) {
|
|
187
|
-
await this.next;
|
|
188
|
-
const nextTime = this._times.get(key);
|
|
189
|
-
if (lastTime !== nextTime) {
|
|
190
|
-
if (typeof nextTime === "number")
|
|
191
|
-
yield this.getQuery(query);
|
|
192
|
-
lastTime = nextTime;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
153
|
setQuery(query, data) {
|
|
197
|
-
let changed =
|
|
154
|
+
let changed = false;
|
|
198
155
|
for (const { id } of queryWritableItems(this._data.values(), query)) {
|
|
199
|
-
|
|
200
|
-
|
|
156
|
+
const item = getItem(id, data);
|
|
157
|
+
if (this._data.get(id) !== item) {
|
|
158
|
+
this._data.set(id, item);
|
|
159
|
+
changed = true;
|
|
160
|
+
}
|
|
201
161
|
}
|
|
202
|
-
if (changed)
|
|
203
|
-
const key = _getQueryKey(query);
|
|
204
|
-
this._times.set(key, Date.now());
|
|
162
|
+
if (changed)
|
|
205
163
|
this.next.resolve();
|
|
206
|
-
}
|
|
207
164
|
}
|
|
208
165
|
updateQuery(query, updates) {
|
|
209
|
-
let
|
|
166
|
+
let changed = false;
|
|
210
167
|
for (const { id } of queryWritableItems(this._data.values(), query)) {
|
|
211
|
-
this.
|
|
212
|
-
|
|
168
|
+
const oldItem = this._data.get(id);
|
|
169
|
+
if (!oldItem)
|
|
170
|
+
continue;
|
|
171
|
+
const nextItem = updateData(oldItem, updates);
|
|
172
|
+
if (this._data.get(id) !== nextItem) {
|
|
173
|
+
this._data.set(id, nextItem);
|
|
174
|
+
changed = true;
|
|
175
|
+
}
|
|
213
176
|
}
|
|
214
|
-
if (
|
|
215
|
-
const key = _getQueryKey(query);
|
|
216
|
-
this._times.set(key, Date.now());
|
|
177
|
+
if (changed)
|
|
217
178
|
this.next.resolve();
|
|
218
|
-
}
|
|
219
179
|
}
|
|
220
180
|
deleteQuery(query) {
|
|
221
|
-
let
|
|
181
|
+
let changed = false;
|
|
222
182
|
for (const { id } of queryWritableItems(this._data.values(), query)) {
|
|
223
|
-
this.
|
|
224
|
-
|
|
183
|
+
if (this._data.has(id)) {
|
|
184
|
+
this._data.delete(id);
|
|
185
|
+
changed = true;
|
|
186
|
+
}
|
|
225
187
|
}
|
|
226
|
-
if (
|
|
227
|
-
const key = _getQueryKey(query);
|
|
228
|
-
this._times.set(key, Date.now());
|
|
188
|
+
if (changed)
|
|
229
189
|
this.next.resolve();
|
|
230
|
-
}
|
|
231
190
|
}
|
|
232
|
-
setItems(items
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
191
|
+
setItems(items) {
|
|
192
|
+
let changed = false;
|
|
193
|
+
for (const item of items) {
|
|
194
|
+
if (this._data.get(item.id) !== item) {
|
|
195
|
+
this._data.set(item.id, item);
|
|
196
|
+
changed = true;
|
|
197
|
+
}
|
|
239
198
|
}
|
|
199
|
+
if (changed)
|
|
200
|
+
this.next.resolve();
|
|
240
201
|
}
|
|
241
|
-
async *setItemsSequence(sequence
|
|
202
|
+
async *setItemsSequence(sequence) {
|
|
242
203
|
for await (const items of sequence) {
|
|
243
|
-
this.setItems(items
|
|
204
|
+
this.setItems(items);
|
|
244
205
|
yield items;
|
|
245
206
|
}
|
|
246
207
|
}
|
|
247
208
|
}
|
|
248
|
-
// Queries that have no limit don't care about sorting either.
|
|
249
|
-
const _getQueryKey = (query) => (query ? JSON.stringify(query) : "{}");
|
package/db/store/ItemStore.js
CHANGED
|
@@ -29,17 +29,15 @@ export class ItemStore extends OptionalDataStore {
|
|
|
29
29
|
this.value = getItem(this.id, data);
|
|
30
30
|
}
|
|
31
31
|
constructor(collection, id, provider, memory) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
super(typeof time === "number" || item ? item : NONE, time); // Use the cached value if it was definitely cached or is not undefined.
|
|
32
|
+
const item = memory?.getTable(collection).getItem(id);
|
|
33
|
+
super(item ?? NONE); // Use the current memory snapshot if available.
|
|
35
34
|
if (memory)
|
|
36
|
-
this.starter = store => runSequence(store.through(memory.
|
|
35
|
+
this.starter = store => runSequence(store.through(memory.getItemSequence(collection, id)));
|
|
37
36
|
this.provider = provider;
|
|
38
37
|
this.collection = collection;
|
|
39
38
|
this.id = id;
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
this.refresh();
|
|
39
|
+
// Always refresh from source, even if memory supplied an initial value.
|
|
40
|
+
this.refresh();
|
|
43
41
|
}
|
|
44
42
|
/** Refresh this store from the source provider. */
|
|
45
43
|
refresh(provider = this.provider) {
|
package/db/store/QueryStore.js
CHANGED
|
@@ -44,18 +44,16 @@ export class QueryStore extends ArrayStore {
|
|
|
44
44
|
return last;
|
|
45
45
|
}
|
|
46
46
|
constructor(collection, query, provider, memory) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
super(typeof time === "number" || items.length ? items : NONE, time); // Use the value if it was definitely cached or is not empty.
|
|
47
|
+
const items = memory?.getTable(collection).getQuery(query);
|
|
48
|
+
super(items ?? NONE); // Use the current memory snapshot if available.
|
|
50
49
|
if (memory)
|
|
51
|
-
this.starter = store => runSequence(store.through(memory.
|
|
50
|
+
this.starter = store => runSequence(store.through(memory.getQuerySequence(collection, query)));
|
|
52
51
|
this.provider = provider;
|
|
53
52
|
this.collection = collection;
|
|
54
53
|
this.query = query;
|
|
55
54
|
this.limit = getLimit(query) ?? Number.POSITIVE_INFINITY;
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
this.refresh();
|
|
55
|
+
// Always refresh from source, even if memory supplied an initial value.
|
|
56
|
+
this.refresh();
|
|
59
57
|
}
|
|
60
58
|
/** Refresh this store from the source provider. */
|
|
61
59
|
refresh(provider = this.provider) {
|
package/package.json
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shelving",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"javascript",
|
|
6
|
-
"typescript",
|
|
7
|
-
"schema",
|
|
8
|
-
"validation",
|
|
9
|
-
"database",
|
|
10
|
-
"database-connector",
|
|
11
|
-
"state-management",
|
|
12
|
-
"query-builder"
|
|
13
|
-
],
|
|
14
|
-
"version": "1.179.0",
|
|
3
|
+
"version": "1.179.2",
|
|
4
|
+
"author": "Dave Houlbrooke <dave@shax.com>",
|
|
15
5
|
"repository": {
|
|
16
6
|
"type": "git",
|
|
17
7
|
"url": "git+https://github.com/dhoulb/shelving.git"
|
|
18
8
|
},
|
|
19
|
-
"author": "Dave Houlbrooke <dave@shax.com>",
|
|
20
|
-
"license": "0BSD",
|
|
21
|
-
"type": "module",
|
|
22
|
-
"module": "./index.js",
|
|
23
9
|
"main": "./index.js",
|
|
24
|
-
"
|
|
10
|
+
"module": "./index.js",
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@biomejs/biome": "^2.4.9",
|
|
13
|
+
"@google-cloud/firestore": "^8.3.0",
|
|
14
|
+
"@types/bun": "^1.3.11",
|
|
15
|
+
"@types/react": "^19.2.14",
|
|
16
|
+
"@types/react-dom": "^19.2.3",
|
|
17
|
+
"firebase": "^12.11.0",
|
|
18
|
+
"react": "^19.2.4",
|
|
19
|
+
"react-dom": "^19.2.4",
|
|
20
|
+
"typescript": "^5.9.3"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"@google-cloud/firestore": ">=7.0.0",
|
|
24
|
+
"firebase": ">=11.0.0",
|
|
25
|
+
"react": ">=19.0.0"
|
|
26
|
+
},
|
|
25
27
|
"exports": {
|
|
26
28
|
".": "./index.js",
|
|
27
29
|
"./api": "./api/index.js",
|
|
@@ -40,11 +42,22 @@
|
|
|
40
42
|
"./test": "./test/index.js",
|
|
41
43
|
"./util": "./util/index.js"
|
|
42
44
|
},
|
|
43
|
-
"
|
|
45
|
+
"description": "Toolkit for using data in JavaScript.",
|
|
44
46
|
"engineStrict": true,
|
|
45
47
|
"engines": {
|
|
46
48
|
"node": ">=16.0.0"
|
|
47
49
|
},
|
|
50
|
+
"keywords": [
|
|
51
|
+
"javascript",
|
|
52
|
+
"typescript",
|
|
53
|
+
"schema",
|
|
54
|
+
"validation",
|
|
55
|
+
"database",
|
|
56
|
+
"database-connector",
|
|
57
|
+
"state-management",
|
|
58
|
+
"query-builder"
|
|
59
|
+
],
|
|
60
|
+
"license": "0BSD",
|
|
48
61
|
"scripts": {
|
|
49
62
|
"test": "bun run --parallel test:*",
|
|
50
63
|
"test:lint": "biome check .",
|
|
@@ -59,20 +72,7 @@
|
|
|
59
72
|
"build:test:syntax": "bun run ./dist/index.js",
|
|
60
73
|
"build:test:unit": "bun test ./dist/**/*.test.js --bail"
|
|
61
74
|
},
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"@types/bun": "^1.3.11",
|
|
66
|
-
"@types/react": "^19.2.14",
|
|
67
|
-
"@types/react-dom": "^19.2.3",
|
|
68
|
-
"firebase": "^12.11.0",
|
|
69
|
-
"react": "^19.2.4",
|
|
70
|
-
"react-dom": "^19.2.4",
|
|
71
|
-
"typescript": "^5.9.3"
|
|
72
|
-
},
|
|
73
|
-
"peerDependencies": {
|
|
74
|
-
"@google-cloud/firestore": ">=7.0.0",
|
|
75
|
-
"firebase": ">=11.0.0",
|
|
76
|
-
"react": ">=19.0.0"
|
|
77
|
-
}
|
|
75
|
+
"sideEffects": false,
|
|
76
|
+
"type": "module",
|
|
77
|
+
"types": "./index.d.ts"
|
|
78
78
|
}
|