@downcity/city 0.2.17

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.
Files changed (142) hide show
  1. package/README.md +182 -0
  2. package/bin/core/auth/authenticator.d.ts +79 -0
  3. package/bin/core/auth/authenticator.d.ts.map +1 -0
  4. package/bin/core/auth/authenticator.js +125 -0
  5. package/bin/core/auth/authenticator.js.map +1 -0
  6. package/bin/core/auth/token-signer.d.ts +27 -0
  7. package/bin/core/auth/token-signer.d.ts.map +1 -0
  8. package/bin/core/auth/token-signer.js +116 -0
  9. package/bin/core/auth/token-signer.js.map +1 -0
  10. package/bin/core/auth/types.d.ts +94 -0
  11. package/bin/core/auth/types.d.ts.map +1 -0
  12. package/bin/core/auth/types.js +7 -0
  13. package/bin/core/auth/types.js.map +1 -0
  14. package/bin/core/base/base-env-catalog.d.ts +13 -0
  15. package/bin/core/base/base-env-catalog.d.ts.map +1 -0
  16. package/bin/core/base/base-env-catalog.js +85 -0
  17. package/bin/core/base/base-env-catalog.js.map +1 -0
  18. package/bin/core/base/base-init.d.ts +48 -0
  19. package/bin/core/base/base-init.d.ts.map +1 -0
  20. package/bin/core/base/base-init.js +105 -0
  21. package/bin/core/base/base-init.js.map +1 -0
  22. package/bin/core/base/base-instruction.d.ts +11 -0
  23. package/bin/core/base/base-instruction.d.ts.map +1 -0
  24. package/bin/core/base/base-instruction.js +37 -0
  25. package/bin/core/base/base-instruction.js.map +1 -0
  26. package/bin/core/base/base-router.d.ts +41 -0
  27. package/bin/core/base/base-router.d.ts.map +1 -0
  28. package/bin/core/base/base-router.js +222 -0
  29. package/bin/core/base/base-router.js.map +1 -0
  30. package/bin/core/base/base-runtime.d.ts +13 -0
  31. package/bin/core/base/base-runtime.d.ts.map +1 -0
  32. package/bin/core/base/base-runtime.js +116 -0
  33. package/bin/core/base/base-runtime.js.map +1 -0
  34. package/bin/core/base/base.d.ts +82 -0
  35. package/bin/core/base/base.d.ts.map +1 -0
  36. package/bin/core/base/base.js +156 -0
  37. package/bin/core/base/base.js.map +1 -0
  38. package/bin/core/runtime.d.ts +120 -0
  39. package/bin/core/runtime.d.ts.map +1 -0
  40. package/bin/core/runtime.js +13 -0
  41. package/bin/core/runtime.js.map +1 -0
  42. package/bin/core/types.d.ts +60 -0
  43. package/bin/core/types.d.ts.map +1 -0
  44. package/bin/core/types.js +7 -0
  45. package/bin/core/types.js.map +1 -0
  46. package/bin/index.d.ts +33 -0
  47. package/bin/index.d.ts.map +1 -0
  48. package/bin/index.js +37 -0
  49. package/bin/index.js.map +1 -0
  50. package/bin/service/action.d.ts +44 -0
  51. package/bin/service/action.d.ts.map +1 -0
  52. package/bin/service/action.js +48 -0
  53. package/bin/service/action.js.map +1 -0
  54. package/bin/service/ai/ai-service.d.ts +56 -0
  55. package/bin/service/ai/ai-service.d.ts.map +1 -0
  56. package/bin/service/ai/ai-service.js +262 -0
  57. package/bin/service/ai/ai-service.js.map +1 -0
  58. package/bin/service/ai/openai-compatible-provider.d.ts +54 -0
  59. package/bin/service/ai/openai-compatible-provider.d.ts.map +1 -0
  60. package/bin/service/ai/openai-compatible-provider.js +175 -0
  61. package/bin/service/ai/openai-compatible-provider.js.map +1 -0
  62. package/bin/service/ai/provider.d.ts +43 -0
  63. package/bin/service/ai/provider.d.ts.map +1 -0
  64. package/bin/service/ai/provider.js +50 -0
  65. package/bin/service/ai/provider.js.map +1 -0
  66. package/bin/service/ai/types.d.ts +94 -0
  67. package/bin/service/ai/types.d.ts.map +1 -0
  68. package/bin/service/ai/types.js +7 -0
  69. package/bin/service/ai/types.js.map +1 -0
  70. package/bin/service/env/env-service.d.ts +12 -0
  71. package/bin/service/env/env-service.d.ts.map +1 -0
  72. package/bin/service/env/env-service.js +39 -0
  73. package/bin/service/env/env-service.js.map +1 -0
  74. package/bin/service/env/env-store.d.ts +15 -0
  75. package/bin/service/env/env-store.d.ts.map +1 -0
  76. package/bin/service/env/env-store.js +35 -0
  77. package/bin/service/env/env-store.js.map +1 -0
  78. package/bin/service/env/schema.d.ts +171 -0
  79. package/bin/service/env/schema.d.ts.map +1 -0
  80. package/bin/service/env/schema.js +52 -0
  81. package/bin/service/env/schema.js.map +1 -0
  82. package/bin/service/env/types.d.ts +79 -0
  83. package/bin/service/env/types.d.ts.map +1 -0
  84. package/bin/service/env/types.js +8 -0
  85. package/bin/service/env/types.js.map +1 -0
  86. package/bin/service/hook.d.ts +23 -0
  87. package/bin/service/hook.d.ts.map +1 -0
  88. package/bin/service/hook.js +49 -0
  89. package/bin/service/hook.js.map +1 -0
  90. package/bin/service/installable-service.d.ts +75 -0
  91. package/bin/service/installable-service.d.ts.map +1 -0
  92. package/bin/service/installable-service.js +89 -0
  93. package/bin/service/installable-service.js.map +1 -0
  94. package/bin/service/instruction.d.ts +106 -0
  95. package/bin/service/instruction.d.ts.map +1 -0
  96. package/bin/service/instruction.js +70 -0
  97. package/bin/service/instruction.js.map +1 -0
  98. package/bin/service/service.d.ts +140 -0
  99. package/bin/service/service.d.ts.map +1 -0
  100. package/bin/service/service.js +94 -0
  101. package/bin/service/service.js.map +1 -0
  102. package/bin/service/studios/schema.d.ts +207 -0
  103. package/bin/service/studios/schema.d.ts.map +1 -0
  104. package/bin/service/studios/schema.js +60 -0
  105. package/bin/service/studios/schema.js.map +1 -0
  106. package/bin/service/studios/studio-store.d.ts +17 -0
  107. package/bin/service/studios/studio-store.d.ts.map +1 -0
  108. package/bin/service/studios/studio-store.js +49 -0
  109. package/bin/service/studios/studio-store.js.map +1 -0
  110. package/bin/service/studios/studios-service.d.ts +15 -0
  111. package/bin/service/studios/studios-service.d.ts.map +1 -0
  112. package/bin/service/studios/studios-service.js +51 -0
  113. package/bin/service/studios/studios-service.js.map +1 -0
  114. package/bin/service/studios/types.d.ts +51 -0
  115. package/bin/service/studios/types.d.ts.map +1 -0
  116. package/bin/service/studios/types.js +7 -0
  117. package/bin/service/studios/types.js.map +1 -0
  118. package/bin/service/types.d.ts +11 -0
  119. package/bin/service/types.d.ts.map +1 -0
  120. package/bin/service/types.js +5 -0
  121. package/bin/service/types.js.map +1 -0
  122. package/bin/store/db.d.ts +61 -0
  123. package/bin/store/db.d.ts.map +1 -0
  124. package/bin/store/db.js +29 -0
  125. package/bin/store/db.js.map +1 -0
  126. package/bin/store/table-api.d.ts +42 -0
  127. package/bin/store/table-api.d.ts.map +1 -0
  128. package/bin/store/table-api.js +100 -0
  129. package/bin/store/table-api.js.map +1 -0
  130. package/bin/store/types.d.ts +14 -0
  131. package/bin/store/types.d.ts.map +1 -0
  132. package/bin/store/types.js +7 -0
  133. package/bin/store/types.js.map +1 -0
  134. package/bin/utils/helpers.d.ts +101 -0
  135. package/bin/utils/helpers.d.ts.map +1 -0
  136. package/bin/utils/helpers.js +192 -0
  137. package/bin/utils/helpers.js.map +1 -0
  138. package/bin/utils/validation.d.ts +18 -0
  139. package/bin/utils/validation.d.ts.map +1 -0
  140. package/bin/utils/validation.js +28 -0
  141. package/bin/utils/validation.js.map +1 -0
  142. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # @downcity/city
