@php-skir/skir-client 1.0.19
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 +21 -0
- package/README.md +40 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/serializer_tester.d.ts +18 -0
- package/dist/cjs/serializer_tester.d.ts.map +1 -0
- package/dist/cjs/serializer_tester.js +124 -0
- package/dist/cjs/serializer_tester.js.map +1 -0
- package/dist/cjs/skir-client.d.ts +1051 -0
- package/dist/cjs/skir-client.d.ts.map +1 -0
- package/dist/cjs/skir-client.js +2673 -0
- package/dist/cjs/skir-client.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/serializer_tester.d.ts +18 -0
- package/dist/esm/serializer_tester.d.ts.map +1 -0
- package/dist/esm/serializer_tester.js +119 -0
- package/dist/esm/serializer_tester.js.map +1 -0
- package/dist/esm/skir-client.d.ts +1051 -0
- package/dist/esm/skir-client.d.ts.map +1 -0
- package/dist/esm/skir-client.js +2655 -0
- package/dist/esm/skir-client.js.map +1 -0
- package/package.json +66 -0
- package/src/serializer_tester.ts +185 -0
- package/src/skir-client.ts +4141 -0
|
@@ -0,0 +1,1051 @@
|
|
|
1
|
+
import type { Express as ExpressApp, json as ExpressJson, raw as ExpressRaw, Request as ExpressRequest, text as ExpressText } from "express";
|
|
2
|
+
/**
|
|
3
|
+
* A single moment in time represented in a platform-independent format, with a
|
|
4
|
+
* precision of one millisecond.
|
|
5
|
+
*
|
|
6
|
+
* A `Timestamp` object can represent a maximum of ±8,640,000,000,000,000
|
|
7
|
+
* milliseconds, or ±100,000,000 (one hundred million) days, relative to the
|
|
8
|
+
* Unix epoch. This is the range from April 20, 271821 BC to September 13,
|
|
9
|
+
* 275760 AD.
|
|
10
|
+
*
|
|
11
|
+
* Unlike the Javascript built-in `Date` type, a `Timestamp` is immutable.
|
|
12
|
+
* Like a `Date`, a `Timestamp` object does not contain a timezone.
|
|
13
|
+
*/
|
|
14
|
+
export declare class Timestamp {
|
|
15
|
+
/** Number of milliseconds ellapsed since the Unix epoch. */
|
|
16
|
+
readonly unixMillis: number;
|
|
17
|
+
/**
|
|
18
|
+
* Returns a `Timestamp` representing the same moment in time as the given
|
|
19
|
+
* Javascript `Date` object.
|
|
20
|
+
*
|
|
21
|
+
* @throws if the given `Date` object has a timestamp value of NaN
|
|
22
|
+
*/
|
|
23
|
+
static from(date: Date): Timestamp;
|
|
24
|
+
/**
|
|
25
|
+
* Creates a `Timestamp` object from a number of milliseconds from the Unix
|
|
26
|
+
* epoch.
|
|
27
|
+
*
|
|
28
|
+
* If the given number if outside the valid range (±8,640,000,000,000,000),
|
|
29
|
+
* this function will return `Timestamp.MAX` or `Timestamp.MIN` depending on
|
|
30
|
+
* the sign of the number.
|
|
31
|
+
*
|
|
32
|
+
* @throws if the given number is NaN
|
|
33
|
+
*/
|
|
34
|
+
static fromUnixMillis(unixMillis: number): Timestamp;
|
|
35
|
+
/**
|
|
36
|
+
* Creates a `Timestamp` object from a number of seconds from the Unix epoch.
|
|
37
|
+
*
|
|
38
|
+
* If the given number if outside the valid range (±8,640,000,000,000), this
|
|
39
|
+
* function will return `Timestamp.MAX` or `Timestamp.MIN` depending on the
|
|
40
|
+
* sign of the number.
|
|
41
|
+
*
|
|
42
|
+
* @throws if the given number is NaN
|
|
43
|
+
*/
|
|
44
|
+
static fromUnixSeconds(unixSeconds: number): Timestamp;
|
|
45
|
+
/**
|
|
46
|
+
* Parses a date in the date time string format.
|
|
47
|
+
*
|
|
48
|
+
* @throws if the given string is not a date in the date time string format
|
|
49
|
+
* @see https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-date-time-string-format
|
|
50
|
+
*/
|
|
51
|
+
static parse(date: string): Timestamp;
|
|
52
|
+
/** Returns a `Timestamp` representing the current moment in time. */
|
|
53
|
+
static now(): Timestamp;
|
|
54
|
+
/** Thursday, 1 January 1970. */
|
|
55
|
+
static readonly UNIX_EPOCH: Timestamp;
|
|
56
|
+
/**
|
|
57
|
+
* Earliest moment in time representable as a `Timestamp`, namely April 20,
|
|
58
|
+
* 271821 BC.
|
|
59
|
+
*
|
|
60
|
+
* @see https://262.ecma-international.org/5.1/#sec-15.9.1.1
|
|
61
|
+
*/
|
|
62
|
+
static readonly MIN: Timestamp;
|
|
63
|
+
/**
|
|
64
|
+
* Latest moment in time representable as a `Timestamp`, namely September 13,
|
|
65
|
+
* 275760 AD.
|
|
66
|
+
*
|
|
67
|
+
* @see https://262.ecma-international.org/5.1/#sec-15.9.1.1
|
|
68
|
+
*/
|
|
69
|
+
static readonly MAX: Timestamp;
|
|
70
|
+
private constructor();
|
|
71
|
+
/** Number of seconds ellapsed since the Unix epoch. */
|
|
72
|
+
get unixSeconds(): number;
|
|
73
|
+
/**
|
|
74
|
+
* Returns a `Date` object representing the same moment in time as this
|
|
75
|
+
* `Timestamp`.
|
|
76
|
+
*/
|
|
77
|
+
toDate(): Date;
|
|
78
|
+
toString(): string;
|
|
79
|
+
}
|
|
80
|
+
/** An immutable array of bytes. */
|
|
81
|
+
export declare class ByteString {
|
|
82
|
+
private readonly arrayBuffer;
|
|
83
|
+
private readonly byteOffset;
|
|
84
|
+
/** The length of this byte string. */
|
|
85
|
+
readonly byteLength: number;
|
|
86
|
+
/**
|
|
87
|
+
* Returns an immutable byte string containing all the bytes of `input` from
|
|
88
|
+
* `start`, inclusive, up to `end`, exclusive.
|
|
89
|
+
*
|
|
90
|
+
* If `input` is an `ArrayBuffer`, this function copies the bytes. Otherwise
|
|
91
|
+
* this function returns a sliced view of the input `ByteString`.
|
|
92
|
+
*
|
|
93
|
+
* @example <caption>Copy an array buffer into a byte string</caption>
|
|
94
|
+
* const byteString = ByteString.sliceOf(arrayBuffer);
|
|
95
|
+
*/
|
|
96
|
+
static sliceOf(input: ArrayBuffer | SharedArrayBuffer | ByteString, start?: number, end?: number): ByteString;
|
|
97
|
+
/**
|
|
98
|
+
* Decodes a Base64 string, which can be obtained by calling `toBase64()`.
|
|
99
|
+
*
|
|
100
|
+
* @throws if the given string is not a valid Base64 string.
|
|
101
|
+
* @see https://en.wikipedia.org/wiki/Base64
|
|
102
|
+
*/
|
|
103
|
+
static fromBase64(base64: string): ByteString;
|
|
104
|
+
/**
|
|
105
|
+
* Decodes a hexadecimal string, which can be obtained by calling
|
|
106
|
+
* `toBase16()`.
|
|
107
|
+
*
|
|
108
|
+
* @throws if the given string is not a valid Base64 string.
|
|
109
|
+
*/
|
|
110
|
+
static fromBase16(base16: string): ByteString;
|
|
111
|
+
/** An empty byte string. */
|
|
112
|
+
static readonly EMPTY: ByteString;
|
|
113
|
+
/** Copies the contents of this byte string into the given array buffer. */
|
|
114
|
+
copyTo(target: ArrayBuffer, targetOffset?: number): void;
|
|
115
|
+
/** Copies the contents of this byte string into a new array buffer. */
|
|
116
|
+
toBuffer(): ArrayBuffer;
|
|
117
|
+
/**
|
|
118
|
+
* Encodes this byte string into a Base64 string.
|
|
119
|
+
*
|
|
120
|
+
* @see https://en.wikipedia.org/wiki/Base64
|
|
121
|
+
*/
|
|
122
|
+
toBase64(): string;
|
|
123
|
+
/** Encodes this byte string into a hexadecimal string. */
|
|
124
|
+
toBase16(): string;
|
|
125
|
+
at(index: number): number | undefined;
|
|
126
|
+
toString(): string;
|
|
127
|
+
private constructor();
|
|
128
|
+
private readonly uint8Array;
|
|
129
|
+
}
|
|
130
|
+
/** A read-only JSON value. */
|
|
131
|
+
export type Json = null | boolean | number | string | readonly Json[] | Readonly<{
|
|
132
|
+
[name: string]: Json;
|
|
133
|
+
}>;
|
|
134
|
+
/**
|
|
135
|
+
* Resolves to the generated mutable class for a struct.
|
|
136
|
+
* The type parameter is the generated frozen class.
|
|
137
|
+
*/
|
|
138
|
+
export type MutableForm<Frozen> = Frozen extends _FrozenBase ? ReturnType<Frozen["toMutable"]> & Freezable<Frozen> : Freezable<Frozen>;
|
|
139
|
+
/** Result of encoding a struct using binary encoding format. */
|
|
140
|
+
export interface BinaryForm {
|
|
141
|
+
/** Length (in bytes) of the binary form. */
|
|
142
|
+
readonly byteLength: number;
|
|
143
|
+
/** Copies the contents of the binary form into the given array buffer. */
|
|
144
|
+
copyTo(target: ArrayBuffer, offset?: number): void;
|
|
145
|
+
/** Copies the contents of this byte string into a new array buffer. */
|
|
146
|
+
toBuffer(): ArrayBuffer;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* When using the JSON serialization format, you can chose between two flavors.
|
|
150
|
+
*
|
|
151
|
+
* The dense flavor is the default flavor and is preferred in most cases.
|
|
152
|
+
* Structs are converted to JSON arrays, where the number of each field
|
|
153
|
+
* corresponds to the index of the value in the array. This results in a more
|
|
154
|
+
* compact representation than when using JSON objects, and this makes
|
|
155
|
+
* serialization and deserialization a bit faster. Because field names are left
|
|
156
|
+
* out of the JSON, it is a representation which allows persistence: you can
|
|
157
|
+
* safely rename a field in a '.skir' file without breaking backwards
|
|
158
|
+
* compatibility.
|
|
159
|
+
* One con of this representation is that it is harder to tell, just by looking
|
|
160
|
+
* at the JSON, what field of the struct each value in the array corresponds to.
|
|
161
|
+
*
|
|
162
|
+
* When using the readable flavor, structs are converted to JSON objects. The
|
|
163
|
+
* name of each field in the '.skir' file is used as-is in the JSON. This
|
|
164
|
+
* results in a representation which is much more readable by humans, but also
|
|
165
|
+
* not suited for persistence: when you rename a field in a '.skir' file, you
|
|
166
|
+
* will no lonnger be able to deserialize old JSONs.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* const jane = Person.create({firstName: "Jane", lastName: "Doe"});
|
|
170
|
+
*
|
|
171
|
+
* console.log(Person.serializer.toJson(jane, "dense"));
|
|
172
|
+
* // Output: ["Jane","Doe"]
|
|
173
|
+
*
|
|
174
|
+
* console.log(Person.serializer.toJson(jane));
|
|
175
|
+
* // Output: ["Jane","Doe"]
|
|
176
|
+
*
|
|
177
|
+
* console.log(Person.serializer.toJson(jane, "readable"));
|
|
178
|
+
* // Output: {
|
|
179
|
+
* // "firstName": "Jane",
|
|
180
|
+
* // "lastName": "Doe"
|
|
181
|
+
* // }
|
|
182
|
+
*/
|
|
183
|
+
export type JsonFlavor = "dense" | "readable";
|
|
184
|
+
/**
|
|
185
|
+
* Serializes and deserializes instances of `T`. Supports two serialization
|
|
186
|
+
* formats: JSON and binary.
|
|
187
|
+
*
|
|
188
|
+
* All deserialization methods return a deeply-immutable `T`. If `T` is the
|
|
189
|
+
* generated frozen class for a struct, all serialization methods accept either
|
|
190
|
+
* a `T` or a `T.Mutable`.
|
|
191
|
+
*
|
|
192
|
+
* Do NOT create your own `Serializer` implementation. Only use implementations
|
|
193
|
+
* provided by Skir.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* let jane = Person.create({firstName: "Jane", lastName: "Doe"});
|
|
197
|
+
* const json = Person.serializer.toJson(jane);
|
|
198
|
+
* jane = Person.serializer.fromJson(json);
|
|
199
|
+
* expect(jane.firstName).toBe("Jane");
|
|
200
|
+
*/
|
|
201
|
+
export interface Serializer<T> {
|
|
202
|
+
/**
|
|
203
|
+
* Converts back the given stringified JSON to `T`.
|
|
204
|
+
* Works with both [flavors]{@link JsonFlavor} of JSON.
|
|
205
|
+
*
|
|
206
|
+
* Pass in "keep-unrecognized-values" if and only if the input JSON comes
|
|
207
|
+
* from a trusted program which might have been built from more recent
|
|
208
|
+
* source files.
|
|
209
|
+
*/
|
|
210
|
+
fromJsonCode(code: string, keep?: "keep-unrecognized-values"): T;
|
|
211
|
+
/**
|
|
212
|
+
* Converts back the given JSON to `T`.
|
|
213
|
+
* Works with both [flavors]{@link JsonFlavor} of JSON.
|
|
214
|
+
*
|
|
215
|
+
* Pass in "keep-unrecognized-values" if and only if the input JSON comes
|
|
216
|
+
* from a trusted program which might have been built from more recent
|
|
217
|
+
* source files.
|
|
218
|
+
*/
|
|
219
|
+
fromJson(json: Json, keep?: "keep-unrecognized-values"): T;
|
|
220
|
+
/**
|
|
221
|
+
* Converts back the given binary form to `T`.
|
|
222
|
+
*
|
|
223
|
+
* Pass in "keep-unrecognized-values" if and only if the input JSON comes
|
|
224
|
+
* from a trusted program which might have been built from more recent
|
|
225
|
+
* source files.
|
|
226
|
+
*/
|
|
227
|
+
fromBytes(bytes: ArrayBuffer, keep?: "keep-unrecognized-values"): T;
|
|
228
|
+
/**
|
|
229
|
+
* Converts the given `T` to JSON and returns the stringified JSON. Same as
|
|
230
|
+
* calling `JSON.stringify()` on the result of `toJson()`.
|
|
231
|
+
*
|
|
232
|
+
* @param flavor dense or readable, defaults to dense
|
|
233
|
+
* @see JsonFlavor
|
|
234
|
+
*/
|
|
235
|
+
toJsonCode(input: T | MutableForm<T>, flavor?: JsonFlavor): string;
|
|
236
|
+
/**
|
|
237
|
+
* Converts the given `T` to JSON. If you only need the stringified JSON, call
|
|
238
|
+
* `toJsonCode()` instead.
|
|
239
|
+
*
|
|
240
|
+
* @param flavor dense or readable, defaults to dense
|
|
241
|
+
* @see JsonFlavor
|
|
242
|
+
*/
|
|
243
|
+
toJson(input: T | MutableForm<T>, flavor?: JsonFlavor): Json;
|
|
244
|
+
/** Converts the given `T` to binary format. */
|
|
245
|
+
toBytes(input: T | MutableForm<T>): BinaryForm;
|
|
246
|
+
/** An object describing the type `T`. Enables reflective programming. */
|
|
247
|
+
typeDescriptor: TypeDescriptorSpecialization<T>;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Returns a serializer of instances of the given Skir primitive type.
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* expect(
|
|
254
|
+
* primitiveSerializer("string").toJsonCode("foo")
|
|
255
|
+
* ).toBe(
|
|
256
|
+
* '"foo"'
|
|
257
|
+
* );
|
|
258
|
+
*/
|
|
259
|
+
export declare function primitiveSerializer<P extends keyof PrimitiveTypes>(primitiveType: P): Serializer<PrimitiveTypes[P]>;
|
|
260
|
+
/**
|
|
261
|
+
* Returns a serializer of arrays of `Item`s.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* expect(
|
|
265
|
+
* arraySerializer(User.serializer).toJsonCode([JANE, JOE])
|
|
266
|
+
* ).toBe(
|
|
267
|
+
* '[["jane"],["joe"]]'
|
|
268
|
+
* );
|
|
269
|
+
*/
|
|
270
|
+
export declare function arraySerializer<Item>(item: Serializer<Item>, keyChain?: string): Serializer<ReadonlyArray<Item>>;
|
|
271
|
+
/** Returns a serializer of nullable `T`s. */
|
|
272
|
+
export declare function optionalSerializer<T>(other: Serializer<T>): Serializer<T | null>;
|
|
273
|
+
/**
|
|
274
|
+
* Describes the type `T`, where `T` is the TypeScript equivalent of a Skir
|
|
275
|
+
* type. Enables reflective programming.
|
|
276
|
+
*
|
|
277
|
+
* Every `TypeDescriptor` instance has a `kind` field which can take one of
|
|
278
|
+
* these 5 values: `"primitive"`, `"optional"`, `"array"`, `"struct"`, `"enum"`.
|
|
279
|
+
*/
|
|
280
|
+
export type TypeDescriptor<T = unknown> = OptionalDescriptor<T> | ArrayDescriptor<T> | StructDescriptor<T> | EnumDescriptor<T> | PrimitiveDescriptor;
|
|
281
|
+
/** Specialization of `TypeDescriptor<T>` when `T` is known. */
|
|
282
|
+
export type TypeDescriptorSpecialization<T> = [
|
|
283
|
+
T
|
|
284
|
+
] extends [_FrozenBase] ? StructDescriptor<T> : [T] extends [_EnumBase] ? EnumDescriptor<T> : TypeDescriptor<T>;
|
|
285
|
+
interface TypeDescriptorBase {
|
|
286
|
+
/** Returns the JSON representation of this `TypeDescriptor`. */
|
|
287
|
+
asJson(): Json;
|
|
288
|
+
/**
|
|
289
|
+
* Returns the JSON code representation of this `TypeDescriptor`.
|
|
290
|
+
* Same as calling `JSON.stringify()` on the result of `asJson()`.
|
|
291
|
+
*/
|
|
292
|
+
asJsonCode(): string;
|
|
293
|
+
/**
|
|
294
|
+
* Converts from one serialized form to another.
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* const denseJson = User.serializer.toJson(user, "dense");
|
|
298
|
+
* expect(
|
|
299
|
+
* User.serializer.typeDescriptor.transform(denseJson, "readable")
|
|
300
|
+
* ).toMatch(
|
|
301
|
+
* User.serializer.toJson(user, "readable")
|
|
302
|
+
* );
|
|
303
|
+
*/
|
|
304
|
+
transform(json_or_bytes: Json | ArrayBuffer, out: JsonFlavor): Json;
|
|
305
|
+
transform(json: Json, out: "bytes"): ArrayBuffer;
|
|
306
|
+
transform(json_or_bytes: Json | ArrayBuffer, out: JsonFlavor | "bytes"): Json | ArrayBuffer;
|
|
307
|
+
}
|
|
308
|
+
/** Describes a primitive Skir type. */
|
|
309
|
+
export interface PrimitiveDescriptor extends TypeDescriptorBase {
|
|
310
|
+
kind: "primitive";
|
|
311
|
+
primitive: keyof PrimitiveTypes;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* An interface mapping a primitive Skir type to the corresponding TypeScript
|
|
315
|
+
* type.
|
|
316
|
+
*/
|
|
317
|
+
export interface PrimitiveTypes {
|
|
318
|
+
bool: boolean;
|
|
319
|
+
int32: number;
|
|
320
|
+
int64: bigint;
|
|
321
|
+
hash64: bigint;
|
|
322
|
+
float32: number;
|
|
323
|
+
float64: number;
|
|
324
|
+
timestamp: Timestamp;
|
|
325
|
+
string: string;
|
|
326
|
+
bytes: ByteString;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Describes an optional type. In a '.skir' file, an optional type is
|
|
330
|
+
* represented with a question mark at the end of another type.
|
|
331
|
+
*/
|
|
332
|
+
export interface OptionalDescriptor<T> extends TypeDescriptorBase {
|
|
333
|
+
readonly kind: "optional";
|
|
334
|
+
/** Describes the other (non-optional) type. */
|
|
335
|
+
readonly otherType: TypeDescriptor<NonNullable<T>>;
|
|
336
|
+
}
|
|
337
|
+
/** Describes an array type. */
|
|
338
|
+
export interface ArrayDescriptor<T> extends TypeDescriptorBase {
|
|
339
|
+
readonly kind: "array";
|
|
340
|
+
/** Describes the type of the array items. */
|
|
341
|
+
readonly itemType: TypeDescriptor<T extends ReadonlyArray<infer Item> ? Item : unknown>;
|
|
342
|
+
readonly keyChain?: string;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Describes a Skir struct.
|
|
346
|
+
* The type parameter `T` refers to the generated frozen class for the struct.
|
|
347
|
+
*/
|
|
348
|
+
export interface StructDescriptor<T = unknown> extends TypeDescriptorBase {
|
|
349
|
+
readonly kind: "struct";
|
|
350
|
+
/** Name of the struct as specified in the '.skir' file. */
|
|
351
|
+
readonly name: string;
|
|
352
|
+
/**
|
|
353
|
+
* A string containing all the names in the hierarchic sequence above and
|
|
354
|
+
* including the struct. For example: "Foo.Bar" if "Bar" is nested within a
|
|
355
|
+
* type called "Foo", or simply "Bar" if "Bar" is defined at the top-level of
|
|
356
|
+
* the module.
|
|
357
|
+
*/
|
|
358
|
+
readonly qualifiedName: string;
|
|
359
|
+
/**
|
|
360
|
+
* Path to the module where the struct is defined, relative to the root of the
|
|
361
|
+
* project.
|
|
362
|
+
*/
|
|
363
|
+
readonly modulePath: string;
|
|
364
|
+
/**
|
|
365
|
+
* If the struct is nested within another type, the descriptor for that type.
|
|
366
|
+
* Undefined if the struct is defined at the top-level of the module.
|
|
367
|
+
*/
|
|
368
|
+
readonly parentType: StructDescriptor | EnumDescriptor | undefined;
|
|
369
|
+
/** The fields of the struct in the order they appear in the '.skir' file. */
|
|
370
|
+
readonly fields: ReadonlyArray<StructField<T>>;
|
|
371
|
+
/** The field numbers marked as removed. */
|
|
372
|
+
readonly removedNumbers: ReadonlySet<number>;
|
|
373
|
+
/**
|
|
374
|
+
* Looks up a field. The key can be one of: the field name (e.g. "user_id");
|
|
375
|
+
* the name of the property in the generated class (e.g. "userId"), the field
|
|
376
|
+
* number.
|
|
377
|
+
*
|
|
378
|
+
* The return type is `StructField<T> | undefined` unless the key is known at
|
|
379
|
+
* compile-time to be the name of the property in the generated class, in
|
|
380
|
+
* which case it is `StructField<T>`.
|
|
381
|
+
*/
|
|
382
|
+
getField<K extends string | number>(key: K): StructFieldResult<T, K>;
|
|
383
|
+
/**
|
|
384
|
+
* Returns a new instance of the generated mutable class for a struct.
|
|
385
|
+
* Performs a shallow copy of `initializer` if `initializer` is specified.
|
|
386
|
+
*/
|
|
387
|
+
newMutable(initializer?: T | MutableForm<T>): MutableForm<T>;
|
|
388
|
+
}
|
|
389
|
+
/** Field of a Skir struct. */
|
|
390
|
+
export interface StructField<Struct = unknown, Value = unknown> {
|
|
391
|
+
/** Field name as specified in the '.skir' file, e.g. "user_id". */
|
|
392
|
+
readonly name: string;
|
|
393
|
+
/** Name of the property in the generated class, e.g. "userId". */
|
|
394
|
+
readonly property: string;
|
|
395
|
+
/** Field number. */
|
|
396
|
+
readonly number: number;
|
|
397
|
+
/** Describes the field type. */
|
|
398
|
+
readonly type: TypeDescriptor<Value>;
|
|
399
|
+
/**
|
|
400
|
+
* Documentation for this struct field, specified as doc comments in the
|
|
401
|
+
* '.skir' file. May be empty.
|
|
402
|
+
*/
|
|
403
|
+
doc: string;
|
|
404
|
+
/** Extracts the value of the field from the given struct. */
|
|
405
|
+
get(struct: Struct | MutableForm<Struct>): Value;
|
|
406
|
+
/** Assigns the given value to the field of the given struct. */
|
|
407
|
+
set(struct: MutableForm<Struct>, value: Value): void;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Return type of the `StructDescriptor.getField` method. If the argument is
|
|
411
|
+
* known at compile-time to be the name of a property of the generated class,
|
|
412
|
+
* resolves to `StructField<Struct>`. Otherwise, resolves to
|
|
413
|
+
* `StructField<Struct> | undefined`.
|
|
414
|
+
*
|
|
415
|
+
* @example <caption>The field is kown at compile-time</caption>
|
|
416
|
+
* const fieldNumber: number =
|
|
417
|
+
* User.serializer.typeDescriptor.getField("userId").number;
|
|
418
|
+
*
|
|
419
|
+
* @example <caption>The field is not kown at compile-time</caption>
|
|
420
|
+
* const fieldNumber: number | undefined =
|
|
421
|
+
* User.serializer.typeDescriptor.getField(variable)?.number;
|
|
422
|
+
*/
|
|
423
|
+
export type StructFieldResult<Struct, Key extends string | number> = StructField<Struct> | (Struct extends _FrozenBase ? Key extends keyof NonNullable<Struct[typeof _INITIALIZER]> ? never : undefined : undefined);
|
|
424
|
+
/** Describes a Skir enum. */
|
|
425
|
+
export interface EnumDescriptor<T = unknown> extends TypeDescriptorBase {
|
|
426
|
+
readonly kind: "enum";
|
|
427
|
+
/** Name of the enum as specified in the '.skir' file. */
|
|
428
|
+
readonly name: string;
|
|
429
|
+
/**
|
|
430
|
+
* A string containing all the names in the hierarchic sequence above and
|
|
431
|
+
* including the enum. For example: "Foo.Bar" if "Bar" is nested within a type
|
|
432
|
+
* called "Foo", or simply "Bar" if "Bar" is defined at the top-level of the
|
|
433
|
+
* module.
|
|
434
|
+
*/
|
|
435
|
+
readonly qualifiedName: string;
|
|
436
|
+
/**
|
|
437
|
+
* Path to the module where the enum is defined, relative to the root of the
|
|
438
|
+
* project.
|
|
439
|
+
*/
|
|
440
|
+
readonly modulePath: string;
|
|
441
|
+
/**
|
|
442
|
+
* If the enum is nested within another type, the descriptor for that type.
|
|
443
|
+
* Undefined if the struct is defined at the top-level of the module.
|
|
444
|
+
*/
|
|
445
|
+
readonly parentType: StructDescriptor | EnumDescriptor | undefined;
|
|
446
|
+
/**
|
|
447
|
+
* Includes the UNKNOWN variant, followed by the other variants in the order
|
|
448
|
+
* they appear in the '.skir' file.
|
|
449
|
+
*/
|
|
450
|
+
readonly variants: ReadonlyArray<EnumVariant<T>>;
|
|
451
|
+
/** The variant numbers marked as removed. */
|
|
452
|
+
readonly removedNumbers: ReadonlySet<number>;
|
|
453
|
+
/**
|
|
454
|
+
* Looks up a variant. The key can be one of the variant name or the variant
|
|
455
|
+
* number.
|
|
456
|
+
*
|
|
457
|
+
* The return type is `EnumVariant<T> | undefined` unless the key is known at
|
|
458
|
+
* compile-time to be a variant name of the enum, in which case it is
|
|
459
|
+
* `EnumVariant<T>`.
|
|
460
|
+
*/
|
|
461
|
+
getVariant<K extends string | number>(key: K): EnumVariantResult<T, K>;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Variant of a Skir enum. Variants which don't hold any value are called
|
|
465
|
+
* constant variants. Their name is always in UPPER_CASE. Variants which hold
|
|
466
|
+
* value of a given type are called wrapper variants, and their name is always
|
|
467
|
+
* in lower_case.
|
|
468
|
+
*/
|
|
469
|
+
export type EnumVariant<Enum = unknown> = EnumConstantVariant<Enum> | EnumWrapperVariant<Enum, unknown>;
|
|
470
|
+
/** Constant variant of a Skir enum. */
|
|
471
|
+
export interface EnumConstantVariant<Enum = unknown> {
|
|
472
|
+
/**
|
|
473
|
+
* Variant name as specified in the '.skir' file, e.g. "MONDAY".
|
|
474
|
+
* Always in UPPER_CASE format.
|
|
475
|
+
*/
|
|
476
|
+
readonly name: string;
|
|
477
|
+
/** Variant number. */
|
|
478
|
+
readonly number: number;
|
|
479
|
+
/** The instance of the generated class which corresponds to this variant. */
|
|
480
|
+
readonly constant: Enum;
|
|
481
|
+
/** Always undefined, unlike the `type` field of `EnumWrapperVariant`. */
|
|
482
|
+
readonly type?: undefined;
|
|
483
|
+
/**
|
|
484
|
+
* Documentation for this enum variant, specified as doc comments in the
|
|
485
|
+
* '.skir' file. May be empty.
|
|
486
|
+
*/
|
|
487
|
+
readonly doc: string;
|
|
488
|
+
}
|
|
489
|
+
/** Wrapper variant of a Skir enum. */
|
|
490
|
+
export interface EnumWrapperVariant<Enum = unknown, Value = unknown> {
|
|
491
|
+
/**
|
|
492
|
+
* Variant name as specified in the '.skir' file, e.g. "v4".
|
|
493
|
+
* Always in lower_case format.
|
|
494
|
+
*/
|
|
495
|
+
readonly name: string;
|
|
496
|
+
/** Variant number. */
|
|
497
|
+
readonly number: number;
|
|
498
|
+
/** Describes the type of the value held by the variant. */
|
|
499
|
+
readonly type: TypeDescriptor<Value>;
|
|
500
|
+
/** Always undefined, unlike the `type` field of `EnumConstantVariant`. */
|
|
501
|
+
readonly constant?: undefined;
|
|
502
|
+
/**
|
|
503
|
+
* Documentation for this enum variant, specified as doc comments in the
|
|
504
|
+
* '.skir' file. May be empty.
|
|
505
|
+
*/
|
|
506
|
+
readonly doc: string;
|
|
507
|
+
/**
|
|
508
|
+
* Extracts the value held by the given enum instance if it matches this
|
|
509
|
+
* enum variant. Returns undefined otherwise.
|
|
510
|
+
*/
|
|
511
|
+
get(e: Enum): Value | unknown;
|
|
512
|
+
/**
|
|
513
|
+
* Returns a new enum instance matching this enum variant and holding the
|
|
514
|
+
* given value.
|
|
515
|
+
*/
|
|
516
|
+
wrap(value: Value): Enum;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Return type of the `EnumDescriptor.getVariant` method. If the argument is
|
|
520
|
+
* known at compile-time to be the name of variant, resolves to
|
|
521
|
+
* `EnumVariant<Enum>`. Otherwise, resolves to
|
|
522
|
+
* `EnumVariant<Struct> | undefined`.
|
|
523
|
+
*
|
|
524
|
+
* @example <caption>The variant is known at compile-time</caption>
|
|
525
|
+
* const variantNumber: number =
|
|
526
|
+
* Weekday.serializer.typeDescriptor.getVariant("MONDAY").number;
|
|
527
|
+
*
|
|
528
|
+
* @example <caption>The variant is not known at compile-time</caption>
|
|
529
|
+
* const variantNumber: number | undefined =
|
|
530
|
+
* Weekday.serializer.typeDescriptor.getVariant(variable)?.number;
|
|
531
|
+
*/
|
|
532
|
+
export type EnumVariantResult<Enum, Key extends string | number> = EnumVariant<Enum> | (Enum extends _EnumBase ? Key extends Enum["union"]["kind"] ? never : undefined : undefined);
|
|
533
|
+
/**
|
|
534
|
+
* Identifies a procedure (the "P" in "RPC") on both the client side and the
|
|
535
|
+
* server side.
|
|
536
|
+
*/
|
|
537
|
+
export interface Method<Request, Response> {
|
|
538
|
+
/** Name of the procedure as specified in the '.skir' file. */
|
|
539
|
+
name: string;
|
|
540
|
+
/**
|
|
541
|
+
* A number which uniquely identifies this procedure.
|
|
542
|
+
* When it is not specified in the '.skir' file, it is obtained by hashing the
|
|
543
|
+
* procedure name.
|
|
544
|
+
*/
|
|
545
|
+
number: number;
|
|
546
|
+
/** Serializer of request objects. */
|
|
547
|
+
requestSerializer: Serializer<Request>;
|
|
548
|
+
/** Serializer of response objects. */
|
|
549
|
+
responseSerializer: Serializer<Response>;
|
|
550
|
+
/**
|
|
551
|
+
* Documentation for this procedure, specified as doc comments in the '.skir'
|
|
552
|
+
* file. May be empty.
|
|
553
|
+
*/
|
|
554
|
+
doc: string;
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Interface implemented by both the frozen and mutable classes generated for a
|
|
558
|
+
* struct. `T` is always the generated frozen class.
|
|
559
|
+
*/
|
|
560
|
+
export interface Freezable<T> {
|
|
561
|
+
/**
|
|
562
|
+
* Returns a deeply-immutable object, either by making a copy of `this` if
|
|
563
|
+
* `this` is mutable, or by returning `this` as-is if `this` is already
|
|
564
|
+
* immutable.
|
|
565
|
+
*/
|
|
566
|
+
toFrozen(): T;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Returns a `TypeDescriptor` from its JSON representation as returned by
|
|
570
|
+
* `asJson()`.
|
|
571
|
+
*/
|
|
572
|
+
export declare function parseTypeDescriptorFromJson(json: Json): TypeDescriptor;
|
|
573
|
+
/**
|
|
574
|
+
* Returns a `TypeDescriptor` from its JSON code representation as returned by
|
|
575
|
+
* `asJsonCode()`.
|
|
576
|
+
*/
|
|
577
|
+
export declare function parseTypeDescriptorFromJsonCode(code: string): TypeDescriptor;
|
|
578
|
+
declare class UnrecognizedEnum {
|
|
579
|
+
readonly token: symbol;
|
|
580
|
+
readonly json?: Json | undefined;
|
|
581
|
+
readonly bytes?: ByteString | undefined;
|
|
582
|
+
constructor(token: symbol, json?: Json | undefined, bytes?: ByteString | undefined);
|
|
583
|
+
}
|
|
584
|
+
export declare const _EMPTY_ARRAY: readonly never[];
|
|
585
|
+
export declare function _toFrozenArray<T, Initializer>(initializers: readonly Initializer[], itemToFrozenFn?: (item: Initializer) => T): readonly T[];
|
|
586
|
+
export declare const _INITIALIZER: unique symbol;
|
|
587
|
+
export declare abstract class _FrozenBase {
|
|
588
|
+
protected constructor(privateKey: symbol);
|
|
589
|
+
toMutable(): unknown;
|
|
590
|
+
toFrozen(): this;
|
|
591
|
+
toString(): string;
|
|
592
|
+
[_INITIALIZER]: unknown;
|
|
593
|
+
}
|
|
594
|
+
export declare abstract class _EnumBase {
|
|
595
|
+
protected constructor(privateKey: symbol, kind: string, value: unknown, unrecognized?: UnrecognizedEnum);
|
|
596
|
+
readonly union: {
|
|
597
|
+
kind: unknown;
|
|
598
|
+
value: unknown;
|
|
599
|
+
};
|
|
600
|
+
toString(): string;
|
|
601
|
+
}
|
|
602
|
+
/** Metadata of an HTTP request sent by a service client. */
|
|
603
|
+
export type RequestMeta = Omit<RequestInit, "body" | "method">;
|
|
604
|
+
/** Wire format used for SkirRPC HTTP request and response bodies. */
|
|
605
|
+
export type TransportCodec = "legacy" | "cbor";
|
|
606
|
+
/** Configuration options for a SkirRPC service client. */
|
|
607
|
+
export interface ServiceClientOptions {
|
|
608
|
+
/**
|
|
609
|
+
* Codec used for RPC request and response bodies.
|
|
610
|
+
*
|
|
611
|
+
* Defaults to `legacy`, which preserves the original Skir colon-separated
|
|
612
|
+
* request body and JSON response body.
|
|
613
|
+
*/
|
|
614
|
+
transportCodec?: TransportCodec;
|
|
615
|
+
}
|
|
616
|
+
export type RawRequestBody = string | Uint8Array | ArrayBuffer;
|
|
617
|
+
/** Sends RPCs to a SkirRPC service. */
|
|
618
|
+
export declare class ServiceClient {
|
|
619
|
+
private readonly serviceUrl;
|
|
620
|
+
private readonly getRequestMetadata;
|
|
621
|
+
private readonly options;
|
|
622
|
+
constructor(serviceUrl: string, getRequestMetadata?: (m: Method<unknown, unknown>) => Promise<RequestMeta> | RequestMeta, options?: ServiceClientOptions);
|
|
623
|
+
/** Invokes the given method on the remote server through an RPC. */
|
|
624
|
+
invokeRemote<Request, Response>(method: Method<Request, Response>, request: Request, httpMethod?: "GET" | "POST"): Promise<Response>;
|
|
625
|
+
}
|
|
626
|
+
/** Raw response returned by the server. */
|
|
627
|
+
export interface RawResponse {
|
|
628
|
+
readonly data: string | Uint8Array;
|
|
629
|
+
readonly statusCode: number;
|
|
630
|
+
readonly contentType: string;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* If this error is thrown from a method implementation, the specified status
|
|
634
|
+
* code and message will be returned in the HTTP response.
|
|
635
|
+
*
|
|
636
|
+
* If any other type of exception is thrown, the response status code will be
|
|
637
|
+
* 500 (Internal Server Error).
|
|
638
|
+
*/
|
|
639
|
+
export declare class ServiceError extends Error {
|
|
640
|
+
private readonly spec;
|
|
641
|
+
constructor(spec: {
|
|
642
|
+
statusCode: 400;
|
|
643
|
+
desc: "Bad Request";
|
|
644
|
+
message?: string;
|
|
645
|
+
} | {
|
|
646
|
+
statusCode: 401;
|
|
647
|
+
desc: "Unauthorized";
|
|
648
|
+
message?: string;
|
|
649
|
+
} | {
|
|
650
|
+
statusCode: 402;
|
|
651
|
+
desc: "Payment Required";
|
|
652
|
+
message?: string;
|
|
653
|
+
} | {
|
|
654
|
+
statusCode: 403;
|
|
655
|
+
desc: "Forbidden";
|
|
656
|
+
message?: string;
|
|
657
|
+
} | {
|
|
658
|
+
statusCode: 404;
|
|
659
|
+
desc: "Not Found";
|
|
660
|
+
message?: string;
|
|
661
|
+
} | {
|
|
662
|
+
statusCode: 405;
|
|
663
|
+
desc: "Method Not Allowed";
|
|
664
|
+
message?: string;
|
|
665
|
+
} | {
|
|
666
|
+
statusCode: 406;
|
|
667
|
+
desc: "Not Acceptable";
|
|
668
|
+
message?: string;
|
|
669
|
+
} | {
|
|
670
|
+
statusCode: 407;
|
|
671
|
+
desc: "Proxy Authentication Required";
|
|
672
|
+
message?: string;
|
|
673
|
+
} | {
|
|
674
|
+
statusCode: 408;
|
|
675
|
+
desc: "Request Timeout";
|
|
676
|
+
message?: string;
|
|
677
|
+
} | {
|
|
678
|
+
statusCode: 409;
|
|
679
|
+
desc: "Conflict";
|
|
680
|
+
message?: string;
|
|
681
|
+
} | {
|
|
682
|
+
statusCode: 410;
|
|
683
|
+
desc: "Gone";
|
|
684
|
+
message?: string;
|
|
685
|
+
} | {
|
|
686
|
+
statusCode: 411;
|
|
687
|
+
desc: "Length Required";
|
|
688
|
+
message?: string;
|
|
689
|
+
} | {
|
|
690
|
+
statusCode: 412;
|
|
691
|
+
desc: "Precondition Failed";
|
|
692
|
+
message?: string;
|
|
693
|
+
} | {
|
|
694
|
+
statusCode: 413;
|
|
695
|
+
desc: "Content Too Large";
|
|
696
|
+
message?: string;
|
|
697
|
+
} | {
|
|
698
|
+
statusCode: 414;
|
|
699
|
+
desc: "URI Too Long";
|
|
700
|
+
message?: string;
|
|
701
|
+
} | {
|
|
702
|
+
statusCode: 415;
|
|
703
|
+
desc: "Unsupported Media Type";
|
|
704
|
+
message?: string;
|
|
705
|
+
} | {
|
|
706
|
+
statusCode: 416;
|
|
707
|
+
desc: "Range Not Satisfiable";
|
|
708
|
+
message?: string;
|
|
709
|
+
} | {
|
|
710
|
+
statusCode: 417;
|
|
711
|
+
desc: "Expectation Failed";
|
|
712
|
+
message?: string;
|
|
713
|
+
} | {
|
|
714
|
+
statusCode: 418;
|
|
715
|
+
desc: "I'm a teapot";
|
|
716
|
+
message?: string;
|
|
717
|
+
} | {
|
|
718
|
+
statusCode: 421;
|
|
719
|
+
desc: "Misdirected Request";
|
|
720
|
+
message?: string;
|
|
721
|
+
} | {
|
|
722
|
+
statusCode: 422;
|
|
723
|
+
desc: "Unprocessable Content";
|
|
724
|
+
message?: string;
|
|
725
|
+
} | {
|
|
726
|
+
statusCode: 423;
|
|
727
|
+
desc: "Locked";
|
|
728
|
+
message?: string;
|
|
729
|
+
} | {
|
|
730
|
+
statusCode: 424;
|
|
731
|
+
desc: "Failed Dependency";
|
|
732
|
+
message?: string;
|
|
733
|
+
} | {
|
|
734
|
+
statusCode: 425;
|
|
735
|
+
desc: "Too Early";
|
|
736
|
+
message?: string;
|
|
737
|
+
} | {
|
|
738
|
+
statusCode: 426;
|
|
739
|
+
desc: "Upgrade Required";
|
|
740
|
+
message?: string;
|
|
741
|
+
} | {
|
|
742
|
+
statusCode: 428;
|
|
743
|
+
desc: "Precondition Required";
|
|
744
|
+
message?: string;
|
|
745
|
+
} | {
|
|
746
|
+
statusCode: 429;
|
|
747
|
+
desc: "Too Many Requests";
|
|
748
|
+
message?: string;
|
|
749
|
+
} | {
|
|
750
|
+
statusCode: 431;
|
|
751
|
+
desc: "Request Header Fields Too Large";
|
|
752
|
+
message?: string;
|
|
753
|
+
} | {
|
|
754
|
+
statusCode: 451;
|
|
755
|
+
desc: "Unavailable For Legal Reasons";
|
|
756
|
+
message?: string;
|
|
757
|
+
} | {
|
|
758
|
+
statusCode: 500;
|
|
759
|
+
desc: "Internal Server Error";
|
|
760
|
+
message?: string;
|
|
761
|
+
} | {
|
|
762
|
+
statusCode: 501;
|
|
763
|
+
desc: "Not Implemented";
|
|
764
|
+
message?: string;
|
|
765
|
+
} | {
|
|
766
|
+
statusCode: 502;
|
|
767
|
+
desc: "Bad Gateway";
|
|
768
|
+
message?: string;
|
|
769
|
+
} | {
|
|
770
|
+
statusCode: 503;
|
|
771
|
+
desc: "Service Unavailable";
|
|
772
|
+
message?: string;
|
|
773
|
+
} | {
|
|
774
|
+
statusCode: 504;
|
|
775
|
+
desc: "Gateway Timeout";
|
|
776
|
+
message?: string;
|
|
777
|
+
} | {
|
|
778
|
+
statusCode: 505;
|
|
779
|
+
desc: "HTTP Version Not Supported";
|
|
780
|
+
message?: string;
|
|
781
|
+
} | {
|
|
782
|
+
statusCode: 506;
|
|
783
|
+
desc: "Variant Also Negotiates";
|
|
784
|
+
message?: string;
|
|
785
|
+
} | {
|
|
786
|
+
statusCode: 507;
|
|
787
|
+
desc: "Insufficient Storage";
|
|
788
|
+
message?: string;
|
|
789
|
+
} | {
|
|
790
|
+
statusCode: 508;
|
|
791
|
+
desc: "Loop Detected";
|
|
792
|
+
message?: string;
|
|
793
|
+
} | {
|
|
794
|
+
statusCode: 510;
|
|
795
|
+
desc: "Not Extended";
|
|
796
|
+
message?: string;
|
|
797
|
+
} | {
|
|
798
|
+
statusCode: 511;
|
|
799
|
+
desc: "Network Authentication Required";
|
|
800
|
+
message?: string;
|
|
801
|
+
});
|
|
802
|
+
toRawResponse(): RawResponse;
|
|
803
|
+
}
|
|
804
|
+
export interface RequestHandler<RequestMeta = ExpressRequest> {
|
|
805
|
+
/**
|
|
806
|
+
* Parses the content of a user request and invokes the appropriate method.
|
|
807
|
+
* If you are using ExpressJS as your web application framework, you don't
|
|
808
|
+
* need to call this method, you can simply call the
|
|
809
|
+
* `installServiceOnExpressApp()` top-level function.
|
|
810
|
+
*
|
|
811
|
+
* If the request is a GET request, pass in the decoded query string as the
|
|
812
|
+
* request's body. The query string is the part of the URL after '?', and it
|
|
813
|
+
* can be decoded with DecodeURIComponent.
|
|
814
|
+
*/
|
|
815
|
+
handleRequest(reqBody: RawRequestBody, reqMeta: RequestMeta): Promise<RawResponse>;
|
|
816
|
+
}
|
|
817
|
+
/** Configuration options for a SkirRPC service. */
|
|
818
|
+
export interface ServiceOptions<RequestMeta = ExpressRequest> {
|
|
819
|
+
/**
|
|
820
|
+
* Codec used for RPC request and response bodies.
|
|
821
|
+
*
|
|
822
|
+
* Defaults to `legacy`, which accepts the original Skir colon-separated
|
|
823
|
+
* string and JSON-object request formats and returns JSON responses.
|
|
824
|
+
*/
|
|
825
|
+
transportCodec: TransportCodec;
|
|
826
|
+
/**
|
|
827
|
+
* Whether to keep unrecognized values when deserializing requests.
|
|
828
|
+
*
|
|
829
|
+
* **WARNING:** Only enable this for data from trusted sources. Malicious
|
|
830
|
+
* actors could inject fields with IDs not yet defined in your schema. If you
|
|
831
|
+
* preserve this data and later define those IDs in a future schema version,
|
|
832
|
+
* the injected data could be deserialized as valid fields, leading to
|
|
833
|
+
* security vulnerabilities or data corruption.
|
|
834
|
+
*
|
|
835
|
+
* Defaults to `false`.
|
|
836
|
+
*/
|
|
837
|
+
keepUnrecognizedValues: boolean;
|
|
838
|
+
/**
|
|
839
|
+
* Whether the message of an unknown error (i.e. not a `ServiceError`) can be
|
|
840
|
+
* sent to the client in the response body, which can help with debugging.
|
|
841
|
+
*
|
|
842
|
+
* By default, unknown errors are masked and the client receives a generic
|
|
843
|
+
* 'server error' message with status 500. This is to prevent leaking
|
|
844
|
+
* sensitive information to the client.
|
|
845
|
+
*
|
|
846
|
+
* You can enable this if your server is internal or if you are sure that your
|
|
847
|
+
* error messages are safe to expose. By passing a predicate instead of true
|
|
848
|
+
* or false, you can control on a per-error basis whether to expose the error
|
|
849
|
+
* message; for example, you can send error messages only if the user is an
|
|
850
|
+
* admin.
|
|
851
|
+
*
|
|
852
|
+
* Defaults to `false`.
|
|
853
|
+
*/
|
|
854
|
+
canSendUnknownErrorMessage: boolean | ((errorInfo: MethodErrorInfo<RequestMeta, unknown>) => boolean);
|
|
855
|
+
/**
|
|
856
|
+
* Callback invoked whenever an error is thrown during method execution.
|
|
857
|
+
*
|
|
858
|
+
* Use this to log errors for monitoring, debugging, or alerting purposes.
|
|
859
|
+
*
|
|
860
|
+
* Defaults to function which logs the method name and error message via
|
|
861
|
+
* `console.error()`.
|
|
862
|
+
*/
|
|
863
|
+
errorLogger: (errorInfo: MethodErrorInfo<RequestMeta, unknown>) => void;
|
|
864
|
+
/**
|
|
865
|
+
* URL to the JavaScript file for the Skir Studio app.
|
|
866
|
+
*
|
|
867
|
+
* Skir Studio is a web interface for exploring and testing your SkirRPC service.
|
|
868
|
+
* It is served when the service receives a request at '${serviceUrl}?studio'.
|
|
869
|
+
*/
|
|
870
|
+
studioAppJsUrl: string;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Information about an error thrown during the execution of a method on the
|
|
874
|
+
* server side.
|
|
875
|
+
*/
|
|
876
|
+
interface MethodErrorInfo<RequestMeta, Request> {
|
|
877
|
+
/** The error thrown during the execution of the method. */
|
|
878
|
+
readonly error: any;
|
|
879
|
+
readonly method: Method<Request, unknown>;
|
|
880
|
+
readonly request: Request;
|
|
881
|
+
/**
|
|
882
|
+
* Metadata coming from the HTTP headers of the request.
|
|
883
|
+
* Undefined if the error was thrown in the execution of the function passed
|
|
884
|
+
* to `withMetaTransformer()`.
|
|
885
|
+
*/
|
|
886
|
+
readonly reqMeta: RequestMeta | undefined;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Implementation of a SkirRPC service.
|
|
890
|
+
*
|
|
891
|
+
* Usage: call `.addMethod()` to register methods, then install the service on
|
|
892
|
+
* an HTTP server either by:
|
|
893
|
+
* - calling the `installServiceOnExpressApp()` top-level function if you are
|
|
894
|
+
* using ExpressJS
|
|
895
|
+
* - writing your own implementation of `installServiceOn*()` which calls
|
|
896
|
+
* `.handleRequest()` if you are using another web application framework
|
|
897
|
+
*
|
|
898
|
+
* ## Handling Request Metadata
|
|
899
|
+
*
|
|
900
|
+
* The `RequestMeta` type parameter specifies what metadata (authentication,
|
|
901
|
+
* headers, etc.) your method implementations receive. There are two approaches:
|
|
902
|
+
*
|
|
903
|
+
* ### Approach 1: Use the framework's request type directly
|
|
904
|
+
*
|
|
905
|
+
* Set `RequestMeta` to your framework's request type (e.g., `ExpressRequest`).
|
|
906
|
+
* All method implementations will receive the full framework request object.
|
|
907
|
+
*
|
|
908
|
+
* ```typescript
|
|
909
|
+
* const service = new Service<ExpressRequest>();
|
|
910
|
+
* service.addMethod(myMethod, async (req, expressReq) => {
|
|
911
|
+
* const isAdmin = expressReq.user?.role === 'admin';
|
|
912
|
+
* // ...
|
|
913
|
+
* });
|
|
914
|
+
* installServiceOnExpressApp(app, '/api', service, text, json);
|
|
915
|
+
* ```
|
|
916
|
+
*
|
|
917
|
+
* ### Approach 2: Use a simplified custom type
|
|
918
|
+
*
|
|
919
|
+
* Set `RequestMeta` to a minimal type containing only what your service needs.
|
|
920
|
+
* Use `withMetaTransformer()` to extract this data from the framework request
|
|
921
|
+
* when installing the service.
|
|
922
|
+
*
|
|
923
|
+
* ```typescript
|
|
924
|
+
* const service = new Service<{ isAdmin: boolean }>();
|
|
925
|
+
* service.addMethod(myMethod, async (req, { isAdmin }) => {
|
|
926
|
+
* // Implementation is framework-agnostic and easy to unit test
|
|
927
|
+
* if (!isAdmin) throw new ServiceError({ statusCode: 403, desc: "Forbidden" });
|
|
928
|
+
* // ...
|
|
929
|
+
* });
|
|
930
|
+
*
|
|
931
|
+
* // Adapt to Express when installing
|
|
932
|
+
* const handler = service.withMetaTransformer((req: ExpressRequest) => ({
|
|
933
|
+
* isAdmin: req.user?.role === 'admin'
|
|
934
|
+
* }));
|
|
935
|
+
* installServiceOnExpressApp(app, '/api', handler, text, json);
|
|
936
|
+
* ```
|
|
937
|
+
*
|
|
938
|
+
* This approach decouples your service from the HTTP framework, making it easier
|
|
939
|
+
* to test and clearer about what request data it actually uses.
|
|
940
|
+
*/
|
|
941
|
+
export declare class Service<RequestMeta = ExpressRequest> implements RequestHandler<RequestMeta> {
|
|
942
|
+
constructor(options?: Partial<ServiceOptions<RequestMeta>>);
|
|
943
|
+
addMethod<Request, Response>(method: Method<Request, Response>, impl: (req: Request, reqMeta: RequestMeta) => Promise<Response>): Service<RequestMeta>;
|
|
944
|
+
handleRequest(reqBody: RawRequestBody, reqMeta: RequestMeta): Promise<RawResponse>;
|
|
945
|
+
private doHandleRequest;
|
|
946
|
+
/**
|
|
947
|
+
* Creates a request handler that extracts simplified request metadata from
|
|
948
|
+
* framework-specific request objects before passing it to this service.
|
|
949
|
+
*
|
|
950
|
+
* This decouples your service implementation from the HTTP framework, making
|
|
951
|
+
* it easier to unit test (tests don't need to mock framework objects) and
|
|
952
|
+
* making the service implementation clearer by explicitly declaring exactly
|
|
953
|
+
* what request data it needs.
|
|
954
|
+
*
|
|
955
|
+
* @param transformFn Function that extracts the necessary data from the
|
|
956
|
+
* framework-specific request object. Can be async or sync.
|
|
957
|
+
* @returns A request handler that accepts the framework-specific request type.
|
|
958
|
+
*
|
|
959
|
+
* @example
|
|
960
|
+
* ```typescript
|
|
961
|
+
* // Define a service that only needs to know if the user is an admin
|
|
962
|
+
*
|
|
963
|
+
* const service = new Service<{ isAdmin: boolean }>();
|
|
964
|
+
*
|
|
965
|
+
* service.addMethod(myMethod, async (req, { isAdmin }) => {
|
|
966
|
+
* // Implementation is framework-agnostic and easy to test
|
|
967
|
+
* if (!isAdmin) throw new ServiceError({ statusCode: 403, desc: "Forbidden" });
|
|
968
|
+
* // ...
|
|
969
|
+
* });
|
|
970
|
+
*
|
|
971
|
+
* // Adapt it to work with Express
|
|
972
|
+
* const expressHandler = service.withMetaTransformer(
|
|
973
|
+
* (req: ExpressRequest) => ({
|
|
974
|
+
* isAdmin: req.user?.role === 'admin'
|
|
975
|
+
* })
|
|
976
|
+
* );
|
|
977
|
+
* installServiceOnExpressApp(app, '/api', expressHandler, text, json);
|
|
978
|
+
* ```
|
|
979
|
+
*/
|
|
980
|
+
withMetaTransformer<OriginalRequestMeta>(transformFn: (reqMeta: OriginalRequestMeta) => RequestMeta | Promise<RequestMeta>): RequestHandler<OriginalRequestMeta>;
|
|
981
|
+
private readonly options;
|
|
982
|
+
private readonly methodImpls;
|
|
983
|
+
}
|
|
984
|
+
export declare function installServiceOnExpressApp(app: ExpressApp, queryPath: string, service: RequestHandler<ExpressRequest>, text: typeof ExpressText, json: typeof ExpressJson, raw?: typeof ExpressRaw): void;
|
|
985
|
+
interface StructSpec {
|
|
986
|
+
kind: "struct";
|
|
987
|
+
ctor: {
|
|
988
|
+
new (privateKey: symbol): unknown;
|
|
989
|
+
};
|
|
990
|
+
initFn: (target: unknown, initializer: unknown) => void;
|
|
991
|
+
name: string;
|
|
992
|
+
parentCtor?: {
|
|
993
|
+
new (): unknown;
|
|
994
|
+
};
|
|
995
|
+
doc?: string;
|
|
996
|
+
fields: readonly StructFieldSpec[];
|
|
997
|
+
removedNumbers?: readonly number[];
|
|
998
|
+
}
|
|
999
|
+
interface StructFieldSpec {
|
|
1000
|
+
name: string;
|
|
1001
|
+
property: string;
|
|
1002
|
+
number: number;
|
|
1003
|
+
type: TypeSpec;
|
|
1004
|
+
doc?: string;
|
|
1005
|
+
mutableGetter?: string;
|
|
1006
|
+
indexable?: IndexableSpec;
|
|
1007
|
+
}
|
|
1008
|
+
interface IndexableSpec {
|
|
1009
|
+
searchMethod: string;
|
|
1010
|
+
keyFn: (v: unknown) => unknown;
|
|
1011
|
+
keyToHashable?: (v: unknown) => unknown;
|
|
1012
|
+
}
|
|
1013
|
+
interface EnumSpec<Enum = unknown> {
|
|
1014
|
+
kind: "enum";
|
|
1015
|
+
ctor: {
|
|
1016
|
+
new (privateKey: symbol, kind: string, value?: unknown, unrecognized?: UnrecognizedEnum): Enum;
|
|
1017
|
+
};
|
|
1018
|
+
createValueFn?: (initializer: unknown) => unknown;
|
|
1019
|
+
name: string;
|
|
1020
|
+
parentCtor?: {
|
|
1021
|
+
new (): unknown;
|
|
1022
|
+
};
|
|
1023
|
+
doc?: string;
|
|
1024
|
+
variants: EnumVariantSpec[];
|
|
1025
|
+
removedNumbers?: readonly number[];
|
|
1026
|
+
}
|
|
1027
|
+
interface EnumVariantSpec {
|
|
1028
|
+
name: string;
|
|
1029
|
+
number: number;
|
|
1030
|
+
type?: TypeSpec;
|
|
1031
|
+
doc?: string;
|
|
1032
|
+
}
|
|
1033
|
+
type TypeSpec = {
|
|
1034
|
+
kind: "optional";
|
|
1035
|
+
other: TypeSpec;
|
|
1036
|
+
} | {
|
|
1037
|
+
kind: "array";
|
|
1038
|
+
item: TypeSpec;
|
|
1039
|
+
keyChain?: string;
|
|
1040
|
+
} | {
|
|
1041
|
+
kind: "record";
|
|
1042
|
+
ctor: {
|
|
1043
|
+
new (): unknown;
|
|
1044
|
+
};
|
|
1045
|
+
} | {
|
|
1046
|
+
kind: "primitive";
|
|
1047
|
+
primitive: keyof PrimitiveTypes;
|
|
1048
|
+
};
|
|
1049
|
+
export declare function _initModuleClasses(modulePath: string, records: ReadonlyArray<StructSpec | EnumSpec>): void;
|
|
1050
|
+
export {};
|
|
1051
|
+
//# sourceMappingURL=skir-client.d.ts.map
|