@alepha/protobuf 0.14.2 → 0.14.4

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
  */
@@ -874,6 +875,7 @@ declare class Alepha {
874
875
  as?: string[];
875
876
  module?: string;
876
877
  }>;
878
+ dump(): AlephaDump;
877
879
  services<T extends object>(base: Service<T>): Array<T>;
878
880
  /**
879
881
  * Get all primitives of the specified type.
@@ -889,6 +891,20 @@ interface Hook<T extends keyof Hooks = any> {
889
891
  priority?: "first" | "last";
890
892
  callback: (payload: Hooks[T]) => Async<void>;
891
893
  }
894
+ interface AlephaDump {
895
+ env: Record<string, AlephaDumpEnvVariable>;
896
+ providers: Record<string, {
897
+ from: string[];
898
+ as?: string[];
899
+ module?: string;
900
+ }>;
901
+ }
902
+ interface AlephaDumpEnvVariable {
903
+ description: string;
904
+ default?: string;
905
+ required?: boolean;
906
+ enum?: Array<string>;
907
+ }
892
908
  /**
893
909
  * This is how we store services in the Alepha container.
894
910
  */
@@ -931,6 +947,34 @@ interface State {
931
947
  "alepha.logger"?: LoggerInterface;
932
948
  /**
933
949
  * If defined, the Alepha container will only register this service and its dependencies.
950
+ *
951
+ * @example
952
+ * ```ts
953
+ * class MigrateCmd {
954
+ * db = $inject(DatabaseProvider);
955
+ * alepha = $inject(Alepha);
956
+ * env = $env(
957
+ * t.object({
958
+ * MIGRATE: t.optional(t.boolean()),
959
+ * }),
960
+ * );
961
+ *
962
+ * constructor() {
963
+ * if (this.env.MIGRATE) {
964
+ * this.alepha.set("alepha.target", MigrateCmd);
965
+ * }
966
+ * }
967
+ *
968
+ * ready = $hook({
969
+ * on: "ready",
970
+ * handler: async () => {
971
+ * if (this.env.MIGRATE) {
972
+ * await this.db.migrate();
973
+ * }
974
+ * },
975
+ * });
976
+ * }
977
+ * ```
934
978
  */
935
979
  "alepha.target"?: Service;
936
980
  /**
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.4",
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.4",
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.4"
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
+ });