@daihum/error-state 0.1.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.
- package/README.md +57 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +63 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# @daihum/error-state
|
|
2
|
+
|
|
3
|
+
Neutral typed error-state mechanism for DAIHUM and downstream hosts.
|
|
4
|
+
|
|
5
|
+
This package owns the runtime-free mechanism:
|
|
6
|
+
|
|
7
|
+
- the frozen `carrel.runtime.error.v0` envelope;
|
|
8
|
+
- local `const` error-kind declarations through `defineErrorKinds`;
|
|
9
|
+
- `ErrorStateOf<typeof kinds>` type derivation;
|
|
10
|
+
- a registry contribution port;
|
|
11
|
+
- an injected schema validation port for typed `details`.
|
|
12
|
+
|
|
13
|
+
It deliberately does not own product feature declarations, registry storage,
|
|
14
|
+
documentation generation, Carrel migration, or schema-library choice.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { defineErrorKinds, type SchemaPort } from "@daihum/error-state";
|
|
18
|
+
|
|
19
|
+
interface MissingDocumentDetails {
|
|
20
|
+
readonly fileUri: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const missingDocumentDetails: SchemaPort<MissingDocumentDetails> = {
|
|
24
|
+
validate(value) {
|
|
25
|
+
if (
|
|
26
|
+
typeof value === "object" &&
|
|
27
|
+
value !== null &&
|
|
28
|
+
typeof (value as { fileUri?: unknown }).fileUri === "string"
|
|
29
|
+
) {
|
|
30
|
+
return { ok: true, value: { fileUri: (value as { fileUri: string }).fileUri } };
|
|
31
|
+
}
|
|
32
|
+
return { ok: false, error: { message: "fileUri is required" } };
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const readerErrors = defineErrorKinds("reader.pdf", {
|
|
37
|
+
missingDocument: {
|
|
38
|
+
retryable: false,
|
|
39
|
+
reason: "missing-document",
|
|
40
|
+
message: "The PDF document is not available.",
|
|
41
|
+
details: missingDocumentDetails,
|
|
42
|
+
},
|
|
43
|
+
} as const);
|
|
44
|
+
|
|
45
|
+
const state = readerErrors.fail("missingDocument", {
|
|
46
|
+
commandId: "reader.open",
|
|
47
|
+
details: { fileUri: "file:///paper.pdf" },
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The returned state uses the same frozen wire schema:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
state.schema === "carrel.runtime.error.v0";
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
No runtime dependencies are used.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export declare const ERROR_STATE_SCHEMA_V0: "carrel.runtime.error.v0";
|
|
2
|
+
export interface ErrorStateV0<Tag extends string = string, Details = unknown> {
|
|
3
|
+
readonly schema: typeof ERROR_STATE_SCHEMA_V0;
|
|
4
|
+
readonly tag: Tag;
|
|
5
|
+
readonly retryable: boolean;
|
|
6
|
+
readonly reason?: string;
|
|
7
|
+
readonly message?: string;
|
|
8
|
+
readonly commandId?: string;
|
|
9
|
+
readonly runId?: string;
|
|
10
|
+
readonly stepId?: string;
|
|
11
|
+
readonly correlationId?: string;
|
|
12
|
+
readonly details?: Details;
|
|
13
|
+
}
|
|
14
|
+
export interface SchemaPortErrorV0 {
|
|
15
|
+
readonly message?: string;
|
|
16
|
+
readonly issues?: readonly unknown[];
|
|
17
|
+
readonly cause?: unknown;
|
|
18
|
+
}
|
|
19
|
+
export type SchemaPortResultV0<T> = {
|
|
20
|
+
readonly ok: true;
|
|
21
|
+
readonly value: T;
|
|
22
|
+
} | {
|
|
23
|
+
readonly ok: false;
|
|
24
|
+
readonly error: SchemaPortErrorV0;
|
|
25
|
+
};
|
|
26
|
+
export interface SchemaPort<T> {
|
|
27
|
+
validate(value: unknown): SchemaPortResultV0<T>;
|
|
28
|
+
}
|
|
29
|
+
export interface ErrorKindSpecV0<Details = never> {
|
|
30
|
+
readonly retryable: boolean;
|
|
31
|
+
readonly reason?: string;
|
|
32
|
+
readonly message?: string;
|
|
33
|
+
readonly details?: SchemaPort<Details>;
|
|
34
|
+
}
|
|
35
|
+
export interface ErrorKindDefinitionV0<Tag extends string = string, Details = never, Retryable extends boolean = boolean> {
|
|
36
|
+
readonly tag: Tag;
|
|
37
|
+
readonly retryable: Retryable;
|
|
38
|
+
readonly reason?: string;
|
|
39
|
+
readonly message?: string;
|
|
40
|
+
readonly details?: SchemaPort<Details>;
|
|
41
|
+
}
|
|
42
|
+
export type ErrorKindSpecMapV0 = Readonly<Record<string, ErrorKindSpecV0<unknown>>>;
|
|
43
|
+
export type ErrorKindDefinitionsV0 = Readonly<Record<string, ErrorKindDefinitionV0<string, unknown, boolean>>>;
|
|
44
|
+
export interface ErrorKindRegistryPortV0<Definitions extends ErrorKindDefinitionsV0 = ErrorKindDefinitionsV0> {
|
|
45
|
+
register(namespace: string, spec: Definitions): void | Promise<void>;
|
|
46
|
+
lookup(tag: string): ErrorKindDefinitionV0 | undefined | Promise<ErrorKindDefinitionV0 | undefined>;
|
|
47
|
+
}
|
|
48
|
+
export type ErrorKindDetailsOf<Spec> = Spec extends {
|
|
49
|
+
readonly details: SchemaPort<infer Details>;
|
|
50
|
+
} ? Details : never;
|
|
51
|
+
export type ErrorKindRetryableOf<Spec> = Spec extends {
|
|
52
|
+
readonly retryable: infer Retryable extends boolean;
|
|
53
|
+
} ? Retryable : boolean;
|
|
54
|
+
export type ErrorKindTag<Namespace extends string, Key extends string> = `${Namespace}.${Key}`;
|
|
55
|
+
export type ErrorKindDefinitionsFor<Namespace extends string, Spec extends ErrorKindSpecMapV0> = Readonly<{
|
|
56
|
+
[Key in keyof Spec & string]: ErrorKindDefinitionV0<ErrorKindTag<Namespace, Key>, ErrorKindDetailsOf<Spec[Key]>, ErrorKindRetryableOf<Spec[Key]>>;
|
|
57
|
+
}>;
|
|
58
|
+
export type ErrorStateForKind<Namespace extends string, Key extends string, Spec> = ErrorStateV0<ErrorKindTag<Namespace, Key>, ErrorKindDetailsOf<Spec>> & {
|
|
59
|
+
readonly retryable: ErrorKindRetryableOf<Spec>;
|
|
60
|
+
};
|
|
61
|
+
export type ErrorStateOf<Kinds extends ErrorKindDefinitionsV0> = {
|
|
62
|
+
[Key in keyof Kinds]: Kinds[Key] extends ErrorKindDefinitionV0<infer Tag, infer Details, infer Retryable> ? ErrorStateV0<Tag, Details> & {
|
|
63
|
+
readonly retryable: Retryable;
|
|
64
|
+
} : never;
|
|
65
|
+
}[keyof Kinds];
|
|
66
|
+
export type ErrorStateFailInputV0<Details = never> = {
|
|
67
|
+
readonly reason?: string;
|
|
68
|
+
readonly message?: string;
|
|
69
|
+
readonly commandId?: string;
|
|
70
|
+
readonly runId?: string;
|
|
71
|
+
readonly stepId?: string;
|
|
72
|
+
readonly correlationId?: string;
|
|
73
|
+
} & ([Details] extends [never] ? {
|
|
74
|
+
readonly details?: never;
|
|
75
|
+
} : {
|
|
76
|
+
readonly details?: Details;
|
|
77
|
+
});
|
|
78
|
+
export type ErrorStateBuildResultV0<State extends ErrorStateV0> = {
|
|
79
|
+
readonly ok: true;
|
|
80
|
+
readonly value: State;
|
|
81
|
+
} | {
|
|
82
|
+
readonly ok: false;
|
|
83
|
+
readonly error: SchemaPortErrorV0;
|
|
84
|
+
readonly state: State;
|
|
85
|
+
};
|
|
86
|
+
export interface DefinedErrorKindsV0<Namespace extends string, Spec extends ErrorKindSpecMapV0> {
|
|
87
|
+
readonly namespace: Namespace;
|
|
88
|
+
readonly kinds: ErrorKindDefinitionsFor<Namespace, Spec>;
|
|
89
|
+
fail<Key extends keyof Spec & string>(tag: Key, input?: ErrorStateFailInputV0<ErrorKindDetailsOf<Spec[Key]>>): ErrorStateForKind<Namespace, Key, Spec[Key]>;
|
|
90
|
+
tryFail<Key extends keyof Spec & string>(tag: Key, input?: ErrorStateFailInputV0<ErrorKindDetailsOf<Spec[Key]>>): ErrorStateBuildResultV0<ErrorStateForKind<Namespace, Key, Spec[Key]>>;
|
|
91
|
+
contribute(registry: ErrorKindRegistryPortV0<ErrorKindDefinitionsFor<Namespace, Spec>>): void | Promise<void>;
|
|
92
|
+
}
|
|
93
|
+
export declare function defineErrorKinds<const Namespace extends string, const Spec extends ErrorKindSpecMapV0>(namespace: Namespace, constSpec: Spec): DefinedErrorKindsV0<Namespace, Spec>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export const ERROR_STATE_SCHEMA_V0 = "carrel.runtime.error.v0";
|
|
2
|
+
function buildInvalidDetailsMessage(tag, error) {
|
|
3
|
+
return error.message ?? `Invalid error-state details for ${tag}.`;
|
|
4
|
+
}
|
|
5
|
+
function hasOwn(value, key) {
|
|
6
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
7
|
+
}
|
|
8
|
+
export function defineErrorKinds(namespace, constSpec) {
|
|
9
|
+
const kinds = Object.freeze(Object.fromEntries(Object.entries(constSpec).map(([key, spec]) => [
|
|
10
|
+
key,
|
|
11
|
+
Object.freeze({
|
|
12
|
+
tag: `${namespace}.${key}`,
|
|
13
|
+
retryable: spec.retryable,
|
|
14
|
+
...(spec.reason !== undefined ? { reason: spec.reason } : {}),
|
|
15
|
+
...(spec.message !== undefined ? { message: spec.message } : {}),
|
|
16
|
+
...(spec.details !== undefined ? { details: spec.details } : {}),
|
|
17
|
+
}),
|
|
18
|
+
])));
|
|
19
|
+
function tryFail(key, input = {}) {
|
|
20
|
+
const kind = kinds[key];
|
|
21
|
+
const reason = input.reason ?? kind.reason;
|
|
22
|
+
const message = input.message ?? kind.message;
|
|
23
|
+
const stateBase = {
|
|
24
|
+
schema: ERROR_STATE_SCHEMA_V0,
|
|
25
|
+
tag: kind.tag,
|
|
26
|
+
retryable: kind.retryable,
|
|
27
|
+
...(reason !== undefined ? { reason } : {}),
|
|
28
|
+
...(message !== undefined ? { message } : {}),
|
|
29
|
+
...(input.commandId !== undefined ? { commandId: input.commandId } : {}),
|
|
30
|
+
...(input.runId !== undefined ? { runId: input.runId } : {}),
|
|
31
|
+
...(input.stepId !== undefined ? { stepId: input.stepId } : {}),
|
|
32
|
+
...(input.correlationId !== undefined ? { correlationId: input.correlationId } : {}),
|
|
33
|
+
};
|
|
34
|
+
if (!hasOwn(input, "details"))
|
|
35
|
+
return { ok: true, value: stateBase };
|
|
36
|
+
const rawDetails = input.details;
|
|
37
|
+
if (kind.details !== undefined) {
|
|
38
|
+
const result = kind.details.validate(rawDetails);
|
|
39
|
+
if (!result.ok) {
|
|
40
|
+
const state = {
|
|
41
|
+
...stateBase,
|
|
42
|
+
reason: "invalid-error-details",
|
|
43
|
+
message: buildInvalidDetailsMessage(kind.tag, result.error),
|
|
44
|
+
};
|
|
45
|
+
return { ok: false, error: result.error, state };
|
|
46
|
+
}
|
|
47
|
+
return { ok: true, value: { ...stateBase, details: result.value } };
|
|
48
|
+
}
|
|
49
|
+
return { ok: true, value: { ...stateBase, details: rawDetails } };
|
|
50
|
+
}
|
|
51
|
+
return Object.freeze({
|
|
52
|
+
namespace,
|
|
53
|
+
kinds,
|
|
54
|
+
fail(key, input) {
|
|
55
|
+
const result = tryFail(key, input);
|
|
56
|
+
return result.ok ? result.value : result.state;
|
|
57
|
+
},
|
|
58
|
+
tryFail,
|
|
59
|
+
contribute(registry) {
|
|
60
|
+
return registry.register(namespace, kinds);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@daihum/error-state",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Neutral typed error-state mechanism: frozen carrel.runtime.error.v0 envelope, const-spec kind definitions, registry contribution port, and injected details validation. Runtime-free and zero dependency.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"daihum",
|
|
22
|
+
"error-state",
|
|
23
|
+
"typed-errors",
|
|
24
|
+
"runtime-contract"
|
|
25
|
+
],
|
|
26
|
+
"author": "DAIHUM",
|
|
27
|
+
"dependencies": {},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"typescript": "~5.6.2",
|
|
30
|
+
"vitest": "^2.1.8"
|
|
31
|
+
},
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "restricted"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc",
|
|
38
|
+
"clean": "rm -rf dist",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"typecheck": "tsc --noEmit"
|
|
41
|
+
}
|
|
42
|
+
}
|