@planet-matrix/mobius-model 0.9.0 → 0.10.1

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 (63) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/oxlint.config.ts +1 -2
  3. package/package.json +5 -5
  4. package/scripts/build.ts +2 -52
  5. package/src/basic/promise.ts +141 -71
  6. package/src/drizzle/pagination.ts +0 -2
  7. package/src/event/class-event-proxy.ts +0 -2
  8. package/src/event/instance-event-proxy.ts +0 -2
  9. package/src/exception/README.md +28 -19
  10. package/src/exception/error/error.ts +123 -0
  11. package/src/exception/error/index.ts +2 -0
  12. package/src/exception/error/match.ts +38 -0
  13. package/src/exception/error/must-fix.ts +17 -0
  14. package/src/exception/index.ts +2 -0
  15. package/src/file-system/find.ts +53 -0
  16. package/src/file-system/index.ts +2 -0
  17. package/src/file-system/path.ts +76 -0
  18. package/src/file-system/resolve.ts +22 -0
  19. package/src/form/inputor-controller/base.ts +0 -13
  20. package/src/form/inputor-controller/form.ts +0 -2
  21. package/src/http/api/api-type.ts +0 -3
  22. package/src/http/api-adapter/api-result-arktype.ts +0 -3
  23. package/src/index.ts +2 -0
  24. package/src/openai/openai.ts +0 -1
  25. package/src/request/fetch/browser.ts +0 -5
  26. package/src/request/fetch/nodejs.ts +0 -5
  27. package/src/request/request/base.ts +0 -4
  28. package/src/request/request/general.ts +0 -1
  29. package/src/result/controller.ts +11 -7
  30. package/src/result/either.ts +230 -60
  31. package/src/result/generator.ts +168 -0
  32. package/src/result/index.ts +1 -0
  33. package/src/route/router/router.ts +0 -1
  34. package/src/route/uri/hash.ts +0 -1
  35. package/src/route/uri/search.ts +0 -1
  36. package/src/service/README.md +1 -0
  37. package/src/service/index.ts +1 -0
  38. package/src/service/service.ts +110 -0
  39. package/src/socket/client/socket-unit.ts +0 -2
  40. package/src/socket/server/socket-unit.ts +0 -1
  41. package/src/tube/helper.ts +0 -1
  42. package/src/weixin/official-account/authorization.ts +0 -2
  43. package/src/weixin/official-account/js-api.ts +0 -2
  44. package/src/weixin/open/oauth2.ts +0 -2
  45. package/tests/unit/aio/json.spec.ts +0 -1
  46. package/tests/unit/basic/promise.spec.ts +158 -50
  47. package/tests/unit/credential/api-key.spec.ts +0 -1
  48. package/tests/unit/credential/password.spec.ts +0 -1
  49. package/tests/unit/exception/error/error.spec.ts +83 -0
  50. package/tests/unit/exception/error/match.spec.ts +81 -0
  51. package/tests/unit/http/api-adapter/node-http.spec.ts +0 -4
  52. package/tests/unit/identifier/uuid.spec.ts +0 -1
  53. package/tests/unit/request/request/base.spec.ts +0 -3
  54. package/tests/unit/request/request/general.spec.ts +0 -1
  55. package/tests/unit/result/controller.spec.ts +82 -0
  56. package/tests/unit/result/either.spec.ts +377 -0
  57. package/tests/unit/result/generator.spec.ts +273 -0
  58. package/tests/unit/route/router/route.spec.ts +0 -1
  59. package/tests/unit/route/uri/pathname.spec.ts +0 -1
  60. package/tests/unit/socket/server.spec.ts +0 -2
  61. package/vite.config.ts +2 -1
  62. package/dist/index.js +0 -720
  63. package/dist/index.js.map +0 -1005
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @planet-matrix/mobius-model
2
2
 
3
+ ## 0.10.1
4
+
5
+ ### Patch Changes
6
+
7
+ - d7c33d7: Add `dist` to `.npmignore`.
8
+
9
+ ## 0.10.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 6f99535: Add service model.
14
+ - 9520154: Add tagged error capabilities to exception model.
15
+
3
16
  ## 0.9.0
