@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.
@@ -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