@contractspec/module.context-storage 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # @contractspec/module.context-storage
2
+
3
+ Website: https://contractspec.io/
4
+
5
+ **Context storage module with persistence adapters**
6
+
7
+ Context storage module for packs, snapshots, and items. Re-exports entities, storage adapters, and the context-snapshot pipeline.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ bun add @contractspec/module.context-storage
13
+ ```
14
+
15
+ ## Exports
16
+
17
+ - `.` — Main entry: entities, storage, and pipeline
18
+ - `./entities` — Schema entities (ContextPack, ContextSnapshot, ContextSnapshotItem)
19
+ - `./storage` — Postgres storage adapter (PostgresContextStorage)
20
+ - `./pipeline` — ContextSnapshotPipeline for building snapshots from packs
21
+
22
+ ## Usage
23
+
24
+ ```typescript
25
+ import {
26
+ PostgresContextStorage,
27
+ ContextSnapshotPipeline,
28
+ } from "@contractspec/module.context-storage";
29
+
30
+ const store = new PostgresContextStorage({ database });
31
+ const pipeline = new ContextSnapshotPipeline({ store });
32
+
33
+ const result = await pipeline.buildSnapshot({
34
+ pack,
35
+ snapshot,
36
+ items,
37
+ index: true,
38
+ });
39
+ ```
@@ -0,0 +1,66 @@
1
+ import type { ModuleSchemaContribution } from '@contractspec/lib.schema';
2
+ export declare const ContextPackEntity: import("@contractspec/lib.schema").EntitySpec<{
3
+ id: import("@contractspec/lib.schema").EntityScalarField;
4
+ packKey: import("@contractspec/lib.schema").EntityScalarField;
5
+ version: import("@contractspec/lib.schema").EntityScalarField;
6
+ title: import("@contractspec/lib.schema").EntityScalarField;
7
+ description: import("@contractspec/lib.schema").EntityScalarField;
8
+ owners: import("@contractspec/lib.schema").EntityScalarField;
9
+ tags: import("@contractspec/lib.schema").EntityScalarField;
10
+ sources: import("@contractspec/lib.schema").EntityScalarField;
11
+ createdAt: import("@contractspec/lib.schema").EntityScalarField;
12
+ updatedAt: import("@contractspec/lib.schema").EntityScalarField;
13
+ }>;
14
+ export declare const ContextSnapshotEntity: import("@contractspec/lib.schema").EntitySpec<{
15
+ id: import("@contractspec/lib.schema").EntityScalarField;
16
+ packKey: import("@contractspec/lib.schema").EntityScalarField;
17
+ packVersion: import("@contractspec/lib.schema").EntityScalarField;
18
+ hash: import("@contractspec/lib.schema").EntityScalarField;
19
+ itemCount: import("@contractspec/lib.schema").EntityScalarField;
20
+ createdBy: import("@contractspec/lib.schema").EntityScalarField;
21
+ metadata: import("@contractspec/lib.schema").EntityScalarField;
22
+ createdAt: import("@contractspec/lib.schema").EntityScalarField;
23
+ }>;
24
+ export declare const ContextSnapshotItemEntity: import("@contractspec/lib.schema").EntitySpec<{
25
+ id: import("@contractspec/lib.schema").EntityScalarField;
26
+ snapshotId: import("@contractspec/lib.schema").EntityScalarField;
27
+ kind: import("@contractspec/lib.schema").EntityScalarField;
28
+ sourceKey: import("@contractspec/lib.schema").EntityScalarField;
29
+ sourceVersion: import("@contractspec/lib.schema").EntityScalarField;
30
+ content: import("@contractspec/lib.schema").EntityScalarField;
31
+ textContent: import("@contractspec/lib.schema").EntityScalarField;
32
+ metadata: import("@contractspec/lib.schema").EntityScalarField;
33
+ createdAt: import("@contractspec/lib.schema").EntityScalarField;
34
+ }>;
35
+ export declare const contextStorageEntities: (import("@contractspec/lib.schema").EntitySpec<{
36
+ id: import("@contractspec/lib.schema").EntityScalarField;
37
+ packKey: import("@contractspec/lib.schema").EntityScalarField;
38
+ version: import("@contractspec/lib.schema").EntityScalarField;
39
+ title: import("@contractspec/lib.schema").EntityScalarField;
40
+ description: import("@contractspec/lib.schema").EntityScalarField;
41
+ owners: import("@contractspec/lib.schema").EntityScalarField;
42
+ tags: import("@contractspec/lib.schema").EntityScalarField;
43
+ sources: import("@contractspec/lib.schema").EntityScalarField;
44
+ createdAt: import("@contractspec/lib.schema").EntityScalarField;
45
+ updatedAt: import("@contractspec/lib.schema").EntityScalarField;
46
+ }> | import("@contractspec/lib.schema").EntitySpec<{
47
+ id: import("@contractspec/lib.schema").EntityScalarField;
48
+ packKey: import("@contractspec/lib.schema").EntityScalarField;
49
+ packVersion: import("@contractspec/lib.schema").EntityScalarField;
50
+ hash: import("@contractspec/lib.schema").EntityScalarField;
51
+ itemCount: import("@contractspec/lib.schema").EntityScalarField;
52
+ createdBy: import("@contractspec/lib.schema").EntityScalarField;
53
+ metadata: import("@contractspec/lib.schema").EntityScalarField;
54
+ createdAt: import("@contractspec/lib.schema").EntityScalarField;
55
+ }> | import("@contractspec/lib.schema").EntitySpec<{
56
+ id: import("@contractspec/lib.schema").EntityScalarField;
57
+ snapshotId: import("@contractspec/lib.schema").EntityScalarField;
58
+ kind: import("@contractspec/lib.schema").EntityScalarField;
59
+ sourceKey: import("@contractspec/lib.schema").EntityScalarField;
60
+ sourceVersion: import("@contractspec/lib.schema").EntityScalarField;
61
+ content: import("@contractspec/lib.schema").EntityScalarField;
62
+ textContent: import("@contractspec/lib.schema").EntityScalarField;
63
+ metadata: import("@contractspec/lib.schema").EntityScalarField;
64
+ createdAt: import("@contractspec/lib.schema").EntityScalarField;
65
+ }>)[];
66
+ export declare const contextStorageSchemaContribution: ModuleSchemaContribution;
@@ -0,0 +1,73 @@
1
+ // @bun
2
+ // src/entities/index.ts
3
+ import { defineEntity, field, index } from "@contractspec/lib.schema";
4
+ var ContextPackEntity = defineEntity({
5
+ name: "ContextPack",
6
+ description: "Context pack definition for snapshots.",
7
+ schema: "lssm_context",
8
+ map: "context_pack",
9
+ fields: {
10
+ id: field.id({ description: "Context pack record ID" }),
11
+ packKey: field.string({ description: "Context pack key" }),
12
+ version: field.string({ description: "Context pack version" }),
13
+ title: field.string({ description: "Pack title" }),
14
+ description: field.string({ isOptional: true }),
15
+ owners: field.json({ isOptional: true }),
16
+ tags: field.json({ isOptional: true }),
17
+ sources: field.json({ isOptional: true }),
18
+ createdAt: field.createdAt(),
19
+ updatedAt: field.updatedAt()
20
+ },
21
+ indexes: [index.unique(["packKey", "version"])]
22
+ });
23
+ var ContextSnapshotEntity = defineEntity({
24
+ name: "ContextSnapshot",
25
+ description: "Immutable snapshot created from a context pack.",
26
+ schema: "lssm_context",
27
+ map: "context_snapshot",
28
+ fields: {
29
+ id: field.id({ description: "Snapshot ID" }),
30
+ packKey: field.string({ description: "Context pack key" }),
31
+ packVersion: field.string({ description: "Context pack version" }),
32
+ hash: field.string({ description: "Snapshot hash" }),
33
+ itemCount: field.int({ isOptional: true }),
34
+ createdBy: field.string({ isOptional: true }),
35
+ metadata: field.json({ isOptional: true }),
36
+ createdAt: field.createdAt()
37
+ },
38
+ indexes: [index.on(["packKey", "packVersion"]), index.on(["createdAt"])]
39
+ });
40
+ var ContextSnapshotItemEntity = defineEntity({
41
+ name: "ContextSnapshotItem",
42
+ description: "Item belonging to a context snapshot.",
43
+ schema: "lssm_context",
44
+ map: "context_snapshot_item",
45
+ fields: {
46
+ id: field.id({ description: "Snapshot item ID" }),
47
+ snapshotId: field.string({ description: "Context snapshot ID" }),
48
+ kind: field.string({ description: "Item kind" }),
49
+ sourceKey: field.string({ description: "Source key" }),
50
+ sourceVersion: field.string({ isOptional: true }),
51
+ content: field.json({ description: "Structured content" }),
52
+ textContent: field.string({ isOptional: true }),
53
+ metadata: field.json({ isOptional: true }),
54
+ createdAt: field.createdAt()
55
+ },
56
+ indexes: [index.on(["snapshotId"]), index.on(["kind"])]
57
+ });
58
+ var contextStorageEntities = [
59
+ ContextPackEntity,
60
+ ContextSnapshotEntity,
61
+ ContextSnapshotItemEntity
62
+ ];
63
+ var contextStorageSchemaContribution = {
64
+ moduleId: "@contractspec/module.context-storage",
65
+ entities: contextStorageEntities
66
+ };
67
+ export {
68
+ contextStorageSchemaContribution,
69
+ contextStorageEntities,
70
+ ContextSnapshotItemEntity,
71
+ ContextSnapshotEntity,
72
+ ContextPackEntity
73
+ };
@@ -0,0 +1,3 @@
1
+ export * from './entities';
2
+ export * from './storage';
3
+ export * from './pipeline/context-snapshot-pipeline';
package/dist/index.js ADDED
@@ -0,0 +1,405 @@
1
+ // @bun
2
+ // src/entities/index.ts
3
+ import { defineEntity, field, index } from "@contractspec/lib.schema";
4
+ var ContextPackEntity = defineEntity({
5
+ name: "ContextPack",
6
+ description: "Context pack definition for snapshots.",
7
+ schema: "lssm_context",
8
+ map: "context_pack",
9
+ fields: {
10
+ id: field.id({ description: "Context pack record ID" }),
11
+ packKey: field.string({ description: "Context pack key" }),
12
+ version: field.string({ description: "Context pack version" }),
13
+ title: field.string({ description: "Pack title" }),
14
+ description: field.string({ isOptional: true }),
15
+ owners: field.json({ isOptional: true }),
16
+ tags: field.json({ isOptional: true }),
17
+ sources: field.json({ isOptional: true }),
18
+ createdAt: field.createdAt(),
19
+ updatedAt: field.updatedAt()
20
+ },
21
+ indexes: [index.unique(["packKey", "version"])]
22
+ });
23
+ var ContextSnapshotEntity = defineEntity({
24
+ name: "ContextSnapshot",
25
+ description: "Immutable snapshot created from a context pack.",
26
+ schema: "lssm_context",
27
+ map: "context_snapshot",
28
+ fields: {
29
+ id: field.id({ description: "Snapshot ID" }),
30
+ packKey: field.string({ description: "Context pack key" }),
31
+ packVersion: field.string({ description: "Context pack version" }),
32
+ hash: field.string({ description: "Snapshot hash" }),
33
+ itemCount: field.int({ isOptional: true }),
34
+ createdBy: field.string({ isOptional: true }),
35
+ metadata: field.json({ isOptional: true }),
36
+ createdAt: field.createdAt()
37
+ },
38
+ indexes: [index.on(["packKey", "packVersion"]), index.on(["createdAt"])]
39
+ });
40
+ var ContextSnapshotItemEntity = defineEntity({
41
+ name: "ContextSnapshotItem",
42
+ description: "Item belonging to a context snapshot.",
43
+ schema: "lssm_context",
44
+ map: "context_snapshot_item",
45
+ fields: {
46
+ id: field.id({ description: "Snapshot item ID" }),
47
+ snapshotId: field.string({ description: "Context snapshot ID" }),
48
+ kind: field.string({ description: "Item kind" }),
49
+ sourceKey: field.string({ description: "Source key" }),
50
+ sourceVersion: field.string({ isOptional: true }),
51
+ content: field.json({ description: "Structured content" }),
52
+ textContent: field.string({ isOptional: true }),
53
+ metadata: field.json({ isOptional: true }),
54
+ createdAt: field.createdAt()
55
+ },
56
+ indexes: [index.on(["snapshotId"]), index.on(["kind"])]
57
+ });
58
+ var contextStorageEntities = [
59
+ ContextPackEntity,
60
+ ContextSnapshotEntity,
61
+ ContextSnapshotItemEntity
62
+ ];
63
+ var contextStorageSchemaContribution = {
64
+ moduleId: "@contractspec/module.context-storage",
65
+ entities: contextStorageEntities
66
+ };
67
+
68
+ // src/storage/index.ts
69
+ class PostgresContextStorage {
70
+ database;
71
+ schema;
72
+ createTablesIfMissing;
73
+ ensured = false;
74
+ constructor(options) {
75
+ this.database = options.database;
76
+ this.schema = options.schema ?? "lssm_context";
77
+ this.createTablesIfMissing = options.createTablesIfMissing ?? true;
78
+ }
79
+ async upsertPack(record) {
80
+ await this.ensureTables();
81
+ const packId = `${record.packKey}:${record.version}`;
82
+ await this.database.execute(`INSERT INTO ${this.table("context_pack")}
83
+ (id, pack_key, version, title, description, owners, tags, sources, created_at, updated_at)
84
+ VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7::jsonb, $8::jsonb, $9, $10)
85
+ ON CONFLICT (pack_key, version)
86
+ DO UPDATE SET
87
+ title = EXCLUDED.title,
88
+ description = EXCLUDED.description,
89
+ owners = EXCLUDED.owners,
90
+ tags = EXCLUDED.tags,
91
+ sources = EXCLUDED.sources,
92
+ updated_at = EXCLUDED.updated_at;`, [
93
+ packId,
94
+ record.packKey,
95
+ record.version,
96
+ record.title,
97
+ record.description ?? null,
98
+ record.owners ? JSON.stringify(record.owners) : null,
99
+ record.tags ? JSON.stringify(record.tags) : null,
100
+ record.sources ? JSON.stringify(record.sources) : null,
101
+ record.createdAt,
102
+ record.updatedAt ?? record.createdAt
103
+ ]);
104
+ return record;
105
+ }
106
+ async getPack(packKey, version) {
107
+ await this.ensureTables();
108
+ const rows = await this.database.query(`SELECT * FROM ${this.table("context_pack")}
109
+ WHERE pack_key = $1
110
+ ${version ? "AND version = $2" : ""}
111
+ ORDER BY version DESC
112
+ LIMIT 1;`, version ? [packKey, version] : [packKey]);
113
+ return rows.rows[0] ? this.mapPack(rows.rows[0]) : null;
114
+ }
115
+ async listPacks(query = {}) {
116
+ await this.ensureTables();
117
+ const { query: q, tag, owner, limit = 50, offset = 0 } = query;
118
+ const filters = [];
119
+ const params = [];
120
+ params.push(limit, offset);
121
+ if (q) {
122
+ params.push(`%${q}%`);
123
+ filters.push(`(pack_key ILIKE $${params.length} OR title ILIKE $${params.length})`);
124
+ }
125
+ if (tag) {
126
+ params.push(tag);
127
+ filters.push(`tags @> to_jsonb(ARRAY[$${params.length}]::text[])`);
128
+ }
129
+ if (owner) {
130
+ params.push(owner);
131
+ filters.push(`owners @> to_jsonb(ARRAY[$${params.length}]::text[])`);
132
+ }
133
+ const where = filters.length ? `WHERE ${filters.join(" AND ")}` : "";
134
+ const rows = await this.database.query(`SELECT * FROM ${this.table("context_pack")}
135
+ ${where}
136
+ ORDER BY updated_at DESC
137
+ LIMIT $1 OFFSET $2;`, params);
138
+ const packs = rows.rows.map((row) => this.mapPack(row));
139
+ const total = rows.rowCount;
140
+ return {
141
+ packs,
142
+ total,
143
+ nextOffset: offset + packs.length < total ? offset + packs.length : undefined
144
+ };
145
+ }
146
+ async createSnapshot(record) {
147
+ await this.ensureTables();
148
+ await this.database.execute(`INSERT INTO ${this.table("context_snapshot")}
149
+ (id, pack_key, pack_version, hash, item_count, created_by, metadata, created_at)
150
+ VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8);`, [
151
+ record.snapshotId,
152
+ record.packKey,
153
+ record.packVersion,
154
+ record.hash,
155
+ record.itemCount ?? null,
156
+ record.createdBy ?? null,
157
+ record.metadata ? JSON.stringify(record.metadata) : null,
158
+ record.createdAt
159
+ ]);
160
+ return record;
161
+ }
162
+ async getSnapshot(snapshotId) {
163
+ await this.ensureTables();
164
+ const rows = await this.database.query(`SELECT * FROM ${this.table("context_snapshot")} WHERE id = $1;`, [snapshotId]);
165
+ return rows.rows[0] ? this.mapSnapshot(rows.rows[0]) : null;
166
+ }
167
+ async listSnapshots(query = {}) {
168
+ await this.ensureTables();
169
+ const { packKey, snapshotId, limit = 50, offset = 0 } = query;
170
+ const filters = [];
171
+ const params = [limit, offset];
172
+ if (packKey) {
173
+ params.push(packKey);
174
+ filters.push(`pack_key = $${params.length}`);
175
+ }
176
+ if (snapshotId) {
177
+ params.push(snapshotId);
178
+ filters.push(`id = $${params.length}`);
179
+ }
180
+ const where = filters.length ? `WHERE ${filters.join(" AND ")}` : "";
181
+ const rows = await this.database.query(`SELECT * FROM ${this.table("context_snapshot")}
182
+ ${where}
183
+ ORDER BY created_at DESC
184
+ LIMIT $1 OFFSET $2;`, params);
185
+ const snapshots = rows.rows.map((row) => this.mapSnapshot(row));
186
+ const total = rows.rowCount;
187
+ return {
188
+ snapshots,
189
+ total,
190
+ nextOffset: offset + snapshots.length < total ? offset + snapshots.length : undefined
191
+ };
192
+ }
193
+ async addSnapshotItems(snapshotId, items) {
194
+ await this.ensureTables();
195
+ const created = [];
196
+ for (const item of items) {
197
+ const createdAt = item.createdAt ?? new Date().toISOString();
198
+ await this.database.execute(`INSERT INTO ${this.table("context_snapshot_item")}
199
+ (id, snapshot_id, kind, source_key, source_version, content, text_content, metadata, created_at)
200
+ VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7, $8::jsonb, $9);`, [
201
+ item.itemId,
202
+ snapshotId,
203
+ item.kind,
204
+ item.sourceKey,
205
+ item.sourceVersion ?? null,
206
+ JSON.stringify(item.content),
207
+ item.textContent ?? null,
208
+ item.metadata ? JSON.stringify(item.metadata) : null,
209
+ createdAt
210
+ ]);
211
+ created.push({ ...item, snapshotId, createdAt });
212
+ }
213
+ return created;
214
+ }
215
+ async listSnapshotItems(snapshotId, options = {}) {
216
+ await this.ensureTables();
217
+ const { limit = 100, offset = 0 } = options;
218
+ const rows = await this.database.query(`SELECT * FROM ${this.table("context_snapshot_item")}
219
+ WHERE snapshot_id = $1
220
+ ORDER BY created_at ASC
221
+ LIMIT $2 OFFSET $3;`, [snapshotId, limit, offset]);
222
+ return rows.rows.map((row) => this.mapItem(row));
223
+ }
224
+ async ensureTables() {
225
+ if (this.ensured || !this.createTablesIfMissing)
226
+ return;
227
+ await this.database.execute(`CREATE SCHEMA IF NOT EXISTS ${this.schema};`);
228
+ await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.table("context_pack")} (
229
+ id text PRIMARY KEY,
230
+ pack_key text NOT NULL,
231
+ version text NOT NULL,
232
+ title text NOT NULL,
233
+ description text,
234
+ owners jsonb,
235
+ tags jsonb,
236
+ sources jsonb,
237
+ created_at timestamptz NOT NULL,
238
+ updated_at timestamptz NOT NULL,
239
+ UNIQUE (pack_key, version)
240
+ );`);
241
+ await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.table("context_snapshot")} (
242
+ id text PRIMARY KEY,
243
+ pack_key text NOT NULL,
244
+ pack_version text NOT NULL,
245
+ hash text NOT NULL,
246
+ item_count int,
247
+ created_by text,
248
+ metadata jsonb,
249
+ created_at timestamptz NOT NULL
250
+ );`);
251
+ await this.database.execute(`CREATE TABLE IF NOT EXISTS ${this.table("context_snapshot_item")} (
252
+ id text PRIMARY KEY,
253
+ snapshot_id text NOT NULL,
254
+ kind text NOT NULL,
255
+ source_key text NOT NULL,
256
+ source_version text,
257
+ content jsonb NOT NULL,
258
+ text_content text,
259
+ metadata jsonb,
260
+ created_at timestamptz NOT NULL
261
+ );`);
262
+ await this.database.execute(`CREATE INDEX IF NOT EXISTS context_snapshot_item_snapshot_idx
263
+ ON ${this.table("context_snapshot_item")} (snapshot_id);`);
264
+ this.ensured = true;
265
+ }
266
+ table(name) {
267
+ return `${this.schema}.${name}`;
268
+ }
269
+ mapPack(row) {
270
+ return {
271
+ packKey: String(row.pack_key),
272
+ version: String(row.version),
273
+ title: String(row.title),
274
+ description: row.description ? String(row.description) : undefined,
275
+ owners: arrayFromJson(row.owners),
276
+ tags: arrayFromJson(row.tags),
277
+ sources: arrayFromJson(row.sources),
278
+ createdAt: String(row.created_at),
279
+ updatedAt: row.updated_at ? String(row.updated_at) : undefined
280
+ };
281
+ }
282
+ mapSnapshot(row) {
283
+ return {
284
+ snapshotId: String(row.id),
285
+ packKey: String(row.pack_key),
286
+ packVersion: String(row.pack_version),
287
+ hash: String(row.hash),
288
+ itemCount: row.item_count != null ? Number(row.item_count) : undefined,
289
+ createdBy: row.created_by ? String(row.created_by) : undefined,
290
+ metadata: recordFromJson(row.metadata),
291
+ createdAt: String(row.created_at)
292
+ };
293
+ }
294
+ mapItem(row) {
295
+ return {
296
+ itemId: String(row.id),
297
+ snapshotId: String(row.snapshot_id),
298
+ kind: String(row.kind),
299
+ sourceKey: String(row.source_key),
300
+ sourceVersion: row.source_version ? String(row.source_version) : undefined,
301
+ content: recordFromJson(row.content) ?? String(row.content ?? ""),
302
+ textContent: row.text_content ? String(row.text_content) : undefined,
303
+ metadata: recordFromJson(row.metadata),
304
+ createdAt: String(row.created_at)
305
+ };
306
+ }
307
+ }
308
+ function arrayFromJson(value) {
309
+ if (!value)
310
+ return;
311
+ if (Array.isArray(value))
312
+ return value.map(String);
313
+ if (typeof value === "string") {
314
+ try {
315
+ const parsed = JSON.parse(value);
316
+ return Array.isArray(parsed) ? parsed.map(String) : undefined;
317
+ } catch {
318
+ return;
319
+ }
320
+ }
321
+ return;
322
+ }
323
+ function recordFromJson(value) {
324
+ if (!value)
325
+ return;
326
+ if (typeof value === "object" && !Array.isArray(value)) {
327
+ return value;
328
+ }
329
+ if (typeof value === "string") {
330
+ try {
331
+ const parsed = JSON.parse(value);
332
+ return typeof parsed === "object" && parsed ? parsed : undefined;
333
+ } catch {
334
+ return;
335
+ }
336
+ }
337
+ return;
338
+ }
339
+
340
+ // src/pipeline/context-snapshot-pipeline.ts
341
+ import { Buffer } from "buffer";
342
+ import {
343
+ DocumentProcessor
344
+ } from "@contractspec/lib.knowledge/ingestion";
345
+
346
+ class ContextSnapshotPipeline {
347
+ store;
348
+ processor;
349
+ embeddingService;
350
+ vectorIndexer;
351
+ constructor(options) {
352
+ this.store = options.store;
353
+ this.processor = options.documentProcessor ?? new DocumentProcessor;
354
+ this.embeddingService = options.embeddingService;
355
+ this.vectorIndexer = options.vectorIndexer;
356
+ }
357
+ async buildSnapshot(input) {
358
+ await this.store.upsertPack(input.pack);
359
+ const snapshot = await this.store.createSnapshot({
360
+ ...input.snapshot,
361
+ itemCount: input.items.length
362
+ });
363
+ await this.store.addSnapshotItems(snapshot.snapshotId, input.items);
364
+ if (input.index !== false && this.embeddingService && this.vectorIndexer) {
365
+ const documents = input.items.map((item) => toRawDocument(item, snapshot.snapshotId));
366
+ const fragments = await this.collectFragments(documents);
367
+ const embeddings = await this.embeddingService.embedFragments(fragments);
368
+ await this.vectorIndexer.upsert(fragments, embeddings);
369
+ }
370
+ return { snapshot, itemCount: input.items.length };
371
+ }
372
+ async collectFragments(documents) {
373
+ const fragments = [];
374
+ for (const document of documents) {
375
+ const next = await this.processor.process(document);
376
+ fragments.push(...next);
377
+ }
378
+ return fragments;
379
+ }
380
+ }
381
+ function toRawDocument(item, snapshotId) {
382
+ const content = typeof item.content === "string" ? item.content : JSON.stringify(item.content);
383
+ const mimeType = typeof item.content === "string" ? "text/plain" : "application/json";
384
+ const metadata = {
385
+ snapshotId,
386
+ sourceKey: item.sourceKey,
387
+ sourceVersion: item.sourceVersion ?? "latest",
388
+ kind: item.kind
389
+ };
390
+ return {
391
+ id: item.itemId,
392
+ mimeType,
393
+ data: Buffer.from(content, "utf-8"),
394
+ metadata
395
+ };
396
+ }
397
+ export {
398
+ contextStorageSchemaContribution,
399
+ contextStorageEntities,
400
+ PostgresContextStorage,
401
+ ContextSnapshotPipeline,
402
+ ContextSnapshotItemEntity,
403
+ ContextSnapshotEntity,
404
+ ContextPackEntity
405
+ };
@@ -0,0 +1,72 @@
1
+ // src/entities/index.ts
2
+ import { defineEntity, field, index } from "@contractspec/lib.schema";
3
+ var ContextPackEntity = defineEntity({
4
+ name: "ContextPack",
5
+ description: "Context pack definition for snapshots.",
6
+ schema: "lssm_context",
7
+ map: "context_pack",
8
+ fields: {
9
+ id: field.id({ description: "Context pack record ID" }),
10
+ packKey: field.string({ description: "Context pack key" }),
11
+ version: field.string({ description: "Context pack version" }),
12
+ title: field.string({ description: "Pack title" }),
13
+ description: field.string({ isOptional: true }),
14
+ owners: field.json({ isOptional: true }),
15
+ tags: field.json({ isOptional: true }),
16
+ sources: field.json({ isOptional: true }),
17
+ createdAt: field.createdAt(),
18
+ updatedAt: field.updatedAt()
19
+ },
20
+ indexes: [index.unique(["packKey", "version"])]
21
+ });
22
+ var ContextSnapshotEntity = defineEntity({
23
+ name: "ContextSnapshot",
24
+ description: "Immutable snapshot created from a context pack.",
25
+ schema: "lssm_context",
26
+ map: "context_snapshot",
27
+ fields: {
28
+ id: field.id({ description: "Snapshot ID" }),
29
+ packKey: field.string({ description: "Context pack key" }),
30
+ packVersion: field.string({ description: "Context pack version" }),
31
+ hash: field.string({ description: "Snapshot hash" }),
32
+ itemCount: field.int({ isOptional: true }),
33
+ createdBy: field.string({ isOptional: true }),
34
+ metadata: field.json({ isOptional: true }),
35
+ createdAt: field.createdAt()
36
+ },
37
+ indexes: [index.on(["packKey", "packVersion"]), index.on(["createdAt"])]
38
+ });
39
+ var ContextSnapshotItemEntity = defineEntity({
40
+ name: "ContextSnapshotItem",
41
+ description: "Item belonging to a context snapshot.",
42
+ schema: "lssm_context",
43
+ map: "context_snapshot_item",
44
+ fields: {
45
+ id: field.id({ description: "Snapshot item ID" }),
46
+ snapshotId: field.string({ description: "Context snapshot ID" }),
47
+ kind: field.string({ description: "Item kind" }),
48
+ sourceKey: field.string({ description: "Source key" }),
49
+ sourceVersion: field.string({ isOptional: true }),
50
+ content: field.json({ description: "Structured content" }),
51
+ textContent: field.string({ isOptional: true }),
52
+ metadata: field.json({ isOptional: true }),
53
+ createdAt: field.createdAt()
54
+ },
55
+ indexes: [index.on(["snapshotId"]), index.on(["kind"])]
56
+ });
57
+ var contextStorageEntities = [
58
+ ContextPackEntity,
59
+ ContextSnapshotEntity,
60
+ ContextSnapshotItemEntity
61
+ ];
62
+ var contextStorageSchemaContribution = {
63
+ moduleId: "@contractspec/module.context-storage",
64
+ entities: contextStorageEntities
65
+ };
66
+ export {
67
+ contextStorageSchemaContribution,
68
+ contextStorageEntities,
69
+ ContextSnapshotItemEntity,
70
+ ContextSnapshotEntity,
71
+ ContextPackEntity
72
+ };