@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 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;
@@ -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 };
@@ -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
+ }