@c9up/ream 0.2.11 → 0.2.13
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/dist/Application.d.ts +9 -0
- package/dist/Application.d.ts.map +1 -1
- package/dist/Application.js +11 -0
- package/dist/Application.js.map +1 -1
- package/dist/Ignitor.js +4 -4
- package/dist/Ignitor.js.map +1 -1
- package/dist/container/Container.d.ts.map +1 -1
- package/dist/container/Container.js +7 -7
- package/dist/container/Container.js.map +1 -1
- package/dist/errors/ModuleErrors.d.ts +4 -0
- package/dist/errors/ModuleErrors.d.ts.map +1 -1
- package/dist/errors/ModuleErrors.js +12 -8
- package/dist/errors/ModuleErrors.js.map +1 -1
- package/dist/errors/PipelineStageError.d.ts +7 -1
- package/dist/errors/PipelineStageError.d.ts.map +1 -1
- package/dist/errors/PipelineStageError.js +9 -3
- package/dist/errors/PipelineStageError.js.map +1 -1
- package/dist/errors/ReamError.d.ts +14 -4
- package/dist/errors/ReamError.d.ts.map +1 -1
- package/dist/errors/ReamError.js +16 -6
- package/dist/errors/ReamError.js.map +1 -1
- package/dist/graphql/GraphQLProvider.js +1 -1
- package/dist/graphql/GraphQLProvider.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/middleware/Pipeline.js +1 -1
- package/dist/middleware/Pipeline.js.map +1 -1
- package/dist/rpc/RpcProvider.js +1 -1
- package/dist/rpc/RpcProvider.js.map +1 -1
- package/dist/scheduler/Schedule.js +2 -2
- package/dist/scheduler/Schedule.js.map +1 -1
- package/dist/scheduler/ScheduleProvider.d.ts +1 -1
- package/dist/scheduler/ScheduleProvider.js +6 -6
- package/dist/scheduler/ScheduleProvider.js.map +1 -1
- package/dist/scheduler/Scheduler.d.ts +1 -1
- package/dist/scheduler/Scheduler.js +13 -13
- package/dist/scheduler/Scheduler.js.map +1 -1
- package/dist/scheduler/locks/LockBackend.js +1 -1
- package/dist/scheduler/locks/LockBackend.js.map +1 -1
- package/events.darwin-arm64.node +0 -0
- package/events.darwin-x64.node +0 -0
- package/events.linux-arm64-gnu.node +0 -0
- package/events.linux-x64-gnu.node +0 -0
- package/events.win32-x64-msvc.node +0 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/Application.ts +12 -0
- package/src/Ignitor.ts +4 -4
- package/src/container/Container.ts +13 -9
- package/src/errors/ModuleErrors.ts +12 -8
- package/src/errors/PipelineStageError.ts +9 -3
- package/src/errors/ReamError.ts +16 -6
- package/src/graphql/GraphQLProvider.ts +1 -1
- package/src/index.ts +1 -1
- package/src/middleware/Pipeline.ts +1 -1
- package/src/rpc/RpcProvider.ts +1 -1
- package/src/scheduler/Schedule.ts +2 -2
- package/src/scheduler/ScheduleProvider.ts +6 -6
- package/src/scheduler/Scheduler.ts +14 -14
- package/src/scheduler/locks/LockBackend.ts +1 -1
package/src/Ignitor.ts
CHANGED
|
@@ -492,7 +492,7 @@ export class Ignitor {
|
|
|
492
492
|
this.#app.setMode('warmup')
|
|
493
493
|
if (this.#phase !== 'created') {
|
|
494
494
|
throw new ReamError(
|
|
495
|
-
'
|
|
495
|
+
'E_IGNITOR_ALREADY_STARTED',
|
|
496
496
|
`warmUp() called while in phase '${this.#phase}' — an Ignitor boots once`,
|
|
497
497
|
{ hint: 'Create a new Ignitor instance instead of re-warming this one.' },
|
|
498
498
|
)
|
|
@@ -521,7 +521,7 @@ export class Ignitor {
|
|
|
521
521
|
// useful thing to be told than "already started", and it is the reason.
|
|
522
522
|
if (this.#app.getMode() !== 'run') {
|
|
523
523
|
throw new ReamError(
|
|
524
|
-
'
|
|
524
|
+
'E_IGNITOR_WARMUP_ONLY',
|
|
525
525
|
`Cannot start an application assembled in '${this.#app.getMode()}' mode.`,
|
|
526
526
|
{
|
|
527
527
|
hint: 'warmUp() is for inspecting an application; build a fresh Ignitor to run one.',
|
|
@@ -532,7 +532,7 @@ export class Ignitor {
|
|
|
532
532
|
// instantiate + register every reamrc provider a second time.
|
|
533
533
|
if (this.#phase !== 'created') {
|
|
534
534
|
throw new ReamError(
|
|
535
|
-
'
|
|
535
|
+
'E_IGNITOR_ALREADY_STARTED',
|
|
536
536
|
`start() called while in phase '${this.#phase}' — an Ignitor boots once`,
|
|
537
537
|
{ hint: 'Create a new Ignitor instance instead of restarting this one.' },
|
|
538
538
|
)
|
|
@@ -777,7 +777,7 @@ export class Ignitor {
|
|
|
777
777
|
}
|
|
778
778
|
} else if (this.#app.getEnvironment() === 'web' && !this.#config.serverFactory) {
|
|
779
779
|
throw new ReamError(
|
|
780
|
-
'
|
|
780
|
+
'E_IGNITOR_NO_SERVER_FACTORY',
|
|
781
781
|
'httpServer() requires a serverFactory in config',
|
|
782
782
|
{
|
|
783
783
|
hint: 'Example: new Ignitor({ serverFactory: (port, host) => new HyperServer(port, host) })',
|
|
@@ -282,7 +282,7 @@ export class Container {
|
|
|
282
282
|
key = this.#tokenToKey(target)
|
|
283
283
|
if (seenAliases.has(key)) {
|
|
284
284
|
const cycle = [...seenAliases, key].join(' → ')
|
|
285
|
-
throw new ReamError('
|
|
285
|
+
throw new ReamError('E_CIRCULAR_DEPENDENCY', `Circular alias detected: ${cycle}`, {
|
|
286
286
|
hint: 'An alias chain must end at a real binding — remove one of the aliases.',
|
|
287
287
|
context: { chain: cycle },
|
|
288
288
|
})
|
|
@@ -302,7 +302,7 @@ export class Container {
|
|
|
302
302
|
|
|
303
303
|
if (chain.set.has(key)) {
|
|
304
304
|
const cycle = [...chain.stack, key].join(' → ')
|
|
305
|
-
throw new ReamError('
|
|
305
|
+
throw new ReamError('E_CIRCULAR_DEPENDENCY', `Circular dependency detected: ${cycle}`, {
|
|
306
306
|
hint: 'Break the cycle by resolving one dependency lazily inside a method (`await container.make(Dep)`) instead of injecting it in the constructor.',
|
|
307
307
|
context: { chain: cycle },
|
|
308
308
|
})
|
|
@@ -565,7 +565,7 @@ export class Container {
|
|
|
565
565
|
const allKeys = [...this.#bindings.keys(), ...this.#overrides.keys()]
|
|
566
566
|
const suggestion = didYouMean(key, allKeys)
|
|
567
567
|
throw new ReamError(
|
|
568
|
-
'
|
|
568
|
+
'E_CONTAINER_NOT_FOUND',
|
|
569
569
|
`No binding found for '${key}'.${suggestion ? ` ${suggestion}` : ''}`,
|
|
570
570
|
{
|
|
571
571
|
hint: 'Register it with container.singleton() or decorate with @inject().',
|
|
@@ -666,7 +666,7 @@ export class Container {
|
|
|
666
666
|
// instead of `new target()` with undefined deps.
|
|
667
667
|
if (target.length > 0) {
|
|
668
668
|
throw new ReamError(
|
|
669
|
-
'
|
|
669
|
+
'E_CONTAINER_MISSING_METADATA',
|
|
670
670
|
`Cannot auto-construct ${target.name}: it declares ${target.length} constructor parameter(s) but no dependency metadata was found.`,
|
|
671
671
|
{
|
|
672
672
|
hint: 'Enable decorator metadata (swc/tsc emitDecoratorMetadata), annotate constructor params with @Inject(token), or declare static containerInjections.',
|
|
@@ -734,13 +734,13 @@ export class Container {
|
|
|
734
734
|
// is the global registry and round-trips across modules.
|
|
735
735
|
if (Symbol.keyFor(token) === undefined) {
|
|
736
736
|
throw new ReamError(
|
|
737
|
-
'
|
|
737
|
+
'E_CONTAINER_INVALID_TOKEN',
|
|
738
738
|
`Unique Symbol() tokens are not supported — use Symbol.for("${token.description ?? 'name'}") so different modules see the same symbol.`,
|
|
739
739
|
)
|
|
740
740
|
}
|
|
741
741
|
const desc = token.description
|
|
742
742
|
if (desc === undefined) {
|
|
743
|
-
throw new ReamError('
|
|
743
|
+
throw new ReamError('E_CONTAINER_INVALID_TOKEN', 'Symbol tokens must have a description.')
|
|
744
744
|
}
|
|
745
745
|
this.#assertNotReserved(desc, 'symbol description')
|
|
746
746
|
return `Symbol(${desc})`
|
|
@@ -750,9 +750,13 @@ export class Container {
|
|
|
750
750
|
|
|
751
751
|
#assertNotReserved(name: string, kind: 'string' | 'symbol description'): void {
|
|
752
752
|
if (RESERVED_TOKEN_NAMES.has(name)) {
|
|
753
|
-
throw new ReamError(
|
|
754
|
-
|
|
755
|
-
|
|
753
|
+
throw new ReamError(
|
|
754
|
+
'E_CONTAINER_RESERVED_TOKEN',
|
|
755
|
+
`Reserved container token name '${name}'.`,
|
|
756
|
+
{
|
|
757
|
+
hint: `'${name}' would collide with an Object.prototype key. Pick a different ${kind}.`,
|
|
758
|
+
},
|
|
759
|
+
)
|
|
756
760
|
}
|
|
757
761
|
}
|
|
758
762
|
}
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Each module gets its own error class with appropriate defaults.
|
|
5
5
|
*
|
|
6
|
+
* Every code these produce is `E_<MODULE>_<REASON>`: `E_` because that is what
|
|
7
|
+
* every framework code carries, the module name because a bare `E_NOT_FOUND`
|
|
8
|
+
* would not say which half of the framework raised it.
|
|
9
|
+
*
|
|
6
10
|
* @implements FR71
|
|
7
11
|
*/
|
|
8
12
|
|
|
@@ -11,7 +15,7 @@ import { ReamError } from './ReamError.js'
|
|
|
11
15
|
/** Container / IoC errors */
|
|
12
16
|
export class ContainerError extends ReamError {
|
|
13
17
|
constructor(code: string, message: string, options?: ConstructorParameters<typeof ReamError>[2]) {
|
|
14
|
-
super(`
|
|
18
|
+
super(code.startsWith('E_') ? code : `E_CONTAINER_${code}`, message, options)
|
|
15
19
|
this.name = 'ContainerError'
|
|
16
20
|
}
|
|
17
21
|
}
|
|
@@ -19,7 +23,7 @@ export class ContainerError extends ReamError {
|
|
|
19
23
|
/** Router errors */
|
|
20
24
|
export class RouterError extends ReamError {
|
|
21
25
|
constructor(code: string, message: string, options?: ConstructorParameters<typeof ReamError>[2]) {
|
|
22
|
-
super(`
|
|
26
|
+
super(code.startsWith('E_') ? code : `E_ROUTER_${code}`, message, options)
|
|
23
27
|
this.name = 'RouterError'
|
|
24
28
|
}
|
|
25
29
|
}
|
|
@@ -27,7 +31,7 @@ export class RouterError extends ReamError {
|
|
|
27
31
|
/** Pipeline / Middleware errors */
|
|
28
32
|
export class PipelineError extends ReamError {
|
|
29
33
|
constructor(code: string, message: string, options?: ConstructorParameters<typeof ReamError>[2]) {
|
|
30
|
-
super(`
|
|
34
|
+
super(code.startsWith('E_') ? code : `E_PIPELINE_${code}`, message, options)
|
|
31
35
|
this.name = 'PipelineError'
|
|
32
36
|
}
|
|
33
37
|
}
|
|
@@ -35,7 +39,7 @@ export class PipelineError extends ReamError {
|
|
|
35
39
|
/** Atlas ORM errors */
|
|
36
40
|
export class AtlasError extends ReamError {
|
|
37
41
|
constructor(code: string, message: string, options?: ConstructorParameters<typeof ReamError>[2]) {
|
|
38
|
-
super(`
|
|
42
|
+
super(code.startsWith('E_') ? code : `E_ATLAS_${code}`, message, options)
|
|
39
43
|
this.name = 'AtlasError'
|
|
40
44
|
}
|
|
41
45
|
}
|
|
@@ -43,7 +47,7 @@ export class AtlasError extends ReamError {
|
|
|
43
47
|
/** Rune validation errors */
|
|
44
48
|
export class RuneError extends ReamError {
|
|
45
49
|
constructor(code: string, message: string, options?: ConstructorParameters<typeof ReamError>[2]) {
|
|
46
|
-
super(`
|
|
50
|
+
super(code.startsWith('E_') ? code : `E_RUNE_${code}`, message, options)
|
|
47
51
|
this.name = 'RuneError'
|
|
48
52
|
}
|
|
49
53
|
}
|
|
@@ -51,7 +55,7 @@ export class RuneError extends ReamError {
|
|
|
51
55
|
/** Warden auth errors */
|
|
52
56
|
export class WardenError extends ReamError {
|
|
53
57
|
constructor(code: string, message: string, options?: ConstructorParameters<typeof ReamError>[2]) {
|
|
54
|
-
super(`
|
|
58
|
+
super(code.startsWith('E_') ? code : `E_WARDEN_${code}`, message, options)
|
|
55
59
|
this.name = 'WardenError'
|
|
56
60
|
}
|
|
57
61
|
}
|
|
@@ -59,7 +63,7 @@ export class WardenError extends ReamError {
|
|
|
59
63
|
/** Event bus errors */
|
|
60
64
|
export class EventsError extends ReamError {
|
|
61
65
|
constructor(code: string, message: string, options?: ConstructorParameters<typeof ReamError>[2]) {
|
|
62
|
-
super(`
|
|
66
|
+
super(code.startsWith('E_') ? code : `E_EVENTS_${code}`, message, options)
|
|
63
67
|
this.name = 'EventsError'
|
|
64
68
|
}
|
|
65
69
|
}
|
|
@@ -67,7 +71,7 @@ export class EventsError extends ReamError {
|
|
|
67
71
|
/** Forge CLI errors */
|
|
68
72
|
export class ForgeError extends ReamError {
|
|
69
73
|
constructor(code: string, message: string, options?: ConstructorParameters<typeof ReamError>[2]) {
|
|
70
|
-
super(`
|
|
74
|
+
super(code.startsWith('E_') ? code : `E_FORGE_${code}`, message, options)
|
|
71
75
|
this.name = 'ForgeError'
|
|
72
76
|
}
|
|
73
77
|
}
|
|
@@ -6,7 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
import { ReamError } from './ReamError.js'
|
|
8
8
|
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Pipeline stage names in execution order.
|
|
11
|
+
*
|
|
12
|
+
* NOT an error code, despite living beside them — it is the list of stages, and
|
|
13
|
+
* an application imports it by this name. The `E_` prefix belongs to codes
|
|
14
|
+
* alone; a rename here breaks a public import for nothing.
|
|
15
|
+
*/
|
|
10
16
|
export const PIPELINE_STAGES = [
|
|
11
17
|
'Security (Blackhole)',
|
|
12
18
|
'Logging',
|
|
@@ -40,7 +46,7 @@ export function createPipelineError(
|
|
|
40
46
|
const position = `${clampedIndex + 1}/${total}`
|
|
41
47
|
const nextStage = clampedIndex + 1 < total ? PIPELINE_STAGES[clampedIndex + 1] : '(end)'
|
|
42
48
|
|
|
43
|
-
const errorCode = originalError instanceof ReamError ? originalError.code : '
|
|
49
|
+
const errorCode = originalError instanceof ReamError ? originalError.code : 'E_PIPELINE_ERROR'
|
|
44
50
|
|
|
45
51
|
return new ReamError(errorCode, originalError.message, {
|
|
46
52
|
pipelineStage: `${position} (${stageName})`,
|
|
@@ -80,7 +86,7 @@ export function validatePipelineConfig(config: {
|
|
|
80
86
|
if (!registered.has(mw) && !reported.has(mw)) {
|
|
81
87
|
reported.add(mw)
|
|
82
88
|
errors.push(
|
|
83
|
-
new ReamError('
|
|
89
|
+
new ReamError('E_PIPELINE_UNKNOWN_MIDDLEWARE', `Middleware '${mw}' is not registered`, {
|
|
84
90
|
hint: `Register it with middleware.register('${mw}', handler) before using it on routes.`,
|
|
85
91
|
context: {
|
|
86
92
|
middleware: mw,
|
package/src/errors/ReamError.ts
CHANGED
|
@@ -42,10 +42,20 @@ export function isReamError(error: unknown, ...codes: string[]): boolean {
|
|
|
42
42
|
|
|
43
43
|
export class ReamError extends Error {
|
|
44
44
|
/**
|
|
45
|
-
* Error code
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
45
|
+
* Error code: `E_<NAMESPACE>_<REASON>`.
|
|
46
|
+
*
|
|
47
|
+
* `E_` because every framework code carries it, so an application can filter
|
|
48
|
+
* on one rule instead of remembering which convention a package followed.
|
|
49
|
+
* The namespace after it says which package raised the error — without it
|
|
50
|
+
* `E_FORBIDDEN` would mean three different things across the ecosystem and a
|
|
51
|
+
* consumer could not tell them apart (e.g. "E_ATLAS_QUERY_ERROR",
|
|
52
|
+
* "E_CONTAINER_NOT_FOUND", "E_WARDEN_UNKNOWN_POLICY").
|
|
53
|
+
*
|
|
54
|
+
* The exception is an identifier that already exists upstream —
|
|
55
|
+
* `E_UNAUTHORIZED_ACCESS`, `E_INVALID_CREDENTIALS`, `E_VALIDATION_ERROR`.
|
|
56
|
+
* Those are passed through verbatim, so code branching on one sees the same
|
|
57
|
+
* string it would elsewhere, and two packages naming the same upstream
|
|
58
|
+
* failure legitimately share it.
|
|
49
59
|
*/
|
|
50
60
|
readonly code: string
|
|
51
61
|
|
|
@@ -98,7 +108,7 @@ export class ReamError extends Error {
|
|
|
98
108
|
try {
|
|
99
109
|
const parsed = JSON.parse(error.message)
|
|
100
110
|
if (typeof parsed !== 'object' || parsed === null || typeof parsed.code !== 'string') {
|
|
101
|
-
return new ReamError('
|
|
111
|
+
return new ReamError('E_UNKNOWN', error.message)
|
|
102
112
|
}
|
|
103
113
|
return new ReamError(parsed.code, parsed.message ?? error.message, {
|
|
104
114
|
context:
|
|
@@ -110,7 +120,7 @@ export class ReamError extends Error {
|
|
|
110
120
|
})
|
|
111
121
|
} catch {
|
|
112
122
|
// Not a JSON error — wrap as generic
|
|
113
|
-
return new ReamError('
|
|
123
|
+
return new ReamError('E_UNKNOWN', error.message)
|
|
114
124
|
}
|
|
115
125
|
}
|
|
116
126
|
|
|
@@ -53,7 +53,7 @@ export class GraphQLProvider extends Provider {
|
|
|
53
53
|
return
|
|
54
54
|
}
|
|
55
55
|
throw new ReamError(
|
|
56
|
-
'
|
|
56
|
+
'E_GRAPHQL_PROVIDER_ALREADY_REGISTERED',
|
|
57
57
|
"Container token 'graphql' is already bound to a different instance",
|
|
58
58
|
{
|
|
59
59
|
hint: 'Only one GraphQLProvider can own the graphql binding. Remove the duplicate provider from your reamrc.ts.',
|
package/src/index.ts
CHANGED
|
@@ -188,7 +188,7 @@ function composeMiddleware(middleware: MiddlewareFunction[]): MiddlewareFunction
|
|
|
188
188
|
|
|
189
189
|
async function dispatch(i: number): Promise<void> {
|
|
190
190
|
if (i <= index) {
|
|
191
|
-
throw new ReamError('
|
|
191
|
+
throw new ReamError('E_PIPELINE_DOUBLE_NEXT', 'next() called multiple times', {
|
|
192
192
|
hint: 'A middleware called next() more than once. Each middleware should call next() at most once.',
|
|
193
193
|
})
|
|
194
194
|
}
|
package/src/rpc/RpcProvider.ts
CHANGED
|
@@ -56,7 +56,7 @@ export class RpcProvider extends Provider {
|
|
|
56
56
|
// `container.resolve('rpc')` because resolution is now async.
|
|
57
57
|
if (this.#registered) return
|
|
58
58
|
throw new ReamError(
|
|
59
|
-
'
|
|
59
|
+
'E_RPC_PROVIDER_ALREADY_REGISTERED',
|
|
60
60
|
"Container token 'rpc' is already bound to a different instance",
|
|
61
61
|
{
|
|
62
62
|
hint: 'Only one RpcProvider can own the rpc binding. Remove the duplicate provider from your reamrc.ts.',
|
|
@@ -55,7 +55,7 @@ export function Schedule(cronExpr: string): MethodDecorator {
|
|
|
55
55
|
// prototype. We need the real class either way.
|
|
56
56
|
if (typeof target === 'function') {
|
|
57
57
|
throw new ReamError(
|
|
58
|
-
'
|
|
58
|
+
'E_SCHEDULE_INVALID_TARGET',
|
|
59
59
|
`@Schedule cannot be applied to static method '${String(propertyKey)}'`,
|
|
60
60
|
{
|
|
61
61
|
hint: 'Move the scheduled logic to an instance method, or register the task manually via Scheduler.register().',
|
|
@@ -67,7 +67,7 @@ export function Schedule(cronExpr: string): MethodDecorator {
|
|
|
67
67
|
// accessors, and the ticker needs a callable method.
|
|
68
68
|
if (descriptor && typeof descriptor.value !== 'function') {
|
|
69
69
|
throw new ReamError(
|
|
70
|
-
'
|
|
70
|
+
'E_SCHEDULE_INVALID_TARGET',
|
|
71
71
|
`@Schedule cannot be applied to non-method '${String(propertyKey)}' (getters and setters are not supported)`,
|
|
72
72
|
{
|
|
73
73
|
hint: 'Apply @Schedule to a regular method, not to an accessor.',
|
|
@@ -63,7 +63,7 @@ export class ScheduleProvider extends Provider {
|
|
|
63
63
|
* reload, repeated `app.start()` in tests) re-binds the same
|
|
64
64
|
* scheduler without error. A DIFFERENT `ScheduleProvider` trying
|
|
65
65
|
* to claim the `'scheduler'` token throws
|
|
66
|
-
* `
|
|
66
|
+
* `E_SCHEDULE_PROVIDER_ALREADY_REGISTERED` so dueling providers do
|
|
67
67
|
* not silently overwrite each other.
|
|
68
68
|
*/
|
|
69
69
|
override register(): void {
|
|
@@ -74,7 +74,7 @@ export class ScheduleProvider extends Provider {
|
|
|
74
74
|
// `container.resolve('scheduler')` because resolution is now async.
|
|
75
75
|
if (this.#registered) return
|
|
76
76
|
throw new ReamError(
|
|
77
|
-
'
|
|
77
|
+
'E_SCHEDULE_PROVIDER_ALREADY_REGISTERED',
|
|
78
78
|
"Container token 'scheduler' is already bound to a different instance",
|
|
79
79
|
{
|
|
80
80
|
hint: 'Only one ScheduleProvider can own the scheduler binding. Remove the duplicate provider from your reamrc.ts.',
|
|
@@ -133,7 +133,7 @@ export class ScheduleProvider extends Provider {
|
|
|
133
133
|
|
|
134
134
|
if (!target.name) {
|
|
135
135
|
throw new ReamError(
|
|
136
|
-
'
|
|
136
|
+
'E_SCHEDULE_ANONYMOUS_CLASS',
|
|
137
137
|
'Anonymous classes cannot declare @Schedule — task names would collide',
|
|
138
138
|
{
|
|
139
139
|
hint: 'Name the class explicitly (e.g. `export class Jobs { ... }`) so task names are unique and debuggable.',
|
|
@@ -148,7 +148,7 @@ export class ScheduleProvider extends Provider {
|
|
|
148
148
|
// documented contract.
|
|
149
149
|
if (typeof methodName === 'symbol') {
|
|
150
150
|
throw new ReamError(
|
|
151
|
-
'
|
|
151
|
+
'E_SCHEDULE_SYMBOL_METHOD',
|
|
152
152
|
`@Schedule on symbol-keyed method is not supported on class '${target.name}'`,
|
|
153
153
|
{
|
|
154
154
|
hint: 'Use a regular named method so the task can be addressed uniquely.',
|
|
@@ -167,7 +167,7 @@ export class ScheduleProvider extends Provider {
|
|
|
167
167
|
const method = instance[methodName]
|
|
168
168
|
if (typeof method !== 'function') {
|
|
169
169
|
throw new ReamError(
|
|
170
|
-
'
|
|
170
|
+
'E_SCHEDULE_METHOD_NOT_FOUND',
|
|
171
171
|
`Scheduled method ${taskName} is not callable on resolved instance`,
|
|
172
172
|
)
|
|
173
173
|
}
|
|
@@ -178,7 +178,7 @@ export class ScheduleProvider extends Provider {
|
|
|
178
178
|
} catch (cause) {
|
|
179
179
|
const inner = cause as ReamError & { message?: string; hint?: string }
|
|
180
180
|
throw new ReamError(
|
|
181
|
-
'
|
|
181
|
+
'E_SCHEDULE_INVALID_CRON',
|
|
182
182
|
`Failed to register scheduled task '${taskName}': ${inner.message ?? String(cause)}`,
|
|
183
183
|
{
|
|
184
184
|
context: { task: taskName, cronExpr },
|
|
@@ -62,7 +62,7 @@ export interface RunTaskOptions {
|
|
|
62
62
|
/**
|
|
63
63
|
* Cancel the on-demand run after `timeoutMs` milliseconds. On
|
|
64
64
|
* timeout, `runTaskNow` returns `{ outcome: 'failed', error: ... }`
|
|
65
|
-
* with a `
|
|
65
|
+
* with a `E_SCHEDULE_TASK_TIMEOUT` error. The user callback continues
|
|
66
66
|
* running in the background — JavaScript does not support
|
|
67
67
|
* pre-emptive cancellation; the timeout is observational only.
|
|
68
68
|
*/
|
|
@@ -140,18 +140,18 @@ function loadNative(): NativeModule {
|
|
|
140
140
|
/**
|
|
141
141
|
* Convert a thrown native error into a `ReamError`. Prefers parsing the
|
|
142
142
|
* JSON payload shipped by Rust (`ream_napi_core::ReamError::into()`);
|
|
143
|
-
* falls back to wrapping opaque errors under `
|
|
143
|
+
* falls back to wrapping opaque errors under `E_SCHEDULER_NATIVE_ERROR`
|
|
144
144
|
* so scheduler context is never lost.
|
|
145
145
|
*/
|
|
146
146
|
function translateNativeError(e: unknown): ReamError {
|
|
147
147
|
const asError = e as Error
|
|
148
148
|
const parsed = ReamError.fromNapi(asError)
|
|
149
|
-
// `fromNapi` returns an `
|
|
149
|
+
// `fromNapi` returns an `E_UNKNOWN`-coded error when the message is not
|
|
150
150
|
// a JSON payload. Preserve that context under a scheduler-specific
|
|
151
151
|
// code so callers can trace the failure back to this layer.
|
|
152
|
-
if (parsed.code === '
|
|
152
|
+
if (parsed.code === 'E_UNKNOWN') {
|
|
153
153
|
return new ReamError(
|
|
154
|
-
'
|
|
154
|
+
'E_SCHEDULER_NATIVE_ERROR',
|
|
155
155
|
`ream-scheduler NAPI call failed: ${asError.message}`,
|
|
156
156
|
{
|
|
157
157
|
hint: 'This is likely a bug in the scheduler. Please file an issue with the stack trace.',
|
|
@@ -204,7 +204,7 @@ export class Scheduler {
|
|
|
204
204
|
const be = options.lockBackend as Partial<LockBackend>
|
|
205
205
|
if (typeof be.acquire !== 'function' || typeof be.release !== 'function') {
|
|
206
206
|
throw new ReamError(
|
|
207
|
-
'
|
|
207
|
+
'E_SCHEDULE_INVALID_LOCK_BACKEND',
|
|
208
208
|
'Scheduler options.lockBackend must implement acquire(name, ttlMs) and release(name)',
|
|
209
209
|
)
|
|
210
210
|
}
|
|
@@ -212,7 +212,7 @@ export class Scheduler {
|
|
|
212
212
|
if (options.defaultLockTtlMs !== undefined) {
|
|
213
213
|
if (!Number.isFinite(options.defaultLockTtlMs) || options.defaultLockTtlMs <= 0) {
|
|
214
214
|
throw new ReamError(
|
|
215
|
-
'
|
|
215
|
+
'E_SCHEDULE_INVALID_LOCK_TTL',
|
|
216
216
|
`Scheduler options.defaultLockTtlMs must be a finite positive number, got ${options.defaultLockTtlMs}`,
|
|
217
217
|
{
|
|
218
218
|
hint: 'Use a millisecond value greater than zero (typical range 1_000 – 600_000).',
|
|
@@ -224,14 +224,14 @@ export class Scheduler {
|
|
|
224
224
|
const sink = options.eventSink as Partial<ScheduleEventSink>
|
|
225
225
|
if (typeof sink.emit !== 'function') {
|
|
226
226
|
throw new ReamError(
|
|
227
|
-
'
|
|
227
|
+
'E_SCHEDULE_INVALID_EVENT_SINK',
|
|
228
228
|
'Scheduler options.eventSink must implement emit(event)',
|
|
229
229
|
)
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
if (options.errorReporter !== undefined && typeof options.errorReporter !== 'function') {
|
|
233
233
|
throw new ReamError(
|
|
234
|
-
'
|
|
234
|
+
'E_SCHEDULE_INVALID_ERROR_REPORTER',
|
|
235
235
|
'Scheduler options.errorReporter must be a function',
|
|
236
236
|
)
|
|
237
237
|
}
|
|
@@ -376,13 +376,13 @@ export class Scheduler {
|
|
|
376
376
|
): void {
|
|
377
377
|
if (typeof callback !== 'function') {
|
|
378
378
|
throw new ReamError(
|
|
379
|
-
'
|
|
379
|
+
'E_SCHEDULE_INVALID_CALLBACK',
|
|
380
380
|
`Scheduler.register expected a function callback for task '${name}', got ${typeof callback}`,
|
|
381
381
|
)
|
|
382
382
|
}
|
|
383
383
|
if (name === '') {
|
|
384
384
|
throw new ReamError(
|
|
385
|
-
'
|
|
385
|
+
'E_SCHEDULE_INVALID_TASK_NAME',
|
|
386
386
|
'Scheduler.register requires a non-empty task name',
|
|
387
387
|
)
|
|
388
388
|
}
|
|
@@ -451,7 +451,7 @@ export class Scheduler {
|
|
|
451
451
|
// thrown error here is unexpected. Drop the local entry anyway
|
|
452
452
|
// — the doc promises idempotence — but surface any unexpected
|
|
453
453
|
// native failure that is not a missing-task situation.
|
|
454
|
-
if (err.code !== 'UNKNOWN_TASK' && err.code !== '
|
|
454
|
+
if (err.code !== 'UNKNOWN_TASK' && err.code !== 'E_SCHEDULER_NATIVE_ERROR') {
|
|
455
455
|
this.#tasks.delete(name)
|
|
456
456
|
throw err
|
|
457
457
|
}
|
|
@@ -538,7 +538,7 @@ export class Scheduler {
|
|
|
538
538
|
// real task invocations.
|
|
539
539
|
this.#report(
|
|
540
540
|
new ReamError(
|
|
541
|
-
'
|
|
541
|
+
'E_SCHEDULE_TASK_UNKNOWN',
|
|
542
542
|
`runTaskNow called with unknown task name: '${name}'`,
|
|
543
543
|
),
|
|
544
544
|
name,
|
|
@@ -560,7 +560,7 @@ export class Scheduler {
|
|
|
560
560
|
outcome: 'failed',
|
|
561
561
|
durationMs: options.timeoutMs ?? 0,
|
|
562
562
|
error: new ReamError(
|
|
563
|
-
'
|
|
563
|
+
'E_SCHEDULE_TASK_TIMEOUT',
|
|
564
564
|
`Task '${name}' did not complete within ${options.timeoutMs} ms (background execution continues)`,
|
|
565
565
|
),
|
|
566
566
|
})
|
|
@@ -51,7 +51,7 @@ export interface LockBackend {
|
|
|
51
51
|
export function assertValidTtl(ttlMs: number): void {
|
|
52
52
|
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
53
53
|
throw new ReamError(
|
|
54
|
-
'
|
|
54
|
+
'E_SCHEDULE_INVALID_LOCK_TTL',
|
|
55
55
|
`Lock TTL must be a finite positive number, got ${ttlMs}`,
|
|
56
56
|
{
|
|
57
57
|
hint: 'Use a millisecond value greater than zero (typical range 1_000 - 600_000).',
|