@f0rbit/corpus 0.1.5 → 0.1.7

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.
Files changed (48) hide show
  1. package/dist/backend/cloudflare.d.ts.map +1 -1
  2. package/dist/backend/cloudflare.js +140 -23
  3. package/dist/backend/file.d.ts.map +1 -1
  4. package/dist/backend/file.js +47 -48
  5. package/dist/backend/layered.d.ts.map +1 -1
  6. package/dist/backend/layered.js +67 -19
  7. package/dist/backend/memory.d.ts +2 -1
  8. package/dist/backend/memory.d.ts.map +1 -1
  9. package/dist/backend/memory.js +29 -43
  10. package/dist/corpus.d.ts +11 -0
  11. package/dist/corpus.d.ts.map +1 -1
  12. package/dist/corpus.js +52 -0
  13. package/dist/index.d.ts +2 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -0
  16. package/dist/observations/client.d.ts +12 -0
  17. package/dist/observations/client.d.ts.map +1 -0
  18. package/dist/observations/client.js +115 -0
  19. package/dist/observations/index.d.ts +12 -0
  20. package/dist/observations/index.d.ts.map +1 -0
  21. package/dist/observations/index.js +11 -0
  22. package/dist/observations/schema.d.ts +267 -0
  23. package/dist/observations/schema.d.ts.map +1 -0
  24. package/dist/observations/schema.js +55 -0
  25. package/dist/observations/storage.d.ts +75 -0
  26. package/dist/observations/storage.d.ts.map +1 -0
  27. package/dist/observations/storage.js +137 -0
  28. package/dist/observations/types.d.ts +219 -0
  29. package/dist/observations/types.d.ts.map +1 -0
  30. package/dist/observations/types.js +40 -0
  31. package/dist/observations/utils.d.ts +183 -0
  32. package/dist/observations/utils.d.ts.map +1 -0
  33. package/dist/observations/utils.js +272 -0
  34. package/dist/sst.d.ts +1 -1
  35. package/dist/sst.d.ts.map +1 -1
  36. package/dist/sst.js +20 -0
  37. package/dist/types.d.ts +61 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/utils.d.ts +38 -1
  40. package/dist/utils.d.ts.map +1 -1
  41. package/dist/utils.js +84 -0
  42. package/package.json +71 -67
  43. package/dist/codecs.d.ts +0 -8
  44. package/dist/codecs.d.ts.map +0 -1
  45. package/dist/codecs.js +0 -6
  46. package/dist/core.d.ts +0 -9
  47. package/dist/core.d.ts.map +0 -1
  48. package/dist/core.js +0 -7
@@ -1 +1 @@
1
- {"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../backend/cloudflare.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAwF,YAAY,EAAE,MAAM,UAAU,CAAC;AAI5I,KAAK,UAAU,GAAG;IAAE,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;CAAE,CAAC;AACxD,KAAK,QAAQ,GAAG;IACf,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;QAAC,WAAW,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACpH,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnF,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CACvD,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,EAAE,EAAE,UAAU,CAAC;IACf,EAAE,EAAE,QAAQ,CAAC;IACb,QAAQ,CAAC,EAAE,YAAY,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CA8OlF"}
1
+ {"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../backend/cloudflare.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAiE,YAAY,EAAE,MAAM,UAAU,CAAC;AAMrH,KAAK,UAAU,GAAG;IAAE,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;CAAE,CAAC;AACxD,KAAK,QAAQ,GAAG;IACf,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;QAAC,WAAW,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IACpH,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnF,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CACvD,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,EAAE,EAAE,UAAU,CAAC;IACf,EAAE,EAAE,QAAQ,CAAC;IACb,QAAQ,CAAC,EAAE,YAAY,CAAC;CACxB,CAAC;AA6IF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAmOlF"}
@@ -2,10 +2,138 @@
2
2
  * @module Backends
3
3
  * @description Cloudflare Workers storage backend using D1 and R2.
4
4
  */
5
- import { eq, and, desc, lt, gt, like, sql } from "drizzle-orm";
5
+ import { eq, and, desc, lt, gt, like, sql, inArray } from "drizzle-orm";
6
6
  import { drizzle } from "drizzle-orm/d1";
