@fnndsc/fond 0.0.0-stage → 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 FNNDSC / BCH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,102 @@
1
- # Temporary Holding Version
1
+ # @fnndsc/fond
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The neutral base of the mise stack: the small, generic pieces that the engine (`@fnndsc/brasa`), the session host (`@fnndsc/calypso`) and the ChRIS packages all stand on, and that have nothing to do with ChRIS.
4
+
5
+ The name is from the kitchen, like the rest of the stack: *fonds de cuisine* are the base stocks a kitchen cooks everything else from.
6
+
7
+ ```
8
+ npm install @fnndsc/fond
9
+ ```
10
+
11
+ ## Why it exists
12
+
13
+ mise is being made backend-neutral: one engine, one session host, one wire and one surface frame, with ChRIS (CUBE) as the first backend among possibly several (see [docs/backend-neutral.adoc](https://github.com/FNNDSC/mise/blob/main/docs/backend-neutral.adoc)). For that to be true, the generic pieces cannot live inside a ChRIS package. Before fond, `Result` and the error stack lived in `@fnndsc/cumin`, whose root also loads CUBE's API client, so any layer that wanted an `Ok()` loaded CUBE with it.
14
+
15
+ fond is where those pieces go instead. Its one rule: **it depends on nothing in `@fnndsc`**. CI holds it to that (`npm run lint:fond`), so a layer that is not about CUBE can use fond without loading CUBE.
16
+
17
+ ## What's in it
18
+
19
+ | Export | What it is |
20
+ | --- | --- |
21
+ | `Result<T>`, `Ok`, `Err`, `result_isOk`, `result_isErr` | An explicit success-or-failure value. A function returns `Result<T>` rather than `T \| null`, and TypeScript will not let a caller read `.value` until it has checked `.ok`. |
22
+ | `errorStack` | The process-wide message stack that failures are reported on, with context-isolated scopes and checkpoints. |
23
+ | `errorStack_configure`, `errorStack_getAllOfType`, `StackMessage` | Configuration, a convenience reader, and the message type. |
24
+
25
+ The steps after this one add the VFS framework (mount interface and dispatcher) and the output sink and surface interfaces the engine and the session host share.
26
+
27
+ ## Using `Result`
28
+
29
+ A failure carries no payload of its own: the reason goes on the error stack, where a surface can show it, and the `Result` says only that it failed.
30
+
31
+ ```typescript
32
+ import { Ok, Err, errorStack, type Result } from '@fnndsc/fond';
33
+
34
+ async function config_read(path: string): Promise<Result<Config>> {
35
+ const text: string | null = await file_read(path);
36
+ if (text === null) {
37
+ errorStack.stack_push('error', `No configuration at ${path}.`);
38
+ return Err();
39
+ }
40
+ return Ok(config_parse(text));
41
+ }
42
+
43
+ const config: Result<Config> = await config_read('~/.config/app.yml');
44
+ if (!config.ok) {
45
+ // config.value does not exist here; TypeScript says so.
46
+ return;
47
+ }
48
+ config_apply(config.value);
49
+ ```
50
+
51
+ ## Using the error stack
52
+
53
+ `errorStack` is a single instance for the whole process. Each message is stamped with the name of the function that pushed it:
54
+
55
+ ```typescript
56
+ errorStack.stack_push('error', 'CUBE refused the login');
57
+ errorStack.stack_pop();
58
+ // { type: 'error', message: '[login_run ] | CUBE refused the login' }
59
+ ```
60
+
61
+ Reading and clearing:
62
+
63
+ | Method | Does |
64
+ | --- | --- |
65
+ | `stack_push(type, message)` | Pushes an `error` or a `warning`. |
66
+ | `stack_pop()`, `stack_getAll()` | Takes the newest message; lists them all. |
67
+ | `stack_search(text)`, `messagesOfType_search(type, text)` | Finds messages containing some text. |
68
+ | `allOfType_get(type)`, `messages_has()`, `messagesOfType_has(type)` | Reads by type; asks whether anything is there. |
69
+ | `stack_clear()`, `type_clear(type)` | Empties the stack, or one type. |
70
+
71
+ Two features keep concurrent work from mixing up its errors:
72
+
73
+ * **Scopes.** Work run inside `errorStack.scope_run(fn)` pushes to and pops from its own isolated stack, carried by Node's `AsyncLocalStorage` through every `await` inside it. Background work (cache warming, refreshes) runs in a scope so its failures never land in a foreground command's report.
74
+ * **Checkpoints.** A command calls `checkpoint_mark()` before it works and `checkpoint_drain(mark)` after, and gets exactly the messages pushed in between.
75
+
76
+ ```typescript
77
+ const mark: number = errorStack.checkpoint_mark();
78
+ const answer: Result<Listing> = await listing_fetch(path);
79
+ const reasons: StackMessage[] = errorStack.checkpoint_drain(mark);
80
+ ```
81
+
82
+ `errorStack_configure({ functionNamePadWidth })` sets how wide the function-name stamp is padded.
83
+
84
+ ## One instance, whoever loads it
85
+
86
+ The error stack only works if the whole process shares one. fond is built as CommonJS, so packages that `require` it (cumin) and packages that `import` it (salsa, brasa, calypso) load the same module and so the same stack. `@fnndsc/cumin` re-exports fond's `Result` and `errorStack` rather than keeping copies, so code that still imports them from cumin shares that stack too. If you bundle code that uses fond, keep it to one copy.
87
+
88
+ ## Where it sits
89
+
90
+ ```
91
+ brasa (engine) calypso (session host) cumin, salsa (the ChRIS backend)
92
+ │ │ │
93
+ └─────────────────────┼──────────────────────────┘
94
+ ▼
95
+ ┌───────────────┐
96
+ │ fond │ depends on nothing in @fnndsc
97
+ └───────────────┘
98
+ ```
99
+
100
+ ## License
101
+
102
+ MIT. Part of [mise](https://github.com/FNNDSC/mise).
@@ -0,0 +1,163 @@
1
+ /**
2
+ * @file Process-wide error/message stack for deferred, structured reporting.
3
+ *
4
+ * The stack is a process singleton, but it is *async-context aware*: work run
5
+ * inside {@link ErrorStack.scope_run} pushes to and pops from its own isolated
6
+ * stack rather than the shared one. This lets fire-and-forget background work
7
+ * (cache warming, background refreshes) keep its error traffic off the shared
8
+ * stack, so a foreground command that checkpoints the stack and drains it
9
+ * afterward (see {@link ErrorStack.checkpoint_mark} /
10
+ * {@link ErrorStack.checkpoint_drain}) captures only its own messages and is
11
+ * never corrupted by a background push landing in its drain window.
12
+ *
13
+ * @module
14
+ */
15
+ type MessageType = "error" | "warning";
16
+ /**
17
+ * Represents a single message in the error stack.
18
+ */
19
+ export interface StackMessage {
20
+ type: MessageType;
21
+ message: string;
22
+ }
23
+ /**
24
+ * Options for configuring the ErrorStack.
25
+ */
26
+ interface ErrorStackOptions {
27
+ functionNamePadWidth?: number;
28
+ }
29
+ /**
30
+ * Singleton class for managing a stack of error and warning messages.
31
+ * Provides methods to push, pop, search, and filter messages.
32
+ */
33
+ declare class ErrorStack {
34
+ private static instance;
35
+ private stack;
36
+ private functionNamePadWidth;
37
+ /**
38
+ * Holds the isolated stack for work running inside {@link scope_run}. When
39
+ * a store is present, all operations target it instead of the shared stack.
40
+ */
41
+ private scopeStorage;
42
+ private constructor();
43
+ /**
44
+ * Returns the stack the current async context should operate on: the
45
+ * isolated stack when inside a {@link scope_run}, otherwise the shared one.
46
+ *
47
+ * @returns The active stack array.
48
+ */
49
+ private stack_active;
50
+ /**
51
+ * Runs a function with an isolated error stack that the shared stack — and
52
+ * any foreground command draining it — never sees. Fire-and-forget
53
+ * background work wraps its body in this so its error traffic cannot land
54
+ * in a concurrent foreground command's drain window. The isolation follows
55
+ * the whole async causal chain started inside `fn`.
56
+ *
57
+ * @param fn - The work to run against a fresh, isolated stack.
58
+ * @returns Whatever `fn` returns.
59
+ */
60
+ scope_run<T>(fn: () => T): T;
61
+ /**
62
+ * Marks the current depth of the active stack, for a later
63
+ * {@link checkpoint_drain}.
64
+ *
65
+ * @returns An opaque checkpoint (the current stack depth).
66
+ */
67
+ checkpoint_mark(): number;
68
+ /**
69
+ * Removes and returns every message pushed above a checkpoint, so a command
70
+ * boundary can drain exactly the messages produced since it marked the
71
+ * stack. Messages the command's own code already popped are simply absent.
72
+ *
73
+ * @param checkpoint - A checkpoint from {@link checkpoint_mark}.
74
+ * @returns The drained messages, oldest first.
75
+ */
76
+ checkpoint_drain(checkpoint: number): StackMessage[];
77
+ /**
78
+ * Get the singleton instance of the ErrorStack.
79
+ *
80
+ * @param options - Optional configuration options for the stack.
81
+ * @returns The singleton ErrorStack instance.
82
+ */
83
+ static instance_get(options?: ErrorStackOptions): ErrorStack;
84
+ /**
85
+ * Push a new message onto the stack.
86
+ *
87
+ * Captures the calling function name and formats the message.
88
+ *
89
+ * @param type - The type of message ("error" or "warning").
90
+ * @param message - The message string.
91
+ */
92
+ stack_push(type: MessageType, message: string): void;
93
+ /**
94
+ * Pop the last message from the stack.
95
+ *
96
+ * @returns The last message object or undefined if the stack is empty.
97
+ */
98
+ stack_pop(): StackMessage | undefined;
99
+ /**
100
+ * Search the stack for messages containing a substring.
101
+ *
102
+ * @param substring - The string to search for (case-insensitive).
103
+ * @returns An array of formatted strings matching the search.
104
+ */
105
+ stack_getAll(): StackMessage[];
106
+ stack_search(substring: string): string[];
107
+ /**
108
+ * Get all messages of a specific type.
109
+ *
110
+ * @param type - The message type to filter by.
111
+ * @returns An array of message strings.
112
+ */
113
+ allOfType_get(type: MessageType): string[];
114
+ /**
115
+ * Search for messages of a specific type containing a substring.
116
+ *
117
+ * @param type - The message type to filter by.
118
+ * @param substring - The string to search for (case-insensitive).
119
+ * @returns An array of matching message strings.
120
+ */
121
+ messagesOfType_search(type: MessageType, substring: string): string[];
122
+ /**
123
+ * Clear all messages from the stack.
124
+ */
125
+ stack_clear(): void;
126
+ /**
127
+ * Clear all messages of a specific type from the stack.
128
+ *
129
+ * @param type - The message type to clear.
130
+ */
131
+ type_clear(type: MessageType): void;
132
+ /**
133
+ * Check if the stack contains any messages.
134
+ *
135
+ * @returns True if the stack is not empty, false otherwise.
136
+ */
137
+ messages_has(): boolean;
138
+ /**
139
+ * Check if the stack contains any messages of a specific type.
140
+ *
141
+ * @param type - The message type to check for.
142
+ * @returns True if messages of the given type exist, false otherwise.
143
+ */
144
+ messagesOfType_has(type: MessageType): boolean;
145
+ }
146
+ /**
147
+ * The global singleton instance of the ErrorStack.
148
+ */
149
+ export declare const errorStack: ErrorStack;
150
+ /**
151
+ * Reconfigures the error stack with new options.
152
+ *
153
+ * @param options - The new configuration options.
154
+ */
155
+ export declare function errorStack_configure(options: ErrorStackOptions): void;
156
+ /**
157
+ * Get all messages of a specific type from the global error stack.
158
+ *
159
+ * @param type - The message type to filter by.
160
+ * @returns An array of message strings.
161
+ */
162
+ export declare function errorStack_getAllOfType(type: MessageType): string[];
163
+ export {};
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ /**
3
+ * @file Process-wide error/message stack for deferred, structured reporting.
4
+ *
5
+ * The stack is a process singleton, but it is *async-context aware*: work run
6
+ * inside {@link ErrorStack.scope_run} pushes to and pops from its own isolated
7
+ * stack rather than the shared one. This lets fire-and-forget background work
8
+ * (cache warming, background refreshes) keep its error traffic off the shared
9
+ * stack, so a foreground command that checkpoints the stack and drains it
10
+ * afterward (see {@link ErrorStack.checkpoint_mark} /
11
+ * {@link ErrorStack.checkpoint_drain}) captures only its own messages and is
12
+ * never corrupted by a background push landing in its drain window.
13
+ *
14
+ * @module
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.errorStack = void 0;
18
+ exports.errorStack_configure = errorStack_configure;
19
+ exports.errorStack_getAllOfType = errorStack_getAllOfType;
20
+ const node_async_hooks_1 = require("node:async_hooks");
21
+ /**
22
+ * Pads a string to the right with spaces to a specified length.
23
+ * If the string is longer than the length, it is truncated with ellipses.
24
+ *
25
+ * @param str - The string to pad.
26
+ * @param length - The target length.
27
+ * @returns The padded or truncated string.
28
+ */
29
+ function str_padRight(str, length) {
30
+ if (str.length > length) {
31
+ return str.substring(0, length - 3) + "...";
32
+ }
33
+ return str.padEnd(length);
34
+ }
35
+ /**
36
+ * Retrieves the name of the function that called the current function.
37
+ * Uses the error stack trace to parse the caller's name.
38
+ *
39
+ * @returns The name of the calling function, or a default string if not found.
40
+ */
41
+ function functionName_getCurrent() {
42
+ const error = new Error();
43
+ const stack = error.stack?.split("\n");
44
+ if (!stack || stack.length < 4) {
45
+ return "Unknown Function";
46
+ }
47
+ const callerLine = stack[3];
48
+ const functionNameMatch = callerLine.match(/at\s+([\w\.<>]+)\s*\(/);
49
+ return functionNameMatch ? functionNameMatch[1] : "Anonymous Function";
50
+ }
51
+ /**
52
+ * Singleton class for managing a stack of error and warning messages.
53
+ * Provides methods to push, pop, search, and filter messages.
54
+ */
55
+ class ErrorStack {
56
+ constructor(options = {}) {
57
+ this.stack = [];
58
+ /**
59
+ * Holds the isolated stack for work running inside {@link scope_run}. When
60
+ * a store is present, all operations target it instead of the shared stack.
61
+ */
62
+ this.scopeStorage = new node_async_hooks_1.AsyncLocalStorage();
63
+ this.functionNamePadWidth = options.functionNamePadWidth || 45; // Default to 45 if not specified
64
+ }
65
+ /**
66
+ * Returns the stack the current async context should operate on: the
67
+ * isolated stack when inside a {@link scope_run}, otherwise the shared one.
68
+ *
69
+ * @returns The active stack array.
70
+ */
71
+ stack_active() {
72
+ return this.scopeStorage.getStore() ?? this.stack;
73
+ }
74
+ /**
75
+ * Runs a function with an isolated error stack that the shared stack — and
76
+ * any foreground command draining it — never sees. Fire-and-forget
77
+ * background work wraps its body in this so its error traffic cannot land
78
+ * in a concurrent foreground command's drain window. The isolation follows
79
+ * the whole async causal chain started inside `fn`.
80
+ *
81
+ * @param fn - The work to run against a fresh, isolated stack.
82
+ * @returns Whatever `fn` returns.
83
+ */
84
+ scope_run(fn) {
85
+ return this.scopeStorage.run([], fn);
86
+ }
87
+ /**
88
+ * Marks the current depth of the active stack, for a later
89
+ * {@link checkpoint_drain}.
90
+ *
91
+ * @returns An opaque checkpoint (the current stack depth).
92
+ */
93
+ checkpoint_mark() {
94
+ return this.stack_active().length;
95
+ }
96
+ /**
97
+ * Removes and returns every message pushed above a checkpoint, so a command
98
+ * boundary can drain exactly the messages produced since it marked the
99
+ * stack. Messages the command's own code already popped are simply absent.
100
+ *
101
+ * @param checkpoint - A checkpoint from {@link checkpoint_mark}.
102
+ * @returns The drained messages, oldest first.
103
+ */
104
+ checkpoint_drain(checkpoint) {
105
+ return this.stack_active().splice(checkpoint);
106
+ }
107
+ /**
108
+ * Get the singleton instance of the ErrorStack.
109
+ *
110
+ * @param options - Optional configuration options for the stack.
111
+ * @returns The singleton ErrorStack instance.
112
+ */
113
+ static instance_get(options) {
114
+ if (!ErrorStack.instance) {
115
+ ErrorStack.instance = new ErrorStack(options);
116
+ }
117
+ return ErrorStack.instance;
118
+ }
119
+ /**
120
+ * Push a new message onto the stack.
121
+ *
122
+ * Captures the calling function name and formats the message.
123
+ *
124
+ * @param type - The type of message ("error" or "warning").
125
+ * @param message - The message string.
126
+ */
127
+ stack_push(type, message) {
128
+ const functionName = functionName_getCurrent();
129
+ const paddedFunctionName = str_padRight(functionName, this.functionNamePadWidth);
130
+ const enhancedMessage = `[${paddedFunctionName}] | ${message}`;
131
+ this.stack_active().push({ type, message: enhancedMessage });
132
+ }
133
+ /**
134
+ * Pop the last message from the stack.
135
+ *
136
+ * @returns The last message object or undefined if the stack is empty.
137
+ */
138
+ stack_pop() {
139
+ return this.stack_active().pop();
140
+ }
141
+ /**
142
+ * Search the stack for messages containing a substring.
143
+ *
144
+ * @param substring - The string to search for (case-insensitive).
145
+ * @returns An array of formatted strings matching the search.
146
+ */
147
+ stack_getAll() {
148
+ return [...this.stack_active()];
149
+ }
150
+ stack_search(substring) {
151
+ return this.stack_active()
152
+ .filter((item) => item.message.toLowerCase().includes(substring.toLowerCase()))
153
+ .map((item) => `${item.type}: ${item.message}`);
154
+ }
155
+ /**
156
+ * Get all messages of a specific type.
157
+ *
158
+ * @param type - The message type to filter by.
159
+ * @returns An array of message strings.
160
+ */
161
+ allOfType_get(type) {
162
+ return this.stack_active()
163
+ .filter((item) => item.type === type)
164
+ .map((item) => item.message);
165
+ }
166
+ /**
167
+ * Search for messages of a specific type containing a substring.
168
+ *
169
+ * @param type - The message type to filter by.
170
+ * @param substring - The string to search for (case-insensitive).
171
+ * @returns An array of matching message strings.
172
+ */
173
+ messagesOfType_search(type, substring) {
174
+ return this.stack_active()
175
+ .filter((item) => item.type === type &&
176
+ item.message.toLowerCase().includes(substring.toLowerCase()))
177
+ .map((item) => item.message);
178
+ }
179
+ /**
180
+ * Clear all messages from the stack.
181
+ */
182
+ stack_clear() {
183
+ const active = this.stack_active();
184
+ active.length = 0;
185
+ }
186
+ /**
187
+ * Clear all messages of a specific type from the stack.
188
+ *
189
+ * @param type - The message type to clear.
190
+ */
191
+ type_clear(type) {
192
+ const active = this.stack_active();
193
+ const kept = active.filter((item) => item.type !== type);
194
+ active.length = 0;
195
+ active.push(...kept);
196
+ }
197
+ /**
198
+ * Check if the stack contains any messages.
199
+ *
200
+ * @returns True if the stack is not empty, false otherwise.
201
+ */
202
+ messages_has() {
203
+ return this.stack_active().length > 0;
204
+ }
205
+ /**
206
+ * Check if the stack contains any messages of a specific type.
207
+ *
208
+ * @param type - The message type to check for.
209
+ * @returns True if messages of the given type exist, false otherwise.
210
+ */
211
+ messagesOfType_has(type) {
212
+ return this.stack_active().some((item) => item.type === type);
213
+ }
214
+ }
215
+ /**
216
+ * The global singleton instance of the ErrorStack.
217
+ */
218
+ exports.errorStack = ErrorStack.instance_get({ functionNamePadWidth: 40 });
219
+ /**
220
+ * Reconfigures the error stack with new options.
221
+ *
222
+ * @param options - The new configuration options.
223
+ */
224
+ function errorStack_configure(options) {
225
+ ErrorStack.instance_get(options);
226
+ }
227
+ /**
228
+ * Get all messages of a specific type from the global error stack.
229
+ *
230
+ * @param type - The message type to filter by.
231
+ * @returns An array of message strings.
232
+ */
233
+ function errorStack_getAllOfType(type) {
234
+ return exports.errorStack.allOfType_get(type);
235
+ }
236
+ //# sourceMappingURL=errorStack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errorStack.js","sourceRoot":"","sources":["../src/errorStack.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @file fond: the neutral base under mise's engine and session host.
3
+ *
4
+ * What is generic and once lived in a ChRIS package moves here, so a layer
5
+ * that is not about CUBE can use it without loading CUBE's client. fond
6
+ * depends on nothing in `@fnndsc` (law of docs/backend-neutral.adoc, held by
7
+ * `npm run lint:fond`).
8
+ *
9
+ * @module
10
+ */
11
+ export * from './result.js';
12
+ export * from './errorStack.js';
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ /**
18
+ * @file fond: the neutral base under mise's engine and session host.
19
+ *
20
+ * What is generic and once lived in a ChRIS package moves here, so a layer
21
+ * that is not about CUBE can use it without loading CUBE's client. fond
22
+ * depends on nothing in `@fnndsc` (law of docs/backend-neutral.adoc, held by
23
+ * `npm run lint:fond`).
24
+ *
25
+ * @module
26
+ */
27
+ __exportStar(require("./result.js"), exports);
28
+ __exportStar(require("./errorStack.js"), exports);
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;;;;;;;;GASG;AACH,8CAA4B;AAC5B,kDAAgC"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @file Result Type for Explicit Error Handling
3
+ *
4
+ * Provides a type-safe Result type that forces explicit error checking.
5
+ * Used in conjunction with errorStack for error accumulation and UI display.
6
+ *
7
+ * Pattern:
8
+ * - Functions return Result<T> instead of T | null
9
+ * - Errors are pushed to errorStack (for UI display)
10
+ * - Result type forces caller to check .ok before accessing .value
11
+ * - TypeScript prevents accessing .value when ok === false
12
+ *
13
+ * @module
14
+ */
15
+ /**
16
+ * Result type representing success or failure.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * async function getData(): Promise<Result<Data>> {
21
+ * if (error) {
22
+ * errorStack.stack_push("error", "Failed to get data");
23
+ * return Err();
24
+ * }
25
+ * return Ok(data);
26
+ * }
27
+ *
28
+ * const result = await getData();
29
+ * if (!result.ok) {
30
+ * // Can't access result.value here - TypeScript error
31
+ * console.log(errorStack.stack_search("data"));
32
+ * return;
33
+ * }
34
+ * // TypeScript knows result.ok === true, so .value exists
35
+ * useData(result.value);
36
+ * ```
37
+ */
38
+ export type Result<T> = {
39
+ ok: true;
40
+ value: T;
41
+ } | {
42
+ ok: false;
43
+ };
44
+ /**
45
+ * Creates a successful Result containing a value.
46
+ *
47
+ * @param value - The success value to wrap
48
+ * @returns A Result indicating success
49
+ */
50
+ export declare function Ok<T>(value: T): Result<T>;
51
+ /**
52
+ * Creates a failed Result.
53
+ *
54
+ * Note: Error details should be pushed to errorStack before calling this.
55
+ *
56
+ * @returns A Result indicating failure
57
+ */
58
+ export declare function Err<T>(): Result<T>;
59
+ /**
60
+ * Type guard to check if a Result is successful.
61
+ *
62
+ * @param result - The Result to check.
63
+ * @returns True if the Result contains a value.
64
+ */
65
+ export declare function result_isOk<T>(result: Result<T>): result is {
66
+ ok: true;
67
+ value: T;
68
+ };
69
+ /**
70
+ * Type guard to check if a Result is a failure.
71
+ *
72
+ * @param result - The Result to check.
73
+ * @returns True if the Result is a failure.
74
+ */
75
+ export declare function result_isErr<T>(result: Result<T>): result is {
76
+ ok: false;
77
+ };
package/dist/result.js ADDED
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ /**
3
+ * @file Result Type for Explicit Error Handling
4
+ *
5
+ * Provides a type-safe Result type that forces explicit error checking.
6
+ * Used in conjunction with errorStack for error accumulation and UI display.
7
+ *
8
+ * Pattern:
9
+ * - Functions return Result<T> instead of T | null
10
+ * - Errors are pushed to errorStack (for UI display)
11
+ * - Result type forces caller to check .ok before accessing .value
12
+ * - TypeScript prevents accessing .value when ok === false
13
+ *
14
+ * @module
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.Ok = Ok;
18
+ exports.Err = Err;
19
+ exports.result_isOk = result_isOk;
20
+ exports.result_isErr = result_isErr;
21
+ /**
22
+ * Creates a successful Result containing a value.
23
+ *
24
+ * @param value - The success value to wrap
25
+ * @returns A Result indicating success
26
+ */
27
+ function Ok(value) {
28
+ return { ok: true, value };
29
+ }
30
+ /**
31
+ * Creates a failed Result.
32
+ *
33
+ * Note: Error details should be pushed to errorStack before calling this.
34
+ *
35
+ * @returns A Result indicating failure
36
+ */
37
+ function Err() {
38
+ return { ok: false };
39
+ }
40
+ /**
41
+ * Type guard to check if a Result is successful.
42
+ *
43
+ * @param result - The Result to check.
44
+ * @returns True if the Result contains a value.
45
+ */
46
+ function result_isOk(result) {
47
+ return result.ok === true;
48
+ }
49
+ /**
50
+ * Type guard to check if a Result is a failure.
51
+ *
52
+ * @param result - The Result to check.
53
+ * @returns True if the Result is a failure.
54
+ */
55
+ function result_isErr(result) {
56
+ return result.ok === false;
57
+ }
58
+ //# sourceMappingURL=result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.js","sourceRoot":"","sources":["../src/result.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AAmCH,gBAEC;AASD,kBAEC;AAQD,kCAEC;AAQD,oCAEC;AAvCD;;;;;GAKG;AACH,SAAgB,EAAE,CAAI,KAAQ;IAC5B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,GAAG;IACjB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;AACvB,CAAC;AAED;;;;;GAKG;AACH,SAAgB,WAAW,CAAI,MAAiB;IAC9C,OAAO,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,SAAgB,YAAY,CAAI,MAAiB;IAC/C,OAAO,MAAM,CAAC,EAAE,KAAK,KAAK,CAAC;AAC7B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "@fnndsc/fond",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "The neutral base under mise's engine and session host: Result, the error stack, and (in later steps) the VFS framework and the sink and surface interfaces. Depends on nothing in @fnndsc.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/FNNDSC/mise.git",
8
+ "directory": "packages/fond"
9
+ },
10
+ "homepage": "https://github.com/FNNDSC/mise/tree/main/packages/fond#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/FNNDSC/mise/issues"
13
+ },
14
+ "main": "dist/index.js",
15
+ "types": "dist/index.d.ts",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "require": "./dist/index.js"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "scripts": {
28
+ "build": "rm -fr dist && tsc",
29
+ "test": "jest --coverage",
30
+ "prepublishOnly": "test -f dist/index.js && test -f dist/index.d.ts"
31
+ },
32
+ "keywords": [
33
+ "chris",
34
+ "mise",
35
+ "fnndsc"
36
+ ],
37
+ "author": "Rudolph Pienaar",
38
+ "license": "MIT",
39
+ "devDependencies": {
40
+ "@jest/globals": "^30.2.0",
41
+ "@types/jest": "^30.0.0",
42
+ "jest": "^30.2.0",
43
+ "ts-jest": "^29.4.5",
44
+ "typescript": "^5.0.0"
45
+ }
46
+ }