gracefulerrors 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 +199 -0
- package/dist/adapters/sonner.cjs +126 -0
- package/dist/adapters/sonner.cjs.map +1 -0
- package/dist/adapters/sonner.d.cts +6 -0
- package/dist/adapters/sonner.d.ts +6 -0
- package/dist/adapters/sonner.js +121 -0
- package/dist/adapters/sonner.js.map +1 -0
- package/dist/index.cjs +486 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +481 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.cjs +169 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.cts +14 -0
- package/dist/internal.d.ts +14 -0
- package/dist/internal.js +166 -0
- package/dist/internal.js.map +1 -0
- package/dist/react.cjs +71 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +35 -0
- package/dist/react.d.ts +35 -0
- package/dist/react.js +61 -0
- package/dist/react.js.map +1 -0
- package/dist/types-CsPmpcbL.d.cts +149 -0
- package/dist/types-CsPmpcbL.d.ts +149 -0
- package/package.json +80 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
type SystemErrorCode = 'GRACEFULERRORS_UNKNOWN' | 'GRACEFULERRORS_UNHANDLED';
|
|
2
|
+
interface AppError<TCode extends string = string, TField extends string = string> {
|
|
3
|
+
code: TCode | SystemErrorCode;
|
|
4
|
+
status?: number;
|
|
5
|
+
message?: string;
|
|
6
|
+
context?: {
|
|
7
|
+
field?: TField;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
};
|
|
10
|
+
raw?: unknown;
|
|
11
|
+
}
|
|
12
|
+
type SuppressionDecision = {
|
|
13
|
+
suppress: true;
|
|
14
|
+
reason: string;
|
|
15
|
+
};
|
|
16
|
+
type TransformResult<TCode extends string = string, TField extends string = string> = AppError<TCode, TField> | SuppressionDecision | null;
|
|
17
|
+
type TransformContext = {
|
|
18
|
+
raw: unknown;
|
|
19
|
+
};
|
|
20
|
+
type UIOptions = {
|
|
21
|
+
ui: 'toast';
|
|
22
|
+
uiOptions?: {
|
|
23
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'top-center' | 'bottom-center';
|
|
24
|
+
severity?: 'info' | 'warning' | 'error' | 'success';
|
|
25
|
+
icon?: string;
|
|
26
|
+
duration?: number;
|
|
27
|
+
};
|
|
28
|
+
} | {
|
|
29
|
+
ui: 'modal';
|
|
30
|
+
uiOptions?: {
|
|
31
|
+
dismissible?: boolean;
|
|
32
|
+
size?: 'sm' | 'md' | 'lg';
|
|
33
|
+
};
|
|
34
|
+
} | {
|
|
35
|
+
ui: 'inline';
|
|
36
|
+
uiOptions?: Record<string, unknown>;
|
|
37
|
+
} | {
|
|
38
|
+
ui: 'silent';
|
|
39
|
+
uiOptions?: never;
|
|
40
|
+
};
|
|
41
|
+
type UIAction = 'toast' | 'modal' | 'inline' | 'silent';
|
|
42
|
+
type ErrorRegistryEntry<TCode extends string = string> = {
|
|
43
|
+
message?: string | ((error: AppError<TCode>) => string);
|
|
44
|
+
ttl?: number;
|
|
45
|
+
} & UIOptions;
|
|
46
|
+
type ErrorRegistryEntryFull<TCode extends string = string> = ErrorRegistryEntry<TCode>;
|
|
47
|
+
type ErrorRegistry<TCode extends string = string> = {
|
|
48
|
+
[K in TCode]?: ErrorRegistryEntry<TCode>;
|
|
49
|
+
};
|
|
50
|
+
type RenderIntent<TCode extends string = string> = {
|
|
51
|
+
ui: UIAction;
|
|
52
|
+
error: AppError<TCode>;
|
|
53
|
+
entry: ErrorRegistryEntryFull<TCode>;
|
|
54
|
+
};
|
|
55
|
+
interface RendererAdapter {
|
|
56
|
+
render<TCode extends string = string>(intent: RenderIntent<TCode>, lifecycle: {
|
|
57
|
+
onDismiss?: () => void;
|
|
58
|
+
}): void;
|
|
59
|
+
clear(code: string): void;
|
|
60
|
+
clearAll(): void;
|
|
61
|
+
}
|
|
62
|
+
interface ErrorEngine<TCode extends string = string> {
|
|
63
|
+
handle(raw: unknown): HandleResult<TCode>;
|
|
64
|
+
clear(code: TCode): void;
|
|
65
|
+
clearAll(): void;
|
|
66
|
+
subscribe(listener: StateListener<TCode>): () => void;
|
|
67
|
+
}
|
|
68
|
+
interface HandleResult<TCode extends string = string> {
|
|
69
|
+
handled: boolean;
|
|
70
|
+
error: AppError<TCode>;
|
|
71
|
+
uiAction: UIAction | null;
|
|
72
|
+
}
|
|
73
|
+
type RoutingStrategy<TCode extends string = string, TField extends string = string> = (error: AppError<TCode, TField>, registryEntry: ErrorRegistryEntryFull<TCode> | undefined, context: {
|
|
74
|
+
activeCount: number;
|
|
75
|
+
queueLength: number;
|
|
76
|
+
}) => UIAction | null;
|
|
77
|
+
type ErrorState = 'ACTIVE' | 'QUEUED' | 'EXPIRED' | 'DROPPED';
|
|
78
|
+
interface ErrorSlot<TCode extends string = string> {
|
|
79
|
+
error: AppError<TCode>;
|
|
80
|
+
state: ErrorState;
|
|
81
|
+
fingerprint: string;
|
|
82
|
+
expiresAt?: number;
|
|
83
|
+
}
|
|
84
|
+
type StateListener<TCode extends string = string> = (event: {
|
|
85
|
+
type: 'ERROR_ADDED';
|
|
86
|
+
error: AppError<TCode>;
|
|
87
|
+
action: UIAction;
|
|
88
|
+
} | {
|
|
89
|
+
type: 'ERROR_CLEARED';
|
|
90
|
+
code: TCode;
|
|
91
|
+
} | {
|
|
92
|
+
type: 'ALL_CLEARED';
|
|
93
|
+
}) => void;
|
|
94
|
+
interface ErrorStateManager<TCode extends string = string> {
|
|
95
|
+
canHandle(fingerprint: string): boolean;
|
|
96
|
+
enqueue(slot: ErrorSlot<TCode> & {
|
|
97
|
+
_pendingAction?: UIAction;
|
|
98
|
+
ttl?: number;
|
|
99
|
+
}): 'active' | 'queued' | 'rejected';
|
|
100
|
+
release(code: TCode): void;
|
|
101
|
+
getActiveSlots(): ErrorSlot<TCode>[];
|
|
102
|
+
getQueueLength(): number;
|
|
103
|
+
clearAll(): void;
|
|
104
|
+
subscribe(listener: StateListener<TCode>): () => void;
|
|
105
|
+
}
|
|
106
|
+
interface UIRouter<TCode extends string = string, TField extends string = string> {
|
|
107
|
+
route(error: AppError<TCode, TField>, registry: ErrorRegistry<TCode>, config: Pick<ErrorEngineConfig<TCode, TField>, 'fallback' | 'requireRegistry' | 'allowFallback' | 'routingStrategy'> & {
|
|
108
|
+
routingContext?: {
|
|
109
|
+
activeCount: number;
|
|
110
|
+
queueLength: number;
|
|
111
|
+
};
|
|
112
|
+
}): UIAction | null;
|
|
113
|
+
}
|
|
114
|
+
type Normalizer<TCode extends string = string, TField extends string = string> = (raw: unknown, current: AppError<TCode, TField> | null) => AppError<TCode, TField> | null;
|
|
115
|
+
interface ErrorEngineConfig<TCode extends string = string, TField extends string = string> {
|
|
116
|
+
registry: ErrorRegistry<TCode>;
|
|
117
|
+
requireRegistry?: boolean;
|
|
118
|
+
allowFallback?: boolean;
|
|
119
|
+
fallback?: {
|
|
120
|
+
ui: 'toast' | 'modal' | 'silent';
|
|
121
|
+
message?: string;
|
|
122
|
+
};
|
|
123
|
+
normalizers?: Normalizer<TCode, TField>[];
|
|
124
|
+
normalizer?: Normalizer<TCode, TField>;
|
|
125
|
+
fingerprint?: (error: AppError<TCode, TField>) => string;
|
|
126
|
+
dedupeWindow?: number;
|
|
127
|
+
maxConcurrent?: number;
|
|
128
|
+
maxQueue?: number;
|
|
129
|
+
aggregation?: boolean | {
|
|
130
|
+
enabled: boolean;
|
|
131
|
+
window?: number;
|
|
132
|
+
};
|
|
133
|
+
routingStrategy?: RoutingStrategy<TCode, TField>;
|
|
134
|
+
transform?: (error: AppError<TCode, TField>, context: TransformContext) => TransformResult<TCode, TField>;
|
|
135
|
+
onError?: (raw: unknown) => void;
|
|
136
|
+
onNormalized?: (error: AppError<TCode, TField>) => void;
|
|
137
|
+
onSuppressed?: (error: AppError<TCode, TField>, reason: string) => void;
|
|
138
|
+
onRouted?: (error: AppError<TCode, TField>, action: UIAction) => void;
|
|
139
|
+
onFallback?: (error: AppError<TCode, TField>) => void;
|
|
140
|
+
onErrorAsync?: (error: AppError<TCode, TField>) => Promise<void>;
|
|
141
|
+
onDropped?: (error: AppError<TCode, TField>, reason: 'dedupe' | 'ttl_expired' | 'queue_overflow') => void;
|
|
142
|
+
debug?: boolean | {
|
|
143
|
+
trace?: boolean;
|
|
144
|
+
};
|
|
145
|
+
modalDismissTimeoutMs?: number;
|
|
146
|
+
renderer?: RendererAdapter;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export type { AppError as A, ErrorEngineConfig as E, HandleResult as H, Normalizer as N, RenderIntent as R, StateListener as S, TransformContext as T, UIAction as U, ErrorEngine as a, ErrorRegistry as b, ErrorRegistryEntry as c, ErrorRegistryEntryFull as d, RendererAdapter as e, RoutingStrategy as f, TransformResult as g, UIOptions as h, ErrorStateManager as i, UIRouter as j, ErrorSlot as k, ErrorState as l };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
type SystemErrorCode = 'GRACEFULERRORS_UNKNOWN' | 'GRACEFULERRORS_UNHANDLED';
|
|
2
|
+
interface AppError<TCode extends string = string, TField extends string = string> {
|
|
3
|
+
code: TCode | SystemErrorCode;
|
|
4
|
+
status?: number;
|
|
5
|
+
message?: string;
|
|
6
|
+
context?: {
|
|
7
|
+
field?: TField;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
};
|
|
10
|
+
raw?: unknown;
|
|
11
|
+
}
|
|
12
|
+
type SuppressionDecision = {
|
|
13
|
+
suppress: true;
|
|
14
|
+
reason: string;
|
|
15
|
+
};
|
|
16
|
+
type TransformResult<TCode extends string = string, TField extends string = string> = AppError<TCode, TField> | SuppressionDecision | null;
|
|
17
|
+
type TransformContext = {
|
|
18
|
+
raw: unknown;
|
|
19
|
+
};
|
|
20
|
+
type UIOptions = {
|
|
21
|
+
ui: 'toast';
|
|
22
|
+
uiOptions?: {
|
|
23
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'top-center' | 'bottom-center';
|
|
24
|
+
severity?: 'info' | 'warning' | 'error' | 'success';
|
|
25
|
+
icon?: string;
|
|
26
|
+
duration?: number;
|
|
27
|
+
};
|
|
28
|
+
} | {
|
|
29
|
+
ui: 'modal';
|
|
30
|
+
uiOptions?: {
|
|
31
|
+
dismissible?: boolean;
|
|
32
|
+
size?: 'sm' | 'md' | 'lg';
|
|
33
|
+
};
|
|
34
|
+
} | {
|
|
35
|
+
ui: 'inline';
|
|
36
|
+
uiOptions?: Record<string, unknown>;
|
|
37
|
+
} | {
|
|
38
|
+
ui: 'silent';
|
|
39
|
+
uiOptions?: never;
|
|
40
|
+
};
|
|
41
|
+
type UIAction = 'toast' | 'modal' | 'inline' | 'silent';
|
|
42
|
+
type ErrorRegistryEntry<TCode extends string = string> = {
|
|
43
|
+
message?: string | ((error: AppError<TCode>) => string);
|
|
44
|
+
ttl?: number;
|
|
45
|
+
} & UIOptions;
|
|
46
|
+
type ErrorRegistryEntryFull<TCode extends string = string> = ErrorRegistryEntry<TCode>;
|
|
47
|
+
type ErrorRegistry<TCode extends string = string> = {
|
|
48
|
+
[K in TCode]?: ErrorRegistryEntry<TCode>;
|
|
49
|
+
};
|
|
50
|
+
type RenderIntent<TCode extends string = string> = {
|
|
51
|
+
ui: UIAction;
|
|
52
|
+
error: AppError<TCode>;
|
|
53
|
+
entry: ErrorRegistryEntryFull<TCode>;
|
|
54
|
+
};
|
|
55
|
+
interface RendererAdapter {
|
|
56
|
+
render<TCode extends string = string>(intent: RenderIntent<TCode>, lifecycle: {
|
|
57
|
+
onDismiss?: () => void;
|
|
58
|
+
}): void;
|
|
59
|
+
clear(code: string): void;
|
|
60
|
+
clearAll(): void;
|
|
61
|
+
}
|
|
62
|
+
interface ErrorEngine<TCode extends string = string> {
|
|
63
|
+
handle(raw: unknown): HandleResult<TCode>;
|
|
64
|
+
clear(code: TCode): void;
|
|
65
|
+
clearAll(): void;
|
|
66
|
+
subscribe(listener: StateListener<TCode>): () => void;
|
|
67
|
+
}
|
|
68
|
+
interface HandleResult<TCode extends string = string> {
|
|
69
|
+
handled: boolean;
|
|
70
|
+
error: AppError<TCode>;
|
|
71
|
+
uiAction: UIAction | null;
|
|
72
|
+
}
|
|
73
|
+
type RoutingStrategy<TCode extends string = string, TField extends string = string> = (error: AppError<TCode, TField>, registryEntry: ErrorRegistryEntryFull<TCode> | undefined, context: {
|
|
74
|
+
activeCount: number;
|
|
75
|
+
queueLength: number;
|
|
76
|
+
}) => UIAction | null;
|
|
77
|
+
type ErrorState = 'ACTIVE' | 'QUEUED' | 'EXPIRED' | 'DROPPED';
|
|
78
|
+
interface ErrorSlot<TCode extends string = string> {
|
|
79
|
+
error: AppError<TCode>;
|
|
80
|
+
state: ErrorState;
|
|
81
|
+
fingerprint: string;
|
|
82
|
+
expiresAt?: number;
|
|
83
|
+
}
|
|
84
|
+
type StateListener<TCode extends string = string> = (event: {
|
|
85
|
+
type: 'ERROR_ADDED';
|
|
86
|
+
error: AppError<TCode>;
|
|
87
|
+
action: UIAction;
|
|
88
|
+
} | {
|
|
89
|
+
type: 'ERROR_CLEARED';
|
|
90
|
+
code: TCode;
|
|
91
|
+
} | {
|
|
92
|
+
type: 'ALL_CLEARED';
|
|
93
|
+
}) => void;
|
|
94
|
+
interface ErrorStateManager<TCode extends string = string> {
|
|
95
|
+
canHandle(fingerprint: string): boolean;
|
|
96
|
+
enqueue(slot: ErrorSlot<TCode> & {
|
|
97
|
+
_pendingAction?: UIAction;
|
|
98
|
+
ttl?: number;
|
|
99
|
+
}): 'active' | 'queued' | 'rejected';
|
|
100
|
+
release(code: TCode): void;
|
|
101
|
+
getActiveSlots(): ErrorSlot<TCode>[];
|
|
102
|
+
getQueueLength(): number;
|
|
103
|
+
clearAll(): void;
|
|
104
|
+
subscribe(listener: StateListener<TCode>): () => void;
|
|
105
|
+
}
|
|
106
|
+
interface UIRouter<TCode extends string = string, TField extends string = string> {
|
|
107
|
+
route(error: AppError<TCode, TField>, registry: ErrorRegistry<TCode>, config: Pick<ErrorEngineConfig<TCode, TField>, 'fallback' | 'requireRegistry' | 'allowFallback' | 'routingStrategy'> & {
|
|
108
|
+
routingContext?: {
|
|
109
|
+
activeCount: number;
|
|
110
|
+
queueLength: number;
|
|
111
|
+
};
|
|
112
|
+
}): UIAction | null;
|
|
113
|
+
}
|
|
114
|
+
type Normalizer<TCode extends string = string, TField extends string = string> = (raw: unknown, current: AppError<TCode, TField> | null) => AppError<TCode, TField> | null;
|
|
115
|
+
interface ErrorEngineConfig<TCode extends string = string, TField extends string = string> {
|
|
116
|
+
registry: ErrorRegistry<TCode>;
|
|
117
|
+
requireRegistry?: boolean;
|
|
118
|
+
allowFallback?: boolean;
|
|
119
|
+
fallback?: {
|
|
120
|
+
ui: 'toast' | 'modal' | 'silent';
|
|
121
|
+
message?: string;
|
|
122
|
+
};
|
|
123
|
+
normalizers?: Normalizer<TCode, TField>[];
|
|
124
|
+
normalizer?: Normalizer<TCode, TField>;
|
|
125
|
+
fingerprint?: (error: AppError<TCode, TField>) => string;
|
|
126
|
+
dedupeWindow?: number;
|
|
127
|
+
maxConcurrent?: number;
|
|
128
|
+
maxQueue?: number;
|
|
129
|
+
aggregation?: boolean | {
|
|
130
|
+
enabled: boolean;
|
|
131
|
+
window?: number;
|
|
132
|
+
};
|
|
133
|
+
routingStrategy?: RoutingStrategy<TCode, TField>;
|
|
134
|
+
transform?: (error: AppError<TCode, TField>, context: TransformContext) => TransformResult<TCode, TField>;
|
|
135
|
+
onError?: (raw: unknown) => void;
|
|
136
|
+
onNormalized?: (error: AppError<TCode, TField>) => void;
|
|
137
|
+
onSuppressed?: (error: AppError<TCode, TField>, reason: string) => void;
|
|
138
|
+
onRouted?: (error: AppError<TCode, TField>, action: UIAction) => void;
|
|
139
|
+
onFallback?: (error: AppError<TCode, TField>) => void;
|
|
140
|
+
onErrorAsync?: (error: AppError<TCode, TField>) => Promise<void>;
|
|
141
|
+
onDropped?: (error: AppError<TCode, TField>, reason: 'dedupe' | 'ttl_expired' | 'queue_overflow') => void;
|
|
142
|
+
debug?: boolean | {
|
|
143
|
+
trace?: boolean;
|
|
144
|
+
};
|
|
145
|
+
modalDismissTimeoutMs?: number;
|
|
146
|
+
renderer?: RendererAdapter;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export type { AppError as A, ErrorEngineConfig as E, HandleResult as H, Normalizer as N, RenderIntent as R, StateListener as S, TransformContext as T, UIAction as U, ErrorEngine as a, ErrorRegistry as b, ErrorRegistryEntry as c, ErrorRegistryEntryFull as d, RendererAdapter as e, RoutingStrategy as f, TransformResult as g, UIOptions as h, ErrorStateManager as i, UIRouter as j, ErrorSlot as k, ErrorState as l };
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gracefulerrors",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Transform technical errors into consistent, user-friendly experiences",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
|
+
},
|
|
13
|
+
"./react": {
|
|
14
|
+
"types": "./dist/react.d.ts",
|
|
15
|
+
"import": "./dist/react.mjs",
|
|
16
|
+
"require": "./dist/react.cjs"
|
|
17
|
+
},
|
|
18
|
+
"./sonner": {
|
|
19
|
+
"types": "./dist/adapters/sonner.d.ts",
|
|
20
|
+
"import": "./dist/adapters/sonner.mjs",
|
|
21
|
+
"require": "./dist/adapters/sonner.cjs"
|
|
22
|
+
},
|
|
23
|
+
"./internal": {
|
|
24
|
+
"types": "./dist/internal.d.ts",
|
|
25
|
+
"import": "./dist/internal.mjs",
|
|
26
|
+
"require": "./dist/internal.cjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"main": "./dist/index.cjs",
|
|
30
|
+
"module": "./dist/index.mjs",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup",
|
|
37
|
+
"dev": "tsup --watch",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:watch": "vitest",
|
|
40
|
+
"pretest:types": "npm run build",
|
|
41
|
+
"test:types": "tsd",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"lint": "eslint src --ext .ts,.tsx"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"react": ">=18",
|
|
47
|
+
"react-dom": ">=18",
|
|
48
|
+
"sonner": ">=1"
|
|
49
|
+
},
|
|
50
|
+
"peerDependenciesMeta": {
|
|
51
|
+
"sonner": {
|
|
52
|
+
"optional": true
|
|
53
|
+
},
|
|
54
|
+
"react": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"react-dom": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"tsd": {
|
|
62
|
+
"directory": "src/__tests__"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@testing-library/react": "^14.0.0",
|
|
66
|
+
"@testing-library/user-event": "^14.0.0",
|
|
67
|
+
"@types/node": "^25.5.2",
|
|
68
|
+
"@types/react": "^18.0.0",
|
|
69
|
+
"@types/react-dom": "^18.0.0",
|
|
70
|
+
"@vitest/coverage-v8": "^1.0.0",
|
|
71
|
+
"jsdom": "^24.0.0",
|
|
72
|
+
"react": "^18.0.0",
|
|
73
|
+
"react-dom": "^18.0.0",
|
|
74
|
+
"sonner": "^1.0.0",
|
|
75
|
+
"tsd": "^0.31.0",
|
|
76
|
+
"tsup": "^8.0.0",
|
|
77
|
+
"typescript": "^5.0.0",
|
|
78
|
+
"vitest": "^1.0.0"
|
|
79
|
+
}
|
|
80
|
+
}
|