@planet-matrix/mobius-model 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +123 -36
  3. package/dist/index.js +45 -4
  4. package/dist/index.js.map +183 -11
  5. package/oxlint.config.ts +6 -0
  6. package/package.json +16 -10
  7. package/src/abort/README.md +92 -0
  8. package/src/abort/abort-manager.ts +278 -0
  9. package/src/abort/abort-signal-listener-manager.ts +81 -0
  10. package/src/abort/index.ts +2 -0
  11. package/src/basic/README.md +69 -118
  12. package/src/basic/function.ts +81 -62
  13. package/src/basic/is.ts +152 -71
  14. package/src/basic/promise.ts +29 -8
  15. package/src/basic/string.ts +2 -33
  16. package/src/color/README.md +105 -0
  17. package/src/color/index.ts +3 -0
  18. package/src/color/internal.ts +42 -0
  19. package/src/color/rgb/analyze.ts +236 -0
  20. package/src/color/rgb/construct.ts +130 -0
  21. package/src/color/rgb/convert.ts +227 -0
  22. package/src/color/rgb/derive.ts +303 -0
  23. package/src/color/rgb/index.ts +6 -0
  24. package/src/color/rgb/internal.ts +208 -0
  25. package/src/color/rgb/parse.ts +302 -0
  26. package/src/color/rgb/serialize.ts +144 -0
  27. package/src/color/types.ts +57 -0
  28. package/src/color/xyz/analyze.ts +80 -0
  29. package/src/color/xyz/construct.ts +19 -0
  30. package/src/color/xyz/convert.ts +71 -0
  31. package/src/color/xyz/index.ts +3 -0
  32. package/src/color/xyz/internal.ts +23 -0
  33. package/src/css/README.md +93 -0
  34. package/src/css/class.ts +559 -0
  35. package/src/css/index.ts +1 -0
  36. package/src/encoding/README.md +66 -79
  37. package/src/encoding/base64.ts +13 -4
  38. package/src/environment/README.md +97 -0
  39. package/src/environment/basic.ts +26 -0
  40. package/src/environment/device.ts +311 -0
  41. package/src/environment/feature.ts +285 -0
  42. package/src/environment/geo.ts +337 -0
  43. package/src/environment/index.ts +7 -0
  44. package/src/environment/runtime.ts +400 -0
  45. package/src/environment/snapshot.ts +60 -0
  46. package/src/environment/variable.ts +239 -0
  47. package/src/event/README.md +90 -0
  48. package/src/event/class-event-proxy.ts +228 -0
  49. package/src/event/common.ts +19 -0
  50. package/src/event/event-manager.ts +203 -0
  51. package/src/event/index.ts +4 -0
  52. package/src/event/instance-event-proxy.ts +186 -0
  53. package/src/event/internal.ts +24 -0
  54. package/src/exception/README.md +96 -0
  55. package/src/exception/browser.ts +219 -0
  56. package/src/exception/index.ts +4 -0
  57. package/src/exception/nodejs.ts +169 -0
  58. package/src/exception/normalize.ts +106 -0
  59. package/src/exception/types.ts +99 -0
  60. package/src/identifier/README.md +92 -0
  61. package/src/identifier/id.ts +119 -0
  62. package/src/identifier/index.ts +2 -0
  63. package/src/identifier/uuid.ts +187 -0
  64. package/src/index.ts +16 -1
  65. package/src/log/README.md +79 -0
  66. package/src/log/index.ts +5 -0
  67. package/src/log/log-emitter.ts +72 -0
  68. package/src/log/log-record.ts +10 -0
  69. package/src/log/log-scheduler.ts +74 -0
  70. package/src/log/log-type.ts +8 -0
  71. package/src/log/logger.ts +543 -0
  72. package/src/orchestration/README.md +89 -0
  73. package/src/orchestration/coordination/barrier.ts +214 -0
  74. package/src/orchestration/coordination/count-down-latch.ts +215 -0
  75. package/src/orchestration/coordination/errors.ts +98 -0
  76. package/src/orchestration/coordination/index.ts +16 -0
  77. package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
  78. package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
  79. package/src/orchestration/coordination/keyed-lock.ts +168 -0
  80. package/src/orchestration/coordination/mutex.ts +257 -0
  81. package/src/orchestration/coordination/permit.ts +127 -0
  82. package/src/orchestration/coordination/read-write-lock.ts +444 -0
  83. package/src/orchestration/coordination/semaphore.ts +280 -0
  84. package/src/orchestration/index.ts +1 -0
  85. package/src/random/README.md +55 -86
  86. package/src/random/index.ts +1 -1
  87. package/src/random/string.ts +35 -0
  88. package/src/reactor/README.md +4 -0
  89. package/src/reactor/reactor-core/primitive.ts +9 -9
  90. package/src/reactor/reactor-core/reactive-system.ts +5 -5
  91. package/src/singleton/README.md +79 -0
  92. package/src/singleton/factory.ts +55 -0
  93. package/src/singleton/index.ts +2 -0
  94. package/src/singleton/manager.ts +204 -0
  95. package/src/storage/README.md +107 -0
  96. package/src/storage/index.ts +1 -0
  97. package/src/storage/table.ts +449 -0
  98. package/src/timer/README.md +86 -0
  99. package/src/timer/expiration/expiration-manager.ts +594 -0
  100. package/src/timer/expiration/index.ts +3 -0
  101. package/src/timer/expiration/min-heap.ts +208 -0
  102. package/src/timer/expiration/remaining-manager.ts +241 -0
  103. package/src/timer/index.ts +1 -0
  104. package/src/type/README.md +54 -307
  105. package/src/type/class.ts +2 -2
  106. package/src/type/index.ts +14 -14
  107. package/src/type/is.ts +265 -2
  108. package/src/type/object.ts +37 -0
  109. package/src/type/string.ts +7 -2
  110. package/src/type/tuple.ts +6 -6
  111. package/src/type/union.ts +16 -0
  112. package/src/web/README.md +77 -0
  113. package/src/web/capture.ts +35 -0
  114. package/src/web/clipboard.ts +97 -0
  115. package/src/web/dom.ts +117 -0
  116. package/src/web/download.ts +16 -0
  117. package/src/web/event.ts +46 -0
  118. package/src/web/index.ts +10 -0
  119. package/src/web/local-storage.ts +113 -0
  120. package/src/web/location.ts +28 -0
  121. package/src/web/permission.ts +172 -0
  122. package/src/web/script-loader.ts +432 -0
  123. package/tests/unit/abort/abort-manager.spec.ts +225 -0
  124. package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
  125. package/tests/unit/basic/array.spec.ts +1 -1
  126. package/tests/unit/basic/stream.spec.ts +1 -1
  127. package/tests/unit/basic/string.spec.ts +0 -9
  128. package/tests/unit/color/rgb/analyze.spec.ts +110 -0
  129. package/tests/unit/color/rgb/construct.spec.ts +56 -0
  130. package/tests/unit/color/rgb/convert.spec.ts +60 -0
  131. package/tests/unit/color/rgb/derive.spec.ts +103 -0
  132. package/tests/unit/color/rgb/parse.spec.ts +66 -0
  133. package/tests/unit/color/rgb/serialize.spec.ts +46 -0
  134. package/tests/unit/color/xyz/analyze.spec.ts +33 -0
  135. package/tests/unit/color/xyz/construct.spec.ts +10 -0
  136. package/tests/unit/color/xyz/convert.spec.ts +18 -0
  137. package/tests/unit/css/class.spec.ts +157 -0
  138. package/tests/unit/environment/basic.spec.ts +20 -0
  139. package/tests/unit/environment/device.spec.ts +146 -0
  140. package/tests/unit/environment/feature.spec.ts +388 -0
  141. package/tests/unit/environment/geo.spec.ts +111 -0
  142. package/tests/unit/environment/runtime.spec.ts +364 -0
  143. package/tests/unit/environment/snapshot.spec.ts +4 -0
  144. package/tests/unit/environment/variable.spec.ts +190 -0
  145. package/tests/unit/event/class-event-proxy.spec.ts +225 -0
  146. package/tests/unit/event/event-manager.spec.ts +246 -0
  147. package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
  148. package/tests/unit/exception/browser.spec.ts +213 -0
  149. package/tests/unit/exception/nodejs.spec.ts +144 -0
  150. package/tests/unit/exception/normalize.spec.ts +57 -0
  151. package/tests/unit/identifier/id.spec.ts +71 -0
  152. package/tests/unit/identifier/uuid.spec.ts +85 -0
  153. package/tests/unit/log/log-emitter.spec.ts +33 -0
  154. package/tests/unit/log/log-scheduler.spec.ts +40 -0
  155. package/tests/unit/log/log-type.spec.ts +7 -0
  156. package/tests/unit/log/logger.spec.ts +222 -0
  157. package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
  158. package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
  159. package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
  160. package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
  161. package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
  162. package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
  163. package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
  164. package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
  165. package/tests/unit/random/string.spec.ts +11 -0
  166. package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
  167. package/tests/unit/reactor/preact-signal.spec.ts +1 -2
  168. package/tests/unit/singleton/singleton.spec.ts +49 -0
  169. package/tests/unit/storage/table.spec.ts +620 -0
  170. package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
  171. package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
  172. package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
  173. package/.oxlintrc.json +0 -5
  174. package/src/random/uuid.ts +0 -103
  175. package/tests/unit/random/uuid.spec.ts +0 -37
