skir-typescript-gen 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,370 @@
1
+ [![npm](https://img.shields.io/npm/v/skir-typescript-gen)](https://www.npmjs.com/package/skir-typescript-gen)
2
+ [![build](https://github.com/gepheum/skir-typescript-gen/workflows/Build/badge.svg)](https://github.com/gepheum/skir-typescript-gen/actions)
3
+
4
+ # Skir's TypeScript code generator
5
+
6
+ Official plugin for generating TypeScript/JavaScript code from [.skir](https://github.com/gepheum/skir) files.
7
+
8
+ Generated code can run Node, Deno or in the browser.
9
+
10
+ ## Installation
11
+
12
+ From your project's root directory, run `npm i --save-dev skir-typescript-gen`.
13
+
14
+ In your `skir.yml` file, add the following snippet under `generators`:
15
+ ```yaml
16
+ - mod: skir-typescript-gen
17
+ config:
18
+ # Possible values: "" (CommonJS), ".js" (ES Modules), ".ts" (Deno)
19
+ importPathExtension: ""
20
+ ```
21
+
22
+ The `npm run skirc` command will now generate .js and .t.ts files within the `skirout` directory.
23
+
24
+ For more information, see this TypeScript project [example](https://github.com/gepheum/skir-typescript-example).
25
+
26
+ ## TypeScript generated code guide
27
+
28
+ The examples below are for the code generated from [this](https://github.com/gepheum/skir-typescript-example/blob/main/skir_src/user.skir) .skir file.
29
+
30
+ ### Referring to generated symbols
31
+
32
+ ```typescript
33
+ import { TARZAN, User, UserHistory, UserRegistry } from "../skirout/user";
34
+ ```
35
+
36
+ ### Struct classes
37
+
38
+ For every struct `S` in the .skir file, skir generates a frozen (deeply immutable) class `S` and a mutable class `S.Mutable`.
39
+
40
+ #### Frozen struct classes
41
+
42
+ ```typescript
43
+ // Construct a frozen User with User.create({...})
44
+ const john = User.create({
45
+ userId: 42,
46
+ name: "John Doe",
47
+ quote: "Coffee is just a socially acceptable form of rage.",
48
+ pets: [
49
+ {
50
+ name: "Dumbo",
51
+ heightInMeters: 1.0,
52
+ picture: "🐘",
53
+ },
54
+ ],
55
+ subscriptionStatus: "FREE",
56
+ // foo: "bar",
57
+ // ^ Does not compile: 'foo' is not a field of User
58
+ });
59
+
60
+ assert(john.name === "John Doe");
61
+
62
+ // john.name = "John Smith";
63
+ // ^ Does not compile: all the properties are read-only
64
+
65
+ // With create<"partial">({...}), you don't need to specify all the fields of
66
+ // the struct.
67
+ const jane = User.create<"partial">({
68
+ userId: 43,
69
+ name: "Jane Doe",
70
+ pets: [{ name: "Fluffy" }, { name: "Fido" }],
71
+ });
72
+
73
+ // Missing fields are initialized to their default values.
74
+ assert(jane.quote === "");
75
+
76
+ const janeHistory = UserHistory.create({
77
+ user: jane,
78
+ // ^ the object you pass to create({...}) can contain struct values
79
+ sessions: [
80
+ {
81
+ login: Timestamp.fromUnixMillis(1234),
82
+ logout: Timestamp.fromUnixMillis(2345),
83
+ },
84
+ ],
85
+ });
86
+
87
+ const defaultUser = User.DEFAULT;
88
+ assert(defaultUser.name === "");
89
+ // User.DEFAULT is same as User.create<"partial">({});
90
+ ```
91
+
92
+ #### Mutable struct classes
93
+
94
+ ```typescript
95
+ // User.Mutable is a mutable version of User.
96
+ const lylaMut = new User.Mutable();
97
+ lylaMut.userId = 44;
98
+ lylaMut.name = "Lyla Doe";
99
+
100
+ // The User.Mutable() constructor also accepts an initializer object.
101
+ const jolyMut = new User.Mutable({ userId: 45 });
102
+ jolyMut.name = "Joly Doe";
103
+
104
+ // jolyHistoryMut.user.quote = "I am Joly.";
105
+ // ^ Does not compile: quote is readonly because jolyHistoryMut.user might be
106
+ // a frozen struct
107
+
108
+ const jolyHistoryMut = new UserHistory.Mutable();
109
+ jolyHistoryMut.user = jolyMut;
110
+ // ^ The right-hand side of the assignment can be either frozen or mutable.
111
+
112
+ // The mutableUser() getter first checks if 'user' is already a mutable struct,
113
+ // and if so, returns it. Otherwise, it assigns to 'user' a mutable shallow copy
114
+ // of itself and returns it.
115
+ jolyHistoryMut.mutableUser.quote = "I am Joly.";
116
+
117
+ // Similarly, mutablePets() first checks if 'pets' is already a mutable array,
118
+ // and if so, returns it. Otherwise, it assigns to 'pets' a mutable shallow copy
119
+ // of itself and returns it.
120
+ lylaMut.mutablePets.push(User.Pet.create<"partial">({ name: "Cupcake" }));
121
+ lylaMut.mutablePets.push(new User.Pet.Mutable({ name: "Simba" }));
122
+ ```
123
+
124
+ #### Converting between frozen and mutable
125
+
126
+ ```typescript
127
+ // toMutable() does a shallow copy of the frozen struct, so it's cheap. All the
128
+ // properties of the copy hold a frozen value.
129
+ const evilJaneMut = jane.toMutable();
130
+ evilJaneMut.name = "Evil Jane";
131
+
132
+ // toFrozen() recursively copies the mutable values held by properties of the
133
+ // object. It's cheap if all the values are frozen, like in this example.
134
+ const evilJane: User = evilJaneMut.toFrozen();
135
+ ```
136
+
137
+ #### Writing logic agnostic of mutability
138
+
139
+ ```typescript
140
+ // 'User.OrMutable' is a type alias for 'User | User.Mutable'.
141
+ function greet(user: User.OrMutable) {
142
+ console.log(`Hello, ${user.name}`);
143
+ }
144
+
145
+ greet(jane);
146
+ // Hello, Jane Doe
147
+ greet(lylaMut);
148
+ // Hello, Lyla Doe
149
+ ```
150
+
151
+ ### Enum classes
152
+
153
+ The definition of the `SubscriptionStatus` enum in the .skir file is:
154
+ ```rust
155
+ enum SubscriptionStatus {
156
+ FREE;
157
+ trial: Trial;
158
+ PREMIUM;
159
+ }
160
+ ```
161
+
162
+ #### Making enum values
163
+
164
+ ```typescript
165
+ const johnStatus = User.SubscriptionStatus.FREE;
166
+ const janeStatus = User.SubscriptionStatus.PREMIUM;
167
+ const lylaStatus = User.SubscriptionStatus.create("PREMIUM");
168
+ // ^ same as User.SubscriptionStatus.PREMIUM
169
+ const jolyStatus = User.SubscriptionStatus.UNKNOWN;
170
+
171
+ // Use create({kind: ..., value: ...}) for data variants.
172
+ const roniStatus = User.SubscriptionStatus.create({
173
+ kind: "trial",
174
+ value: {
175
+ startTime: Timestamp.fromUnixMillis(1234),
176
+ },
177
+ });
178
+ ```
179
+
180
+ #### Conditions on enums
181
+
182
+ ```typescript
183
+ // Use e.kind === "CONSTANT_NAME" to check if the enum value is a constant.
184
+ assert(johnStatus.kind === "FREE");
185
+ assert(johnStatus.value === undefined);
186
+
187
+ // Use "?" for UNKNOWN.
188
+ assert(jolyStatus.kind === "?");
189
+
190
+ assert(roniStatus.kind === "trial");
191
+ assert(roniStatus.value!.startTime.unixMillis === 1234);
192
+
193
+ function getSubscriptionInfoText(status: User.SubscriptionStatus): string {
194
+ // Use the 'union' getter for typesafe switches on enums.
195
+ switch (status.union.kind) {
196
+ case "?":
197
+ return "Unknown subscription status";
198
+ case "FREE":
199
+ return "Free user";
200
+ case "PREMIUM":
201
+ return "Premium user";
202
+ case "trial":
203
+ // Here the compiler knows that the type of union.value is 'User.Trial'.
204
+ return "On trial since " + status.union.value.startTime;
205
+ }
206
+ }
207
+ ```
208
+
209
+ ### Serialization
210
+
211
+ Every frozen struct class and enum class has a static readonly `serializer` property which can be used for serializing and deserializing instances of the class.
212
+
213
+ ```typescript
214
+
215
+ const serializer = User.serializer;
216
+
217
+ // Serialize 'john' to dense JSON.
218
+ console.log(serializer.toJsonCode(john));
219
+ // [42,"John Doe"]
220
+
221
+ // Serialize 'john' to readable JSON.
222
+ console.log(serializer.toJsonCode(john, "readable"));
223
+ // {
224
+ // "user_id": 42,
225
+ // "name": "John Doe"
226
+ // }
227
+
228
+ // The dense JSON flavor is the flavor you should pick if you intend to
229
+ // deserialize the value in the future. Skir allows fields to be renamed, and
230
+ // because fields names are not part of the dense JSON, renaming a field does
231
+ // not prevent you from deserializing the value.
232
+ // You should pick the readable flavor mostly for debugging purposes.
233
+
234
+ // Serialize 'john' to binary format.
235
+ console.log(serializer.toBytes(john));
236
+
237
+ // The binary format is not human readable, but it is slightly more compact than
238
+ // JSON, and serialization/deserialization can be a bit faster in languages like
239
+ // C++. Only use it when this small performance gain is likely to matter, which
240
+ // should be rare.
241
+ ```
242
+
243
+ ### Deserialization
244
+
245
+ ```typescript
246
+ // Use fromJson(), fromJsonCode() and fromBytes() to deserialize.
247
+
248
+ const reserializedJohn = serializer.fromJsonCode(serializer.toJsonCode(john));
249
+ assert(reserializedJohn.name === "John Doe");
250
+
251
+ const reserializedJane = serializer.fromJsonCode(
252
+ serializer.toJsonCode(jane, "readable"),
253
+ );
254
+ assert(reserializedJane.name === "Jane Doe");
255
+
256
+ const reserializedLyla = serializer.fromBytes(
257
+ serializer.toBytes(lylaMut).toBuffer(),
258
+ );
259
+ assert(reserializedLyla.name === "Lyla Doe");
260
+ ```
261
+
262
+ ### Frozen arrays and copies
263
+
264
+ ```typescript
265
+ const pets = [
266
+ User.Pet.create<"partial">({ name: "Fluffy" }),
267
+ User.Pet.create<"partial">({ name: "Fido" }),
268
+ ];
269
+
270
+ const jade = User.create<"partial">({
271
+ pets: pets,
272
+ // ^ makes a copy of 'pets' because 'pets' is mutable
273
+ });
274
+
275
+ // jade.pets.push(...)
276
+ // ^ Compile-time error: pets is readonly
277
+
278
+ assert(jade.pets !== pets);
279
+
280
+ const jack = User.create<"partial">({
281
+ pets: jade.pets,
282
+ // ^ doesn't make a copy because 'jade.pets' is frozen
283
+ });
284
+
285
+ assert(jack.pets === jade.pets);
286
+ ```
287
+
288
+ ### Keyed arrays
289
+
290
+ ```typescript
291
+ const userRegistry = UserRegistry.create({
292
+ users: [john, jane, lylaMut],
293
+ });
294
+
295
+ // searchUsers() returns the user with the given key (specified in the .skir
296
+ // file). In this example, the key is the user id.
297
+ // The first lookup runs in O(N) time, and the following lookups run in O(1)
298
+ // time.
299
+ assert(userRegistry.searchUsers(42) === john);
300
+ assert(userRegistry.searchUsers(100) === undefined);
301
+ ```
302
+
303
+ ### Constants
304
+
305
+ ```typescript
306
+ console.log(TARZAN);
307
+ // User {
308
+ // userId: 123,
309
+ // name: 'Tarzan',
310
+ // quote: 'AAAAaAaAaAyAAAAaAaAaAyAAAAaAaAaA',
311
+ // pets: [ User_Pet { name: 'Cheeta', heightInMeters: 1.67, picture: '🐒' } ],
312
+ // subscriptionStatus: User_SubscriptionStatus {
313
+ // kind: 'trial',
314
+ // value: User_Trial { startTime: [Timestamp] }
315
+ // }
316
+ // }
317
+ ```
318
+
319
+ ### Skir services
320
+
321
+ #### Starting a skir service on an HTTP server
322
+
323
+ Full example [here](https://github.com/gepheum/skir-typescript-example/blob/main/src/server.ts).
324
+
325
+ #### Sending RPCs to a skir service
326
+
327
+ Full example [here](https://github.com/gepheum/skir-typescript-example/blob/main/src/client.ts).
328
+
329
+ ### Reflection
330
+
331
+ Reflection allows you to inspect a skir type at runtime.
332
+
333
+ ```typescript
334
+ const fieldNames: string[] = [];
335
+ for (const field of User.serializer.typeDescriptor.fields) {
336
+ const { name, number, property, type } = field;
337
+ fieldNames.push(name);
338
+ }
339
+ console.log(fieldNames);
340
+ // [ 'user_id', 'name', 'quote', 'pets', 'subscription_status' ]
341
+
342
+ // A type descriptor can be serialized to JSON and deserialized later.
343
+ const typeDescriptor = parseTypeDescriptorFromJson(
344
+ User.serializer.typeDescriptor.asJson(),
345
+ );
346
+ ```
347
+
348
+ ### Writing unit tests
349
+
350
+ With mocha and [buckwheat](https://github.com/gepheum/buckwheat).
351
+
352
+ ```typescript
353
+ expect(tarzan).toMatch({
354
+ name: "Tarzan",
355
+ quote: /^A/, // must start with the letter A
356
+ pets: [
357
+ {
358
+ name: "Cheeta",
359
+ heightInMeters: near(1.6, 0.1),
360
+ },
361
+ ],
362
+ subscriptionStatus: {
363
+ kind: "trial",
364
+ value: {
365
+ startTime: Timestamp.fromUnixMillis(1234),
366
+ },
367
+ },
368
+ // `userId` is not specified so it can be anything
369
+ });
370
+ ```
@@ -0,0 +1,48 @@
1
+ import type { Module, RecordLocation } from "skir-internal";
2
+ export interface ClassName {
3
+ /**
4
+ * Name of the TypeScript class, e.g. "Bar". It is obtained by taking the
5
+ * record name, as specified in the '.skir' file, and appending one or
6
+ * multiple underscores if there is a conflict with another name.
7
+ * If the class is nested, the `name` does not include the name of the parent
8
+ * classes.
9
+ */
10
+ readonly name: string;
11
+ /**
12
+ * Qualified type name, e.g. "Foo.Bar".
13
+ * This is what you want to use in type declarations.
14
+ */
15
+ readonly type: string;
16
+ /**
17
+ * Class expression, e.g. "Foo_Bar" if the class is defined in the same
18
+ * module, or "x_other_module.Foo.Bar" if it is defined in a different module.
19
+ * This is what you want to use in the TypeScript code that gets transpiled to
20
+ * Javascript.
21
+ */
22
+ readonly value: string;
23
+ /**
24
+ * Expression referring to the parent class if this class is nested, undefined
25
+ * otherwise.
26
+ */
27
+ parentClassValue: string | undefined;
28
+ /**
29
+ * Name of the record, as specified in the '.skir' file. This can be different
30
+ * from `name` if for example the record name conflicts with a built-in class.
31
+ * If the record is nested, the `recordName` does not include the name of the
32
+ * parent records.
33
+ */
34
+ readonly recordName: string;
35
+ /**
36
+ * Dot-separated record names, where the first record is a record defined at
37
+ * the top-level, the last record is this record, and every record in the
38
+ * chain is nested within the previous record.
39
+ */
40
+ readonly recordQualifiedName: string;
41
+ readonly recordType: "struct" | "enum";
42
+ /** True if the class is nested within another class. */
43
+ readonly isNested: boolean;
44
+ }
45
+ /** Returns the name of the frozen TypeScript class for the given record. */
46
+ export declare function getClassName(record: RecordLocation, origin: Module): ClassName;
47
+ export declare function maybeEscapeTopLevelUpperCaseName(name: string): string;
48
+ //# sourceMappingURL=class_speller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"class_speller.d.ts","sourceRoot":"","sources":["../src/class_speller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE5D,MAAM,WAAW,SAAS;IACxB;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IAErC,QAAQ,CAAC,UAAU,EAAE,QAAQ,GAAG,MAAM,CAAC;IACvC,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAC1B,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,MAAM,GACb,SAAS,CA6DX;AAED,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErE"}
@@ -0,0 +1,84 @@
1
+ /** Returns the name of the frozen TypeScript class for the given record. */
2
+ export function getClassName(record, origin) {
3
+ const recordType = record.record.recordType;
4
+ const { recordAncestors } = record;
5
+ const isNested = recordAncestors.length >= 2;
6
+ const seenNames = new Set();
7
+ const parts = [];
8
+ for (let i = 0; i < recordAncestors.length; ++i) {
9
+ const reservedNames = i === 0
10
+ ? BUILTIN_TYPE_NAMES
11
+ : recordAncestors[i - 1].recordType === "struct"
12
+ ? STRUCT_NESTED_TYPE_NAMES
13
+ : ENUM_NESTED_TYPE_NAMES;
14
+ const record = recordAncestors[i];
15
+ let name = record.name.text;
16
+ while (seenNames.has(name) || reservedNames.has(name)) {
17
+ name += "_";
18
+ }
19
+ parts.push(name);
20
+ seenNames.add(name);
21
+ }
22
+ const name = parts.at(-1);
23
+ const recordName = record.record.name.text;
24
+ const recordQualifiedName = recordAncestors.map((r) => r.name.text).join(".");
25
+ let type;
26
+ let value;
27
+ let parentClassValue;
28
+ if (record.modulePath !== origin.path) {
29
+ // The record is located in an imported module.
30
+ const path = record.modulePath;
31
+ const importedNames = origin.pathToImportedNames[path];
32
+ if (importedNames.kind === "all") {
33
+ const alias = importedNames.alias;
34
+ type = [`x_${alias}`].concat(parts).join(".");
35
+ }
36
+ else {
37
+ type = `${parts.join(".")}`;
38
+ }
39
+ value = type;
40
+ }
41
+ else {
42
+ type = parts.join(".");
43
+ value = parts.join("_");
44
+ if (isNested) {
45
+ parentClassValue = parts.slice(0, -1).join("_");
46
+ }
47
+ }
48
+ return {
49
+ name: name,
50
+ type: type,
51
+ value: value,
52
+ parentClassValue: parentClassValue,
53
+ recordName: recordName,
54
+ recordQualifiedName: recordQualifiedName,
55
+ recordType: recordType,
56
+ isNested: isNested,
57
+ };
58
+ }
59
+ export function maybeEscapeTopLevelUpperCaseName(name) {
60
+ return BUILTIN_TYPE_NAMES.has(name) ? `${name}_` : name;
61
+ }
62
+ const BUILTIN_TYPE_NAMES = new Set([
63
+ // Subset of the standard built-in Typescript types and Javascript class names
64
+ // used in the generated code, either in the ".d.ts" file or the ".js" file.
65
+ // We can't use those as names for generated declarations at the top-level.
66
+ "Array",
67
+ "BigInt",
68
+ "Partial",
69
+ "ReadonlyArray",
70
+ ]);
71
+ /** Generated types nested within a struct namespace. */
72
+ const STRUCT_NESTED_TYPE_NAMES = new Set([
73
+ "Initializer",
74
+ "Mutable",
75
+ "OrMutable",
76
+ ]);
77
+ /** Generated types nested within an enum namespace. */
78
+ const ENUM_NESTED_TYPE_NAMES = new Set([
79
+ "Initializer",
80
+ "Kind",
81
+ "Value",
82
+ "UnionView",
83
+ ]);
84
+ //# sourceMappingURL=class_speller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"class_speller.js","sourceRoot":"","sources":["../src/class_speller.ts"],"names":[],"mappings":"AA+CA,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAC1B,MAAsB,EACtB,MAAc;IAEd,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC;IAC5C,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,CAAC;IACnC,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,IAAI,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;QAChD,MAAM,aAAa,GACjB,CAAC,KAAK,CAAC;YACL,CAAC,CAAC,kBAAkB;YACpB,CAAC,CAAC,eAAe,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,UAAU,KAAK,QAAQ;gBAC/C,CAAC,CAAC,wBAAwB;gBAC1B,CAAC,CAAC,sBAAsB,CAAC;QAE/B,MAAM,MAAM,GAAG,eAAe,CAAC,CAAC,CAAE,CAAC;QACnC,IAAI,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;QAE5B,OAAO,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACtD,IAAI,IAAI,GAAG,CAAC;QACd,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC;IAC3B,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;IAC3C,MAAM,mBAAmB,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE9E,IAAI,IAAY,CAAC;IACjB,IAAI,KAAa,CAAC;IAClB,IAAI,gBAAoC,CAAC;IACzC,IAAI,MAAM,CAAC,UAAU,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC;QACtC,+CAA+C;QAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC;QAC/B,MAAM,aAAa,GAAG,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAE,CAAC;QACxD,IAAI,aAAa,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC;YAClC,IAAI,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChD,CAAC;aAAM,CAAC;YACN,IAAI,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,CAAC;QACD,KAAK,GAAG,IAAI,CAAC;IACf,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACvB,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,QAAQ,EAAE,CAAC;YACb,gBAAgB,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,KAAK;QACZ,gBAAgB,EAAE,gBAAgB;QAClC,UAAU,EAAE,UAAU;QACtB,mBAAmB,EAAE,mBAAmB;QACxC,UAAU,EAAE,UAAU;QACtB,QAAQ,EAAE,QAAQ;KACnB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gCAAgC,CAAC,IAAY;IAC3D,OAAO,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1D,CAAC;AAED,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC;IACtD,8EAA8E;IAC9E,4EAA4E;IAC5E,2EAA2E;IAC3E,OAAO;IACP,QAAQ;IACR,SAAS;IACT,eAAe;CAChB,CAAC,CAAC;AAEH,wDAAwD;AACxD,MAAM,wBAAwB,GAAwB,IAAI,GAAG,CAAC;IAC5D,aAAa;IACb,SAAS;IACT,WAAW;CACZ,CAAC,CAAC;AAEH,uDAAuD;AACvD,MAAM,sBAAsB,GAAwB,IAAI,GAAG,CAAC;IAC1D,aAAa;IACb,MAAM;IACN,OAAO;IACP,WAAW;CACZ,CAAC,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @fileoverview Returns a TypeScript expression transforming a value from a
3
+ * `initializer` type into a `frozen` type.
4
+ */
5
+ import type { ResolvedType } from "skir-internal";
6
+ import { TypeSpeller } from "./type_speller.js";
7
+ export interface ToFrozenExpressionArg {
8
+ type: ResolvedType;
9
+ inExpr: string;
10
+ maybeUndefined: boolean;
11
+ typeSpeller: TypeSpeller;
12
+ }
13
+ export declare function toFrozenExpression(arg: ToFrozenExpressionArg): string;
14
+ //# sourceMappingURL=expression_maker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expression_maker.d.ts","sourceRoot":"","sources":["../src/expression_maker.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,YAAY,CAAC;IAEnB,MAAM,EAAE,MAAM,CAAC;IAEf,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,qBAAqB,GAAG,MAAM,CA2FrE"}
@@ -0,0 +1,120 @@
1
+ export function toFrozenExpression(arg) {
2
+ const { type, inExpr, maybeUndefined, typeSpeller } = arg;
3
+ if (type.kind === "record") {
4
+ const frozenClass = typeSpeller.getClassName(type.key);
5
+ const defaultExpr = type.recordType === "struct"
6
+ ? `${frozenClass.value}.DEFAULT`
7
+ : `${frozenClass.value}.UNKNOWN`;
8
+ const inExprOrDefault = maybeUndefined
9
+ ? `${inExpr} ?? ${defaultExpr}`
10
+ : inExpr;
11
+ return `${frozenClass.value}.create(${inExprOrDefault})`;
12
+ }
13
+ else if (type.kind === "array") {
14
+ const transformItemExpr = toFrozenExpression({
15
+ type: type.item,
16
+ inExpr: "e",
17
+ maybeUndefined: false,
18
+ typeSpeller: typeSpeller,
19
+ });
20
+ const inExprOrEmpty = maybeUndefined ? `${inExpr} || []` : inExpr;
21
+ if (transformItemExpr === "e") {
22
+ return `$._toFrozenArray(\n${inExprOrEmpty})`;
23
+ }
24
+ else {
25
+ let mapFnExpr;
26
+ if (type.item.kind === "record") {
27
+ // Instead of creating a lambda, we can just get the `create` static
28
+ // function.
29
+ const frozenClass = typeSpeller.getClassName(type.item.key);
30
+ mapFnExpr = `${frozenClass.value}.create`;
31
+ }
32
+ else {
33
+ mapFnExpr = `(e) => ${transformItemExpr}`;
34
+ }
35
+ const funName = "$._toFrozenArray";
36
+ return `${funName}(\n${inExprOrEmpty},\n${mapFnExpr},\n)`;
37
+ }
38
+ }
39
+ else if (type.kind === "optional") {
40
+ const otherType = type.other;
41
+ const otherExpr = toFrozenExpression({
42
+ type: otherType,
43
+ inExpr: inExpr,
44
+ maybeUndefined: false,
45
+ typeSpeller: typeSpeller,
46
+ });
47
+ if (otherExpr === inExpr) {
48
+ return maybeUndefined ? `${inExpr} ?? null` : inExpr;
49
+ }
50
+ // The condition for returning otherExpr.
51
+ let condition;
52
+ if (canBeFalsy(otherType)) {
53
+ if (maybeUndefined) {
54
+ // This is one way to test that inExpr is not null or undefined.
55
+ // Works because if inExpr was === 0, then we would have already
56
+ // returned.
57
+ condition = `((${inExpr} ?? 0) !== 0)`;
58
+ }
59
+ else {
60
+ condition = `${inExpr} !== null`;
61
+ }
62
+ }
63
+ else {
64
+ // Just rely on implicit boolean conversion.
65
+ // Also works if maybeUndefined is true.
66
+ condition = inExpr;
67
+ }
68
+ return `${condition} ? ${otherExpr} : null`;
69
+ }
70
+ // A primitive type.
71
+ if (!maybeUndefined) {
72
+ return inExpr;
73
+ }
74
+ const { primitive } = type;
75
+ let defaultValue;
76
+ if (primitive === "bool") {
77
+ defaultValue = "false";
78
+ }
79
+ else if (primitive === "int32" ||
80
+ primitive === "float32" ||
81
+ primitive === "float64") {
82
+ defaultValue = "0";
83
+ }
84
+ else if (primitive === "int64" || primitive === "uint64") {
85
+ defaultValue = "BigInt(0)";
86
+ }
87
+ else if (primitive === "timestamp") {
88
+ defaultValue = "$.Timestamp.UNIX_EPOCH";
89
+ }
90
+ else if (primitive === "string") {
91
+ defaultValue = '""';
92
+ }
93
+ else if (primitive === "bytes") {
94
+ defaultValue = "$.ByteString.EMPTY";
95
+ }
96
+ else {
97
+ const _ = primitive;
98
+ throw TypeError();
99
+ }
100
+ return `${inExpr} ?? ${defaultValue}`;
101
+ }
102
+ // Returns true if values of the given type can ever be falsy.
103
+ // See https://developer.mozilla.org/en-US/docs/Glossary/Falsy
104
+ function canBeFalsy(type) {
105
+ if (type.kind === "optional") {
106
+ return true;
107
+ }
108
+ if (type.kind !== "primitive") {
109
+ return false;
110
+ }
111
+ const { primitive } = type;
112
+ return (primitive === "bool" ||
113
+ primitive === "int32" ||
114
+ primitive === "int64" ||
115
+ primitive === "uint64" ||
116
+ primitive === "float32" ||
117
+ primitive === "float64" ||
118
+ primitive === "string");
119
+ }
120
+ //# sourceMappingURL=expression_maker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expression_maker.js","sourceRoot":"","sources":["../src/expression_maker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { CodeGenerator } from "skir-internal";
2
+ import { z } from "zod";
3
+ declare const Config: z.ZodObject<{
4
+ importPathExtension: z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodLiteral<".js">, z.ZodLiteral<".ts">]>;
5
+ clientModulePath: z.ZodOptional<z.ZodString>;
6
+ }, z.core.$strip>;
7
+ type Config = z.infer<typeof Config>;
8
+ declare class TypescriptCodeGenerator implements CodeGenerator<Config> {
9
+ readonly id = "typescript";
10
+ readonly configType: z.ZodObject<{
11
+ importPathExtension: z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodLiteral<".js">, z.ZodLiteral<".ts">]>;
12
+ clientModulePath: z.ZodOptional<z.ZodString>;
13
+ }, z.core.$strip>;
14
+ readonly version = "1.0.0";
15
+ generateCode(input: CodeGenerator.Input<Config>): CodeGenerator.Output;
16
+ }
17
+ export declare const GENERATOR: TypescriptCodeGenerator;
18
+ export {};
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EAOd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,QAAA,MAAM,MAAM;;;iBAOV,CAAC;AAEH,KAAK,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAErC,cAAM,uBAAwB,YAAW,aAAa,CAAC,MAAM,CAAC;IAC5D,QAAQ,CAAC,EAAE,gBAAgB;IAC3B,QAAQ,CAAC,UAAU;;;sBAAU;IAC7B,QAAQ,CAAC,OAAO,WAAW;IAE3B,YAAY,CAAC,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM;CAyBvE;AAkvBD,eAAO,MAAM,SAAS,yBAAgC,CAAC"}