@standards/client 0.0.0-stage → 1.0.0-alpha.382
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/LICENSE +111 -0
- package/README.md +61 -2
- package/dist/app.d.mts +67 -0
- package/dist/app.d.ts +67 -0
- package/dist/app.js +2 -0
- package/dist/app.mjs +2 -0
- package/dist/create-standards-3ODOXxRV.d.ts +439 -0
- package/dist/create-standards-Cl_Lxvy3.d.mts +439 -0
- package/dist/create-standards-DVK0prD0.js +2 -0
- package/dist/create-standards-p0dGJXSu.mjs +2 -0
- package/dist/http-transport-17TmxnFl.mjs +2 -0
- package/dist/http-transport-DBgedLFQ.d.mts +165 -0
- package/dist/http-transport-DBgedLFQ.d.ts +165 -0
- package/dist/http-transport-DHbErYa4.js +2 -0
- package/dist/index.d.mts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +2 -0
- package/dist/index.mjs +2 -0
- package/dist/testing.d.mts +79 -0
- package/dist/testing.d.ts +79 -0
- package/dist/testing.js +2 -0
- package/dist/testing.mjs +2 -0
- package/package.json +81 -3
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
|
|
2
|
+
import { r as MultipartRequestOptions, s as TransportConfig, t as HttpTransport } from "./http-transport-DBgedLFQ.js";
|
|
3
|
+
import { AggregateRecordsOptions, ContentViewDefinition, DocumentFile, ExtractRecord, ExtractRecordInputStrict, ExtractRecordUpdateStrict, FilterRule, ObjectBuilderLike, ObjectBuilderLike as ObjectBuilderLike$1, ObjectDefinition, RecordAccessRole, RecordAggregates, RecordAudience, RuleValue, SchemaGroupPresentation, SchemaSourceResolution, ViewDeclaration } from "@standards/schema";
|
|
4
|
+
//#region src/define-schema-source.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* `@standards/constants` (the home of `IconName`) is not a dependency of this
|
|
7
|
+
* package; deriving the type from `SchemaGroupPresentation` avoids adding one
|
|
8
|
+
* just for a type alias.
|
|
9
|
+
*/
|
|
10
|
+
type IconName = NonNullable<SchemaGroupPresentation["icon"]>;
|
|
11
|
+
type ViewInput = ContentViewDefinition | ViewDeclaration | {
|
|
12
|
+
build(): ContentViewDefinition | ViewDeclaration;
|
|
13
|
+
};
|
|
14
|
+
interface SchemaSourceConfig<TObjects extends readonly ObjectBuilderLike[]> {
|
|
15
|
+
/**
|
|
16
|
+
* Slug identity of the source, required to sync. Write it by hand, or derive
|
|
17
|
+
* it with `sourceIdFromPackageName(packageJson.name)`.
|
|
18
|
+
*/
|
|
19
|
+
id?: string;
|
|
20
|
+
objects: TObjects;
|
|
21
|
+
views?: ViewInput[];
|
|
22
|
+
label?: string;
|
|
23
|
+
icon?: IconName;
|
|
24
|
+
}
|
|
25
|
+
/** What `defineSchemaSource` returns: the declaration only — nothing is built until it is resolved. */
|
|
26
|
+
interface SchemaSource<TObjects extends readonly ObjectBuilderLike[] = readonly ObjectBuilderLike[]> {
|
|
27
|
+
readonly id: string | null;
|
|
28
|
+
/** The builders, kept for type inference of the typed client (`db.<camelCaseName>`). */
|
|
29
|
+
readonly builders: TObjects;
|
|
30
|
+
readonly views: readonly ViewInput[];
|
|
31
|
+
readonly label: string | null;
|
|
32
|
+
readonly icon: IconName | null;
|
|
33
|
+
}
|
|
34
|
+
/** A source built, validated and hashed — what a sync sends. */
|
|
35
|
+
interface ResolvedSchemaSource {
|
|
36
|
+
id: string | null;
|
|
37
|
+
/** sha256 hex of `canonicalStringify({ objects, views })` — the same digest the server recomputes. */
|
|
38
|
+
hash: string;
|
|
39
|
+
objects: ObjectDefinition[];
|
|
40
|
+
views: ContentViewDefinition[];
|
|
41
|
+
label: string | null;
|
|
42
|
+
icon: IconName | null;
|
|
43
|
+
}
|
|
44
|
+
/** sha256 hex over the canonical JSON, with Web Crypto (Node >= 20, Edge, browsers). */
|
|
45
|
+
declare function hashSchemaSource(payload: {
|
|
46
|
+
objects: ObjectDefinition[];
|
|
47
|
+
views: ContentViewDefinition[];
|
|
48
|
+
}): Promise<string>;
|
|
49
|
+
/** Declares the schema an external codebase owns. Does no work: see `resolveSchemaSource`. */
|
|
50
|
+
declare function defineSchemaSource<const TObjects extends readonly ObjectBuilderLike[]>(config: SchemaSourceConfig<TObjects>): SchemaSource<TObjects>;
|
|
51
|
+
/** Builds, validates and hashes a source once; later calls share the same promise. */
|
|
52
|
+
declare function resolveSchemaSource(source: SchemaSource): Promise<ResolvedSchemaSource>;
|
|
53
|
+
/** `@acme/crm` -> `acme-crm`; refuses a name the server would refuse as a source id. */
|
|
54
|
+
declare function sourceIdFromPackageName(packageName: string): string;
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/files/file-input.d.ts
|
|
57
|
+
/**
|
|
58
|
+
* What the files and documents methods upload: a `File` (or any `Blob` carrying
|
|
59
|
+
* a `name`) with a non-empty filename and MIME type. The server checks the MIME
|
|
60
|
+
* type, the extension and the file signature against each other, so all three
|
|
61
|
+
* travel with the bytes. A `Buffer` or a path is not an input: wrap the bytes in
|
|
62
|
+
* `new File([bytes], "name.png", { type: "image/png" })`.
|
|
63
|
+
*/
|
|
64
|
+
type FileInput = Blob & {
|
|
65
|
+
readonly name: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* The timeout of one upload's request, in ms (#4741). A large file on a slow
|
|
69
|
+
* link outlives the client's `timeoutMs` (30 s by default) and surfaces as a
|
|
70
|
+
* connection failure, possibly after the server stored it — so raise it for
|
|
71
|
+
* large uploads rather than retrying a timed-out attach.
|
|
72
|
+
*/
|
|
73
|
+
interface UploadTimeoutOption {
|
|
74
|
+
timeoutMs?: number;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/files/documents-api.d.ts
|
|
78
|
+
interface AttachToRecordInput extends UploadTimeoutOption {
|
|
79
|
+
objectName: string;
|
|
80
|
+
recordId: string;
|
|
81
|
+
/** The record's `document` attribute; the server defaults to `attachments`. */
|
|
82
|
+
attributeName?: string;
|
|
83
|
+
/** Append to this existing document's pack instead of creating a document. */
|
|
84
|
+
documentId?: string;
|
|
85
|
+
/** The new document's title; the server defaults to the filename without its extension. */
|
|
86
|
+
title?: string;
|
|
87
|
+
/**
|
|
88
|
+
* The bytes to upload, private, in the same request — the input
|
|
89
|
+
* `files.upload` takes. The attach route takes one file per call: append
|
|
90
|
+
* more by passing the returned `document.id` as `documentId`. An
|
|
91
|
+
* already-uploaded file is not an input, so a public upload can never end up
|
|
92
|
+
* in a document.
|
|
93
|
+
*/
|
|
94
|
+
file: FileInput;
|
|
95
|
+
}
|
|
96
|
+
interface AttachToRecordResult {
|
|
97
|
+
document: {
|
|
98
|
+
id: string;
|
|
99
|
+
title: string;
|
|
100
|
+
};
|
|
101
|
+
file: DocumentFile;
|
|
102
|
+
/** `created`: a new document on the record; `appended`: added to `documentId`'s pack. */
|
|
103
|
+
action: "created" | "appended";
|
|
104
|
+
}
|
|
105
|
+
interface DocumentsApi {
|
|
106
|
+
/**
|
|
107
|
+
* Upload a file and attach it to a record's document attribute in ONE
|
|
108
|
+
* multipart `POST /records/:object/:id/documents/attach`. The server
|
|
109
|
+
* validates the file against the attribute and deletes the upload if the
|
|
110
|
+
* attach fails, so an error never leaves an orphaned file.
|
|
111
|
+
*/
|
|
112
|
+
attachToRecord(input: AttachToRecordInput): Promise<AttachToRecordResult>;
|
|
113
|
+
/** The document's files in pack order (`GET /documents/:id/files`). */
|
|
114
|
+
listFiles(documentId: string): Promise<DocumentFile[]>;
|
|
115
|
+
}
|
|
116
|
+
/** The two verbs this module needs, typed non-generically so a stub satisfies it. */
|
|
117
|
+
interface DocumentsTransport {
|
|
118
|
+
get(path: string): Promise<unknown>;
|
|
119
|
+
postMultipart(path: string, form: FormData, options?: MultipartRequestOptions): Promise<unknown>;
|
|
120
|
+
}
|
|
121
|
+
declare function createDocumentsApi(transport: DocumentsTransport): DocumentsApi;
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/files/files-api.d.ts
|
|
124
|
+
interface UploadFileBase extends UploadTimeoutOption {
|
|
125
|
+
file: FileInput;
|
|
126
|
+
/** Stored as the file's folder (`folderPath` on `POST /files/upload`). */
|
|
127
|
+
folderPath?: string;
|
|
128
|
+
}
|
|
129
|
+
/** Anyone holding the returned `publicUrl` can open the file. A public file never joins a document. */
|
|
130
|
+
interface UploadPublicFileInput extends UploadFileBase {
|
|
131
|
+
public: true;
|
|
132
|
+
}
|
|
133
|
+
/** The default: readable through a signed URL from `getDownloadUrl`. */
|
|
134
|
+
interface UploadPrivateFileInput extends UploadFileBase {
|
|
135
|
+
public?: false;
|
|
136
|
+
}
|
|
137
|
+
type UploadFileInput = UploadPublicFileInput | UploadPrivateFileInput;
|
|
138
|
+
interface UploadedFileBase {
|
|
139
|
+
id: string;
|
|
140
|
+
/** The stored (sanitized) filename. */
|
|
141
|
+
name: string;
|
|
142
|
+
mimeType: string;
|
|
143
|
+
size: number;
|
|
144
|
+
}
|
|
145
|
+
interface PublicUploadedFile extends UploadedFileBase {
|
|
146
|
+
isPublic: true;
|
|
147
|
+
/** `{publicFilesUrl}/{id}`: permanent, and never carries a credential. */
|
|
148
|
+
publicUrl: string;
|
|
149
|
+
}
|
|
150
|
+
interface PrivateUploadedFile extends UploadedFileBase {
|
|
151
|
+
isPublic: false;
|
|
152
|
+
publicUrl?: never;
|
|
153
|
+
}
|
|
154
|
+
/** Discriminated on `isPublic`: only the public branch has a `publicUrl`. */
|
|
155
|
+
type UploadedFile = PublicUploadedFile | PrivateUploadedFile;
|
|
156
|
+
interface FilesApi {
|
|
157
|
+
/** One multipart `POST /files/upload`. */
|
|
158
|
+
upload(input: UploadPublicFileInput): Promise<PublicUploadedFile>;
|
|
159
|
+
upload(input: UploadPrivateFileInput): Promise<PrivateUploadedFile>;
|
|
160
|
+
upload(input: UploadFileInput): Promise<UploadedFile>;
|
|
161
|
+
/** A fresh signed read URL (`GET /files/:id/url`); it expires, so ask again each time. */
|
|
162
|
+
getDownloadUrl(fileId: string): Promise<string>;
|
|
163
|
+
}
|
|
164
|
+
/** The one verb this module needs: the `files` operations. */
|
|
165
|
+
type FilesTransport = Pick<HttpTransport, "call">;
|
|
166
|
+
declare function createFilesApi(transport: FilesTransport): FilesApi;
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/object-keys.d.ts
|
|
169
|
+
/** `daily-stats` → `dailyStats`. Object names are kebab-case (enforced by the builder). */
|
|
170
|
+
type CamelCase<S extends string> = S extends `${infer Head}-${infer Tail}` ? `${Head}${Capitalize<CamelCase<Tail>>}` : S;
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/records/query.d.ts
|
|
173
|
+
type Attr<TBuilder> = keyof ExtractRecord<TBuilder> & string;
|
|
174
|
+
/**
|
|
175
|
+
* The builder's own attributes keep their exact types (a `select` refuses an
|
|
176
|
+
* option it does not declare); runtime-added custom attributes stay writable
|
|
177
|
+
* through the open index. `ExtractRecordInput`'s own index is typed
|
|
178
|
+
* `CustomAttributeValue`, which has no `Date` member and would reject the very
|
|
179
|
+
* date values the REST API accepts — hence the strict variant plus this index.
|
|
180
|
+
*/
|
|
181
|
+
type RecordInput<TBuilder> = ExtractRecordInputStrict<TBuilder> & {
|
|
182
|
+
[key: string]: unknown;
|
|
183
|
+
};
|
|
184
|
+
type RecordUpdate<TBuilder> = ExtractRecordUpdateStrict<TBuilder> & {
|
|
185
|
+
[key: string]: unknown;
|
|
186
|
+
};
|
|
187
|
+
/** What `export()` takes beside its columns. */
|
|
188
|
+
interface RecordsExportOptions {
|
|
189
|
+
/** Export only these records (at most `EXPORT_MAX_IDS`, 20 000). */
|
|
190
|
+
ids?: string[];
|
|
191
|
+
/** BCP 47 locale numbers, dates and headers are written in. Default: the request's. */
|
|
192
|
+
locale?: string;
|
|
193
|
+
/** IANA zone dates are read in. Default: the caller's (`Intl`). */
|
|
194
|
+
timezone?: string;
|
|
195
|
+
/** The tab name the downloaded file is named after (`{object}-{tab}-{date}.csv`). */
|
|
196
|
+
filename?: string;
|
|
197
|
+
}
|
|
198
|
+
/** What `aggregate()` takes: the aggregate contract minus the query, which is this one. */
|
|
199
|
+
type RecordsAggregateOptions = Omit<AggregateRecordsOptions, "query">;
|
|
200
|
+
interface FetchResult<TBuilder> {
|
|
201
|
+
records: ExtractRecord<TBuilder>[];
|
|
202
|
+
total: number | null;
|
|
203
|
+
}
|
|
204
|
+
/** One input `createMany` did not create; `index` is its position in the caller's array. */
|
|
205
|
+
interface CreateManyError {
|
|
206
|
+
index: number;
|
|
207
|
+
error: string;
|
|
208
|
+
}
|
|
209
|
+
interface CreateManyResult<TBuilder> {
|
|
210
|
+
created: ExtractRecord<TBuilder>[];
|
|
211
|
+
errors: CreateManyError[];
|
|
212
|
+
}
|
|
213
|
+
/** One `{ id, data }` pair of `updateMany`; `data` is typed like `update`'s patch. */
|
|
214
|
+
interface UpdateManyEntry<TBuilder> {
|
|
215
|
+
id: string;
|
|
216
|
+
data: RecordUpdate<TBuilder>;
|
|
217
|
+
}
|
|
218
|
+
/** One entry `updateMany` did not update. */
|
|
219
|
+
/**
|
|
220
|
+
* One id `updateMany` did not update. `code` is the server's `SchemaErrorCode`
|
|
221
|
+
* when the runtime raised the refusal (`SCHEMA_FORBIDDEN`,
|
|
222
|
+
* `SCHEMA_SYSTEM_ENTITY_IMMUTABLE`, …); a database-side rejection carries its message only.
|
|
223
|
+
*/
|
|
224
|
+
interface UpdateManyError {
|
|
225
|
+
id: string;
|
|
226
|
+
error: string;
|
|
227
|
+
code?: string;
|
|
228
|
+
}
|
|
229
|
+
interface UpdateManyResult<TBuilder> {
|
|
230
|
+
updated: ExtractRecord<TBuilder>[];
|
|
231
|
+
errors: UpdateManyError[];
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* One id `deleteMany` did not delete. `code` is the server's `SchemaErrorCode`
|
|
235
|
+
* when the runtime raised the refusal (`SCHEMA_FORBIDDEN`,
|
|
236
|
+
* `SCHEMA_RECORD_NOT_FOUND`, …); a database-side rejection carries its message only.
|
|
237
|
+
*/
|
|
238
|
+
interface DeleteManyError {
|
|
239
|
+
id: string;
|
|
240
|
+
error: string;
|
|
241
|
+
code?: string;
|
|
242
|
+
}
|
|
243
|
+
interface DeleteManyResult {
|
|
244
|
+
deleted: string[];
|
|
245
|
+
errors: DeleteManyError[];
|
|
246
|
+
}
|
|
247
|
+
interface RecordsQuery<TBuilder> {
|
|
248
|
+
eq(attribute: Attr<TBuilder>, value: RuleValue): RecordsQuery<TBuilder>;
|
|
249
|
+
neq(attribute: Attr<TBuilder>, value: RuleValue): RecordsQuery<TBuilder>;
|
|
250
|
+
/** Appends rules as they are: any operator, a qualified `property`, `exists`, dynamic values. */
|
|
251
|
+
where(...rules: (Omit<FilterRule, "attribute"> & {
|
|
252
|
+
attribute: Attr<TBuilder>;
|
|
253
|
+
})[]): RecordsQuery<TBuilder>;
|
|
254
|
+
gt(attribute: Attr<TBuilder>, value: RuleValue): RecordsQuery<TBuilder>;
|
|
255
|
+
gte(attribute: Attr<TBuilder>, value: RuleValue): RecordsQuery<TBuilder>;
|
|
256
|
+
lt(attribute: Attr<TBuilder>, value: RuleValue): RecordsQuery<TBuilder>;
|
|
257
|
+
lte(attribute: Attr<TBuilder>, value: RuleValue): RecordsQuery<TBuilder>;
|
|
258
|
+
contains(attribute: Attr<TBuilder>, value: RuleValue): RecordsQuery<TBuilder>;
|
|
259
|
+
notContains(attribute: Attr<TBuilder>, value: RuleValue): RecordsQuery<TBuilder>;
|
|
260
|
+
startsWith(attribute: Attr<TBuilder>, value: string): RecordsQuery<TBuilder>;
|
|
261
|
+
endsWith(attribute: Attr<TBuilder>, value: string): RecordsQuery<TBuilder>;
|
|
262
|
+
in(attribute: Attr<TBuilder>, values: string[]): RecordsQuery<TBuilder>;
|
|
263
|
+
notIn(attribute: Attr<TBuilder>, values: string[]): RecordsQuery<TBuilder>;
|
|
264
|
+
isEmpty(attribute: Attr<TBuilder>): RecordsQuery<TBuilder>;
|
|
265
|
+
isNotEmpty(attribute: Attr<TBuilder>): RecordsQuery<TBuilder>;
|
|
266
|
+
or(): RecordsQuery<TBuilder>;
|
|
267
|
+
orderBy(attribute: Attr<TBuilder>, direction?: "asc" | "desc"): RecordsQuery<TBuilder>;
|
|
268
|
+
limit(count: number): RecordsQuery<TBuilder>;
|
|
269
|
+
offset(count: number): RecordsQuery<TBuilder>;
|
|
270
|
+
search(query: string): RecordsQuery<TBuilder>;
|
|
271
|
+
/**
|
|
272
|
+
* `POST /records/<object>/export`: this query's search, filters and sorts as
|
|
273
|
+
* a CSV with the given attributes as columns, in order. Resolves to the
|
|
274
|
+
* unread `Response` (`text/csv`, UTF-8 BOM, CRLF) so a large export can be
|
|
275
|
+
* streamed; a refusal (`EXPORT_TOO_LARGE`, validation) rejects before any byte.
|
|
276
|
+
* `ids` restricts it to those records; `timezone` (default: the caller's) is
|
|
277
|
+
* the zone dates are read in; `filename` is the tab name the file is named after.
|
|
278
|
+
*/
|
|
279
|
+
export(columns: string[], options?: RecordsExportOptions): Promise<Response>;
|
|
280
|
+
/**
|
|
281
|
+
* `POST /records/<object>/aggregate`: one measure over this query's records,
|
|
282
|
+
* optionally grouped and compared with the previous period. The query's
|
|
283
|
+
* filters and relation scope are measured; its sorts and limit are ignored,
|
|
284
|
+
* and a search is refused (`AGGREGATE_SEARCH_UNSUPPORTED`). `timezone`
|
|
285
|
+
* defaults to the caller's.
|
|
286
|
+
*/
|
|
287
|
+
aggregate(options: RecordsAggregateOptions): Promise<RecordAggregates>;
|
|
288
|
+
fetch(): Promise<FetchResult<TBuilder>>;
|
|
289
|
+
/**
|
|
290
|
+
* Every matching record, walked in pages of {@link RECORDS_PAGE_SIZE} with
|
|
291
|
+
* this query's filters, sorts and search while the response says
|
|
292
|
+
* `page.hasMore`, resuming at its `nextOffset`. An
|
|
293
|
+
* explicit `limit(n)` caps the total, an explicit `offset(k)` is where the
|
|
294
|
+
* walk starts. A page that fails rejects the whole call.
|
|
295
|
+
*/
|
|
296
|
+
all(): Promise<ExtractRecord<TBuilder>[]>;
|
|
297
|
+
single(): Promise<ExtractRecord<TBuilder> | null>;
|
|
298
|
+
get(id: string): Promise<ExtractRecord<TBuilder>>;
|
|
299
|
+
create(input: RecordInput<TBuilder>): Promise<ExtractRecord<TBuilder>>;
|
|
300
|
+
update(id: string, patch: RecordUpdate<TBuilder>): Promise<ExtractRecord<TBuilder>>;
|
|
301
|
+
delete(id: string): Promise<void>;
|
|
302
|
+
/**
|
|
303
|
+
* `POST /records/<object>/bulk-create`, 100 inputs per request, sent one after
|
|
304
|
+
* another. A refused input is reported in `errors` (never thrown); a failed
|
|
305
|
+
* request rejects, and the chunks before it stay created. Every record is
|
|
306
|
+
* workspace-visible: the route takes no visibility.
|
|
307
|
+
*/
|
|
308
|
+
createMany(inputs: readonly RecordInput<TBuilder>[]): Promise<CreateManyResult<TBuilder>>;
|
|
309
|
+
/** `POST /records/<object>/bulk-update`, chunked and reported like `createMany`. */
|
|
310
|
+
updateMany(entries: readonly UpdateManyEntry<TBuilder>[]): Promise<UpdateManyResult<TBuilder>>;
|
|
311
|
+
/** `POST /records/<object>/bulk-delete`, chunked and reported like `createMany`. */
|
|
312
|
+
deleteMany(ids: readonly string[]): Promise<DeleteManyResult>;
|
|
313
|
+
/** Who can reach this private record beyond its owner: `GET /records/<object>/<id>/access`. */
|
|
314
|
+
listAccess(id: string): Promise<RecordAudience>;
|
|
315
|
+
/** Share a private record you own or edit: `PUT /records/<object>/<id>/access/<actorId>`. */
|
|
316
|
+
grantAccess(id: string, actorId: string, role: RecordAccessRole): Promise<RecordAudience>;
|
|
317
|
+
/** Remove your manual share: `DELETE /records/<object>/<id>/access/<actorId>`. */
|
|
318
|
+
revokeAccess(id: string, actorId: string): Promise<RecordAudience>;
|
|
319
|
+
/** Make a shared record private again — every manual share at once: `DELETE /records/<object>/<id>/access`. */
|
|
320
|
+
clearAccess(id: string): Promise<RecordAudience>;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* The builder shape `from(objectName: string)` stands in for: no declared
|
|
324
|
+
* attributes, so `ExtractRecord` resolves to an open record and `records` is a
|
|
325
|
+
* usable `Record<string, unknown> & RecordMetadata` array instead of `never[]`.
|
|
326
|
+
* `build()` returns `never` because there is no definition to build — the
|
|
327
|
+
* string overload never calls it.
|
|
328
|
+
*/
|
|
329
|
+
interface UntypedBuilder {
|
|
330
|
+
readonly _types: {
|
|
331
|
+
name: string;
|
|
332
|
+
record: Record<string, unknown>;
|
|
333
|
+
input: Record<string, unknown>;
|
|
334
|
+
update: Record<string, unknown>;
|
|
335
|
+
};
|
|
336
|
+
build(): never;
|
|
337
|
+
}
|
|
338
|
+
/** What `standards.from("object-name")` hands back. */
|
|
339
|
+
type UntypedRecordsQuery = RecordsQuery<UntypedBuilder>;
|
|
340
|
+
/**
|
|
341
|
+
* The verbs this module needs, typed non-generically: the real `Transport`
|
|
342
|
+
* satisfies it, and so does a hand-rolled stub, which a `Pick` of the generic
|
|
343
|
+
* signatures would reject. `postRaw` only serves `export()`, which refuses
|
|
344
|
+
* when a stub leaves it out. Each path is its contract operation's
|
|
345
|
+
* (`buildOperationPath`, every parameter one encoded segment); the verbs stay
|
|
346
|
+
* the transport's.
|
|
347
|
+
*/
|
|
348
|
+
interface RecordsTransport {
|
|
349
|
+
get(path: string): Promise<unknown>;
|
|
350
|
+
post(path: string, body?: unknown): Promise<unknown>;
|
|
351
|
+
postRaw?(path: string, body: unknown): Promise<Response>;
|
|
352
|
+
put(path: string, body?: unknown): Promise<unknown>;
|
|
353
|
+
delete(path: string): Promise<unknown>;
|
|
354
|
+
}
|
|
355
|
+
/** A builder gives typed records; a bare object name is the untyped escape hatch. */
|
|
356
|
+
declare function createRecordsQuery<TBuilder extends ObjectBuilderLike$1>(transport: RecordsTransport, target: TBuilder): RecordsQuery<TBuilder>;
|
|
357
|
+
declare function createRecordsQuery(transport: RecordsTransport, target: string): UntypedRecordsQuery;
|
|
358
|
+
//#endregion
|
|
359
|
+
//#region src/schema-sync.d.ts
|
|
360
|
+
interface SchemaSourceSummary {
|
|
361
|
+
sourceId: string;
|
|
362
|
+
hash: string;
|
|
363
|
+
appliedAt: string;
|
|
364
|
+
objectNames: string[];
|
|
365
|
+
/** Absent on an older server that predates presentation; treated as "no presentation". */
|
|
366
|
+
label?: string | null;
|
|
367
|
+
icon?: string | null;
|
|
368
|
+
}
|
|
369
|
+
interface SyncResult {
|
|
370
|
+
applied: boolean;
|
|
371
|
+
hash: string;
|
|
372
|
+
objects?: {
|
|
373
|
+
created: number;
|
|
374
|
+
updated: number;
|
|
375
|
+
deleted: number;
|
|
376
|
+
};
|
|
377
|
+
views?: {
|
|
378
|
+
created: number;
|
|
379
|
+
updated: number;
|
|
380
|
+
adopted: number;
|
|
381
|
+
released: number;
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* What a sync carries besides the source itself.
|
|
386
|
+
*
|
|
387
|
+
* `resolutions` resolve the renames and retypes the server refuses as
|
|
388
|
+
* ambiguous (`SCHEMA_AMBIGUOUS_CHANGE`), which names the exact entries to
|
|
389
|
+
* send. They travel outside the hash: a source whose hash is already applied
|
|
390
|
+
* is not pushed, and the resolutions with it. A server older than
|
|
391
|
+
* `resolutions` answers 400 `Unknown source payload fields` — upgrade the
|
|
392
|
+
* client and the server together.
|
|
393
|
+
*/
|
|
394
|
+
interface SyncOptions {
|
|
395
|
+
resolutions?: SchemaSourceResolution[];
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* The two verbs a sync needs, typed non-generically: the real `Transport`
|
|
399
|
+
* satisfies it, and so does a hand-rolled stub, which a `Pick` of the generic
|
|
400
|
+
* signatures would reject.
|
|
401
|
+
*/
|
|
402
|
+
interface SyncTransport {
|
|
403
|
+
get(path: string): Promise<unknown>;
|
|
404
|
+
put(path: string, body?: unknown): Promise<unknown>;
|
|
405
|
+
}
|
|
406
|
+
/** GET the remote summary; PUT when the hash or the presentation differs, or the source was never pushed. */
|
|
407
|
+
declare function syncSchemaSource(transport: SyncTransport, declared: SchemaSource, options?: SyncOptions): Promise<SyncResult>;
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/create-standards.d.ts
|
|
410
|
+
type NameOf<B> = B extends {
|
|
411
|
+
_types: {
|
|
412
|
+
name: infer N extends string;
|
|
413
|
+
};
|
|
414
|
+
} ? N : never;
|
|
415
|
+
/** One typed query per object of a source, keyed by the camelCase of its name. */
|
|
416
|
+
type ObjectsOf<TObjects extends readonly ObjectBuilderLike$1[]> = { readonly [B in TObjects[number] as CamelCase<NameOf<B>>]: RecordsQuery<B>; };
|
|
417
|
+
interface Standards {
|
|
418
|
+
schema: {
|
|
419
|
+
sync(source: SchemaSource, options?: SyncOptions): Promise<SyncResult>;
|
|
420
|
+
};
|
|
421
|
+
/** Untyped access to an object the code does not declare (created at runtime). */
|
|
422
|
+
from(objectName: string): UntypedRecordsQuery;
|
|
423
|
+
/** Upload files (private or public) and read them back through signed URLs. */
|
|
424
|
+
files: FilesApi;
|
|
425
|
+
/** Attach uploads to a record's document attribute and list a document's files. */
|
|
426
|
+
documents: DocumentsApi;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* The options `createStandards` takes. Every field of `TransportConfig` is
|
|
430
|
+
* optional here: on the server a missing `baseUrl` / `apiKey` is read from
|
|
431
|
+
* `STANDARDS_API_URL` / `STANDARDS_API_KEY`, and an explicit value always wins.
|
|
432
|
+
*/
|
|
433
|
+
type StandardsConfig = Partial<TransportConfig>;
|
|
434
|
+
declare function createStandards<const TObjects extends readonly ObjectBuilderLike$1[]>(config: StandardsConfig & {
|
|
435
|
+
schema: SchemaSource<TObjects>;
|
|
436
|
+
}): Standards & ObjectsOf<TObjects>;
|
|
437
|
+
declare function createStandards(config?: StandardsConfig): Standards;
|
|
438
|
+
//#endregion
|
|
439
|
+
export { UploadFileInput as A, FileInput as B, UpdateManyResult as C, FilesTransport as D, FilesApi as E, AttachToRecordInput as F, SchemaSource as G, IconName as H, AttachToRecordResult as I, defineSchemaSource as J, SchemaSourceConfig as K, DocumentsApi as L, UploadPublicFileInput as M, UploadedFile as N, PrivateUploadedFile as O, createFilesApi as P, DocumentsTransport as R, UpdateManyError as S, CamelCase as T, ObjectBuilderLike$1 as U, UploadTimeoutOption as V, ResolvedSchemaSource as W, resolveSchemaSource as X, hashSchemaSource as Y, sourceIdFromPackageName as Z, RecordsExportOptions as _, SchemaSourceSummary as a, UntypedRecordsQuery as b, syncSchemaSource as c, DeleteManyError as d, DeleteManyResult as f, RecordsAggregateOptions as g, RecordUpdate as h, createStandards as i, UploadPrivateFileInput as j, PublicUploadedFile as k, CreateManyError as l, RecordInput as m, Standards as n, SyncOptions as o, FetchResult as p, ViewInput as q, StandardsConfig as r, SyncResult as s, ObjectsOf as t, CreateManyResult as u, RecordsQuery as v, createRecordsQuery as w, UpdateManyEntry as x, UntypedBuilder as y, createDocumentsApi as z };
|