@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/dist/index.mjs
CHANGED
|
@@ -19,15 +19,24 @@ var ClientError = class extends Error {
|
|
|
19
19
|
//#endregion
|
|
20
20
|
//#region src/client.ts
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* HTTP client for moost-db REST endpoints.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* Each method maps 1:1 to a controller endpoint:
|
|
25
|
+
* - `query()` → `GET /query`
|
|
26
|
+
* - `count()` → `GET /query` with `$count`
|
|
27
|
+
* - `aggregate()` → `GET /query` with `$groupBy`
|
|
28
|
+
* - `pages()` → `GET /pages`
|
|
29
|
+
* - `one()` → `GET /one/:id` or `GET /one?compositeKeys`
|
|
30
|
+
* - `insert()` → `POST /`
|
|
31
|
+
* - `update()` → `PATCH /`
|
|
32
|
+
* - `replace()` → `PUT /`
|
|
33
|
+
* - `remove()` → `DELETE /:id` or `DELETE /?compositeKeys`
|
|
34
|
+
* - `meta()` → `GET /meta`
|
|
28
35
|
*
|
|
29
|
-
*
|
|
30
|
-
* const users = new Client<typeof User>('/
|
|
36
|
+
* ```typescript
|
|
37
|
+
* const users = new Client<typeof User>('/api/users')
|
|
38
|
+
* const all = await users.query()
|
|
39
|
+
* const page = await users.pages({ filter: { active: true } }, 1, 20)
|
|
31
40
|
* ```
|
|
32
41
|
*/
|
|
33
42
|
var Client = class {
|
|
@@ -36,124 +45,127 @@ var Client = class {
|
|
|
36
45
|
_fetch;
|
|
37
46
|
_headers;
|
|
38
47
|
_metaPromise;
|
|
48
|
+
_validatorPromise;
|
|
39
49
|
constructor(path, opts) {
|
|
40
50
|
this._path = path.endsWith("/") ? path.slice(0, -1) : path;
|
|
41
51
|
this._baseUrl = opts?.baseUrl ?? "";
|
|
42
52
|
this._fetch = opts?.fetch ?? globalThis.fetch.bind(globalThis);
|
|
43
53
|
this._headers = opts?.headers;
|
|
44
54
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
};
|
|
50
|
-
return (await this._get("query", {
|
|
51
|
-
...query,
|
|
52
|
-
controls
|
|
53
|
-
}))[0] ?? null;
|
|
54
|
-
}
|
|
55
|
-
async findMany(query) {
|
|
55
|
+
/**
|
|
56
|
+
* `GET /query` — query records with typed filter, sort, select, and relations.
|
|
57
|
+
*/
|
|
58
|
+
async query(query) {
|
|
56
59
|
return this._get("query", query);
|
|
57
60
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
}
|
|
61
|
+
/**
|
|
62
|
+
* `GET /query` with `$count: true` — returns record count.
|
|
63
|
+
*/
|
|
81
64
|
async count(query) {
|
|
82
|
-
const controls = {
|
|
83
|
-
...query?.controls,
|
|
84
|
-
$count: true
|
|
85
|
-
};
|
|
86
65
|
return this._get("query", {
|
|
87
66
|
...query,
|
|
88
|
-
controls
|
|
67
|
+
controls: { $count: true }
|
|
89
68
|
});
|
|
90
69
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
70
|
+
/**
|
|
71
|
+
* `GET /query` with `$groupBy` — aggregate query with typed dimension/measure fields.
|
|
72
|
+
*/
|
|
73
|
+
async aggregate(query) {
|
|
74
|
+
return this._get("query", query);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* `GET /pages` — paginated query with typed filter and relations.
|
|
78
|
+
*/
|
|
79
|
+
async pages(query, page = 1, size = 10) {
|
|
80
|
+
return this._get("pages", {
|
|
97
81
|
...query,
|
|
98
82
|
controls: {
|
|
99
|
-
...controls,
|
|
83
|
+
...query?.controls,
|
|
100
84
|
$page: page,
|
|
101
|
-
$size:
|
|
85
|
+
$size: size
|
|
102
86
|
}
|
|
103
87
|
});
|
|
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
88
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
89
|
+
/**
|
|
90
|
+
* `GET /one/:id` or `GET /one?k1=v1&k2=v2` — single record by primary key.
|
|
91
|
+
*
|
|
92
|
+
* Returns `null` on 404.
|
|
93
|
+
*/
|
|
94
|
+
async one(id, query) {
|
|
95
|
+
const controlStr = query?.controls ? buildUrl({ controls: query.controls }) : "";
|
|
96
|
+
if (id !== null && typeof id === "object") {
|
|
97
|
+
const params = this._idToParams(id);
|
|
98
|
+
if (controlStr) for (const [k, v] of new URLSearchParams(controlStr)) params.set(k, v);
|
|
99
|
+
const qs = params.toString();
|
|
100
|
+
return this._getOrNull(`one${qs ? `?${qs}` : ""}`);
|
|
101
|
+
}
|
|
102
|
+
return this._getOrNull(`one/${encodeURIComponent(String(id))}${controlStr ? `?${controlStr}` : ""}`);
|
|
128
103
|
}
|
|
129
|
-
async
|
|
104
|
+
async insert(data) {
|
|
105
|
+
await this._validateData(data, "insert");
|
|
130
106
|
return this._request("POST", "", data);
|
|
131
107
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
async
|
|
108
|
+
/**
|
|
109
|
+
* `PATCH /` — partial update one or many records by primary key.
|
|
110
|
+
*/
|
|
111
|
+
async update(data) {
|
|
112
|
+
await this._validateData(data, "patch");
|
|
136
113
|
return this._request("PATCH", "", data);
|
|
137
114
|
}
|
|
138
|
-
|
|
115
|
+
/**
|
|
116
|
+
* `PUT /` — full replace one or many records by primary key.
|
|
117
|
+
*/
|
|
118
|
+
async replace(data) {
|
|
119
|
+
await this._validateData(data, "replace");
|
|
139
120
|
return this._request("PUT", "", data);
|
|
140
121
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
async
|
|
122
|
+
/**
|
|
123
|
+
* `DELETE /:id` or `DELETE /?k1=v1&k2=v2` — remove a record by primary key.
|
|
124
|
+
*/
|
|
125
|
+
async remove(id) {
|
|
145
126
|
if (id !== null && typeof id === "object") return this._request("DELETE", `?${this._idToParams(id).toString()}`);
|
|
146
127
|
return this._request("DELETE", encodeURIComponent(String(id)));
|
|
147
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* `GET /meta` — table/view metadata (cached after first call).
|
|
131
|
+
*/
|
|
148
132
|
async meta() {
|
|
149
|
-
if (!this._metaPromise) this._metaPromise = this._request("GET", "meta")
|
|
133
|
+
if (!this._metaPromise) this._metaPromise = this._request("GET", "meta").catch((err) => {
|
|
134
|
+
this._metaPromise = void 0;
|
|
135
|
+
throw err;
|
|
136
|
+
});
|
|
150
137
|
return this._metaPromise;
|
|
151
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Returns a lazily-initialized {@link ClientValidator} backed by the `/meta` type.
|
|
141
|
+
* Useful for accessing `flatMap` and `navFields` (e.g. for form generation).
|
|
142
|
+
*/
|
|
143
|
+
getValidator() {
|
|
144
|
+
return this._getValidator();
|
|
145
|
+
}
|
|
146
|
+
async _validateData(data, mode) {
|
|
147
|
+
(await this._getValidator()).validate(data, mode);
|
|
148
|
+
}
|
|
149
|
+
_getValidator() {
|
|
150
|
+
if (!this._validatorPromise) this._validatorPromise = Promise.all([this.meta(), import("./validator.mjs")]).then(([m, { createClientValidator }]) => createClientValidator(m)).catch((err) => {
|
|
151
|
+
this._validatorPromise = void 0;
|
|
152
|
+
throw err;
|
|
153
|
+
});
|
|
154
|
+
return this._validatorPromise;
|
|
155
|
+
}
|
|
152
156
|
_idToParams(id) {
|
|
153
157
|
const params = new URLSearchParams();
|
|
154
158
|
for (const [k, v] of Object.entries(id)) params.set(k, String(v));
|
|
155
159
|
return params;
|
|
156
160
|
}
|
|
161
|
+
async _getOrNull(endpoint) {
|
|
162
|
+
try {
|
|
163
|
+
return await this._request("GET", endpoint);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
if (e instanceof ClientError && e.status === 404) return null;
|
|
166
|
+
throw e;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
157
169
|
async _get(endpoint, query) {
|
|
158
170
|
const qs = query ? buildUrl(query) : "";
|
|
159
171
|
return this._request("GET", `${endpoint}${qs ? `?${qs}` : ""}`);
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, FilterExpr, TypedWithRelation, Uniquery as Uniquery$1, UniqueryControls as UniqueryControls$1 } from "@uniqu/core";
|
|
2
|
+
import { TDbDeleteResult as TDbDeleteResult$1, TDbInsertManyResult as TDbInsertManyResult$1, TDbInsertResult as TDbInsertResult$1, TDbUpdateResult as TDbUpdateResult$1 } from "@atscript/db";
|
|
3
|
+
import { TAtscriptAnnotatedType, TAtscriptTypeObject, TSerializedAnnotatedType } from "@atscript/typescript/utils";
|
|
4
|
+
import { DbValidationContext, ValidatorMode, ValidatorMode as ValidatorMode$1 } from "@atscript/db/validator";
|
|
5
|
+
|
|
6
|
+
//#region src/types.d.ts
|
|
7
|
+
/** Options for creating a Client instance. */
|
|
8
|
+
interface ClientOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Custom fetch implementation. Defaults to `globalThis.fetch`.
|
|
11
|
+
* Use this to inject auth headers, interceptors, or a custom HTTP client.
|
|
12
|
+
*/
|
|
13
|
+
fetch?: typeof globalThis.fetch;
|
|
14
|
+
/**
|
|
15
|
+
* Default headers to include with every request.
|
|
16
|
+
* Can be a static object or an async factory (e.g. for refreshing auth tokens).
|
|
17
|
+
*/
|
|
18
|
+
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
19
|
+
/**
|
|
20
|
+
* Base URL prefix. Prepended to the client path for full URL construction.
|
|
21
|
+
* @example "https://api.example.com"
|
|
22
|
+
*/
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Search index metadata from the server. */
|
|
26
|
+
interface SearchIndexInfo {
|
|
27
|
+
name: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
type?: "text" | "vector";
|
|
30
|
+
}
|
|
31
|
+
/** Relation summary in meta response. */
|
|
32
|
+
interface RelationInfo {
|
|
33
|
+
name: string;
|
|
34
|
+
direction: "to" | "from" | "via";
|
|
35
|
+
isArray: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Per-field capability flags. */
|
|
38
|
+
interface FieldMeta {
|
|
39
|
+
sortable: boolean;
|
|
40
|
+
filterable: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** Enhanced meta response from the server (`GET /meta`). */
|
|
43
|
+
interface MetaResponse {
|
|
44
|
+
searchable: boolean;
|
|
45
|
+
vectorSearchable: boolean;
|
|
46
|
+
searchIndexes: SearchIndexInfo[];
|
|
47
|
+
primaryKeys: string[];
|
|
48
|
+
readOnly: boolean;
|
|
49
|
+
relations: RelationInfo[];
|
|
50
|
+
fields: Record<string, FieldMeta>;
|
|
51
|
+
type: TSerializedAnnotatedType;
|
|
52
|
+
}
|
|
53
|
+
/** Paginated response shape from `GET /pages`. */
|
|
54
|
+
interface PageResult<T> {
|
|
55
|
+
data: T[];
|
|
56
|
+
page: number;
|
|
57
|
+
itemsPerPage: number;
|
|
58
|
+
pages: number;
|
|
59
|
+
count: number;
|
|
60
|
+
}
|
|
61
|
+
/** Server error response shape (matches moost-db error transform). */
|
|
62
|
+
interface ServerError {
|
|
63
|
+
message: string;
|
|
64
|
+
statusCode: number;
|
|
65
|
+
errors?: Array<{
|
|
66
|
+
path: string;
|
|
67
|
+
message: string;
|
|
68
|
+
details?: unknown[];
|
|
69
|
+
}>;
|
|
70
|
+
}
|
|
71
|
+
/** Extract the data type from an Atscript annotated type `T`. */
|
|
72
|
+
type DataOf<T> = T extends {
|
|
73
|
+
type: {
|
|
74
|
+
__dataType?: infer D;
|
|
75
|
+
};
|
|
76
|
+
} ? unknown extends D ? T extends (new (...a: any[]) => infer I) ? I : Record<string, unknown> : D & Record<string, unknown> : Record<string, unknown>;
|
|
77
|
+
/** Extract own (non-nav) properties from an Atscript annotated type. */
|
|
78
|
+
type OwnOf<T> = T extends {
|
|
79
|
+
__ownProps: infer O;
|
|
80
|
+
} ? O : DataOf<T>;
|
|
81
|
+
/** Extract nav properties from an Atscript annotated type. */
|
|
82
|
+
type NavOf<T> = T extends {
|
|
83
|
+
__navProps: infer N extends Record<string, unknown>;
|
|
84
|
+
} ? N : Record<string, never>;
|
|
85
|
+
/** Extract primary key type from an Atscript annotated type. */
|
|
86
|
+
type IdOf<T> = T extends {
|
|
87
|
+
__pk: infer PK;
|
|
88
|
+
} ? PK : unknown;
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/validator.d.ts
|
|
91
|
+
/**
|
|
92
|
+
* Client-side validator backed by an Atscript type from the `/meta` endpoint.
|
|
93
|
+
*
|
|
94
|
+
* Caches validators per mode. Lazily initializes from a meta response promise.
|
|
95
|
+
*/
|
|
96
|
+
declare class ClientValidator {
|
|
97
|
+
private _type;
|
|
98
|
+
private _validators;
|
|
99
|
+
/** Flat map of dotted field paths to their annotated types. */
|
|
100
|
+
readonly flatMap: Map<string, TAtscriptAnnotatedType>;
|
|
101
|
+
/** Set of field paths that are navigation relations (TO/FROM/VIA). */
|
|
102
|
+
readonly navFields: ReadonlySet<string>;
|
|
103
|
+
constructor(type: TAtscriptAnnotatedType<TAtscriptTypeObject>);
|
|
104
|
+
/**
|
|
105
|
+
* Validate data for a given write mode.
|
|
106
|
+
* Throws `ClientValidationError` if validation fails.
|
|
107
|
+
*/
|
|
108
|
+
validate(data: unknown, mode: ValidatorMode): void;
|
|
109
|
+
private _getValidator;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Structured validation error thrown before HTTP requests when client-side
|
|
113
|
+
* validation fails.
|
|
114
|
+
*/
|
|
115
|
+
declare class ClientValidationError extends Error {
|
|
116
|
+
readonly errors: Array<{
|
|
117
|
+
path: string;
|
|
118
|
+
message: string;
|
|
119
|
+
}>;
|
|
120
|
+
constructor(errors: Array<{
|
|
121
|
+
path: string;
|
|
122
|
+
message: string;
|
|
123
|
+
}>);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create a {@link ClientValidator} from a meta response (or promise).
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* const client = new Client<typeof User>('/db/tables/users')
|
|
131
|
+
* const validator = createClientValidator(await client.meta())
|
|
132
|
+
* validator.validate({ name: 'foo' }, 'insert')
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
declare function createClientValidator(meta: MetaResponse): ClientValidator;
|
|
136
|
+
//#endregion
|
|
137
|
+
export { TDbUpdateResult$1 as C, UniqueryControls$1 as E, TDbInsertResult$1 as S, Uniquery$1 as T, RelationInfo as _, createClientValidator as a, TDbDeleteResult$1 as b, ClientOptions as c, FilterExpr as d, IdOf as f, PageResult as g, OwnOf as h, ValidatorMode$1 as i, DataOf as l, NavOf as m, ClientValidator as n, AggregateQuery$1 as o, MetaResponse as p, DbValidationContext as r, AggregateResult$1 as s, ClientValidationError as t, FieldMeta as u, SearchIndexInfo as v, TypedWithRelation as w, TDbInsertManyResult$1 as x, ServerError as y };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { TAtscriptAnnotatedType, TAtscriptTypeObject, TSerializedAnnotatedType } from "@atscript/typescript/utils";
|
|
2
|
+
import { DbValidationContext, ValidatorMode, ValidatorMode as ValidatorMode$1 } from "@atscript/db/validator";
|
|
3
|
+
import { AggregateQuery as AggregateQuery$1, AggregateResult as AggregateResult$1, FilterExpr, TypedWithRelation, Uniquery as Uniquery$1, UniqueryControls as UniqueryControls$1 } from "@uniqu/core";
|
|
4
|
+
import { TDbDeleteResult as TDbDeleteResult$1, TDbInsertManyResult as TDbInsertManyResult$1, TDbInsertResult as TDbInsertResult$1, TDbUpdateResult as TDbUpdateResult$1 } from "@atscript/db";
|
|
5
|
+
|
|
6
|
+
//#region src/types.d.ts
|
|
7
|
+
/** Options for creating a Client instance. */
|
|
8
|
+
interface ClientOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Custom fetch implementation. Defaults to `globalThis.fetch`.
|
|
11
|
+
* Use this to inject auth headers, interceptors, or a custom HTTP client.
|
|
12
|
+
*/
|
|
13
|
+
fetch?: typeof globalThis.fetch;
|
|
14
|
+
/**
|
|
15
|
+
* Default headers to include with every request.
|
|
16
|
+
* Can be a static object or an async factory (e.g. for refreshing auth tokens).
|
|
17
|
+
*/
|
|
18
|
+
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
19
|
+
/**
|
|
20
|
+
* Base URL prefix. Prepended to the client path for full URL construction.
|
|
21
|
+
* @example "https://api.example.com"
|
|
22
|
+
*/
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Search index metadata from the server. */
|
|
26
|
+
interface SearchIndexInfo {
|
|
27
|
+
name: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
type?: "text" | "vector";
|
|
30
|
+
}
|
|
31
|
+
/** Relation summary in meta response. */
|
|
32
|
+
interface RelationInfo {
|
|
33
|
+
name: string;
|
|
34
|
+
direction: "to" | "from" | "via";
|
|
35
|
+
isArray: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Per-field capability flags. */
|
|
38
|
+
interface FieldMeta {
|
|
39
|
+
sortable: boolean;
|
|
40
|
+
filterable: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** Enhanced meta response from the server (`GET /meta`). */
|
|
43
|
+
interface MetaResponse {
|
|
44
|
+
searchable: boolean;
|
|
45
|
+
vectorSearchable: boolean;
|
|
46
|
+
searchIndexes: SearchIndexInfo[];
|
|
47
|
+
primaryKeys: string[];
|
|
48
|
+
readOnly: boolean;
|
|
49
|
+
relations: RelationInfo[];
|
|
50
|
+
fields: Record<string, FieldMeta>;
|
|
51
|
+
type: TSerializedAnnotatedType;
|
|
52
|
+
}
|
|
53
|
+
/** Paginated response shape from `GET /pages`. */
|
|
54
|
+
interface PageResult<T> {
|
|
55
|
+
data: T[];
|
|
56
|
+
page: number;
|
|
57
|
+
itemsPerPage: number;
|
|
58
|
+
pages: number;
|
|
59
|
+
count: number;
|
|
60
|
+
}
|
|
61
|
+
/** Server error response shape (matches moost-db error transform). */
|
|
62
|
+
interface ServerError {
|
|
63
|
+
message: string;
|
|
64
|
+
statusCode: number;
|
|
65
|
+
errors?: Array<{
|
|
66
|
+
path: string;
|
|
67
|
+
message: string;
|
|
68
|
+
details?: unknown[];
|
|
69
|
+
}>;
|
|
70
|
+
}
|
|
71
|
+
/** Extract the data type from an Atscript annotated type `T`. */
|
|
72
|
+
type DataOf<T> = T extends {
|
|
73
|
+
type: {
|
|
74
|
+
__dataType?: infer D;
|
|
75
|
+
};
|
|
76
|
+
} ? unknown extends D ? T extends (new (...a: any[]) => infer I) ? I : Record<string, unknown> : D & Record<string, unknown> : Record<string, unknown>;
|
|
77
|
+
/** Extract own (non-nav) properties from an Atscript annotated type. */
|
|
78
|
+
type OwnOf<T> = T extends {
|
|
79
|
+
__ownProps: infer O;
|
|
80
|
+
} ? O : DataOf<T>;
|
|
81
|
+
/** Extract nav properties from an Atscript annotated type. */
|
|
82
|
+
type NavOf<T> = T extends {
|
|
83
|
+
__navProps: infer N extends Record<string, unknown>;
|
|
84
|
+
} ? N : Record<string, never>;
|
|
85
|
+
/** Extract primary key type from an Atscript annotated type. */
|
|
86
|
+
type IdOf<T> = T extends {
|
|
87
|
+
__pk: infer PK;
|
|
88
|
+
} ? PK : unknown;
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/validator.d.ts
|
|
91
|
+
/**
|
|
92
|
+
* Client-side validator backed by an Atscript type from the `/meta` endpoint.
|
|
93
|
+
*
|
|
94
|
+
* Caches validators per mode. Lazily initializes from a meta response promise.
|
|
95
|
+
*/
|
|
96
|
+
declare class ClientValidator {
|
|
97
|
+
private _type;
|
|
98
|
+
private _validators;
|
|
99
|
+
/** Flat map of dotted field paths to their annotated types. */
|
|
100
|
+
readonly flatMap: Map<string, TAtscriptAnnotatedType>;
|
|
101
|
+
/** Set of field paths that are navigation relations (TO/FROM/VIA). */
|
|
102
|
+
readonly navFields: ReadonlySet<string>;
|
|
103
|
+
constructor(type: TAtscriptAnnotatedType<TAtscriptTypeObject>);
|
|
104
|
+
/**
|
|
105
|
+
* Validate data for a given write mode.
|
|
106
|
+
* Throws `ClientValidationError` if validation fails.
|
|
107
|
+
*/
|
|
108
|
+
validate(data: unknown, mode: ValidatorMode): void;
|
|
109
|
+
private _getValidator;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Structured validation error thrown before HTTP requests when client-side
|
|
113
|
+
* validation fails.
|
|
114
|
+
*/
|
|
115
|
+
declare class ClientValidationError extends Error {
|
|
116
|
+
readonly errors: Array<{
|
|
117
|
+
path: string;
|
|
118
|
+
message: string;
|
|
119
|
+
}>;
|
|
120
|
+
constructor(errors: Array<{
|
|
121
|
+
path: string;
|
|
122
|
+
message: string;
|
|
123
|
+
}>);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create a {@link ClientValidator} from a meta response (or promise).
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* const client = new Client<typeof User>('/db/tables/users')
|
|
131
|
+
* const validator = createClientValidator(await client.meta())
|
|
132
|
+
* validator.validate({ name: 'foo' }, 'insert')
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
declare function createClientValidator(meta: MetaResponse): ClientValidator;
|
|
136
|
+
//#endregion
|
|
137
|
+
export { TDbUpdateResult$1 as C, UniqueryControls$1 as E, TDbInsertResult$1 as S, Uniquery$1 as T, RelationInfo as _, createClientValidator as a, TDbDeleteResult$1 as b, ClientOptions as c, FilterExpr as d, IdOf as f, PageResult as g, OwnOf as h, ValidatorMode$1 as i, DataOf as l, NavOf as m, ClientValidator as n, AggregateQuery$1 as o, MetaResponse as p, DbValidationContext as r, AggregateResult$1 as s, ClientValidationError as t, FieldMeta as u, SearchIndexInfo as v, TypedWithRelation as w, TDbInsertManyResult$1 as x, ServerError as y };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _atscript_typescript_utils = require("@atscript/typescript/utils");
|
|
3
|
+
let _atscript_db_validator = require("@atscript/db/validator");
|
|
4
|
+
//#region src/validator.ts
|
|
5
|
+
/**
|
|
6
|
+
* Client-side validator backed by an Atscript type from the `/meta` endpoint.
|
|
7
|
+
*
|
|
8
|
+
* Caches validators per mode. Lazily initializes from a meta response promise.
|
|
9
|
+
*/
|
|
10
|
+
var ClientValidator = class {
|
|
11
|
+
_type;
|
|
12
|
+
_validators = /* @__PURE__ */ new Map();
|
|
13
|
+
/** Flat map of dotted field paths to their annotated types. */
|
|
14
|
+
flatMap;
|
|
15
|
+
/** Set of field paths that are navigation relations (TO/FROM/VIA). */
|
|
16
|
+
navFields;
|
|
17
|
+
constructor(type) {
|
|
18
|
+
this._type = type;
|
|
19
|
+
const ctx = (0, _atscript_db_validator.buildValidationContext)(type);
|
|
20
|
+
this.flatMap = ctx.flatMap;
|
|
21
|
+
this.navFields = ctx.navFields;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Validate data for a given write mode.
|
|
25
|
+
* Throws `ClientValidationError` if validation fails.
|
|
26
|
+
*/
|
|
27
|
+
validate(data, mode) {
|
|
28
|
+
const isArray = Array.isArray(data);
|
|
29
|
+
const items = isArray ? data : [data];
|
|
30
|
+
const validator = this._getValidator(mode);
|
|
31
|
+
const ctx = {
|
|
32
|
+
mode,
|
|
33
|
+
flatMap: this.flatMap,
|
|
34
|
+
navFields: this.navFields
|
|
35
|
+
};
|
|
36
|
+
for (let i = 0; i < items.length; i++) if (!validator.validate(items[i], true, ctx)) {
|
|
37
|
+
const prefix = isArray ? `[${i}]` : "";
|
|
38
|
+
throw new ClientValidationError(validator.errors.map((e) => ({
|
|
39
|
+
path: prefix ? e.path ? `${prefix}.${e.path}` : prefix : e.path,
|
|
40
|
+
message: e.message
|
|
41
|
+
})));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
_getValidator(mode) {
|
|
45
|
+
let v = this._validators.get(mode);
|
|
46
|
+
if (!v) {
|
|
47
|
+
v = (0, _atscript_db_validator.buildDbValidator)(this._type, mode);
|
|
48
|
+
this._validators.set(mode, v);
|
|
49
|
+
}
|
|
50
|
+
return v;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Structured validation error thrown before HTTP requests when client-side
|
|
55
|
+
* validation fails.
|
|
56
|
+
*/
|
|
57
|
+
var ClientValidationError = class extends Error {
|
|
58
|
+
errors;
|
|
59
|
+
constructor(errors) {
|
|
60
|
+
const msg = errors.length === 1 ? errors[0].message : `Validation failed with ${errors.length} errors`;
|
|
61
|
+
super(msg);
|
|
62
|
+
this.name = "ClientValidationError";
|
|
63
|
+
this.errors = errors;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Create a {@link ClientValidator} from a meta response (or promise).
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* const client = new Client<typeof User>('/db/tables/users')
|
|
72
|
+
* const validator = createClientValidator(await client.meta())
|
|
73
|
+
* validator.validate({ name: 'foo' }, 'insert')
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
function createClientValidator(meta) {
|
|
77
|
+
return new ClientValidator((0, _atscript_typescript_utils.deserializeAnnotatedType)(meta.type));
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
exports.ClientValidationError = ClientValidationError;
|
|
81
|
+
exports.ClientValidator = ClientValidator;
|
|
82
|
+
exports.createClientValidator = createClientValidator;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as createClientValidator, i as ValidatorMode, n as ClientValidator, r as DbValidationContext, t as ClientValidationError } from "./validator-Baaf7PPf.cjs";
|
|
2
|
+
export { ClientValidationError, ClientValidator, DbValidationContext, ValidatorMode, createClientValidator };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as createClientValidator, i as ValidatorMode, n as ClientValidator, r as DbValidationContext, t as ClientValidationError } from "./validator-D0lqruKm.mjs";
|
|
2
|
+
export { ClientValidationError, ClientValidator, DbValidationContext, ValidatorMode, createClientValidator };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { deserializeAnnotatedType } from "@atscript/typescript/utils";
|
|
2
|
+
import { buildDbValidator, buildValidationContext } from "@atscript/db/validator";
|
|
3
|
+
//#region src/validator.ts
|
|
4
|
+
/**
|
|
5
|
+
* Client-side validator backed by an Atscript type from the `/meta` endpoint.
|
|
6
|
+
*
|
|
7
|
+
* Caches validators per mode. Lazily initializes from a meta response promise.
|
|
8
|
+
*/
|
|
9
|
+
var ClientValidator = class {
|
|
10
|
+
_type;
|
|
11
|
+
_validators = /* @__PURE__ */ new Map();
|
|
12
|
+
/** Flat map of dotted field paths to their annotated types. */
|
|
13
|
+
flatMap;
|
|
14
|
+
/** Set of field paths that are navigation relations (TO/FROM/VIA). */
|
|
15
|
+
navFields;
|
|
16
|
+
constructor(type) {
|
|
17
|
+
this._type = type;
|
|
18
|
+
const ctx = buildValidationContext(type);
|
|
19
|
+
this.flatMap = ctx.flatMap;
|
|
20
|
+
this.navFields = ctx.navFields;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Validate data for a given write mode.
|
|
24
|
+
* Throws `ClientValidationError` if validation fails.
|
|
25
|
+
*/
|
|
26
|
+
validate(data, mode) {
|
|
27
|
+
const isArray = Array.isArray(data);
|
|
28
|
+
const items = isArray ? data : [data];
|
|
29
|
+
const validator = this._getValidator(mode);
|
|
30
|
+
const ctx = {
|
|
31
|
+
mode,
|
|
32
|
+
flatMap: this.flatMap,
|
|
33
|
+
navFields: this.navFields
|
|
34
|
+
};
|
|
35
|
+
for (let i = 0; i < items.length; i++) if (!validator.validate(items[i], true, ctx)) {
|
|
36
|
+
const prefix = isArray ? `[${i}]` : "";
|
|
37
|
+
throw new ClientValidationError(validator.errors.map((e) => ({
|
|
38
|
+
path: prefix ? e.path ? `${prefix}.${e.path}` : prefix : e.path,
|
|
39
|
+
message: e.message
|
|
40
|
+
})));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
_getValidator(mode) {
|
|
44
|
+
let v = this._validators.get(mode);
|
|
45
|
+
if (!v) {
|
|
46
|
+
v = buildDbValidator(this._type, mode);
|
|
47
|
+
this._validators.set(mode, v);
|
|
48
|
+
}
|
|
49
|
+
return v;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Structured validation error thrown before HTTP requests when client-side
|
|
54
|
+
* validation fails.
|
|
55
|
+
*/
|
|
56
|
+
var ClientValidationError = class extends Error {
|
|
57
|
+
errors;
|
|
58
|
+
constructor(errors) {
|
|
59
|
+
const msg = errors.length === 1 ? errors[0].message : `Validation failed with ${errors.length} errors`;
|
|
60
|
+
super(msg);
|
|
61
|
+
this.name = "ClientValidationError";
|
|
62
|
+
this.errors = errors;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Create a {@link ClientValidator} from a meta response (or promise).
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* const client = new Client<typeof User>('/db/tables/users')
|
|
71
|
+
* const validator = createClientValidator(await client.meta())
|
|
72
|
+
* validator.validate({ name: 'foo' }, 'insert')
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
function createClientValidator(meta) {
|
|
76
|
+
return new ClientValidator(deserializeAnnotatedType(meta.type));
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
export { ClientValidationError, ClientValidator, createClientValidator };
|