@f0rbit/corpus 0.1.8 → 0.2.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.
@@ -0,0 +1,280 @@
1
+ /**
2
+ * @module Result
3
+ * @description Extended utilities for working with Result types.
4
+ *
5
+ * Provides functional utilities for error handling without exceptions:
6
+ * - Pattern matching with `match`
7
+ * - Safe unwrapping with `unwrap_or`, `unwrap`, `unwrap_err`
8
+ * - Exception-to-Result conversion with `try_catch`, `try_catch_async`
9
+ * - Fetch wrapper with `fetch_result`
10
+ * - Composable pipelines with `pipe`
11
+ */
12
+ import { type Result } from "./types";
13
+ /**
14
+ * Pattern match on a Result, extracting the value with appropriate handler.
15
+ *
16
+ * @param result - The Result to match on
17
+ * @param on_ok - Handler for success case
18
+ * @param on_err - Handler for error case
19
+ * @returns The return value of the matching handler
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const result = await fetchUser(id)
24
+ * const message = match(
25
+ * result,
26
+ * user => `Hello, ${user.name}!`,
27
+ * error => `Failed: ${error.message}`
28
+ * )
29
+ * ```
30
+ */
31
+ export declare const match: <T, E, R>(result: Result<T, E>, on_ok: (value: T) => R, on_err: (error: E) => R) => R;
32
+ /**
33
+ * Extract value from Result, returning default if error.
34
+ *
35
+ * @param result - The Result to unwrap
36
+ * @param default_value - Value to return if Result is an error
37
+ * @returns The success value or default
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const users = unwrap_or(await fetchUsers(), [])
42
+ * ```
43
+ */
44
+ export declare const unwrap_or: <T, E>(result: Result<T, E>, default_value: T) => T;
45
+ /**
46
+ * Extract value from Result, throwing if error.
47
+ * Use only when you're certain the Result is Ok, or in tests.
48
+ *
49
+ * @param result - The Result to unwrap
50
+ * @returns The success value
51
+ * @throws Error if Result is an error
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * // In tests
56
+ * const user = unwrap(await createUser(data))
57
+ * expect(user.name).toBe('Alice')
58
+ * ```
59
+ */
60
+ export declare const unwrap: <T, E>(result: Result<T, E>) => T;
61
+ /**
62
+ * Extract error from Result, throwing if Ok.
63
+ * Use only when you're certain the Result is Err, or in tests.
64
+ *
65
+ * @param result - The Result to unwrap
66
+ * @returns The error value
67
+ * @throws Error if Result is Ok
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * // In tests
72
+ * const error = unwrap_err(await createUser(invalidData))
73
+ * expect(error.kind).toBe('validation_error')
74
+ * ```
75
+ */
76
+ export declare const unwrap_err: <T, E>(result: Result<T, E>) => E;
77
+ /**
78
+ * Execute a function and convert exceptions to Result.
79
+ *
80
+ * @param fn - Function to execute
81
+ * @param on_error - Transform caught exception to error type
82
+ * @returns Result containing success value or transformed error
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const result = try_catch(
87
+ * () => JSON.parse(input),
88
+ * e => ({ kind: 'parse_error', message: format_error(e) })
89
+ * )
90
+ * ```
91
+ */
92
+ export declare const try_catch: <T, E>(fn: () => T, on_error: (e: unknown) => E) => Result<T, E>;
93
+ /**
94
+ * Execute an async function and convert exceptions to Result.
95
+ *
96
+ * @param fn - Async function to execute
97
+ * @param on_error - Transform caught exception to error type
98
+ * @returns Promise of Result containing success value or transformed error
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * const result = await try_catch_async(
103
+ * () => db.query('SELECT * FROM users'),
104
+ * e => ({ kind: 'database_error', cause: e })
105
+ * )
106
+ * ```
107
+ */
108
+ export declare const try_catch_async: <T, E>(fn: () => Promise<T>, on_error: (e: unknown) => E) => Promise<Result<T, E>>;
109
+ /**
110
+ * Error types for fetch operations.
111
+ */
112
+ export type FetchError = {
113
+ type: "network";
114
+ cause: unknown;
115
+ } | {
116
+ type: "http";
117
+ status: number;
118
+ status_text: string;
119
+ };
120
+ /**
121
+ * Fetch wrapper that returns Result instead of throwing.
122
+ *
123
+ * @param input - URL or Request to fetch
124
+ * @param init - Fetch options
125
+ * @param on_error - Transform FetchError to your error type
126
+ * @param parse_body - Custom body parser (defaults to JSON)
127
+ * @returns Promise of Result with parsed response or error
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * const result = await fetch_result(
132
+ * 'https://api.example.com/users',
133
+ * { headers: { Authorization: `Bearer ${token}` } },
134
+ * e => e.type === 'http' ? `HTTP ${e.status}` : 'Network error'
135
+ * )
136
+ * ```
137
+ */
138
+ export declare const fetch_result: <T, E>(input: string | URL | Request, init: RequestInit | undefined, on_error: (e: FetchError) => E, parse_body?: (response: Response) => Promise<T>) => Promise<Result<T, E>>;
139
+ type MaybePromise<T> = T | Promise<T>;
140
+ /**
141
+ * A composable pipeline for chaining Result operations.
142
+ *
143
+ * All operations are lazy - nothing executes until `.result()` or `.unwrap_or()` is called.
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * const user = await pipe(fetchUser(id))
148
+ * .map(user => user.profile)
149
+ * .flat_map(profile => fetchAvatar(profile.avatar_id))
150
+ * .map(avatar => avatar.url)
151
+ * .unwrap_or('/default-avatar.png')
152
+ * ```
153
+ */
154
+ export type Pipe<T, E> = {
155
+ /** Transform the success value */
156
+ map: <U>(fn: (value: T) => U) => Pipe<U, E>;
157
+ /** Transform the success value with an async function */
158
+ map_async: <U>(fn: (value: T) => Promise<U>) => Pipe<U, E>;
159
+ /** Chain with another Result-returning operation */
160
+ flat_map: <U>(fn: (value: T) => MaybePromise<Result<U, E>>) => Pipe<U, E>;
161
+ /** Transform the error value */
162
+ map_err: <F>(fn: (error: E) => F) => Pipe<T, F>;
163
+ /** Execute side effect on success (logging, metrics) */
164
+ tap: (fn: (value: T) => MaybePromise<void>) => Pipe<T, E>;
165
+ /** Execute side effect on error */
166
+ tap_err: (fn: (error: E) => MaybePromise<void>) => Pipe<T, E>;
167
+ /** Extract value with fallback */
168
+ unwrap_or: (default_value: T) => Promise<T>;
169
+ /** Get the underlying Result */
170
+ result: () => Promise<Result<T, E>>;
171
+ };
172
+ /**
173
+ * Create a composable pipeline from a Result or Promise<Result>.
174
+ *
175
+ * @param initial - Starting Result value (sync or async)
176
+ * @returns A Pipe for chaining operations
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * // From existing Result
181
+ * const result = await pipe(ok(42))
182
+ * .map(n => n * 2)
183
+ * .result()
184
+ *
185
+ * // From async operation
186
+ * const user = await pipe(fetchUser(id))
187
+ * .flat_map(u => fetchProfile(u.id))
188
+ * .result()
189
+ * ```
190
+ */
191
+ export declare const pipe: {
192
+ <T, E>(initial: MaybePromise<Result<T, E>>): Pipe<T, E>;
193
+ ok<T>(value: T): Pipe<T, never>;
194
+ err<E>(error: E): Pipe<never, E>;
195
+ try<T, E>(fn: () => Promise<T>, on_error: (e: unknown) => E): Pipe<T, E>;
196
+ fetch<T, E>(input: string | URL | Request, init: RequestInit | undefined, on_error: (e: FetchError) => E, parse_body?: (response: Response) => Promise<T>): Pipe<T, E>;
197
+ };
198
+ /**
199
+ * Extract value from Result, returning null for any error.
200
+ * Use for "fetch single resource" patterns where not-found is expected.
201
+ *
202
+ * @param result - The Result to convert
203
+ * @returns The value or null
204
+ *
205
+ * @example
206
+ * ```ts
207
+ * const user = to_nullable(await store.get(userId))
208
+ * if (!user) return <NotFound />
209
+ * ```
210
+ */
211
+ export declare const to_nullable: <T, E>(result: Result<T, E>) => T | null;
212
+ /**
213
+ * Extract value from Result, returning fallback for any error.
214
+ * Use for list endpoints where empty array is acceptable.
215
+ *
216
+ * @param result - The Result to convert
217
+ * @param fallback - Value to return on error
218
+ * @returns The value or fallback
219
+ *
220
+ * @example
221
+ * ```ts
222
+ * const items = to_fallback(await store.list(), [])
223
+ * ```
224
+ */
225
+ export declare const to_fallback: <T, E>(result: Result<T, E>, fallback: T) => T;
226
+ /**
227
+ * Return null if error matches predicate, otherwise throw the error.
228
+ * Use for 404-as-null pattern specifically.
229
+ *
230
+ * @param result - The Result to check
231
+ * @param predicate - Returns true for expected errors (e.g., not_found)
232
+ * @returns The value or null for expected errors
233
+ * @throws The error if predicate returns false
234
+ *
235
+ * @example
236
+ * ```ts
237
+ * const user = null_on(
238
+ * await store.get(id),
239
+ * e => e.kind === 'not_found'
240
+ * )
241
+ * ```
242
+ */
243
+ export declare const null_on: <T, E>(result: Result<T, E>, predicate: (error: E) => boolean) => T | null;
244
+ /**
245
+ * Return fallback if error matches predicate, otherwise throw.
246
+ *
247
+ * @param result - The Result to check
248
+ * @param predicate - Returns true for expected errors
249
+ * @param fallback - Value to return for expected errors
250
+ * @returns The value or fallback for expected errors
251
+ * @throws The error if predicate returns false
252
+ *
253
+ * @example
254
+ * ```ts
255
+ * const count = fallback_on(
256
+ * await store.count(),
257
+ * e => e.kind === 'not_found',
258
+ * 0
259
+ * )
260
+ * ```
261
+ */
262
+ export declare const fallback_on: <T, E>(result: Result<T, E>, predicate: (error: E) => boolean, fallback: T) => T;
263
+ /**
264
+ * Format an unknown error to a string message.
265
+ *
266
+ * @param e - Any error value
267
+ * @returns A string representation
268
+ *
269
+ * @example
270
+ * ```ts
271
+ * try {
272
+ * riskyOperation()
273
+ * } catch (e) {
274
+ * console.error(format_error(e)) // Handles Error, string, or anything
275
+ * }
276
+ * ```
277
+ */
278
+ export declare const format_error: (e: unknown) => string;
279
+ export {};
280
+ //# sourceMappingURL=result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../result.ts"],"names":[],"mappings":"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"}
package/dist/result.js ADDED
@@ -0,0 +1,319 @@
1
+ /**
2
+ * @module Result
3
+ * @description Extended utilities for working with Result types.
4
+ *
5
+ * Provides functional utilities for error handling without exceptions:
6
+ * - Pattern matching with `match`
7
+ * - Safe unwrapping with `unwrap_or`, `unwrap`, `unwrap_err`
8
+ * - Exception-to-Result conversion with `try_catch`, `try_catch_async`
9
+ * - Fetch wrapper with `fetch_result`
10
+ * - Composable pipelines with `pipe`
11
+ */
12
+ import { ok, err } from "./types";
13
+ /**
14
+ * Pattern match on a Result, extracting the value with appropriate handler.
15
+ *
16
+ * @param result - The Result to match on
17
+ * @param on_ok - Handler for success case
18
+ * @param on_err - Handler for error case
19
+ * @returns The return value of the matching handler
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const result = await fetchUser(id)
24
+ * const message = match(
25
+ * result,
26
+ * user => `Hello, ${user.name}!`,
27
+ * error => `Failed: ${error.message}`
28
+ * )
29
+ * ```
30
+ */
31
+ export const match = (result, on_ok, on_err) => {
32
+ if (result.ok)
33
+ return on_ok(result.value);
34
+ return on_err(result.error);
35
+ };
36
+ /**
37
+ * Extract value from Result, returning default if error.
38
+ *
39
+ * @param result - The Result to unwrap
40
+ * @param default_value - Value to return if Result is an error
41
+ * @returns The success value or default
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const users = unwrap_or(await fetchUsers(), [])
46
+ * ```
47
+ */
48
+ export const unwrap_or = (result, default_value) => (result.ok ? result.value : default_value);
49
+ /**
50
+ * Extract value from Result, throwing if error.
51
+ * Use only when you're certain the Result is Ok, or in tests.
52
+ *
53
+ * @param result - The Result to unwrap
54
+ * @returns The success value
55
+ * @throws Error if Result is an error
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * // In tests
60
+ * const user = unwrap(await createUser(data))
61
+ * expect(user.name).toBe('Alice')
62
+ * ```
63
+ */
64
+ export const unwrap = (result) => {
65
+ if (!result.ok)
66
+ throw new Error(`unwrap called on error result: ${JSON.stringify(result.error)}`);
67
+ return result.value;
68
+ };
69
+ /**
70
+ * Extract error from Result, throwing if Ok.
71
+ * Use only when you're certain the Result is Err, or in tests.
72
+ *
73
+ * @param result - The Result to unwrap
74
+ * @returns The error value
75
+ * @throws Error if Result is Ok
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * // In tests
80
+ * const error = unwrap_err(await createUser(invalidData))
81
+ * expect(error.kind).toBe('validation_error')
82
+ * ```
83
+ */
84
+ export const unwrap_err = (result) => {
85
+ if (result.ok)
86
+ throw new Error(`unwrap_err called on ok result: ${JSON.stringify(result.value)}`);
87
+ return result.error;
88
+ };
89
+ /**
90
+ * Execute a function and convert exceptions to Result.
91
+ *
92
+ * @param fn - Function to execute
93
+ * @param on_error - Transform caught exception to error type
94
+ * @returns Result containing success value or transformed error
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * const result = try_catch(
99
+ * () => JSON.parse(input),
100
+ * e => ({ kind: 'parse_error', message: format_error(e) })
101
+ * )
102
+ * ```
103
+ */
104
+ export const try_catch = (fn, on_error) => {
105
+ try {
106
+ return ok(fn());
107
+ }
108
+ catch (e) {
109
+ return err(on_error(e));
110
+ }
111
+ };
112
+ /**
113
+ * Execute an async function and convert exceptions to Result.
114
+ *
115
+ * @param fn - Async function to execute
116
+ * @param on_error - Transform caught exception to error type
117
+ * @returns Promise of Result containing success value or transformed error
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * const result = await try_catch_async(
122
+ * () => db.query('SELECT * FROM users'),
123
+ * e => ({ kind: 'database_error', cause: e })
124
+ * )
125
+ * ```
126
+ */
127
+ export const try_catch_async = async (fn, on_error) => {
128
+ try {
129
+ return ok(await fn());
130
+ }
131
+ catch (e) {
132
+ return err(on_error(e));
133
+ }
134
+ };
135
+ /**
136
+ * Fetch wrapper that returns Result instead of throwing.
137
+ *
138
+ * @param input - URL or Request to fetch
139
+ * @param init - Fetch options
140
+ * @param on_error - Transform FetchError to your error type
141
+ * @param parse_body - Custom body parser (defaults to JSON)
142
+ * @returns Promise of Result with parsed response or error
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * const result = await fetch_result(
147
+ * 'https://api.example.com/users',
148
+ * { headers: { Authorization: `Bearer ${token}` } },
149
+ * e => e.type === 'http' ? `HTTP ${e.status}` : 'Network error'
150
+ * )
151
+ * ```
152
+ */
153
+ export const fetch_result = async (input, init, on_error, parse_body = r => r.json()) => {
154
+ try {
155
+ const response = await fetch(input, init);
156
+ if (!response.ok) {
157
+ return err(on_error({ type: "http", status: response.status, status_text: response.statusText }));
158
+ }
159
+ return ok(await parse_body(response));
160
+ }
161
+ catch (e) {
162
+ return err(on_error({ type: "network", cause: e }));
163
+ }
164
+ };
165
+ const create_pipe = (promised) => ({
166
+ map: (fn) => create_pipe(promised.then((r) => {
167
+ if (r.ok)
168
+ return ok(fn(r.value));
169
+ return err(r.error);
170
+ })),
171
+ map_async: (fn) => create_pipe(promised.then(async (r) => {
172
+ if (r.ok)
173
+ return ok(await fn(r.value));
174
+ return err(r.error);
175
+ })),
176
+ flat_map: (fn) => create_pipe(promised.then((r) => {
177
+ if (r.ok)
178
+ return fn(r.value);
179
+ return err(r.error);
180
+ })),
181
+ map_err: (fn) => create_pipe(promised.then((r) => {
182
+ if (r.ok)
183
+ return ok(r.value);
184
+ return err(fn(r.error));
185
+ })),
186
+ tap: (fn) => create_pipe(promised.then(async (r) => {
187
+ if (r.ok)
188
+ await fn(r.value);
189
+ return r;
190
+ })),
191
+ tap_err: (fn) => create_pipe(promised.then(async (r) => {
192
+ if (!r.ok)
193
+ await fn(r.error);
194
+ return r;
195
+ })),
196
+ unwrap_or: (default_value) => promised.then(r => (r.ok ? r.value : default_value)),
197
+ result: () => promised,
198
+ });
199
+ /**
200
+ * Create a composable pipeline from a Result or Promise<Result>.
201
+ *
202
+ * @param initial - Starting Result value (sync or async)
203
+ * @returns A Pipe for chaining operations
204
+ *
205
+ * @example
206
+ * ```ts
207
+ * // From existing Result
208
+ * const result = await pipe(ok(42))
209
+ * .map(n => n * 2)
210
+ * .result()
211
+ *
212
+ * // From async operation
213
+ * const user = await pipe(fetchUser(id))
214
+ * .flat_map(u => fetchProfile(u.id))
215
+ * .result()
216
+ * ```
217
+ */
218
+ export const pipe = (initial) => create_pipe(Promise.resolve(initial));
219
+ /** Create a pipe starting with an Ok value */
220
+ pipe.ok = (value) => pipe(ok(value));
221
+ /** Create a pipe starting with an Err value */
222
+ pipe.err = (error) => pipe(err(error));
223
+ /** Create a pipe from a function that may throw */
224
+ pipe.try = (fn, on_error) => pipe(try_catch_async(fn, on_error));
225
+ /** Create a pipe from a fetch operation */
226
+ pipe.fetch = (input, init, on_error, parse_body) => pipe(fetch_result(input, init, on_error, parse_body));
227
+ /**
228
+ * Extract value from Result, returning null for any error.
229
+ * Use for "fetch single resource" patterns where not-found is expected.
230
+ *
231
+ * @param result - The Result to convert
232
+ * @returns The value or null
233
+ *
234
+ * @example
235
+ * ```ts
236
+ * const user = to_nullable(await store.get(userId))
237
+ * if (!user) return <NotFound />
238
+ * ```
239
+ */
240
+ export const to_nullable = (result) => (result.ok ? result.value : null);
241
+ /**
242
+ * Extract value from Result, returning fallback for any error.
243
+ * Use for list endpoints where empty array is acceptable.
244
+ *
245
+ * @param result - The Result to convert
246
+ * @param fallback - Value to return on error
247
+ * @returns The value or fallback
248
+ *
249
+ * @example
250
+ * ```ts
251
+ * const items = to_fallback(await store.list(), [])
252
+ * ```
253
+ */
254
+ export const to_fallback = (result, fallback) => (result.ok ? result.value : fallback);
255
+ /**
256
+ * Return null if error matches predicate, otherwise throw the error.
257
+ * Use for 404-as-null pattern specifically.
258
+ *
259
+ * @param result - The Result to check
260
+ * @param predicate - Returns true for expected errors (e.g., not_found)
261
+ * @returns The value or null for expected errors
262
+ * @throws The error if predicate returns false
263
+ *
264
+ * @example
265
+ * ```ts
266
+ * const user = null_on(
267
+ * await store.get(id),
268
+ * e => e.kind === 'not_found'
269
+ * )
270
+ * ```
271
+ */
272
+ export const null_on = (result, predicate) => {
273
+ if (result.ok)
274
+ return result.value;
275
+ if (predicate(result.error))
276
+ return null;
277
+ throw result.error;
278
+ };
279
+ /**
280
+ * Return fallback if error matches predicate, otherwise throw.
281
+ *
282
+ * @param result - The Result to check
283
+ * @param predicate - Returns true for expected errors
284
+ * @param fallback - Value to return for expected errors
285
+ * @returns The value or fallback for expected errors
286
+ * @throws The error if predicate returns false
287
+ *
288
+ * @example
289
+ * ```ts
290
+ * const count = fallback_on(
291
+ * await store.count(),
292
+ * e => e.kind === 'not_found',
293
+ * 0
294
+ * )
295
+ * ```
296
+ */
297
+ export const fallback_on = (result, predicate, fallback) => {
298
+ if (result.ok)
299
+ return result.value;
300
+ if (predicate(result.error))
301
+ return fallback;
302
+ throw result.error;
303
+ };
304
+ /**
305
+ * Format an unknown error to a string message.
306
+ *
307
+ * @param e - Any error value
308
+ * @returns A string representation
309
+ *
310
+ * @example
311
+ * ```ts
312
+ * try {
313
+ * riskyOperation()
314
+ * } catch (e) {
315
+ * console.error(format_error(e)) // Handles Error, string, or anything
316
+ * }
317
+ * ```
318
+ */
319
+ export const format_error = (e) => (e instanceof Error ? e.message : String(e));
package/dist/types.d.ts CHANGED
@@ -214,10 +214,12 @@ export type Snapshot<T = unknown> = {
214
214
  meta: SnapshotMeta;
215
215
  data: T;
216
216
  };
