@f0rbit/corpus 0.1.9 → 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,108 @@
1
+ /**
2
+ * @module Concurrency
3
+ * @description Utilities for controlling concurrent async operations.
4
+ */
5
+ /**
6
+ * Semaphore for controlling concurrent operations.
7
+ *
8
+ * Limits the number of concurrent async operations by requiring
9
+ * callers to acquire a permit before proceeding. When all permits
10
+ * are taken, subsequent acquires wait until a permit is released.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const semaphore = new Semaphore(3) // Allow 3 concurrent operations
15
+ *
16
+ * async function rateLimitedFetch(url: string) {
17
+ * await semaphore.acquire()
18
+ * try {
19
+ * return await fetch(url)
20
+ * } finally {
21
+ * semaphore.release()
22
+ * }
23
+ * }
24
+ *
25
+ * // Only 3 fetches will run concurrently
26
+ * await Promise.all(urls.map(rateLimitedFetch))
27
+ * ```
28
+ */
29
+ export class Semaphore {
30
+ permits;
31
+ waiting = [];
32
+ constructor(permits) {
33
+ this.permits = permits;
34
+ }
35
+ /**
36
+ * Acquire a permit. Resolves immediately if available,
37
+ * otherwise waits until a permit is released.
38
+ */
39
+ async acquire() {
40
+ if (this.permits > 0) {
41
+ this.permits--;
42
+ return;
43
+ }
44
+ return new Promise(resolve => {
45
+ this.waiting.push(resolve);
46
+ });
47
+ }
48
+ /**
49
+ * Release a permit, allowing the next waiting operation to proceed.
50
+ */
51
+ release() {
52
+ const next = this.waiting.shift();
53
+ if (next) {
54
+ next();
55
+ }
56
+ else {
57
+ this.permits++;
58
+ }
59
+ }
60
+ }
61
+ /**
62
+ * Map over array with controlled concurrency.
63
+ *
64
+ * Unlike Promise.all which starts all operations at once, this limits
65
+ * concurrent operations. Results are returned in the same order as inputs.
66
+ *
67
+ * @param items - Array of items to process
68
+ * @param mapper - Async function to apply to each item
69
+ * @param concurrency - Maximum number of concurrent operations
70
+ * @returns Array of results in the same order as inputs
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * // Process 100 items, but only 5 at a time
75
+ * const results = await parallel_map(
76
+ * urls,
77
+ * async (url, index) => {
78
+ * console.log(`Fetching ${index + 1}/${urls.length}`)
79
+ * return fetch(url).then(r => r.json())
80
+ * },
81
+ * 5
82
+ * )
83
+ * ```
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * // Use with AI APIs that have rate limits
88
+ * const summaries = await parallel_map(
89
+ * documents,
90
+ * doc => summarize(doc),
91
+ * 3 // Only 3 concurrent API calls
92
+ * )
93
+ * ```
94
+ */
95
+ export const parallel_map = async (items, mapper, concurrency) => {
96
+ const semaphore = new Semaphore(concurrency);
97
+ const results = new Array(items.length);
98
+ await Promise.all(items.map(async (item, index) => {
99
+ await semaphore.acquire();
100
+ try {
101
+ results[index] = await mapper(item, index);
102
+ }
103
+ finally {
104
+ semaphore.release();
105
+ }
106
+ }));
107
+ return results;
108
+ };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,14 @@
1
- export { create_corpus, create_store } from './corpus';
2
- export { create_memory_backend, type MemoryBackendOptions } from './backend/memory';
3
- export { create_file_backend, type FileBackendConfig } from './backend/file';
4
- export { create_cloudflare_backend, type CloudflareBackendConfig } from './backend/cloudflare';
5
- export { create_layered_backend, type LayeredBackendOptions } from './backend/layered';
6
- export { json_codec, text_codec, binary_codec, compute_hash, generate_version } from './utils';
7
- export { corpus_snapshots, type CorpusSnapshotRow, type CorpusSnapshotInsert } from './schema';
8
- export type { ContentType, ParentRef, SnapshotMeta, Snapshot, DataHandle, MetadataClient, DataClient, ListOpts, Backend, Codec, Store, StoreDefinition, DefineStoreOpts, DataKeyContext, PutOpts, CorpusBuilder, Corpus, CorpusError, Result, CorpusEvent, EventHandler, ObservationsClient, } from './types';
9
- export { ok, err, define_store } from './types';
10
- export * from './observations';
11
- export { createCorpusInfra, CORPUS_MIGRATION_SQL, type CorpusInfra, type CorpusInfraConfig } from './sst';
1
+ export { create_corpus, create_store } from "./corpus";
2
+ export { create_memory_backend, type MemoryBackendOptions } from "./backend/memory";
3
+ export { create_file_backend, type FileBackendConfig } from "./backend/file";
4
+ export { create_cloudflare_backend, type CloudflareBackendConfig } from "./backend/cloudflare";
5
+ export { create_layered_backend, type LayeredBackendOptions } from "./backend/layered";
6
+ export { json_codec, text_codec, binary_codec, compute_hash, generate_version } from "./utils";
7
+ export { corpus_snapshots, type CorpusSnapshotRow, type CorpusSnapshotInsert } from "./schema";
8
+ export type { ContentType, ParentRef, SnapshotMeta, Snapshot, DataHandle, MetadataClient, DataClient, ListOpts, Backend, Codec, Parser, Store, StoreDefinition, DefineStoreOpts, DataKeyContext, PutOpts, CorpusBuilder, Corpus, CorpusError, Result, CorpusEvent, EventHandler, ObservationsClient, } from "./types";
9
+ export { ok, err, define_store } from "./types";
10
+ export { match, unwrap_or, unwrap, unwrap_err, try_catch, try_catch_async, fetch_result, pipe, to_nullable, to_fallback, null_on, fallback_on, format_error, type FetchError, type Pipe, } from "./result";
11
+ export { Semaphore, parallel_map } from "./concurrency";
12
+ export * from "./observations";
13
+ export { createCorpusInfra, CORPUS_MIGRATION_SQL, type CorpusInfra, type CorpusInfraConfig } from "./sst";
12
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAEtD,OAAO,EAAE,qBAAqB,EAAE,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACnF,OAAO,EAAE,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAC5E,OAAO,EAAE,yBAAyB,EAAE,KAAK,uBAAuB,EAAE,MAAM,sBAAsB,CAAA;AAC9F,OAAO,EAAE,sBAAsB,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAEtF,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE9F,OAAO,EAAE,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,KAAK,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAE9F,YAAY,EACV,WAAW,EACX,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,cAAc,EACd,UAAU,EACV,QAAQ,EACR,OAAO,EACP,KAAK,EACL,KAAK,EACL,eAAe,EACf,eAAe,EACf,cAAc,EACd,OAAO,EACP,aAAa,EACb,MAAM,EACN,WAAW,EACX,MAAM,EACN,WAAW,EACX,YAAY,EACZ,kBAAkB,GACnB,MAAM,SAAS,CAAA;AAEhB,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE/C,cAAc,gBAAgB,CAAA;AAE9B,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEvD,OAAO,EAAE,qBAAqB,EAAE,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACpF,OAAO,EAAE,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,yBAAyB,EAAE,KAAK,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/F,OAAO,EAAE,sBAAsB,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAEvF,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE/F,OAAO,EAAE,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,KAAK,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAE/F,YAAY,EACX,WAAW,EACX,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,cAAc,EACd,UAAU,EACV,QAAQ,EACR,OAAO,EACP,KAAK,EACL,MAAM,EACN,KAAK,EACL,eAAe,EACf,eAAe,EACf,cAAc,EACd,OAAO,EACP,aAAa,EACb,MAAM,EACN,WAAW,EACX,MAAM,EACN,WAAW,EACX,YAAY,EACZ,kBAAkB,GAClB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEhD,OAAO,EACN,KAAK,EACL,SAAS,EACT,MAAM,EACN,UAAU,EACV,SAAS,EACT,eAAe,EACf,YAAY,EACZ,IAAI,EACJ,WAAW,EACX,WAAW,EACX,OAAO,EACP,WAAW,EACX,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,IAAI,GACT,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAExD,cAAc,gBAAgB,CAAC;AAE/B,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC"}
package/dist/index.js CHANGED
@@ -1,10 +1,12 @@
1
- export { create_corpus, create_store } from './corpus';
2
- export { create_memory_backend } from './backend/memory';
3
- export { create_file_backend } from './backend/file';
4
- export { create_cloudflare_backend } from './backend/cloudflare';
5
- export { create_layered_backend } from './backend/layered';
6
- export { json_codec, text_codec, binary_codec, compute_hash, generate_version } from './utils';
7
- export { corpus_snapshots } from './schema';
8
- export { ok, err, define_store } from './types';
9
- export * from './observations';
10
- export { createCorpusInfra, CORPUS_MIGRATION_SQL } from './sst';
1
+ export { create_corpus, create_store } from "./corpus";
2
+ export { create_memory_backend } from "./backend/memory";
3
+ export { create_file_backend } from "./backend/file";
4
+ export { create_cloudflare_backend } from "./backend/cloudflare";
5
+ export { create_layered_backend } from "./backend/layered";
6
+ export { json_codec, text_codec, binary_codec, compute_hash, generate_version } from "./utils";
7
+ export { corpus_snapshots } from "./schema";
8
+ export { ok, err, define_store } from "./types";
9
+ export { match, unwrap_or, unwrap, unwrap_err, try_catch, try_catch_async, fetch_result, pipe, to_nullable, to_fallback, null_on, fallback_on, format_error, } from "./result";
10
+ export { Semaphore, parallel_map } from "./concurrency";
11
+ export * from "./observations";
12
+ export { createCorpusInfra, CORPUS_MIGRATION_SQL } from "./sst";
@@ -5,7 +5,7 @@
5
5
  export * from './types';