7
+ import { create_emitter, parse_snapshot_meta } from "../utils";
7
8
  import { ok, err } from "../types";
8
9
  import { corpus_snapshots } from "../schema";
10
+ import { corpus_observations, create_observations_client } from "../observations";
11
+ function create_cloudflare_storage(db) {
12
+ return {
13
+ async put_row(row) {
14
+ try {
15
+ await db.insert(corpus_observations).values(row);
16
+ return ok(row);
17
+ }
18
+ catch (cause) {
19
+ return err({
20
+ kind: "storage_error",
21
+ cause: cause,
22
+ operation: "observations.put"
23
+ });
24
+ }
25
+ },
26
+ async get_row(id) {
27
+ try {
28
+ const rows = await db
29
+ .select()
30
+ .from(corpus_observations)
31
+ .where(eq(corpus_observations.id, id))
32
+ .limit(1);
33
+ return ok(rows[0] ?? null);
34
+ }
35
+ catch (cause) {
36
+ return err({
37
+ kind: "storage_error",
38
+ cause: cause,
39
+ operation: "observations.get"
40
+ });
41
+ }
42
+ },
43
+ async *query_rows(opts = {}) {
44
+ const conditions = [];
45
+ if (opts.type) {
46
+ if (Array.isArray(opts.type)) {
47
+ conditions.push(inArray(corpus_observations.type, opts.type));
48
+ }
49
+ else {
50
+ conditions.push(eq(corpus_observations.type, opts.type));
51
+ }
52
+ }
53
+ if (opts.source_store_id) {
54
+ conditions.push(eq(corpus_observations.source_store_id, opts.source_store_id));
55
+ }
56
+ if (opts.source_version) {
57
+ conditions.push(eq(corpus_observations.source_version, opts.source_version));
58
+ }
59
+ if (opts.source_prefix) {
60
+ conditions.push(like(corpus_observations.source_version, `${opts.source_prefix}%`));
61
+ }
62
+ if (opts.created_after) {
63
+ conditions.push(gt(corpus_observations.created_at, opts.created_after));
64
+ }
65
+ if (opts.created_before) {
66
+ conditions.push(lt(corpus_observations.created_at, opts.created_before));
67
+ }
68
+ if (opts.observed_after) {
69
+ conditions.push(gt(corpus_observations.observed_at, opts.observed_after));
70
+ }
71
+ if (opts.observed_before) {
72
+ conditions.push(lt(corpus_observations.observed_at, opts.observed_before));
73
+ }
74
+ let query = db
75
+ .select()
76
+ .from(corpus_observations)
77
+ .where(conditions.length > 0 ? and(...conditions) : undefined)
78
+ .orderBy(desc(corpus_observations.created_at));
79
+ if (opts.limit) {
80
+ query = query.limit(opts.limit);
81
+ }
82
+ const rows = await query;
83
+ for (const row of rows) {
84
+ yield row;
85
+ }
86
+ },
87
+ async delete_row(id) {
88
+ try {
89
+ const existing = await db
90
+ .select()
91
+ .from(corpus_observations)
92
+ .where(eq(corpus_observations.id, id))
93
+ .limit(1);
94
+ if (existing.length === 0) {
95
+ return ok(false);
96
+ }
97
+ await db.delete(corpus_observations).where(eq(corpus_observations.id, id));
98
+ return ok(true);
99
+ }
100
+ catch (cause) {
101
+ return err({
102
+ kind: "storage_error",
103
+ cause: cause,
104
+ operation: "observations.delete"
105
+ });
106
+ }
107
+ },
108
+ async delete_by_source(store_id, version, path) {
109
+ try {
110
+ const conditions = [
111
+ eq(corpus_observations.source_store_id, store_id),
112
+ eq(corpus_observations.source_version, version)
113
+ ];
114
+ if (path !== undefined) {
115
+ conditions.push(eq(corpus_observations.source_path, path));
116
+ }
117
+ const toDelete = await db
118
+ .select()
119
+ .from(corpus_observations)
120
+ .where(and(...conditions));
121
+ const count = toDelete.length;
122
+ if (count > 0) {
123
+ await db.delete(corpus_observations).where(and(...conditions));
124
+ }
125
+ return ok(count);
126
+ }
127
+ catch (cause) {
128
+ return err({
129
+ kind: "storage_error",
130
+ cause: cause,
131
+ operation: "observations.delete_by_source"
132
+ });
133
+ }
134
+ }
135
+ };
136
+ }
9
137
  /**
10
138
  * Creates a Cloudflare Workers storage backend using D1 and R2.
11
139
  * @category Backends
@@ -45,22 +173,9 @@ import { corpus_snapshots } from "../schema";
45
173
  export function create_cloudflare_backend(config) {
46
174
  const db = drizzle(config.d1);
47
175
  const { r2, on_event } = config;
48
- function emit(event) {
49
- on_event?.(event);
50
- }
51
- function row_to_meta(row) {
52
- return {
53
- store_id: row.store_id,
54
- version: row.version,
55
- parents: JSON.parse(row.parents),
56
- created_at: new Date(row.created_at),
57
- invoked_at: row.invoked_at ? new Date(row.invoked_at) : undefined,
58
- content_hash: row.content_hash,
59
- content_type: row.content_type,
60
- size_bytes: row.size_bytes,
61
- data_key: row.data_key,
62
- tags: row.tags ? JSON.parse(row.tags) : undefined,
63
- };
176
+ const emit = create_emitter(on_event);
177
+ function snapshot_row_to_meta(row) {
178
+ return parse_snapshot_meta(row);
64
179
  }
65
180
  const metadata = {
66
181
  async get(store_id, version) {
@@ -75,7 +190,7 @@ export function create_cloudflare_backend(config) {
75
190
  if (!row) {
76
191
  return err({ kind: "not_found", store_id, version });
77
192
  }
78
- return ok(row_to_meta(row));
193
+ return ok(snapshot_row_to_meta(row));
79
194
  }
80
195
  catch (cause) {
81
196
  const error = { kind: "storage_error", cause: cause, operation: "metadata.get" };
@@ -152,7 +267,7 @@ export function create_cloudflare_backend(config) {
152
267
  const rows = await query;
153
268
  let count = 0;
154
269
  for (const row of rows) {
155
- const meta = row_to_meta(row);
270
+ const meta = snapshot_row_to_meta(row);
156
271
  if (opts?.tags?.length && !opts.tags.every(t => meta.tags?.includes(t))) {
157
272
  continue;
158
273
  }
@@ -168,7 +283,7 @@ export function create_cloudflare_backend(config) {
168
283
  if (!row) {
169
284
  return err({ kind: "not_found", store_id, version: "latest" });
170
285
  }
171
- return ok(row_to_meta(row));
286
+ return ok(snapshot_row_to_meta(row));
172
287
  }
173
288
  catch (cause) {
174
289
  const error = { kind: "storage_error", cause: cause, operation: "metadata.get_latest" };
@@ -186,7 +301,7 @@ export function create_cloudflare_backend(config) {
186
301
  AND json_extract(value, '$.version') = ${parent_version}
187
302
  )`);
188
303
  for (const row of rows) {
189
- yield row_to_meta(row);
304
+ yield snapshot_row_to_meta(row);
190
305
  }
191
306
  },
192
307
  async find_by_hash(store_id, content_hash) {
@@ -197,7 +312,7 @@ export function create_cloudflare_backend(config) {
197
312
  .where(and(eq(corpus_snapshots.store_id, store_id), eq(corpus_snapshots.content_hash, content_hash)))
198
313
  .limit(1);
199
314
  const row = rows[0];
200
- return row ? row_to_meta(row) : null;
315
+ return row ? snapshot_row_to_meta(row) : null;
201
316
  }
202
317
  catch {
203
318
  return null;
@@ -255,5 +370,7 @@ export function create_cloudflare_backend(config) {
255
370
  }
256
371
  },
257
372
  };
258
- return { metadata, data, on_event };
373
+ const storage = create_cloudflare_storage(db);
374
+ const observations = create_observations_client(storage, metadata);
375
+ return { metadata, data, observations, on_event };
259
376
  }
@@ -1 +1 @@
1
- {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../backend/file.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAwF,YAAY,EAAE,MAAM,UAAU,CAAC;AAK5I,MAAM,MAAM,iBAAiB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,YAAY,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAkMtE"}
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../backend/file.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAiE,YAAY,EAAE,MAAM,UAAU,CAAC;AAQrH,MAAM,MAAM,iBAAiB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,YAAY,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CA6MtE"}
@@ -2,7 +2,9 @@
2
2
  * @module Backends
3
3
  * @description File-system storage backend for local persistence.
4
4
  */
