@rocketmq/schema 0.1.1 → 0.1.2
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/.turbo/turbo-build.log +11 -9
- package/CHANGELOG.md +7 -0
- package/dist/index.cjs +128 -12
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +45 -47
- package/dist/index.d.ts +45 -47
- package/dist/index.js +130 -12
- package/dist/index.js.map +1 -0
- package/package.json +4 -1
- package/src/decorators.test.ts +14 -1
- package/src/decorators.ts +119 -47
- package/src/index.ts +2 -1
- package/src/inference.test.ts +46 -0
- package/src/inference.ts +45 -0
- package/src/metadata.ts +20 -7
- package/src/registry.test.ts +16 -0
- package/src/registry.ts +30 -11
- package/src/types.ts +10 -0
- package/tsup.config.ts +1 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @rocketmq/schema@0.1.
|
|
3
|
+
> @rocketmq/schema@0.1.2 build /home/edilson/learnspace/rocketmq-broker/rocketmq.js/packages/schema
|
|
4
4
|
> tsup
|
|
5
5
|
|
|
6
6
|
CLI Building entry: src/index.ts
|
|
7
7
|
CLI Using tsconfig: tsconfig.json
|
|
8
8
|
CLI tsup v8.5.1
|
|
9
|
-
CLI Using tsup config: /home/edilson/learnspace/rocketmq-broker/
|
|
9
|
+
CLI Using tsup config: /home/edilson/learnspace/rocketmq-broker/rocketmq.js/packages/schema/tsup.config.ts
|
|
10
10
|
CLI Target: es2022
|
|
11
11
|
CLI Cleaning output folder
|
|
12
12
|
ESM Build start
|
|
13
13
|
CJS Build start
|
|
14
|
-
CJS dist/index.cjs
|
|
15
|
-
CJS
|
|
16
|
-
|
|
17
|
-
ESM
|
|
14
|
+
CJS dist/index.cjs 6.92 KB
|
|
15
|
+
CJS dist/index.cjs.map 13.75 KB
|
|
16
|
+
CJS ⚡️ Build success in 24ms
|
|
17
|
+
ESM dist/index.js 5.83 KB
|
|
18
|
+
ESM dist/index.js.map 13.45 KB
|
|
19
|
+
ESM ⚡️ Build success in 25ms
|
|
18
20
|
DTS Build start
|
|
19
|
-
DTS ⚡️ Build success in
|
|
20
|
-
DTS dist/index.d.ts 4.
|
|
21
|
-
DTS dist/index.d.cts 4.
|
|
21
|
+
DTS ⚡️ Build success in 558ms
|
|
22
|
+
DTS dist/index.d.ts 4.39 KB
|
|
23
|
+
DTS dist/index.d.cts 4.39 KB
|
package/CHANGELOG.md
ADDED
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
7
|
var __export = (target, all) => {
|
|
7
8
|
for (var name in all)
|
|
8
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -29,12 +30,34 @@ module.exports = __toCommonJS(index_exports);
|
|
|
29
30
|
|
|
30
31
|
// src/registry.ts
|
|
31
32
|
var SchemaRegistry = class {
|
|
33
|
+
static {
|
|
34
|
+
__name(this, "SchemaRegistry");
|
|
35
|
+
}
|
|
32
36
|
/** Queue name → schema entry. */
|
|
33
37
|
byQueue = /* @__PURE__ */ new Map();
|
|
34
38
|
/** Class constructor → field metadata (populated by decorators). */
|
|
35
39
|
fieldStore = /* @__PURE__ */ new Map();
|
|
36
40
|
/** Class constructor → subject prefix (populated by @Schema("subject")). */
|
|
37
41
|
subjectStore = /* @__PURE__ */ new Map();
|
|
42
|
+
/** Set of all decorated schema constructors. */
|
|
43
|
+
allSchemas = /* @__PURE__ */ new Set();
|
|
44
|
+
/** Tracks a constructor as a decorated schema. */
|
|
45
|
+
registerSchema(ctor) {
|
|
46
|
+
this.allSchemas.add(ctor);
|
|
47
|
+
}
|
|
48
|
+
/** Returns true if a constructor is a registered schema. */
|
|
49
|
+
isSchema(ctor) {
|
|
50
|
+
return this.allSchemas.has(ctor);
|
|
51
|
+
}
|
|
52
|
+
/** Looks up a schema constructor by its class name. */
|
|
53
|
+
getSchemaByName(name) {
|
|
54
|
+
for (const ctor of this.allSchemas) {
|
|
55
|
+
if (ctor.name === name) {
|
|
56
|
+
return ctor;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
38
61
|
/** Binds a queue name to a fully resolved schema entry. */
|
|
39
62
|
register(queueName, entry) {
|
|
40
63
|
this.byQueue.set(queueName, entry);
|
|
@@ -45,7 +68,9 @@ var SchemaRegistry = class {
|
|
|
45
68
|
}
|
|
46
69
|
/** Returns all registered schema entries. */
|
|
47
70
|
listAll() {
|
|
48
|
-
return [
|
|
71
|
+
return [
|
|
72
|
+
...this.byQueue.values()
|
|
73
|
+
];
|
|
49
74
|
}
|
|
50
75
|
/** Stores the subject prefix set by @Schema("subject"). */
|
|
51
76
|
setSubject(ctor, subject) {
|
|
@@ -71,9 +96,59 @@ var SchemaRegistry = class {
|
|
|
71
96
|
};
|
|
72
97
|
var defaultRegistry = new SchemaRegistry();
|
|
73
98
|
|
|
99
|
+
// src/inference.ts
|
|
100
|
+
function inferFromReflectMetadata(target, key) {
|
|
101
|
+
const R = Reflect;
|
|
102
|
+
if (!R.getMetadata) return "string";
|
|
103
|
+
const designType = R.getMetadata("design:type", target, key);
|
|
104
|
+
if (!designType) return "string";
|
|
105
|
+
return inferFromConstructor(designType);
|
|
106
|
+
}
|
|
107
|
+
__name(inferFromReflectMetadata, "inferFromReflectMetadata");
|
|
108
|
+
function inferFromConstructor(ctor) {
|
|
109
|
+
if (ctor === Number) return "double";
|
|
110
|
+
if (ctor === Boolean) return "bool";
|
|
111
|
+
if (ctor === Date) return "google.protobuf.Timestamp";
|
|
112
|
+
if (ctor === Array || ctor === Object) return "bytes";
|
|
113
|
+
if (defaultRegistry.isSchema(ctor)) return ctor.name;
|
|
114
|
+
return "string";
|
|
115
|
+
}
|
|
116
|
+
__name(inferFromConstructor, "inferFromConstructor");
|
|
117
|
+
function inferFromValue(value) {
|
|
118
|
+
if (value instanceof Date) return "google.protobuf.Timestamp";
|
|
119
|
+
if (value && typeof value === "object") {
|
|
120
|
+
const proto = Object.getPrototypeOf(value);
|
|
121
|
+
if (proto && proto.constructor && defaultRegistry.isSchema(proto.constructor)) {
|
|
122
|
+
return proto.constructor.name;
|
|
123
|
+
}
|
|
124
|
+
return "bytes";
|
|
125
|
+
}
|
|
126
|
+
switch (typeof value) {
|
|
127
|
+
case "number":
|
|
128
|
+
return "double";
|
|
129
|
+
case "boolean":
|
|
130
|
+
return "bool";
|
|
131
|
+
default:
|
|
132
|
+
return "string";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
__name(inferFromValue, "inferFromValue");
|
|
136
|
+
|
|
74
137
|
// src/decorators.ts
|
|
138
|
+
var import_reflect_metadata = require("reflect-metadata");
|
|
139
|
+
var SCHEMA_SYMBOL = /* @__PURE__ */ Symbol.for("rocketmq:schema");
|
|
75
140
|
function Schema(subject) {
|
|
76
|
-
return function(target,
|
|
141
|
+
return function(target, _ctxOrUndefined) {
|
|
142
|
+
Object.defineProperty(target, SCHEMA_SYMBOL, {
|
|
143
|
+
value: true,
|
|
144
|
+
enumerable: false,
|
|
145
|
+
writable: false
|
|
146
|
+
});
|
|
147
|
+
const R = Reflect;
|
|
148
|
+
if (R.defineMetadata) {
|
|
149
|
+
R.defineMetadata(SCHEMA_SYMBOL, true, target);
|
|
150
|
+
}
|
|
151
|
+
defaultRegistry.registerSchema(target);
|
|
77
152
|
defaultRegistry.getOrCreateFields(target);
|
|
78
153
|
if (subject) {
|
|
79
154
|
defaultRegistry.setSubject(target, subject);
|
|
@@ -81,21 +156,61 @@ function Schema(subject) {
|
|
|
81
156
|
return target;
|
|
82
157
|
};
|
|
83
158
|
}
|
|
159
|
+
__name(Schema, "Schema");
|
|
84
160
|
function Field(opts) {
|
|
85
|
-
return function(
|
|
86
|
-
|
|
161
|
+
return function(targetOrValue, keyOrCtx) {
|
|
162
|
+
if (targetOrValue === void 0 || typeof keyOrCtx === "object") {
|
|
163
|
+
return handleTC39Field(opts, keyOrCtx);
|
|
164
|
+
}
|
|
165
|
+
handleExperimentalField(opts, targetOrValue, keyOrCtx);
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
__name(Field, "Field");
|
|
169
|
+
function handleExperimentalField(opts, target, propertyKey) {
|
|
170
|
+
const ctor = target.constructor;
|
|
171
|
+
const name = String(propertyKey);
|
|
172
|
+
const fields = defaultRegistry.getOrCreateFields(ctor);
|
|
173
|
+
if (fields.some((f) => f.name === name)) return;
|
|
174
|
+
const R = Reflect;
|
|
175
|
+
const reflectedType = R.getMetadata ? R.getMetadata("design:type", target, propertyKey) : void 0;
|
|
176
|
+
const protoType = opts?.type ?? inferFromReflectMetadata(target, propertyKey);
|
|
177
|
+
fields.push({
|
|
178
|
+
name,
|
|
179
|
+
protoType,
|
|
180
|
+
number: fields.length + 1,
|
|
181
|
+
reflectedType,
|
|
182
|
+
...opts
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
__name(handleExperimentalField, "handleExperimentalField");
|
|
186
|
+
function handleTC39Field(opts, ctx) {
|
|
187
|
+
const name = String(ctx.name);
|
|
188
|
+
if (opts?.type) {
|
|
87
189
|
ctx.addInitializer(function() {
|
|
88
|
-
|
|
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
|
-
});
|
|
190
|
+
registerTC39Field(this, name, opts.type, opts);
|
|
96
191
|
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
return function(initialValue) {
|
|
195
|
+
registerTC39Field(this, name, inferFromValue(initialValue), opts, initialValue);
|
|
196
|
+
return initialValue;
|
|
97
197
|
};
|
|
98
198
|
}
|
|
199
|
+
__name(handleTC39Field, "handleTC39Field");
|
|
200
|
+
function registerTC39Field(instance, name, protoType, opts, initialValue) {
|
|
201
|
+
const ctor = instance.constructor;
|
|
202
|
+
const fields = defaultRegistry.getOrCreateFields(ctor);
|
|
203
|
+
if (fields.some((f) => f.name === name)) return;
|
|
204
|
+
const reflectedType = initialValue && typeof initialValue === "object" ? Object.getPrototypeOf(initialValue).constructor : void 0;
|
|
205
|
+
fields.push({
|
|
206
|
+
name,
|
|
207
|
+
protoType,
|
|
208
|
+
number: fields.length + 1,
|
|
209
|
+
reflectedType,
|
|
210
|
+
...opts
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
__name(registerTC39Field, "registerTC39Field");
|
|
99
214
|
// Annotate the CommonJS export names for ESM import in node:
|
|
100
215
|
0 && (module.exports = {
|
|
101
216
|
Field,
|
|
@@ -103,3 +218,4 @@ function Field(opts) {
|
|
|
103
218
|
SchemaRegistry,
|
|
104
219
|
defaultRegistry
|
|
105
220
|
});
|
|
221
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/registry.ts","../src/inference.ts","../src/decorators.ts"],"sourcesContent":["export type { ProtoType, FieldOptions, FieldMeta, SchemaEntry } from './metadata.js';\nexport { SchemaRegistry, defaultRegistry } from './registry.js';\nexport { Schema, Field } from './decorators.js';\nexport type { Constructor } from './types.js';\n","/**\n * In-memory schema registry.\n *\n * Replaces the module-level Map globals from the original schema.ts.\n * Injectable via constructor so tests can use isolated instances.\n */\n\nimport type { FieldMeta, SchemaEntry } from './metadata.js';\nimport type { Constructor } from './types.js';\n\nexport class SchemaRegistry {\n /** Queue name → schema entry. */\n private byQueue = new Map<string, SchemaEntry>();\n\n /** Class constructor → field metadata (populated by decorators). */\n private fieldStore = new Map<Constructor, FieldMeta[]>();\n\n /** Class constructor → subject prefix (populated by @Schema(\"subject\")). */\n private subjectStore = new Map<Constructor, string>();\n\n /** Set of all decorated schema constructors. */\n private allSchemas = new Set<Constructor>();\n\n /** Tracks a constructor as a decorated schema. */\n registerSchema(ctor: Constructor): void {\n this.allSchemas.add(ctor);\n }\n\n /** Returns true if a constructor is a registered schema. */\n isSchema(ctor: Constructor): boolean {\n return this.allSchemas.has(ctor);\n }\n\n /** Looks up a schema constructor by its class name. */\n getSchemaByName(name: string): Constructor | undefined {\n for (const ctor of this.allSchemas) {\n if (ctor.name === name) {\n return ctor;\n }\n }\n return undefined;\n }\n\n /** Binds a queue name to a fully resolved schema entry. */\n register(queueName: string, entry: SchemaEntry): void {\n this.byQueue.set(queueName, entry);\n }\n\n /** Returns the schema entry for a queue, or undefined if unregistered. */\n lookup(queueName: string): SchemaEntry | undefined {\n return this.byQueue.get(queueName);\n }\n\n /** Returns all registered schema entries. */\n listAll(): SchemaEntry[] {\n return [...this.byQueue.values()];\n }\n\n /** Stores the subject prefix set by @Schema(\"subject\"). */\n setSubject(ctor: Constructor, subject: string): void {\n this.subjectStore.set(ctor, subject);\n }\n\n /** Returns the subject prefix for a class, if any. */\n getSubject(ctor: Constructor): string | undefined {\n return this.subjectStore.get(ctor);\n }\n\n /** Returns the mutable field list for a class, creating it if absent. */\n getOrCreateFields(ctor: Constructor): FieldMeta[] {\n let fields = this.fieldStore.get(ctor);\n if (!fields) {\n fields = [];\n this.fieldStore.set(ctor, fields);\n }\n return fields;\n }\n\n /** Returns the field metadata for a class (read-only snapshot). */\n getFields(ctor: Constructor): readonly FieldMeta[] {\n return this.fieldStore.get(ctor) ?? [];\n }\n}\n\n/**\n * Default singleton registry used by decorators.\n *\n * Exported so @Schema() and @Field() can register metadata at class\n * definition time without requiring explicit wiring. The core package\n * reads from this same instance during publish/consume.\n */\nexport const defaultRegistry = new SchemaRegistry();\n","import { defaultRegistry } from './registry.js';\nimport type { Constructor } from './types.js';\n\n/** Infers proto type from reflect metadata. */\nexport function inferFromReflectMetadata(target: object, key: string | symbol): string {\n const R = Reflect as unknown as {\n getMetadata?: (key: string, target: object, prop: string | symbol) => Constructor | undefined;\n };\n if (!R.getMetadata) return 'string';\n\n const designType = R.getMetadata('design:type', target, key);\n if (!designType) return 'string';\n\n return inferFromConstructor(designType);\n}\n\n/** Infers type from primitive or schema constructors. */\nexport function inferFromConstructor(ctor: Constructor): string {\n if (ctor === Number) return 'double';\n if (ctor === Boolean) return 'bool';\n if (ctor === Date) return 'google.protobuf.Timestamp';\n if (ctor === Array || ctor === Object) return 'bytes';\n if (defaultRegistry.isSchema(ctor)) return ctor.name;\n return 'string';\n}\n\n/** Infers type from runtime value (TC39). */\nexport function inferFromValue(value: unknown): string {\n if (value instanceof Date) return 'google.protobuf.Timestamp';\n if (value && typeof value === 'object') {\n const proto = Object.getPrototypeOf(value);\n if (proto && proto.constructor && defaultRegistry.isSchema(proto.constructor)) {\n return proto.constructor.name;\n }\n return 'bytes';\n }\n switch (typeof value) {\n case 'number':\n return 'double';\n case 'boolean':\n return 'bool';\n default:\n return 'string';\n }\n}\n","/**\n * Dual-mode decorators: works with BOTH experimental decorators\n * (reflect-metadata) AND TC39 stage 3 decorators.\n */\n\nimport { inferFromReflectMetadata, inferFromValue } from './inference.js';\nimport type { FieldOptions } from './metadata.js';\nimport { defaultRegistry } from './registry.js';\nimport type { Constructor } from './types.js';\n\nimport 'reflect-metadata';\n\nconst SCHEMA_SYMBOL = Symbol.for('rocketmq:schema');\n\n/** Checks if a constructor is decorated as a schema class. */\nexport function isSchemaClass(ctor: unknown): boolean {\n if (typeof ctor !== 'function') return false;\n const R = Reflect as unknown as {\n hasOwnMetadata?: (k: unknown, t: unknown) => boolean;\n };\n return (\n SCHEMA_SYMBOL in ctor ||\n (R.hasOwnMetadata !== undefined && R.hasOwnMetadata(SCHEMA_SYMBOL, ctor))\n );\n}\n\n/** Marks a class as a schema definition. */\nexport function Schema(subject?: string) {\n return function <T extends Constructor>(target: T, _ctxOrUndefined?: ClassDecoratorContext): T {\n Object.defineProperty(target, SCHEMA_SYMBOL, {\n value: true,\n enumerable: false,\n writable: false,\n });\n const R = Reflect as unknown as {\n defineMetadata?: (k: unknown, v: unknown, t: unknown) => void;\n };\n if (R.defineMetadata) {\n R.defineMetadata(SCHEMA_SYMBOL, true, target);\n }\n defaultRegistry.registerSchema(target);\n defaultRegistry.getOrCreateFields(target);\n if (subject) {\n defaultRegistry.setSubject(target, subject);\n }\n return target;\n };\n}\n\n/** Marks a property as a schema field. */\nexport function Field(opts?: FieldOptions) {\n return function (\n targetOrValue: object | undefined,\n keyOrCtx: string | symbol | ClassFieldDecoratorContext,\n // WHY void: Node16 moduleResolution enforces TS1271 — decorator return must be void|any.\n // The TC39 initializer return is consumed by the runtime, not the decorator call site.\n ): void {\n if (targetOrValue === undefined || typeof keyOrCtx === 'object') {\n // WHY cast: TC39 path returns an initializer function at runtime,\n // but the .d.ts must declare void to satisfy experimental decorator constraints.\n return handleTC39Field(opts, keyOrCtx as ClassFieldDecoratorContext) as void;\n }\n handleExperimentalField(opts, targetOrValue, keyOrCtx as string | symbol);\n };\n}\n\n/** Experimental decorator path. */\nfunction handleExperimentalField(\n opts: FieldOptions | undefined,\n target: object,\n propertyKey: string | symbol,\n): void {\n const ctor = target.constructor as Constructor;\n const name = String(propertyKey);\n const fields = defaultRegistry.getOrCreateFields(ctor);\n\n if (fields.some((f) => f.name === name)) return;\n\n const R = Reflect as unknown as {\n getMetadata?: (k: string, t: object, p: string | symbol) => Constructor | undefined;\n };\n const reflectedType = R.getMetadata\n ? R.getMetadata('design:type', target, propertyKey)\n : undefined;\n const protoType = opts?.type ?? inferFromReflectMetadata(target, propertyKey);\n\n fields.push({\n name,\n protoType,\n number: fields.length + 1,\n reflectedType,\n ...opts,\n });\n}\n\n/* v8 ignore start */\n/** TC39 stage 3 decorator path. */\nfunction handleTC39Field(\n opts: FieldOptions | undefined,\n ctx: ClassFieldDecoratorContext,\n): ((this: unknown, initialValue: unknown) => unknown) | void {\n const name = String(ctx.name);\n\n if (opts?.type) {\n ctx.addInitializer(function (this: unknown) {\n registerTC39Field(this, name, opts.type!, opts);\n });\n return;\n }\n\n return function (this: unknown, initialValue: unknown): unknown {\n registerTC39Field(this, name, inferFromValue(initialValue), opts, initialValue);\n return initialValue;\n };\n}\n\n/** Internal TC39 helper to register field inside initializer. */\nfunction registerTC39Field(\n instance: unknown,\n name: string,\n protoType: string,\n opts: FieldOptions | undefined,\n initialValue?: unknown,\n): void {\n const ctor = (instance as Record<string, unknown>).constructor as Constructor;\n const fields = defaultRegistry.getOrCreateFields(ctor);\n if (fields.some((f) => f.name === name)) return;\n\n const reflectedType =\n initialValue && typeof initialValue === 'object'\n ? (Object.getPrototypeOf(initialValue).constructor as Constructor)\n : undefined;\n\n fields.push({\n name,\n protoType,\n number: fields.length + 1,\n reflectedType,\n ...opts,\n });\n}\n"],"mappings":"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","names":["SchemaRegistry","byQueue","Map","fieldStore","subjectStore","allSchemas","Set","registerSchema","ctor","add","isSchema","has","getSchemaByName","name","undefined","register","queueName","entry","set","lookup","get","listAll","values","setSubject","subject","getSubject","getOrCreateFields","fields","getFields","defaultRegistry","inferFromReflectMetadata","target","key","R","Reflect","getMetadata","designType","inferFromConstructor","ctor","Number","Boolean","Date","Array","Object","defaultRegistry","isSchema","name","inferFromValue","value","proto","getPrototypeOf","SCHEMA_SYMBOL","Symbol","for","Schema","subject","target","_ctxOrUndefined","Object","defineProperty","SCHEMA_SYMBOL","value","enumerable","writable","R","Reflect","defineMetadata","defaultRegistry","registerSchema","getOrCreateFields","setSubject","Field","opts","targetOrValue","keyOrCtx","undefined","handleTC39Field","handleExperimentalField","propertyKey","ctor","name","String","fields","some","f","reflectedType","getMetadata","protoType","type","inferFromReflectMetadata","push","number","length","ctx","addInitializer","registerTC39Field","initialValue","inferFromValue","instance","getPrototypeOf"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,26 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for the schema package.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Represents a class constructor.
|
|
6
|
+
* Used to replace the generic 'Function' type for stricter type safety.
|
|
7
|
+
*/
|
|
8
|
+
type Constructor<T = object> = new (...args: any[]) => T;
|
|
9
|
+
|
|
1
10
|
/**
|
|
2
11
|
* Protobuf wire types supported by the schema decorator system.
|
|
3
12
|
*
|
|
4
13
|
* Maps 1:1 to proto3 scalar types so the SDK can generate
|
|
5
14
|
* `.proto` definitions without requiring protobuf knowledge.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* @Field({ type: "int32" }) qty!: number;
|
|
9
15
|
*/
|
|
10
16
|
type ProtoType = 'string' | 'int32' | 'int64' | 'uint32' | 'uint64' | 'float' | 'double' | 'bool' | 'bytes';
|
|
17
|
+
/** Additional configuration options passed to the @Field() decorator. */
|
|
18
|
+
interface FieldOptions {
|
|
19
|
+
/** Explicit proto3 type override (scalar or custom message name). */
|
|
20
|
+
type?: string;
|
|
21
|
+
/** Emits 'repeated' prefix for arrays. */
|
|
22
|
+
repeated?: boolean;
|
|
23
|
+
/** Emits 'optional' prefix for optional presence. */
|
|
24
|
+
optional?: boolean;
|
|
25
|
+
/** Inline comment written above the field. */
|
|
26
|
+
comment?: string;
|
|
27
|
+
}
|
|
11
28
|
/** Metadata captured by the @Field() decorator for a single class property. */
|
|
12
|
-
interface FieldMeta {
|
|
29
|
+
interface FieldMeta extends FieldOptions {
|
|
13
30
|
/** Property name on the decorated class. */
|
|
14
31
|
name: string;
|
|
15
|
-
/** Proto3
|
|
16
|
-
protoType:
|
|
32
|
+
/** Proto3 type. Defaults to "string" when omitted. */
|
|
33
|
+
protoType: string;
|
|
17
34
|
/** 1-based field number for proto ordering. */
|
|
18
35
|
number: number;
|
|
36
|
+
/** Constructor captured by reflect-metadata (String, Number, Boolean, Date, etc.). */
|
|
37
|
+
reflectedType?: Constructor;
|
|
19
38
|
}
|
|
20
39
|
/** Collected metadata for a decorated schema class. */
|
|
21
40
|
interface SchemaEntry {
|
|
22
41
|
/** Class constructor reference. */
|
|
23
|
-
ctor:
|
|
42
|
+
ctor: Constructor;
|
|
24
43
|
/** Human-readable schema name (defaults to class name). */
|
|
25
44
|
name: string;
|
|
26
45
|
/** Optional subject prefix for registry-based validation. */
|
|
@@ -34,11 +53,6 @@ interface SchemaEntry {
|
|
|
34
53
|
*
|
|
35
54
|
* Replaces the module-level Map globals from the original schema.ts.
|
|
36
55
|
* 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
56
|
*/
|
|
43
57
|
|
|
44
58
|
declare class SchemaRegistry {
|
|
@@ -48,6 +62,14 @@ declare class SchemaRegistry {
|
|
|
48
62
|
private fieldStore;
|
|
49
63
|
/** Class constructor → subject prefix (populated by @Schema("subject")). */
|
|
50
64
|
private subjectStore;
|
|
65
|
+
/** Set of all decorated schema constructors. */
|
|
66
|
+
private allSchemas;
|
|
67
|
+
/** Tracks a constructor as a decorated schema. */
|
|
68
|
+
registerSchema(ctor: Constructor): void;
|
|
69
|
+
/** Returns true if a constructor is a registered schema. */
|
|
70
|
+
isSchema(ctor: Constructor): boolean;
|
|
71
|
+
/** Looks up a schema constructor by its class name. */
|
|
72
|
+
getSchemaByName(name: string): Constructor | undefined;
|
|
51
73
|
/** Binds a queue name to a fully resolved schema entry. */
|
|
52
74
|
register(queueName: string, entry: SchemaEntry): void;
|
|
53
75
|
/** Returns the schema entry for a queue, or undefined if unregistered. */
|
|
@@ -55,13 +77,13 @@ declare class SchemaRegistry {
|
|
|
55
77
|
/** Returns all registered schema entries. */
|
|
56
78
|
listAll(): SchemaEntry[];
|
|
57
79
|
/** Stores the subject prefix set by @Schema("subject"). */
|
|
58
|
-
setSubject(ctor:
|
|
80
|
+
setSubject(ctor: Constructor, subject: string): void;
|
|
59
81
|
/** Returns the subject prefix for a class, if any. */
|
|
60
|
-
getSubject(ctor:
|
|
82
|
+
getSubject(ctor: Constructor): string | undefined;
|
|
61
83
|
/** Returns the mutable field list for a class, creating it if absent. */
|
|
62
|
-
getOrCreateFields(ctor:
|
|
84
|
+
getOrCreateFields(ctor: Constructor): FieldMeta[];
|
|
63
85
|
/** Returns the field metadata for a class (read-only snapshot). */
|
|
64
|
-
getFields(ctor:
|
|
86
|
+
getFields(ctor: Constructor): readonly FieldMeta[];
|
|
65
87
|
}
|
|
66
88
|
/**
|
|
67
89
|
* Default singleton registry used by decorators.
|
|
@@ -73,37 +95,13 @@ declare class SchemaRegistry {
|
|
|
73
95
|
declare const defaultRegistry: SchemaRegistry;
|
|
74
96
|
|
|
75
97
|
/**
|
|
76
|
-
*
|
|
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
|
-
* }
|
|
98
|
+
* Dual-mode decorators: works with BOTH experimental decorators
|
|
99
|
+
* (reflect-metadata) AND TC39 stage 3 decorators.
|
|
88
100
|
*/
|
|
89
101
|
|
|
90
|
-
/**
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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;
|
|
102
|
+
/** Marks a class as a schema definition. */
|
|
103
|
+
declare function Schema(subject?: string): <T extends Constructor>(target: T, _ctxOrUndefined?: ClassDecoratorContext) => T;
|
|
104
|
+
/** Marks a property as a schema field. */
|
|
105
|
+
declare function Field(opts?: FieldOptions): (targetOrValue: object | undefined, keyOrCtx: string | symbol | ClassFieldDecoratorContext) => void;
|
|
108
106
|
|
|
109
|
-
export { Field, type FieldMeta, type ProtoType, Schema, type SchemaEntry, SchemaRegistry, defaultRegistry };
|
|
107
|
+
export { type Constructor, Field, type FieldMeta, type FieldOptions, type ProtoType, Schema, type SchemaEntry, SchemaRegistry, defaultRegistry };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,26 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for the schema package.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Represents a class constructor.
|
|
6
|
+
* Used to replace the generic 'Function' type for stricter type safety.
|
|
7
|
+
*/
|
|
8
|
+
type Constructor<T = object> = new (...args: any[]) => T;
|
|
9
|
+
|
|
1
10
|
/**
|
|
2
11
|
* Protobuf wire types supported by the schema decorator system.
|
|
3
12
|
*
|
|
4
13
|
* Maps 1:1 to proto3 scalar types so the SDK can generate
|
|
5
14
|
* `.proto` definitions without requiring protobuf knowledge.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* @Field({ type: "int32" }) qty!: number;
|
|
9
15
|
*/
|
|
10
16
|
type ProtoType = 'string' | 'int32' | 'int64' | 'uint32' | 'uint64' | 'float' | 'double' | 'bool' | 'bytes';
|
|
17
|
+
/** Additional configuration options passed to the @Field() decorator. */
|
|
18
|
+
interface FieldOptions {
|
|
19
|
+
/** Explicit proto3 type override (scalar or custom message name). */
|
|
20
|
+
type?: string;
|
|
21
|
+
/** Emits 'repeated' prefix for arrays. */
|
|
22
|
+
repeated?: boolean;
|
|
23
|
+
/** Emits 'optional' prefix for optional presence. */
|
|
24
|
+
optional?: boolean;
|
|
25
|
+
/** Inline comment written above the field. */
|
|
26
|
+
comment?: string;
|
|
27
|
+
}
|
|
11
28
|
/** Metadata captured by the @Field() decorator for a single class property. */
|
|
12
|
-
interface FieldMeta {
|
|
29
|
+
interface FieldMeta extends FieldOptions {
|
|
13
30
|
/** Property name on the decorated class. */
|
|
14
31
|
name: string;
|
|
15
|
-
/** Proto3
|
|
16
|
-
protoType:
|
|
32
|
+
/** Proto3 type. Defaults to "string" when omitted. */
|
|
33
|
+
protoType: string;
|
|
17
34
|
/** 1-based field number for proto ordering. */
|
|
18
35
|
number: number;
|
|
36
|
+
/** Constructor captured by reflect-metadata (String, Number, Boolean, Date, etc.). */
|
|
37
|
+
reflectedType?: Constructor;
|
|
19
38
|
}
|
|
20
39
|
/** Collected metadata for a decorated schema class. */
|
|
21
40
|
interface SchemaEntry {
|
|
22
41
|
/** Class constructor reference. */
|
|
23
|
-
ctor:
|
|
42
|
+
ctor: Constructor;
|
|
24
43
|
/** Human-readable schema name (defaults to class name). */
|
|
25
44
|
name: string;
|
|
26
45
|
/** Optional subject prefix for registry-based validation. */
|
|
@@ -34,11 +53,6 @@ interface SchemaEntry {
|
|
|
34
53
|
*
|
|
35
54
|
* Replaces the module-level Map globals from the original schema.ts.
|
|
36
55
|
* 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
56
|
*/
|
|
43
57
|
|
|
44
58
|
declare class SchemaRegistry {
|
|
@@ -48,6 +62,14 @@ declare class SchemaRegistry {
|
|
|
48
62
|
private fieldStore;
|
|
49
63
|
/** Class constructor → subject prefix (populated by @Schema("subject")). */
|
|
50
64
|
private subjectStore;
|
|
65
|
+
/** Set of all decorated schema constructors. */
|
|
66
|
+
private allSchemas;
|
|
67
|
+
/** Tracks a constructor as a decorated schema. */
|
|
68
|
+
registerSchema(ctor: Constructor): void;
|
|
69
|
+
/** Returns true if a constructor is a registered schema. */
|
|
70
|
+
isSchema(ctor: Constructor): boolean;
|
|
71
|
+
/** Looks up a schema constructor by its class name. */
|
|
72
|
+
getSchemaByName(name: string): Constructor | undefined;
|
|
51
73
|
/** Binds a queue name to a fully resolved schema entry. */
|
|
52
74
|
register(queueName: string, entry: SchemaEntry): void;
|
|
53
75
|
/** Returns the schema entry for a queue, or undefined if unregistered. */
|
|
@@ -55,13 +77,13 @@ declare class SchemaRegistry {
|
|
|
55
77
|
/** Returns all registered schema entries. */
|
|
56
78
|
listAll(): SchemaEntry[];
|
|
57
79
|
/** Stores the subject prefix set by @Schema("subject"). */
|
|
58
|
-
setSubject(ctor:
|
|
80
|
+
setSubject(ctor: Constructor, subject: string): void;
|
|
59
81
|
/** Returns the subject prefix for a class, if any. */
|
|
60
|
-
getSubject(ctor:
|
|
82
|
+
getSubject(ctor: Constructor): string | undefined;
|
|
61
83
|
/** Returns the mutable field list for a class, creating it if absent. */
|
|
62
|
-
getOrCreateFields(ctor:
|
|
84
|
+
getOrCreateFields(ctor: Constructor): FieldMeta[];
|
|
63
85
|
/** Returns the field metadata for a class (read-only snapshot). */
|
|
64
|
-
getFields(ctor:
|
|
86
|
+
getFields(ctor: Constructor): readonly FieldMeta[];
|
|
65
87
|
}
|
|
66
88
|
/**
|
|
67
89
|
* Default singleton registry used by decorators.
|
|
@@ -73,37 +95,13 @@ declare class SchemaRegistry {
|
|
|
73
95
|
declare const defaultRegistry: SchemaRegistry;
|
|
74
96
|
|
|
75
97
|
/**
|
|
76
|
-
*
|
|
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
|
-
* }
|
|
98
|
+
* Dual-mode decorators: works with BOTH experimental decorators
|
|
99
|
+
* (reflect-metadata) AND TC39 stage 3 decorators.
|
|
88
100
|
*/
|
|
89
101
|
|
|
90
|
-
/**
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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;
|
|
102
|
+
/** Marks a class as a schema definition. */
|
|
103
|
+
declare function Schema(subject?: string): <T extends Constructor>(target: T, _ctxOrUndefined?: ClassDecoratorContext) => T;
|
|
104
|
+
/** Marks a property as a schema field. */
|
|
105
|
+
declare function Field(opts?: FieldOptions): (targetOrValue: object | undefined, keyOrCtx: string | symbol | ClassFieldDecoratorContext) => void;
|
|
108
106
|
|
|
109
|
-
export { Field, type FieldMeta, type ProtoType, Schema, type SchemaEntry, SchemaRegistry, defaultRegistry };
|
|
107
|
+
export { type Constructor, Field, type FieldMeta, type FieldOptions, type ProtoType, Schema, type SchemaEntry, SchemaRegistry, defaultRegistry };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,36 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
1
4
|
// src/registry.ts
|
|
2
5
|
var SchemaRegistry = class {
|
|
6
|
+
static {
|
|
7
|
+
__name(this, "SchemaRegistry");
|
|
8
|
+
}
|
|
3
9
|
/** Queue name → schema entry. */
|
|
4
10
|
byQueue = /* @__PURE__ */ new Map();
|
|
5
11
|
/** Class constructor → field metadata (populated by decorators). */
|
|
6
12
|
fieldStore = /* @__PURE__ */ new Map();
|
|
7
13
|
/** Class constructor → subject prefix (populated by @Schema("subject")). */
|
|
8
14
|
subjectStore = /* @__PURE__ */ new Map();
|
|
15
|
+
/** Set of all decorated schema constructors. */
|
|
16
|
+
allSchemas = /* @__PURE__ */ new Set();
|
|
17
|
+
/** Tracks a constructor as a decorated schema. */
|
|
18
|
+
registerSchema(ctor) {
|
|
19
|
+
this.allSchemas.add(ctor);
|
|
20
|
+
}
|
|
21
|
+
/** Returns true if a constructor is a registered schema. */
|
|
22
|
+
isSchema(ctor) {
|
|
23
|
+
return this.allSchemas.has(ctor);
|
|
24
|
+
}
|
|
25
|
+
/** Looks up a schema constructor by its class name. */
|
|
26
|
+
getSchemaByName(name) {
|
|
27
|
+
for (const ctor of this.allSchemas) {
|
|
28
|
+
if (ctor.name === name) {
|
|
29
|
+
return ctor;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
9
34
|
/** Binds a queue name to a fully resolved schema entry. */
|
|
10
35
|
register(queueName, entry) {
|
|
11
36
|
this.byQueue.set(queueName, entry);
|
|
@@ -16,7 +41,9 @@ var SchemaRegistry = class {
|
|
|
16
41
|
}
|
|
17
42
|
/** Returns all registered schema entries. */
|
|
18
43
|
listAll() {
|
|
19
|
-
return [
|
|
44
|
+
return [
|
|
45
|
+
...this.byQueue.values()
|
|
46
|
+
];
|
|
20
47
|
}
|
|
21
48
|
/** Stores the subject prefix set by @Schema("subject"). */
|
|
22
49
|
setSubject(ctor, subject) {
|
|
@@ -42,9 +69,59 @@ var SchemaRegistry = class {
|
|
|
42
69
|
};
|
|
43
70
|
var defaultRegistry = new SchemaRegistry();
|
|
44
71
|
|
|
72
|
+
// src/inference.ts
|
|
73
|
+
function inferFromReflectMetadata(target, key) {
|
|
74
|
+
const R = Reflect;
|
|
75
|
+
if (!R.getMetadata) return "string";
|
|
76
|
+
const designType = R.getMetadata("design:type", target, key);
|
|
77
|
+
if (!designType) return "string";
|
|
78
|
+
return inferFromConstructor(designType);
|
|
79
|
+
}
|
|
80
|
+
__name(inferFromReflectMetadata, "inferFromReflectMetadata");
|
|
81
|
+
function inferFromConstructor(ctor) {
|
|
82
|
+
if (ctor === Number) return "double";
|
|
83
|
+
if (ctor === Boolean) return "bool";
|
|
84
|
+
if (ctor === Date) return "google.protobuf.Timestamp";
|
|
85
|
+
if (ctor === Array || ctor === Object) return "bytes";
|
|
86
|
+
if (defaultRegistry.isSchema(ctor)) return ctor.name;
|
|
87
|
+
return "string";
|
|
88
|
+
}
|
|
89
|
+
__name(inferFromConstructor, "inferFromConstructor");
|
|
90
|
+
function inferFromValue(value) {
|
|
91
|
+
if (value instanceof Date) return "google.protobuf.Timestamp";
|
|
92
|
+
if (value && typeof value === "object") {
|
|
93
|
+
const proto = Object.getPrototypeOf(value);
|
|
94
|
+
if (proto && proto.constructor && defaultRegistry.isSchema(proto.constructor)) {
|
|
95
|
+
return proto.constructor.name;
|
|
96
|
+
}
|
|
97
|
+
return "bytes";
|
|
98
|
+
}
|
|
99
|
+
switch (typeof value) {
|
|
100
|
+
case "number":
|
|
101
|
+
return "double";
|
|
102
|
+
case "boolean":
|
|
103
|
+
return "bool";
|
|
104
|
+
default:
|
|
105
|
+
return "string";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
__name(inferFromValue, "inferFromValue");
|
|
109
|
+
|
|
45
110
|
// src/decorators.ts
|
|
111
|
+
import "reflect-metadata";
|
|
112
|
+
var SCHEMA_SYMBOL = /* @__PURE__ */ Symbol.for("rocketmq:schema");
|
|
46
113
|
function Schema(subject) {
|
|
47
|
-
return function(target,
|
|
114
|
+
return function(target, _ctxOrUndefined) {
|
|
115
|
+
Object.defineProperty(target, SCHEMA_SYMBOL, {
|
|
116
|
+
value: true,
|
|
117
|
+
enumerable: false,
|
|
118
|
+
writable: false
|
|
119
|
+
});
|
|
120
|
+
const R = Reflect;
|
|
121
|
+
if (R.defineMetadata) {
|
|
122
|
+
R.defineMetadata(SCHEMA_SYMBOL, true, target);
|
|
123
|
+
}
|
|
124
|
+
defaultRegistry.registerSchema(target);
|
|
48
125
|
defaultRegistry.getOrCreateFields(target);
|
|
49
126
|
if (subject) {
|
|
50
127
|
defaultRegistry.setSubject(target, subject);
|
|
@@ -52,24 +129,65 @@ function Schema(subject) {
|
|
|
52
129
|
return target;
|
|
53
130
|
};
|
|
54
131
|
}
|
|
132
|
+
__name(Schema, "Schema");
|
|
55
133
|
function Field(opts) {
|
|
56
|
-
return function(
|
|
57
|
-
|
|
134
|
+
return function(targetOrValue, keyOrCtx) {
|
|
135
|
+
if (targetOrValue === void 0 || typeof keyOrCtx === "object") {
|
|
136
|
+
return handleTC39Field(opts, keyOrCtx);
|
|
137
|
+
}
|
|
138
|
+
handleExperimentalField(opts, targetOrValue, keyOrCtx);
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
__name(Field, "Field");
|
|
142
|
+
function handleExperimentalField(opts, target, propertyKey) {
|
|
143
|
+
const ctor = target.constructor;
|
|
144
|
+
const name = String(propertyKey);
|
|
145
|
+
const fields = defaultRegistry.getOrCreateFields(ctor);
|
|
146
|
+
if (fields.some((f) => f.name === name)) return;
|
|
147
|
+
const R = Reflect;
|
|
148
|
+
const reflectedType = R.getMetadata ? R.getMetadata("design:type", target, propertyKey) : void 0;
|
|
149
|
+
const protoType = opts?.type ?? inferFromReflectMetadata(target, propertyKey);
|
|
150
|
+
fields.push({
|
|
151
|
+
name,
|
|
152
|
+
protoType,
|
|
153
|
+
number: fields.length + 1,
|
|
154
|
+
reflectedType,
|
|
155
|
+
...opts
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
__name(handleExperimentalField, "handleExperimentalField");
|
|
159
|
+
function handleTC39Field(opts, ctx) {
|
|
160
|
+
const name = String(ctx.name);
|
|
161
|
+
if (opts?.type) {
|
|
58
162
|
ctx.addInitializer(function() {
|
|
59
|
-
|
|
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
|
-
});
|
|
163
|
+
registerTC39Field(this, name, opts.type, opts);
|
|
67
164
|
});
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
return function(initialValue) {
|
|
168
|
+
registerTC39Field(this, name, inferFromValue(initialValue), opts, initialValue);
|
|
169
|
+
return initialValue;
|
|
68
170
|
};
|
|
69
171
|
}
|
|
172
|
+
__name(handleTC39Field, "handleTC39Field");
|
|
173
|
+
function registerTC39Field(instance, name, protoType, opts, initialValue) {
|
|
174
|
+
const ctor = instance.constructor;
|
|
175
|
+
const fields = defaultRegistry.getOrCreateFields(ctor);
|
|
176
|
+
if (fields.some((f) => f.name === name)) return;
|
|
177
|
+
const reflectedType = initialValue && typeof initialValue === "object" ? Object.getPrototypeOf(initialValue).constructor : void 0;
|
|
178
|
+
fields.push({
|
|
179
|
+
name,
|
|
180
|
+
protoType,
|
|
181
|
+
number: fields.length + 1,
|
|
182
|
+
reflectedType,
|
|
183
|
+
...opts
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
__name(registerTC39Field, "registerTC39Field");
|
|
70
187
|
export {
|
|
71
188
|
Field,
|
|
72
189
|
Schema,
|
|
73
190
|
SchemaRegistry,
|
|
74
191
|
defaultRegistry
|
|
75
192
|
};
|
|
193
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/registry.ts","../src/inference.ts","../src/decorators.ts"],"sourcesContent":["/**\n * In-memory schema registry.\n *\n * Replaces the module-level Map globals from the original schema.ts.\n * Injectable via constructor so tests can use isolated instances.\n */\n\nimport type { FieldMeta, SchemaEntry } from './metadata.js';\nimport type { Constructor } from './types.js';\n\nexport class SchemaRegistry {\n /** Queue name → schema entry. */\n private byQueue = new Map<string, SchemaEntry>();\n\n /** Class constructor → field metadata (populated by decorators). */\n private fieldStore = new Map<Constructor, FieldMeta[]>();\n\n /** Class constructor → subject prefix (populated by @Schema(\"subject\")). */\n private subjectStore = new Map<Constructor, string>();\n\n /** Set of all decorated schema constructors. */\n private allSchemas = new Set<Constructor>();\n\n /** Tracks a constructor as a decorated schema. */\n registerSchema(ctor: Constructor): void {\n this.allSchemas.add(ctor);\n }\n\n /** Returns true if a constructor is a registered schema. */\n isSchema(ctor: Constructor): boolean {\n return this.allSchemas.has(ctor);\n }\n\n /** Looks up a schema constructor by its class name. */\n getSchemaByName(name: string): Constructor | undefined {\n for (const ctor of this.allSchemas) {\n if (ctor.name === name) {\n return ctor;\n }\n }\n return undefined;\n }\n\n /** Binds a queue name to a fully resolved schema entry. */\n register(queueName: string, entry: SchemaEntry): void {\n this.byQueue.set(queueName, entry);\n }\n\n /** Returns the schema entry for a queue, or undefined if unregistered. */\n lookup(queueName: string): SchemaEntry | undefined {\n return this.byQueue.get(queueName);\n }\n\n /** Returns all registered schema entries. */\n listAll(): SchemaEntry[] {\n return [...this.byQueue.values()];\n }\n\n /** Stores the subject prefix set by @Schema(\"subject\"). */\n setSubject(ctor: Constructor, subject: string): void {\n this.subjectStore.set(ctor, subject);\n }\n\n /** Returns the subject prefix for a class, if any. */\n getSubject(ctor: Constructor): string | undefined {\n return this.subjectStore.get(ctor);\n }\n\n /** Returns the mutable field list for a class, creating it if absent. */\n getOrCreateFields(ctor: Constructor): FieldMeta[] {\n let fields = this.fieldStore.get(ctor);\n if (!fields) {\n fields = [];\n this.fieldStore.set(ctor, fields);\n }\n return fields;\n }\n\n /** Returns the field metadata for a class (read-only snapshot). */\n getFields(ctor: Constructor): readonly FieldMeta[] {\n return this.fieldStore.get(ctor) ?? [];\n }\n}\n\n/**\n * Default singleton registry used by decorators.\n *\n * Exported so @Schema() and @Field() can register metadata at class\n * definition time without requiring explicit wiring. The core package\n * reads from this same instance during publish/consume.\n */\nexport const defaultRegistry = new SchemaRegistry();\n","import { defaultRegistry } from './registry.js';\nimport type { Constructor } from './types.js';\n\n/** Infers proto type from reflect metadata. */\nexport function inferFromReflectMetadata(target: object, key: string | symbol): string {\n const R = Reflect as unknown as {\n getMetadata?: (key: string, target: object, prop: string | symbol) => Constructor | undefined;\n };\n if (!R.getMetadata) return 'string';\n\n const designType = R.getMetadata('design:type', target, key);\n if (!designType) return 'string';\n\n return inferFromConstructor(designType);\n}\n\n/** Infers type from primitive or schema constructors. */\nexport function inferFromConstructor(ctor: Constructor): string {\n if (ctor === Number) return 'double';\n if (ctor === Boolean) return 'bool';\n if (ctor === Date) return 'google.protobuf.Timestamp';\n if (ctor === Array || ctor === Object) return 'bytes';\n if (defaultRegistry.isSchema(ctor)) return ctor.name;\n return 'string';\n}\n\n/** Infers type from runtime value (TC39). */\nexport function inferFromValue(value: unknown): string {\n if (value instanceof Date) return 'google.protobuf.Timestamp';\n if (value && typeof value === 'object') {\n const proto = Object.getPrototypeOf(value);\n if (proto && proto.constructor && defaultRegistry.isSchema(proto.constructor)) {\n return proto.constructor.name;\n }\n return 'bytes';\n }\n switch (typeof value) {\n case 'number':\n return 'double';\n case 'boolean':\n return 'bool';\n default:\n return 'string';\n }\n}\n","/**\n * Dual-mode decorators: works with BOTH experimental decorators\n * (reflect-metadata) AND TC39 stage 3 decorators.\n */\n\nimport { inferFromReflectMetadata, inferFromValue } from './inference.js';\nimport type { FieldOptions } from './metadata.js';\nimport { defaultRegistry } from './registry.js';\nimport type { Constructor } from './types.js';\n\nimport 'reflect-metadata';\n\nconst SCHEMA_SYMBOL = Symbol.for('rocketmq:schema');\n\n/** Checks if a constructor is decorated as a schema class. */\nexport function isSchemaClass(ctor: unknown): boolean {\n if (typeof ctor !== 'function') return false;\n const R = Reflect as unknown as {\n hasOwnMetadata?: (k: unknown, t: unknown) => boolean;\n };\n return (\n SCHEMA_SYMBOL in ctor ||\n (R.hasOwnMetadata !== undefined && R.hasOwnMetadata(SCHEMA_SYMBOL, ctor))\n );\n}\n\n/** Marks a class as a schema definition. */\nexport function Schema(subject?: string) {\n return function <T extends Constructor>(target: T, _ctxOrUndefined?: ClassDecoratorContext): T {\n Object.defineProperty(target, SCHEMA_SYMBOL, {\n value: true,\n enumerable: false,\n writable: false,\n });\n const R = Reflect as unknown as {\n defineMetadata?: (k: unknown, v: unknown, t: unknown) => void;\n };\n if (R.defineMetadata) {\n R.defineMetadata(SCHEMA_SYMBOL, true, target);\n }\n defaultRegistry.registerSchema(target);\n defaultRegistry.getOrCreateFields(target);\n if (subject) {\n defaultRegistry.setSubject(target, subject);\n }\n return target;\n };\n}\n\n/** Marks a property as a schema field. */\nexport function Field(opts?: FieldOptions) {\n return function (\n targetOrValue: object | undefined,\n keyOrCtx: string | symbol | ClassFieldDecoratorContext,\n // WHY void: Node16 moduleResolution enforces TS1271 — decorator return must be void|any.\n // The TC39 initializer return is consumed by the runtime, not the decorator call site.\n ): void {\n if (targetOrValue === undefined || typeof keyOrCtx === 'object') {\n // WHY cast: TC39 path returns an initializer function at runtime,\n // but the .d.ts must declare void to satisfy experimental decorator constraints.\n return handleTC39Field(opts, keyOrCtx as ClassFieldDecoratorContext) as void;\n }\n handleExperimentalField(opts, targetOrValue, keyOrCtx as string | symbol);\n };\n}\n\n/** Experimental decorator path. */\nfunction handleExperimentalField(\n opts: FieldOptions | undefined,\n target: object,\n propertyKey: string | symbol,\n): void {\n const ctor = target.constructor as Constructor;\n const name = String(propertyKey);\n const fields = defaultRegistry.getOrCreateFields(ctor);\n\n if (fields.some((f) => f.name === name)) return;\n\n const R = Reflect as unknown as {\n getMetadata?: (k: string, t: object, p: string | symbol) => Constructor | undefined;\n };\n const reflectedType = R.getMetadata\n ? R.getMetadata('design:type', target, propertyKey)\n : undefined;\n const protoType = opts?.type ?? inferFromReflectMetadata(target, propertyKey);\n\n fields.push({\n name,\n protoType,\n number: fields.length + 1,\n reflectedType,\n ...opts,\n });\n}\n\n/* v8 ignore start */\n/** TC39 stage 3 decorator path. */\nfunction handleTC39Field(\n opts: FieldOptions | undefined,\n ctx: ClassFieldDecoratorContext,\n): ((this: unknown, initialValue: unknown) => unknown) | void {\n const name = String(ctx.name);\n\n if (opts?.type) {\n ctx.addInitializer(function (this: unknown) {\n registerTC39Field(this, name, opts.type!, opts);\n });\n return;\n }\n\n return function (this: unknown, initialValue: unknown): unknown {\n registerTC39Field(this, name, inferFromValue(initialValue), opts, initialValue);\n return initialValue;\n };\n}\n\n/** Internal TC39 helper to register field inside initializer. */\nfunction registerTC39Field(\n instance: unknown,\n name: string,\n protoType: string,\n opts: FieldOptions | undefined,\n initialValue?: unknown,\n): void {\n const ctor = (instance as Record<string, unknown>).constructor as Constructor;\n const fields = defaultRegistry.getOrCreateFields(ctor);\n if (fields.some((f) => f.name === name)) return;\n\n const reflectedType =\n initialValue && typeof initialValue === 'object'\n ? (Object.getPrototypeOf(initialValue).constructor as Constructor)\n : undefined;\n\n fields.push({\n name,\n protoType,\n number: fields.length + 1,\n reflectedType,\n ...opts,\n });\n}\n"],"mappings":"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","names":["SchemaRegistry","byQueue","Map","fieldStore","subjectStore","allSchemas","Set","registerSchema","ctor","add","isSchema","has","getSchemaByName","name","undefined","register","queueName","entry","set","lookup","get","listAll","values","setSubject","subject","getSubject","getOrCreateFields","fields","getFields","defaultRegistry","inferFromReflectMetadata","target","key","R","Reflect","getMetadata","designType","inferFromConstructor","ctor","Number","Boolean","Date","Array","Object","defaultRegistry","isSchema","name","inferFromValue","value","proto","getPrototypeOf","SCHEMA_SYMBOL","Symbol","for","Schema","subject","target","_ctxOrUndefined","Object","defineProperty","SCHEMA_SYMBOL","value","enumerable","writable","R","Reflect","defineMetadata","defaultRegistry","registerSchema","getOrCreateFields","setSubject","Field","opts","targetOrValue","keyOrCtx","undefined","handleTC39Field","handleExperimentalField","propertyKey","ctor","name","String","fields","some","f","reflectedType","getMetadata","protoType","type","inferFromReflectMetadata","push","number","length","ctx","addInitializer","registerTC39Field","initialValue","inferFromValue","instance","getPrototypeOf"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rocketmq/schema",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"types": "./dist/index.d.ts"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"reflect-metadata": "^0.2.2"
|
|
13
|
+
},
|
|
11
14
|
"scripts": {
|
|
12
15
|
"build": "tsup",
|
|
13
16
|
"test": "vitest run"
|
package/src/decorators.test.ts
CHANGED
|
@@ -7,9 +7,22 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { describe, it, expect } from 'vitest';
|
|
10
|
-
import { Schema, Field } from './decorators.js';
|
|
10
|
+
import { Schema, Field, isSchemaClass } from './decorators.js';
|
|
11
11
|
import { defaultRegistry } from './registry.js';
|
|
12
12
|
|
|
13
|
+
describe('isSchemaClass', () => {
|
|
14
|
+
it('detects valid and invalid schemas', () => {
|
|
15
|
+
@Schema()
|
|
16
|
+
class A {}
|
|
17
|
+
expect(isSchemaClass(A)).toBe(true);
|
|
18
|
+
|
|
19
|
+
class B {}
|
|
20
|
+
expect(isSchemaClass(B)).toBe(false);
|
|
21
|
+
expect(isSchemaClass(null)).toBe(false);
|
|
22
|
+
expect(isSchemaClass(123)).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
13
26
|
describe('@Schema() decorator', () => {
|
|
14
27
|
it('registers field list for a decorated class', () => {
|
|
15
28
|
@Schema()
|
package/src/decorators.ts
CHANGED
|
@@ -1,35 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
-
* }
|
|
2
|
+
* Dual-mode decorators: works with BOTH experimental decorators
|
|
3
|
+
* (reflect-metadata) AND TC39 stage 3 decorators.
|
|
14
4
|
*/
|
|
15
5
|
|
|
16
|
-
import
|
|
6
|
+
import { inferFromReflectMetadata, inferFromValue } from './inference.js';
|
|
7
|
+
import type { FieldOptions } from './metadata.js';
|
|
17
8
|
import { defaultRegistry } from './registry.js';
|
|
9
|
+
import type { Constructor } from './types.js';
|
|
18
10
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
11
|
+
import 'reflect-metadata';
|
|
12
|
+
|
|
13
|
+
const SCHEMA_SYMBOL = Symbol.for('rocketmq:schema');
|
|
14
|
+
|
|
15
|
+
/** Checks if a constructor is decorated as a schema class. */
|
|
16
|
+
export function isSchemaClass(ctor: unknown): boolean {
|
|
17
|
+
if (typeof ctor !== 'function') return false;
|
|
18
|
+
const R = Reflect as unknown as {
|
|
19
|
+
hasOwnMetadata?: (k: unknown, t: unknown) => boolean;
|
|
20
|
+
};
|
|
21
|
+
return (
|
|
22
|
+
SCHEMA_SYMBOL in ctor ||
|
|
23
|
+
(R.hasOwnMetadata !== undefined && R.hasOwnMetadata(SCHEMA_SYMBOL, ctor))
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Marks a class as a schema definition. */
|
|
25
28
|
export function Schema(subject?: string) {
|
|
26
|
-
return function <T extends
|
|
27
|
-
target
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
return function <T extends Constructor>(target: T, _ctxOrUndefined?: ClassDecoratorContext): T {
|
|
30
|
+
Object.defineProperty(target, SCHEMA_SYMBOL, {
|
|
31
|
+
value: true,
|
|
32
|
+
enumerable: false,
|
|
33
|
+
writable: false,
|
|
34
|
+
});
|
|
35
|
+
const R = Reflect as unknown as {
|
|
36
|
+
defineMetadata?: (k: unknown, v: unknown, t: unknown) => void;
|
|
37
|
+
};
|
|
38
|
+
if (R.defineMetadata) {
|
|
39
|
+
R.defineMetadata(SCHEMA_SYMBOL, true, target);
|
|
40
|
+
}
|
|
41
|
+
defaultRegistry.registerSchema(target);
|
|
31
42
|
defaultRegistry.getOrCreateFields(target);
|
|
32
|
-
|
|
33
43
|
if (subject) {
|
|
34
44
|
defaultRegistry.setSubject(target, subject);
|
|
35
45
|
}
|
|
@@ -37,33 +47,95 @@ export function Schema(subject?: string) {
|
|
|
37
47
|
};
|
|
38
48
|
}
|
|
39
49
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
/** Marks a property as a schema field. */
|
|
51
|
+
export function Field(opts?: FieldOptions) {
|
|
52
|
+
return function (
|
|
53
|
+
targetOrValue: object | undefined,
|
|
54
|
+
keyOrCtx: string | symbol | ClassFieldDecoratorContext,
|
|
55
|
+
// WHY void: Node16 moduleResolution enforces TS1271 — decorator return must be void|any.
|
|
56
|
+
// The TC39 initializer return is consumed by the runtime, not the decorator call site.
|
|
57
|
+
): void {
|
|
58
|
+
if (targetOrValue === undefined || typeof keyOrCtx === 'object') {
|
|
59
|
+
// WHY cast: TC39 path returns an initializer function at runtime,
|
|
60
|
+
// but the .d.ts must declare void to satisfy experimental decorator constraints.
|
|
61
|
+
return handleTC39Field(opts, keyOrCtx as ClassFieldDecoratorContext) as void;
|
|
62
|
+
}
|
|
63
|
+
handleExperimentalField(opts, targetOrValue, keyOrCtx as string | symbol);
|
|
64
|
+
};
|
|
43
65
|
}
|
|
44
66
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
67
|
+
/** Experimental decorator path. */
|
|
68
|
+
function handleExperimentalField(
|
|
69
|
+
opts: FieldOptions | undefined,
|
|
70
|
+
target: object,
|
|
71
|
+
propertyKey: string | symbol,
|
|
72
|
+
): void {
|
|
73
|
+
const ctor = target.constructor as Constructor;
|
|
74
|
+
const name = String(propertyKey);
|
|
75
|
+
const fields = defaultRegistry.getOrCreateFields(ctor);
|
|
54
76
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
77
|
+
if (fields.some((f) => f.name === name)) return;
|
|
78
|
+
|
|
79
|
+
const R = Reflect as unknown as {
|
|
80
|
+
getMetadata?: (k: string, t: object, p: string | symbol) => Constructor | undefined;
|
|
81
|
+
};
|
|
82
|
+
const reflectedType = R.getMetadata
|
|
83
|
+
? R.getMetadata('design:type', target, propertyKey)
|
|
84
|
+
: undefined;
|
|
85
|
+
const protoType = opts?.type ?? inferFromReflectMetadata(target, propertyKey);
|
|
86
|
+
|
|
87
|
+
fields.push({
|
|
88
|
+
name,
|
|
89
|
+
protoType,
|
|
90
|
+
number: fields.length + 1,
|
|
91
|
+
reflectedType,
|
|
92
|
+
...opts,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
58
95
|
|
|
59
|
-
|
|
60
|
-
|
|
96
|
+
/* v8 ignore start */
|
|
97
|
+
/** TC39 stage 3 decorator path. */
|
|
98
|
+
function handleTC39Field(
|
|
99
|
+
opts: FieldOptions | undefined,
|
|
100
|
+
ctx: ClassFieldDecoratorContext,
|
|
101
|
+
): ((this: unknown, initialValue: unknown) => unknown) | void {
|
|
102
|
+
const name = String(ctx.name);
|
|
61
103
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
number: fields.length + 1,
|
|
66
|
-
});
|
|
104
|
+
if (opts?.type) {
|
|
105
|
+
ctx.addInitializer(function (this: unknown) {
|
|
106
|
+
registerTC39Field(this, name, opts.type!, opts);
|
|
67
107
|
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return function (this: unknown, initialValue: unknown): unknown {
|
|
112
|
+
registerTC39Field(this, name, inferFromValue(initialValue), opts, initialValue);
|
|
113
|
+
return initialValue;
|
|
68
114
|
};
|
|
69
115
|
}
|
|
116
|
+
|
|
117
|
+
/** Internal TC39 helper to register field inside initializer. */
|
|
118
|
+
function registerTC39Field(
|
|
119
|
+
instance: unknown,
|
|
120
|
+
name: string,
|
|
121
|
+
protoType: string,
|
|
122
|
+
opts: FieldOptions | undefined,
|
|
123
|
+
initialValue?: unknown,
|
|
124
|
+
): void {
|
|
125
|
+
const ctor = (instance as Record<string, unknown>).constructor as Constructor;
|
|
126
|
+
const fields = defaultRegistry.getOrCreateFields(ctor);
|
|
127
|
+
if (fields.some((f) => f.name === name)) return;
|
|
128
|
+
|
|
129
|
+
const reflectedType =
|
|
130
|
+
initialValue && typeof initialValue === 'object'
|
|
131
|
+
? (Object.getPrototypeOf(initialValue).constructor as Constructor)
|
|
132
|
+
: undefined;
|
|
133
|
+
|
|
134
|
+
fields.push({
|
|
135
|
+
name,
|
|
136
|
+
protoType,
|
|
137
|
+
number: fields.length + 1,
|
|
138
|
+
reflectedType,
|
|
139
|
+
...opts,
|
|
140
|
+
});
|
|
141
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export type { ProtoType, FieldMeta, SchemaEntry } from './metadata.js';
|
|
1
|
+
export type { ProtoType, FieldOptions, FieldMeta, SchemaEntry } from './metadata.js';
|
|
2
2
|
export { SchemaRegistry, defaultRegistry } from './registry.js';
|
|
3
3
|
export { Schema, Field } from './decorators.js';
|
|
4
|
+
export type { Constructor } from './types.js';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { inferFromConstructor, inferFromValue, inferFromReflectMetadata } from './inference.js';
|
|
3
|
+
import { defaultRegistry } from './registry.js';
|
|
4
|
+
|
|
5
|
+
describe('inference', () => {
|
|
6
|
+
describe('inferFromConstructor', () => {
|
|
7
|
+
it('infers proto types from JS constructors', () => {
|
|
8
|
+
expect(inferFromConstructor(Number)).toBe('double');
|
|
9
|
+
expect(inferFromConstructor(Boolean)).toBe('bool');
|
|
10
|
+
expect(inferFromConstructor(Date)).toBe('google.protobuf.Timestamp');
|
|
11
|
+
expect(inferFromConstructor(Array)).toBe('bytes');
|
|
12
|
+
expect(inferFromConstructor(Object)).toBe('bytes');
|
|
13
|
+
expect(inferFromConstructor(String)).toBe('string');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('infers from schema constructors', () => {
|
|
17
|
+
class NestedSchema {}
|
|
18
|
+
defaultRegistry.registerSchema(NestedSchema);
|
|
19
|
+
expect(inferFromConstructor(NestedSchema)).toBe('NestedSchema');
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('inferFromValue', () => {
|
|
24
|
+
it('infers proto types from JS values', () => {
|
|
25
|
+
expect(inferFromValue(1)).toBe('double');
|
|
26
|
+
expect(inferFromValue(true)).toBe('bool');
|
|
27
|
+
expect(inferFromValue(new Date())).toBe('google.protobuf.Timestamp');
|
|
28
|
+
expect(inferFromValue([])).toBe('bytes');
|
|
29
|
+
expect(inferFromValue({})).toBe('bytes');
|
|
30
|
+
expect(inferFromValue('hello')).toBe('string');
|
|
31
|
+
expect(inferFromValue(undefined)).toBe('string');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('infers from schema instances', () => {
|
|
35
|
+
class NestedSchema {}
|
|
36
|
+
defaultRegistry.registerSchema(NestedSchema);
|
|
37
|
+
expect(inferFromValue(new NestedSchema())).toBe('NestedSchema');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('inferFromReflectMetadata', () => {
|
|
42
|
+
it('falls back to string if design:type is missing', () => {
|
|
43
|
+
expect(inferFromReflectMetadata({}, 'key')).toBe('string');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
package/src/inference.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { defaultRegistry } from './registry.js';
|
|
2
|
+
import type { Constructor } from './types.js';
|
|
3
|
+
|
|
4
|
+
/** Infers proto type from reflect metadata. */
|
|
5
|
+
export function inferFromReflectMetadata(target: object, key: string | symbol): string {
|
|
6
|
+
const R = Reflect as unknown as {
|
|
7
|
+
getMetadata?: (key: string, target: object, prop: string | symbol) => Constructor | undefined;
|
|
8
|
+
};
|
|
9
|
+
if (!R.getMetadata) return 'string';
|
|
10
|
+
|
|
11
|
+
const designType = R.getMetadata('design:type', target, key);
|
|
12
|
+
if (!designType) return 'string';
|
|
13
|
+
|
|
14
|
+
return inferFromConstructor(designType);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Infers type from primitive or schema constructors. */
|
|
18
|
+
export function inferFromConstructor(ctor: Constructor): string {
|
|
19
|
+
if (ctor === Number) return 'double';
|
|
20
|
+
if (ctor === Boolean) return 'bool';
|
|
21
|
+
if (ctor === Date) return 'google.protobuf.Timestamp';
|
|
22
|
+
if (ctor === Array || ctor === Object) return 'bytes';
|
|
23
|
+
if (defaultRegistry.isSchema(ctor)) return ctor.name;
|
|
24
|
+
return 'string';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Infers type from runtime value (TC39). */
|
|
28
|
+
export function inferFromValue(value: unknown): string {
|
|
29
|
+
if (value instanceof Date) return 'google.protobuf.Timestamp';
|
|
30
|
+
if (value && typeof value === 'object') {
|
|
31
|
+
const proto = Object.getPrototypeOf(value);
|
|
32
|
+
if (proto && proto.constructor && defaultRegistry.isSchema(proto.constructor)) {
|
|
33
|
+
return proto.constructor.name;
|
|
34
|
+
}
|
|
35
|
+
return 'bytes';
|
|
36
|
+
}
|
|
37
|
+
switch (typeof value) {
|
|
38
|
+
case 'number':
|
|
39
|
+
return 'double';
|
|
40
|
+
case 'boolean':
|
|
41
|
+
return 'bool';
|
|
42
|
+
default:
|
|
43
|
+
return 'string';
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/metadata.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
+
import type { Constructor } from './types.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Protobuf wire types supported by the schema decorator system.
|
|
3
5
|
*
|
|
4
6
|
* Maps 1:1 to proto3 scalar types so the SDK can generate
|
|
5
7
|
* `.proto` definitions without requiring protobuf knowledge.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* @Field({ type: "int32" }) qty!: number;
|
|
9
8
|
*/
|
|
10
9
|
export type ProtoType =
|
|
11
10
|
| 'string'
|
|
@@ -18,20 +17,34 @@ export type ProtoType =
|
|
|
18
17
|
| 'bool'
|
|
19
18
|
| 'bytes';
|
|
20
19
|
|
|
20
|
+
/** Additional configuration options passed to the @Field() decorator. */
|
|
21
|
+
export interface FieldOptions {
|
|
22
|
+
/** Explicit proto3 type override (scalar or custom message name). */
|
|
23
|
+
type?: string;
|
|
24
|
+
/** Emits 'repeated' prefix for arrays. */
|
|
25
|
+
repeated?: boolean;
|
|
26
|
+
/** Emits 'optional' prefix for optional presence. */
|
|
27
|
+
optional?: boolean;
|
|
28
|
+
/** Inline comment written above the field. */
|
|
29
|
+
comment?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
/** Metadata captured by the @Field() decorator for a single class property. */
|
|
22
|
-
export interface FieldMeta {
|
|
33
|
+
export interface FieldMeta extends FieldOptions {
|
|
23
34
|
/** Property name on the decorated class. */
|
|
24
35
|
name: string;
|
|
25
|
-
/** Proto3
|
|
26
|
-
protoType:
|
|
36
|
+
/** Proto3 type. Defaults to "string" when omitted. */
|
|
37
|
+
protoType: string;
|
|
27
38
|
/** 1-based field number for proto ordering. */
|
|
28
39
|
number: number;
|
|
40
|
+
/** Constructor captured by reflect-metadata (String, Number, Boolean, Date, etc.). */
|
|
41
|
+
reflectedType?: Constructor;
|
|
29
42
|
}
|
|
30
43
|
|
|
31
44
|
/** Collected metadata for a decorated schema class. */
|
|
32
45
|
export interface SchemaEntry {
|
|
33
46
|
/** Class constructor reference. */
|
|
34
|
-
ctor:
|
|
47
|
+
ctor: Constructor;
|
|
35
48
|
/** Human-readable schema name (defaults to class name). */
|
|
36
49
|
name: string;
|
|
37
50
|
/** Optional subject prefix for registry-based validation. */
|
package/src/registry.test.ts
CHANGED
|
@@ -43,6 +43,22 @@ describe('SchemaRegistry', () => {
|
|
|
43
43
|
});
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
describe('schema constructors', () => {
|
|
47
|
+
it('tracks decorated schemas via isSchema', () => {
|
|
48
|
+
class A {}
|
|
49
|
+
registry.registerSchema(A);
|
|
50
|
+
expect(registry.isSchema(A)).toBe(true);
|
|
51
|
+
expect(registry.isSchema(class B {})).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('looks up schema constructor by name', () => {
|
|
55
|
+
class FooSchema {}
|
|
56
|
+
registry.registerSchema(FooSchema);
|
|
57
|
+
expect(registry.getSchemaByName('FooSchema')).toBe(FooSchema);
|
|
58
|
+
expect(registry.getSchemaByName('BarSchema')).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
46
62
|
describe('listAll', () => {
|
|
47
63
|
it('returns empty array when nothing registered', () => {
|
|
48
64
|
expect(registry.listAll()).toEqual([]);
|
package/src/registry.ts
CHANGED
|
@@ -3,24 +3,43 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Replaces the module-level Map globals from the original schema.ts.
|
|
5
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
6
|
*/
|
|
12
7
|
|
|
13
8
|
import type { FieldMeta, SchemaEntry } from './metadata.js';
|
|
9
|
+
import type { Constructor } from './types.js';
|
|
14
10
|
|
|
15
11
|
export class SchemaRegistry {
|
|
16
12
|
/** Queue name → schema entry. */
|
|
17
13
|
private byQueue = new Map<string, SchemaEntry>();
|
|
18
14
|
|
|
19
15
|
/** Class constructor → field metadata (populated by decorators). */
|
|
20
|
-
private fieldStore = new Map<
|
|
16
|
+
private fieldStore = new Map<Constructor, FieldMeta[]>();
|
|
21
17
|
|
|
22
18
|
/** Class constructor → subject prefix (populated by @Schema("subject")). */
|
|
23
|
-
private subjectStore = new Map<
|
|
19
|
+
private subjectStore = new Map<Constructor, string>();
|
|
20
|
+
|
|
21
|
+
/** Set of all decorated schema constructors. */
|
|
22
|
+
private allSchemas = new Set<Constructor>();
|
|
23
|
+
|
|
24
|
+
/** Tracks a constructor as a decorated schema. */
|
|
25
|
+
registerSchema(ctor: Constructor): void {
|
|
26
|
+
this.allSchemas.add(ctor);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Returns true if a constructor is a registered schema. */
|
|
30
|
+
isSchema(ctor: Constructor): boolean {
|
|
31
|
+
return this.allSchemas.has(ctor);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Looks up a schema constructor by its class name. */
|
|
35
|
+
getSchemaByName(name: string): Constructor | undefined {
|
|
36
|
+
for (const ctor of this.allSchemas) {
|
|
37
|
+
if (ctor.name === name) {
|
|
38
|
+
return ctor;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
24
43
|
|
|
25
44
|
/** Binds a queue name to a fully resolved schema entry. */
|
|
26
45
|
register(queueName: string, entry: SchemaEntry): void {
|
|
@@ -38,17 +57,17 @@ export class SchemaRegistry {
|
|
|
38
57
|
}
|
|
39
58
|
|
|
40
59
|
/** Stores the subject prefix set by @Schema("subject"). */
|
|
41
|
-
setSubject(ctor:
|
|
60
|
+
setSubject(ctor: Constructor, subject: string): void {
|
|
42
61
|
this.subjectStore.set(ctor, subject);
|
|
43
62
|
}
|
|
44
63
|
|
|
45
64
|
/** Returns the subject prefix for a class, if any. */
|
|
46
|
-
getSubject(ctor:
|
|
65
|
+
getSubject(ctor: Constructor): string | undefined {
|
|
47
66
|
return this.subjectStore.get(ctor);
|
|
48
67
|
}
|
|
49
68
|
|
|
50
69
|
/** Returns the mutable field list for a class, creating it if absent. */
|
|
51
|
-
getOrCreateFields(ctor:
|
|
70
|
+
getOrCreateFields(ctor: Constructor): FieldMeta[] {
|
|
52
71
|
let fields = this.fieldStore.get(ctor);
|
|
53
72
|
if (!fields) {
|
|
54
73
|
fields = [];
|
|
@@ -58,7 +77,7 @@ export class SchemaRegistry {
|
|
|
58
77
|
}
|
|
59
78
|
|
|
60
79
|
/** Returns the field metadata for a class (read-only snapshot). */
|
|
61
|
-
getFields(ctor:
|
|
80
|
+
getFields(ctor: Constructor): readonly FieldMeta[] {
|
|
62
81
|
return this.fieldStore.get(ctor) ?? [];
|
|
63
82
|
}
|
|
64
83
|
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for the schema package.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents a class constructor.
|
|
7
|
+
* Used to replace the generic 'Function' type for stricter type safety.
|
|
8
|
+
*/
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
export type Constructor<T = object> = new (...args: any[]) => T;
|