shelving 1.181.2 → 1.182.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/api/cache/APICache.d.ts +1 -0
- package/api/cache/APICache.js +8 -6
- package/api/cache/EndpointCache.d.ts +5 -6
- package/api/cache/EndpointCache.js +7 -13
- package/api/endpoint/Endpoint.d.ts +1 -1
- package/api/index.d.ts +0 -1
- package/api/index.js +0 -1
- package/api/provider/APIProvider.d.ts +39 -4
- package/api/provider/APIProvider.js +76 -0
- package/api/provider/MockAPIProvider.d.ts +5 -5
- package/api/provider/MockAPIProvider.js +2 -2
- package/api/provider/ThroughAPIProvider.d.ts +6 -0
- package/api/provider/ThroughAPIProvider.js +15 -0
- package/bun/BunPostgreSQLProvider.d.ts +3 -2
- package/cloudflare/CloudflareD1Provider.d.ts +3 -2
- package/cloudflare/CloudflareKVProvider.d.ts +16 -18
- package/cloudflare/CloudflareKVProvider.js +11 -13
- package/db/cache/CollectionCache.d.ts +37 -0
- package/db/cache/CollectionCache.js +62 -0
- package/db/cache/DBCache.d.ts +38 -0
- package/db/cache/DBCache.js +59 -0
- package/db/collection/Collection.d.ts +12 -10
- package/db/index.d.ts +2 -0
- package/db/index.js +2 -0
- package/db/provider/CacheDBProvider.d.ts +18 -18
- package/db/provider/ChangesDBProvider.d.ts +11 -11
- package/db/provider/DBProvider.d.ts +17 -17
- package/db/provider/DBProvider.js +2 -2
- package/db/provider/DebugDBProvider.d.ts +15 -15
- package/db/provider/MemoryDBProvider.d.ts +28 -26
- package/db/provider/MemoryDBProvider.js +10 -5
- package/db/provider/MockDBProvider.d.ts +17 -17
- package/db/provider/PostgreSQLProvider.d.ts +4 -2
- package/db/provider/PostgreSQLProvider.js +1 -1
- package/db/provider/SQLProvider.d.ts +23 -23
- package/db/provider/SQLProvider.js +24 -23
- package/db/provider/SQLiteProvider.d.ts +6 -2
- package/db/provider/SQLiteProvider.js +14 -1
- package/db/provider/ThroughDBProvider.d.ts +19 -19
- package/db/provider/ValidationDBProvider.d.ts +14 -14
- package/db/provider/ValidationDBProvider.js +4 -8
- package/db/store/QueryStore.d.ts +3 -3
- package/firestore/client/FirestoreClientProvider.d.ts +21 -15
- package/firestore/client/FirestoreClientProvider.js +40 -37
- package/firestore/lite/FirestoreLiteProvider.d.ts +21 -15
- package/firestore/lite/FirestoreLiteProvider.js +38 -29
- package/firestore/server/FirestoreServerProvider.d.ts +21 -15
- package/firestore/server/FirestoreServerProvider.js +67 -71
- package/package.json +1 -1
- package/react/createAPIContext.d.ts +4 -2
- package/react/createAPIContext.js +22 -15
- package/react/createDBContext.d.ts +31 -0
- package/react/createDBContext.js +35 -0
- package/react/index.d.ts +1 -2
- package/react/index.js +1 -2
- package/util/http.d.ts +3 -2
- package/util/http.js +6 -6
- package/util/item.d.ts +4 -4
- package/util/query.d.ts +1 -4
- package/util/uri.d.ts +9 -4
- package/util/uri.js +4 -0
- package/api/provider/ClientAPIProvider.d.ts +0 -37
- package/api/provider/ClientAPIProvider.js +0 -51
- package/react/createCacheContext.d.ts +0 -13
- package/react/createCacheContext.js +0 -22
- package/react/createDataContext.d.ts +0 -26
- package/react/createDataContext.js +0 -31
package/api/cache/APICache.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare class APICache implements Disposable {
|
|
|
9
9
|
private readonly _caches;
|
|
10
10
|
readonly provider: APIProvider;
|
|
11
11
|
constructor(provider: APIProvider);
|
|
12
|
+
private _get;
|
|
12
13
|
/** Get (or create) the `EndpointCache` for the given endpoint. */
|
|
13
14
|
get<P, R>(endpoint: Endpoint<P, R>): EndpointCache<P, R>;
|
|
14
15
|
/** Invalidate a specific store for an endpoint. */
|
package/api/cache/APICache.js
CHANGED
|
@@ -10,25 +10,27 @@ export class APICache {
|
|
|
10
10
|
constructor(provider) {
|
|
11
11
|
this.provider = provider;
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
_get(endpoint) {
|
|
14
|
+
return this._caches.get(endpoint);
|
|
15
|
+
}
|
|
14
16
|
get(endpoint) {
|
|
15
|
-
return this.
|
|
17
|
+
return this._get(endpoint) || setMapItem(this._caches, endpoint, new EndpointCache(endpoint, this.provider));
|
|
16
18
|
}
|
|
17
19
|
/** Invalidate a specific store for an endpoint. */
|
|
18
20
|
invalidate(endpoint, payload) {
|
|
19
|
-
this.
|
|
21
|
+
this._get(endpoint)?.invalidate(payload);
|
|
20
22
|
}
|
|
21
23
|
/** Invalidate all stores for an endpoint. */
|
|
22
24
|
invalidateAll(endpoint) {
|
|
23
|
-
this.
|
|
25
|
+
this._get(endpoint)?.invalidateAll();
|
|
24
26
|
}
|
|
25
27
|
/** Trigger a refetch on a specific store for an endpoint. */
|
|
26
28
|
refetch(endpoint, payload) {
|
|
27
|
-
this.
|
|
29
|
+
this._get(endpoint)?.refetch(payload);
|
|
28
30
|
}
|
|
29
31
|
/** Trigger a refetch on all stores for an endpoint. */
|
|
30
32
|
refetchAll(endpoint) {
|
|
31
|
-
this.
|
|
33
|
+
this._get(endpoint)?.refetchAll();
|
|
32
34
|
}
|
|
33
35
|
// Implement Disposable.
|
|
34
36
|
[Symbol.dispose]() {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AnyCaller } from "../../util/function.js";
|
|
1
2
|
import type { Endpoint } from "../endpoint/Endpoint.js";
|
|
2
3
|
import type { APIProvider } from "../provider/APIProvider.js";
|
|
3
4
|
import { EndpointStore } from "../store/EndpointStore.js";
|
|
@@ -5,22 +6,20 @@ import { EndpointStore } from "../store/EndpointStore.js";
|
|
|
5
6
|
* Cache of `EndpointStore` objects for a single endpoint, keyed by serialized payload.
|
|
6
7
|
* - Use `get(payload)` to retrieve or create the `EndpointStore` for a given payload.
|
|
7
8
|
*/
|
|
8
|
-
export declare class EndpointCache<P, R> implements Disposable {
|
|
9
|
+
export declare class EndpointCache<P = unknown, R = unknown> implements Disposable {
|
|
9
10
|
private readonly _stores;
|
|
10
11
|
readonly endpoint: Endpoint<P, R>;
|
|
11
12
|
readonly provider: APIProvider;
|
|
12
13
|
constructor(endpoint: Endpoint<P, R>, provider: APIProvider);
|
|
13
14
|
/** Get (or create) the `EndpointStore` for the given payload. */
|
|
14
|
-
get(payload: P): EndpointStore<P, R>;
|
|
15
|
+
get(payload: P, caller?: AnyCaller): EndpointStore<P, R>;
|
|
15
16
|
/** Invalidate a specific store. */
|
|
16
|
-
invalidate(payload: P): void;
|
|
17
|
+
invalidate(payload: P, caller?: AnyCaller): void;
|
|
17
18
|
/** Invalidate all stores. */
|
|
18
19
|
invalidateAll(): void;
|
|
19
20
|
/** Trigger a refetch on a specific store. */
|
|
20
|
-
refetch(payload: P): void;
|
|
21
|
+
refetch(payload: P, caller?: AnyCaller): void;
|
|
21
22
|
/** Trigger a refetch on all stores. */
|
|
22
23
|
refetchAll(): void;
|
|
23
24
|
[Symbol.dispose](): void;
|
|
24
25
|
}
|
|
25
|
-
/** Any endpoint cache. */
|
|
26
|
-
export type AnyEndpointCache = EndpointCache<any, any>;
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { setMapItem } from "../../util/map.js";
|
|
2
2
|
import { EndpointStore } from "../store/EndpointStore.js";
|
|
3
|
-
/** Serialize a payload to a stable string key for use in a `Map`. */
|
|
4
|
-
function _serializePayload(payload) {
|
|
5
|
-
if (payload === undefined)
|
|
6
|
-
return "";
|
|
7
|
-
return JSON.stringify(payload);
|
|
8
|
-
}
|
|
9
3
|
/**
|
|
10
4
|
* Cache of `EndpointStore` objects for a single endpoint, keyed by serialized payload.
|
|
11
5
|
* - Use `get(payload)` to retrieve or create the `EndpointStore` for a given payload.
|
|
@@ -19,13 +13,13 @@ export class EndpointCache {
|
|
|
19
13
|
this.provider = provider;
|
|
20
14
|
}
|
|
21
15
|
/** Get (or create) the `EndpointStore` for the given payload. */
|
|
22
|
-
get(payload) {
|
|
23
|
-
const
|
|
24
|
-
return this._stores.get(
|
|
16
|
+
get(payload, caller = this.get) {
|
|
17
|
+
const url = this.provider.renderURL(this.endpoint, payload, caller).href;
|
|
18
|
+
return this._stores.get(url) || setMapItem(this._stores, url, new EndpointStore(this.endpoint, payload, this.provider));
|
|
25
19
|
}
|
|
26
20
|
/** Invalidate a specific store. */
|
|
27
|
-
invalidate(payload) {
|
|
28
|
-
this.get(payload)?.invalidate();
|
|
21
|
+
invalidate(payload, caller = this.invalidate) {
|
|
22
|
+
this.get(payload, caller)?.invalidate();
|
|
29
23
|
}
|
|
30
24
|
/** Invalidate all stores. */
|
|
31
25
|
invalidateAll() {
|
|
@@ -33,8 +27,8 @@ export class EndpointCache {
|
|
|
33
27
|
store.invalidate();
|
|
34
28
|
}
|
|
35
29
|
/** Trigger a refetch on a specific store. */
|
|
36
|
-
refetch(payload) {
|
|
37
|
-
this.get(payload)?.fetch();
|
|
30
|
+
refetch(payload, caller = this.invalidate) {
|
|
31
|
+
this.get(payload, caller)?.fetch();
|
|
38
32
|
}
|
|
39
33
|
/** Trigger a refetch on all stores. */
|
|
40
34
|
refetchAll() {
|
|
@@ -14,7 +14,7 @@ import type { EndpointCallback, EndpointHandler } from "./util.js";
|
|
|
14
14
|
* @param payload A `Schema` for the payload of the endpoint.
|
|
15
15
|
* @param result A `Schema` for the result of the endpoint.
|
|
16
16
|
*/
|
|
17
|
-
export declare class Endpoint<P, R> {
|
|
17
|
+
export declare class Endpoint<P = unknown, R = unknown> {
|
|
18
18
|
/** Endpoint method. */
|
|
19
19
|
readonly method: RequestMethod;
|
|
20
20
|
/** Endpoint path, possibly including placeholders e.g. `/users/{id}` */
|
package/api/index.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ export * from "./cache/EndpointCache.js";
|
|
|
3
3
|
export * from "./endpoint/Endpoint.js";
|
|
4
4
|
export * from "./endpoint/util.js";
|
|
5
5
|
export * from "./provider/APIProvider.js";
|
|
6
|
-
export * from "./provider/ClientAPIProvider.js";
|
|
7
6
|
export * from "./provider/DebugAPIProvider.js";
|
|
8
7
|
export * from "./provider/MockAPIProvider.js";
|
|
9
8
|
export * from "./provider/MockEndpointAPIProvider.js";
|
package/api/index.js
CHANGED
|
@@ -3,7 +3,6 @@ export * from "./cache/EndpointCache.js";
|
|
|
3
3
|
export * from "./endpoint/Endpoint.js";
|
|
4
4
|
export * from "./endpoint/util.js";
|
|
5
5
|
export * from "./provider/APIProvider.js";
|
|
6
|
-
export * from "./provider/ClientAPIProvider.js";
|
|
7
6
|
export * from "./provider/DebugAPIProvider.js";
|
|
8
7
|
export * from "./provider/MockAPIProvider.js";
|
|
9
8
|
export * from "./provider/MockEndpointAPIProvider.js";
|
|
@@ -1,9 +1,44 @@
|
|
|
1
1
|
import type { AnyCaller } from "../../util/function.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type RequestOptions } from "../../util/http.js";
|
|
3
|
+
import { type PossibleURL, type URL, type URLString } from "../../util/url.js";
|
|
3
4
|
import type { Endpoint } from "../endpoint/Endpoint.js";
|
|
4
|
-
|
|
5
|
+
/** Options for an `APIProvider`. */
|
|
6
|
+
export interface APIProviderOptions {
|
|
7
|
+
/** The common base URL for all rendered endpoint requests. */
|
|
8
|
+
readonly url: PossibleURL;
|
|
9
|
+
/** Options used for HTTP requests created with `this.getRequest()` and `this.fetch()` */
|
|
10
|
+
readonly options?: RequestOptions;
|
|
11
|
+
}
|
|
12
|
+
/** Provider for API endpoints rooted at a common base URL. */
|
|
13
|
+
export declare class APIProvider {
|
|
14
|
+
/** The common base URL for all rendered endpoint requests. */
|
|
15
|
+
readonly url: URLString;
|
|
16
|
+
/** Default options used for HTTP requests created with `this.getRequest()` and `this.fetch()` */
|
|
17
|
+
readonly options: RequestOptions;
|
|
18
|
+
constructor({ url, options }: APIProviderOptions);
|
|
19
|
+
/**
|
|
20
|
+
* Render the full final URL for an API request to a given endpoint with a given payload.
|
|
21
|
+
* - Includes `?query` params if this is a `HEAD` or `GET` request.
|
|
22
|
+
*
|
|
23
|
+
* @throws {RequiredError} if this endpoint's path has `{placeholders}` but `payload` is not a data object.
|
|
24
|
+
* @throws {RequiredError} if this is a `HEAD` or `GET` request but `payload` is not a data object.
|
|
25
|
+
*/
|
|
26
|
+
renderURL<P, R>(endpoint: Endpoint<P, R>, payload: P, caller?: AnyCaller): URL;
|
|
27
|
+
/**
|
|
28
|
+
* Create a `Request` that targets this endpoint with a given base URL.
|
|
29
|
+
* - Path `{placeholders}` are rendered from the payload.
|
|
30
|
+
* - For `GET` and `HEAD`, remaining payload fields are appended as `?query` params.
|
|
31
|
+
* - For all other requests, payload is sent as the body.
|
|
32
|
+
*
|
|
33
|
+
* @throws {RequiredError} if this endpoint's path has `{placeholders}` but `payload` is not a data object.
|
|
34
|
+
* @throws {RequiredError} if this is a `HEAD` or `GET` request but `payload` is not a data object.
|
|
35
|
+
*/
|
|
36
|
+
getRequest<P, R>(endpoint: Endpoint<P, R>, payload: P, options?: RequestOptions, caller?: AnyCaller): Request;
|
|
5
37
|
/**
|
|
6
|
-
*
|
|
38
|
+
* Parse an HTTP `Response` for this endpoint.
|
|
39
|
+
* - Non-2xx responses become `ResponseError`.
|
|
40
|
+
* - Does not validate the result against the endpoint schema — use `ValidationAPIProvider` for that.
|
|
7
41
|
*/
|
|
8
|
-
|
|
42
|
+
parseResponse<P, R>(_endpoint: Endpoint<P, R>, response: Response, caller?: AnyCaller): Promise<R>;
|
|
43
|
+
fetch<P, R>(endpoint: Endpoint<P, R>, payload: P, options?: RequestOptions, caller?: AnyCaller): Promise<R>;
|
|
9
44
|
}
|
|
@@ -1,2 +1,78 @@
|
|
|
1
|
+
import { ResponseError } from "../../error/ResponseError.js";
|
|
2
|
+
import { isArrayItem } from "../../util/array.js";
|
|
3
|
+
import { getMessage } from "../../util/error.js";
|
|
4
|
+
import { assertHeadMethodPayload, getRequest, getResponseContent, HTTP_HEAD_METHODS, mergeRequestOptions, } from "../../util/http.js";
|
|
5
|
+
import { omitProps } from "../../util/object.js";
|
|
6
|
+
import { withURIParams } from "../../util/uri.js";
|
|
7
|
+
import { requireBaseURL, requireURL } from "../../util/url.js";
|
|
8
|
+
/** Provider for API endpoints rooted at a common base URL. */
|
|
1
9
|
export class APIProvider {
|
|
10
|
+
/** The common base URL for all rendered endpoint requests. */
|
|
11
|
+
url;
|
|
12
|
+
/** Default options used for HTTP requests created with `this.getRequest()` and `this.fetch()` */
|
|
13
|
+
options;
|
|
14
|
+
constructor({ url, options = {} }) {
|
|
15
|
+
this.url = requireBaseURL(url, undefined, APIProvider);
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Render the full final URL for an API request to a given endpoint with a given payload.
|
|
20
|
+
* - Includes `?query` params if this is a `HEAD` or `GET` request.
|
|
21
|
+
*
|
|
22
|
+
* @throws {RequiredError} if this endpoint's path has `{placeholders}` but `payload` is not a data object.
|
|
23
|
+
* @throws {RequiredError} if this is a `HEAD` or `GET` request but `payload` is not a data object.
|
|
24
|
+
*/
|
|
25
|
+
renderURL(endpoint, payload, caller = this.renderURL) {
|
|
26
|
+
const url = requireURL(`.${endpoint.renderPath(payload, caller)}`, this.url, caller);
|
|
27
|
+
// HEAD or GET have no body (but payload can only be data object).
|
|
28
|
+
if (isArrayItem(HTTP_HEAD_METHODS, endpoint.method)) {
|
|
29
|
+
assertHeadMethodPayload(payload, endpoint.method, caller);
|
|
30
|
+
if (payload) {
|
|
31
|
+
const params = endpoint.placeholders.length ? omitProps(payload, ...endpoint.placeholders) : payload; // Omit any params that were already embedded as `{placeholders}`
|
|
32
|
+
return withURIParams(url, params, caller);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return url;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create a `Request` that targets this endpoint with a given base URL.
|
|
39
|
+
* - Path `{placeholders}` are rendered from the payload.
|
|
40
|
+
* - For `GET` and `HEAD`, remaining payload fields are appended as `?query` params.
|
|
41
|
+
* - For all other requests, payload is sent as the body.
|
|
42
|
+
*
|
|
43
|
+
* @throws {RequiredError} if this endpoint's path has `{placeholders}` but `payload` is not a data object.
|
|
44
|
+
* @throws {RequiredError} if this is a `HEAD` or `GET` request but `payload` is not a data object.
|
|
45
|
+
*/
|
|
46
|
+
getRequest(endpoint, payload, options, caller = this.getRequest) {
|
|
47
|
+
// Render the path into the base URL.
|
|
48
|
+
const url = this.renderURL(endpoint, payload, caller);
|
|
49
|
+
// HEAD or GET requests have no payload because it was already rendered into the URL as `?query` params.
|
|
50
|
+
if (isArrayItem(HTTP_HEAD_METHODS, endpoint.method)) {
|
|
51
|
+
return getRequest(endpoint.method, url, undefined, options);
|
|
52
|
+
}
|
|
53
|
+
// Placeholders are rendered into the path so get omitted from the body payload.
|
|
54
|
+
if (endpoint.placeholders.length) {
|
|
55
|
+
const params = omitProps(payload, ...endpoint.placeholders); // Omit any params that were already embedded as `{placeholders}`
|
|
56
|
+
return getRequest(endpoint.method, url, params, options);
|
|
57
|
+
}
|
|
58
|
+
// No placeholders.
|
|
59
|
+
return getRequest(endpoint.method, url, payload, options);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Parse an HTTP `Response` for this endpoint.
|
|
63
|
+
* - Non-2xx responses become `ResponseError`.
|
|
64
|
+
* - Does not validate the result against the endpoint schema — use `ValidationAPIProvider` for that.
|
|
65
|
+
*/
|
|
66
|
+
async parseResponse(_endpoint, response, caller = this.parseResponse) {
|
|
67
|
+
const { ok, status } = response;
|
|
68
|
+
const content = await getResponseContent(response, caller);
|
|
69
|
+
if (!ok)
|
|
70
|
+
throw new ResponseError(getMessage(content) ?? `Error ${status}`, { code: status, cause: response, caller });
|
|
71
|
+
return content;
|
|
72
|
+
}
|
|
73
|
+
async fetch(endpoint, payload, options, caller = this.fetch) {
|
|
74
|
+
const request = this.getRequest(endpoint, payload, mergeRequestOptions(this.options, options), caller);
|
|
75
|
+
const response = await fetch(request);
|
|
76
|
+
return this.parseResponse(endpoint, response, caller);
|
|
77
|
+
}
|
|
2
78
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AnyCaller } from "../../util/function.js";
|
|
2
2
|
import { type RequestHandler, type RequestOptions } from "../../util/http.js";
|
|
3
3
|
import type { AnyEndpoint, Endpoint } from "../endpoint/Endpoint.js";
|
|
4
|
-
import {
|
|
4
|
+
import { APIProvider, type APIProviderOptions } from "./APIProvider.js";
|
|
5
5
|
/** A structured log entry emitted by `MockAPIProvider` for one of its provider operations. */
|
|
6
6
|
export type MockAPICall = {
|
|
7
7
|
readonly type: "fetch";
|
|
@@ -14,14 +14,14 @@ export type MockAPICall = {
|
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
16
16
|
* Construction options for a `MockAPIProvider`
|
|
17
|
-
* - Same as options for a normal `
|
|
17
|
+
* - Same as options for a normal `APIProvider`, but with an optional URL.
|
|
18
18
|
*/
|
|
19
|
-
export interface MockAPIProviderOptions extends Omit<
|
|
19
|
+
export interface MockAPIProviderOptions extends Omit<APIProviderOptions, "url"> {
|
|
20
20
|
/** Optional URL, defaults to `"https://api.mock.com"` */
|
|
21
|
-
url?:
|
|
21
|
+
url?: APIProviderOptions["url"];
|
|
22
22
|
}
|
|
23
23
|
/** Provider that logs API calls without sending network requests. */
|
|
24
|
-
export declare class MockAPIProvider extends
|
|
24
|
+
export declare class MockAPIProvider extends APIProvider {
|
|
25
25
|
readonly calls: MockAPICall[];
|
|
26
26
|
readonly handler: RequestHandler;
|
|
27
27
|
constructor(handler: RequestHandler, { url, ...options }?: MockAPIProviderOptions);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mergeRequestOptions } from "../../util/http.js";
|
|
2
|
-
import {
|
|
2
|
+
import { APIProvider } from "./APIProvider.js";
|
|
3
3
|
/** Provider that logs API calls without sending network requests. */
|
|
4
|
-
export class MockAPIProvider extends
|
|
4
|
+
export class MockAPIProvider extends APIProvider {
|
|
5
5
|
calls = [];
|
|
6
6
|
handler;
|
|
7
7
|
constructor(handler, { url = "https://api.mock.com", ...options } = {}) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AnyCaller } from "../../util/function.js";
|
|
2
2
|
import type { RequestOptions } from "../../util/http.js";
|
|
3
|
+
import type { URL, URLString } from "../../util/url.js";
|
|
3
4
|
import type { Endpoint } from "../endpoint/Endpoint.js";
|
|
4
5
|
import type { APIProvider } from "./APIProvider.js";
|
|
5
6
|
/**
|
|
@@ -8,6 +9,11 @@ import type { APIProvider } from "./APIProvider.js";
|
|
|
8
9
|
*/
|
|
9
10
|
export declare class ThroughAPIProvider implements APIProvider {
|
|
10
11
|
readonly source: APIProvider;
|
|
12
|
+
get url(): URLString;
|
|
13
|
+
get options(): RequestOptions;
|
|
11
14
|
constructor(source: APIProvider);
|
|
15
|
+
renderURL<P, R>(endpoint: Endpoint<P, R>, payload: P, caller?: AnyCaller): URL;
|
|
16
|
+
getRequest<P, R>(endpoint: Endpoint<P, R>, payload: P, options?: RequestOptions, caller?: AnyCaller): Request;
|
|
17
|
+
parseResponse<P, R>(endpoint: Endpoint<P, R>, response: Response, caller?: AnyCaller): Promise<R>;
|
|
12
18
|
fetch<P, R>(endpoint: Endpoint<P, R>, payload: P, options?: RequestOptions, caller?: AnyCaller): Promise<R>;
|
|
13
19
|
}
|
|
@@ -4,9 +4,24 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export class ThroughAPIProvider {
|
|
6
6
|
source;
|
|
7
|
+
get url() {
|
|
8
|
+
return this.source.url;
|
|
9
|
+
}
|
|
10
|
+
get options() {
|
|
11
|
+
return this.source.options;
|
|
12
|
+
}
|
|
7
13
|
constructor(source) {
|
|
8
14
|
this.source = source;
|
|
9
15
|
}
|
|
16
|
+
renderURL(endpoint, payload, caller = this.renderURL) {
|
|
17
|
+
return this.source.renderURL(endpoint, payload, caller);
|
|
18
|
+
}
|
|
19
|
+
getRequest(endpoint, payload, options, caller = this.getRequest) {
|
|
20
|
+
return this.source.getRequest(endpoint, payload, options, caller);
|
|
21
|
+
}
|
|
22
|
+
parseResponse(endpoint, response, caller = this.parseResponse) {
|
|
23
|
+
return this.source.parseResponse(endpoint, response, caller);
|
|
24
|
+
}
|
|
10
25
|
fetch(endpoint, payload, options, caller = this.fetch) {
|
|
11
26
|
return this.source.fetch(endpoint, payload, options, caller);
|
|
12
27
|
}
|
|
@@ -2,9 +2,10 @@ import type { SQL } from "bun";
|
|
|
2
2
|
import { PostgreSQLProvider, type SQLFragment } from "../db/index.js";
|
|
3
3
|
import type { ImmutableArray } from "../util/array.js";
|
|
4
4
|
import type { Data } from "../util/data.js";
|
|
5
|
-
|
|
5
|
+
import type { Identifier } from "../util/item.js";
|
|
6
|
+
export declare class BunPostgreSQLProvider<I extends Identifier = Identifier, T extends Data = Data> extends PostgreSQLProvider<I, T> {
|
|
6
7
|
private _sql;
|
|
7
8
|
constructor(sql: SQL);
|
|
8
|
-
exec<
|
|
9
|
+
exec<X extends Data>(strings: TemplateStringsArray, ...values: ImmutableArray<unknown>): Promise<ImmutableArray<X>>;
|
|
9
10
|
sqlIdentifier(name: string): SQLFragment;
|
|
10
11
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { SQLiteProvider } from "../db/provider/SQLiteProvider.js";
|
|
2
2
|
import { type ImmutableArray } from "../util/array.js";
|
|
3
3
|
import type { Data } from "../util/data.js";
|
|
4
|
+
import type { Identifier } from "../util/item.js";
|
|
4
5
|
import type { D1Database } from "./types.js";
|
|
5
6
|
/**
|
|
6
7
|
* Cloudflare D1 database provider.
|
|
7
8
|
*
|
|
8
9
|
* Uses the D1 Worker API for execution and standard SQL from `SQLProvider`.
|
|
9
10
|
*/
|
|
10
|
-
export declare class CloudflareD1Provider extends SQLiteProvider {
|
|
11
|
+
export declare class CloudflareD1Provider<I extends Identifier = Identifier, T extends Data = Data> extends SQLiteProvider<I, T> {
|
|
11
12
|
private readonly _db;
|
|
12
13
|
constructor(db: D1Database);
|
|
13
|
-
exec<
|
|
14
|
+
exec<X extends Data>(strings: TemplateStringsArray, ...values: ImmutableArray<unknown>): Promise<readonly X[]>;
|
|
14
15
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Collection } from "../db/collection/Collection.js";
|
|
2
2
|
import { DBProvider } from "../db/provider/DBProvider.js";
|
|
3
3
|
import type { Data } from "../util/data.js";
|
|
4
|
-
import type
|
|
5
|
-
import type {
|
|
4
|
+
import { type Item, type Items, type OptionalItem } from "../util/item.js";
|
|
5
|
+
import type { Query } from "../util/query.js";
|
|
6
6
|
import type { Updates } from "../util/update.js";
|
|
7
7
|
import type { KVNamespace } from "./types.js";
|
|
8
8
|
/**
|
|
@@ -20,27 +20,25 @@ import type { KVNamespace } from "./types.js";
|
|
|
20
20
|
* KV has no change feed or push notification mechanism.
|
|
21
21
|
* - **Updates:** `updateItem()` and `updateQuery()` throw `UnimplementedError`.
|
|
22
22
|
* - **Collection queries:** `getQuery()`, `setQuery()`, `deleteQuery()`, and `countQuery()` are not supported.
|
|
23
|
-
* KV does not expose efficient filtering, sorting, or collection scans, so this provider avoids
|
|
24
|
-
* the old "read everything and filter in memory" behavior.
|
|
23
|
+
* KV does not expose efficient filtering, sorting, or collection scans, so this provider avoids the old "read everything and filter in memory" behavior.
|
|
25
24
|
*
|
|
26
25
|
* ### Performance limitations
|
|
27
26
|
* - **Single-key store only:** This provider is intentionally limited to direct key reads and writes.
|
|
28
27
|
* If you need collection queries, filtering, sorting, or bulk mutations, use a different backend.
|
|
29
|
-
* - **Eventual consistency:** KV is eventually consistent, so reads may briefly return stale values
|
|
30
|
-
* shortly after writes.
|
|
28
|
+
* - **Eventual consistency:** KV is eventually consistent, so reads may briefly return stale values shortly after writes.
|
|
31
29
|
*/
|
|
32
|
-
export declare class CloudflareKVProvider extends DBProvider<
|
|
30
|
+
export declare class CloudflareKVProvider<I extends string = string, T extends Data = Data> extends DBProvider<I, T> {
|
|
33
31
|
private readonly _kv;
|
|
34
32
|
constructor(kv: KVNamespace);
|
|
35
|
-
getItem<
|
|
36
|
-
getItemSequence<
|
|
37
|
-
addItem<
|
|
38
|
-
setItem<
|
|
39
|
-
updateItem<
|
|
40
|
-
deleteItem<
|
|
41
|
-
getQuery<
|
|
42
|
-
getQuerySequence<
|
|
43
|
-
setQuery<
|
|
44
|
-
updateQuery<
|
|
45
|
-
deleteQuery<
|
|
33
|
+
getItem<II extends I, TT extends T>({ name }: Collection<string, II, TT>, id: II): Promise<OptionalItem<II, TT>>;
|
|
34
|
+
getItemSequence<II extends I, TT extends T>(_collection: Collection<string, II, TT>, _id: II): AsyncIterable<OptionalItem<II, TT>>;
|
|
35
|
+
addItem<II extends I, TT extends T>({ name }: Collection<string, II, TT>, data: TT): Promise<II>;
|
|
36
|
+
setItem<II extends I, TT extends T>({ name }: Collection<string, II, TT>, id: II, data: TT): Promise<void>;
|
|
37
|
+
updateItem<II extends I, TT extends T>(_collection: Collection<string, II, TT>, _id: II, _updates: Updates<Item<II, TT>>): Promise<void>;
|
|
38
|
+
deleteItem<II extends I, TT extends T>({ name }: Collection<string, II, TT>, id: II): Promise<void>;
|
|
39
|
+
getQuery<II extends I, TT extends T>(_collection: Collection<string, II, TT>, _query?: Query<Item<II, TT>>): Promise<Items<II, TT>>;
|
|
40
|
+
getQuerySequence<II extends I, TT extends T>(_collection: Collection<string, II, TT>, _query?: Query<Item<II, TT>>): AsyncIterable<Items<II, TT>>;
|
|
41
|
+
setQuery<II extends I, TT extends T>(_collection: Collection<string, II, TT>, _query: Query<Item<II, TT>>, _data: TT): Promise<void>;
|
|
42
|
+
updateQuery<II extends I, TT extends T>(_collection: Collection<string, II, TT>, _query: Query<Item<II, TT>>, _updates: Updates<TT>): Promise<void>;
|
|
43
|
+
deleteQuery<II extends I, TT extends T>(_collection: Collection<string, II, TT>, _query: Query<Item<II, TT>>): Promise<void>;
|
|
46
44
|
}
|
|
@@ -17,14 +17,12 @@ import { randomUUID } from "../util/uuid.js";
|
|
|
17
17
|
* KV has no change feed or push notification mechanism.
|
|
18
18
|
* - **Updates:** `updateItem()` and `updateQuery()` throw `UnimplementedError`.
|
|
19
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.
|
|
20
|
+
* KV does not expose efficient filtering, sorting, or collection scans, so this provider avoids the old "read everything and filter in memory" behavior.
|
|
22
21
|
*
|
|
23
22
|
* ### Performance limitations
|
|
24
23
|
* - **Single-key store only:** This provider is intentionally limited to direct key reads and writes.
|
|
25
24
|
* 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.
|
|
25
|
+
* - **Eventual consistency:** KV is eventually consistent, so reads may briefly return stale values shortly after writes.
|
|
28
26
|
*/
|
|
29
27
|
export class CloudflareKVProvider extends DBProvider {
|
|
30
28
|
_kv;
|
|
@@ -33,40 +31,40 @@ export class CloudflareKVProvider extends DBProvider {
|
|
|
33
31
|
this._kv = kv;
|
|
34
32
|
}
|
|
35
33
|
async getItem({ name }, id) {
|
|
36
|
-
const data = (await this._kv.get(_getKey(name, id), { type: "json" }));
|
|
34
|
+
const data = (await this._kv.get(_getKey(name, id), { type: "json" })); // `as TT` needed: KV returns unknown from JSON parse.
|
|
37
35
|
if (data)
|
|
38
36
|
return getItem(id, data);
|
|
39
37
|
}
|
|
40
|
-
getItemSequence(
|
|
38
|
+
getItemSequence(_collection, _id) {
|
|
41
39
|
throw new UnimplementedError("CloudflareKVProvider does not support realtime subscriptions");
|
|
42
40
|
}
|
|
43
41
|
async addItem({ name }, data) {
|
|
44
|
-
const id = randomUUID();
|
|
42
|
+
const id = randomUUID(); // `as II` needed: TypeScript can't narrow II from string return type.
|
|
45
43
|
await this._kv.put(_getKey(name, id), JSON.stringify(data));
|
|
46
44
|
return id;
|
|
47
45
|
}
|
|
48
46
|
async setItem({ name }, id, data) {
|
|
49
47
|
await this._kv.put(_getKey(name, id), JSON.stringify(data));
|
|
50
48
|
}
|
|
51
|
-
async updateItem(
|
|
49
|
+
async updateItem(_collection, _id, _updates) {
|
|
52
50
|
throw new UnimplementedError("CloudflareKVProvider does not support updates to items");
|
|
53
51
|
}
|
|
54
52
|
async deleteItem({ name }, id) {
|
|
55
53
|
await this._kv.delete(_getKey(name, id));
|
|
56
54
|
}
|
|
57
|
-
async getQuery(
|
|
55
|
+
async getQuery(_collection, _query) {
|
|
58
56
|
throw new UnimplementedError("CloudflareKVProvider does not support querying items");
|
|
59
57
|
}
|
|
60
|
-
getQuerySequence(
|
|
58
|
+
getQuerySequence(_collection, _query) {
|
|
61
59
|
throw new UnimplementedError("CloudflareKVProvider does not support realtime subscriptions");
|
|
62
60
|
}
|
|
63
|
-
async setQuery(
|
|
61
|
+
async setQuery(_collection, _query, _data) {
|
|
64
62
|
throw new UnimplementedError("CloudflareKVProvider does not support querying items");
|
|
65
63
|
}
|
|
66
|
-
async updateQuery(
|
|
64
|
+
async updateQuery(_collection, _query, _updates) {
|
|
67
65
|
throw new UnimplementedError("CloudflareKVProvider does not support updates to items");
|
|
68
66
|
}
|
|
69
|
-
async deleteQuery(
|
|
67
|
+
async deleteQuery(_collection, _query) {
|
|
70
68
|
throw new UnimplementedError("CloudflareKVProvider does not support querying items");
|
|
71
69
|
}
|
|
72
70
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Data } from "../../util/data.js";
|
|
2
|
+
import type { Identifier, Item } from "../../util/item.js";
|
|
3
|
+
import type { Query } from "../../util/query.js";
|
|
4
|
+
import type { Collection } from "../collection/Collection.js";
|
|
5
|
+
import type { DBProvider } from "../provider/DBProvider.js";
|
|
6
|
+
import type { MemoryDBProvider } from "../provider/MemoryDBProvider.js";
|
|
7
|
+
import { ItemStore } from "../store/ItemStore.js";
|
|
8
|
+
import { QueryStore } from "../store/QueryStore.js";
|
|
9
|
+
/**
|
|
10
|
+
* Cache of `ItemStore` and `QueryStore` objects for a single collection.
|
|
11
|
+
* - Use `getItem(id)` to retrieve or create the `ItemStore` for a given id.
|
|
12
|
+
* - Use `getQuery(query)` to retrieve or create the `QueryStore` for a given query.
|
|
13
|
+
*/
|
|
14
|
+
export declare class CollectionCache<I extends Identifier, T extends Data> implements Disposable {
|
|
15
|
+
private readonly _items;
|
|
16
|
+
private readonly _queries;
|
|
17
|
+
readonly collection: Collection<string, I, T>;
|
|
18
|
+
readonly provider: DBProvider<I>;
|
|
19
|
+
readonly memory: MemoryDBProvider<I> | undefined;
|
|
20
|
+
constructor(collection: Collection<string, I, T>, provider: DBProvider<I>, memory?: MemoryDBProvider<I>);
|
|
21
|
+
/** Get (or create) the `ItemStore` for the given id. */
|
|
22
|
+
getItem(id: I): ItemStore<I, T>;
|
|
23
|
+
/** Get (or create) the `QueryStore` for the given query. */
|
|
24
|
+
getQuery(query: Query<Item<I, T>>): QueryStore<I, T>;
|
|
25
|
+
/** Refresh a specific item store. */
|
|
26
|
+
refreshItem(id: I): void;
|
|
27
|
+
/** Refresh every cached item store. */
|
|
28
|
+
refreshItems(): void;
|
|
29
|
+
/** Refresh a specific query store. */
|
|
30
|
+
refreshQuery(query: Query<Item<I, T>>): void;
|
|
31
|
+
/** Refresh every cached query store. */
|
|
32
|
+
refreshQueries(): void;
|
|
33
|
+
/** Refresh every cached store (items and queries). */
|
|
34
|
+
refreshAll(): void;
|
|
35
|
+
private _queryKey;
|
|
36
|
+
[Symbol.dispose](): void;
|
|
37
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { setMapItem } from "../../util/map.js";
|
|
2
|
+
import { ItemStore } from "../store/ItemStore.js";
|
|
3
|
+
import { QueryStore } from "../store/QueryStore.js";
|
|
4
|
+
/**
|
|
5
|
+
* Cache of `ItemStore` and `QueryStore` objects for a single collection.
|
|
6
|
+
* - Use `getItem(id)` to retrieve or create the `ItemStore` for a given id.
|
|
7
|
+
* - Use `getQuery(query)` to retrieve or create the `QueryStore` for a given query.
|
|
8
|
+
*/
|
|
9
|
+
export class CollectionCache {
|
|
10
|
+
_items = new Map();
|
|
11
|
+
_queries = new Map();
|
|
12
|
+
collection;
|
|
13
|
+
provider;
|
|
14
|
+
memory;
|
|
15
|
+
constructor(collection, provider, memory) {
|
|
16
|
+
this.collection = collection;
|
|
17
|
+
this.provider = provider;
|
|
18
|
+
this.memory = memory;
|
|
19
|
+
}
|
|
20
|
+
/** Get (or create) the `ItemStore` for the given id. */
|
|
21
|
+
getItem(id) {
|
|
22
|
+
const key = String(id);
|
|
23
|
+
return this._items.get(key) || setMapItem(this._items, key, new ItemStore(this.collection, id, this.provider, this.memory));
|
|
24
|
+
}
|
|
25
|
+
/** Get (or create) the `QueryStore` for the given query. */
|
|
26
|
+
getQuery(query) {
|
|
27
|
+
const key = this._queryKey(query);
|
|
28
|
+
return this._queries.get(key) || setMapItem(this._queries, key, new QueryStore(this.collection, query, this.provider, this.memory));
|
|
29
|
+
}
|
|
30
|
+
/** Refresh a specific item store. */
|
|
31
|
+
refreshItem(id) {
|
|
32
|
+
this._items.get(String(id))?.refresh();
|
|
33
|
+
}
|
|
34
|
+
/** Refresh every cached item store. */
|
|
35
|
+
refreshItems() {
|
|
36
|
+
for (const store of this._items.values())
|
|
37
|
+
store.refresh();
|
|
38
|
+
}
|
|
39
|
+
/** Refresh a specific query store. */
|
|
40
|
+
refreshQuery(query) {
|
|
41
|
+
this._queries.get(this._queryKey(query))?.refresh();
|
|
42
|
+
}
|
|
43
|
+
/** Refresh every cached query store. */
|
|
44
|
+
refreshQueries() {
|
|
45
|
+
for (const store of this._queries.values())
|
|
46
|
+
store.refresh();
|
|
47
|
+
}
|
|
48
|
+
/** Refresh every cached store (items and queries). */
|
|
49
|
+
refreshAll() {
|
|
50
|
+
this.refreshItems();
|
|
51
|
+
this.refreshQueries();
|
|
52
|
+
}
|
|
53
|
+
_queryKey(query) {
|
|
54
|
+
return JSON.stringify(query);
|
|
55
|
+
}
|
|
56
|
+
// Implement Disposable.
|
|
57
|
+
[Symbol.dispose]() {
|
|
58
|
+
// Note: `ItemStore`/`QueryStore` are not themselves `Disposable`, so we only drop references.
|
|
59
|
+
this._items.clear();
|
|
60
|
+
this._queries.clear();
|
|
61
|
+
}
|
|
62
|
+
}
|