217
+ /** @internal */
217
218
  export type DataHandle = {
218
219
  stream: () => ReadableStream<Uint8Array>;
219
220
  bytes: () => Promise<Uint8Array>;
220
221
  };
222
+ /** @internal */
221
223
  export type MetadataClient = {
222
224
  get: (store_id: string, version: string) => Promise<Result<SnapshotMeta, CorpusError>>;
223
225
  put: (meta: SnapshotMeta) => Promise<Result<void, CorpusError>>;
@@ -227,6 +229,7 @@ export type MetadataClient = {
227
229
  get_children: (parent_store_id: string, parent_version: string) => AsyncIterable<SnapshotMeta>;
228
230
  find_by_hash: (store_id: string, content_hash: string) => Promise<SnapshotMeta | null>;
229
231
  };
232
+ /** @internal */
230
233
  export type DataClient = {
231
234
  get: (data_key: string) => Promise<Result<DataHandle, CorpusError>>;
232
235
  put: (data_key: string, data: ReadableStream<Uint8Array> | Uint8Array) => Promise<Result<void, CorpusError>>;
@@ -301,6 +304,34 @@ export type Codec<T> = {
301
304
  encode: (value: T) => Uint8Array;
302
305
  decode: (bytes: Uint8Array) => T;
303
306
  };
307
+ /**
308
+ * Structural type for schema validators (Zod, Valibot, or custom).
309
+ *
310
+ * Any object with a `parse(data: unknown) => T` method satisfies this interface.
311
+ * Used by `json_codec()` to validate data on decode without importing a specific library.
312
+ *
313
+ * @category Types
314
+ * @group Codec Types
315
+ * @example
316
+ * ```ts
317
+ * import { z } from 'zod'
318
+ *
319
+ * // Zod schemas satisfy Parser<T>
320
+ * const UserSchema = z.object({ name: z.string() })
321
+ * const codec = json_codec(UserSchema) // works!
322
+ *
323
+ * // Custom parsers work too
324
+ * const myParser: Parser<number> = {
325
+ * parse: (data) => {
326
+ * if (typeof data !== 'number') throw new Error('Expected number')
327
+ * return data
328
+ * }
329
+ * }
330
+ * ```
331
+ */
332
+ export type Parser<T> = {
333
+ parse: (data: unknown) => T;
334
+ };
304
335
  /**
305
336
  * A typed store for managing versioned data snapshots.
306
337
  *
@@ -335,6 +366,7 @@ export type PutOpts = {
335
366
  };
336
367
  /**
337
368
  * Context passed to data_key_fn for generating custom storage paths.
369
+ * @internal
338
370
  */
339
371
  export type DataKeyContext = {
340
372
  store_id: string;
@@ -397,6 +429,7 @@ export type DefineStoreOpts = {
397
429
  * ```
398
430
  */
399
431
  export declare function define_store<Id extends string, T>(id: Id, codec: Codec<T>, opts?: DefineStoreOpts | string): StoreDefinition<Id, T>;
432
+ /** @internal */
400
433
  export type CorpusBuilder<Stores extends Record<string, Store<any>> = {}> = {
401
434
  with_backend: (backend: Backend) => CorpusBuilder<Stores>;
402
435
  with_store: <Id extends string, T>(definition: StoreDefinition<Id, T>) => CorpusBuilder<Stores & Record<Id, Store<T>>>;