4
17
 
5
18
  ### Minor Changes
package/oxlint.config.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { defineConfig } from "oxlint"
2
1
  import { Lint } from "@planet-matrix/mobius-mono"
3
2
 
4
- const config: Lint.OxlintConfig = defineConfig(Lint.defineOxlintConfig({}))
3
+ const config: Lint.OxlintConfig = Lint.defineOxlintConfig({})
5
4
 
6
5
  export default config
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planet-matrix/mobius-model",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "Mobius model.",
5
5
  "keywords": [
6
6
  "mobius",
@@ -59,6 +59,7 @@
59
59
  "prepublishOnly": "bun run build"
60
60
  },
61
61
  "dependencies": {
62
+ "typescript": "^6.0.2",
62
63
  "ua-parser-js": "^2.0.9",
63
64
  "html-to-image": "^1.11.13",
64
65
  "undici": "^7.24.6",
@@ -77,14 +78,13 @@
77
78
  "openai": "^6.33.0"
78
79
  },
79
80
  "devDependencies": {
80
- "@planet-matrix/mobius-mono": "0.8.0",
81
+ "@planet-matrix/mobius-mono": "0.10.1",
81
82
  "@types/bun": "^1.3.11",
83
+ "@typescript/native-preview": "^7.0.0-dev.20260329.1",
82
84
  "oxlint": "^1.57.0",
83
85
  "oxlint-tsgolint": "^0.18.1",
84
- "typescript": "^6.0.2",
85
- "@typescript/native-preview": "^7.0.0-dev.20260329.1",
86
- "vite": "^8.0.3",
87
86
  "vitest": "^4.1.2",
87
+ "vite": "^8.0.3",
88
88
  "drizzle-kit": "^0.31.10"
89
89
  },
90
90
  "peerDependencies": {},
package/scripts/build.ts CHANGED
@@ -1,53 +1,3 @@
1
- import { $, build } from "bun"
1
+ import { Build } from "@planet-matrix/mobius-mono"
2
2
 
3
- // ensure the outdir is existing and cleaned up before building
4
- console.log("Cleaning and preparing ./dist directory...")
5
- await $`rm -rf dist`
6
-
7
- // console.log("Building TypeScript declarations...")
8
- // await $`bun run tsgo --noEmit false`
9
-
10
- console.log("Building the library with Bun...")
11
- const builtOutput = await build({
12
- banner: "// Enjoy using Example Library!",
13
- bytecode: false,
14
- conditions: [],
15
- define: {},
16
- drop: [],
17
- emitDCEAnnotations: false,
18
- entrypoints: ["./src/index.ts"],
19
- env: "PUBLIC_*",
20
- external: [],
21
- footer: "// Made with ♥ by the Planet Matrix team!",
22
- format: "esm",
23
- ignoreDCEAnnotations: false,
24
- loader: {},
25
- minify: {
26
- identifiers: true,
27
- keepNames: false,
28
- syntax: true,
29
- whitespace: true,
30
- },
31
- naming: {
32
- entry: '[dir]/[name].[ext]',
33
- chunk: '[name]-[hash].[ext]',
34
- asset: '[name]-[hash].[ext]',
35
- },
36
- outdir: "./dist",
37
- packages: "bundle",
38
- plugins: [],
39
- // publicPath: undefined,
40
- // root: undefined,
41
- sourcemap: "linked",
42
- splitting: true,
43
- target: "bun",
44
- throw: false,
45
- tsconfig: "./tsconfig.json",
46
- })
47
-
48
- if (builtOutput.success === false) {
49
- console.error('Build failed:', builtOutput.logs);
50
- throw new Error('Build process failed.');
51
- } else {
52
- console.log("Build completed successfully.");
53
- }
3
+ await Build.Library.build({})
@@ -6,16 +6,16 @@ import { isPlainObject } from "./is.ts"
6
6
  * @example
