speed 1.3.1 → 1.3.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/README.md CHANGED
@@ -33,6 +33,22 @@ registry.unregister("user-service", "127.0.0.1", 8080); // true
33
33
 
34
34
  > 详见 `docs/1.3.1-本地注册表.md`(为什么 → 怎么用 → 原理 → 怎么扩展)。
35
35
 
36
+ ## 服务声明(1.3.2)
37
+
38
+ `@Service` 装饰器把类标记为「可被发现的微服务」——**只声明身份,不注册实例**(真实 host/port 要等启动后拿到再提交):
39
+
40
+ ```ts
41
+ import { Service, serviceRegistrar } from "speed";
42
+
43
+ @Service("user-service")
44
+ class UserService {}
45
+
46
+ serviceRegistrar.register("user-service", { host: "127.0.0.1", port: 8080 });
47
+ serviceRegistrar.getRegistry().getInstances("user-service");
48
+ ```
49
+
50
+ > `@Service` 只声明,真实 host/port 在启动后用 `serviceRegistrar.register()` 提交;详见 `docs/1.3.2-Service装饰器.md`。
51
+
36
52
  ## 模块(单包 `speed` + 子路径导出)
37
53
 
38
54
  | 子路径 | 模块 | 版本 |
@@ -1 +1 @@
1
- export {};
1
+ export { Service } from "./service.decorator";
@@ -1,2 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Service = void 0;
4
+ // core —— 共享核心:@Service / @RpcClient / @GatewayRoute / @Config 装饰器 + 基础设施
5
+ var service_decorator_1 = require("./service.decorator");
6
+ Object.defineProperty(exports, "Service", { enumerable: true, get: function () { return service_decorator_1.Service; } });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @Service —— 类装饰器:把类标记为「可被发现的微服务」,声明即注册。
3
+ *
4
+ * 支持四种形式:
5
+ * @Service // 服务名默认用类名
6
+ * @Service() // 同上,显式空参数
7
+ * @Service("user-service") // 指定服务名
8
+ * @Service({ name, group, version }) // 带元数据
9
+ *
10
+ * 注意:装饰器只做「声明」(登记身份元数据),不注册实例——
11
+ * 真实 host/port 要等服务 start 后才能拿到,由 serviceRegistrar.register() 在启动后提交。
12
+ */
13
+ export declare function Service(...args: any[]): any;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Service = Service;
4
+ const service_registrar_1 = require("../registry/service-registrar");
5
+ // 解析 @Service 的入参:string 视为服务名,对象视为元数据,undefined 用默认。
6
+ function resolveOptions(param) {
7
+ if (typeof param === "string") {
8
+ return { name: param };
9
+ }
10
+ return param ?? {};
11
+ }
12
+ /**
13
+ * @Service —— 类装饰器:把类标记为「可被发现的微服务」,声明即注册。
14
+ *
15
+ * 支持四种形式:
16
+ * @Service // 服务名默认用类名
17
+ * @Service() // 同上,显式空参数
18
+ * @Service("user-service") // 指定服务名
19
+ * @Service({ name, group, version }) // 带元数据
20
+ *
21
+ * 注意:装饰器只做「声明」(登记身份元数据),不注册实例——
22
+ * 真实 host/port 要等服务 start 后才能拿到,由 serviceRegistrar.register() 在启动后提交。
23
+ */
24
+ function Service(...args) {
25
+ // 直接装饰形式:@Service(无参数)—— 标准类装饰器收到 (value, context)
26
+ if (args.length === 2 && typeof args[1] === "object" && args[1] !== null && args[1].kind === "class") {
27
+ const context = args[1];
28
+ context.addInitializer(function () {
29
+ // 标准类装饰器 initializer 的 this 是类本身(非实例),this.name 即类名
30
+ service_registrar_1.serviceRegistrar.declare(this.name, {});
31
+ });
32
+ return;
33
+ }
34
+ // 工厂形式:@Service() / @Service("name") / @Service({...})
35
+ const options = resolveOptions(args[0]);
36
+ return function (value, context) {
37
+ context.addInitializer(function () {
38
+ service_registrar_1.serviceRegistrar.declare(this.name, options);
39
+ });
40
+ };
41
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./registry";
2
+ export * from "./core";
2
3
  export {};
package/dist/index.js CHANGED
@@ -15,8 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  // speed —— TypeScript 微服务编排框架(对标 Spring Cloud),构建于 typespeed 之上。
18
- //
19
- // 骨架阶段(1.3.x):speed 自身的微服务能力(@Service / @RpcClient / @GatewayRoute / @Config 等)
20
- // 随模块开发逐步加入;项目入口引导暂由 typespeed 的 @app / ServerFactory 承担
21
- // (脚手架模板 src/main.ts 直接从 "typespeed" 引入,保证 MAIN_PATH 正确解析到应用目录)。
22
18
  __exportStar(require("./registry"), exports);
19
+ __exportStar(require("./core"), exports);
@@ -1,2 +1,4 @@
1
1
  export { LocalRegistry } from "./registry";
2
- export type { ServiceInstance } from "./registry";
2
+ export type { ServiceInstance, Registration, Registry } from "./registry";
3
+ export { ServiceRegistrar, serviceRegistrar } from "./service-registrar";
4
+ export type { ServiceOptions } from "./service-registrar";
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LocalRegistry = void 0;
3
+ exports.serviceRegistrar = exports.ServiceRegistrar = exports.LocalRegistry = void 0;
4
4
  // ① 注册中心(服务注册 + 发现)—— 1.3.x 实现
5
5
  var registry_1 = require("./registry");
6
6
  Object.defineProperty(exports, "LocalRegistry", { enumerable: true, get: function () { return registry_1.LocalRegistry; } });
7
+ var service_registrar_1 = require("./service-registrar");
8
+ Object.defineProperty(exports, "ServiceRegistrar", { enumerable: true, get: function () { return service_registrar_1.ServiceRegistrar; } });
9
+ Object.defineProperty(exports, "serviceRegistrar", { enumerable: true, get: function () { return service_registrar_1.serviceRegistrar; } });
@@ -3,12 +3,25 @@ export interface ServiceInstance {
3
3
  port: number;
4
4
  registeredAt?: number;
5
5
  metadata?: Record<string, unknown>;
6
+ status?: string;
6
7
  }
7
- export declare class LocalRegistry {
8
+ export interface Registration {
9
+ readonly instanceId: string;
10
+ renew(): void;
11
+ deregister(): boolean;
12
+ }
13
+ export interface Registry {
14
+ register(serviceName: string, instance: ServiceInstance): Registration;
15
+ unregister(serviceName: string, host: string, port: number): boolean;
16
+ getInstances(serviceName: string): ServiceInstance[];
17
+ }
18
+ export declare class LocalRegistry implements Registry {
8
19
  private instances;
9
- register(serviceName: string, instance: ServiceInstance): ServiceInstance;
20
+ register(serviceName: string, instance: ServiceInstance): Registration;
10
21
  unregister(serviceName: string, host: string, port: number): boolean;
11
22
  getInstances(serviceName: string): ServiceInstance[];
12
23
  getServiceNames(): string[];
13
24
  getInstanceCount(serviceName: string): number;
25
+ private normalize;
26
+ private makeRegistration;
14
27
  }
@@ -4,39 +4,33 @@ exports.LocalRegistry = void 0;
4
4
  // 本地注册表:服务注册 + 发现的单点真相(纯内存,不涉及网络)。
5
5
  // 数据结构为两级:服务名 -> 实例列表(实例列表用数组,实例带 metadata 扩展位)。
6
6
  class LocalRegistry {
7
- // 两级结构:服务名 -> 实例列表(实例列表用数组)
8
7
  instances = new Map();
9
8
  // 注册一个实例。同 host:port 重复注册只更新不新增。
10
9
  register(serviceName, instance) {
11
- // 去重键:host:port(同一服务内的实例唯一标识)
12
10
  const key = `${instance.host}:${instance.port}`;
13
11
  const list = this.instances.get(serviceName);
14
- // 服务已存在时,在其列表里做线性查找(实例数量少,线性足够)
12
+ let target;
15
13
  if (list) {
16
14
  const existing = list.find((item) => `${item.host}:${item.port}` === key);
17
15
  if (existing) {
18
- // 原地刷新注册时间(心跳语义),并替换 metadata(若本次提供)
16
+ // 原地刷新注册时间(心跳语义),并替换 metadata/status(若本次提供)
19
17
  existing.registeredAt = Date.now();
20
- if (instance.metadata !== undefined) {
18
+ if (instance.metadata !== undefined)
21
19
  existing.metadata = instance.metadata;
22
- }
23
- return existing;
20
+ if (instance.status !== undefined)
21
+ existing.status = instance.status;
22
+ target = existing;
23
+ }
24
+ else {
25
+ target = this.normalize(instance);
26
+ list.push(target);
24
27
  }
25
- // 新实例补全 registeredAt 后 push 进列表
26
- const newInstance = {
27
- ...instance,
28
- registeredAt: instance.registeredAt ?? Date.now(),
29
- };
30
- list.push(newInstance);
31
- return newInstance;
32
28
  }
33
- // 服务不存在:新建列表并存入 map
34
- const newInstance = {
35
- ...instance,
36
- registeredAt: instance.registeredAt ?? Date.now(),
37
- };
38
- this.instances.set(serviceName, [newInstance]);
39
- return newInstance;
29
+ else {
30
+ target = this.normalize(instance);
31
+ this.instances.set(serviceName, [target]);
32
+ }
33
+ return this.makeRegistration(serviceName, target);
40
34
  }
41
35
  // 注销。找到并移除同 host:port 的实例返回 true;否则返回 false。
42
36
  unregister(serviceName, host, port) {
@@ -69,5 +63,26 @@ class LocalRegistry {
69
63
  getInstanceCount(serviceName) {
70
64
  return this.instances.get(serviceName)?.length ?? 0;
71
65
  }
66
+ // 补全实例的 registeredAt(缺省 Date.now())与 status(缺省 "UP")
67
+ normalize(instance) {
68
+ return {
69
+ ...instance,
70
+ registeredAt: instance.registeredAt ?? Date.now(),
71
+ status: instance.status ?? "UP",
72
+ };
73
+ }
74
+ // 为已登记实例生成注册句柄;句柄闭包持有该实例引用与所在服务名。
75
+ makeRegistration(serviceName, target) {
76
+ const instanceId = `${target.host}:${target.port}`;
77
+ const registry = this;
78
+ return {
79
+ instanceId,
80
+ renew: () => {
81
+ // 1.3.2 占位:刷新 registeredAt;1.3.3 起改为刷新 lastHeartbeat
82
+ target.registeredAt = Date.now();
83
+ },
84
+ deregister: () => registry.unregister(serviceName, target.host, target.port),
85
+ };
86
+ }
72
87
  }
73
88
  exports.LocalRegistry = LocalRegistry;
@@ -0,0 +1,15 @@
1
+ import { ServiceInstance, Registration, Registry } from "./registry";
2
+ export interface ServiceOptions {
3
+ name?: string;
4
+ group?: string;
5
+ version?: string;
6
+ }
7
+ export declare class ServiceRegistrar {
8
+ private declarations;
9
+ private registry;
10
+ declare(className: string, options: ServiceOptions): void;
11
+ register(serviceName: string, instance: ServiceInstance): Registration;
12
+ getRegistry(): Registry;
13
+ clear(): void;
14
+ }
15
+ export declare const serviceRegistrar: ServiceRegistrar;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serviceRegistrar = exports.ServiceRegistrar = void 0;
4
+ const registry_1 = require("./registry");
5
+ // 服务注册协调器:负责「声明 → 提交」两阶段。
6
+ // - 声明:@Service 装饰器在类定义时调用 declare(),只登记身份元数据,不注册实例。
7
+ // - 提交:服务启动后调用 register(),用真实 host/port 注册到注册表(每次启动一次)。
8
+ // 「同名多实例」= 一次声明 + N 次提交(合法,负载均衡基础);
9
+ // 「同名异类」= 同一服务名出现多次声明(declare 时告警)。
10
+ class ServiceRegistrar {
11
+ // 声明表:服务名 -> 声明列表(同名异类时会有多条)
12
+ declarations = new Map();
13
+ registry = new registry_1.LocalRegistry();
14
+ // @Service 装饰器调用:只做声明(幂等——同一类重复声明不重复登记)。
15
+ declare(className, options) {
16
+ const name = options.name ?? className;
17
+ const list = this.declarations.get(name) ?? [];
18
+ // 幂等:同一类重复声明(重复 import / 多次装饰)不重复登记
19
+ if (list.some((d) => d.className === className)) {
20
+ return;
21
+ }
22
+ // 同名异类:已有别的类占用该服务名 → 告警(不阻断,多实例场景由「提交」区分)
23
+ if (list.length > 0) {
24
+ console.warn(`[speed] 服务名 "${name}" 已被类 ${list.map((d) => d.className).join(", ")} 占用,` +
25
+ `本次 ${className} 声明可能造成同名异类冲突。`);
26
+ }
27
+ list.push({ className, name, group: options.group, version: options.version });
28
+ this.declarations.set(name, list);
29
+ }
30
+ // 提交:把已声明的服务名注册到注册表(每次启动调用一次)。
31
+ // host/port 唯一可信来源是真实监听值(server.address().port / start() 后拿到)。
32
+ register(serviceName, instance) {
33
+ const declarations = this.declarations.get(serviceName);
34
+ if (!declarations || declarations.length === 0) {
35
+ throw new Error(`[speed] 服务名 "${serviceName}" 未通过 @Service 声明,无法注册。`);
36
+ }
37
+ // 取第一条声明的 group/version 合并进 metadata(同名异类场景下取首条;一般只有一个)
38
+ const decl = declarations[0];
39
+ const metadata = { ...(instance.metadata ?? {}) };
40
+ if (decl.group !== undefined) {
41
+ metadata.group = decl.group;
42
+ }
43
+ if (decl.version !== undefined) {
44
+ metadata.version = decl.version;
45
+ }
46
+ return this.registry.register(serviceName, { ...instance, metadata });
47
+ }
48
+ // 查询入口:拿到底层注册表(供 getInstances / 后续 discover 使用)。
49
+ getRegistry() {
50
+ return this.registry;
51
+ }
52
+ // 测试/重置用:清空声明与实例。
53
+ clear() {
54
+ this.declarations.clear();
55
+ this.registry = new registry_1.LocalRegistry();
56
+ }
57
+ }
58
+ exports.ServiceRegistrar = ServiceRegistrar;
59
+ // 进程内单例:@Service 装饰器与启动代码共享同一个注册协调器。
60
+ exports.serviceRegistrar = new ServiceRegistrar();
@@ -41,7 +41,7 @@ const program = new commander_1.Command();
41
41
  program
42
42
  .name("speed")
43
43
  .description("A microservices orchestration framework for TypeScript (the Spring Cloud equivalent, built on typespeed).")
44
- .version("1.3.1");
44
+ .version("1.3.2");
45
45
  program.command('new <projectName>')
46
46
  .description('Create a new speed project.')
47
47
  .action((projectName) => {
@@ -2,25 +2,33 @@
2
2
  // 标准装饰器的 context.metadata 依赖它,缺失会 TypeError,必须最先执行。
3
3
  (Symbol as { metadata?: symbol }).metadata ??= Symbol("Symbol.metadata");
4
4
 
5
- // 骨架阶段直接使用 typespeed 的 @app 引导(speed 的微服务装饰器 @Service 等自 1.3.2 起提供)。
5
+ // 骨架入口:直接使用 typespeed 的 @app 引导 + speed 的 @Service 声明服务。
6
6
  import { app, autoware, ServerFactory } from "typespeed";
7
+ import { Service, serviceRegistrar } from "speed";
7
8
 
8
9
  /**
9
10
  * speed 项目入口骨架(标准装饰器模式)。
10
11
  *
11
12
  * 目录约定:
12
13
  * - 服务类放 src/(本文件即入口);
13
- * - 配置文件 src/config.json(可选,typespeed 启动时自动读取,可用 config()/@value 访问);
14
+ * - 配置文件 src/config.json(可选,typespeed 启动时自动读取);
14
15
  * - tsconfig.json 已固定标准装饰器(experimentalDecorators:false + esnext.decorators lib)。
16
+ *
17
+ * @Service 声明本服务可被发现(服务名 = 应用名 ###appName###);
18
+ * 启动完成后在 main() 里用真实监听端口调用 serviceRegistrar.register() 提交注册。
15
19
  */
16
20
  @app
21
+ @Service("###appName###")
17
22
  class Main {
18
23
 
19
24
  @autoware(ServerFactory)
20
25
  public server!: ServerFactory;
21
26
 
22
27
  public main() {
23
- this.server.start(8080);
24
- console.log("speed service started at http://127.0.0.1:8080");
28
+ const port = 8080;
29
+ this.server.start(port);
30
+ // 声明/提交分离:@Service 只做声明,这里用真实 host:port 提交注册
31
+ serviceRegistrar.register("###appName###", { host: "127.0.0.1", port });
32
+ console.log(`speed service "###appName###" started at http://127.0.0.1:${port}`);
25
33
  }
26
34
  }
@@ -8,7 +8,7 @@
8
8
  "test": "ts-node --transpile-only src/main.ts"
9
9
  },
10
10
  "dependencies": {
11
- "speed": "^1.3.0",
11
+ "speed": "^1.3.2",
12
12
  "typespeed": "^2.6.7"
13
13
  },
14
14
  "devDependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speed",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "A microservices orchestration framework for TypeScript (the Spring Cloud equivalent, built on typespeed).",
5
5
  "author": "speedphp",
6
6
  "license": "MIT",
@@ -15,6 +15,10 @@
15
15
  "types": "./dist/registry/index.d.ts",
16
16
  "default": "./dist/registry/index.js"
17
17
  },
18
+ "./core": {
19
+ "types": "./dist/core/index.d.ts",
20
+ "default": "./dist/core/index.js"
21
+ },
18
22
  "./gateway": {
19
23
  "types": "./dist/gateway/index.d.ts",
20
24
  "default": "./dist/gateway/index.js"