5
+ import { create_observations_client, create_observations_storage } from "../observations";
5
6
  import { ok, err } from "../types";
7
+ import { to_bytes, create_emitter, filter_snapshots, parse_snapshot_meta } from "../utils";
6
8
  import { mkdir, readdir } from "node:fs/promises";
7
9
  import { join, dirname } from "node:path";
8
10
  /**
@@ -38,9 +40,7 @@ import { join, dirname } from "node:path";
38
40
  */
39
41
  export function create_file_backend(config) {
40
42
  const { base_path, on_event } = config;
41
- function emit(event) {
42
- on_event?.(event);
43
- }
43
+ const emit = create_emitter(on_event);
44
44
  function meta_path(store_id) {
45
45
  return join(base_path, store_id, "_meta.json");
46
46
  }
@@ -54,13 +54,8 @@ export function create_file_backend(config) {
54
54
  return new Map();
55
55
  try {
56
56
  const content = await file.text();
57
- const entries = JSON.parse(content, (key, value) => {
58
- if (key === "created_at" || key === "invoked_at") {
59
- return value ? new Date(value) : value;
60
- }
61
- return value;
62
- });
63
- return new Map(entries);
57
+ const entries = JSON.parse(content);
58
+ return new Map(entries.map(([key, raw]) => [key, parse_snapshot_meta(raw)]));
64
59
  }
65
60
  catch {
66
61
  return new Map();
@@ -98,20 +93,9 @@ export function create_file_backend(config) {
98
93
  },
99
94
  async *list(store_id, opts) {
100
95
  const store_meta = await read_store_meta(store_id);
101
- const matches = Array.from(store_meta.values())
102
- .filter(meta => {
103
- if (opts?.before && meta.created_at >= opts.before)
104
- return false;
105
- if (opts?.after && meta.created_at <= opts.after)
106
- return false;
107
- if (opts?.tags?.length && !opts.tags.every(t => meta.tags?.includes(t)))
108
- return false;
109
- return true;
110
- })
111
- .sort((a, b) => b.created_at.getTime() - a.created_at.getTime());
112
- const limit = opts?.limit ?? Infinity;
96
+ const filtered = filter_snapshots(Array.from(store_meta.values()), opts);
113
97
  let count = 0;
114
- for (const meta of matches.slice(0, limit)) {
98
+ for (const meta of filtered) {
115
99
  yield meta;
116
100
  count++;
117
101
  }
@@ -174,21 +158,8 @@ export function create_file_backend(config) {
174
158
  const path = data_path(data_key);
175
159
  await mkdir(dirname(path), { recursive: true });
176
160
  try {
177
- if (input instanceof Uint8Array) {
178
- await Bun.write(path, input);
179
- }
180
- else {
181
- const chunks = [];
182
- const reader = input.getReader();
183
- while (true) {
184
- const { done, value } = await reader.read();
185
- if (done)
186
- break;
187
- chunks.push(value);
188
- }
189
- const bytes = concat_bytes(chunks);
190
- await Bun.write(path, bytes);
191
- }
161
+ const bytes = await to_bytes(input);
162
+ await Bun.write(path, bytes);
192
163
  return ok(undefined);
193
164
  }
194
165
  catch (cause) {
@@ -214,15 +185,43 @@ export function create_file_backend(config) {
214
185
  return file.exists();
215
186
  },
216
187
  };
217
- return { metadata, data, on_event };
218
- }
219
- function concat_bytes(chunks) {
220
- const total = chunks.reduce((sum, c) => sum + c.length, 0);
221
- const result = new Uint8Array(total);
222
- let offset = 0;
223
- for (const chunk of chunks) {
224
- result.set(chunk, offset);
225
- offset += chunk.length;
188
+ const file_path = join(base_path, "_observations.json");
189
+ async function read_observations() {
190
+ const file = Bun.file(file_path);
191
+ if (!(await file.exists()))
192
+ return [];
193
+ try {
194
+ return await file.json();
195
+ }
196
+ catch {
197
+ return [];
198
+ }
226
199
  }
227
- return result;
200
+ async function write_observations(rows) {
201
+ await Bun.write(file_path, JSON.stringify(rows, null, 2));
202
+ }
203
+ const storage = create_observations_storage({
204
+ get_all: read_observations,
205
+ set_all: write_observations,
206
+ get_one: async (id) => {
207
+ const rows = await read_observations();
208
+ return rows.find(r => r.id === id) ?? null;
209
+ },
210
+ add_one: async (row) => {
211
+ const rows = await read_observations();
212
+ rows.push(row);
213
+ await write_observations(rows);
214
+ },
215
+ remove_one: async (id) => {
216
+ const rows = await read_observations();
217
+ const idx = rows.findIndex(r => r.id === id);
218
+ if (idx === -1)
219
+ return false;
220
+ rows.splice(idx, 1);
221
+ await write_observations(rows);
222
+ return true;
223
+ }
224
+ });
225
+ const observations = create_observations_client(storage, metadata);
226
+ return { metadata, data, observations, on_event };
228
227
  }
@@ -1 +1 @@
1
- {"version":3,"file":"layered.d.ts","sourceRoot":"","sources":["../../backend/layered.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAA6E,MAAM,UAAU,CAAA;AAGlH,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,aAAa,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;CAClC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CA4I9E"}
1
+ {"version":3,"file":"layered.d.ts","sourceRoot":"","sources":["../../backend/layered.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAiG,MAAM,UAAU,CAAA;AAItI,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,aAAa,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;CAClC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAkJ9E"}
@@ -3,6 +3,7 @@
3
3
  * @description Layered backend for caching and replication strategies.
4
4
  */
5
5
  import { ok, err } from '../types';
6
+ import { to_bytes } from '../utils';
6
7
  /**
7
8
  * Creates a layered backend that combines multiple backends with read/write separation.
8
9
  * @category Backends
@@ -169,25 +170,72 @@ export function create_layered_backend(options) {
169
170
  return false;
170
171
  },
171
172
  };
172
- return { metadata, data };
173
+ const observations = createLayeredObservationsClient(read, write);
174
+ return {
175
+ metadata,
176
+ data,
177
+ ...(observations ? { observations } : {}),
178
+ };
173
179
  }
174
- async function to_bytes(data) {
175
- if (data instanceof Uint8Array)
176
- return data;
177
- const chunks = [];
178
- const reader = data.getReader();
179
- while (true) {
180
- const { done, value } = await reader.read();
181
- if (done)
182
- break;
183
- chunks.push(value);
184
- }
185
- const total = chunks.reduce((sum, c) => sum + c.length, 0);
186
- const result = new Uint8Array(total);
187
- let offset = 0;
188
- for (const chunk of chunks) {
189
- result.set(chunk, offset);
190
- offset += chunk.length;
180
+ function createLayeredObservationsClient(readLayers, writeLayers) {
181
+ const readLayer = readLayers.find(l => l.observations);
182
+ const writeLayersWithObs = writeLayers.filter(l => l.observations);
183
+ if (!readLayer?.observations && writeLayersWithObs.length === 0) {
184
+ return undefined;
191
185
  }
192
- return result;
186
+ const primary = readLayer?.observations;
187
+ return {
188
+ async put(type, opts) {
189
+ if (writeLayersWithObs.length === 0) {
190
+ return err({ kind: 'invalid_config', message: 'No write layers support observations' });
191
+ }
192
+ let result;
193
+ for (const layer of writeLayersWithObs) {
194
+ result = await layer.observations.put(type, opts);
195
+ if (!result.ok)
196
+ return result;
197
+ }
198
+ return result;
199
+ },
200
+ async get(id) {
201
+ if (!primary) {
202
+ return err({ kind: 'observation_not_found', id });
203
+ }
204
+ return primary.get(id);
205
+ },
206
+ async *query(opts) {
207
+ if (!primary)
208
+ return;
209
+ yield* primary.query(opts);
210
+ },
211
+ async *query_meta(opts) {
212
+ if (!primary)
213
+ return;
214
+ yield* primary.query_meta(opts);
215
+ },
216
+ async delete(id) {
217
+ if (writeLayersWithObs.length === 0) {
218
+ return err({ kind: 'observation_not_found', id });
219
+ }
220
+ let result;
221
+ for (const layer of writeLayersWithObs) {
222
+ result = await layer.observations.delete(id);
223
+ }
224
+ return result;
225
+ },
226
+ async delete_by_source(source) {
227
+ let total = 0;
228
+ for (const layer of writeLayersWithObs) {
229
+ const result = await layer.observations.delete_by_source(source);
230
+ if (result.ok)
231
+ total += result.value;
232
+ }
233
+ return ok(total);
234
+ },
235
+ async is_stale(pointer) {
236
+ if (!primary)
237
+ return false;
238
+ return primary.is_stale(pointer);
239
+ },
240
+ };
193
241
  }
@@ -2,7 +2,8 @@
2
2
  * @module Backends
3
3
  * @description In-memory storage backend for testing and development.
4
4
  */
5
- import type { Backend, EventHandler } from "../types";
5
+ import type { Backend } from "../types";
6
+ import type { EventHandler } from "../types";
6
7
  export type MemoryBackendOptions = {
7
8
  on_event?: EventHandler;
8
9
  };
@@ -1 +1 @@
1
- {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../backend/memory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAwF,YAAY,EAAE,MAAM,UAAU,CAAC;AAG5I,MAAM,MAAM,oBAAoB,GAAG;IAClC,QAAQ,CAAC,EAAE,YAAY,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAgJ7E"}
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../backend/memory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAiE,MAAM,UAAU,CAAC;AAKvG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,MAAM,MAAM,oBAAoB,GAAG;IAClC,QAAQ,CAAC,EAAE,YAAY,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CA4I7E"}
@@ -2,7 +2,9 @@
2
2
  * @module Backends
3
3
  * @description In-memory storage backend for testing and development.
4
4
  */
5
+ import { create_observations_client, create_observations_storage } from "../observations";
5
6
  import { ok, err } from "../types";
7
+ import { to_bytes, create_emitter, filter_snapshots } from "../utils";
6
8
  /**
7
9
  * Creates an in-memory storage backend.
8
10
  * @category Backends
@@ -32,10 +34,9 @@ import { ok, err } from "../types";
32
34
  export function create_memory_backend(options) {
33
35
  const meta_store = new Map();
34
36
  const data_store = new Map();
37
+ const observation_store = new Map();
35
38
  const on_event = options?.on_event;
36
- function emit(event) {
37
- on_event?.(event);
38
- }
39
+ const emit = create_emitter(on_event);
39
40
  function make_meta_key(store_id, version) {
40
41
  return `${store_id}:${version}`;
41
42
  }
@@ -60,23 +61,16 @@ export function create_memory_backend(options) {
60
61
  },
61
62
  async *list(store_id, opts) {
62
63
  const prefix = `${store_id}:`;
63
- const matches = [];
64
+ const store_metas = [];
64
65
  for (const [key, meta] of meta_store) {
65
- if (!key.startsWith(prefix))
66
- continue;
67
- if (opts?.before && meta.created_at >= opts.before)
68
- continue;
69
- if (opts?.after && meta.created_at <= opts.after)
70
- continue;
71
- if (opts?.tags?.length && !opts.tags.every(t => meta.tags?.includes(t)))
72
- continue;
73
- matches.push(meta);
66
+ if (key.startsWith(prefix)) {
67
+ store_metas.push(meta);
68
+ }
74
69
  }
75
- matches.sort((a, b) => b.created_at.getTime() - a.created_at.getTime());
76
- const limit = opts?.limit ?? Infinity;
70
+ const filtered = filter_snapshots(store_metas, opts);
77
71
  let count = 0;
78
- for (const match of matches.slice(0, limit)) {
79
- yield match;
72
+ for (const meta of filtered) {
73
+ yield meta;
80
74
  count++;
81
75
  }
82
76
  emit({ type: "meta_list", store_id, count });
@@ -131,21 +125,7 @@ export function create_memory_backend(options) {
131
125
  });
132
126
  },
133
127
  async put(data_key, input) {
134
- let bytes;
135
- if (input instanceof Uint8Array) {
136
- bytes = input;
137
- }
138
- else {
139
- const chunks = [];
140
- const reader = input.getReader();
141
- while (true) {
142
- const { done, value } = await reader.read();
143
- if (done)
144
- break;
145
- chunks.push(value);
146
- }
147
- bytes = concat_bytes(chunks);
148
- }
128
+ const bytes = await to_bytes(input);
149
129
  data_store.set(data_key, bytes);
150
130
  return ok(undefined);
151
131
  },
@@ -157,15 +137,21 @@ export function create_memory_backend(options) {
157
137
  return data_store.has(data_key);
158
138
  },
159
139
  };
160
- return { metadata, data, on_event };
161
- }
162
- function concat_bytes(chunks) {
163
- const total = chunks.reduce((sum, c) => sum + c.length, 0);
164
- const result = new Uint8Array(total);
165
- let offset = 0;
166
- for (const chunk of chunks) {
167
- result.set(chunk, offset);
168
- offset += chunk.length;
169
- }
170
- return result;
140
+ const storage = create_observations_storage({
141
+ get_all: async () => Array.from(observation_store.values()),
142
+ set_all: async (rows) => {
143
+ observation_store.clear();
144
+ for (const row of rows)
145
+ observation_store.set(row.id, row);
146
+ },
147
+ get_one: async (id) => observation_store.get(id) ?? null,
148
+ add_one: async (row) => { observation_store.set(row.id, row); },
149
+ remove_one: async (id) => {
150
+ const had = observation_store.has(id);
151
+ observation_store.delete(id);
152
+ return had;
153
+ }
154
+ });
155
+ const observations = create_observations_client(storage, metadata);
156
+ return { metadata, data, observations, on_event };
171
157
  }
package/dist/corpus.d.ts CHANGED
@@ -64,6 +64,17 @@ export declare function create_store<T>(backend: Backend, definition: StoreDefin
64
64
  * // Type-safe access to stores
65
65
  * await corpus.stores.users.put({ name: 'Alice', email: 'alice@example.com' })
66
66
  * await corpus.stores.notes.put('Hello, world!')
67
+ *
68
+ * // With observations
69
+ * const corpus_with_obs = create_corpus()
70
+ * .with_backend(create_memory_backend())
71
+ * .with_store(users)
72
+ * .with_observations([EntityType, SentimentType])
73
+ * .build()
74
+ *
75
+ * // Pointer utilities
76
+ * const pointer = corpus_with_obs.create_pointer('users', 'v123', '$.name')
77
+ * const value = await corpus_with_obs.resolve_pointer(pointer)
67
78
  * ```
68
79
  */
69
80
  export declare function create_corpus(): CorpusBuilder<{}>;
@@ -1 +1 @@
1
- {"version":3,"file":"corpus.d.ts","sourceRoot":"","sources":["../corpus.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAU,aAAa,EAAE,eAAe,EAAE,KAAK,EAAqD,MAAM,SAAS,CAAA;AAIxI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAmJlG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,aAAa,IAAI,aAAa,CAAC,EAAE,CAAC,CAoCjD"}
1
+ {"version":3,"file":"corpus.d.ts","sourceRoot":"","sources":["../corpus.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAU,aAAa,EAAE,eAAe,EAAE,KAAK,EAAyE,MAAM,SAAS,CAAA;AAM5J;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAmJlG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,aAAa,IAAI,aAAa,CAAC,EAAE,CAAC,CAiFjD"}