7
7
  * ```
8
8
  * // Expect: 6
9
- * const example1 = await promiseThen((value: number) => value * 2, Promise.resolve(3))
9
+ * const example1 = await promiseThen(Promise.resolve(3), (value: number) => value * 2)
10
10
  * // Expect: "ok!"
11
- * const example2 = await promiseThen((value: string) => `${value}!`, Promise.resolve("ok"))
11
+ * const example2 = await promiseThen(Promise.resolve("ok"), (value: string) => `${value}!`)
12
12
  * ```
13
13
  */
14
14
  export const promiseThen = async <V, R>(
15
- doSomething: (value: V) => R | PromiseLike<R>,
16
15
  target: Promise<V>,
16
+ doSomething: (value: V) => R | PromiseLike<R>,
17
17
  ): Promise<R> => {
18
- return await target.then(doSomething)
18
+ return await target.then((value) => doSomething(value))
19
19
  }
20
20
 
21
21
  /**
@@ -24,16 +24,16 @@ export const promiseThen = async <V, R>(
24
24
  * @example
25
25
  * ```
26
26
  * // Expect: "fallback"
27
- * const example1 = await promiseCatch(() => "fallback", Promise.reject(new Error("x")))
27
+ * const example1 = await promiseCatch(Promise.reject(new Error("x")), () => "fallback")
28
28
  * // Expect: 3
29
- * const example2 = await promiseCatch(() => 0, Promise.resolve(3))
29
+ * const example2 = await promiseCatch(Promise.resolve(3), () => 0)
30
30
  * ```
31
31
  */
32
32
  export const promiseCatch = async <V, R>(
33
- doSomething: (reason: unknown) => R | PromiseLike<R>,
34
33
  target: Promise<V>,
34
+ doSomething: (reason: unknown) => R | PromiseLike<R>,
35
35
  ): Promise<V | R> => {
36
- return await target.catch(doSomething)
36
+ return await target.catch((reason: unknown) => doSomething(reason))
37
37
  }
38
38
 
39
39
  /**
@@ -43,25 +43,54 @@ export const promiseCatch = async <V, R>(
43
43
  * ```
44
44
  * let cleaned = false
45
45
  * // Expect: 10
46
- * const example1 = await promiseFinally(() => { cleaned = true }, Promise.resolve(10))
46
+ * const example1 = await promiseFinally(Promise.resolve(10), () => { cleaned = true })
47
47
  * // Expect: true
48
48
  * const example2 = cleaned
49
49
  * ```
50
50
  */
51
51
  export const promiseFinally = async <V>(
52
- doSomething: () => void,
53
52
  target: Promise<V>,
53
+ doSomething: () => void,
54
54
  ): Promise<V> => {
55
- return await target.finally(doSomething)
55
+ return await target.finally(() => doSomething())
56
+ }
57
+
58
+ export interface PromiseDeferred<V> {
59
+ promise: Promise<V>
60
+ resolve: (value: V) => void
61
+ reject: (reason: unknown) => void
62
+ }
63
+ /**
64
+ * Create a deferred promise with external resolve and reject functions.
65
+ *
66
+ * @example
67
+ * ```
68
+ * const deferred = promiseDeferred<number>()
69
+ * // Expect: typeof deferred.resolve === "function"
70
+ * const example1 = typeof deferred.resolve
71
+ * deferred.resolve(42)
72
+ * const example2 = await deferred.promise
73
+ * // Expect: 42
74
+ * ```
75
+ */
76
+ export const promiseDeferred = <V>(): PromiseDeferred<V> => {
77
+ let externalResolve!: (value: V) => void
78
+ let externalReject!: (reason: unknown) => void
79
+ const promise = new Promise<V>((resolve, reject) => {
80
+ externalResolve = resolve
81
+ externalReject = reject
82
+ })
83
+ return { promise, resolve: externalResolve, reject: externalReject }
56
84
  }
57
85
 
58
- const INTERNAL_PROMISE_FAIL_RESULT_TYPE: symbol = Symbol("fail")
86
+ const INTERNAL_PROMISE_FAIL_RESULT_TYPE: unique symbol = Symbol("fail")
87
+ type InternalPromiseFailResultType = typeof INTERNAL_PROMISE_FAIL_RESULT_TYPE
59
88
 
