@rocketmq/schema 0.1.0

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,21 @@
1
+
2
+
3
+ > @rocketmq/schema@0.1.0 build /home/edilson/learnspace/rocketmq-broker/client-ts/packages/schema
4
+ > tsup
5
+
6
+ CLI Building entry: src/index.ts
7
+ CLI Using tsconfig: tsconfig.json
8
+ CLI tsup v8.5.1
9
+ CLI Using tsup config: /home/edilson/learnspace/rocketmq-broker/client-ts/packages/schema/tsup.config.ts
10
+ CLI Target: es2022
11
+ CLI Cleaning output folder
12
+ ESM Build start
13
+ CJS Build start
14
+ CJS dist/index.cjs 3.21 KB
15
+ CJS ⚡️ Build success in 10ms
16
+ ESM dist/index.js 2.11 KB
17
+ ESM ⚡️ Build success in 10ms
18
+ DTS Build start
19
+ DTS ⚡️ Build success in 932ms
20
+ DTS dist/index.d.ts 4.01 KB
21
+ DTS dist/index.d.cts 4.01 KB
package/dist/index.cjs ADDED
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Field: () => Field,
24
+ Schema: () => Schema,
25
+ SchemaRegistry: () => SchemaRegistry,
26
+ defaultRegistry: () => defaultRegistry
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/registry.ts
31
+ var SchemaRegistry = class {
32
+ /** Queue name → schema entry. */
33
+ byQueue = /* @__PURE__ */ new Map();
34
+ /** Class constructor → field metadata (populated by decorators). */
35
+ fieldStore = /* @__PURE__ */ new Map();
36
+ /** Class constructor → subject prefix (populated by @Schema("subject")). */
37
+ subjectStore = /* @__PURE__ */ new Map();
38
+ /** Binds a queue name to a fully resolved schema entry. */
39
+ register(queueName, entry) {
40
+ this.byQueue.set(queueName, entry);
41
+ }
42
+ /** Returns the schema entry for a queue, or undefined if unregistered. */
43
+ lookup(queueName) {
44
+ return this.byQueue.get(queueName);
45
+ }
46
+ /** Returns all registered schema entries. */
47
+ listAll() {
48
+ return [...this.byQueue.values()];
49
+ }
50
+ /** Stores the subject prefix set by @Schema("subject"). */
51
+ setSubject(ctor, subject) {
52
+ this.subjectStore.set(ctor, subject);
53
+ }
54
+ /** Returns the subject prefix for a class, if any. */
55
+ getSubject(ctor) {
56
+ return this.subjectStore.get(ctor);
57
+ }
58
+ /** Returns the mutable field list for a class, creating it if absent. */
59
+ getOrCreateFields(ctor) {
60
+ let fields = this.fieldStore.get(ctor);
61
+ if (!fields) {
62
+ fields = [];
63
+ this.fieldStore.set(ctor, fields);
64
+ }
65
+ return fields;
66
+ }
67
+ /** Returns the field metadata for a class (read-only snapshot). */
68
+ getFields(ctor) {
69
+ return this.fieldStore.get(ctor) ?? [];
70
+ }
71
+ };
72
+ var defaultRegistry = new SchemaRegistry();
73
+
74
+ // src/decorators.ts
75
+ function Schema(subject) {
76
+ return function(target, _ctx) {
77
+ defaultRegistry.getOrCreateFields(target);
78
+ if (subject) {
79
+ defaultRegistry.setSubject(target, subject);
80
+ }
81
+ return target;
82
+ };
83
+ }
84
+ function Field(opts) {
85
+ return function(_value, ctx) {
86
+ const name = String(ctx.name);
87
+ ctx.addInitializer(function() {
88
+ const ctor = this.constructor;
89
+ const fields = defaultRegistry.getOrCreateFields(ctor);
90
+ if (fields.some((f) => f.name === name)) return;
91
+ fields.push({
92
+ name,
93
+ protoType: opts?.type ?? "string",
94
+ number: fields.length + 1
95
+ });
96
+ });
97
+ };
98
+ }
99
+ // Annotate the CommonJS export names for ESM import in node:
100
+ 0 && (module.exports = {
101
+ Field,
102
+ Schema,
103
+ SchemaRegistry,
104
+ defaultRegistry
105
+ });
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Protobuf wire types supported by the schema decorator system.
3
+ *
4
+ * Maps 1:1 to proto3 scalar types so the SDK can generate
5
+ * `.proto` definitions without requiring protobuf knowledge.
6
+ *
7
+ * Usage:
8
+ * @Field({ type: "int32" }) qty!: number;
9
+ */
10
+ type ProtoType = 'string' | 'int32' | 'int64' | 'uint32' | 'uint64' | 'float' | 'double' | 'bool' | 'bytes';
11
+ /** Metadata captured by the @Field() decorator for a single class property. */
12
+ interface FieldMeta {
13
+ /** Property name on the decorated class. */
14
+ name: string;
15
+ /** Proto3 scalar type. Defaults to "string" when omitted. */
16
+ protoType: ProtoType;
17
+ /** 1-based field number for proto ordering. */
18
+ number: number;
19
+ }
20
+ /** Collected metadata for a decorated schema class. */
21
+ interface SchemaEntry {
22
+ /** Class constructor reference. */
23
+ ctor: Function;
24
+ /** Human-readable schema name (defaults to class name). */
25
+ name: string;
26
+ /** Optional subject prefix for registry-based validation. */
27
+ subject?: string;
28
+ /** Ordered field definitions extracted from @Field() decorators. */
29
+ fields: FieldMeta[];
30
+ }
31
+
32
+ /**
33
+ * In-memory schema registry.
34
+ *
35
+ * Replaces the module-level Map globals from the original schema.ts.
36
+ * Injectable via constructor so tests can use isolated instances.
37
+ *
38
+ * Usage:
39
+ * const registry = new SchemaRegistry();
40
+ * registry.register("orders", entry);
41
+ * const entry = registry.lookup("orders");
42
+ */
43
+
44
+ declare class SchemaRegistry {
45
+ /** Queue name → schema entry. */
46
+ private byQueue;
47
+ /** Class constructor → field metadata (populated by decorators). */
48
+ private fieldStore;
49
+ /** Class constructor → subject prefix (populated by @Schema("subject")). */
50
+ private subjectStore;
51
+ /** Binds a queue name to a fully resolved schema entry. */
52
+ register(queueName: string, entry: SchemaEntry): void;
53
+ /** Returns the schema entry for a queue, or undefined if unregistered. */
54
+ lookup(queueName: string): SchemaEntry | undefined;
55
+ /** Returns all registered schema entries. */
56
+ listAll(): SchemaEntry[];
57
+ /** Stores the subject prefix set by @Schema("subject"). */
58
+ setSubject(ctor: Function, subject: string): void;
59
+ /** Returns the subject prefix for a class, if any. */
60
+ getSubject(ctor: Function): string | undefined;
61
+ /** Returns the mutable field list for a class, creating it if absent. */
62
+ getOrCreateFields(ctor: Function): FieldMeta[];
63
+ /** Returns the field metadata for a class (read-only snapshot). */
64
+ getFields(ctor: Function): readonly FieldMeta[];
65
+ }
66
+ /**
67
+ * Default singleton registry used by decorators.
68
+ *
69
+ * Exported so @Schema() and @Field() can register metadata at class
70
+ * definition time without requiring explicit wiring. The core package
71
+ * reads from this same instance during publish/consume.
72
+ */
73
+ declare const defaultRegistry: SchemaRegistry;
74
+
75
+ /**
76
+ * TC39 stage 3 decorators for schema definition.
77
+ *
78
+ * Collects field names and types from decorated classes into the
79
+ * default SchemaRegistry so the SDK can generate proto3 definitions
80
+ * and validate payloads at runtime.
81
+ *
82
+ * Usage:
83
+ * @Schema("notifications")
84
+ * class Notification {
85
+ * @Field() id!: string;
86
+ * @Field({ type: "int64" }) timestamp!: number;
87
+ * }
88
+ */
89
+
90
+ /**
91
+ * Marks a class as a schema definition.
92
+ *
93
+ * @param subject - Optional subject prefix for registry-based validation
94
+ * (e.g. "notifications" → "notifications-value").
95
+ */
96
+ declare function Schema(subject?: string): <T extends new (...args: unknown[]) => object>(target: T, _ctx: ClassDecoratorContext) => T;
97
+ interface FieldOptions {
98
+ /** Proto3 scalar type. Defaults to "string". */
99
+ type?: ProtoType;
100
+ }
101
+ /**
102
+ * Marks a property as a schema field (TC39 stage 3 field decorator).
103
+ *
104
+ * Defers registration to `addInitializer` so the class constructor is
105
+ * fully defined before we read `this.constructor`.
106
+ */
107
+ declare function Field(opts?: FieldOptions): (_value: undefined, ctx: ClassFieldDecoratorContext) => void;
108
+
109
+ export { Field, type FieldMeta, type ProtoType, Schema, type SchemaEntry, SchemaRegistry, defaultRegistry };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Protobuf wire types supported by the schema decorator system.
3
+ *
4
+ * Maps 1:1 to proto3 scalar types so the SDK can generate
5
+ * `.proto` definitions without requiring protobuf knowledge.
6
+ *
7
+ * Usage:
8
+ * @Field({ type: "int32" }) qty!: number;
9
+ */
10
+ type ProtoType = 'string' | 'int32' | 'int64' | 'uint32' | 'uint64' | 'float' | 'double' | 'bool' | 'bytes';
11
+ /** Metadata captured by the @Field() decorator for a single class property. */
12
+ interface FieldMeta {
13
+ /** Property name on the decorated class. */
14
+ name: string;
15
+ /** Proto3 scalar type. Defaults to "string" when omitted. */
16
+ protoType: ProtoType;
17
+ /** 1-based field number for proto ordering. */
18
+ number: number;
19
+ }
20
+ /** Collected metadata for a decorated schema class. */
21
+ interface SchemaEntry {
22
+ /** Class constructor reference. */
23
+ ctor: Function;
24
+ /** Human-readable schema name (defaults to class name). */
25
+ name: string;
26
+ /** Optional subject prefix for registry-based validation. */
27
+ subject?: string;
28
+ /** Ordered field definitions extracted from @Field() decorators. */
29
+ fields: FieldMeta[];
30
+ }
31
+
32
+ /**
33
+ * In-memory schema registry.
34
+ *
35
+ * Replaces the module-level Map globals from the original schema.ts.
36
+ * Injectable via constructor so tests can use isolated instances.
37
+ *
38
+ * Usage:
39
+ * const registry = new SchemaRegistry();
40
+ * registry.register("orders", entry);
41
+ * const entry = registry.lookup("orders");
42
+ */
43
+
44
+ declare class SchemaRegistry {
45
+ /** Queue name → schema entry. */
46
+ private byQueue;
47
+ /** Class constructor → field metadata (populated by decorators). */
48
+ private fieldStore;
49
+ /** Class constructor → subject prefix (populated by @Schema("subject")). */
50
+ private subjectStore;
51
+ /** Binds a queue name to a fully resolved schema entry. */
52
+ register(queueName: string, entry: SchemaEntry): void;
53
+ /** Returns the schema entry for a queue, or undefined if unregistered. */
54
+ lookup(queueName: string): SchemaEntry | undefined;
55
+ /** Returns all registered schema entries. */
56
+ listAll(): SchemaEntry[];
57
+ /** Stores the subject prefix set by @Schema("subject"). */
58
+ setSubject(ctor: Function, subject: string): void;
59
+ /** Returns the subject prefix for a class, if any. */
60
+ getSubject(ctor: Function): string | undefined;
61
+ /** Returns the mutable field list for a class, creating it if absent. */
62
+ getOrCreateFields(ctor: Function): FieldMeta[];
63
+ /** Returns the field metadata for a class (read-only snapshot). */
64
+ getFields(ctor: Function): readonly FieldMeta[];
65
+ }
66
+ /**
67
+ * Default singleton registry used by decorators.
68
+ *
69
+ * Exported so @Schema() and @Field() can register metadata at class
70
+ * definition time without requiring explicit wiring. The core package
71
+ * reads from this same instance during publish/consume.
72
+ */
73
+ declare const defaultRegistry: SchemaRegistry;
74
+
75
+ /**
76
+ * TC39 stage 3 decorators for schema definition.
77
+ *
78
+ * Collects field names and types from decorated classes into the
79
+ * default SchemaRegistry so the SDK can generate proto3 definitions
80
+ * and validate payloads at runtime.
81
+ *
82
+ * Usage:
83
+ * @Schema("notifications")
84
+ * class Notification {
85
+ * @Field() id!: string;
86
+ * @Field({ type: "int64" }) timestamp!: number;
87
+ * }
88
+ */
89
+
90
+ /**
91
+ * Marks a class as a schema definition.
92
+ *
93
+ * @param subject - Optional subject prefix for registry-based validation
94
+ * (e.g. "notifications" → "notifications-value").
95
+ */
96
+ declare function Schema(subject?: string): <T extends new (...args: unknown[]) => object>(target: T, _ctx: ClassDecoratorContext) => T;
97
+ interface FieldOptions {
98
+ /** Proto3 scalar type. Defaults to "string". */
99
+ type?: ProtoType;
100
+ }
101
+ /**
102
+ * Marks a property as a schema field (TC39 stage 3 field decorator).
103
+ *
104
+ * Defers registration to `addInitializer` so the class constructor is
105
+ * fully defined before we read `this.constructor`.
106
+ */
107
+ declare function Field(opts?: FieldOptions): (_value: undefined, ctx: ClassFieldDecoratorContext) => void;
108
+
109
+ export { Field, type FieldMeta, type ProtoType, Schema, type SchemaEntry, SchemaRegistry, defaultRegistry };
package/dist/index.js ADDED
@@ -0,0 +1,75 @@
1
+ // src/registry.ts
2
+ var SchemaRegistry = class {
3
+ /** Queue name → schema entry. */
4
+ byQueue = /* @__PURE__ */ new Map();
5
+ /** Class constructor → field metadata (populated by decorators). */
6
+ fieldStore = /* @__PURE__ */ new Map();
7
+ /** Class constructor → subject prefix (populated by @Schema("subject")). */
8
+ subjectStore = /* @__PURE__ */ new Map();
9
+ /** Binds a queue name to a fully resolved schema entry. */
10
+ register(queueName, entry) {
11
+ this.byQueue.set(queueName, entry);
12
+ }
13
+ /** Returns the schema entry for a queue, or undefined if unregistered. */
14
+ lookup(queueName) {
15
+ return this.byQueue.get(queueName);
16
+ }
17
+ /** Returns all registered schema entries. */
18
+ listAll() {
19
+ return [...this.byQueue.values()];
20
+ }
21
+ /** Stores the subject prefix set by @Schema("subject"). */
22
+ setSubject(ctor, subject) {
23
+ this.subjectStore.set(ctor, subject);
24
+ }
25
+ /** Returns the subject prefix for a class, if any. */
26
+ getSubject(ctor) {
27
+ return this.subjectStore.get(ctor);
28
+ }
29
+ /** Returns the mutable field list for a class, creating it if absent. */
30
+ getOrCreateFields(ctor) {
31
+ let fields = this.fieldStore.get(ctor);
32
+ if (!fields) {
33
+ fields = [];
34
+ this.fieldStore.set(ctor, fields);
35
+ }
36
+ return fields;
37
+ }
38
+ /** Returns the field metadata for a class (read-only snapshot). */
39
+ getFields(ctor) {
40
+ return this.fieldStore.get(ctor) ?? [];
41
+ }
42
+ };
43
+ var defaultRegistry = new SchemaRegistry();
44
+
45
+ // src/decorators.ts
46
+ function Schema(subject) {
47
+ return function(target, _ctx) {
48
+ defaultRegistry.getOrCreateFields(target);
49
+ if (subject) {
50
+ defaultRegistry.setSubject(target, subject);
51
+ }
52
+ return target;
53
+ };
54
+ }
55
+ function Field(opts) {
56
+ return function(_value, ctx) {
57
+ const name = String(ctx.name);
58
+ ctx.addInitializer(function() {
59
+ const ctor = this.constructor;
60
+ const fields = defaultRegistry.getOrCreateFields(ctor);
61
+ if (fields.some((f) => f.name === name)) return;
62
+ fields.push({
63
+ name,
64
+ protoType: opts?.type ?? "string",
65
+ number: fields.length + 1
66
+ });
67
+ });
68
+ };
69
+ }
70
+ export {
71
+ Field,
72
+ Schema,
73
+ SchemaRegistry,
74
+ defaultRegistry
75
+ };
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@rocketmq/schema",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "import": "./dist/index.js",
8
+ "types": "./dist/index.d.ts"
9
+ }
10
+ },
11
+ "scripts": {
12
+ "build": "tsup",
13
+ "test": "vitest run"
14
+ }
15
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Tests for @Schema() and @Field() decorators.
3
+ *
4
+ * Uses the defaultRegistry singleton to verify that decorators
5
+ * register metadata correctly. Each test uses unique classes to
6
+ * avoid cross-contamination in the shared singleton.
7
+ */
8
+
9
+ import { describe, it, expect } from 'vitest';
10
+ import { Schema, Field } from './decorators.js';
11
+ import { defaultRegistry } from './registry.js';
12
+
13
+ describe('@Schema() decorator', () => {
14
+ it('registers field list for a decorated class', () => {
15
+ @Schema()
16
+ class Alpha {
17
+ @Field()
18
+ id!: string;
19
+ }
20
+ // Force field registration via instantiation
21
+ new Alpha();
22
+ const fields = defaultRegistry.getFields(Alpha);
23
+ expect(fields.length).toBeGreaterThanOrEqual(1);
24
+ expect(fields[0].name).toBe('id');
25
+ });
26
+
27
+ it('stores subject prefix when provided', () => {
28
+ @Schema('my-subject')
29
+ class Beta {
30
+ @Field()
31
+ name!: string;
32
+ }
33
+ new Beta();
34
+ expect(defaultRegistry.getSubject(Beta)).toBe('my-subject');
35
+ });
36
+
37
+ it('does not set subject when omitted', () => {
38
+ @Schema()
39
+ class Gamma {
40
+ @Field()
41
+ val!: string;
42
+ }
43
+ new Gamma();
44
+ expect(defaultRegistry.getSubject(Gamma)).toBeUndefined();
45
+ });
46
+
47
+ it('creates field list even with no @Field() decorators', () => {
48
+ @Schema()
49
+ class Empty {}
50
+ // getOrCreateFields was called by @Schema
51
+ const fields = defaultRegistry.getFields(Empty);
52
+ expect(fields).toEqual([]);
53
+ });
54
+ });
55
+
56
+ describe('@Field() decorator', () => {
57
+ it("defaults to protoType 'string'", () => {
58
+ @Schema()
59
+ class DefaultType {
60
+ @Field()
61
+ name!: string;
62
+ }
63
+ new DefaultType();
64
+ const fields = defaultRegistry.getFields(DefaultType);
65
+ expect(fields[0].protoType).toBe('string');
66
+ });
67
+
68
+ it('respects explicit type option', () => {
69
+ @Schema()
70
+ class ExplicitType {
71
+ @Field({ type: 'int64' })
72
+ timestamp!: number;
73
+ }
74
+ new ExplicitType();
75
+ const fields = defaultRegistry.getFields(ExplicitType);
76
+ expect(fields[0].protoType).toBe('int64');
77
+ });
78
+
79
+ it('assigns sequential field numbers', () => {
80
+ @Schema()
81
+ class Multi {
82
+ @Field()
83
+ a!: string;
84
+
85
+ @Field()
86
+ b!: string;
87
+
88
+ @Field()
89
+ c!: string;
90
+ }
91
+ new Multi();
92
+ const fields = defaultRegistry.getFields(Multi);
93
+ expect(fields.map((f) => f.number)).toEqual([1, 2, 3]);
94
+ });
95
+
96
+ it('prevents duplicate registrations from multiple instances', () => {
97
+ @Schema()
98
+ class NoDup {
99
+ @Field()
100
+ id!: string;
101
+ }
102
+ new NoDup();
103
+ new NoDup();
104
+ new NoDup();
105
+ const fields = defaultRegistry.getFields(NoDup);
106
+ expect(fields.filter((f) => f.name === 'id')).toHaveLength(1);
107
+ });
108
+ });
@@ -0,0 +1,69 @@
1
+ /**
2
+ * TC39 stage 3 decorators for schema definition.
3
+ *
4
+ * Collects field names and types from decorated classes into the
5
+ * default SchemaRegistry so the SDK can generate proto3 definitions
6
+ * and validate payloads at runtime.
7
+ *
8
+ * Usage:
9
+ * @Schema("notifications")
10
+ * class Notification {
11
+ * @Field() id!: string;
12
+ * @Field({ type: "int64" }) timestamp!: number;
13
+ * }
14
+ */
15
+
16
+ import type { ProtoType } from './metadata.js';
17
+ import { defaultRegistry } from './registry.js';
18
+
19
+ /**
20
+ * Marks a class as a schema definition.
21
+ *
22
+ * @param subject - Optional subject prefix for registry-based validation
23
+ * (e.g. "notifications" → "notifications-value").
24
+ */
25
+ export function Schema(subject?: string) {
26
+ return function <T extends new (...args: unknown[]) => object>(
27
+ target: T,
28
+ _ctx: ClassDecoratorContext,
29
+ ): T {
30
+ // Ensure the field list exists even if no @Field() was used
31
+ defaultRegistry.getOrCreateFields(target);
32
+
33
+ if (subject) {
34
+ defaultRegistry.setSubject(target, subject);
35
+ }
36
+ return target;
37
+ };
38
+ }
39
+
40
+ interface FieldOptions {
41
+ /** Proto3 scalar type. Defaults to "string". */
42
+ type?: ProtoType;
43
+ }
44
+
45
+ /**
46
+ * Marks a property as a schema field (TC39 stage 3 field decorator).
47
+ *
48
+ * Defers registration to `addInitializer` so the class constructor is
49
+ * fully defined before we read `this.constructor`.
50
+ */
51
+ export function Field(opts?: FieldOptions) {
52
+ return function (_value: undefined, ctx: ClassFieldDecoratorContext) {
53
+ const name = String(ctx.name);
54
+
55
+ ctx.addInitializer(function (this: unknown) {
56
+ const ctor = (this as Record<string, unknown>).constructor as Function;
57
+ const fields = defaultRegistry.getOrCreateFields(ctor);
58
+
59
+ // Prevent duplicate registrations from multiple instantiations
60
+ if (fields.some((f) => f.name === name)) return;
61
+
62
+ fields.push({
63
+ name,
64
+ protoType: opts?.type ?? 'string',
65
+ number: fields.length + 1,
66
+ });
67
+ });
68
+ };
69
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export type { ProtoType, FieldMeta, SchemaEntry } from './metadata.js';
2
+ export { SchemaRegistry, defaultRegistry } from './registry.js';
3
+ export { Schema, Field } from './decorators.js';
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Tests for schema metadata types.
3
+ *
4
+ * Verifies that the type definitions compile correctly and that
5
+ * SchemaEntry can be constructed with all required fields.
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+ import type { ProtoType, FieldMeta, SchemaEntry } from './metadata.js';
10
+
11
+ describe('metadata types', () => {
12
+ it('accepts all valid ProtoType values', () => {
13
+ const types: ProtoType[] = [
14
+ 'string',
15
+ 'int32',
16
+ 'int64',
17
+ 'uint32',
18
+ 'uint64',
19
+ 'float',
20
+ 'double',
21
+ 'bool',
22
+ 'bytes',
23
+ ];
24
+ expect(types).toHaveLength(9);
25
+ });
26
+
27
+ it('constructs FieldMeta with required properties', () => {
28
+ const field: FieldMeta = { name: 'id', protoType: 'string', number: 1 };
29
+ expect(field.name).toBe('id');
30
+ expect(field.protoType).toBe('string');
31
+ expect(field.number).toBe(1);
32
+ });
33
+
34
+ it('constructs SchemaEntry with all properties', () => {
35
+ class Stub {}
36
+ const entry: SchemaEntry = {
37
+ ctor: Stub,
38
+ name: 'Stub',
39
+ subject: 'test-subject',
40
+ fields: [{ name: 'id', protoType: 'string', number: 1 }],
41
+ };
42
+ expect(entry.ctor).toBe(Stub);
43
+ expect(entry.name).toBe('Stub');
44
+ expect(entry.subject).toBe('test-subject');
45
+ expect(entry.fields).toHaveLength(1);
46
+ });
47
+
48
+ it('constructs SchemaEntry without optional subject', () => {
49
+ class Stub {}
50
+ const entry: SchemaEntry = {
51
+ ctor: Stub,
52
+ name: 'Stub',
53
+ fields: [],
54
+ };
55
+ expect(entry.subject).toBeUndefined();
56
+ expect(entry.fields).toHaveLength(0);
57
+ });
58
+ });
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Protobuf wire types supported by the schema decorator system.
3
+ *
4
+ * Maps 1:1 to proto3 scalar types so the SDK can generate
5
+ * `.proto` definitions without requiring protobuf knowledge.
6
+ *
7
+ * Usage:
8
+ * @Field({ type: "int32" }) qty!: number;
9
+ */
10
+ export type ProtoType =
11
+ | 'string'
12
+ | 'int32'
13
+ | 'int64'
14
+ | 'uint32'
15
+ | 'uint64'
16
+ | 'float'
17
+ | 'double'
18
+ | 'bool'
19
+ | 'bytes';
20
+
21
+ /** Metadata captured by the @Field() decorator for a single class property. */
22
+ export interface FieldMeta {
23
+ /** Property name on the decorated class. */
24
+ name: string;
25
+ /** Proto3 scalar type. Defaults to "string" when omitted. */
26
+ protoType: ProtoType;
27
+ /** 1-based field number for proto ordering. */
28
+ number: number;
29
+ }
30
+
31
+ /** Collected metadata for a decorated schema class. */
32
+ export interface SchemaEntry {
33
+ /** Class constructor reference. */
34
+ ctor: Function;
35
+ /** Human-readable schema name (defaults to class name). */
36
+ name: string;
37
+ /** Optional subject prefix for registry-based validation. */
38
+ subject?: string;
39
+ /** Ordered field definitions extracted from @Field() decorators. */
40
+ fields: FieldMeta[];
41
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Tests for SchemaRegistry.
3
+ *
4
+ * Covers register/lookup, field store CRUD, subject store,
5
+ * and listAll enumeration.
6
+ */
7
+
8
+ import { beforeEach, describe, expect, it } from 'vitest';
9
+ import type { SchemaEntry } from './metadata.js';
10
+ import { SchemaRegistry } from './registry.js';
11
+
12
+ describe('SchemaRegistry', () => {
13
+ let registry: SchemaRegistry;
14
+
15
+ beforeEach(() => {
16
+ registry = new SchemaRegistry();
17
+ });
18
+
19
+ describe('register / lookup', () => {
20
+ it('stores and retrieves a schema entry by queue name', () => {
21
+ class Order {}
22
+ const entry: SchemaEntry = {
23
+ ctor: Order,
24
+ name: 'Order',
25
+ fields: [{ name: 'id', protoType: 'string', number: 1 }],
26
+ };
27
+ registry.register('orders', entry);
28
+ expect(registry.lookup('orders')).toBe(entry);
29
+ });
30
+
31
+ it('returns undefined for unregistered queue', () => {
32
+ expect(registry.lookup('nonexistent')).toBeUndefined();
33
+ });
34
+
35
+ it('overwrites previous entry on re-registration', () => {
36
+ class V1 {}
37
+ class V2 {}
38
+ const entry1: SchemaEntry = { ctor: V1, name: 'V1', fields: [] };
39
+ const entry2: SchemaEntry = { ctor: V2, name: 'V2', fields: [] };
40
+ registry.register('q', entry1);
41
+ registry.register('q', entry2);
42
+ expect(registry.lookup('q')).toBe(entry2);
43
+ });
44
+ });
45
+
46
+ describe('listAll', () => {
47
+ it('returns empty array when nothing registered', () => {
48
+ expect(registry.listAll()).toEqual([]);
49
+ });
50
+
51
+ it('returns all registered entries', () => {
52
+ class A {}
53
+ class B {}
54
+ const a: SchemaEntry = { ctor: A, name: 'A', fields: [] };
55
+ const b: SchemaEntry = { ctor: B, name: 'B', fields: [] };
56
+ registry.register('qa', a);
57
+ registry.register('qb', b);
58
+ expect(registry.listAll()).toHaveLength(2);
59
+ expect(registry.listAll()).toContain(a);
60
+ expect(registry.listAll()).toContain(b);
61
+ });
62
+ });
63
+
64
+ describe('subject store', () => {
65
+ it('stores and retrieves subject prefix', () => {
66
+ class X {}
67
+ registry.setSubject(X, 'my-subject');
68
+ expect(registry.getSubject(X)).toBe('my-subject');
69
+ });
70
+
71
+ it('returns undefined for class without subject', () => {
72
+ class Y {}
73
+ expect(registry.getSubject(Y)).toBeUndefined();
74
+ });
75
+ });
76
+
77
+ describe('field store', () => {
78
+ it('creates empty field list on first access', () => {
79
+ class Z {}
80
+ const fields = registry.getOrCreateFields(Z);
81
+ expect(fields).toEqual([]);
82
+ });
83
+
84
+ it('returns same array on subsequent calls', () => {
85
+ class W {}
86
+ const first = registry.getOrCreateFields(W);
87
+ first.push({ name: 'x', protoType: 'int32', number: 1 });
88
+ const second = registry.getOrCreateFields(W);
89
+ expect(second).toBe(first);
90
+ expect(second).toHaveLength(1);
91
+ });
92
+
93
+ it('getFields returns empty for unknown class', () => {
94
+ class Unknown {}
95
+ expect(registry.getFields(Unknown)).toEqual([]);
96
+ });
97
+
98
+ it('getFields returns registered fields', () => {
99
+ class F {}
100
+ const fields = registry.getOrCreateFields(F);
101
+ fields.push({ name: 'a', protoType: 'string', number: 1 });
102
+ expect(registry.getFields(F)).toEqual([{ name: 'a', protoType: 'string', number: 1 }]);
103
+ });
104
+ });
105
+ });
@@ -0,0 +1,73 @@
1
+ /**
2
+ * In-memory schema registry.
3
+ *
4
+ * Replaces the module-level Map globals from the original schema.ts.
5
+ * Injectable via constructor so tests can use isolated instances.
6
+ *
7
+ * Usage:
8
+ * const registry = new SchemaRegistry();
9
+ * registry.register("orders", entry);
10
+ * const entry = registry.lookup("orders");
11
+ */
12
+
13
+ import type { FieldMeta, SchemaEntry } from './metadata.js';
14
+
15
+ export class SchemaRegistry {
16
+ /** Queue name → schema entry. */
17
+ private byQueue = new Map<string, SchemaEntry>();
18
+
19
+ /** Class constructor → field metadata (populated by decorators). */
20
+ private fieldStore = new Map<Function, FieldMeta[]>();
21
+
22
+ /** Class constructor → subject prefix (populated by @Schema("subject")). */
23
+ private subjectStore = new Map<Function, string>();
24
+
25
+ /** Binds a queue name to a fully resolved schema entry. */
26
+ register(queueName: string, entry: SchemaEntry): void {
27
+ this.byQueue.set(queueName, entry);
28
+ }
29
+
30
+ /** Returns the schema entry for a queue, or undefined if unregistered. */
31
+ lookup(queueName: string): SchemaEntry | undefined {
32
+ return this.byQueue.get(queueName);
33
+ }
34
+
35
+ /** Returns all registered schema entries. */
36
+ listAll(): SchemaEntry[] {
37
+ return [...this.byQueue.values()];
38
+ }
39
+
40
+ /** Stores the subject prefix set by @Schema("subject"). */
41
+ setSubject(ctor: Function, subject: string): void {
42
+ this.subjectStore.set(ctor, subject);
43
+ }
44
+
45
+ /** Returns the subject prefix for a class, if any. */
46
+ getSubject(ctor: Function): string | undefined {
47
+ return this.subjectStore.get(ctor);
48
+ }
49
+
50
+ /** Returns the mutable field list for a class, creating it if absent. */
51
+ getOrCreateFields(ctor: Function): FieldMeta[] {
52
+ let fields = this.fieldStore.get(ctor);
53
+ if (!fields) {
54
+ fields = [];
55
+ this.fieldStore.set(ctor, fields);
56
+ }
57
+ return fields;
58
+ }
59
+
60
+ /** Returns the field metadata for a class (read-only snapshot). */
61
+ getFields(ctor: Function): readonly FieldMeta[] {
62
+ return this.fieldStore.get(ctor) ?? [];
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Default singleton registry used by decorators.
68
+ *
69
+ * Exported so @Schema() and @Field() can register metadata at class
70
+ * definition time without requiring explicit wiring. The core package
71
+ * reads from this same instance during publish/consume.
72
+ */
73
+ export const defaultRegistry = new SchemaRegistry();
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"]
8
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/index.ts"],
5
+ format: ["esm", "cjs"],
6
+ dts: true,
7
+ clean: true,
8
+ });