skir-dart-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,409 @@
1
+ [![npm](https://img.shields.io/npm/v/skir-dart-gen)](https://www.npmjs.com/package/skir-dart-gen)
2
+ [![build](https://github.com/gepheum/skir-dart-gen/workflows/Build/badge.svg)](https://github.com/gepheum/skir-dart-gen/actions)
3
+
4
+ # Skir's Dart code generator
5
+
6
+ Official plugin for generating Dart code from [.skir](https://github.com/gepheum/skir) files.
7
+
8
+ Targets Dart 3.0 and higher.
9
+
10
+ ## Installation
11
+
12
+ From your project's root directory, run `npm i --save-dev skir-dart-gen`.
13
+
14
+ In your `skir.yml` file, add the following snippet under `generators`:
15
+ ```yaml
16
+ - mod: skir-dart-gen
17
+ config: {}
18
+ ```
19
+
20
+ The `npm run skirc` command will now generate .dart files within the `skirout` directory.
21
+
22
+ The generated Dart code has a runtime dependency on the `skir` library. Add this line to your `pubspec.yaml` file under `dependencies`:
23
+
24
+ ```yaml
25
+ skir: ^0.3.0 # Use the latest version
26
+ ```
27
+
28
+ For more information, see this Dart project [example](https://github.com/gepheum/skir-dart-example).
29
+
30
+ ## Dart generated code guide
31
+
32
+ The examples below are for the code generated from [this](https://github.com/gepheum/skir-dart-example/blob/main/lib/skir_src/user.skir) .skir file.
33
+
34
+ ### Referring to generated symbols
35
+
36
+ ```dart
37
+ // Import the given symbols from the Dart module generated from "user.skir"
38
+ import 'package:skir_dart_example/skirout/user.dart';
39
+
40
+ // Now you can use: tarzan, User, UserHistory, UserRegistry, etc.
41
+ ```
42
+
43
+ ### Struct classes
44
+
45
+ For every struct `S` in the .skir file, skir generates a frozen (deeply immutable) class `S` and a mutable class `S_mutable`.
46
+
47
+ #### Frozen struct classes
48
+
49
+ ```dart
50
+ // To construct a frozen User, call the User constructor.
51
+
52
+ final john = User(
53
+ // All fields are required.
54
+ userId: 42,
55
+ name: "John Doe",
56
+ quote: "Coffee is just a socially acceptable form of rage.",
57
+ pets: [
58
+ User_Pet(
59
+ name: "Dumbo",
60
+ heightInMeters: 1.0,
61
+ picture: "🐘",
62
+ ),
63
+ ],
64
+ subscriptionStatus: User_SubscriptionStatus.free,
65
+ // foo: "bar",
66
+ // ^ Does not compile: 'foo' is not a field of User
67
+ );
68
+
69
+ assert(john.name == "John Doe");
70
+
71
+ // Lists passed to the constructor are copied into frozen lists to ensure
72
+ // deep immutability.
73
+ assert(john.pets is List);
74
+
75
+ // Static type checkers will raise an error if you try to modify a frozen struct:
76
+ // john.name = "John Smith";
77
+ // ^ Does not compile: all the properties are read-only
78
+
79
+ // You can also construct a frozen User using the builder pattern with a
80
+ // mutable instance as the builder.
81
+ final User jane = (User.mutable()
82
+ ..userId = 43
83
+ ..name = "Jane Doe"
84
+ ..pets = [
85
+ User_Pet(name: "Fluffy", heightInMeters: 0.2, picture: "🐱"),
86
+ User_Pet.mutable()
87
+ ..name = "Fido"
88
+ ..heightInMeters = 0.25
89
+ ..picture = "🐶"
90
+ ..toFrozen(),
91
+ ])
92
+ .toFrozen();
93
+
94
+ // Fields not explicitly set are initialized to their default values.
95
+ assert(jane.quote == "");
96
+
97
+ // User.defaultInstance is an instance of User with all fields set to their
98
+ // default values.
99
+ assert(User.defaultInstance.name == "");
100
+ assert(User.defaultInstance.pets.isEmpty);
101
+ ```
102
+
103
+ #### Mutable struct classes
104
+
105
+ ```dart
106
+ // 'User_mutable' is a dataclass similar to User except it is mutable.
107
+ // Use User.mutable() to create a new instance.
108
+ final mutableLyla = User.mutable()..userId = 44;
109
+ mutableLyla.name = "Lyla Doe";
110
+
111
+ final userHistory = UserHistory.mutable();
112
+ userHistory.user = mutableLyla;
113
+ // ^ The right-hand side of the assignment can be either frozen or mutable.
114
+
115
+ // userHistory.user.quote = "I am Lyla."
116
+ // ^ Static error: quote is readonly because userHistory.user may be frozen
117
+
118
+ // The 'mutableUser' getter provides access to a mutable version of 'user'.
119
+ // If 'user' is already mutable, it returns it directly.
120
+ // If 'user' is frozen, it creates a mutable shallow copy, assigns it to
121
+ // 'user', and returns it.
122
+ userHistory.mutableUser.quote = "I am Lyla.";
123
+
124
+ // Similarly, 'mutablePets' provides access to a mutable version of 'pets'.
125
+ // It returns the existing list if already mutable, or creates and returns a
126
+ // mutable shallow copy.
127
+ mutableLyla.mutablePets.add(User_Pet(
128
+ name: "Simba",
129
+ heightInMeters: 0.4,
130
+ picture: "🦁",
131
+ ));
132
+ mutableLyla.mutablePets.add(User_Pet.mutable()..name = "Cupcake");
133
+ ```
134
+
135
+ #### Converting between frozen and mutable
136
+
137
+ ```dart
138
+ // toMutable() does a shallow copy of the frozen struct, so it's cheap. All the
139
+ // properties of the copy hold a frozen value.
140
+ final evilJane = (jane.toMutable()
141
+ ..name = "Evil Jane"
142
+ ..quote = "I solemnly swear I am up to no good.")
143
+ .toFrozen();
144
+
145
+ assert(evilJane.name == "Evil Jane");
146
+ assert(evilJane.userId == 43);
147
+ ```
148
+
149
+ #### Writing logic agnostic of mutability
150
+
151
+ ```dart
152
+ // 'User_orMutable' is a type alias for the sealed class that both 'User' and
153
+ // 'User_mutable' implement.
154
+ void greet(User_orMutable user) {
155
+ print("Hello, ${user.name}");
156
+ }
157
+
158
+ greet(jane);
159
+ // Hello, Jane Doe
160
+ greet(mutableLyla);
161
+ // Hello, Lyla Doe
162
+ ```
163
+
164
+ ### Enum classes
165
+
166
+ The definition of the `SubscriptionStatus` enum in the .skir file is:
167
+ ```rust
168
+ enum SubscriptionStatus {
169
+ FREE;
170
+ trial: Trial;
171
+ PREMIUM;
172
+ }
173
+ ```
174
+
175
+ #### Making enum values
176
+
177
+ ```dart
178
+ final johnStatus = User_SubscriptionStatus.free;
179
+ final janeStatus = User_SubscriptionStatus.premium;
180
+
181
+ final jolyStatus = User_SubscriptionStatus.unknown;
182
+
183
+ // Use wrapX() for data variants (wraps an existing value).
184
+ final roniStatus = User_SubscriptionStatus.wrapTrial(
185
+ User_Trial(
186
+ startTime: DateTime.fromMillisecondsSinceEpoch(1234, isUtc: true),
187
+ ),
188
+ );
189
+
190
+ // More concisely, use createX() to create and wrap a new struct with the given params.
191
+ final ericStatus = User_SubscriptionStatus.createTrial(
192
+ startTime: DateTime.fromMillisecondsSinceEpoch(5678, isUtc: true),
193
+ );
194
+ ```
195
+
196
+ #### Conditions on enums
197
+
198
+ ```dart
199
+ assert(johnStatus == User_SubscriptionStatus.free);
200
+ assert(janeStatus == User_SubscriptionStatus.premium);
201
+ assert(jolyStatus == User_SubscriptionStatus.unknown);
202
+
203
+ if (roniStatus is User_SubscriptionStatus_trialWrapper) {
204
+ assert(roniStatus.value.startTime.millisecondsSinceEpoch == 1234);
205
+ }
206
+
207
+ String getSubscriptionInfoText(User_SubscriptionStatus status) {
208
+ // Use pattern matching for typesafe switches on enums.
209
+ return switch (status) {
210
+ User_SubscriptionStatus_unknown() => "Unknown subscription status",
211
+ User_SubscriptionStatus.free => "Free user",
212
+ User_SubscriptionStatus.premium => "Premium user",
213
+ User_SubscriptionStatus_trialWrapper(:final value) =>
214
+ "On trial since ${value.startTime}",
215
+ };
216
+ }
217
+ ```
218
+
219
+ ### Serialization
220
+
221
+ 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.
222
+
223
+ ```dart
224
+ // Serialize 'john' to dense JSON.
225
+
226
+ final serializer = User.serializer;
227
+
228
+ print(serializer.toJsonCode(john));
229
+ // [42,"John Doe","Coffee is just a socially acceptable form of rage.",[["Dumbo",1.0,"🐘"]],[1]]
230
+
231
+ // Serialize 'john' to readable JSON.
232
+ print(serializer.toJsonCode(john, readableFlavor: true));
233
+ // {
234
+ // "user_id": 42,
235
+ // "name": "John Doe",
236
+ // "quote": "Coffee is just a socially acceptable form of rage.",
237
+ // "pets": [
238
+ // {
239
+ // "name": "Dumbo",
240
+ // "height_in_meters": 1.0,
241
+ // "picture": "🐘"
242
+ // }
243
+ // ],
244
+ // "subscription_status": "FREE"
245
+ // }
246
+
247
+ // The dense JSON flavor is the flavor you should pick if you intend to
248
+ // deserialize the value in the future. Skir allows fields to be renamed, and
249
+ // because field names are not part of the dense JSON, renaming a field does
250
+ // not prevent you from deserializing the value.
251
+ // You should pick the readable flavor mostly for debugging purposes.
252
+
253
+ // Serialize 'john' to binary format.
254
+ print(serializer.toBytes(john));
255
+
256
+ // The binary format is not human readable, but it is slightly more compact
257
+ // than JSON, and serialization/deserialization can be a bit faster in
258
+ // languages like C++. Only use it when this small performance gain is likely
259
+ // to matter, which should be rare.
260
+ ```
261
+
262
+ ### Deserialization
263
+
264
+ ```dart
265
+ // Use fromJson(), fromJsonCode() and fromBytes() to deserialize.
266
+
267
+ assert(john == serializer.fromJsonCode(serializer.toJsonCode(john)));
268
+
269
+ // Also works with readable JSON.
270
+ assert(john == serializer.fromJsonCode(
271
+ serializer.toJsonCode(john, readableFlavor: true),
272
+ ));
273
+
274
+ final reserializedLyla =
275
+ serializer.fromBytes(serializer.toBytes(mutableLyla.toFrozen()));
276
+ assert(reserializedLyla.name == "Lyla Doe");
277
+ ```
278
+
279
+ ### Frozen lists and copies
280
+
281
+ ```dart
282
+ final pets = [
283
+ User_Pet(name: "Fluffy", heightInMeters: 0.25, picture: "🐶"),
284
+ User_Pet(name: "Fido", heightInMeters: 0.5, picture: "🐻"),
285
+ ];
286
+
287
+ final jade = User(
288
+ userId: 46,
289
+ name: "Jade",
290
+ quote: "",
291
+ pets: pets,
292
+ // ^ makes a copy of 'pets' because 'pets' is mutable
293
+ subscriptionStatus: User_SubscriptionStatus.unknown,
294
+ );
295
+
296
+ // jade.pets.add(...)
297
+ // ^ Compile-time error: pets is a frozen list
298
+
299
+ assert(!identical(jade.pets, pets));
300
+
301
+ final jack = User(
302
+ userId: 47,
303
+ name: "Jack",
304
+ quote: "",
305
+ pets: jade.pets,
306
+ // ^ doesn't make a copy because 'jade.pets' is frozen
307
+ subscriptionStatus: User_SubscriptionStatus.unknown,
308
+ );
309
+
310
+ assert(identical(jack.pets, jade.pets));
311
+ ```
312
+
313
+ ### Keyed lists
314
+
315
+ ```dart
316
+ final userRegistry = UserRegistry(
317
+ users: [john, jane, mutableLyla],
318
+ );
319
+
320
+ // findByKey() returns the user with the given key (specified in the .skir file).
321
+ // In this example, the key is 'user_id'.
322
+ // The first lookup runs in O(N) time, and the following lookups run in O(1)
323
+ // time.
324
+ assert(userRegistry.users.findByKey(42) == john);
325
+ assert(userRegistry.users.findByKey(100) == null);
326
+ ```
327
+
328
+ ### Constants
329
+
330
+ ```dart
331
+ print(tarzan);
332
+ // User(
333
+ // userId: 123,
334
+ // name: "Tarzan",
335
+ // quote: "AAAAaAaAaAyAAAAaAaAaAyAAAAaAaAaA",
336
+ // pets: [
337
+ // User_Pet(
338
+ // name: "Cheeta",
339
+ // heightInMeters: 1.67,
340
+ // picture: "🐒",
341
+ // ),
342
+ // ],
343
+ // subscriptionStatus: user_skir:User_SubscriptionStatus.wrapTrial(
344
+ // User_Trial(
345
+ // startTime: DateTime.fromMillisecondsSinceEpoch(
346
+ // // 2025-04-02T11:13:29.000Z
347
+ // 1743592409000
348
+ // ),
349
+ // )
350
+ // ),
351
+ // )
352
+ ```
353
+
354
+ ### Skir services
355
+
356
+ #### Starting a skir service on an HTTP server
357
+
358
+ Full example [here](https://github.com/gepheum/skir-dart-example/blob/main/bin/start_service.dart).
359
+
360
+ #### Sending RPCs to a skir service
361
+
362
+ Full example [here](https://github.com/gepheum/skir-dart-example/blob/main/bin/call_service.dart).
363
+
364
+ ### Reflection
365
+
366
+ Reflection allows you to inspect a skir type at runtime.
367
+
368
+ ```dart
369
+ import 'package:skir/skir.dart' as skir;
370
+
371
+ final fieldNames = <String>[];
372
+ for (final field in User.serializer.typeDescriptor.fields) {
373
+ fieldNames.add(field.name);
374
+ }
375
+ print(fieldNames);
376
+ // [user_id, name, quote, pets, subscription_status]
377
+
378
+ // A type descriptor can be serialized to JSON and deserialized later.
379
+ final typeDescriptor = skir.TypeDescriptor.parseFromJson(
380
+ User.serializer.typeDescriptor.asJson,
381
+ );
382
+ print("Type descriptor deserialized successfully");
383
+
384
+ // The 'allStringsToUpperCase' function uses reflection to convert all the
385
+ // strings contained in a given Skir value to upper case.
386
+ // See the implementation at
387
+ // https://github.com/gepheum/skir-dart-example/blob/main/lib/all_strings_to_upper_case.dart
388
+ print(allStringsToUpperCase<User>(tarzan, User.serializer.typeDescriptor));
389
+ // User(
390
+ // userId: 123,
391
+ // name: "TARZAN",
392
+ // quote: "AAAAAAAAAAYAAAAAAAAAAYAAAAAAAAAA",
393
+ // pets: [
394
+ // User_Pet(
395
+ // name: "CHEETA",
396
+ // heightInMeters: 1.67,
397
+ // picture: "🐒",
398
+ // ),
399
+ // ],
400
+ // subscriptionStatus: User_SubscriptionStatus.wrapTrial(
401
+ // User_Trial(
402
+ // startTime: DateTime.fromMillisecondsSinceEpoch(
403
+ // // 2025-04-02T11:13:29.000Z
404
+ // 1743592409000
405
+ // ),
406
+ // )
407
+ // ),
408
+ // )
409
+ ```
@@ -0,0 +1,13 @@
1
+ import { type CodeGenerator } from "skir-internal";
2
+ import { z } from "zod";
3
+ declare const Config: z.ZodObject<{}, z.core.$strip>;
4
+ type Config = z.infer<typeof Config>;
5
+ declare class DartCodeGenerator implements CodeGenerator<Config> {
6
+ readonly id = "dart";
7
+ readonly configType: z.ZodObject<{}, z.core.$strip>;
8
+ readonly version = "1.0.0";
9
+ generateCode(input: CodeGenerator.Input<Config>): CodeGenerator.Output;
10
+ }
11
+ export declare const GENERATOR: DartCodeGenerator;
12
+ export {};
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,aAAa,EAOnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB,QAAA,MAAM,MAAM,gCAAe,CAAC;AAE5B,KAAK,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAErC,cAAM,iBAAkB,YAAW,aAAa,CAAC,MAAM,CAAC;IACtD,QAAQ,CAAC,EAAE,UAAU;IACrB,QAAQ,CAAC,UAAU,iCAAU;IAC7B,QAAQ,CAAC,OAAO,WAAW;IAE3B,YAAY,CAAC,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM;CAWvE;AAo0BD,eAAO,MAAM,SAAS,mBAA0B,CAAC"}