60
89
  /**
61
90
  * 表示标准化的 Promise 失败结果。
62
91
  */
63
92
  export interface PromiseFailResult {
64
- __type__: typeof INTERNAL_PROMISE_FAIL_RESULT_TYPE
93
+ __type__: InternalPromiseFailResultType
65
94
  reason: unknown
66
95
  }
67
96
 
@@ -75,14 +104,23 @@ export interface PromiseIndexedFailResult extends PromiseFailResult {
75
104
  /**
76
105
  * 根据拒因构造标准化的 Promise 失败结果。
77
106
  */
78
- export const promiseConstructFailResult = (reason: unknown): PromiseFailResult => {
107
+ export const promiseCreateFailResult = (reason: unknown): PromiseFailResult => {
79
108
  return { __type__: INTERNAL_PROMISE_FAIL_RESULT_TYPE, reason }
80
109
  }
81
110
  /**
82
111
  * 判断目标值是否为标准化的 Promise 失败结果。
83
112
  */
84
- export const isPromiseFailResult = (target: unknown): target is PromiseFailResult => {
85
- return isPlainObject(target) && target["__type__"] === INTERNAL_PROMISE_FAIL_RESULT_TYPE
113
+ export const promiseIsFailResult = (target: unknown): target is PromiseFailResult => {
114
+ if (isPlainObject(target) === false) {
115
+ return false
116
+ }
117
+ if ("__type__" in target === false) {
118
+ return false
119
+ }
120
+ if (target["__type__"] !== INTERNAL_PROMISE_FAIL_RESULT_TYPE) {
121
+ return false
122
+ }
123
+ return true
86
124
  }
87
125
  /**
88
126
  * 用作 `Promise.catch` 的 `onrejected` 回调,并返回标准化失败结果。
@@ -98,9 +136,8 @@ export const promiseFilterSuccessResults = <V>(
98
136
  results: Array<V | PromiseFailResult>,
99
137
  ): V[] => {
100
138
  const filtered = results.filter(result => {
101
- return isPromiseFailResult(result) === false
139
+ return promiseIsFailResult(result) === false
102
140
  })
103
- // oxlint-disable-next-line no-unsafe-type-assertion
104
141
  return filtered as V[]
105
142
  }
106
143
  /**
@@ -112,7 +149,7 @@ export const promiseFilterFailResults = <V>(
112
149
  const filtered = results.reduce<PromiseIndexedFailResult[]>((
113
150
  accumulatedResults, currentResult, index
114
151
  ) => {
115
- if (isPromiseFailResult(currentResult)) {
152
+ if (promiseIsFailResult(currentResult)) {
116
153
  accumulatedResults.push({ ...currentResult, index })
117
154
  }
118
155
  return accumulatedResults
@@ -131,8 +168,10 @@ interface PromiseQueueRestPromiseMakerContext<V, S = unknown> {
131
168
  previousResult: V | PromiseFailResult
132
169
  state: S
133
170
  }
134
- type PromiseQueueFirstPromiseMaker<T, S = unknown> = (context: PromiseQueueFirstPromiseMakerContext<S>) => Promise<T>
135
- type PromiseQueueRestPromiseMaker<T, S = unknown> = (context: PromiseQueueRestPromiseMakerContext<T, S>) => Promise<T>
171
+ type PromiseQueueFirstPromiseMaker<T, S = unknown>
172
+ = (context: PromiseQueueFirstPromiseMakerContext<S>) => Promise<T>
173
+ type PromiseQueueRestPromiseMaker<T, S = unknown>
174
+ = (context: PromiseQueueRestPromiseMakerContext<T, S>) => Promise<T>
136
175
  type PromiseQueuePromiseMakers<T, S = unknown> = [
137
176
  PromiseQueueFirstPromiseMaker<T, S>,
138
177
  ...Array<PromiseQueueRestPromiseMaker<T, S>>
@@ -155,7 +194,7 @@ export interface PromiseQueueOptions {
155
194
  * // Expect: [1, 2]
156
195
  * const example1 = await promiseQueue<number>([
157
196
  * async () => 1,
158
- * async ({ previousResult }) => (isPromiseFailResult(previousResult) ? 0 : previousResult + 1),
197
+ * async ({ previousResult }) => (promiseIsFailResult(previousResult) ? 0 : previousResult + 1),
159
198
  * ])
160
199
  * ```
161
200
  */
@@ -169,7 +208,6 @@ export const promiseQueue = async <T, S = unknown>(
169
208
  let context: PromiseQueueFirstPromiseMakerContext<S> | PromiseQueueRestPromiseMakerContext<T, S> = {
170
209
  index: 0,
171
210
  hasPreviousResult: false,
172
- // oxlint-disable-next-line no-unsafe-type-assertion
173
211
  state: undefined as unknown as S
174
212
  }
175
213
 
@@ -205,18 +243,20 @@ export const promiseQueue = async <T, S = unknown>(
205
243
  }
206
244
 
207
245
  interface PromiseRetryFirstPromiseMakerContext<S = unknown> {
208
- time: number
246
+ index: number
209
247
  hasPreviousResult: false
210
248
  state: S
211
249
  }
212
250
  interface PromiseRetryRestPromiseMakerContext<T, S = unknown> {
213
- time: number
251
+ index: number
214
252
  hasPreviousResult: true
215
253
  previousResult: T | PromiseFailResult
216
254
  state: S
217
255
  }
218
256
  type PromiseRetryPromiseMaker<T, S = unknown> = (
219
- context: PromiseRetryFirstPromiseMakerContext<S> | PromiseRetryRestPromiseMakerContext<T, S>
257
+ context:
258
+ | PromiseRetryFirstPromiseMakerContext<S>
259
+ | PromiseRetryRestPromiseMakerContext<T, S>
220
260
  ) => Promise<T>
221
261
 
222
262
  /**
@@ -228,9 +268,10 @@ export interface PromiseRetryOptions {
228
268
  */
229
269
  breakTime?: number
230
270
  /**
271
+ * `0` 表示仅执行首次尝试,不进行额外重试。
231
272
  * @default Infinity
232
273
  */
233
- maxTryTimes?: number
274
+ maxTryIndex?: number | undefined
234
275
  }
235
276
  /**
236
277
  * Retry while the predicate indicates the result is not acceptable.
@@ -257,34 +298,33 @@ export const promiseRetryWhile = async <T, S = unknown>(
257
298
  ): Promise<T | PromiseFailResult> => {
258
299
  const {
259
300
  breakTime = 0,
260
- maxTryTimes = Infinity,
301
+ maxTryIndex = Infinity,
261
302
  } = options ?? {}
262
303
 
263
- if (maxTryTimes < 1) {
264
- throw new Error("`maxTryTimes` must be greater than 0.")
304
+ if (maxTryIndex < 0) {
305
+ throw new Error("`maxTryIndex` must be greater than or equal to 0.")
265
306
  }
266
307
 
267
- let time = 1
308
+ let index = 0
268
309
  // oxlint-disable-next-line no-accumulating-spread
269
310
  let context: PromiseRetryFirstPromiseMakerContext<S> | PromiseRetryRestPromiseMakerContext<T, S> = {
270
- time,
311
+ index,
271
312
  hasPreviousResult: false,
272
- // oxlint-disable-next-line no-unsafe-type-assertion
273
313
  state: undefined as unknown as S
274
314
  }
275
315
  let result = await promiseMaker(context).catch(promiseCatchToFailResult)
276
316
 
277
317
  while (
278
- (isPromiseFailResult(result) || (await predicate(result)))
279
- && time < maxTryTimes
318
+ (promiseIsFailResult(result) || (await predicate(result)))
319
+ && index < maxTryIndex
280
320
  ) {
281
- time = time + 1
321
+ index = index + 1
282
322
  // oxlint-disable-next-line no-loop-func
283
323
  result = await new Promise<T | PromiseFailResult>((resolve) => {
284
324
  setTimeout(() => {
285
325
  context = {
286
326
  ...context,
287
- time,
327
+ index,
288
328
  hasPreviousResult: true,
289
329
  previousResult: result,
290
330
  }
@@ -315,40 +355,39 @@ export const promiseRetryWhile = async <T, S = unknown>(
315
355
  * @see {@link promiseRetryWhile}
316
356
  */
317
357
  export const promiseRetryUntil = async <T, S = unknown>(
318
- predicate: (value: T, time: number) => boolean | Promise<boolean>,
358
+ predicate: (value: T, index: number) => boolean | Promise<boolean>,
319
359
  promiseMaker: PromiseRetryPromiseMaker<T, S>,
320
360
  options?: PromiseRetryOptions | undefined,
321
361
  ): Promise<T | PromiseFailResult> => {
322
362
  const {
323
363
  breakTime = 0,
324
- maxTryTimes = Infinity,
364
+ maxTryIndex = Infinity,
325
365
  } = options ?? {}
326
366
 
327
- if (maxTryTimes < 1) {
328
- throw new Error("`maxTryTimes` must be greater than 0.")
367
+ if (maxTryIndex < 0) {
368
+ throw new Error("`maxTryIndex` must be greater than or equal to 0.")
329
369
  }
330
370
 
331
- let time = 1
371
+ let index = 0
332
372
  // oxlint-disable-next-line no-accumulating-spread
333
373
  let context: PromiseRetryFirstPromiseMakerContext<S> | PromiseRetryRestPromiseMakerContext<T, S> = {
334
- time,
374
+ index,
335
375
  hasPreviousResult: false,
336
- // oxlint-disable-next-line no-unsafe-type-assertion
337
376
  state: undefined as unknown as S
338
377
  }
339
378
  let result = await promiseMaker(context).catch(promiseCatchToFailResult)
340
379
 
341
380
  while (
342
- (isPromiseFailResult(result) || !(await predicate(result, time)))
343
- && time < maxTryTimes
381
+ (promiseIsFailResult(result) || !(await predicate(result, index)))
382
+ && index < maxTryIndex
344
383
  ) {
345
- time = time + 1
384
+ index = index + 1
346
385
  // oxlint-disable-next-line no-loop-func
347
386
  result = await new Promise<T | PromiseFailResult>((resolve) => {
348
387
  setTimeout(() => {
349
388
  context = {
350
389
  ...context,
351
- time,
390
+ index,
352
391
  hasPreviousResult: true,
353
392
  previousResult: result
354
393
  }
@@ -362,13 +401,14 @@ export const promiseRetryUntil = async <T, S = unknown>(
362
401
 
363
402
  /**
364
403
  * Start periodic asynchronous execution and return a function to stop it.
404
+ * Rejected runs are logged and ignored to avoid unhandled promise rejections.
365
405
  *
366
406
  * @example
367
407
  * ```
368
408
  * const records: number[] = []
369
- * const stop = promiseInterval(10, async (time) => {
370
- * records.push(time)
371
- * return time
409
+ * const stop = promiseInterval(10, async (index) => {
410
+ * records.push(index)
411
+ * return index
372
412
  * })
373
413
  * // Later: stop()
374
414
  * // Expect: typeof stop === "function"
@@ -377,13 +417,15 @@ export const promiseRetryUntil = async <T, S = unknown>(
377
417
  */
378
418
  export const promiseInterval = <T>(
379
419
  interval: number,
380
- promiseMaker: (time: number) => Promise<T>,
420
+ promiseMaker: (index: number) => Promise<T>,
381
421
  ): (() => void) => {
382
- let time = 1
422
+ let index = 0
383
423
 
384
424
  const intervalID = setInterval(() => {
385
- void promiseMaker(time)
386
- time = time + 1
425
+ void promiseMaker(index).catch((error: unknown) => {
426
+ console.error("[promiseInterval] unexpected error occurred:", error)
427
+ })
428
+ index = index + 1
387
429
  }, interval)
388
430
 
389
431
  return () => {
@@ -392,18 +434,20 @@ export const promiseInterval = <T>(
392
434
  }
393
435
 
394
436
  interface PromiseForeverFirstPromiseMakerContext<S = unknown> {
395
- time: number
437
+ index: number
396
438
  hasPreviousResult: false
397
439
  state: S
398
440
  }
399
441
  interface PromiseForeverRestPromiseMakerContext<T, S = unknown> {
400
- time: number
442
+ index: number
401
443
  hasPreviousResult: true
402
444
  previousResult: T | PromiseFailResult
403
445
  state: S
404
446
  }
405
447
  type PromiseForeverPromiseMaker<T, S = unknown> = (
406
- context: PromiseForeverFirstPromiseMakerContext<S> | PromiseForeverRestPromiseMakerContext<T, S>
448
+ context:
449
+ | PromiseForeverFirstPromiseMakerContext<S>
450
+ | PromiseForeverRestPromiseMakerContext<T, S>
407
451
  ) => Promise<T>
408
452
 
409
453
  /**
@@ -414,60 +458,80 @@ export interface PromiseForeverOptions {
414
458
  * @default 0
415
459
  */
416
460
  breakTime?: number | undefined
417
- onRejected?: ((time: number, result: PromiseFailResult) => void) | undefined
461
+ onRejected?: ((index: number, result: PromiseFailResult) => void) | undefined
462
+ }
463
+ export interface PromiseForeverResult {
464
+ index: number
465
+ isStopped: boolean
466
+ stop: () => void
418
467
  }
419
468
  /**
420
469
  * Continuously execute an async maker forever with optional delay and rejection hook.
421
470
  *
471
+ * - 至少会执行一次,`stop()` 可以用来提前停止后续执行。
472
+ * - `index` 从 `0` 开始,表示当前执行轮次。
473
+ * - 如果某一轮结束后已经排入了下一轮的定时器,调用 `stop()` 不会取消那一轮;
474
+ * 它只会阻止后续继续排入新的轮次。
475
+ *
422
476
  * @example
423
477
  * ```
424
478
  * let times = 0
425
- * promiseForever(async () => {
479
+ * const controller = promiseForever(async () => {
426
480
  * times = times + 1
427
481
  * return times
428
482
  * }, { breakTime: 0 })
429
- * // Expect: no return value
430
- * const example1 = promiseForever(async () => 1)
483
+ * controller.stop()
484
+ * // Expect: typeof controller.stop === "function"
485
+ * const example1 = typeof controller.stop
431
486
  * ```
432
487
  */
433
488
  export const promiseForever = <T, S = unknown>(
434
489
  promiseMaker: PromiseForeverPromiseMaker<T, S>,
435
490
  options?: PromiseForeverOptions | undefined,
436
- ): void => {
491
+ ): PromiseForeverResult => {
437
492
  const {
438
493
  breakTime = 0,
439
494
  onRejected,
440
495
  } = options ?? {}
441
496
 
442
- let time = 1
497
+ let index = 0
443
498
  let context: PromiseForeverFirstPromiseMakerContext<S> | PromiseForeverRestPromiseMakerContext<T, S> = {
444
- time,
499
+ index,
445
500
  hasPreviousResult: false,
446
- // oxlint-disable-next-line no-unsafe-type-assertion
447
501
  state: undefined as unknown as S
448
502
  }
503
+ const foreverResult: PromiseForeverResult = {
504
+ index,
505
+ isStopped: false,
506
+ stop: () => {
507
+ foreverResult.isStopped = true
508
+ }
509
+ }
449
510
 
450
511
  const handlePromise = (result: T | PromiseFailResult): void => {
451
- time = time + 1
452
512
  setTimeout(() => {
513
+ index = index + 1
453
514
  context = {
454
515
  ...context,
455
- time,
516
+ index,
456
517
  hasPreviousResult: true,
457
518
  previousResult: result
458
519
  }
520
+ foreverResult.index = index
459
521
  runPromise(context)
460
522
  }, breakTime)
461
523
  }
462
524
  const runPromise = (
463
- context: PromiseForeverFirstPromiseMakerContext<S> | PromiseForeverRestPromiseMakerContext<T, S>
525
+ context:
526
+ | PromiseForeverFirstPromiseMakerContext<S>
527
+ | PromiseForeverRestPromiseMakerContext<T, S>
464
528
  ): void => {
465
529
  void promiseMaker(context)
466
530
  .catch((error: unknown) => {
467
531
  const failedResult = promiseCatchToFailResult(error)
468
532
  if (onRejected !== undefined) {
469
533
  try {
470
- onRejected(time, failedResult)
534
+ onRejected(index, failedResult)
471
535
  }
472
536
  catch {
473
537
  // omit exceptions from execution of `onRejected`
@@ -478,8 +542,14 @@ export const promiseForever = <T, S = unknown>(
478
542
  }
479
543
  return failedResult
480
544
  })
481
- .then(result => handlePromise(result))
545
+ .then(result => {
546
+ if (foreverResult.isStopped === false) {
547
+ handlePromise(result)
548
+ }
549
+ })
482
550
  }
483
551
 
484
552
  runPromise(context)
553
+
554
+ return foreverResult
485
555
  }
@@ -70,7 +70,6 @@ export const decodeCursor = <Model, Key extends keyof Model = keyof Model>(
70
70
  const key = orderBy[index]!.key
71
71
  return { ...acc, [key]: value }
72
72
  }, {})
73
- // oxlint-disable-next-line typescript/no-unsafe-type-assertion
74
73
  return decoded as DecodedCursor<Model, Key>
75
74
  }
76
75
 
@@ -164,7 +163,6 @@ export const buildCursorBasedPagination = <Table extends AnyPgTableWithColumns,
164
163
  type Model = InferModelFromPgTableWithColumns<Table>
165
164
 
166
165
  const decodeCursorResult = decodeCursor({
167
- // oxlint-disable-next-line typescript/no-unsafe-type-assertion
168
166
  modelForType: {} as Model,
169
167
  cursor,
170
168
  orderBy,
@@ -66,7 +66,6 @@ export class ClassEventProxy<Target, Events extends EventsConstraint<Events>> {
66
66
 
67
67
  if (proxySubscriberEntry === undefined) {
68
68
  const managedSubscribers = new Map<Events[K], SubscriberEntry<Events, K>>()
69
- // oxlint-disable-next-line no-unsafe-type-assertion
70
69
  const proxySubscriber = ((...args: Parameters<Events[K]>) => {
71
70
  const managedSubscriberEntries = [...managedSubscribers.values()]
72
71
  for (const managedSubscriberEntry of managedSubscriberEntries) {
@@ -196,7 +195,6 @@ export class ClassEventProxy<Target, Events extends EventsConstraint<Events>> {
196
195
 
197
196
  const events = Object.keys(proxySubscriberEntryMap)
198
197
  for (const event of events) {
199
- // oxlint-disable-next-line typescript/no-unsafe-type-assertion
200
198
  this.removeSubscribersOfEvent(target, event as keyof Events)
201
199
  }
202
200
  this.subscribers.delete(target)
@@ -58,7 +58,6 @@ export class InstanceEventProxy<Events extends EventsConstraint<Events>> {
58
58
 
59
59
  if (proxySubscriberEntry === undefined) {
60
60
  const managedSubscribers = new Map<Events[K], SubscriberEntry<Events, K>>()
61
- // oxlint-disable-next-line no-unsafe-type-assertion
62
61
  const proxySubscriber = ((...args: Parameters<Events[K]>) => {
63
62
  const managedSubscriberEntries = [...managedSubscribers.values()]
64
63
  for (const managedSubscriberEntry of managedSubscriberEntries) {
@@ -168,7 +167,6 @@ export class InstanceEventProxy<Events extends EventsConstraint<Events>> {
168
167
  const events = Object.keys(this.subscribers)
169
168
 
170
169
  for (const event of events) {
171
- // oxlint-disable-next-line typescript/no-unsafe-type-assertion
172
170
  this.removeSubscribersOfEvent(event as keyof Events)
173
171
  }
174
172
  this.subscribers = {}