2
+
3
+ `@downcity/city` 是 Downcity 的服务端运行时内核。
4
+
5
+ 它负责这些共用能力:
6
+
7
+ - 挂载 `Service` / `AIService`
8
+ - 初始化内置 `studios` / `env` 表
9
+ - 校验 `user_token` 和 `admin_secret_key`
10
+ - 暴露统一的 `/v1/*` HTTP 路由
11
+ - 提供 env、数据库、hook 和鉴权上下文
12
+
13
+ ## 安装
14
+
15
+ ```bash
16
+ pnpm add @downcity/city
17
+ ```
18
+
19
+ ## 最小示例
20
+
21
+ ```ts
22
+ import { City, AIService } from "@downcity/city";
23
+ import Database from "better-sqlite3";
24
+ import { drizzle } from "drizzle-orm/better-sqlite3";
25
+
26
+ const sqlite = new Database("./data.sqlite");
27
+ sqlite.pragma("journal_mode = WAL");
28
+
29
+ const db = Object.assign(drizzle(sqlite), {
30
+ $client: { exec: (sql: string) => sqlite.exec(sql) },
31
+ });
32
+
33
+ const city = new City({ db, dialect: "sqlite", raw: sqlite });
34
+
35
+ const ai = new AIService();
36
+ ai.use({
37
+ id: "local-echo",
38
+ name: "Local Echo",
39
+ default: ["text"],
40
+ actions: {
41
+ text: async (ctx) => ({
42
+ id: crypto.randomUUID(),
43
+ role: "assistant",
44
+ parts: [
45
+ {
46
+ type: "text",
47
+ text: String(ctx.input.prompt ?? ""),
48
+ state: "done",
49
+ },
50
+ ],
51
+ }),
52
+ },
53
+ });
54
+
55
+ city.use(ai);
56
+ ```
57
+
58
+ 启动 HTTP 服务:
59
+
60
+ ```ts
61
+ import { serve } from "@hono/node-server";
62
+
63
+ await city.health();
64
+ serve({ fetch: city.router().fetch, port: 43127, hostname: "127.0.0.1" });
65
+ ```
66
+
67
+ ## City 说明文档
68
+
69
+ `city.instruction()` 会返回当前 City 实例的聚合说明文档字符串,内容包含:
70
+
71
+ - City 的基本使用方式
72
+ - 当前已挂载的 service
73
+ - 每个模块需要的 env 配置
74
+ - 每个模块补充的使用说明
75
+
76
+ ```ts
77
+ const text = await city.instruction();
78
+ console.log(text);
79
+ ```
80
+
81
+ 如果你需要从远程管理端读取同一份说明,可以请求:
82
+
83
+ ```txt
84
+ GET /v1/city/instruction
85
+ ```
86
+
87
+ 这个接口只允许 `admin_secret_key` 访问,返回 `text/plain`。
88
+
89
+ ## Service
90
+
91
+ `Service` 是一组 `Action` 的容器:
92
+
93
+ ```ts
94
+ import { Service } from "@downcity/city";
95
+
96
+ const notes = new Service({ id: "notes", name: "Notes" });
97
+
98
+ notes.action("create", async (ctx) => {
99
+ return {
100
+ ok: true,
101
+ title: String(ctx.input.title ?? ""),
102
+ };
103
+ });
104
+
105
+ notes.action("list", async () => {
106
+ return { items: [] };
107
+ }, { method: "GET", auth: ["admin"] });
108
+
109
+ city.use(notes);
110
+ ```
111
+
112
+ City 会自动映射为:
113
+
114
+ - `POST /v1/notes/create`
115
+ - `GET /v1/notes/list`
116
+
117
+ ## AIService
118
+
119
+ `AIService` 负责模型目录和模态路由:
120
+
121
+ ```ts
122
+ import { AIService, Provider } from "@downcity/city";
123
+
124
+ const deepseek = new Provider("deepseek", {
125
+ baseURL: "https://api.deepseek.com/v1",
126
+ envKey: "DEEPSEEK_API_KEY",
127
+ text: myTextAction,
128
+ stream: myStreamAction,
129
+ });
130
+
131
+ const ai = new AIService();
132
+ ai.use(
133
+ deepseek.model({
134
+ id: "deepseek-v4-flash",
135
+ name: "DeepSeek V4 Flash",
136
+ default: ["text", "stream"],
137
+ }),
138
+ );
139
+
140
+ city.use(ai);
141
+ ```
142
+
143
+ ## 官方服务
144
+
145
+ 官方服务用于封装多 studio 复用能力:
146
+
147
+ ```ts
148
+ import { accountsService } from "@downcity/services";
149
+ import { usageService } from "@downcity/services";
150
+
151
+ city.use(accountsService());
152
+ city.use(usageService());
153
+ ```
154
+
155
+ 服务路由当前只支持:
156
+
157
+ - `GET`
158
+ - `POST`
159
+
160
+ ## 鉴权语义
161
+
162
+ - 默认 action 需要 `user_token`
163
+ - `auth: ["admin"]` 只允许 `admin_secret_key`
164
+ - `auth: []` 表示免登录
165
+
166
+ 对于用户侧请求,`user_token` 绑定 studio 身份。如果请求体或 query 中传了 `studio_id`,它必须与 token 里的 studio 一致。
167
+
168
+ ## 主要导出
169
+
170
+ - `City`
171
+ - `Service`
172
+ - `ServiceDefinition`
173
+ - `AIService`
174
+ - `Provider`
175
+ - `TokenSigner`
176
+ - `EnvService`
177
+ - `StudiosService`
178
+
179
+ ## 文档
180
+
181
+ - 仓库首页:[downcity](https://github.com/wangenius/downcity)
182
+ - 文档目录:[homepage/content/docs](https://github.com/wangenius/downcity/tree/main/homepage/content/docs)
@@ -0,0 +1,79 @@
1
+ /**
2
+ * 统一鉴权模块。
3
+ *
4
+ * Authenticator 统一处理 admin(secret key)和 user(JWT token)两种鉴权方式。
5
+ * 所有鉴权失败统一抛出 httpError(ErrorWithStatus)。
6
+ *
7
+ * TokenSigner 实例通过 getSigner() 缓存,避免重复 new TokenSigner(key)。
8
+ */
9
+ import { type RouteAuth, type RouteIdentity } from "../../service/service.js";
10
+ import type { EnvProvider } from "../runtime.js";
11
+ import type { CreateUserTokenInput, UserTokenPayload, UserTokenIssueResult, RuntimeUser } from "./types.js";
12
+ /** 鉴权级别 */
13
+ /** 鉴权结果 */
14
+ export interface AuthResult {
15
+ /** 鉴权后的实际级别 */
16
+ level: RouteIdentity;
17
+ /** 解析出的用户信息(user 级别时可用) */
18
+ user?: RuntimeUser;
19
+ /** 解析出的 Studio 信息(user 级别时可用) */
20
+ studio?: {
21
+ studio_id: string;
22
+ status: string;
23
+ };
24
+ }
25
+ /** 统一鉴权器 */
26
+ export declare class Authenticator {
27
+ private env;
28
+ private store;
29
+ /** 缓存的 TokenSigner 实例 */
30
+ private tokenSigner?;
31
+ constructor(env: EnvProvider, store: () => Promise<{
32
+ studio: {
33
+ get(id: string): Promise<{
34
+ studio_id: string;
35
+ status: string;
36
+ } | undefined>;
37
+ };
38
+ }>);
39
+ /**
40
+ * 获取(或创建)TokenSigner 单例。
41
+ *
42
+ * 首次调用时从 env 读取 DOWNCITY_CITY_TOKEN_SIGNING_KEY 创建实例并缓存。
43
+ */
44
+ private getSigner;
45
+ /**
46
+ * 解析请求身份。
47
+ *
48
+ * @param request - 原始 HTTP Request
49
+ * @returns 当前请求身份;无 token 或 token 无效时返回 guest
50
+ */
51
+ resolve(request: Request): Promise<AuthResult>;
52
+ /**
53
+ * 根据 action 的 auth 配置判断当前身份是否允许继续。
54
+ *
55
+ * @param result - 当前已解析身份
56
+ * @param required - action 声明的允许身份集合
57
+ * @returns 通过授权后的身份结果
58
+ */
59
+ authorize(result: AuthResult, required?: RouteAuth): AuthResult;
60
+ /**
61
+ * 对请求执行鉴权并强制满足 action 的 auth 配置。
62
+ */
63
+ authenticate(request: Request, required?: RouteAuth): Promise<AuthResult>;
64
+ /**
65
+ * 签发 user_token(验证 studio 状态后签发)。
66
+ *
67
+ * @param input - token 创建参数
68
+ * @returns 签发结果(含 token 字符串)
69
+ */
70
+ createToken(input: CreateUserTokenInput): Promise<UserTokenIssueResult>;
71
+ /**
72
+ * 校验 user_token 并返回载荷。
73
+ *
74
+ * @param token - user_token 字符串
75
+ * @returns 解析出的 token 载荷
76
+ */
77
+ verifyToken(token: string): Promise<UserTokenPayload>;
78
+ }
79
+ //# sourceMappingURL=authenticator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authenticator.d.ts","sourceRoot":"","sources":["../../../src/core/auth/authenticator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAsB,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE5G,WAAW;AACX,WAAW;AACX,MAAM,WAAW,UAAU;IACzB,eAAe;IACf,KAAK,EAAE,aAAa,CAAC;IACrB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,iCAAiC;IACjC,MAAM,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAChD;AAED,YAAY;AACZ,qBAAa,aAAa;IAKtB,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,KAAK;IALf,yBAAyB;IACzB,OAAO,CAAC,WAAW,CAAC,CAAc;gBAGxB,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,MAAM,EAAE;YAAE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;gBAAE,SAAS,EAAE,MAAM,CAAC;gBAAC,MAAM,EAAE,MAAM,CAAA;aAAE,GAAG,SAAS,CAAC,CAAA;SAAE,CAAA;KAAE,CAAC;IAG3H;;;;OAIG;IACH,OAAO,CAAC,SAAS;IASjB;;;;;OAKG;IACG,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IA0BpD;;;;;;OAMG;IACH,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,UAAU;IAY/D;;OAEG;IACG,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC;IAI/E;;;;;OAKG;IACG,WAAW,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAiB7E;;;;;OAKG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAG5D"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * 统一鉴权模块。
3
+ *
4
+ * Authenticator 统一处理 admin(secret key)和 user(JWT token)两种鉴权方式。
5
+ * 所有鉴权失败统一抛出 httpError(ErrorWithStatus)。
6
+ *
7
+ * TokenSigner 实例通过 getSigner() 缓存,避免重复 new TokenSigner(key)。
8
+ */
9
+ import { bearerToken, httpError } from "../../utils/helpers.js";
10
+ import { TokenSigner } from "./token-signer.js";
11
+ import { normalizeRouteAuth } from "../../service/service.js";
12
+ /** 统一鉴权器 */
13
+ export class Authenticator {
14
+ env;
15
+ store;
16
+ /** 缓存的 TokenSigner 实例 */
17
+ tokenSigner;
18
+ constructor(env, store) {
19
+ this.env = env;
20
+ this.store = store;
21
+ }
22
+ /**
23
+ * 获取(或创建)TokenSigner 单例。
24
+ *
25
+ * 首次调用时从 env 读取 DOWNCITY_CITY_TOKEN_SIGNING_KEY 创建实例并缓存。
26
+ */
27
+ getSigner() {
28
+ if (!this.tokenSigner) {
29
+ const signingKey = this.env.get("DOWNCITY_CITY_TOKEN_SIGNING_KEY");
30
+ if (!signingKey)
31
+ throw new Error("DOWNCITY_CITY_TOKEN_SIGNING_KEY is required");
32
+ this.tokenSigner = new TokenSigner(signingKey);
33
+ }
34
+ return this.tokenSigner;
35
+ }
36
+ /**
37
+ * 解析请求身份。
38
+ *
39
+ * @param request - 原始 HTTP Request
40
+ * @returns 当前请求身份;无 token 或 token 无效时返回 guest
41
+ */
42
+ async resolve(request) {
43
+ const token = bearerToken(request);
44
+ if (!token)
45
+ return { level: "guest" };
46
+ const adminKey = this.env.get("DOWNCITY_CITY_ADMIN_SECRET_KEY");
47
+ if (adminKey && token === adminKey) {
48
+ return { level: "admin" };
49
+ }
50
+ try {
51
+ const payload = await this.getSigner().verify(token);
52
+ const store = await this.store();
53
+ const studio = await store.studio.get(payload.studio_id);
54
+ if (!studio)
55
+ return { level: "guest" };
56
+ if (studio.status !== "active")
57
+ return { level: "guest" };
58
+ return {
59
+ level: "user",
60
+ user: { user_id: payload.user_id, metadata: payload.metadata ?? {} },
61
+ studio,
62
+ };
63
+ }
64
+ catch {
65
+ return { level: "guest" };
66
+ }
67
+ }
68
+ /**
69
+ * 根据 action 的 auth 配置判断当前身份是否允许继续。
70
+ *
71
+ * @param result - 当前已解析身份
72
+ * @param required - action 声明的允许身份集合
73
+ * @returns 通过授权后的身份结果
74
+ */
75
+ authorize(result, required) {
76
+ const allowed = normalizeRouteAuth(required);
77
+ if (allowed.length === 0)
78
+ return result;
79
+ if (result.level !== "guest" && allowed.includes(result.level))
80
+ return result;
81
+ if (result.level === "guest") {
82
+ throw httpError(401, "Authentication required");
83
+ }
84
+ throw httpError(403, `Forbidden for identity: ${result.level}`);
85
+ }
86
+ /**
87
+ * 对请求执行鉴权并强制满足 action 的 auth 配置。
88
+ */
89
+ async authenticate(request, required) {
90
+ return this.authorize(await this.resolve(request), required);
91
+ }
92
+ /**
93
+ * 签发 user_token(验证 studio 状态后签发)。
94
+ *
95
+ * @param input - token 创建参数
96
+ * @returns 签发结果(含 token 字符串)
97
+ */
98
+ async createToken(input) {
99
+ const store = await this.store();
100
+ const studio = await store.studio.get(input.studio_id);
101
+ if (!studio)
102
+ throw httpError(404, `Unknown studio: ${input.studio_id}`);
103
+ if (studio.status !== "active")
104
+ throw httpError(403, `Studio is not active: ${input.studio_id}`);
105
+ const user_token = await this.getSigner().sign(input);
106
+ return {
107
+ user_token,
108
+ studio_id: input.studio_id,
109
+ user_id: input.user_id,
110
+ ...(input.ttl
111
+ ? { expires_at: new Date(Date.now() + TokenSigner.parseTTL(input.ttl) * 1000).toISOString() }
112
+ : {}),
113
+ };
114
+ }
115
+ /**
116
+ * 校验 user_token 并返回载荷。
117
+ *
118
+ * @param token - user_token 字符串
119
+ * @returns 解析出的 token 载荷
120
+ */
121
+ async verifyToken(token) {
122
+ return this.getSigner().verify(token);
123
+ }
124
+ }
125
+ //# sourceMappingURL=authenticator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authenticator.js","sourceRoot":"","sources":["../../../src/core/auth/authenticator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * user_token 签发与校验模块。
3
+ *
4
+ * Downcity 使用 Web Crypto API 的 HMAC-SHA256 生成最小可用的 user_token。
5
+ * 零 Node.js 依赖,兼容所有现代运行时(Node、Workers、Deno、Bun、浏览器)。
6
+ */
7
+ import type { CreateUserTokenInput, UserTokenPayload } from "./types.js";
8
+ export declare class TokenSigner {
9
+ readonly signingKey: string;
10
+ /** 缓存的 CryptoKey,避免每次签名都重新 import */
11
+ private cryptoKey?;
12
+ constructor(signingKey: string);
13
+ /**
14
+ * 获取(或缓存)HMAC CryptoKey。
15
+ *
16
+ * 使用 Web Crypto API 的 crypto.subtle.importKey,
17
+ * 首次调用时创建并缓存 Promise,后续复用。
18
+ */
19
+ private getCryptoKey;
20
+ /** 签发 user_token */
21
+ sign(input: CreateUserTokenInput): Promise<string>;
22
+ /** 校验并解析 user_token,失败抛 httpError(401) */
23
+ verify(token: string): Promise<UserTokenPayload>;
24
+ /** 把 ttl 解析成秒数 */
25
+ static parseTTL(ttl: string | number): number;
26
+ }
27
+ //# sourceMappingURL=token-signer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-signer.d.ts","sourceRoot":"","sources":["../../../src/core/auth/token-signer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEzE,qBAAa,WAAW;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B,qCAAqC;IACrC,OAAO,CAAC,SAAS,CAAC,CAAqB;gBAE3B,UAAU,EAAE,MAAM;IAO9B;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAcpB,oBAAoB;IACd,IAAI,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC;IA4BxD,0CAA0C;IACpC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA6BtD,kBAAkB;IAClB,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM;CAyB9C"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * user_token 签发与校验模块。
3
+ *
4
+ * Downcity 使用 Web Crypto API 的 HMAC-SHA256 生成最小可用的 user_token。
5
+ * 零 Node.js 依赖,兼容所有现代运行时(Node、Workers、Deno、Bun、浏览器)。
6
+ */
7
+ import { httpError, base64UrlEncode, base64UrlDecode, base64UrlEncodeBytes, base64UrlDecodeBytes, timingSafeEqualBytes } from "../../utils/helpers.js";
8
+ export class TokenSigner {
9
+ signingKey;
10
+ /** 缓存的 CryptoKey,避免每次签名都重新 import */
11
+ cryptoKey;
12
+ constructor(signingKey) {
13
+ if (!signingKey) {
14
+ throw new Error("TokenSigner requires a signing key");
15
+ }
16
+ this.signingKey = signingKey;
17
+ }
18
+ /**
19
+ * 获取(或缓存)HMAC CryptoKey。
20
+ *
21
+ * 使用 Web Crypto API 的 crypto.subtle.importKey,
22
+ * 首次调用时创建并缓存 Promise,后续复用。
23
+ */
24
+ getCryptoKey() {
25
+ if (!this.cryptoKey) {
26
+ const encoder = new TextEncoder();
27
+ this.cryptoKey = crypto.subtle.importKey("raw", encoder.encode(this.signingKey), { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"]);
28
+ }
29
+ return this.cryptoKey;
30
+ }
31
+ /** 签发 user_token */
32
+ async sign(input) {
33
+ if (!input || typeof input.studio_id !== "string" || input.studio_id.length === 0) {
34
+ throw new TypeError("studio_id is required");
35
+ }
36
+ if (typeof input.user_id !== "string" || input.user_id.length === 0) {
37
+ throw new TypeError("user_id is required");
38
+ }
39
+ const ttl = input.ttl;
40
+ const now = Math.floor(Date.now() / 1000);
41
+ const payload = {
42
+ aud: "downcity:user",
43
+ studio_id: input.studio_id,
44
+ user_id: input.user_id,
45
+ metadata: input.metadata ?? {},
46
+ iat: now,
47
+ };
48
+ if (ttl) {
49
+ payload.exp = now + TokenSigner.parseTTL(ttl);
50
+ }
51
+ const encodedPayload = base64UrlEncode(JSON.stringify(payload));
52
+ const signature = await signPayload(await this.getCryptoKey(), encodedPayload);
53
+ return `ub_${encodedPayload}.${signature}`;
54
+ }
55
+ /** 校验并解析 user_token,失败抛 httpError(401) */
56
+ async verify(token) {
57
+ const rawToken = token.startsWith("ub_") ? token.slice(3) : token;
58
+ const [encodedPayload, signature] = rawToken.split(".");
59
+ if (!encodedPayload || !signature) {
60
+ throw httpError(401, "Invalid user token");
61
+ }
62
+ if (!await verifyPayload(await this.getCryptoKey(), encodedPayload, signature)) {
63
+ throw httpError(401, "Invalid user token signature");
64
+ }
65
+ const payload = JSON.parse(base64UrlDecode(encodedPayload));
66
+ const now = Math.floor(Date.now() / 1000);
67
+ if (payload.exp && payload.exp <= now) {
68
+ throw httpError(401, "User token expired");
69
+ }
70
+ if (payload.aud !== "downcity:user") {
71
+ throw httpError(401, "Invalid user token audience");
72
+ }
73
+ if (!payload.studio_id || !payload.user_id) {
74
+ throw httpError(401, "Invalid user token payload");
75
+ }
76
+ return payload;
77
+ }
78
+ /** 把 ttl 解析成秒数 */
79
+ static parseTTL(ttl) {
80
+ if (typeof ttl === "number" && Number.isFinite(ttl) && ttl > 0) {
81
+ return ttl;
82
+ }
83
+ if (typeof ttl !== "string") {
84
+ throw new TypeError("ttl must be a positive number of seconds or a string like 1h");
85
+ }
86
+ const match = ttl.match(/^(\d+)(s|m|h|d)$/);
87
+ if (!match) {
88
+ throw new Error(`Invalid ttl: ${ttl}`);
89
+ }
90
+ const value = Number(match[1]);
91
+ const unit = match[2];
92
+ const multipliers = {
93
+ s: 1,
94
+ m: 60,
95
+ h: 60 * 60,
96
+ d: 24 * 60 * 60,
97
+ };
98
+ return value * multipliers[unit];
99
+ }
100
+ }
101
+ /**
102
+ * 使用 HMAC-SHA256 对 payload 签名,返回 base64url 编码的签名。
103
+ */
104
+ async function signPayload(cryptoKey, encodedPayload) {
105
+ const signature = await crypto.subtle.sign("HMAC", cryptoKey, new TextEncoder().encode(encodedPayload));
106
+ return base64UrlEncodeBytes(new Uint8Array(signature));
107
+ }
108
+ /**
109
+ * 验证 HMAC-SHA256 签名是否匹配。
110
+ */
111
+ async function verifyPayload(cryptoKey, encodedPayload, signature) {
112
+ const expected = base64UrlDecodeBytes(signature);
113
+ const actual = await crypto.subtle.sign("HMAC", cryptoKey, new TextEncoder().encode(encodedPayload));
114
+ return timingSafeEqualBytes(new Uint8Array(actual), expected);
115
+ }
116
+ //# sourceMappingURL=token-signer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-signer.js","sourceRoot":"","sources":["../../../src/core/auth/token-signer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Auth 域公共类型。
3
+ *
4
+ * 包含 token 相关的用户、载荷和签发结果类型。
5
+ */
6
+ /**
7
+ * Runtime 中的终端用户信息。
8
+ */
9
+ export interface RuntimeUser {
10
+ /**
11
+ * 开发者系统中的用户主键。
12
+ */
13
+ user_id: string;
14
+ /**
15
+ * 附带在 token 中的业务元数据。
16
+ */
17
+ metadata?: Record<string, unknown>;
18
+ }
19
+ /**
20
+ * 签发 user_token 的输入。
21
+ */
22
+ export interface CreateUserTokenInput {
23
+ /**
24
+ * token 所属的 Studio ID。
25
+ */
26
+ studio_id: string;
27
+ /**
28
+ * token 所属的终端用户 ID。
29
+ */
30
+ user_id: string;
31
+ /**
32
+ * 附带进 token 的业务元数据。
33
+ */
34
+ metadata?: Record<string, unknown>;
35
+ /**
36
+ * token 有效期。
37
+ *
38
+ * 支持 `30m`、`1h`、`7d` 或秒数。
39
+ */
40
+ ttl?: string | number;
41
+ }
42
+ /**
43
+ * user_token 的标准载荷。
44
+ */
45
+ export interface UserTokenPayload {
46
+ /**
47
+ * token 受众。
48
+ */
49
+ aud: "downcity:user";
50
+ /**
51
+ * token 所属 Studio ID。
52
+ */
53
+ studio_id: string;
54
+ /**
55
+ * token 所属用户 ID。
56
+ */
57
+ user_id: string;
58
+ /**
59
+ * 业务元数据。
60
+ */
61
+ metadata?: Record<string, unknown>;
62
+ /**
63
+ * 签发时间。
64
+ */
65
+ iat: number;
66
+ /**
67
+ * 过期时间。
68
+ */
69
+ exp?: number;
70
+ }
71
+ /**
72
+ * City 返回给管理端的发 token 结果。
73
+ */
74
+ export interface UserTokenIssueResult {
75
+ /**
76
+ * 可交给 UserClient 使用的 token。
77
+ */
78
+ user_token: string;
79
+ /**
80
+ * token 所属 Studio ID。
81
+ */
82
+ studio_id: string;
83
+ /**
84
+ * token 所属用户 ID。
85
+ */
86
+ user_id: string;
87
+ /**
88
+ * token 过期时间。
89
+ *
90
+ * 未设置 ttl 时省略。
91
+ */
92
+ expires_at?: string;
93
+ }
94
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/auth/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnC;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,GAAG,EAAE,eAAe,CAAC;IAErB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnC;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Auth 域公共类型。
3
+ *
4
+ * 包含 token 相关的用户、载荷和签发结果类型。
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/auth/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * City env requirement 聚合模块。
3
+ *
4
+ * 负责把 service.env 与 AI 模型 env requirement 汇总成统一目录。
5
+ */
6
+ import type { Service } from "../../service/service.js";
7
+ import type { EnvCatalogScope } from "../../service/env/types.js";
8
+ import type { EnvProvider } from "../runtime.js";
9
+ /**
10
+ * 聚合当前 City 暴露的 env requirement。
11
+ */
12
+ export declare function collect_city_env_catalog(services: Service[], env_provider: EnvProvider): EnvCatalogScope[];
13
+ //# sourceMappingURL=base-env-catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-env-catalog.d.ts","sourceRoot":"","sources":["../../../src/core/base/base-env-catalog.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAwB,MAAM,4BAA4B,CAAC;AACxF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,OAAO,EAAE,EACnB,YAAY,EAAE,WAAW,GACxB,eAAe,EAAE,CAsBnB"}