@alepha/protobuf 0.14.2 → 0.14.3

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/dist/index.d.ts CHANGED
@@ -399,6 +399,7 @@ declare class EventManager {
399
399
  protected events: Record<string, Array<Hook>>;
400
400
  constructor(logFn?: () => LoggerInterface | undefined);
401
401
  protected get log(): LoggerInterface | undefined;
402
+ clear(): void;
402
403
  /**
403
404
  * Registers a hook for the specified event.
404
405
  */
@@ -931,6 +932,34 @@ interface State {
931
932
  "alepha.logger"?: LoggerInterface;
932
933
  /**
933
934
  * If defined, the Alepha container will only register this service and its dependencies.
935
+ *
936
+ * @example
937
+ * ```ts
938
+ * class MigrateCmd {
939
+ * db = $inject(DatabaseProvider);
940
+ * alepha = $inject(Alepha);
941
+ * env = $env(
942
+ * t.object({
943
+ * MIGRATE: t.optional(t.boolean()),
944
+ * }),
945
+ * );
946
+ *
947
+ * constructor() {
948
+ * if (this.env.MIGRATE) {
949
+ * this.alepha.set("alepha.target", MigrateCmd);
950
+ * }
951
+ * }
952
+ *
953
+ * ready = $hook({
954
+ * on: "ready",
955
+ * handler: async () => {
956
+ * if (this.env.MIGRATE) {
957
+ * await this.db.migrate();
958
+ * }
959
+ * },
960
+ * });
961
+ * }
962
+ * ```
934
963
  */
935
964
  "alepha.target"?: Service;
936
965
  /**
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@alepha/protobuf",
3
3
  "description": "Protocol Buffers (Protobuf) codec support for Alepha framework.",
4
4
  "author": "Nicolas Foures",
5
- "version": "0.14.2",
5
+ "version": "0.14.3",
6
6
  "type": "module",
7
7
  "engines": {
8
8
  "node": ">=22.0.0"
@@ -18,13 +18,13 @@
18
18
  "protobufjs": "^8.0.0"
19
19
  },
20
20
  "devDependencies": {
21
- "alepha": "0.14.2",
22
- "tsdown": "^0.19.0-beta.2",
21
+ "alepha": "0.14.3",
22
+ "tsdown": "^0.19.0-beta.5",
23
23
  "typescript": "^5.9.3",
24
24
  "vitest": "^4.0.16"
25
25
  },
26
26
  "peerDependencies": {
27
- "alepha": "0.14.2"
27
+ "alepha": "0.14.3"
28
28
  },
29
29
  "scripts": {
30
30
  "lint": "alepha lint",
@@ -0,0 +1,465 @@
1
+ import { Alepha, t } from "alepha";
2
+ import { describe, test } from "vitest";
3
+ import { AlephaProtobuf, ProtobufProvider } from "../index.ts";
4
+
5
+ const alepha = Alepha.create().with(AlephaProtobuf);
6
+ const protobuf = alepha.inject(ProtobufProvider);
7
+
8
+ describe("ProtobufProvider", () => {
9
+ describe("Basic types", () => {
10
+ test("should handle primitive types", async ({ expect }) => {
11
+ const userSchema = t.object({
12
+ username: t.text(),
13
+ createdAt: t.datetime(),
14
+ age: t.integer(),
15
+ isActive: t.boolean(),
16
+ score: t.number(),
17
+ bigNumber: t.bigint(),
18
+ level: t.integer(),
19
+ points: t.integer(),
20
+ });
21
+
22
+ const schema = protobuf.createProtobufSchema(userSchema);
23
+ expect(schema).toBe(
24
+ `package root;
25
+ syntax = "proto3";
26
+
27
+ message Target {
28
+ string username = 1;
29
+ string createdAt = 2;
30
+ int32 age = 3;
31
+ bool isActive = 4;
32
+ double score = 5;
33
+ int64 bigNumber = 6;
34
+ int32 level = 7;
35
+ int32 points = 8;
36
+ }
37
+ `,
38
+ );
39
+ });
40
+
41
+ test("should encode and decode primitive types", async ({ expect }) => {
42
+ const userSchema = t.object({
43
+ username: t.text(),
44
+ createdAt: t.datetime(),
45
+ age: t.integer(),
46
+ isActive: t.boolean(),
47
+ });
48
+
49
+ const createdAt = new Date().toISOString();
50
+ const data = {
51
+ username: "John Doe",
52
+ createdAt,
53
+ age: 30,
54
+ isActive: true,
55
+ };
56
+ const buf = alepha.codec.encode(userSchema, data, {
57
+ as: "binary",
58
+ encoder: "protobuf",
59
+ });
60
+ expect(buf).toBeInstanceOf(Uint8Array);
61
+
62
+ const user = alepha.codec.decode(userSchema, buf, {
63
+ encoder: "protobuf",
64
+ });
65
+ // When decoding, dates come back as dayjs objects, so compare the ISO strings
66
+ expect(user.username).toBe(data.username);
67
+ expect(user.age).toBe(data.age);
68
+ expect(user.isActive).toBe(data.isActive);
69
+ // Compare datetime as ISO strings since dayjs might be used
70
+ expect(user.createdAt).toBe(createdAt);
71
+ });
72
+ });
73
+
74
+ describe("Arrays", () => {
75
+ test("should handle arrays of primitives", async ({ expect }) => {
76
+ const schema = t.object({
77
+ tags: t.array(t.text()),
78
+ scores: t.array(t.number()),
79
+ flags: t.array(t.boolean()),
80
+ });
81
+
82
+ const protoSchema = protobuf.createProtobufSchema(schema);
83
+ expect(protoSchema).toBe(
84
+ `package root;
85
+ syntax = "proto3";
86
+
87
+ message Target {
88
+ repeated string tags = 1;
89
+ repeated double scores = 2;
90
+ repeated bool flags = 3;
91
+ }
92
+ `,
93
+ );
94
+ });
95
+
96
+ test("should handle arrays of objects", async ({ expect }) => {
97
+ const schema = t.object({
98
+ users: t.array(
99
+ t.object({
100
+ name: t.text(),
101
+ age: t.integer(),
102
+ }),
103
+ ),
104
+ });
105
+
106
+ const protoSchema = protobuf.createProtobufSchema(schema);
107
+ expect(protoSchema).toBe(
108
+ `package root;
109
+ syntax = "proto3";
110
+
111
+ message Target_users {
112
+ string name = 1;
113
+ int32 age = 2;
114
+ }
115
+ message Target {
116
+ repeated Target_users users = 1;
117
+ }
118
+ `,
119
+ );
120
+ });
121
+
122
+ test("should encode and decode arrays", async ({ expect }) => {
123
+ const schema = t.object({
124
+ tags: t.array(t.text()),
125
+ users: t.array(
126
+ t.object({
127
+ name: t.text(),
128
+ age: t.integer(),
129
+ }),
130
+ ),
131
+ });
132
+
133
+ const data = {
134
+ tags: ["admin", "user"],
135
+ users: [
136
+ { name: "John", age: 30 },
137
+ { name: "Jane", age: 25 },
138
+ ],
139
+ };
140
+
141
+ const buf = alepha.codec.encode(schema, data, {
142
+ as: "binary",
143
+ encoder: "protobuf",
144
+ });
145
+ expect(buf).toBeInstanceOf(Uint8Array);
146
+
147
+ const decoded = alepha.codec.decode(schema, buf, {
148
+ encoder: "protobuf",
149
+ });
150
+ expect(decoded).toEqual(data);
151
+ });
152
+ });
153
+
154
+ describe("Nested objects", () => {
155
+ test("should handle nested objects", async ({ expect }) => {
156
+ const schema = t.object({
157
+ user: t.object({
158
+ profile: t.object({
159
+ name: t.text(),
160
+ bio: t.text(),
161
+ }),
162
+ settings: t.object({
163
+ theme: t.text(),
164
+ notifications: t.boolean(),
165
+ }),
166
+ }),
167
+ });
168
+
169
+ const protoSchema = protobuf.createProtobufSchema(schema);
170
+ expect(protoSchema).toBe(
171
+ `package root;
172
+ syntax = "proto3";
173
+
174
+ message Target_user_profile {
175
+ string name = 1;
176
+ string bio = 2;
177
+ }
178
+ message Target_user_settings {
179
+ string theme = 1;
180
+ bool notifications = 2;
181
+ }
182
+ message Target_user {
183
+ Target_user_profile profile = 1;
184
+ Target_user_settings settings = 2;
185
+ }
186
+ message Target {
187
+ Target_user user = 1;
188
+ }
189
+ `,
190
+ );
191
+ });
192
+
193
+ test("should encode and decode nested objects", async ({ expect }) => {
194
+ const schema = t.object({
195
+ user: t.object({
196
+ profile: t.object({
197
+ name: t.text(),
198
+ bio: t.text(),
199
+ }),
200
+ age: t.integer(),
201
+ }),
202
+ });
203
+
204
+ const data = {
205
+ user: {
206
+ profile: {
207
+ name: "John Doe",
208
+ bio: "Software developer",
209
+ },
210
+ age: 30,
211
+ },
212
+ };
213
+
214
+ const buf = alepha.codec.encode(schema, data, {
215
+ as: "binary",
216
+ encoder: "protobuf",
217
+ });
218
+ expect(buf).toBeInstanceOf(Uint8Array);
219
+
220
+ const decoded = alepha.codec.decode(schema, buf, {
221
+ encoder: "protobuf",
222
+ });
223
+ expect(decoded).toEqual(data);
224
+ });
225
+ });
226
+
227
+ describe("Optional and nullable types", () => {
228
+ test("should handle nullable types", async ({ expect }) => {
229
+ const schema = t.object({
230
+ name: t.text(),
231
+ email: t.nullable(t.text()),
232
+ age: t.nullable(t.integer()),
233
+ });
234
+
235
+ const protoSchema = protobuf.createProtobufSchema(schema);
236
+ expect(protoSchema).toBe(
237
+ `package root;
238
+ syntax = "proto3";
239
+
240
+ message Target {
241
+ string name = 1;
242
+ string email = 2;
243
+ int32 age = 3;
244
+ }
245
+ `,
246
+ );
247
+ });
248
+
249
+ test("should encode and decode nullable types", async ({ expect }) => {
250
+ const schema = t.object({
251
+ name: t.text(),
252
+ email: t.nullable(t.text()),
253
+ });
254
+
255
+ const data1 = {
256
+ name: "John",
257
+ email: "john@example.com",
258
+ };
259
+
260
+ const data2 = {
261
+ name: "Jane",
262
+ email: "", // In proto3, null string becomes empty string
263
+ };
264
+
265
+ const buf1 = alepha.codec.encode(schema, data1, {
266
+ as: "binary",
267
+ encoder: "protobuf",
268
+ });
269
+ const buf2 = alepha.codec.encode(schema, data2, {
270
+ as: "binary",
271
+ encoder: "protobuf",
272
+ });
273
+
274
+ const decoded1 = alepha.codec.decode(schema, buf1, {
275
+ encoder: "protobuf",
276
+ });
277
+ const decoded2 = alepha.codec.decode(schema, buf2, {
278
+ encoder: "protobuf",
279
+ });
280
+
281
+ expect(decoded1).toEqual(data1);
282
+ expect(decoded2.name).toEqual("Jane");
283
+ expect(decoded2.email).toEqual(""); // Proto3 default value for string is ""
284
+ });
285
+ });
286
+
287
+ describe("Enums and special types", () => {
288
+ test("should handle enums", async ({ expect }) => {
289
+ const schema = t.object({
290
+ status: t.enum(["ACTIVE", "INACTIVE", "PENDING"]),
291
+ role: t.enum(["USER", "ADMIN", "MODERATOR"]),
292
+ });
293
+
294
+ const protoSchema = protobuf.createProtobufSchema(schema);
295
+ expect(protoSchema).toBe(
296
+ `package root;
297
+ syntax = "proto3";
298
+
299
+ enum Status {
300
+ ACTIVE = 0;
301
+ INACTIVE = 1;
302
+ PENDING = 2;
303
+ }
304
+ enum Role {
305
+ USER = 0;
306
+ ADMIN = 1;
307
+ MODERATOR = 2;
308
+ }
309
+ message Target {
310
+ Status status = 1;
311
+ Role role = 2;
312
+ }
313
+ `,
314
+ );
315
+ });
316
+
317
+ test("should handle special string types", async ({ expect }) => {
318
+ const schema = t.object({
319
+ id: t.uuid(),
320
+ createdAt: t.datetime(),
321
+ birthDate: t.date(),
322
+ shortText: t.text({ size: "short" }),
323
+ longText: t.text({ size: "long" }),
324
+ richText: t.text({ size: "rich" }),
325
+ });
326
+
327
+ const protoSchema = protobuf.createProtobufSchema(schema);
328
+ expect(protoSchema).toBe(
329
+ `package root;
330
+ syntax = "proto3";
331
+
332
+ message Target {
333
+ string id = 1;
334
+ string createdAt = 2;
335
+ string birthDate = 3;
336
+ string shortText = 4;
337
+ string longText = 5;
338
+ string richText = 6;
339
+ }
340
+ `,
341
+ );
342
+ });
343
+
344
+ test("should encode and decode enums", async ({ expect }) => {
345
+ const schema = t.object({
346
+ status: t.enum(["ACTIVE", "INACTIVE"]),
347
+ name: t.text(),
348
+ });
349
+
350
+ const data = {
351
+ status: "ACTIVE",
352
+ name: "Test",
353
+ };
354
+
355
+ const buf = alepha.codec.encode(schema, data, {
356
+ as: "binary",
357
+ encoder: "protobuf",
358
+ });
359
+ const decoded = alepha.codec.decode(schema, buf, {
360
+ encoder: "protobuf",
361
+ });
362
+
363
+ expect(decoded).toEqual(data);
364
+ });
365
+ });
366
+
367
+ describe("Records (maps)", () => {
368
+ test("should handle records", async ({ expect }) => {
369
+ const schema = t.object({
370
+ metadata: t.record(t.text(), t.text()),
371
+ scores: t.record(t.text(), t.number()),
372
+ });
373
+
374
+ const protoSchema = protobuf.createProtobufSchema(schema);
375
+ expect(protoSchema).toBe(
376
+ `package root;
377
+ syntax = "proto3";
378
+
379
+ message Target {
380
+ map<string, string> metadata = 1;
381
+ map<string, double> scores = 2;
382
+ }
383
+ `,
384
+ );
385
+ });
386
+
387
+ test("should encode and decode records", async ({ expect }) => {
388
+ const schema = t.object({
389
+ metadata: t.record(t.text(), t.text()),
390
+ });
391
+
392
+ const data = {
393
+ metadata: {
394
+ version: "1.0.0",
395
+ author: "John Doe",
396
+ },
397
+ };
398
+
399
+ const buf = alepha.codec.encode(schema, data, {
400
+ as: "binary",
401
+ encoder: "protobuf",
402
+ });
403
+ const decoded = alepha.codec.decode(schema, buf, {
404
+ encoder: "protobuf",
405
+ });
406
+
407
+ expect(decoded).toEqual(data);
408
+ });
409
+ });
410
+
411
+ describe("Complex combinations", () => {
412
+ test("should handle complex nested structures", async ({ expect }) => {
413
+ const schema = t.object({
414
+ user: t.object({
415
+ id: t.uuid(),
416
+ profile: t.object({
417
+ name: t.text(),
418
+ age: t.nullable(t.integer()),
419
+ tags: t.array(t.text()),
420
+ }),
421
+ posts: t.array(
422
+ t.object({
423
+ title: t.text(),
424
+ content: t.text({ size: "rich" }),
425
+ metadata: t.record(t.text(), t.text()),
426
+ }),
427
+ ),
428
+ }),
429
+ status: t.enum(["ACTIVE", "INACTIVE"]),
430
+ });
431
+
432
+ const data = {
433
+ user: {
434
+ id: "123e4567-e89b-12d3-a456-426614174000",
435
+ profile: {
436
+ name: "John Doe",
437
+ age: 30,
438
+ tags: ["developer", "typescript"],
439
+ },
440
+ posts: [
441
+ {
442
+ title: "Hello World",
443
+ content: "This is my first post",
444
+ metadata: {
445
+ category: "tech",
446
+ draft: "false",
447
+ },
448
+ },
449
+ ],
450
+ },
451
+ status: "ACTIVE",
452
+ };
453
+
454
+ const buf = alepha.codec.encode(schema, data, {
455
+ as: "binary",
456
+ encoder: "protobuf",
457
+ });
458
+ const decoded = alepha.codec.decode(schema, buf, {
459
+ encoder: "protobuf",
460
+ });
461
+
462
+ expect(decoded).toEqual(data);
463
+ });
464
+ });
465
+ });