6
6
  export type { ObservationRow, ObservationInsert } from './schema';
7
7
  export { corpus_observations } from './schema';
8
- export type { ObservationsStorage, StorageQueryOpts, ObservationsCRUD } from './storage';
8
+ export type { ObservationsStorage, StorageQueryOpts, ObservationsCRUD, ObservationsAdapter, ObservationsCRUDBase, ObservationsCRUDOptimized } from './storage';
9
9
  export { row_to_observation, row_to_meta, create_observation_row, filter_observation_rows, create_observations_storage } from './storage';
10
10
  export { create_observations_client } from './client';
11
11
  export * from './utils';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../observations/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,SAAS,CAAA;AACvB,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAC9C,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAGxF,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAA;AACzI,OAAO,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAA;AACrD,cAAc,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../observations/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,SAAS,CAAA;AACvB,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAC9C,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAA;AAG9J,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAA;AACzI,OAAO,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAA;AACrD,cAAc,SAAS,CAAA"}
@@ -58,7 +58,31 @@ export declare function create_observation_row(id: string, type_name: string, so
58
58
  */
59
59
  export declare function filter_observation_rows(rows: ObservationRow[], opts?: StorageQueryOpts): ObservationRow[];
60
60
  /**
61
- * Simple CRUD interface for observation storage backends.
61
+ * Base CRUD operations for observation storage backends.
62
+ * All backends must implement at minimum these operations.
63
+ */
64
+ export type ObservationsCRUDBase = {
65
+ get_all: () => Promise<ObservationRow[]>;
66
+ set_all: (rows: ObservationRow[]) => Promise<void>;
67
+ get_one: (id: string) => Promise<ObservationRow | null>;
68
+ add_one: (row: ObservationRow) => Promise<Result<void, CorpusError>>;
69
+ remove_one: (id: string) => Promise<Result<boolean, CorpusError>>;
70
+ };
71
+ /**
72
+ * Optional optimized operations for backends with native query capabilities.
73
+ * When provided, these are used instead of loading all rows into memory.
74
+ */
75
+ export type ObservationsCRUDOptimized = {
76
+ query: (opts: StorageQueryOpts) => AsyncIterable<ObservationRow>;
77
+ delete_by_source: (store_id: string, version: string, path?: string) => Promise<Result<number, CorpusError>>;
78
+ };
79
+ /**
80
+ * Storage adapter interface for observation backends.
81
+ * Backends provide base CRUD operations and optionally optimized operations.
82
+ */
83
+ export type ObservationsAdapter = ObservationsCRUDBase & Partial<ObservationsCRUDOptimized>;
84
+ /**
85
+ * @deprecated Use ObservationsAdapter instead
62
86
  */
63
87
  export type ObservationsCRUD = {
64
88
  get_all: () => Promise<ObservationRow[]>;
@@ -68,8 +92,13 @@ export type ObservationsCRUD = {
68
92
  remove_one: (id: string) => Promise<boolean>;
69
93
  };
70
94
  /**
71
- * Create an ObservationsStorage from simple CRUD operations.
72
- * Used by memory and file backends.
95
+ * Create an ObservationsStorage from an adapter.
96
+ *
97
+ * Backends provide simple CRUD operations and optionally optimized query/delete operations.
98
+ * When optimized operations are not provided, falls back to loading all rows into memory.
99
+ *
100
+ * @param adapter - Storage adapter with base CRUD and optional optimized operations
101
+ * @returns ObservationsStorage interface for use with create_observations_client
73
102
  */
74
- export declare function create_observations_storage(crud: ObservationsCRUD): ObservationsStorage;
103
+ export declare function create_observations_storage(adapter: ObservationsAdapter | ObservationsCRUD): ObservationsStorage;
75
104
  //# sourceMappingURL=storage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../observations/storage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../observations/storage.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@
3
3
  * @description Raw storage interface and row conversion utilities for observations.
4
4
  */
5
5
  import { ok } from '../types';
6
+ import { create_filter_pipeline } from '../utils';
6
7
  /**
7
8
  * Extract common fields from an observation row (everything except content).
8
9
  * Used internally by row_to_observation and row_to_meta.
@@ -63,74 +64,93 @@ export function create_observation_row(id, type_name, source, content, opts) {
63
64
  derived_from: opts.derived_from ? JSON.stringify(opts.derived_from) : null
64
65
  };
65
66
  }
67
+ const observation_filter_pipeline = create_filter_pipeline({
68
+ filters: [
69
+ {
70
+ key: 'type',
71
+ predicate: (r, type) => {
72
+ const types = Array.isArray(type) ? type : [type];
73
+ return types.includes(r.type);
74
+ }
75
+ },
76
+ { key: 'source_store_id', predicate: (r, id) => r.source_store_id === id },
77
+ { key: 'source_version', predicate: (r, version) => r.source_version === version },
78
+ { key: 'source_prefix', predicate: (r, prefix) => r.source_version.startsWith(prefix) },
79
+ { key: 'created_after', predicate: (r, after) => r.created_at > after },
80
+ { key: 'created_before', predicate: (r, before) => r.created_at < before },
81
+ { key: 'observed_after', predicate: (r, after) => r.observed_at !== null && r.observed_at > after },
82
+ { key: 'observed_before', predicate: (r, before) => r.observed_at !== null && r.observed_at < before }
83
+ ],
84
+ sort: (a, b) => b.created_at.localeCompare(a.created_at)
85
+ });
66
86
  /**
67
87
  * Filter and sort observation rows based on query options.
68
88
  * Used by in-memory storage implementations (memory backend, file backend).
69
89
  */
70
90
  export function filter_observation_rows(rows, opts = {}) {
71
- let filtered = rows;
72
- if (opts.type) {
73
- const types = Array.isArray(opts.type) ? opts.type : [opts.type];
74
- filtered = filtered.filter(r => types.includes(r.type));
75
- }
76
- if (opts.source_store_id) {
77
- filtered = filtered.filter(r => r.source_store_id === opts.source_store_id);
78
- }
79
- if (opts.source_version) {
80
- filtered = filtered.filter(r => r.source_version === opts.source_version);
81
- }
82
- if (opts.source_prefix) {
83
- filtered = filtered.filter(r => r.source_version.startsWith(opts.source_prefix));
84
- }
85
- if (opts.created_after) {
86
- filtered = filtered.filter(r => r.created_at > opts.created_after);
87
- }
88
- if (opts.created_before) {
89
- filtered = filtered.filter(r => r.created_at < opts.created_before);
90
- }
91
- if (opts.observed_after) {
92
- filtered = filtered.filter(r => r.observed_at && r.observed_at > opts.observed_after);
93
- }
94
- if (opts.observed_before) {
95
- filtered = filtered.filter(r => r.observed_at && r.observed_at < opts.observed_before);
96
- }
97
- filtered.sort((a, b) => b.created_at.localeCompare(a.created_at));
98
- if (opts.limit) {
99
- filtered = filtered.slice(0, opts.limit);
100
- }
101
- return filtered;
91
+ return observation_filter_pipeline(rows, opts);
102
92
  }
103
93
  /**
104
- * Create an ObservationsStorage from simple CRUD operations.
105
- * Used by memory and file backends.
94
+ * Create an ObservationsStorage from an adapter.
95
+ *
96
+ * Backends provide simple CRUD operations and optionally optimized query/delete operations.
97
+ * When optimized operations are not provided, falls back to loading all rows into memory.
98
+ *
99
+ * @param adapter - Storage adapter with base CRUD and optional optimized operations
100
+ * @returns ObservationsStorage interface for use with create_observations_client
106
101
  */
107
- export function create_observations_storage(crud) {
102
+ export function create_observations_storage(adapter) {
103
+ const wrap_add_one = async (row) => {
104
+ const result = await adapter.add_one(row);
105
+ if (result === undefined || result === null)
106
+ return ok(undefined);
107
+ if (typeof result === 'object' && 'ok' in result)
108
+ return result;
109
+ return ok(undefined);
110
+ };
111
+ const wrap_remove_one = async (id) => {
112
+ const result = await adapter.remove_one(id);
113
+ if (typeof result === 'boolean')
114
+ return ok(result);
115
+ if (typeof result === 'object' && 'ok' in result)
116
+ return result;
117
+ return ok(false);
118
+ };
108
119
  return {
109
120
  async put_row(row) {
110
- await crud.add_one(row);
121
+ const result = await wrap_add_one(row);
122
+ if (!result.ok)
123
+ return result;
111
124
  return ok(row);
112
125
  },
113
126
  async get_row(id) {
114
- const row = await crud.get_one(id);
127
+ const row = await adapter.get_one(id);
115
128
  return ok(row);
116
129
  },
117
130
  async *query_rows(opts = {}) {
118
- const rows = filter_observation_rows(await crud.get_all(), opts);
119
- for (const row of rows) {
120
- yield row;
131
+ if (adapter.query) {
132
+ yield* adapter.query(opts);
133
+ }
134
+ else {
135
+ const rows = filter_observation_rows(await adapter.get_all(), opts);
136
+ for (const row of rows) {
137
+ yield row;
138
+ }
121
139
  }
122
140
  },
123
141
  async delete_row(id) {
124
- const deleted = await crud.remove_one(id);
125
- return ok(deleted);
142
+ return wrap_remove_one(id);
126
143
  },
127
144
  async delete_by_source(store_id, version, path) {
128
- const rows = await crud.get_all();
145
+ if (adapter.delete_by_source) {
146
+ return adapter.delete_by_source(store_id, version, path);
147
+ }
148
+ const rows = await adapter.get_all();
129
149
  const toKeep = rows.filter(r => !(r.source_store_id === store_id &&
130
150
  r.source_version === version &&
131
151
  (path === undefined || r.source_path === path)));
132
152
  const deleted = rows.length - toKeep.length;
133
- await crud.set_all(toKeep);
153
+ await adapter.set_all(toKeep);
134
154
  return ok(deleted);
135
155
  }
136
156
  };
@@ -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