@atscript/db-client 0.1.43
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/README.md +66 -0
- package/dist/index.cjs +197 -0
- package/dist/index.d.cts +398 -0
- package/dist/index.d.mts +398 -0
- package/dist/index.mjs +195 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://db.atscript.dev/logo.svg" alt="Atscript" width="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">@atscript/db-client</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
Browser-compatible HTTP client for <code>@atscript/moost-db</code> REST endpoints.
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://db.atscript.dev">Documentation</a> · <a href="https://db.atscript.dev/http/client">HTTP Client Guide</a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
Type-safe HTTP client that mirrors the server-side `AtscriptDbTable` API over REST. Works in browsers, Node.js, and any runtime with `fetch`. Supports the full query surface — filters, sorting, pagination, relation loading, text search, and aggregation.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add @atscript/db-client
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { Client } from "@atscript/db-client";
|
|
29
|
+
import type { User } from "./models/user.as";
|
|
30
|
+
|
|
31
|
+
const users = new Client<typeof User>("/api/users", {
|
|
32
|
+
baseUrl: "https://api.example.com",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Query
|
|
36
|
+
const active = await users.findMany({ filter: { status: "active" } });
|
|
37
|
+
const user = await users.findById("abc-123");
|
|
38
|
+
|
|
39
|
+
// Write
|
|
40
|
+
const { insertedId } = await users.insertOne({ name: "Alice" });
|
|
41
|
+
await users.updateOne({ id: insertedId, role: "admin" });
|
|
42
|
+
await users.deleteOne(insertedId);
|
|
43
|
+
|
|
44
|
+
// Metadata
|
|
45
|
+
const meta = await users.meta();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
- **Full CRUD** — `findMany`, `findOne`, `findById`, `count`, `pages`, `insertOne`, `insertMany`, `updateOne`, `bulkUpdate`, `replaceOne`, `bulkReplace`, `deleteOne`
|
|
51
|
+
- **URL query syntax** — filtering, sorting, pagination, field selection, relation loading via `$with`
|
|
52
|
+
- **Search** — full-text (`search()`) and vector search via query controls
|
|
53
|
+
- **Aggregation** — `$groupBy` with aggregate functions
|
|
54
|
+
- **Error handling** — `ClientError` with structured validation errors
|
|
55
|
+
- **SSR isomorphism** — `DbInterface<T>` shared between server `AtscriptDbTable` and client `Client`
|
|
56
|
+
- **Configurable** — custom `fetch`, static or async headers, base URL
|
|
57
|
+
|
|
58
|
+
## Documentation
|
|
59
|
+
|
|
60
|
+
- [HTTP Client Guide](https://db.atscript.dev/http/client) — Full API reference with examples
|
|
61
|
+
- [HTTP API Guide](https://db.atscript.dev/http/) — Server-side setup
|
|
62
|
+
- [URL Query Syntax](https://db.atscript.dev/http/query-syntax) — Filter, sort, and pagination syntax
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _uniqu_url_builder = require("@uniqu/url/builder");
|
|
3
|
+
//#region src/client-error.ts
|
|
4
|
+
/**
|
|
5
|
+
* Error thrown by `Client` when the server responds with a non-2xx status code.
|
|
6
|
+
* Captures the HTTP status and the structured error body from moost-db.
|
|
7
|
+
*/
|
|
8
|
+
var ClientError = class extends Error {
|
|
9
|
+
name = "ClientError";
|
|
10
|
+
constructor(status, body) {
|
|
11
|
+
super(body.message || `HTTP ${status}`);
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.body = body;
|
|
14
|
+
}
|
|
15
|
+
/** Shortcut to structured validation/DB errors from the server. */
|
|
16
|
+
get errors() {
|
|
17
|
+
return this.body.errors ?? [];
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/client.ts
|
|
22
|
+
/**
|
|
23
|
+
* Browser-compatible HTTP client for moost-db REST endpoints.
|
|
24
|
+
*
|
|
25
|
+
* Two usage modes (same class, different generic):
|
|
26
|
+
* ```typescript
|
|
27
|
+
* // Untyped — broad Record<string, unknown> typing
|
|
28
|
+
* const users = new Client('/db/tables/users')
|
|
29
|
+
*
|
|
30
|
+
* // Type-safe — Atscript type as generic parameter
|
|
31
|
+
* const users = new Client<typeof User>('/db/tables/users')
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
var Client = class {
|
|
35
|
+
_path;
|
|
36
|
+
_baseUrl;
|
|
37
|
+
_fetch;
|
|
38
|
+
_headers;
|
|
39
|
+
_metaPromise;
|
|
40
|
+
constructor(path, opts) {
|
|
41
|
+
this._path = path.endsWith("/") ? path.slice(0, -1) : path;
|
|
42
|
+
this._baseUrl = opts?.baseUrl ?? "";
|
|
43
|
+
this._fetch = opts?.fetch ?? globalThis.fetch.bind(globalThis);
|
|
44
|
+
this._headers = opts?.headers;
|
|
45
|
+
}
|
|
46
|
+
async findOne(query) {
|
|
47
|
+
const controls = {
|
|
48
|
+
...query?.controls,
|
|
49
|
+
$limit: 1
|
|
50
|
+
};
|
|
51
|
+
return (await this._get("query", {
|
|
52
|
+
...query,
|
|
53
|
+
controls
|
|
54
|
+
}))[0] ?? null;
|
|
55
|
+
}
|
|
56
|
+
async findMany(query) {
|
|
57
|
+
return this._get("query", query);
|
|
58
|
+
}
|
|
59
|
+
async findById(id, query) {
|
|
60
|
+
if (id !== null && typeof id === "object") {
|
|
61
|
+
const params = this._idToParams(id);
|
|
62
|
+
if (query?.controls) {
|
|
63
|
+
const controlStr = (0, _uniqu_url_builder.buildUrl)({ controls: query.controls });
|
|
64
|
+
if (controlStr) for (const [k, v] of new URLSearchParams(controlStr)) params.set(k, v);
|
|
65
|
+
}
|
|
66
|
+
const qs = params.toString();
|
|
67
|
+
try {
|
|
68
|
+
return await this._request("GET", `one${qs ? `?${qs}` : ""}`);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
if (e instanceof ClientError && e.status === 404) return null;
|
|
71
|
+
throw e;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const controlStr = query?.controls ? (0, _uniqu_url_builder.buildUrl)({ controls: query.controls }) : "";
|
|
75
|
+
try {
|
|
76
|
+
return await this._request("GET", `one/${encodeURIComponent(String(id))}${controlStr ? `?${controlStr}` : ""}`);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
if (e instanceof ClientError && e.status === 404) return null;
|
|
79
|
+
throw e;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async count(query) {
|
|
83
|
+
const controls = {
|
|
84
|
+
...query?.controls,
|
|
85
|
+
$count: true
|
|
86
|
+
};
|
|
87
|
+
return this._get("query", {
|
|
88
|
+
...query,
|
|
89
|
+
controls
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
async findManyWithCount(query) {
|
|
93
|
+
const controls = query?.controls ?? {};
|
|
94
|
+
const limit = controls.$limit || 1e3;
|
|
95
|
+
const skip = controls.$skip || 0;
|
|
96
|
+
const page = Math.floor(skip / limit) + 1;
|
|
97
|
+
const result = await this._get("pages", {
|
|
98
|
+
...query,
|
|
99
|
+
controls: {
|
|
100
|
+
...controls,
|
|
101
|
+
$page: page,
|
|
102
|
+
$size: limit
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
data: result.data,
|
|
107
|
+
count: result.count
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
async pages(query) {
|
|
111
|
+
return this._get("pages", query);
|
|
112
|
+
}
|
|
113
|
+
async search(text, query, indexName) {
|
|
114
|
+
const controls = {
|
|
115
|
+
...query?.controls,
|
|
116
|
+
$search: text
|
|
117
|
+
};
|
|
118
|
+
if (indexName) controls.$index = indexName;
|
|
119
|
+
return this._get("query", {
|
|
120
|
+
...query,
|
|
121
|
+
controls
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
async aggregate(query) {
|
|
125
|
+
return this._get("query", query);
|
|
126
|
+
}
|
|
127
|
+
async insertOne(data) {
|
|
128
|
+
return this._request("POST", "", data);
|
|
129
|
+
}
|
|
130
|
+
async insertMany(data) {
|
|
131
|
+
return this._request("POST", "", data);
|
|
132
|
+
}
|
|
133
|
+
async updateOne(data) {
|
|
134
|
+
return this._request("PATCH", "", data);
|
|
135
|
+
}
|
|
136
|
+
async bulkUpdate(data) {
|
|
137
|
+
return this._request("PATCH", "", data);
|
|
138
|
+
}
|
|
139
|
+
async replaceOne(data) {
|
|
140
|
+
return this._request("PUT", "", data);
|
|
141
|
+
}
|
|
142
|
+
async bulkReplace(data) {
|
|
143
|
+
return this._request("PUT", "", data);
|
|
144
|
+
}
|
|
145
|
+
async deleteOne(id) {
|
|
146
|
+
if (id !== null && typeof id === "object") return this._request("DELETE", `?${this._idToParams(id).toString()}`);
|
|
147
|
+
return this._request("DELETE", encodeURIComponent(String(id)));
|
|
148
|
+
}
|
|
149
|
+
async meta() {
|
|
150
|
+
if (!this._metaPromise) this._metaPromise = this._request("GET", "meta");
|
|
151
|
+
return this._metaPromise;
|
|
152
|
+
}
|
|
153
|
+
_idToParams(id) {
|
|
154
|
+
const params = new URLSearchParams();
|
|
155
|
+
for (const [k, v] of Object.entries(id)) params.set(k, String(v));
|
|
156
|
+
return params;
|
|
157
|
+
}
|
|
158
|
+
async _get(endpoint, query) {
|
|
159
|
+
const qs = query ? (0, _uniqu_url_builder.buildUrl)(query) : "";
|
|
160
|
+
return this._request("GET", `${endpoint}${qs ? `?${qs}` : ""}`);
|
|
161
|
+
}
|
|
162
|
+
async _resolveHeaders() {
|
|
163
|
+
if (!this._headers) return {};
|
|
164
|
+
if (typeof this._headers === "function") return await this._headers();
|
|
165
|
+
return this._headers;
|
|
166
|
+
}
|
|
167
|
+
async _request(method, endpoint, body) {
|
|
168
|
+
const sep = endpoint && !endpoint.startsWith("?") ? "/" : "";
|
|
169
|
+
const url = `${this._baseUrl}${this._path}${sep}${endpoint}`;
|
|
170
|
+
const headers = { ...await this._resolveHeaders() };
|
|
171
|
+
const init = {
|
|
172
|
+
method,
|
|
173
|
+
headers
|
|
174
|
+
};
|
|
175
|
+
if (body !== void 0) {
|
|
176
|
+
headers["Content-Type"] = "application/json";
|
|
177
|
+
init.body = JSON.stringify(body);
|
|
178
|
+
}
|
|
179
|
+
const res = await this._fetch(url, init);
|
|
180
|
+
if (!res.ok) {
|
|
181
|
+
let errorBody;
|
|
182
|
+
try {
|
|
183
|
+
errorBody = await res.json();
|
|
184
|
+
} catch {
|
|
185
|
+
errorBody = {
|
|
186
|
+
message: res.statusText,
|
|
187
|
+
statusCode: res.status
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
throw new ClientError(res.status, errorBody);
|
|
191
|
+
}
|
|
192
|
+
return res.json();
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
//#endregion
|
|
196
|
+
exports.Client = Client;
|
|
197
|
+
exports.ClientError = ClientError;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
//#region ../../node_modules/.pnpm/@uniqu+core@0.1.2/node_modules/@uniqu/core/dist/index.d.ts
|
|
2
|
+
/** All comparison operators supported by the filter format. */
|
|
3
|
+
type ComparisonOp = '$eq' | '$ne' | '$gt' | '$gte' | '$lt' | '$lte' | '$in' | '$nin' | '$regex' | '$exists';
|
|
4
|
+
/**
|
|
5
|
+
* Per-field typed operator map. When `V` is the field's value type, operators
|
|
6
|
+
* are constrained accordingly:
|
|
7
|
+
* - `$regex` is only available when `V` extends `string`
|
|
8
|
+
* - `$gt/$gte/$lt/$lte` are only available when `V` extends `number | string | Date`
|
|
9
|
+
*/
|
|
10
|
+
type FieldOpsFor<V> = {
|
|
11
|
+
$eq?: V;
|
|
12
|
+
$ne?: V;
|
|
13
|
+
$in?: V[];
|
|
14
|
+
$nin?: V[];
|
|
15
|
+
$exists?: boolean;
|
|
16
|
+
} & (V extends string ? {
|
|
17
|
+
$regex?: RegExp | string;
|
|
18
|
+
} : {}) & (V extends number | string | Date ? {
|
|
19
|
+
$gt?: V;
|
|
20
|
+
$gte?: V;
|
|
21
|
+
$lt?: V;
|
|
22
|
+
$lte?: V;
|
|
23
|
+
} : {});
|
|
24
|
+
/** Untyped operator map. */
|
|
25
|
+
/**
|
|
26
|
+
* A filter expression is either a comparison leaf or a logical branch.
|
|
27
|
+
* `T` is the entity shape — provides type-safe field names and value types.
|
|
28
|
+
* Defaults to `Record<string, unknown>` (untyped).
|
|
29
|
+
*/
|
|
30
|
+
type FilterExpr<T = Record<string, unknown>> = ComparisonNode<T> | LogicalNode<T>;
|
|
31
|
+
/**
|
|
32
|
+
* Leaf node: one or more field comparisons.
|
|
33
|
+
* When `T` is typed, only known keys are allowed.
|
|
34
|
+
* When untyped (default), any string key is accepted.
|
|
35
|
+
*/
|
|
36
|
+
type ComparisonNode<T = Record<string, unknown>> = { [K in keyof T & string]?: T[K] | FieldOpsFor<T[K]> };
|
|
37
|
+
/**
|
|
38
|
+
* Branch node: logical combination of child expressions.
|
|
39
|
+
* Each variant forbids the other logical keys via `never` to prevent
|
|
40
|
+
* mixing comparison fields with logical operators at the type level.
|
|
41
|
+
*/
|
|
42
|
+
type LogicalNode<T = Record<string, unknown>> = {
|
|
43
|
+
$and: FilterExpr<T>[];
|
|
44
|
+
$or?: never;
|
|
45
|
+
$not?: never;
|
|
46
|
+
} | {
|
|
47
|
+
$or: FilterExpr<T>[];
|
|
48
|
+
$and?: never;
|
|
49
|
+
$not?: never;
|
|
50
|
+
} | {
|
|
51
|
+
$not: FilterExpr<T>;
|
|
52
|
+
$and?: never;
|
|
53
|
+
$or?: never;
|
|
54
|
+
};
|
|
55
|
+
/** Known aggregate function names. Consumers may support additional functions via the (string & {}) escape hatch. */
|
|
56
|
+
type AggregateFn = 'sum' | 'count' | 'avg' | 'min' | 'max';
|
|
57
|
+
/** A single aggregate function call within $select. Generic params preserve literal types for result inference. */
|
|
58
|
+
interface AggregateExpr<Fn extends string = AggregateFn | (string & {}), Field extends string = string, Alias extends string = string> {
|
|
59
|
+
/** Function name (sum, count, avg, min, max, or custom). */
|
|
60
|
+
$fn: Fn;
|
|
61
|
+
/** Field to aggregate. '*' for count(*). */
|
|
62
|
+
$field: Field;
|
|
63
|
+
/** Alias for the result. Auto-generated by URL parser if omitted. */
|
|
64
|
+
$as?: Alias;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Projection definition.
|
|
68
|
+
* - Array form: inclusion list with optional aggregates.
|
|
69
|
+
* Plain strings select fields; AggregateExpr objects define computed columns.
|
|
70
|
+
* - Object form: inclusion/exclusion map (0 or 1 per field). No aggregates in this form.
|
|
71
|
+
*/
|
|
72
|
+
type SelectExpr<T = Record<string, unknown>> = ((keyof T & string) | AggregateExpr)[] | Partial<Record<keyof T & string, 0 | 1>>;
|
|
73
|
+
/** Query controls (pagination, projection, sorting, grouping). Generic `T` constrains field names. */
|
|
74
|
+
interface UniqueryControls<T = Record<string, unknown>, Nav extends Record<string, unknown> = Record<string, unknown>> {
|
|
75
|
+
$sort?: Partial<Record<keyof T & string, 1 | -1>>;
|
|
76
|
+
$skip?: number;
|
|
77
|
+
$limit?: number;
|
|
78
|
+
$count?: boolean;
|
|
79
|
+
$select?: SelectExpr<T>;
|
|
80
|
+
/** Fields to group by for aggregate queries. */
|
|
81
|
+
$groupBy?: (keyof T & string)[];
|
|
82
|
+
/** Post-aggregation filter. Operates on aggregate aliases and dimension fields. */
|
|
83
|
+
$having?: FilterExpr;
|
|
84
|
+
/** Relations to populate alongside the query. */
|
|
85
|
+
$with?: TypedWithRelation<Nav>[];
|
|
86
|
+
/** Pass-through for unknown $-prefixed keywords. */
|
|
87
|
+
[key: `$${string}`]: unknown;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Canonical query representation.
|
|
91
|
+
* When `name` is present this is a nested relation (sub-query inside `$with`).
|
|
92
|
+
* When absent it is the root query.
|
|
93
|
+
*/
|
|
94
|
+
interface Uniquery<T = Record<string, unknown>, Nav extends Record<string, unknown> = Record<string, unknown>> {
|
|
95
|
+
/** Relation name. Present only for nested `$with` sub-queries. */
|
|
96
|
+
name?: string;
|
|
97
|
+
filter?: FilterExpr<T>;
|
|
98
|
+
controls?: UniqueryControls<T, Nav>;
|
|
99
|
+
/** Pre-computed insights. */
|
|
100
|
+
insights?: UniqueryInsights;
|
|
101
|
+
}
|
|
102
|
+
/** Unwrap array types to get the element type for nav props. */
|
|
103
|
+
type NavTarget<T> = T extends Array<infer U> ? U : T;
|
|
104
|
+
/**
|
|
105
|
+
* A typed $with relation entry.
|
|
106
|
+
* When Nav is typed (from __navProps), name is constrained to known nav prop keys.
|
|
107
|
+
* Each entry gets its own filter/controls typed to the target entity.
|
|
108
|
+
* Falls back to untyped WithRelation when Nav has no known keys.
|
|
109
|
+
*/
|
|
110
|
+
type TypedWithRelation<Nav extends Record<string, unknown>> = [keyof Nav & string] extends [never] ? WithRelation | string : { [K in keyof Nav & string]: {
|
|
111
|
+
name: K;
|
|
112
|
+
filter?: FilterExpr<NavTarget<Nav[K]> extends {
|
|
113
|
+
__ownProps: infer F;
|
|
114
|
+
} ? F : Record<string, unknown>>;
|
|
115
|
+
controls?: UniqueryControls<NavTarget<Nav[K]> extends {
|
|
116
|
+
__ownProps: infer F;
|
|
117
|
+
} ? F : Record<string, unknown>, NavTarget<Nav[K]> extends {
|
|
118
|
+
__navProps: infer N extends Record<string, unknown>;
|
|
119
|
+
} ? N : Record<string, unknown>>;
|
|
120
|
+
insights?: UniqueryInsights;
|
|
121
|
+
} }[keyof Nav & string] | (keyof Nav & string);
|
|
122
|
+
/** Untyped $with relation — used when Nav generic is not provided. */
|
|
123
|
+
type WithRelation = {
|
|
124
|
+
name: string;
|
|
125
|
+
filter?: FilterExpr;
|
|
126
|
+
controls?: UniqueryControls;
|
|
127
|
+
insights?: UniqueryInsights;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Insight operator includes comparison ops, control ops ($-prefixed),
|
|
131
|
+
* and aggregate function names (bare, e.g. 'sum', 'avg').
|
|
132
|
+
*/
|
|
133
|
+
type InsightOp = ComparisonOp | '$select' | '$order' | '$with' | '$groupBy' | '$having' | AggregateFn | (string & {});
|
|
134
|
+
/** Map of field names to the set of operators used on that field. */
|
|
135
|
+
type UniqueryInsights = Map<string, Set<InsightOp>>;
|
|
136
|
+
/** Aggregate query controls. Separate from UniqueryControls: $groupBy is required, $with is forbidden. */
|
|
137
|
+
interface AggregateControls<T = Record<string, unknown>, D extends keyof T & string = keyof T & string, M extends keyof T & string = keyof T & string> {
|
|
138
|
+
$groupBy: D[];
|
|
139
|
+
$select?: (D | AggregateExpr<AggregateFn, M | '*'>)[];
|
|
140
|
+
$having?: FilterExpr;
|
|
141
|
+
$sort?: Record<string, 1 | -1>;
|
|
142
|
+
$skip?: number;
|
|
143
|
+
$limit?: number;
|
|
144
|
+
$count?: boolean;
|
|
145
|
+
[key: `$${string}`]: unknown;
|
|
146
|
+
}
|
|
147
|
+
/** Aggregate query — no name (can't nest), no Nav (no $with). */
|
|
148
|
+
interface AggregateQuery<T = Record<string, unknown>, D extends keyof T & string = keyof T & string, M extends keyof T & string = keyof T & string> {
|
|
149
|
+
filter?: FilterExpr<T>;
|
|
150
|
+
controls: AggregateControls<T, D, M>;
|
|
151
|
+
insights?: UniqueryInsights;
|
|
152
|
+
}
|
|
153
|
+
/** Resolve the output alias of an AggregateExpr. Uses $as if provided, otherwise generates {fn}_{field}. */
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region ../../node_modules/.pnpm/@atscript+typescript@0.1.41_@atscript+core@0.1.41_@atscript+db@packages+db_@prostojs+lo_041ff52083ad05a1dfbd1c9f541c1d21/node_modules/@atscript/typescript/dist/utils.d.ts
|
|
156
|
+
/** Top-level serialized annotated type. JSON-safe representation of a {@link TAtscriptAnnotatedType}. */
|
|
157
|
+
interface TSerializedAnnotatedType extends TSerializedAnnotatedTypeInner {
|
|
158
|
+
/** Format version for forward compatibility */
|
|
159
|
+
$v: number;
|
|
160
|
+
}
|
|
161
|
+
/** Serialized annotated type node (used for nested types within the top-level). */
|
|
162
|
+
interface TSerializedAnnotatedTypeInner {
|
|
163
|
+
type: TSerializedTypeDef;
|
|
164
|
+
metadata: Record<string, unknown>;
|
|
165
|
+
optional?: boolean;
|
|
166
|
+
id?: string;
|
|
167
|
+
}
|
|
168
|
+
interface TSerializedTypeFinal {
|
|
169
|
+
kind: '';
|
|
170
|
+
designType: string;
|
|
171
|
+
value?: string | number | boolean;
|
|
172
|
+
tags: string[];
|
|
173
|
+
}
|
|
174
|
+
interface TSerializedTypeObject {
|
|
175
|
+
kind: 'object';
|
|
176
|
+
props: Record<string, TSerializedAnnotatedTypeInner>;
|
|
177
|
+
propsPatterns: Array<{
|
|
178
|
+
pattern: {
|
|
179
|
+
source: string;
|
|
180
|
+
flags: string;
|
|
181
|
+
};
|
|
182
|
+
def: TSerializedAnnotatedTypeInner;
|
|
183
|
+
}>;
|
|
184
|
+
tags: string[];
|
|
185
|
+
}
|
|
186
|
+
interface TSerializedTypeArray {
|
|
187
|
+
kind: 'array';
|
|
188
|
+
of: TSerializedAnnotatedTypeInner;
|
|
189
|
+
tags: string[];
|
|
190
|
+
}
|
|
191
|
+
interface TSerializedTypeComplex {
|
|
192
|
+
kind: 'union' | 'intersection' | 'tuple';
|
|
193
|
+
items: TSerializedAnnotatedTypeInner[];
|
|
194
|
+
tags: string[];
|
|
195
|
+
}
|
|
196
|
+
interface TSerializedTypeRef {
|
|
197
|
+
kind: '$ref';
|
|
198
|
+
id: string;
|
|
199
|
+
}
|
|
200
|
+
type TSerializedTypeDef = TSerializedTypeFinal | TSerializedTypeObject | TSerializedTypeArray | TSerializedTypeComplex | TSerializedTypeRef;
|
|
201
|
+
/** Context passed to {@link TSerializeOptions.processAnnotation} for each annotation entry. */
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/types.d.ts
|
|
204
|
+
/** Options for creating a Client instance. */
|
|
205
|
+
interface ClientOptions {
|
|
206
|
+
/**
|
|
207
|
+
* Custom fetch implementation. Defaults to `globalThis.fetch`.
|
|
208
|
+
* Use this to inject auth headers, interceptors, or a custom HTTP client.
|
|
209
|
+
*/
|
|
210
|
+
fetch?: typeof globalThis.fetch;
|
|
211
|
+
/**
|
|
212
|
+
* Default headers to include with every request.
|
|
213
|
+
* Can be a static object or an async factory (e.g. for refreshing auth tokens).
|
|
214
|
+
*/
|
|
215
|
+
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
216
|
+
/**
|
|
217
|
+
* Base URL prefix. Prepended to the client path for full URL construction.
|
|
218
|
+
* @example "https://api.example.com"
|
|
219
|
+
*/
|
|
220
|
+
baseUrl?: string;
|
|
221
|
+
}
|
|
222
|
+
/** Search index metadata from the server. */
|
|
223
|
+
interface SearchIndexInfo {
|
|
224
|
+
name: string;
|
|
225
|
+
description?: string;
|
|
226
|
+
type?: "text" | "vector";
|
|
227
|
+
}
|
|
228
|
+
/** Relation summary in meta response. */
|
|
229
|
+
interface RelationInfo {
|
|
230
|
+
name: string;
|
|
231
|
+
direction: "to" | "from" | "via";
|
|
232
|
+
isArray: boolean;
|
|
233
|
+
}
|
|
234
|
+
/** Per-field capability flags. */
|
|
235
|
+
interface FieldMeta {
|
|
236
|
+
sortable: boolean;
|
|
237
|
+
filterable: boolean;
|
|
238
|
+
}
|
|
239
|
+
/** Enhanced meta response from the server. */
|
|
240
|
+
interface MetaResponse {
|
|
241
|
+
searchable: boolean;
|
|
242
|
+
vectorSearchable: boolean;
|
|
243
|
+
searchIndexes: SearchIndexInfo[];
|
|
244
|
+
primaryKeys: string[];
|
|
245
|
+
readOnly: boolean;
|
|
246
|
+
relations: RelationInfo[];
|
|
247
|
+
fields: Record<string, FieldMeta>;
|
|
248
|
+
type: TSerializedAnnotatedType;
|
|
249
|
+
}
|
|
250
|
+
interface InsertResult {
|
|
251
|
+
insertedId: unknown;
|
|
252
|
+
}
|
|
253
|
+
interface InsertManyResult {
|
|
254
|
+
insertedCount: number;
|
|
255
|
+
insertedIds: unknown[];
|
|
256
|
+
}
|
|
257
|
+
interface UpdateResult {
|
|
258
|
+
matchedCount: number;
|
|
259
|
+
modifiedCount: number;
|
|
260
|
+
}
|
|
261
|
+
interface DeleteResult {
|
|
262
|
+
deletedCount: number;
|
|
263
|
+
}
|
|
264
|
+
/** Paginated response shape (matches moost-db /pages response). */
|
|
265
|
+
interface PagesResponse<T> {
|
|
266
|
+
data: T[];
|
|
267
|
+
page: number;
|
|
268
|
+
itemsPerPage: number;
|
|
269
|
+
pages: number;
|
|
270
|
+
count: number;
|
|
271
|
+
}
|
|
272
|
+
/** Server error response shape (matches moost-db error transform). */
|
|
273
|
+
interface ServerError {
|
|
274
|
+
message: string;
|
|
275
|
+
statusCode: number;
|
|
276
|
+
errors?: Array<{
|
|
277
|
+
path: string;
|
|
278
|
+
message: string;
|
|
279
|
+
details?: unknown[];
|
|
280
|
+
}>;
|
|
281
|
+
}
|
|
282
|
+
/** Extract the data type from an Atscript annotated type `T`. */
|
|
283
|
+
type DataOf<T> = T extends {
|
|
284
|
+
type: {
|
|
285
|
+
__dataType?: infer D;
|
|
286
|
+
};
|
|
287
|
+
} ? unknown extends D ? T extends (new (...a: any[]) => infer I) ? I : Record<string, unknown> : D & Record<string, unknown> : Record<string, unknown>;
|
|
288
|
+
/** Extract own (non-nav) properties from an Atscript annotated type. */
|
|
289
|
+
type OwnOf<T> = T extends {
|
|
290
|
+
__ownProps: infer O;
|
|
291
|
+
} ? O : DataOf<T>;
|
|
292
|
+
/** Extract nav properties from an Atscript annotated type. */
|
|
293
|
+
type NavOf<T> = T extends {
|
|
294
|
+
__navProps: infer N extends Record<string, unknown>;
|
|
295
|
+
} ? N : Record<string, never>;
|
|
296
|
+
/** Extract primary key type from an Atscript annotated type. */
|
|
297
|
+
type IdOf<T> = T extends {
|
|
298
|
+
__pk: infer PK;
|
|
299
|
+
} ? PK : unknown;
|
|
300
|
+
/**
|
|
301
|
+
* Shared interface for both server-side `AtscriptDbTable` and client-side `Client`.
|
|
302
|
+
* Enables SSR isomorphism: same code runs on server (direct DB) and browser (HTTP).
|
|
303
|
+
*
|
|
304
|
+
* ```typescript
|
|
305
|
+
* const users: DbInterface<typeof User> = isServer ? serverTable : httpClient
|
|
306
|
+
* await users.findMany({ filter: { active: true } })
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
interface DbInterface<T = Record<string, unknown>> {
|
|
310
|
+
findOne(query: Uniquery<OwnOf<T>, NavOf<T>>): Promise<DataOf<T> | null>;
|
|
311
|
+
findMany(query?: Uniquery<OwnOf<T>, NavOf<T>>): Promise<DataOf<T>[]>;
|
|
312
|
+
findById(id: IdOf<T>, query?: {
|
|
313
|
+
controls?: UniqueryControls<OwnOf<T>, NavOf<T>>;
|
|
314
|
+
}): Promise<DataOf<T> | null>;
|
|
315
|
+
count(query?: Uniquery<OwnOf<T>, NavOf<T>>): Promise<number>;
|
|
316
|
+
search(text: string, query?: Uniquery<OwnOf<T>, NavOf<T>>, indexName?: string): Promise<DataOf<T>[]>;
|
|
317
|
+
aggregate(query: AggregateQuery): Promise<Record<string, unknown>[]>;
|
|
318
|
+
insertOne(data: Partial<DataOf<T>>): Promise<InsertResult>;
|
|
319
|
+
insertMany(data: Partial<DataOf<T>>[]): Promise<InsertManyResult>;
|
|
320
|
+
updateOne(data: Partial<DataOf<T>>): Promise<UpdateResult>;
|
|
321
|
+
bulkUpdate(data: Partial<DataOf<T>>[]): Promise<UpdateResult>;
|
|
322
|
+
replaceOne(data: DataOf<T>): Promise<UpdateResult>;
|
|
323
|
+
bulkReplace(data: DataOf<T>[]): Promise<UpdateResult>;
|
|
324
|
+
deleteOne(id: IdOf<T>): Promise<DeleteResult>;
|
|
325
|
+
}
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/client.d.ts
|
|
328
|
+
/**
|
|
329
|
+
* Browser-compatible HTTP client for moost-db REST endpoints.
|
|
330
|
+
*
|
|
331
|
+
* Two usage modes (same class, different generic):
|
|
332
|
+
* ```typescript
|
|
333
|
+
* // Untyped — broad Record<string, unknown> typing
|
|
334
|
+
* const users = new Client('/db/tables/users')
|
|
335
|
+
*
|
|
336
|
+
* // Type-safe — Atscript type as generic parameter
|
|
337
|
+
* const users = new Client<typeof User>('/db/tables/users')
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
declare class Client<T = Record<string, unknown>> implements DbInterface<T> {
|
|
341
|
+
private readonly _path;
|
|
342
|
+
private readonly _baseUrl;
|
|
343
|
+
private readonly _fetch;
|
|
344
|
+
private readonly _headers?;
|
|
345
|
+
private _metaPromise?;
|
|
346
|
+
constructor(path: string, opts?: ClientOptions);
|
|
347
|
+
findOne(query: Uniquery<OwnOf<T>, NavOf<T>>): Promise<DataOf<T> | null>;
|
|
348
|
+
findMany(query?: Uniquery<OwnOf<T>, NavOf<T>>): Promise<DataOf<T>[]>;
|
|
349
|
+
findById(id: IdOf<T>, query?: {
|
|
350
|
+
controls?: UniqueryControls<OwnOf<T>, NavOf<T>>;
|
|
351
|
+
}): Promise<DataOf<T> | null>;
|
|
352
|
+
count(query?: Uniquery<OwnOf<T>, NavOf<T>>): Promise<number>;
|
|
353
|
+
findManyWithCount(query: Uniquery<OwnOf<T>, NavOf<T>>): Promise<{
|
|
354
|
+
data: DataOf<T>[];
|
|
355
|
+
count: number;
|
|
356
|
+
}>;
|
|
357
|
+
pages(query?: Uniquery<OwnOf<T>, NavOf<T>>): Promise<PagesResponse<DataOf<T>>>;
|
|
358
|
+
search(text: string, query?: Uniquery<OwnOf<T>, NavOf<T>>, indexName?: string): Promise<DataOf<T>[]>;
|
|
359
|
+
aggregate(query: AggregateQuery): Promise<Record<string, unknown>[]>;
|
|
360
|
+
insertOne(data: Partial<DataOf<T>>): Promise<InsertResult>;
|
|
361
|
+
insertMany(data: Partial<DataOf<T>>[]): Promise<InsertManyResult>;
|
|
362
|
+
updateOne(data: Partial<DataOf<T>>): Promise<UpdateResult>;
|
|
363
|
+
bulkUpdate(data: Partial<DataOf<T>>[]): Promise<UpdateResult>;
|
|
364
|
+
replaceOne(data: DataOf<T>): Promise<UpdateResult>;
|
|
365
|
+
bulkReplace(data: DataOf<T>[]): Promise<UpdateResult>;
|
|
366
|
+
deleteOne(id: IdOf<T>): Promise<DeleteResult>;
|
|
367
|
+
meta(): Promise<MetaResponse>;
|
|
368
|
+
private _idToParams;
|
|
369
|
+
private _get;
|
|
370
|
+
private _resolveHeaders;
|
|
371
|
+
private _request;
|
|
372
|
+
}
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/client-error.d.ts
|
|
375
|
+
/**
|
|
376
|
+
* Error thrown by `Client` when the server responds with a non-2xx status code.
|
|
377
|
+
* Captures the HTTP status and the structured error body from moost-db.
|
|
378
|
+
*/
|
|
379
|
+
declare class ClientError extends Error {
|
|
380
|
+
/** HTTP status code (e.g. 400, 404, 409, 500). */
|
|
381
|
+
readonly status: number;
|
|
382
|
+
/** Structured error response from the server. */
|
|
383
|
+
readonly body: ServerError;
|
|
384
|
+
name: string;
|
|
385
|
+
constructor(/** HTTP status code (e.g. 400, 404, 409, 500). */
|
|
386
|
+
|
|
387
|
+
status: number, /** Structured error response from the server. */
|
|
388
|
+
|
|
389
|
+
body: ServerError);
|
|
390
|
+
/** Shortcut to structured validation/DB errors from the server. */
|
|
391
|
+
get errors(): {
|
|
392
|
+
path: string;
|
|
393
|
+
message: string;
|
|
394
|
+
details?: unknown[];
|
|
395
|
+
}[];
|
|
396
|
+
}
|
|
397
|
+
//#endregion
|
|
398
|
+
export { type AggregateQuery, Client, ClientError, type ClientOptions, type DataOf, type DbInterface, type DeleteResult, type FieldMeta, type FilterExpr, type IdOf, type InsertManyResult, type InsertResult, type MetaResponse, type NavOf, type OwnOf, type PagesResponse, type RelationInfo, type SearchIndexInfo, type ServerError, type TSerializedAnnotatedType, type TypedWithRelation, type Uniquery, type UniqueryControls, type UpdateResult };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
//#region ../../node_modules/.pnpm/@uniqu+core@0.1.2/node_modules/@uniqu/core/dist/index.d.ts
|
|
2
|
+
/** All comparison operators supported by the filter format. */
|
|
3
|
+
type ComparisonOp = '$eq' | '$ne' | '$gt' | '$gte' | '$lt' | '$lte' | '$in' | '$nin' | '$regex' | '$exists';
|
|
4
|
+
/**
|
|
5
|
+
* Per-field typed operator map. When `V` is the field's value type, operators
|
|
6
|
+
* are constrained accordingly:
|
|
7
|
+
* - `$regex` is only available when `V` extends `string`
|
|
8
|
+
* - `$gt/$gte/$lt/$lte` are only available when `V` extends `number | string | Date`
|
|
9
|
+
*/
|
|
10
|
+
type FieldOpsFor<V> = {
|
|
11
|
+
$eq?: V;
|
|
12
|
+
$ne?: V;
|
|
13
|
+
$in?: V[];
|
|
14
|
+
$nin?: V[];
|
|
15
|
+
$exists?: boolean;
|
|
16
|
+
} & (V extends string ? {
|
|
17
|
+
$regex?: RegExp | string;
|
|
18
|
+
} : {}) & (V extends number | string | Date ? {
|
|
19
|
+
$gt?: V;
|
|
20
|
+
$gte?: V;
|
|
21
|
+
$lt?: V;
|
|
22
|
+
$lte?: V;
|
|
23
|
+
} : {});
|
|
24
|
+
/** Untyped operator map. */
|
|
25
|
+
/**
|
|
26
|
+
* A filter expression is either a comparison leaf or a logical branch.
|
|
27
|
+
* `T` is the entity shape — provides type-safe field names and value types.
|
|
28
|
+
* Defaults to `Record<string, unknown>` (untyped).
|
|
29
|
+
*/
|
|
30
|
+
type FilterExpr<T = Record<string, unknown>> = ComparisonNode<T> | LogicalNode<T>;
|
|
31
|
+
/**
|
|
32
|
+
* Leaf node: one or more field comparisons.
|
|
33
|
+
* When `T` is typed, only known keys are allowed.
|
|
34
|
+
* When untyped (default), any string key is accepted.
|
|
35
|
+
*/
|
|
36
|
+
type ComparisonNode<T = Record<string, unknown>> = { [K in keyof T & string]?: T[K] | FieldOpsFor<T[K]> };
|
|
37
|
+
/**
|
|
38
|
+
* Branch node: logical combination of child expressions.
|
|
39
|
+
* Each variant forbids the other logical keys via `never` to prevent
|
|
40
|
+
* mixing comparison fields with logical operators at the type level.
|
|
41
|
+
*/
|
|
42
|
+
type LogicalNode<T = Record<string, unknown>> = {
|
|
43
|
+
$and: FilterExpr<T>[];
|
|
44
|
+
$or?: never;
|
|
45
|
+
$not?: never;
|
|
46
|
+
} | {
|
|
47
|
+
$or: FilterExpr<T>[];
|
|
48
|
+
$and?: never;
|
|
49
|
+
$not?: never;
|
|
50
|
+
} | {
|
|
51
|
+
$not: FilterExpr<T>;
|
|
52
|
+
$and?: never;
|
|
53
|
+
$or?: never;
|
|
54
|
+
};
|
|
55
|
+
/** Known aggregate function names. Consumers may support additional functions via the (string & {}) escape hatch. */
|
|
56
|
+
type AggregateFn = 'sum' | 'count' | 'avg' | 'min' | 'max';
|
|
57
|
+
/** A single aggregate function call within $select. Generic params preserve literal types for result inference. */
|
|
58
|
+
interface AggregateExpr<Fn extends string = AggregateFn | (string & {}), Field extends string = string, Alias extends string = string> {
|
|
59
|
+
/** Function name (sum, count, avg, min, max, or custom). */
|
|
60
|
+
$fn: Fn;
|
|
61
|
+
/** Field to aggregate. '*' for count(*). */
|
|
62
|
+
$field: Field;
|
|
63
|
+
/** Alias for the result. Auto-generated by URL parser if omitted. */
|
|
64
|
+
$as?: Alias;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Projection definition.
|
|
68
|
+
* - Array form: inclusion list with optional aggregates.
|
|
69
|
+
* Plain strings select fields; AggregateExpr objects define computed columns.
|
|
70
|
+
* - Object form: inclusion/exclusion map (0 or 1 per field). No aggregates in this form.
|
|
71
|
+
*/
|
|
72
|
+
type SelectExpr<T = Record<string, unknown>> = ((keyof T & string) | AggregateExpr)[] | Partial<Record<keyof T & string, 0 | 1>>;
|
|
73
|
+
/** Query controls (pagination, projection, sorting, grouping). Generic `T` constrains field names. */
|
|
74
|
+
interface UniqueryControls<T = Record<string, unknown>, Nav extends Record<string, unknown> = Record<string, unknown>> {
|
|
75
|
+
$sort?: Partial<Record<keyof T & string, 1 | -1>>;
|
|
76
|
+
$skip?: number;
|
|
77
|
+
$limit?: number;
|
|
78
|
+
$count?: boolean;
|
|
79
|
+
$select?: SelectExpr<T>;
|
|
80
|
+
/** Fields to group by for aggregate queries. */
|
|
81
|
+
$groupBy?: (keyof T & string)[];
|
|
82
|
+
/** Post-aggregation filter. Operates on aggregate aliases and dimension fields. */
|
|
83
|
+
$having?: FilterExpr;
|
|
84
|
+
/** Relations to populate alongside the query. */
|
|
85
|
+
$with?: TypedWithRelation<Nav>[];
|
|
86
|
+
/** Pass-through for unknown $-prefixed keywords. */
|
|
87
|
+
[key: `$${string}`]: unknown;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Canonical query representation.
|
|
91
|
+
* When `name` is present this is a nested relation (sub-query inside `$with`).
|
|
92
|
+
* When absent it is the root query.
|
|
93
|
+
*/
|
|
94
|
+
interface Uniquery<T = Record<string, unknown>, Nav extends Record<string, unknown> = Record<string, unknown>> {
|
|
95
|
+
/** Relation name. Present only for nested `$with` sub-queries. */
|
|
96
|
+
name?: string;
|
|
97
|
+
filter?: FilterExpr<T>;
|
|
98
|
+
controls?: UniqueryControls<T, Nav>;
|
|
99
|
+
/** Pre-computed insights. */
|
|
100
|
+
insights?: UniqueryInsights;
|
|
101
|
+
}
|
|
102
|
+
/** Unwrap array types to get the element type for nav props. */
|
|
103
|
+
type NavTarget<T> = T extends Array<infer U> ? U : T;
|
|
104
|
+
/**
|
|
105
|
+
* A typed $with relation entry.
|
|
106
|
+
* When Nav is typed (from __navProps), name is constrained to known nav prop keys.
|
|
107
|
+
* Each entry gets its own filter/controls typed to the target entity.
|
|
108
|
+
* Falls back to untyped WithRelation when Nav has no known keys.
|
|
109
|
+
*/
|
|
110
|
+
type TypedWithRelation<Nav extends Record<string, unknown>> = [keyof Nav & string] extends [never] ? WithRelation | string : { [K in keyof Nav & string]: {
|
|
111
|
+
name: K;
|
|
112
|
+
filter?: FilterExpr<NavTarget<Nav[K]> extends {
|
|
113
|
+
__ownProps: infer F;
|
|
114
|
+
} ? F : Record<string, unknown>>;
|
|
115
|
+
controls?: UniqueryControls<NavTarget<Nav[K]> extends {
|
|
116
|
+
__ownProps: infer F;
|
|
117
|
+
} ? F : Record<string, unknown>, NavTarget<Nav[K]> extends {
|
|
118
|
+
__navProps: infer N extends Record<string, unknown>;
|
|
119
|
+
} ? N : Record<string, unknown>>;
|
|
120
|
+
insights?: UniqueryInsights;
|
|
121
|
+
} }[keyof Nav & string] | (keyof Nav & string);
|
|
122
|
+
/** Untyped $with relation — used when Nav generic is not provided. */
|
|
123
|
+
type WithRelation = {
|
|
124
|
+
name: string;
|
|
125
|
+
filter?: FilterExpr;
|
|
126
|
+
controls?: UniqueryControls;
|
|
127
|
+
insights?: UniqueryInsights;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Insight operator includes comparison ops, control ops ($-prefixed),
|
|
131
|
+
* and aggregate function names (bare, e.g. 'sum', 'avg').
|
|
132
|
+
*/
|
|
133
|
+
type InsightOp = ComparisonOp | '$select' | '$order' | '$with' | '$groupBy' | '$having' | AggregateFn | (string & {});
|
|
134
|
+
/** Map of field names to the set of operators used on that field. */
|
|
135
|
+
type UniqueryInsights = Map<string, Set<InsightOp>>;
|
|
136
|
+
/** Aggregate query controls. Separate from UniqueryControls: $groupBy is required, $with is forbidden. */
|
|
137
|
+
interface AggregateControls<T = Record<string, unknown>, D extends keyof T & string = keyof T & string, M extends keyof T & string = keyof T & string> {
|
|
138
|
+
$groupBy: D[];
|
|
139
|
+
$select?: (D | AggregateExpr<AggregateFn, M | '*'>)[];
|
|
140
|
+
$having?: FilterExpr;
|
|
141
|
+
$sort?: Record<string, 1 | -1>;
|
|
142
|
+
$skip?: number;
|
|
143
|
+
$limit?: number;
|
|
144
|
+
$count?: boolean;
|
|
145
|
+
[key: `$${string}`]: unknown;
|
|
146
|
+
}
|
|
147
|
+
/** Aggregate query — no name (can't nest), no Nav (no $with). */
|
|
148
|
+
interface AggregateQuery<T = Record<string, unknown>, D extends keyof T & string = keyof T & string, M extends keyof T & string = keyof T & string> {
|
|
149
|
+
filter?: FilterExpr<T>;
|
|
150
|
+
controls: AggregateControls<T, D, M>;
|
|
151
|
+
insights?: UniqueryInsights;
|
|
152
|
+
}
|
|
153
|
+
/** Resolve the output alias of an AggregateExpr. Uses $as if provided, otherwise generates {fn}_{field}. */
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region ../../node_modules/.pnpm/@atscript+typescript@0.1.41_@atscript+core@0.1.41_@atscript+db@packages+db_@prostojs+lo_041ff52083ad05a1dfbd1c9f541c1d21/node_modules/@atscript/typescript/dist/utils.d.ts
|
|
156
|
+
/** Top-level serialized annotated type. JSON-safe representation of a {@link TAtscriptAnnotatedType}. */
|
|
157
|
+
interface TSerializedAnnotatedType extends TSerializedAnnotatedTypeInner {
|
|
158
|
+
/** Format version for forward compatibility */
|
|
159
|
+
$v: number;
|
|
160
|
+
}
|
|
161
|
+
/** Serialized annotated type node (used for nested types within the top-level). */
|
|
162
|
+
interface TSerializedAnnotatedTypeInner {
|
|
163
|
+
type: TSerializedTypeDef;
|
|
164
|
+
metadata: Record<string, unknown>;
|
|
165
|
+
optional?: boolean;
|
|
166
|
+
id?: string;
|
|
167
|
+
}
|
|
168
|
+
interface TSerializedTypeFinal {
|
|
169
|
+
kind: '';
|
|
170
|
+
designType: string;
|
|
171
|
+
value?: string | number | boolean;
|
|
172
|
+
tags: string[];
|
|
173
|
+
}
|
|
174
|
+
interface TSerializedTypeObject {
|
|
175
|
+
kind: 'object';
|
|
176
|
+
props: Record<string, TSerializedAnnotatedTypeInner>;
|
|
177
|
+
propsPatterns: Array<{
|
|
178
|
+
pattern: {
|
|
179
|
+
source: string;
|
|
180
|
+
flags: string;
|
|
181
|
+
};
|
|
182
|
+
def: TSerializedAnnotatedTypeInner;
|
|
183
|
+
}>;
|
|
184
|
+
tags: string[];
|
|
185
|
+
}
|
|
186
|
+
interface TSerializedTypeArray {
|
|
187
|
+
kind: 'array';
|
|
188
|
+
of: TSerializedAnnotatedTypeInner;
|
|
189
|
+
tags: string[];
|
|
190
|
+
}
|
|
191
|
+
interface TSerializedTypeComplex {
|
|
192
|
+
kind: 'union' | 'intersection' | 'tuple';
|
|
193
|
+
items: TSerializedAnnotatedTypeInner[];
|
|
194
|
+
tags: string[];
|
|
195
|
+
}
|
|
196
|
+
interface TSerializedTypeRef {
|
|
197
|
+
kind: '$ref';
|
|
198
|
+
id: string;
|
|
199
|
+
}
|
|
200
|
+
type TSerializedTypeDef = TSerializedTypeFinal | TSerializedTypeObject | TSerializedTypeArray | TSerializedTypeComplex | TSerializedTypeRef;
|
|
201
|
+
/** Context passed to {@link TSerializeOptions.processAnnotation} for each annotation entry. */
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/types.d.ts
|
|
204
|
+
/** Options for creating a Client instance. */
|
|
205
|
+
interface ClientOptions {
|
|
206
|
+
/**
|
|
207
|
+
* Custom fetch implementation. Defaults to `globalThis.fetch`.
|
|
208
|
+
* Use this to inject auth headers, interceptors, or a custom HTTP client.
|
|
209
|
+
*/
|
|
210
|
+
fetch?: typeof globalThis.fetch;
|
|
211
|
+
/**
|
|
212
|
+
* Default headers to include with every request.
|
|
213
|
+
* Can be a static object or an async factory (e.g. for refreshing auth tokens).
|
|
214
|
+
*/
|
|
215
|
+
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
216
|
+
/**
|
|
217
|
+
* Base URL prefix. Prepended to the client path for full URL construction.
|
|
218
|
+
* @example "https://api.example.com"
|
|
219
|
+
*/
|
|
220
|
+
baseUrl?: string;
|
|
221
|
+
}
|
|
222
|
+
/** Search index metadata from the server. */
|
|
223
|
+
interface SearchIndexInfo {
|
|
224
|
+
name: string;
|
|
225
|
+
description?: string;
|
|
226
|
+
type?: "text" | "vector";
|
|
227
|
+
}
|
|
228
|
+
/** Relation summary in meta response. */
|
|
229
|
+
interface RelationInfo {
|
|
230
|
+
name: string;
|
|
231
|
+
direction: "to" | "from" | "via";
|
|
232
|
+
isArray: boolean;
|
|
233
|
+
}
|
|
234
|
+
/** Per-field capability flags. */
|
|
235
|
+
interface FieldMeta {
|
|
236
|
+
sortable: boolean;
|
|
237
|
+
filterable: boolean;
|
|
238
|
+
}
|
|
239
|
+
/** Enhanced meta response from the server. */
|
|
240
|
+
interface MetaResponse {
|
|
241
|
+
searchable: boolean;
|
|
242
|
+
vectorSearchable: boolean;
|
|
243
|
+
searchIndexes: SearchIndexInfo[];
|
|
244
|
+
primaryKeys: string[];
|
|
245
|
+
readOnly: boolean;
|
|
246
|
+
relations: RelationInfo[];
|
|
247
|
+
fields: Record<string, FieldMeta>;
|
|
248
|
+
type: TSerializedAnnotatedType;
|
|
249
|
+
}
|
|
250
|
+
interface InsertResult {
|
|
251
|
+
insertedId: unknown;
|
|
252
|
+
}
|
|
253
|
+
interface InsertManyResult {
|
|
254
|
+
insertedCount: number;
|
|
255
|
+
insertedIds: unknown[];
|
|
256
|
+
}
|
|
257
|
+
interface UpdateResult {
|
|
258
|
+
matchedCount: number;
|
|
259
|
+
modifiedCount: number;
|
|
260
|
+
}
|
|
261
|
+
interface DeleteResult {
|
|
262
|
+
deletedCount: number;
|
|
263
|
+
}
|
|
264
|
+
/** Paginated response shape (matches moost-db /pages response). */
|
|
265
|
+
interface PagesResponse<T> {
|
|
266
|
+
data: T[];
|
|
267
|
+
page: number;
|
|
268
|
+
itemsPerPage: number;
|
|
269
|
+
pages: number;
|
|
270
|
+
count: number;
|
|
271
|
+
}
|
|
272
|
+
/** Server error response shape (matches moost-db error transform). */
|
|
273
|
+
interface ServerError {
|
|
274
|
+
message: string;
|
|
275
|
+
statusCode: number;
|
|
276
|
+
errors?: Array<{
|
|
277
|
+
path: string;
|
|
278
|
+
message: string;
|
|
279
|
+
details?: unknown[];
|
|
280
|
+
}>;
|
|
281
|
+
}
|
|
282
|
+
/** Extract the data type from an Atscript annotated type `T`. */
|
|
283
|
+
type DataOf<T> = T extends {
|
|
284
|
+
type: {
|
|
285
|
+
__dataType?: infer D;
|
|
286
|
+
};
|
|
287
|
+
} ? unknown extends D ? T extends (new (...a: any[]) => infer I) ? I : Record<string, unknown> : D & Record<string, unknown> : Record<string, unknown>;
|
|
288
|
+
/** Extract own (non-nav) properties from an Atscript annotated type. */
|
|
289
|
+
type OwnOf<T> = T extends {
|
|
290
|
+
__ownProps: infer O;
|
|
291
|
+
} ? O : DataOf<T>;
|
|
292
|
+
/** Extract nav properties from an Atscript annotated type. */
|
|
293
|
+
type NavOf<T> = T extends {
|
|
294
|
+
__navProps: infer N extends Record<string, unknown>;
|
|
295
|
+
} ? N : Record<string, never>;
|
|
296
|
+
/** Extract primary key type from an Atscript annotated type. */
|
|
297
|
+
type IdOf<T> = T extends {
|
|
298
|
+
__pk: infer PK;
|
|
299
|
+
} ? PK : unknown;
|
|
300
|
+
/**
|
|
301
|
+
* Shared interface for both server-side `AtscriptDbTable` and client-side `Client`.
|
|
302
|
+
* Enables SSR isomorphism: same code runs on server (direct DB) and browser (HTTP).
|
|
303
|
+
*
|
|
304
|
+
* ```typescript
|
|
305
|
+
* const users: DbInterface<typeof User> = isServer ? serverTable : httpClient
|
|
306
|
+
* await users.findMany({ filter: { active: true } })
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
interface DbInterface<T = Record<string, unknown>> {
|
|
310
|
+
findOne(query: Uniquery<OwnOf<T>, NavOf<T>>): Promise<DataOf<T> | null>;
|
|
311
|
+
findMany(query?: Uniquery<OwnOf<T>, NavOf<T>>): Promise<DataOf<T>[]>;
|
|
312
|
+
findById(id: IdOf<T>, query?: {
|
|
313
|
+
controls?: UniqueryControls<OwnOf<T>, NavOf<T>>;
|
|
314
|
+
}): Promise<DataOf<T> | null>;
|
|
315
|
+
count(query?: Uniquery<OwnOf<T>, NavOf<T>>): Promise<number>;
|
|
316
|
+
search(text: string, query?: Uniquery<OwnOf<T>, NavOf<T>>, indexName?: string): Promise<DataOf<T>[]>;
|
|
317
|
+
aggregate(query: AggregateQuery): Promise<Record<string, unknown>[]>;
|
|
318
|
+
insertOne(data: Partial<DataOf<T>>): Promise<InsertResult>;
|
|
319
|
+
insertMany(data: Partial<DataOf<T>>[]): Promise<InsertManyResult>;
|
|
320
|
+
updateOne(data: Partial<DataOf<T>>): Promise<UpdateResult>;
|
|
321
|
+
bulkUpdate(data: Partial<DataOf<T>>[]): Promise<UpdateResult>;
|
|
322
|
+
replaceOne(data: DataOf<T>): Promise<UpdateResult>;
|
|
323
|
+
bulkReplace(data: DataOf<T>[]): Promise<UpdateResult>;
|
|
324
|
+
deleteOne(id: IdOf<T>): Promise<DeleteResult>;
|
|
325
|
+
}
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/client.d.ts
|
|
328
|
+
/**
|
|
329
|
+
* Browser-compatible HTTP client for moost-db REST endpoints.
|
|
330
|
+
*
|
|
331
|
+
* Two usage modes (same class, different generic):
|
|
332
|
+
* ```typescript
|
|
333
|
+
* // Untyped — broad Record<string, unknown> typing
|
|
334
|
+
* const users = new Client('/db/tables/users')
|
|
335
|
+
*
|
|
336
|
+
* // Type-safe — Atscript type as generic parameter
|
|
337
|
+
* const users = new Client<typeof User>('/db/tables/users')
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
declare class Client<T = Record<string, unknown>> implements DbInterface<T> {
|
|
341
|
+
private readonly _path;
|
|
342
|
+
private readonly _baseUrl;
|
|
343
|
+
private readonly _fetch;
|
|
344
|
+
private readonly _headers?;
|
|
345
|
+
private _metaPromise?;
|
|
346
|
+
constructor(path: string, opts?: ClientOptions);
|
|
347
|
+
findOne(query: Uniquery<OwnOf<T>, NavOf<T>>): Promise<DataOf<T> | null>;
|
|
348
|
+
findMany(query?: Uniquery<OwnOf<T>, NavOf<T>>): Promise<DataOf<T>[]>;
|
|
349
|
+
findById(id: IdOf<T>, query?: {
|
|
350
|
+
controls?: UniqueryControls<OwnOf<T>, NavOf<T>>;
|
|
351
|
+
}): Promise<DataOf<T> | null>;
|
|
352
|
+
count(query?: Uniquery<OwnOf<T>, NavOf<T>>): Promise<number>;
|
|
353
|
+
findManyWithCount(query: Uniquery<OwnOf<T>, NavOf<T>>): Promise<{
|
|
354
|
+
data: DataOf<T>[];
|
|
355
|
+
count: number;
|
|
356
|
+
}>;
|
|
357
|
+
pages(query?: Uniquery<OwnOf<T>, NavOf<T>>): Promise<PagesResponse<DataOf<T>>>;
|
|
358
|
+
search(text: string, query?: Uniquery<OwnOf<T>, NavOf<T>>, indexName?: string): Promise<DataOf<T>[]>;
|
|
359
|
+
aggregate(query: AggregateQuery): Promise<Record<string, unknown>[]>;
|
|
360
|
+
insertOne(data: Partial<DataOf<T>>): Promise<InsertResult>;
|
|
361
|
+
insertMany(data: Partial<DataOf<T>>[]): Promise<InsertManyResult>;
|
|
362
|
+
updateOne(data: Partial<DataOf<T>>): Promise<UpdateResult>;
|
|
363
|
+
bulkUpdate(data: Partial<DataOf<T>>[]): Promise<UpdateResult>;
|
|
364
|
+
replaceOne(data: DataOf<T>): Promise<UpdateResult>;
|
|
365
|
+
bulkReplace(data: DataOf<T>[]): Promise<UpdateResult>;
|
|
366
|
+
deleteOne(id: IdOf<T>): Promise<DeleteResult>;
|
|
367
|
+
meta(): Promise<MetaResponse>;
|
|
368
|
+
private _idToParams;
|
|
369
|
+
private _get;
|
|
370
|
+
private _resolveHeaders;
|
|
371
|
+
private _request;
|
|
372
|
+
}
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/client-error.d.ts
|
|
375
|
+
/**
|
|
376
|
+
* Error thrown by `Client` when the server responds with a non-2xx status code.
|
|
377
|
+
* Captures the HTTP status and the structured error body from moost-db.
|
|
378
|
+
*/
|
|
379
|
+
declare class ClientError extends Error {
|
|
380
|
+
/** HTTP status code (e.g. 400, 404, 409, 500). */
|
|
381
|
+
readonly status: number;
|
|
382
|
+
/** Structured error response from the server. */
|
|
383
|
+
readonly body: ServerError;
|
|
384
|
+
name: string;
|
|
385
|
+
constructor(/** HTTP status code (e.g. 400, 404, 409, 500). */
|
|
386
|
+
|
|
387
|
+
status: number, /** Structured error response from the server. */
|
|
388
|
+
|
|
389
|
+
body: ServerError);
|
|
390
|
+
/** Shortcut to structured validation/DB errors from the server. */
|
|
391
|
+
get errors(): {
|
|
392
|
+
path: string;
|
|
393
|
+
message: string;
|
|
394
|
+
details?: unknown[];
|
|
395
|
+
}[];
|
|
396
|
+
}
|
|
397
|
+
//#endregion
|
|
398
|
+
export { type AggregateQuery, Client, ClientError, type ClientOptions, type DataOf, type DbInterface, type DeleteResult, type FieldMeta, type FilterExpr, type IdOf, type InsertManyResult, type InsertResult, type MetaResponse, type NavOf, type OwnOf, type PagesResponse, type RelationInfo, type SearchIndexInfo, type ServerError, type TSerializedAnnotatedType, type TypedWithRelation, type Uniquery, type UniqueryControls, type UpdateResult };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { buildUrl } from "@uniqu/url/builder";
|
|
2
|
+
//#region src/client-error.ts
|
|
3
|
+
/**
|
|
4
|
+
* Error thrown by `Client` when the server responds with a non-2xx status code.
|
|
5
|
+
* Captures the HTTP status and the structured error body from moost-db.
|
|
6
|
+
*/
|
|
7
|
+
var ClientError = class extends Error {
|
|
8
|
+
name = "ClientError";
|
|
9
|
+
constructor(status, body) {
|
|
10
|
+
super(body.message || `HTTP ${status}`);
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.body = body;
|
|
13
|
+
}
|
|
14
|
+
/** Shortcut to structured validation/DB errors from the server. */
|
|
15
|
+
get errors() {
|
|
16
|
+
return this.body.errors ?? [];
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/client.ts
|
|
21
|
+
/**
|
|
22
|
+
* Browser-compatible HTTP client for moost-db REST endpoints.
|
|
23
|
+
*
|
|
24
|
+
* Two usage modes (same class, different generic):
|
|
25
|
+
* ```typescript
|
|
26
|
+
* // Untyped — broad Record<string, unknown> typing
|
|
27
|
+
* const users = new Client('/db/tables/users')
|
|
28
|
+
*
|
|
29
|
+
* // Type-safe — Atscript type as generic parameter
|
|
30
|
+
* const users = new Client<typeof User>('/db/tables/users')
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
var Client = class {
|
|
34
|
+
_path;
|
|
35
|
+
_baseUrl;
|
|
36
|
+
_fetch;
|
|
37
|
+
_headers;
|
|
38
|
+
_metaPromise;
|
|
39
|
+
constructor(path, opts) {
|
|
40
|
+
this._path = path.endsWith("/") ? path.slice(0, -1) : path;
|
|
41
|
+
this._baseUrl = opts?.baseUrl ?? "";
|
|
42
|
+
this._fetch = opts?.fetch ?? globalThis.fetch.bind(globalThis);
|
|
43
|
+
this._headers = opts?.headers;
|
|
44
|
+
}
|
|
45
|
+
async findOne(query) {
|
|
46
|
+
const controls = {
|
|
47
|
+
...query?.controls,
|
|
48
|
+
$limit: 1
|
|
49
|
+
};
|
|
50
|
+
return (await this._get("query", {
|
|
51
|
+
...query,
|
|
52
|
+
controls
|
|
53
|
+
}))[0] ?? null;
|
|
54
|
+
}
|
|
55
|
+
async findMany(query) {
|
|
56
|
+
return this._get("query", query);
|
|
57
|
+
}
|
|
58
|
+
async findById(id, query) {
|
|
59
|
+
if (id !== null && typeof id === "object") {
|
|
60
|
+
const params = this._idToParams(id);
|
|
61
|
+
if (query?.controls) {
|
|
62
|
+
const controlStr = buildUrl({ controls: query.controls });
|
|
63
|
+
if (controlStr) for (const [k, v] of new URLSearchParams(controlStr)) params.set(k, v);
|
|
64
|
+
}
|
|
65
|
+
const qs = params.toString();
|
|
66
|
+
try {
|
|
67
|
+
return await this._request("GET", `one${qs ? `?${qs}` : ""}`);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
if (e instanceof ClientError && e.status === 404) return null;
|
|
70
|
+
throw e;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const controlStr = query?.controls ? buildUrl({ controls: query.controls }) : "";
|
|
74
|
+
try {
|
|
75
|
+
return await this._request("GET", `one/${encodeURIComponent(String(id))}${controlStr ? `?${controlStr}` : ""}`);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
if (e instanceof ClientError && e.status === 404) return null;
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async count(query) {
|
|
82
|
+
const controls = {
|
|
83
|
+
...query?.controls,
|
|
84
|
+
$count: true
|
|
85
|
+
};
|
|
86
|
+
return this._get("query", {
|
|
87
|
+
...query,
|
|
88
|
+
controls
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async findManyWithCount(query) {
|
|
92
|
+
const controls = query?.controls ?? {};
|
|
93
|
+
const limit = controls.$limit || 1e3;
|
|
94
|
+
const skip = controls.$skip || 0;
|
|
95
|
+
const page = Math.floor(skip / limit) + 1;
|
|
96
|
+
const result = await this._get("pages", {
|
|
97
|
+
...query,
|
|
98
|
+
controls: {
|
|
99
|
+
...controls,
|
|
100
|
+
$page: page,
|
|
101
|
+
$size: limit
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
data: result.data,
|
|
106
|
+
count: result.count
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
async pages(query) {
|
|
110
|
+
return this._get("pages", query);
|
|
111
|
+
}
|
|
112
|
+
async search(text, query, indexName) {
|
|
113
|
+
const controls = {
|
|
114
|
+
...query?.controls,
|
|
115
|
+
$search: text
|
|
116
|
+
};
|
|
117
|
+
if (indexName) controls.$index = indexName;
|
|
118
|
+
return this._get("query", {
|
|
119
|
+
...query,
|
|
120
|
+
controls
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async aggregate(query) {
|
|
124
|
+
return this._get("query", query);
|
|
125
|
+
}
|
|
126
|
+
async insertOne(data) {
|
|
127
|
+
return this._request("POST", "", data);
|
|
128
|
+
}
|
|
129
|
+
async insertMany(data) {
|
|
130
|
+
return this._request("POST", "", data);
|
|
131
|
+
}
|
|
132
|
+
async updateOne(data) {
|
|
133
|
+
return this._request("PATCH", "", data);
|
|
134
|
+
}
|
|
135
|
+
async bulkUpdate(data) {
|
|
136
|
+
return this._request("PATCH", "", data);
|
|
137
|
+
}
|
|
138
|
+
async replaceOne(data) {
|
|
139
|
+
return this._request("PUT", "", data);
|
|
140
|
+
}
|
|
141
|
+
async bulkReplace(data) {
|
|
142
|
+
return this._request("PUT", "", data);
|
|
143
|
+
}
|
|
144
|
+
async deleteOne(id) {
|
|
145
|
+
if (id !== null && typeof id === "object") return this._request("DELETE", `?${this._idToParams(id).toString()}`);
|
|
146
|
+
return this._request("DELETE", encodeURIComponent(String(id)));
|
|
147
|
+
}
|
|
148
|
+
async meta() {
|
|
149
|
+
if (!this._metaPromise) this._metaPromise = this._request("GET", "meta");
|
|
150
|
+
return this._metaPromise;
|
|
151
|
+
}
|
|
152
|
+
_idToParams(id) {
|
|
153
|
+
const params = new URLSearchParams();
|
|
154
|
+
for (const [k, v] of Object.entries(id)) params.set(k, String(v));
|
|
155
|
+
return params;
|
|
156
|
+
}
|
|
157
|
+
async _get(endpoint, query) {
|
|
158
|
+
const qs = query ? buildUrl(query) : "";
|
|
159
|
+
return this._request("GET", `${endpoint}${qs ? `?${qs}` : ""}`);
|
|
160
|
+
}
|
|
161
|
+
async _resolveHeaders() {
|
|
162
|
+
if (!this._headers) return {};
|
|
163
|
+
if (typeof this._headers === "function") return await this._headers();
|
|
164
|
+
return this._headers;
|
|
165
|
+
}
|
|
166
|
+
async _request(method, endpoint, body) {
|
|
167
|
+
const sep = endpoint && !endpoint.startsWith("?") ? "/" : "";
|
|
168
|
+
const url = `${this._baseUrl}${this._path}${sep}${endpoint}`;
|
|
169
|
+
const headers = { ...await this._resolveHeaders() };
|
|
170
|
+
const init = {
|
|
171
|
+
method,
|
|
172
|
+
headers
|
|
173
|
+
};
|
|
174
|
+
if (body !== void 0) {
|
|
175
|
+
headers["Content-Type"] = "application/json";
|
|
176
|
+
init.body = JSON.stringify(body);
|
|
177
|
+
}
|
|
178
|
+
const res = await this._fetch(url, init);
|
|
179
|
+
if (!res.ok) {
|
|
180
|
+
let errorBody;
|
|
181
|
+
try {
|
|
182
|
+
errorBody = await res.json();
|
|
183
|
+
} catch {
|
|
184
|
+
errorBody = {
|
|
185
|
+
message: res.statusText,
|
|
186
|
+
statusCode: res.status
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
throw new ClientError(res.status, errorBody);
|
|
190
|
+
}
|
|
191
|
+
return res.json();
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
//#endregion
|
|
195
|
+
export { Client, ClientError };
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atscript/db-client",
|
|
3
|
+
"version": "0.1.43",
|
|
4
|
+
"description": "Browser-compatible HTTP client for @atscript/moost-db REST endpoints.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"atscript",
|
|
7
|
+
"client",
|
|
8
|
+
"database",
|
|
9
|
+
"http",
|
|
10
|
+
"rest"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/moostjs/atscript-db/tree/main/packages/db-client#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/moostjs/atscript-db/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "Artem Maltsev",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/moostjs/atscript-db.git",
|
|
21
|
+
"directory": "packages/db-client"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "dist/index.mjs",
|
|
28
|
+
"types": "dist/index.d.mts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.mts",
|
|
32
|
+
"import": "./dist/index.mjs",
|
|
33
|
+
"require": "./dist/index.cjs"
|
|
34
|
+
},
|
|
35
|
+
"./package.json": "./package.json"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@uniqu/url": "^0.1.2"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@atscript/typescript": "^0.1.41",
|
|
45
|
+
"@uniqu/core": "^0.1.2"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "vp pack",
|
|
49
|
+
"dev": "vp pack --watch",
|
|
50
|
+
"test": "vp test",
|
|
51
|
+
"check": "vp check"
|
|
52
|
+
}
|
|
53
|
+
}
|