@@ -0,0 +1,543 @@
1
+ import { generateUuidV4FromUrl } from "#Source/identifier/index.ts"
2
+ import { getGlobalLogScheduler } from "./log-scheduler.ts"
3
+
4
+ import type { LogRecord } from "./log-record.ts"
5
+ import type { LogTask } from "./log-scheduler.ts"
6
+ import type { LogType } from "./log-type.ts"
7
+ import {
8
+ ConsoleDebugLogEmitter,
9
+ ConsoleErrorLogEmitter,
10
+ ConsoleInfoLogEmitter,
11
+ ConsoleLogLogEmitter,
12
+ ConsoleWarnLogEmitter,
13
+ } from "./log-emitter.ts"
14
+ import type { LogEmitter, LogEmitterOptions } from "./log-emitter.ts"
15
+
16
+ const globalLogScheduler = getGlobalLogScheduler()
17
+
18
+ /**
19
+ * Describe an object that exposes a `Logger` instance.
20
+ */
21
+ export interface LoggerFriendly {
22
+ logger: Logger
23
+ }
24
+ /**
25
+ * Describe options that may provide an existing `Logger`.
26
+ */
27
+ export interface LoggerFriendlyOptions {
28
+ logger?: Logger | undefined
29
+ }
30
+
31
+ /**
32
+ * Describe an emitter registration for a specific log type.
33
+ */
34
+ export interface LogEmitterItem {
35
+ logType: LogType
36
+ // oxlint-disable-next-line no-explicit-any
37
+ LogEmitter: new (...args: any[]) => LogEmitter
38
+ injectOptions?: ((options: LogEmitterOptions) => LogEmitterOptions) | undefined
39
+ }
40
+ /**
41
+ * Define the default console-based emitters.
42
+ */
43
+ const DEFAULT_CONSOLE_LOG_EMITTER_ITEMS: LogEmitterItem[] = [
44
+ {
45
+ logType: "log",
46
+ LogEmitter: ConsoleLogLogEmitter,
47
+ },
48
+ {
49
+ logType: "info",
50
+ LogEmitter: ConsoleInfoLogEmitter,
51
+ },
52
+ {
53
+ logType: "warn",
54
+ LogEmitter: ConsoleWarnLogEmitter,
55
+ },
56
+ {
57
+ logType: "error",
58
+ LogEmitter: ConsoleErrorLogEmitter,
59
+ },
60
+ {
61
+ logType: "debug",
62
+ LogEmitter: ConsoleDebugLogEmitter,
63
+ },
64
+ ]
65
+
66
+ /**
67
+ * Describe runtime logger configuration values.
68
+ */
69
+ export interface LoggerConfigs {
70
+ enabled: boolean
71
+ autoSend: boolean
72
+ filter: ((logRecord: LogRecord) => boolean)
73
+ emitters: LogEmitterItem[]
74
+ }
75
+ /**
76
+ * Describe options used to construct a `Logger`.
77
+ */
78
+ export interface LoggerOptions {
79
+ name?: string | undefined
80
+ parent?: Logger | undefined
81
+ configs?: Partial<LoggerConfigs> | undefined
82
+ }
83
+ /**
84
+ * 表示支持层级继承、标签管理与延迟发送的日志记录器。
85
+ *
86
+ * 规范:
87
+ * - 初始配置的优先级为 parentConfigs -> configs -> 默认值。
88
+ * - 当 parent 为 undefined 时,parentConfigs 也应该是 undefined。
89
+ * - 更新 parent 时,parentConfigs 也应该同步更新。
90
+ * - 作为 parent 修改自己的 configs 时,不会影响 child 的 configs。
91
+ * - 作为 child 可以独立修改自己的 configs,也可以将 parentConfigs 应用到自己的 configs。
92
+ */
93
+ export class Logger implements Disposable {
94
+ /**
95
+ * 从选项中解析 logger;如果未提供,则基于全局 logger 派生一个新 logger。
96
+ *
97
+ * @example
98
+ * ```
99
+ * // Expect: example1 === customLogger
100
+ * const customLogger = new Logger({ name: "Custom" })
101
+ * const example1 = Logger.fromOptions({ logger: customLogger })
102
+ *
103
+ * // Expect: example2 !== globalLogger
104
+ * // Expect: example2.hasParent() === true
105
+ * const globalLogger = getGlobalLogger()
106
+ * const example2 = Logger.fromOptions({})
107
+ *
108
+ * class CustomClass implements LoggerFriendly {
109
+ * logger: Logger
110
+ *
111
+ * constructor(options: LoggerFriendlyOptions) {
112
+ * this.logger = Logger.fromOptions(options)
113
+ * }
114
+ * }
115
+ *
116
+ * // Expect: example3.logger instanceof Logger
117
+ * const example3 = new CustomClass({ logger: customLogger })
118
+ * ```
119
+ */
120
+ static fromOptions(options: LoggerFriendlyOptions): Logger {
121
+ if (options.logger !== undefined) {
122
+ return options.logger
123
+ }
124
+
125
+ const parent = getGlobalLogger()
126
+ const logger = Logger.derive(parent)
127
+ return logger
128
+ }
129
+
130
+ /**
131
+ * Create a child logger from a parent logger.
132
+ */
133
+ static derive(parentLogger: Logger): Logger {
134
+ const logger = new Logger({ parent: parentLogger })
135
+ return logger
136
+ }
137
+
138
+ protected name: string
139
+ protected parent?: Logger | undefined
140
+ protected childs: Logger[]
141
+ protected parentConfigs?: LoggerConfigs | undefined
142
+ protected configs: LoggerConfigs
143
+
144
+ protected instanceTags: string[]
145
+ protected sessionTags: string[]
146
+ protected onceTags: string[]
147
+
148
+ private logTasks: LogTask[]
149
+ private cachedLogMethodMap: Map<string, (...messages: unknown[]) => this>
150
+
151
+ constructor(options: LoggerOptions) {
152
+ this.name = options.name ?? "Unnamed"
153
+ this.parent = options.parent
154
+ this.childs = []
155
+ const parentConfigs = options.parent?.getConfigs()
156
+ const configs = options.configs
157
+ this.parentConfigs = parentConfigs
158
+ this.configs = {
159
+ enabled: parentConfigs?.enabled ?? configs?.enabled ?? true,
160
+ autoSend: parentConfigs?.autoSend ?? configs?.autoSend ?? true,
161
+ filter: parentConfigs?.filter ?? configs?.filter ?? ((): boolean => true),
162
+ emitters: parentConfigs?.emitters ?? configs?.emitters ?? DEFAULT_CONSOLE_LOG_EMITTER_ITEMS
163
+ }
164
+
165
+ this.instanceTags = []
166
+ this.sessionTags = []
167
+ this.onceTags = []
168
+
169
+ this.logTasks = []
170
+ this.cachedLogMethodMap = new Map()
171
+ }
172
+
173
+ /**
174
+ * Get the logger display name.
175
+ */
176
+ getName(): string {
177
+ return this.name
178
+ }
179
+
180
+ /**
181
+ * Set the logger display name.
182
+ */
183
+ setName(name: string): this {
184
+ this.name = name
185
+ return this
186
+ }
187
+
188
+ /**
189
+ * Check whether the logger currently has a parent.
190
+ */
191
+ hasParent(): boolean {
192
+ return this.parent !== undefined
193
+ }
194
+
195
+ /**
196
+ * Set or clear the parent logger.
197
+ */
198
+ setParent(parentLogger?: Logger | undefined): this {
199
+ this.parent = parentLogger
200
+ this.parentConfigs = parentLogger?.getConfigs()
201
+ return this
202
+ }
203
+
204
+ /**
205
+ * Get merged effective configs from parent and local overrides.
206
+ */
207
+ getConfigs(): LoggerConfigs {
208
+ return { ...this.parentConfigs, ...this.configs }
209
+ }
210
+
211
+ /**
212
+ * Update local configs and refresh inherited configs on child loggers.
213
+ */
214
+ setConfigs(configs: Partial<LoggerConfigs>): this {
215
+ this.configs = { ...this.getConfigs(), ...configs }
216
+ for (const child of this.childs) {
217
+ child.setParent(this)
218
+ }
219
+ return this
220
+ }
221
+
222
+ /**
223
+ * Apply current parent configs onto local configs.
224
+ */
225
+ useParentConfigs(): this {
226
+ if (this.parentConfigs !== undefined) {
227
+ this.setConfigs(this.parentConfigs)
228
+ }
229
+ return this
230
+ }
231
+
232
+ /**
233
+ * Get all configured emitter registrations.
234
+ */
235
+ getAllLogEmitters(): LogEmitterItem[] {
236
+ const emitters = this.getConfigs().emitters
237
+ return emitters
238
+ }
239
+
240
+ /**
241
+ * Get emitter registrations for a specific log type.
242
+ */
243
+ getLogEmitters(logType: LogType): LogEmitterItem[] {
244
+ const emitters = this.getConfigs().emitters
245
+ const filteredEmitters = emitters.filter(item => item.logType === logType)
246
+ return filteredEmitters
247
+ }
248
+
249
+ /**
250
+ * 支持为同一类型的 Log 添加多个 LogEmitter。
251
+ */
252
+ addLogEmitters(logEmitters: LogEmitterItem[]): this {
253
+ const newEmitters = [...this.getConfigs().emitters]
254
+ for (const item of logEmitters) {
255
+ if (!newEmitters.some((e) => e.logType === item.logType && e.LogEmitter === item.LogEmitter)) {
256
+ newEmitters.push(item)
257
+ }
258
+ }
259
+ this.setConfigs({
260
+ emitters: newEmitters
261
+ })
262
+ return this
263
+ }
264
+
265
+ /**
266
+ * Remove matched emitter registrations.
267
+ */
268
+ removeLogEmitters(logEmitters: LogEmitterItem[]): this {
269
+ const emitters = this.getConfigs().emitters
270
+ const remainingEmitters = emitters.filter((item) => {
271
+ return !logEmitters.some((e) => e.logType === item.logType && e.LogEmitter === item.LogEmitter)
272
+ })
273
+ this.setConfigs({
274
+ emitters: remainingEmitters
275
+ })
276
+ return this
277
+ }
278
+
279
+ /**
280
+ * Invoke all emitters that match the record log type.
281
+ */
282
+ invokeLogEmitter(record: LogRecord): void {
283
+ const { type } = record
284
+ if (type === undefined) {
285
+ return
286
+ }
287
+ const logEmitterList = this.getLogEmitters(type)
288
+ if (logEmitterList.length === 0) {
289
+ return
290
+ }
291
+ logEmitterList.forEach((item) => {
292
+ const injectOptions = item.injectOptions ?? (<T>(v: T): T => v)
293
+ const LogEmitter = item.LogEmitter
294
+ const logEmitter = new LogEmitter(injectOptions(record))
295
+ logEmitter.emit()
296
+ })
297
+ }
298
+
299
+ /**
300
+ * Get full hierarchical name tags from root to current logger.
301
+ */
302
+ getNameTags(): string[] {
303
+ const names = [...this.parent?.getNameTags() ?? [], this.getName()]
304
+ return names
305
+ }
306
+
307
+ /**
308
+ * Add persistent instance tags.
309
+ */
310
+ addInstanceTags(tags: string[]): this {
311
+ this.instanceTags.push(...tags)
312
+ return this
313
+ }
314
+
315
+ /**
316
+ * Remove matched instance tags.
317
+ */
318
+ removeInstanceTags(tags: string[]): this {
319
+ this.instanceTags = this.instanceTags.filter(tag => !tags.includes(tag))
320
+ return this
321
+ }
322
+
323
+ /**
324
+ * Get current instance tags.
325
+ */
326
+ getInstanceTags(): string[] {
327
+ return this.instanceTags
328
+ }
329
+
330
+ /**
331
+ * Clear all instance tags.
332
+ */
333
+ clearInstanceTags(): this {
334
+ this.instanceTags = []
335
+ return this
336
+ }
337
+
338
+ /**
339
+ * Add session tags used until `tagEnd` is called.
340
+ */
341
+ addSessionTags(tags: string[]): this {
342
+ this.sessionTags.push(...tags)
343
+ return this
344
+ }
345
+
346
+ /**
347
+ * Remove matched session tags.
348
+ */
349
+ removeSessionTags(tags: string[]): this {
350
+ this.sessionTags = this.sessionTags.filter(tag => !tags.includes(tag))
351
+ return this
352
+ }
353
+
354
+ /**
355
+ * Get current session tags.
356
+ */
357
+ getSessionTags(): string[] {
358
+ return this.sessionTags
359
+ }
360
+
361
+ /**
362
+ * Clear all session tags.
363
+ */
364
+ clearSessionTags(): this {
365
+ this.sessionTags = []
366
+ return this
367
+ }
368
+
369
+ /**
370
+ * Start and automatically end a session tag scope on dispose.
371
+ */
372
+ autoTag(tags: string[]): Disposable {
373
+ this.tagStart(tags)
374
+ return {
375
+ [Symbol.dispose]: (): void => {
376
+ this.tagEnd()
377
+ },
378
+ }
379
+ }
380
+
381
+ /**
382
+ * Start a session tag scope.
383
+ */
384
+ tagStart(tags: string[]): this {
385
+ this.addSessionTags(tags)
386
+ return this
387
+ }
388
+
389
+ /**
390
+ * End the current session tag scope.
391
+ */
392
+ tagEnd(): this {
393
+ this.clearSessionTags()
394
+ return this
395
+ }
396
+
397
+ /**
398
+ * Add one-shot tags consumed by the next log call.
399
+ */
400
+ addOnceTags(tags: string[]): this {
401
+ this.onceTags.push(...tags)
402
+ return this
403
+ }
404
+
405
+ /**
406
+ * Get current one-shot tags.
407
+ */
408
+ getOnceTags(): string[] {
409
+ return this.onceTags
410
+ }
411
+
412
+ /**
413
+ * Clear all one-shot tags.
414
+ */
415
+ clearOnceTags(): this {
416
+ this.onceTags = []
417
+ return this
418
+ }
419
+
420
+ /**
421
+ * Build and cache a log method bound to type and prefix.
422
+ */
423
+ private makeLogMethod(logType: LogType, prefix: string): ((...args: unknown[]) => this) {
424
+ const cachedLogMethodKey = `${logType}-${prefix}`
425
+ const cachedLogMethod = this.cachedLogMethodMap.get(cachedLogMethodKey)
426
+ if (cachedLogMethod !== undefined) {
427
+ return cachedLogMethod
428
+ }
429
+
430
+ const newMethod = (...messages: unknown[]): this => {
431
+ const configs = this.getConfigs()
432
+ if (configs.enabled === false) {
433
+ return this
434
+ }
435
+
436
+ const tags = [
437
+ prefix,
438
+ ...this.getNameTags(),
439
+ ...this.getInstanceTags(),
440
+ ...this.getSessionTags(),
441
+ ...this.getOnceTags()
442
+ ]
443
+ const logRecord: LogRecord = {
444
+ type: logType,
445
+ tags: tags,
446
+ messages,
447
+ }
448
+
449
+ // oxlint-disable-next-line no-array-callback-reference
450
+ if (configs.filter(logRecord) === false) {
451
+ return this
452
+ }
453
+
454
+ this.logTasks.push({
455
+ record: logRecord,
456
+ emit: () => {
457
+ this.invokeLogEmitter(logRecord)
458
+ }
459
+ })
460
+ if (configs.autoSend === true) {
461
+ this.send()
462
+ }
463
+ this.clearOnceTags()
464
+ return this
465
+ }
466
+ this.cachedLogMethodMap.set(cachedLogMethodKey, newMethod)
467
+
468
+ return newMethod
469
+ }
470
+
471
+ /**
472
+ * Queue a `log` level record.
473
+ */
474
+ log(...messages: unknown[]): this {
475
+ return this.makeLogMethod("log", "🟢")(...messages)
476
+ }
477
+
478
+ /**
479
+ * Queue an `info` level record.
480
+ */
481
+ info(...messages: unknown[]): this {
482
+ return this.makeLogMethod("info", "🔵")(...messages)
483
+ }
484
+
485
+ /**
486
+ * Queue a `warn` level record.
487
+ */
488
+ warn(...messages: unknown[]): this {
489
+ return this.makeLogMethod("warn", "🟡")(...messages)
490
+ }
491
+
492
+ /**
493
+ * Queue an `error` level record.
494
+ */
495
+ error(...messages: unknown[]): this {
496
+ return this.makeLogMethod("error", "🔴")(...messages)
497
+ }
498
+
499
+ /**
500
+ * Queue a `debug` level record.
501
+ */
502
+ debug(...messages: unknown[]): this {
503
+ return this.makeLogMethod("debug", "🟤")(...messages)
504
+ }
505
+
506
+ /**
507
+ * Create a child logger configured for manual batched sending.
508
+ */
509
+ batch(id?: string | undefined): Logger {
510
+ const logger = Logger.derive(this)
511
+ logger.setConfigs({ autoSend: false })
512
+ logger.addInstanceTags([id ?? generateUuidV4FromUrl()])
513
+ return logger
514
+ }
515
+
516
+ /**
517
+ * Flush queued records to the global scheduler.
518
+ */
519
+ send(): void {
520
+ globalLogScheduler.enqueue(this.logTasks)
521
+ this.logTasks = []
522
+ }
523
+
524
+ /**
525
+ * Flush queued records when used with `using` disposal.
526
+ */
527
+ [Symbol.dispose](): void {
528
+ this.send()
529
+ }
530
+ }
531
+
532
+ /**
533
+ * Get the singleton global `Logger` instance.
534
+ */
535
+ export const getGlobalLogger: () => Logger = (
536
+ () => {
537
+ let instance: Logger | undefined = undefined
538
+ return (): Logger => {
539
+ instance = instance ?? new Logger({ name: "GlobalLogger" })
540
+ return instance
541
+ }
542
+ }
543
+ )()
@@ -0,0 +1,89 @@
1
+ # Orchestration
2
+
3
+ ## Description
4
+
5
+ Orchestration 模块提供围绕执行单元编排(orchestration)的基础建模能力,用于把多个执行单元之间的创建、组织、调度、等待、放行、推进、收束以及整体协作关系表达为稳定且可复用的公共语义。
6
+
7
+ 它关注的不是某个具体框架、某个具体队列系统或某个具体工作流引擎的宿主接口,而是“多个执行单元如何被组织为可理解、可组合、可控制、可持续演化的编排结构”这一更一般的问题。因此,这个模块不应被理解为仅仅承载互斥锁、信号量或栅栏的协调原语集合;这些内容只是当前实现中更基础的一层,而不是 Orchestration 这个词所指向的全部问题域。
8
+
9
+ ## For Understanding
10
+
11
+ 理解 Orchestration 模块时,应先把它看作“执行单元组织关系”的建模层,而不是一组彼此孤立的小工具。它要解决的问题,不只是多个异步流程怎样竞争资源、怎样等待同步点,也包括执行单元该如何被表达、如何被组合成更大的结构、流程阶段该如何被推进,以及这些结构之间应该暴露什么样的稳定公共语义。
12
+
13
+ 因此,Orchestration 更适合被理解为一个分层的问题域:较底层是 coordination,用于表达进入控制、等待约束、同步点和资源占用;再往上则可以逐步生长出更接近编排结构本身的能力,用于表达单个执行单元、执行单元之间的关系、阶段推进、依赖收敛、生命周期以及整体执行边界。未来这些能力的形态可能会落在 task、job、workflow 或其它更合适的抽象上,但 coordination 只是这些能力可复用的基础,并不等同于整个 orchestration 模块。
14
+
15
+ 理解这个模块时,还应守住几条边界原则:
16
+
17
+ - Orchestration 模块表达的是编排语义与协调语义,但不应直接承担某个业务系统专属的流程定义语言、运营规则或领域状态机。
18
+ - 它可以吸收一切稳定且通用的编排概念,但这些概念应以模型形式出现,而不是某个具体平台配置格式、DSL 或产品术语的直接翻版。
19
+ - coordination 适合表达进程内、运行时内的基础协作约束;更高层的编排能力即使依赖这些约束,也不应把自己退化成底层锁语义的别名。
20
+ - 它可以为重试、取消、阶段推进、依赖关系或执行计划等语义提供抽象空间,但不应把监控、持久化、分布式共识、租约续期或平台运维策略直接混入核心公共 API。
21
+
22
+ ## For Using
23
+
24
+ 当你希望把一组执行动作组织成清楚、可维护、可组合的编排结构,而不是在业务代码中反复拼接 `Promise`、计数器、布尔状态、临时队列和零散流程控制逻辑时,可以使用 Orchestration 模块。
25
+
26
+ 从使用角度看,当前模块中的能力大致可以分为三类:
27
+
28
+ - 底层协调能力:用于表达互斥进入、限量并发、阶段会合、闭锁等待、资源占用句柄以及超时和中止等等待约束。
29
+ - 更高层编排能力:用于表达单个执行单元的边界、多个执行单元之间的关系、阶段推进、失败传播、取消语义、执行计划以及整体完成条件。这部分目前还不是主实现重心,但属于该模块未来合理的组成方向。
30
+ - 结构化执行能力:用于把一组原本零散的异步动作收束为可以理解、组合与扩展的执行结构。未来它可能体现为 task、job、workflow,也可能体现为其它更贴切的抽象,但都不应退化成业务代码里对底层 Promise 的随意组合。
31
+
32
+ 更合适的接入方式,是先判断你的问题属于哪一层:如果你的核心诉求是某个时刻能否进入、是否需要排队、何时统一继续,那么你更接近 coordination;如果你的核心诉求已经开始涉及执行单元如何被声明、取消、组合、计划、收束或被纳入更大的执行结构,那么你已经进入更高层的 orchestration 语义。
33
+
34
+ 当前对外已经落地的主要是 `coordination` 子模块,因此现阶段使用时更多是在处理互斥、信号量、读写锁、闭锁与栅栏这类问题;但理解这个模块时不应把它局限为“并发锁工具箱”。更合适的看法是:coordination 先提供底层编排积木,而 orchestration 的整体职责会继续向更完整的执行组织与流程编排语义扩展。
35
+
36
+ ## For Contributing
37
+
38
+ 为 Orchestration 模块贡献内容时,优先判断新增能力是否真的在澄清某种稳定的编排语义,而不是只是在补一个局部业务流程的便捷封装。这个模块应长期服务于“执行单元如何被表达”“多个执行单元如何组成更大的结构”“流程如何推进与结束”“等待与放行如何成为可复用原语”这几类问题。如果一个能力只有在绑定特定平台、特定产品流程或特定部署环境时才成立,那么它通常不应直接成为该模块的公共组成部分。
39
+
40
+ 扩展这个模块时,应特别警惕以下倾向:把 orchestration 退化成一堆零散锁工具;把某个具体工作流引擎的配置模型直接照搬进公共 API;把重试、补偿、优先级、监控或持久化策略无差别地下沉到所有层级;把调用方的一次性业务约定固化为模块语义;或者为了某个实现细节而暴露内部等待队列、内部状态节点与临时调度结构。文档应说明哪些编排边界是长期成立的,以及为什么这些边界成立,而不是复述当前版本的局部实现技巧。
41
+
42
+ ### JSDoc 注释格式要求
43
+
44
+ - 每个公开导出的目标(类型、函数、变量、类等)都应包含 JSDoc 注释,让人在不跳转实现的情况下就能理解用途。
45
+ - JSDoc 注释第一行应为清晰且简洁的描述,该描述优先使用中文(英文也可以)。
46
+ - 如果描述后还有其他内容,应在描述后加一个空行。
47
+ - 如果有示例,应使用 `@example` 标签,后接三重反引号代码块(不带语言标识)。
48
+ - 如果有示例,应包含多个场景,展示不同用法,尤其要覆盖常见组合方式或边界输入。
49
+ - 如果有示例,应使用注释格式说明每个场景:`// Expect: <result>`。
50
+ - 如果有示例,应将结果赋值给 `example1`、`example2` 之类的变量,以保持示例易读。
51
+ - 如果有示例,`// Expect: <result>` 应该位于 `example1`、`example2` 之前,以保持示例的逻辑清晰。
52
+ - 如果有示例,应优先使用确定性示例;避免断言精确的随机输出。
53
+ - 如果函数返回结构化字符串,应展示其预期格式特征。
54
+ - 如果有参考资料,应将 `@see` 放在 `@example` 代码块之后,并用一个空行分隔。
55
+
56
+ ### 实现规范要求
57
+
58
+ - 不同程序元素之间使用一个空行分隔,保持结构清楚。这里的程序元素,通常指函数、类型、常量,以及直接服务于它们的辅助元素。
59
+ - 某程序元素独占的辅助元素与该程序元素本身视为一个整体,不要在它们之间添加空行。
60
+ - 程序元素的辅助元素应该放置在该程序元素的上方,以保持阅读时的逻辑顺序。
61
+ - 若辅助元素被多个程序元素共享,则应将其视为独立的程序元素,放在这些程序元素中第一个相关目标的上方,并与后续程序元素之间保留一个空行。
62
+ - 辅助元素也应该像其它程序元素一样,保持清晰的命名和适当的注释,以便在需要阅读实现细节时能够快速理解它们的作用和使用方式。
63
+ - 辅助元素的命名必须以前缀 `internal` 开头(或 `Internal`,大小写不敏感)。
64
+ - 辅助元素永远不要公开导出。
65
+ - 被模块内多个不同文件中的程序元素共享的辅助元素,应该放在一个单独的文件中,例如 `./src/orchestration/internal.ts`。
66
+ - 模块内可以包含子模块。只有当某个子目录表达一个稳定、可单独理解、且可能被父模块重导出的子问题域时,才应将其视为子模块。
67
+ - 子模块包含多个文件时,应该为其单独创建子文件夹,并为其创建单独的 Barrel 文件;父模块的 Barrel 文件再重导出子模块的 Barrel 文件。
68
+ - 子模块不需要有自己的 `README.md`。
69
+ - 子模块可以有自己的 `internal.ts` 文件,多个子模块共享的辅助元素应该放在父模块的 `internal.ts` 文件中,单个子模块共享的辅助元素应该放在该子模块的 `internal.ts` 文件中。
70
+ - 对模块依赖关系的要求(通常是不循环依赖或不反向依赖)与对 DRY 的要求可能产生冲突。此时,若复用的代码数量不大,可以适当牺牲 DRY,复制粘贴并保留必要的注释说明;若复用的代码数量较大,则可以将其抽象到新的文件或子模块中,如 `common.ts`,并在需要的地方导入使用。
71
+ - 与 orchestration 相关的实现应优先围绕执行单元边界、执行单元关系、等待约束、生命周期推进、阶段语义与可组合性组织;其中 coordination 子模块再单独关注占用句柄、队列调度、公平性与同步阶段语义,避免把具体业务流程或宿主运行时策略混入模块边界。
72
+
73
+ ### 导出策略要求
74
+
75
+ - 保持内部辅助项和内部符号为私有,不要让外部接入依赖临时性的内部结构。
76
+ - 每个模块都应有一个用于重导出所有公共 API 的 Barrel 文件。
77
+ - Barrel 文件应命名为 `index.ts`,放在模块目录根部,并且所有公共 API 都应从该文件导出。
78
+ - 新增公共能力时,应优先检查它是否表达稳定、清楚且值得长期维护的编排语义或协调语义,而不是某段实现细节的便捷暴露;仅在确认需要长期对外承诺时再加入 Barrel 导出。
79
+
80
+ ### 测试要求
81
+
82
+ - 若程序元素是函数,则只为该函数编写一个测试,如果该函数需要测试多个用例,应放在同一个测试中。
83
+ - 若程序元素是类,则至少要为该类的每一个方法编写一个测试,如果该方法需要测试多个用例,应放在同一个测试中。
84
+ - 若程序元素是类,除了为该类的每一个方法编写至少一个测试之外,还可以为该类编写任意多个测试,以覆盖该类的不同使用场景或边界情况。
85
+ - 若编写测试时需要用到辅助元素(Mock 或 Spy 等),可以在测试文件中直接定义这些辅助元素。若辅助元素较为简单,则可以直接放在每一个测试内部,优先保证每个测试的独立性,而不是追求极致 DRY;若辅助元素较为复杂或需要在多个测试中复用,则可以放在测试文件顶部,供该测试文件中的所有测试使用。
86
+ - 测试顺序应与源文件中被测试目标的原始顺序保持一致。
87
+ - 若该模块不需要测试,必须在说明文件中明确说明该模块不需要测试,并说明理由。一般来说,只有在该模块没有可执行的公共函数、只承载类型层表达,或其语义已被上层模块的测试完整覆盖且重复测试几乎不再带来额外价值时,才适合这样处理。
88
+ - 模块的单元测试文件目录是 `./tests/unit/orchestration`,若模块包含子模块,则子模块的单元测试文件目录为 `./tests/unit/orchestration/<sub-module-name>`。
89
+ - 对这个模块来说,测试应按层覆盖:coordination 层优先覆盖资源获取与释放、等待队列的 FIFO 或公平性约束、超时与中止导致的失败语义、同步点完成或破坏时的整体行为,以及 permit 重复释放等误用路径;未来若引入更高层编排能力,则应优先覆盖生命周期推进、依赖关系收敛、失败传播、取消与重试边界,以及整体编排结果的确定性。