@atscript/db-client 0.1.43 → 0.1.44
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 +29 -15
- package/dist/index.cjs +100 -88
- package/dist/index.d.cts +88 -355
- package/dist/index.d.mts +88 -355
- package/dist/index.mjs +100 -88
- package/dist/validator-Baaf7PPf.d.cts +137 -0
- package/dist/validator-D0lqruKm.d.mts +137 -0
- package/dist/validator.cjs +82 -0
- package/dist/validator.d.cts +2 -0
- package/dist/validator.d.mts +2 -0
- package/dist/validator.mjs +79 -0
- package/package.json +16 -3
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h1 align="center">@atscript/db-client</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
|
|
8
|
+
HTTP client for <code>@atscript/moost-db</code> REST endpoints.
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
Type-safe HTTP client that mirrors
|
|
17
|
+
Type-safe HTTP client that mirrors moost-db controller endpoints. Works in browsers, Node.js, and any runtime with `fetch`. Each method maps 1:1 to a controller endpoint — filters, sorting, pagination, relation loading, text search, and aggregation are all supported through typed query controls.
|
|
18
|
+
|
|
19
|
+
In SSR environments, Moost's `fetch` automatically routes local requests to handlers in-process, so the same `Client` instance works on both server and browser.
|
|
18
20
|
|
|
19
21
|
## Installation
|
|
20
22
|
|
|
@@ -32,27 +34,39 @@ const users = new Client<typeof User>("/api/users", {
|
|
|
32
34
|
baseUrl: "https://api.example.com",
|
|
33
35
|
});
|
|
34
36
|
|
|
35
|
-
// Query
|
|
36
|
-
const active = await users.
|
|
37
|
-
|
|
37
|
+
// Query → GET /query
|
|
38
|
+
const active = await users.query({ filter: { status: "active" } });
|
|
39
|
+
|
|
40
|
+
// Get one → GET /one/:id
|
|
41
|
+
const user = await users.one("abc-123");
|
|
42
|
+
|
|
43
|
+
// Insert → POST /
|
|
44
|
+
const { insertedId } = await users.insert({ name: "Alice" });
|
|
45
|
+
|
|
46
|
+
// Update → PATCH /
|
|
47
|
+
await users.update({ id: insertedId, role: "admin" });
|
|
48
|
+
|
|
49
|
+
// Remove → DELETE /:id
|
|
50
|
+
await users.remove(insertedId);
|
|
51
|
+
|
|
52
|
+
// Count → GET /query ($count)
|
|
53
|
+
const total = await users.count();
|
|
38
54
|
|
|
39
|
-
//
|
|
40
|
-
const
|
|
41
|
-
await users.updateOne({ id: insertedId, role: "admin" });
|
|
42
|
-
await users.deleteOne(insertedId);
|
|
55
|
+
// Paginate → GET /pages
|
|
56
|
+
const page = await users.pages({ filter: { active: true } }, 1, 20);
|
|
43
57
|
|
|
44
|
-
// Metadata
|
|
58
|
+
// Metadata → GET /meta
|
|
45
59
|
const meta = await users.meta();
|
|
46
60
|
```
|
|
47
61
|
|
|
48
62
|
## Features
|
|
49
63
|
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
52
|
-
- **
|
|
53
|
-
- **
|
|
64
|
+
- **Typed queries** — filter keys, sort fields, `$with` relation names, and primary keys are type-checked against the Atscript model
|
|
65
|
+
- **Full CRUD** — `query`, `count`, `pages`, `one`, `insert`, `update`, `replace`, `remove`
|
|
66
|
+
- **Aggregation** — typed `$groupBy` dimensions and measures with inferred result types
|
|
67
|
+
- **Search** — full-text and vector search via query controls
|
|
68
|
+
- **Client-side validation** — validates writes against the Atscript schema before sending
|
|
54
69
|
- **Error handling** — `ClientError` with structured validation errors
|
|
55
|
-
- **SSR isomorphism** — `DbInterface<T>` shared between server `AtscriptDbTable` and client `Client`
|
|
56
70
|
- **Configurable** — custom `fetch`, static or async headers, base URL
|
|
57
71
|
|
|
58
72
|
## Documentation
|
package/dist/index.cjs
CHANGED
|
@@ -20,15 +20,24 @@ var ClientError = class extends Error {
|
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/client.ts
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* HTTP client for moost-db REST endpoints.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* Each method maps 1:1 to a controller endpoint:
|
|
26
|
+
* - `query()` → `GET /query`
|
|
27
|
+
* - `count()` → `GET /query` with `$count`
|
|
28
|
+
* - `aggregate()` → `GET /query` with `$groupBy`
|
|
29
|
+
* - `pages()` → `GET /pages`
|
|
30
|
+
* - `one()` → `GET /one/:id` or `GET /one?compositeKeys`
|
|
31
|
+
* - `insert()` → `POST /`
|
|
32
|
+
* - `update()` → `PATCH /`
|
|
33
|
+
* - `replace()` → `PUT /`
|
|
34
|
+
* - `remove()` → `DELETE /:id` or `DELETE /?compositeKeys`
|
|
35
|
+
* - `meta()` → `GET /meta`
|
|
29
36
|
*
|
|
30
|
-
*
|
|
31
|
-
* const users = new Client<typeof User>('/
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const users = new Client<typeof User>('/api/users')
|
|
39
|
+
* const all = await users.query()
|
|
40
|
+
* const page = await users.pages({ filter: { active: true } }, 1, 20)
|
|
32
41
|
* ```
|
|
33
42
|
*/
|
|
34
43
|
var Client = class {
|
|
@@ -37,124 +46,127 @@ var Client = class {
|
|
|
37
46
|
_fetch;
|
|
38
47
|
_headers;
|
|
39
48
|
_metaPromise;
|
|
49
|
+
_validatorPromise;
|
|
40
50
|
constructor(path, opts) {
|
|
41
51
|
this._path = path.endsWith("/") ? path.slice(0, -1) : path;
|
|
42
52
|
this._baseUrl = opts?.baseUrl ?? "";
|
|
43
53
|
this._fetch = opts?.fetch ?? globalThis.fetch.bind(globalThis);
|
|
44
54
|
this._headers = opts?.headers;
|
|
45
55
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
};
|
|
51
|
-
return (await this._get("query", {
|
|
52
|
-
...query,
|
|
53
|
-
controls
|
|
54
|
-
}))[0] ?? null;
|
|
55
|
-
}
|
|
56
|
-
async findMany(query) {
|
|
56
|
+
/**
|
|
57
|
+
* `GET /query` — query records with typed filter, sort, select, and relations.
|
|
58
|
+
*/
|
|
59
|
+
async query(query) {
|
|
57
60
|
return this._get("query", query);
|
|
58
61
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
}
|
|
62
|
+
/**
|
|
63
|
+
* `GET /query` with `$count: true` — returns record count.
|
|
64
|
+
*/
|
|
82
65
|
async count(query) {
|
|
83
|
-
const controls = {
|
|
84
|
-
...query?.controls,
|
|
85
|
-
$count: true
|
|
86
|
-
};
|
|
87
66
|
return this._get("query", {
|
|
88
67
|
...query,
|
|
89
|
-
controls
|
|
68
|
+
controls: { $count: true }
|
|
90
69
|
});
|
|
91
70
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
71
|
+
/**
|
|
72
|
+
* `GET /query` with `$groupBy` — aggregate query with typed dimension/measure fields.
|
|
73
|
+
*/
|
|
74
|
+
async aggregate(query) {
|
|
75
|
+
return this._get("query", query);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* `GET /pages` — paginated query with typed filter and relations.
|
|
79
|
+
*/
|
|
80
|
+
async pages(query, page = 1, size = 10) {
|
|
81
|
+
return this._get("pages", {
|
|
98
82
|
...query,
|
|
99
83
|
controls: {
|
|
100
|
-
...controls,
|
|
84
|
+
...query?.controls,
|
|
101
85
|
$page: page,
|
|
102
|
-
$size:
|
|
86
|
+
$size: size
|
|
103
87
|
}
|
|
104
88
|
});
|
|
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
89
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
90
|
+
/**
|
|
91
|
+
* `GET /one/:id` or `GET /one?k1=v1&k2=v2` — single record by primary key.
|
|
92
|
+
*
|
|
93
|
+
* Returns `null` on 404.
|
|
94
|
+
*/
|
|
95
|
+
async one(id, query) {
|
|
96
|
+
const controlStr = query?.controls ? (0, _uniqu_url_builder.buildUrl)({ controls: query.controls }) : "";
|
|
97
|
+
if (id !== null && typeof id === "object") {
|
|
98
|
+
const params = this._idToParams(id);
|
|
99
|
+
if (controlStr) for (const [k, v] of new URLSearchParams(controlStr)) params.set(k, v);
|
|
100
|
+
const qs = params.toString();
|
|
101
|
+
return this._getOrNull(`one${qs ? `?${qs}` : ""}`);
|
|
102
|
+
}
|
|
103
|
+
return this._getOrNull(`one/${encodeURIComponent(String(id))}${controlStr ? `?${controlStr}` : ""}`);
|
|
129
104
|
}
|
|
130
|
-
async
|
|
105
|
+
async insert(data) {
|
|
106
|
+
await this._validateData(data, "insert");
|
|
131
107
|
return this._request("POST", "", data);
|
|
132
108
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
async
|
|
109
|
+
/**
|
|
110
|
+
* `PATCH /` — partial update one or many records by primary key.
|
|
111
|
+
*/
|
|
112
|
+
async update(data) {
|
|
113
|
+
await this._validateData(data, "patch");
|
|
137
114
|
return this._request("PATCH", "", data);
|
|
138
115
|
}
|
|
139
|
-
|
|
116
|
+
/**
|
|
117
|
+
* `PUT /` — full replace one or many records by primary key.
|
|
118
|
+
*/
|
|
119
|
+
async replace(data) {
|
|
120
|
+
await this._validateData(data, "replace");
|
|
140
121
|
return this._request("PUT", "", data);
|
|
141
122
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
async
|
|
123
|
+
/**
|
|
124
|
+
* `DELETE /:id` or `DELETE /?k1=v1&k2=v2` — remove a record by primary key.
|
|
125
|
+
*/
|
|
126
|
+
async remove(id) {
|
|
146
127
|
if (id !== null && typeof id === "object") return this._request("DELETE", `?${this._idToParams(id).toString()}`);
|
|
147
128
|
return this._request("DELETE", encodeURIComponent(String(id)));
|
|
148
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* `GET /meta` — table/view metadata (cached after first call).
|
|
132
|
+
*/
|
|
149
133
|
async meta() {
|
|
150
|
-
if (!this._metaPromise) this._metaPromise = this._request("GET", "meta")
|
|
134
|
+
if (!this._metaPromise) this._metaPromise = this._request("GET", "meta").catch((err) => {
|
|
135
|
+
this._metaPromise = void 0;
|
|
136
|
+
throw err;
|
|
137
|
+
});
|
|
151
138
|
return this._metaPromise;
|
|
152
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Returns a lazily-initialized {@link ClientValidator} backed by the `/meta` type.
|
|
142
|
+
* Useful for accessing `flatMap` and `navFields` (e.g. for form generation).
|
|
143
|
+
*/
|
|
144
|
+
getValidator() {
|
|
145
|
+
return this._getValidator();
|
|
146
|
+
}
|
|
147
|
+
async _validateData(data, mode) {
|
|
148
|
+
(await this._getValidator()).validate(data, mode);
|
|
149
|
+
}
|
|
150
|
+
_getValidator() {
|
|
151
|
+
if (!this._validatorPromise) this._validatorPromise = Promise.all([this.meta(), Promise.resolve().then(() => require("./validator.cjs"))]).then(([m, { createClientValidator }]) => createClientValidator(m)).catch((err) => {
|
|
152
|
+
this._validatorPromise = void 0;
|
|
153
|
+
throw err;
|
|
154
|
+
});
|
|
155
|
+
return this._validatorPromise;
|
|
156
|
+
}
|
|
153
157
|
_idToParams(id) {
|
|
154
158
|
const params = new URLSearchParams();
|
|
155
159
|
for (const [k, v] of Object.entries(id)) params.set(k, String(v));
|
|
156
160
|
return params;
|
|
157
161
|
}
|
|
162
|
+
async _getOrNull(endpoint) {
|
|
163
|
+
try {
|
|
164
|
+
return await this._request("GET", endpoint);
|
|
165
|
+
} catch (e) {
|
|
166
|
+
if (e instanceof ClientError && e.status === 404) return null;
|
|
167
|
+
throw e;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
158
170
|
async _get(endpoint, query) {
|
|
159
171
|
const qs = query ? (0, _uniqu_url_builder.buildUrl)(query) : "";
|
|
160
172
|
return this._request("GET", `${endpoint}${qs ? `?${qs}` : ""}`);
|