@atscript/db-client 0.1.43 → 0.1.45

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/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
- * Browser-compatible HTTP client for moost-db REST endpoints.
22
+ * HTTP client for moost-db REST endpoints.
23
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')
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
- * // Type-safe — Atscript type as generic parameter
30
- * const users = new Client<typeof User>('/db/tables/users')
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
- 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) {
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
- 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
- }
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
- 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", {
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: limit
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
- async aggregate(query) {
124
- return this._get("query", query);
125
- }
126
- async insertOne(data) {
127
- return this._request("POST", "", data);
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 insertMany(data) {
104
+ async insert(data) {
105
+ await this._validateData(data, "insert");
130
106
  return this._request("POST", "", data);
131
107
  }
132
- async updateOne(data) {
133
- return this._request("PATCH", "", data);
134
- }
135
- async bulkUpdate(data) {
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
- async replaceOne(data) {
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
- async bulkReplace(data) {
142
- return this._request("PUT", "", data);
143
- }
144
- async deleteOne(id) {
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 };