@absolutejs/artifacts 0.0.2 → 0.0.4
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 +65 -0
- package/dist/index.js +340 -74
- package/dist/rag.js +36 -0
- package/dist/src/generators.d.ts +34 -0
- package/dist/src/index.d.ts +3 -2
- package/dist/src/manifest.d.ts +17 -2
- package/dist/src/rag.d.ts +31 -0
- package/dist/src/service.d.ts +21 -3
- package/dist/src/store.d.ts +19 -3
- package/dist/src/types.d.ts +53 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,11 @@ routes, authorization, UI, or hosting.
|
|
|
23
23
|
- Standard file-backed kinds for documents, presentations, spreadsheets,
|
|
24
24
|
datasets, code, images, audio, video, email, archives, and generic files
|
|
25
25
|
- An optional bridge to `@absolutejs/rag` ingestion
|
|
26
|
+
- Provider-neutral generation registries with atomic multi-file bundles
|
|
27
|
+
- Revision-pinned or explicitly live publications
|
|
28
|
+
- Durable lifecycle events designed for transactional outboxes
|
|
29
|
+
- Per-revision RAG indexing state and an indexing coordinator
|
|
30
|
+
- Artifact/source lineage and history-aware asset garbage collection
|
|
26
31
|
|
|
27
32
|
Your application retains authorization, durable persistence, public tokens,
|
|
28
33
|
URLs, notifications, analytics, submissions, and product-specific rendering.
|
|
@@ -121,6 +126,46 @@ await artifacts.attach("owner-123", report.id, {
|
|
|
121
126
|
});
|
|
122
127
|
```
|
|
123
128
|
|
|
129
|
+
Multiple generated files should use one staged transaction and therefore one
|
|
130
|
+
artifact revision:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const report = await artifacts.createBundle("owner-123", {
|
|
134
|
+
assets: [pdfOutput, docxOutput, thumbnailOutput],
|
|
135
|
+
content: { summary: "Quarterly results" },
|
|
136
|
+
createdBy: "agent",
|
|
137
|
+
kind: "document",
|
|
138
|
+
provenance: {
|
|
139
|
+
lineage: [{ relation: "generated_from", sourceId: "rag-document-123" }],
|
|
140
|
+
tool: "quarterly_report_generator",
|
|
141
|
+
},
|
|
142
|
+
title: "Q3 report",
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Generation
|
|
147
|
+
|
|
148
|
+
Generators are provider-neutral. They return validated structured content and
|
|
149
|
+
zero or more file writes; the registry commits those outputs through the same
|
|
150
|
+
artifact bundle lifecycle:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const generators = createArtifactGeneratorRegistry([
|
|
154
|
+
{
|
|
155
|
+
kind: "presentation",
|
|
156
|
+
name: "company-deck",
|
|
157
|
+
generate: async ({ prompt }) => buildPresentation(prompt),
|
|
158
|
+
},
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
const deck = await generators.generate(artifacts, {
|
|
162
|
+
createdBy: "agent",
|
|
163
|
+
kind: "presentation",
|
|
164
|
+
ownerId: member.id,
|
|
165
|
+
prompt: "Build the partner launch deck",
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
124
169
|
## RAG ingestion
|
|
125
170
|
|
|
126
171
|
The optional `@absolutejs/artifacts/rag` entry point resolves one current or
|
|
@@ -137,6 +182,22 @@ const uploads = await artifactToRAGUploads(revision, assetStore);
|
|
|
137
182
|
const upsert = await buildRAGUpsertInputFromUploads({ uploads });
|
|
138
183
|
```
|
|
139
184
|
|
|
185
|
+
`createArtifactRAGIndexCoordinator` wraps that conversion with durable
|
|
186
|
+
`pending`, `indexed`, and `failed` state. It removes document IDs from the
|
|
187
|
+
previous indexed revision after the replacement succeeds.
|
|
188
|
+
|
|
189
|
+
## Events and retention
|
|
190
|
+
|
|
191
|
+
Every lifecycle mutation supplies its event to the artifact store in the same
|
|
192
|
+
call that writes the current record and immutable revision. Durable adapters
|
|
193
|
+
should commit those rows in one database transaction, then workers can consume
|
|
194
|
+
unprocessed events for RAG indexing, previews, notifications, scanning, or
|
|
195
|
+
conversion.
|
|
196
|
+
|
|
197
|
+
Asset collection compares storage candidates with references across every
|
|
198
|
+
retained revision. `collectAssetGarbage({ dryRun: true })` previews deletion;
|
|
199
|
+
only unreferenced objects older than the configured minimum age are eligible.
|
|
200
|
+
|
|
140
201
|
## Compose publishing and rendering
|
|
141
202
|
|
|
142
203
|
Publishing is an adapter because public access is a host policy:
|
|
@@ -154,6 +215,10 @@ const artifacts = createArtifactService({
|
|
|
154
215
|
});
|
|
155
216
|
```
|
|
156
217
|
|
|
218
|
+
Publishing defaults to `pinned`: the public record names the exact immutable
|
|
219
|
+
revision. `mode: "live"` is an explicit alternative whose revision advances
|
|
220
|
+
with later edits.
|
|
221
|
+
|
|
157
222
|
Renderers are independently registered by artifact kind and output format:
|
|
158
223
|
|
|
159
224
|
```ts
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,18 @@ import { Value } from "@sinclair/typebox/value";
|
|
|
4
4
|
|
|
5
5
|
// src/types.ts
|
|
6
6
|
var ARTIFACT_STATUSES = ["draft", "published", "archived"];
|
|
7
|
+
var ARTIFACT_EVENT_TYPES = [
|
|
8
|
+
"artifact.archived",
|
|
9
|
+
"artifact.asset_attached",
|
|
10
|
+
"artifact.asset_detached",
|
|
11
|
+
"artifact.created",
|
|
12
|
+
"artifact.generated",
|
|
13
|
+
"artifact.indexing_changed",
|
|
14
|
+
"artifact.published",
|
|
15
|
+
"artifact.restored",
|
|
16
|
+
"artifact.revised",
|
|
17
|
+
"artifact.unpublished"
|
|
18
|
+
];
|
|
7
19
|
|
|
8
20
|
class ArtifactError extends Error {
|
|
9
21
|
code;
|
|
@@ -31,6 +43,38 @@ var defineArtifactRegistry = (definitions) => ({
|
|
|
31
43
|
return content;
|
|
32
44
|
}
|
|
33
45
|
});
|
|
46
|
+
// src/generators.ts
|
|
47
|
+
var createArtifactGeneratorRegistry = (initial = []) => {
|
|
48
|
+
const generators = new Map(initial.map((generator) => [generator.kind, generator]));
|
|
49
|
+
return {
|
|
50
|
+
generate: async (service, input) => {
|
|
51
|
+
const generator = generators.get(input.kind);
|
|
52
|
+
if (!generator) {
|
|
53
|
+
throw new ArtifactError("generator_unavailable", `No generator is registered for ${input.kind} artifacts`);
|
|
54
|
+
}
|
|
55
|
+
const result = await generator.generate(input, {
|
|
56
|
+
ownerId: input.ownerId
|
|
57
|
+
});
|
|
58
|
+
const artifact = await service.createBundle(input.ownerId, {
|
|
59
|
+
assets: result.assets,
|
|
60
|
+
content: result.content,
|
|
61
|
+
createdBy: input.createdBy,
|
|
62
|
+
kind: input.kind,
|
|
63
|
+
metadata: {
|
|
64
|
+
...result.metadata,
|
|
65
|
+
...result.warnings?.length ? { generationWarnings: result.warnings } : {}
|
|
66
|
+
},
|
|
67
|
+
provenance: result.provenance,
|
|
68
|
+
title: result.title ?? input.title ?? `Generated ${input.kind}`
|
|
69
|
+
});
|
|
70
|
+
return artifact;
|
|
71
|
+
},
|
|
72
|
+
kinds: () => [...generators.keys()],
|
|
73
|
+
register: (generator) => {
|
|
74
|
+
generators.set(generator.kind, generator);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
};
|
|
34
78
|
// src/standardKinds.ts
|
|
35
79
|
import { Type } from "@sinclair/typebox";
|
|
36
80
|
var FileArtifactContentSchema = Type.Object({
|
|
@@ -135,6 +179,16 @@ var mediaTypeMatches = (accepted, actual) => {
|
|
|
135
179
|
var createArtifactService = (options) => {
|
|
136
180
|
const now = () => (options.clock ?? (() => new Date))().toISOString();
|
|
137
181
|
const idFactory = options.idFactory ?? (() => crypto.randomUUID());
|
|
182
|
+
const eventIdFactory = options.eventIdFactory ?? (() => crypto.randomUUID());
|
|
183
|
+
const event = (artifact, type, payload) => ({
|
|
184
|
+
artifactId: artifact.id,
|
|
185
|
+
createdAt: now(),
|
|
186
|
+
id: eventIdFactory(),
|
|
187
|
+
ownerId: artifact.ownerId,
|
|
188
|
+
payload,
|
|
189
|
+
revision: artifact.revision,
|
|
190
|
+
type
|
|
191
|
+
});
|
|
138
192
|
const validateAssets = (kind, assets) => {
|
|
139
193
|
const policy = options.registry.definitions[kind]?.assets;
|
|
140
194
|
if (!policy) {
|
|
@@ -152,47 +206,81 @@ var createArtifactService = (options) => {
|
|
|
152
206
|
}
|
|
153
207
|
return assets;
|
|
154
208
|
};
|
|
155
|
-
const
|
|
209
|
+
const validateAssetInputs = (kind, inputs, currentCount = 0) => {
|
|
156
210
|
const policy = options.registry.definitions[kind]?.assets;
|
|
157
|
-
if (!policy) {
|
|
211
|
+
if (!policy && inputs.length > 0) {
|
|
158
212
|
throw new ArtifactError("invalid_content", `${kind} artifacts do not accept file assets`);
|
|
159
213
|
}
|
|
160
|
-
if (policy
|
|
214
|
+
if (policy?.maxCount !== undefined && currentCount + inputs.length > policy.maxCount) {
|
|
161
215
|
throw new ArtifactError("invalid_content", `${kind} artifacts accept at most ${policy.maxCount} file assets`);
|
|
162
216
|
}
|
|
163
|
-
|
|
164
|
-
|
|
217
|
+
const rejected = inputs.find((input) => policy?.acceptedMediaTypes?.length && !policy.acceptedMediaTypes.some((accepted) => mediaTypeMatches(accepted, input.mediaType)));
|
|
218
|
+
if (rejected) {
|
|
219
|
+
throw new ArtifactError("invalid_content", `${rejected.mediaType} is not accepted by ${kind} artifacts`);
|
|
165
220
|
}
|
|
166
221
|
};
|
|
222
|
+
const buildRecord = (ownerId, input, assets) => {
|
|
223
|
+
const definition = options.registry.definitions[input.kind];
|
|
224
|
+
if (!definition) {
|
|
225
|
+
throw new ArtifactError("unknown_kind", `Unknown artifact kind: ${input.kind}`);
|
|
226
|
+
}
|
|
227
|
+
const timestamp = now();
|
|
228
|
+
const artifact = {
|
|
229
|
+
assets: validateAssets(input.kind, assets),
|
|
230
|
+
capabilities: definition.capabilities ?? ["archive", "edit", "preview"],
|
|
231
|
+
content: options.registry.parse(input.kind, input.content),
|
|
232
|
+
createdAt: timestamp,
|
|
233
|
+
createdBy: input.createdBy,
|
|
234
|
+
id: idFactory(),
|
|
235
|
+
kind: input.kind,
|
|
236
|
+
metadata: input.metadata ?? {},
|
|
237
|
+
ownerId,
|
|
238
|
+
provenance: input.provenance,
|
|
239
|
+
revision: 1,
|
|
240
|
+
schemaVersion: definition.schemaVersion ?? 1,
|
|
241
|
+
status: "draft",
|
|
242
|
+
title: input.title.trim(),
|
|
243
|
+
updatedAt: timestamp
|
|
244
|
+
};
|
|
245
|
+
return artifact;
|
|
246
|
+
};
|
|
167
247
|
const get = async (ownerId, artifactId) => {
|
|
168
248
|
const artifact = await options.store.get(ownerId, artifactId);
|
|
169
|
-
if (!artifact)
|
|
249
|
+
if (!artifact)
|
|
170
250
|
throw new ArtifactError("not_found", "Artifact not found");
|
|
171
|
-
}
|
|
172
251
|
return artifact;
|
|
173
252
|
};
|
|
174
|
-
const saveRevision = async (artifact, expectedRevision) => {
|
|
175
|
-
const saved = await options.store.save(artifact, expectedRevision
|
|
253
|
+
const saveRevision = async (artifact, expectedRevision, type, payload) => {
|
|
254
|
+
const saved = await options.store.save(artifact, expectedRevision, [
|
|
255
|
+
event(artifact, type, payload)
|
|
256
|
+
]);
|
|
176
257
|
if (!saved) {
|
|
177
258
|
throw new ArtifactError("conflict", "Artifact changed since it was opened; reload before saving");
|
|
178
259
|
}
|
|
179
260
|
return artifact;
|
|
180
261
|
};
|
|
181
|
-
|
|
262
|
+
const requireAssetTransactions = () => {
|
|
263
|
+
if (!options.assetStore?.stage) {
|
|
264
|
+
throw new ArtifactError("asset_transaction_unavailable", "The configured artifact asset store does not support atomic bundles");
|
|
265
|
+
}
|
|
266
|
+
return options.assetStore;
|
|
267
|
+
};
|
|
268
|
+
const service = {
|
|
182
269
|
archive: async (ownerId, artifactId) => {
|
|
183
270
|
const current = await get(ownerId, artifactId);
|
|
184
271
|
requireCapability(current, "archive");
|
|
185
|
-
|
|
272
|
+
const archived = {
|
|
186
273
|
...current,
|
|
187
274
|
revision: current.revision + 1,
|
|
188
275
|
status: "archived",
|
|
189
276
|
updatedAt: now()
|
|
190
|
-
}
|
|
277
|
+
};
|
|
278
|
+
return saveRevision(archived, current.revision, "artifact.archived");
|
|
191
279
|
},
|
|
192
280
|
attach: async (ownerId, artifactId, input, expectedRevision) => {
|
|
193
281
|
const current = await get(ownerId, artifactId);
|
|
194
282
|
requireCapability(current, "attach");
|
|
195
|
-
|
|
283
|
+
validateAssetInputs(current.kind, [input], current.assets.length);
|
|
196
284
|
if (!options.assetStore) {
|
|
197
285
|
throw new ArtifactError("asset_store_unavailable", "No artifact asset store is configured");
|
|
198
286
|
}
|
|
@@ -200,43 +288,96 @@ var createArtifactService = (options) => {
|
|
|
200
288
|
artifact: current,
|
|
201
289
|
idempotencyKey: `artifact:${current.id}:asset:${current.revision + 1}`
|
|
202
290
|
});
|
|
291
|
+
return saveRevision({
|
|
292
|
+
...current,
|
|
293
|
+
assets: validateAssets(current.kind, [...current.assets, reference]),
|
|
294
|
+
revision: current.revision + 1,
|
|
295
|
+
updatedAt: now()
|
|
296
|
+
}, expectedRevision ?? current.revision, "artifact.asset_attached", { assetIds: [reference.id] });
|
|
297
|
+
},
|
|
298
|
+
attachBundle: async (ownerId, artifactId, inputs, expectedRevision) => {
|
|
299
|
+
const current = await get(ownerId, artifactId);
|
|
300
|
+
requireCapability(current, "attach");
|
|
301
|
+
validateAssetInputs(current.kind, inputs, current.assets.length);
|
|
302
|
+
const assetStore = requireAssetTransactions();
|
|
303
|
+
const transaction = await assetStore.stage(inputs, {
|
|
304
|
+
artifact: current,
|
|
305
|
+
idempotencyKey: `artifact:${current.id}:bundle:${current.revision + 1}`
|
|
306
|
+
});
|
|
203
307
|
const assets = validateAssets(current.kind, [
|
|
204
|
-
...current.assets
|
|
205
|
-
|
|
308
|
+
...current.assets,
|
|
309
|
+
...transaction.references
|
|
206
310
|
]);
|
|
207
|
-
|
|
311
|
+
const revised = {
|
|
208
312
|
...current,
|
|
209
313
|
assets,
|
|
210
314
|
revision: current.revision + 1,
|
|
211
315
|
updatedAt: now()
|
|
212
|
-
}
|
|
316
|
+
};
|
|
317
|
+
try {
|
|
318
|
+
await transaction.commit();
|
|
319
|
+
return await saveRevision(revised, expectedRevision ?? current.revision, "artifact.asset_attached", { assetIds: transaction.references.map((asset) => asset.id) });
|
|
320
|
+
} catch (error) {
|
|
321
|
+
await transaction.rollback();
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
collectAssetGarbage: async (input) => {
|
|
326
|
+
if (!options.assetStore) {
|
|
327
|
+
throw new ArtifactError("asset_store_unavailable", "No artifact asset store is configured");
|
|
328
|
+
}
|
|
329
|
+
const referenced = new Set(await options.store.listReferencedAssetIds());
|
|
330
|
+
const cutoff = Date.now() - (input.minimumAgeMs ?? 0);
|
|
331
|
+
const candidates = await options.assetStore.listCandidates();
|
|
332
|
+
const deleted = [];
|
|
333
|
+
const retained = [];
|
|
334
|
+
for (const candidate of candidates) {
|
|
335
|
+
if (referenced.has(candidate.reference.id) || new Date(candidate.createdAt).getTime() > cutoff) {
|
|
336
|
+
retained.push(candidate.reference);
|
|
337
|
+
} else {
|
|
338
|
+
deleted.push(candidate.reference);
|
|
339
|
+
if (!input.dryRun)
|
|
340
|
+
await options.assetStore.delete(candidate.reference);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return { deleted, retained };
|
|
213
344
|
},
|
|
214
345
|
create: async (ownerId, input) => {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
346
|
+
const artifact = buildRecord(ownerId, input, input.assets ?? []);
|
|
347
|
+
await options.store.create(artifact, [
|
|
348
|
+
event(artifact, "artifact.created")
|
|
349
|
+
]);
|
|
350
|
+
return artifact;
|
|
351
|
+
},
|
|
352
|
+
createBundle: async (ownerId, input) => {
|
|
353
|
+
const { assets: assetInputs = [], ...createInput } = input;
|
|
354
|
+
validateAssetInputs(input.kind, assetInputs);
|
|
355
|
+
if (assetInputs.length === 0) {
|
|
356
|
+
return service.create(ownerId, createInput);
|
|
218
357
|
}
|
|
219
|
-
const
|
|
220
|
-
const
|
|
358
|
+
const assetStore = requireAssetTransactions();
|
|
359
|
+
const provisional = buildRecord(ownerId, createInput, []);
|
|
360
|
+
const transaction = await assetStore.stage(assetInputs, {
|
|
361
|
+
artifact: provisional,
|
|
362
|
+
idempotencyKey: `artifact:${provisional.id}:bundle:1`
|
|
363
|
+
});
|
|
221
364
|
const artifact = {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
content,
|
|
225
|
-
createdAt: timestamp,
|
|
226
|
-
createdBy: input.createdBy,
|
|
227
|
-
id: idFactory(),
|
|
228
|
-
kind: input.kind,
|
|
229
|
-
metadata: input.metadata ?? {},
|
|
230
|
-
ownerId,
|
|
231
|
-
provenance: input.provenance,
|
|
232
|
-
revision: 1,
|
|
233
|
-
schemaVersion: definition.schemaVersion ?? 1,
|
|
234
|
-
status: "draft",
|
|
235
|
-
title: input.title.trim(),
|
|
236
|
-
updatedAt: timestamp
|
|
365
|
+
...provisional,
|
|
366
|
+
assets: validateAssets(input.kind, transaction.references)
|
|
237
367
|
};
|
|
238
|
-
|
|
239
|
-
|
|
368
|
+
try {
|
|
369
|
+
await transaction.commit();
|
|
370
|
+
await options.store.create(artifact, [
|
|
371
|
+
event(artifact, "artifact.created"),
|
|
372
|
+
event(artifact, "artifact.generated", {
|
|
373
|
+
assetIds: transaction.references.map((asset) => asset.id)
|
|
374
|
+
})
|
|
375
|
+
]);
|
|
376
|
+
return artifact;
|
|
377
|
+
} catch (error) {
|
|
378
|
+
await transaction.rollback();
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
240
381
|
},
|
|
241
382
|
detach: async (ownerId, artifactId, assetId, expectedRevision) => {
|
|
242
383
|
const current = await get(ownerId, artifactId);
|
|
@@ -250,9 +391,10 @@ var createArtifactService = (options) => {
|
|
|
250
391
|
assets,
|
|
251
392
|
revision: current.revision + 1,
|
|
252
393
|
updatedAt: now()
|
|
253
|
-
}, expectedRevision ?? current.revision);
|
|
394
|
+
}, expectedRevision ?? current.revision, "artifact.asset_detached", { assetId });
|
|
254
395
|
},
|
|
255
396
|
get,
|
|
397
|
+
getIndexingState: (ownerId, artifactId) => options.store.getIndexingState(ownerId, artifactId),
|
|
256
398
|
getRevision: async (ownerId, artifactId, revision) => {
|
|
257
399
|
const snapshot = await options.store.getRevision(ownerId, artifactId, revision);
|
|
258
400
|
if (!snapshot) {
|
|
@@ -261,53 +403,66 @@ var createArtifactService = (options) => {
|
|
|
261
403
|
return snapshot;
|
|
262
404
|
},
|
|
263
405
|
list: (ownerId, query) => options.store.list(ownerId, query),
|
|
406
|
+
listEvents: (query) => options.store.listEvents(query),
|
|
264
407
|
listRevisions: (ownerId, artifactId) => options.store.listRevisions(ownerId, artifactId),
|
|
265
|
-
|
|
408
|
+
markEventProcessed: (eventId, processedAt = now()) => options.store.markEventProcessed(eventId, processedAt),
|
|
409
|
+
markIndexing: async (ownerId, artifactId, input) => {
|
|
410
|
+
const artifact = await get(ownerId, artifactId);
|
|
411
|
+
const state = {
|
|
412
|
+
artifactId,
|
|
413
|
+
documentIds: input.documentIds ?? [],
|
|
414
|
+
error: input.error,
|
|
415
|
+
indexedAt: input.status === "indexed" ? now() : undefined,
|
|
416
|
+
revision: input.revision,
|
|
417
|
+
status: input.status,
|
|
418
|
+
updatedAt: now()
|
|
419
|
+
};
|
|
420
|
+
await options.store.putIndexingState(ownerId, state, [
|
|
421
|
+
event(artifact, "artifact.indexing_changed", {
|
|
422
|
+
indexingRevision: state.revision,
|
|
423
|
+
indexingStatus: state.status
|
|
424
|
+
})
|
|
425
|
+
]);
|
|
426
|
+
return state;
|
|
427
|
+
},
|
|
428
|
+
publish: async (ownerId, artifactId, input = {}) => {
|
|
266
429
|
const current = await get(ownerId, artifactId);
|
|
267
430
|
requireCapability(current, "publish");
|
|
268
431
|
if (!options.publisher) {
|
|
269
432
|
throw new ArtifactError("publisher_unavailable", "No artifact publisher is configured");
|
|
270
433
|
}
|
|
434
|
+
const mode = input.mode ?? "pinned";
|
|
435
|
+
const publishedRevision = current.revision;
|
|
271
436
|
const result = await options.publisher.publish(current, {
|
|
272
|
-
idempotencyKey: `artifact:${current.id}:publish:${
|
|
437
|
+
idempotencyKey: `artifact:${current.id}:publish:${publishedRevision}:${mode}`,
|
|
438
|
+
mode,
|
|
439
|
+
revision: publishedRevision
|
|
273
440
|
});
|
|
274
441
|
const publishedAt = now();
|
|
275
442
|
const publication = {
|
|
276
443
|
id: result.id,
|
|
444
|
+
mode,
|
|
277
445
|
publishedAt,
|
|
446
|
+
revision: publishedRevision,
|
|
278
447
|
url: result.url
|
|
279
448
|
};
|
|
280
|
-
|
|
449
|
+
const published = {
|
|
281
450
|
...current,
|
|
282
451
|
publication,
|
|
283
452
|
revision: current.revision + 1,
|
|
284
453
|
status: "published",
|
|
285
454
|
updatedAt: publishedAt
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
requireCapability(current, "publish");
|
|
291
|
-
if (!options.publisher) {
|
|
292
|
-
throw new ArtifactError("publisher_unavailable", "No artifact publisher is configured");
|
|
293
|
-
}
|
|
294
|
-
await options.publisher.unpublish(current, {
|
|
295
|
-
idempotencyKey: `artifact:${current.id}:unpublish:${current.revision + 1}`
|
|
455
|
+
};
|
|
456
|
+
return saveRevision(published, current.revision, "artifact.published", {
|
|
457
|
+
mode,
|
|
458
|
+
publishedRevision
|
|
296
459
|
});
|
|
297
|
-
return saveRevision({
|
|
298
|
-
...current,
|
|
299
|
-
publication: undefined,
|
|
300
|
-
revision: current.revision + 1,
|
|
301
|
-
status: "draft",
|
|
302
|
-
updatedAt: now()
|
|
303
|
-
}, current.revision);
|
|
304
460
|
},
|
|
305
461
|
readAsset: async (ownerId, artifactId, assetId) => {
|
|
306
462
|
const artifact = await get(ownerId, artifactId);
|
|
307
463
|
const asset = artifact.assets.find((candidate) => candidate.id === assetId);
|
|
308
|
-
if (!asset)
|
|
464
|
+
if (!asset)
|
|
309
465
|
throw new ArtifactError("not_found", "Artifact asset not found");
|
|
310
|
-
}
|
|
311
466
|
if (!options.assetStore) {
|
|
312
467
|
throw new ArtifactError("asset_store_unavailable", "No artifact asset store is configured");
|
|
313
468
|
}
|
|
@@ -323,35 +478,54 @@ var createArtifactService = (options) => {
|
|
|
323
478
|
if (!snapshot) {
|
|
324
479
|
throw new ArtifactError("not_found", "Artifact revision not found");
|
|
325
480
|
}
|
|
326
|
-
const timestamp = now();
|
|
327
481
|
return saveRevision({
|
|
328
482
|
...current,
|
|
329
483
|
assets: validateAssets(current.kind, snapshot.assets),
|
|
330
484
|
content: options.registry.parse(current.kind, snapshot.content),
|
|
331
485
|
metadata: snapshot.metadata,
|
|
332
486
|
publication: undefined,
|
|
487
|
+
provenance: snapshot.provenance,
|
|
333
488
|
revision: current.revision + 1,
|
|
334
489
|
status: "draft",
|
|
335
490
|
title: snapshot.title,
|
|
336
|
-
updatedAt:
|
|
337
|
-
}, expectedRevision ?? current.revision);
|
|
491
|
+
updatedAt: now()
|
|
492
|
+
}, expectedRevision ?? current.revision, "artifact.restored", { restoredRevision: revision });
|
|
493
|
+
},
|
|
494
|
+
unpublish: async (ownerId, artifactId) => {
|
|
495
|
+
const current = await get(ownerId, artifactId);
|
|
496
|
+
requireCapability(current, "publish");
|
|
497
|
+
if (!options.publisher) {
|
|
498
|
+
throw new ArtifactError("publisher_unavailable", "No artifact publisher is configured");
|
|
499
|
+
}
|
|
500
|
+
await options.publisher.unpublish(current, {
|
|
501
|
+
idempotencyKey: `artifact:${current.id}:unpublish:${current.revision + 1}`
|
|
502
|
+
});
|
|
503
|
+
return saveRevision({
|
|
504
|
+
...current,
|
|
505
|
+
publication: undefined,
|
|
506
|
+
revision: current.revision + 1,
|
|
507
|
+
status: "draft",
|
|
508
|
+
updatedAt: now()
|
|
509
|
+
}, current.revision, "artifact.unpublished");
|
|
338
510
|
},
|
|
339
511
|
update: async (ownerId, artifactId, input) => {
|
|
340
512
|
const current = await get(ownerId, artifactId);
|
|
341
513
|
requireCapability(current, "edit");
|
|
342
|
-
const
|
|
343
|
-
const
|
|
514
|
+
const nextRevision = current.revision + 1;
|
|
515
|
+
const publication = current.publication?.mode === "live" ? { ...current.publication, revision: nextRevision } : current.publication;
|
|
344
516
|
return saveRevision({
|
|
345
517
|
...current,
|
|
346
518
|
assets: input.assets === undefined ? current.assets : validateAssets(current.kind, input.assets),
|
|
347
|
-
content,
|
|
519
|
+
content: input.content === undefined ? current.content : options.registry.parse(current.kind, input.content),
|
|
348
520
|
metadata: input.metadata ?? current.metadata,
|
|
349
|
-
|
|
521
|
+
publication,
|
|
522
|
+
revision: nextRevision,
|
|
350
523
|
title: input.title?.trim() || current.title,
|
|
351
524
|
updatedAt: now()
|
|
352
|
-
}, expectedRevision);
|
|
525
|
+
}, input.expectedRevision ?? current.revision, "artifact.revised");
|
|
353
526
|
}
|
|
354
527
|
};
|
|
528
|
+
return service;
|
|
355
529
|
};
|
|
356
530
|
// src/store.ts
|
|
357
531
|
import { createHash } from "crypto";
|
|
@@ -361,6 +535,14 @@ var createMemoryArtifactAssetStore = () => {
|
|
|
361
535
|
const references = new Map;
|
|
362
536
|
const idempotency = new Map;
|
|
363
537
|
return {
|
|
538
|
+
delete: async (reference) => {
|
|
539
|
+
bytes.delete(reference.id);
|
|
540
|
+
references.delete(reference.id);
|
|
541
|
+
},
|
|
542
|
+
listCandidates: async () => [...references.values()].map((reference) => ({
|
|
543
|
+
createdAt: reference.createdAt,
|
|
544
|
+
reference: clone(reference)
|
|
545
|
+
})),
|
|
364
546
|
read: async (reference) => {
|
|
365
547
|
const data = bytes.get(reference.id);
|
|
366
548
|
if (!data)
|
|
@@ -377,6 +559,7 @@ var createMemoryArtifactAssetStore = () => {
|
|
|
377
559
|
algorithm: "sha256",
|
|
378
560
|
value: createHash("sha256").update(input.data).digest("hex")
|
|
379
561
|
},
|
|
562
|
+
createdAt: new Date().toISOString(),
|
|
380
563
|
id,
|
|
381
564
|
mediaType: input.mediaType,
|
|
382
565
|
metadata: input.metadata,
|
|
@@ -389,25 +572,81 @@ var createMemoryArtifactAssetStore = () => {
|
|
|
389
572
|
references.set(id, clone(reference));
|
|
390
573
|
idempotency.set(context.idempotencyKey, id);
|
|
391
574
|
return reference;
|
|
575
|
+
},
|
|
576
|
+
stage: async (inputs, context) => {
|
|
577
|
+
const staged = inputs.map((input, index) => {
|
|
578
|
+
const key = `${context.idempotencyKey}:${index}`;
|
|
579
|
+
const existingId = idempotency.get(key);
|
|
580
|
+
if (existingId) {
|
|
581
|
+
return {
|
|
582
|
+
data: bytes.get(existingId),
|
|
583
|
+
key,
|
|
584
|
+
reference: references.get(existingId)
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
const id = crypto.randomUUID();
|
|
588
|
+
const reference = {
|
|
589
|
+
checksum: {
|
|
590
|
+
algorithm: "sha256",
|
|
591
|
+
value: createHash("sha256").update(input.data).digest("hex")
|
|
592
|
+
},
|
|
593
|
+
createdAt: new Date().toISOString(),
|
|
594
|
+
id,
|
|
595
|
+
mediaType: input.mediaType,
|
|
596
|
+
metadata: input.metadata,
|
|
597
|
+
name: input.name,
|
|
598
|
+
role: input.role ?? "attachment",
|
|
599
|
+
size: input.data.byteLength,
|
|
600
|
+
uri: `memory://${id}`
|
|
601
|
+
};
|
|
602
|
+
return { data: clone(input.data), key, reference };
|
|
603
|
+
});
|
|
604
|
+
return {
|
|
605
|
+
commit: async () => {
|
|
606
|
+
for (const item of staged) {
|
|
607
|
+
bytes.set(item.reference.id, clone(item.data));
|
|
608
|
+
references.set(item.reference.id, clone(item.reference));
|
|
609
|
+
idempotency.set(item.key, item.reference.id);
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
references: staged.map((item) => clone(item.reference)),
|
|
613
|
+
rollback: async () => {
|
|
614
|
+
for (const item of staged) {
|
|
615
|
+
bytes.delete(item.reference.id);
|
|
616
|
+
references.delete(item.reference.id);
|
|
617
|
+
idempotency.delete(item.key);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
};
|
|
392
621
|
}
|
|
393
622
|
};
|
|
394
623
|
};
|
|
395
624
|
var createMemoryArtifactStore = (initial = []) => {
|
|
396
625
|
const records = new Map(initial.map((record) => [record.id, clone(record)]));
|
|
397
626
|
const revisions = new Map;
|
|
627
|
+
const events = new Map;
|
|
628
|
+
const indexing = new Map;
|
|
398
629
|
for (const record of initial)
|
|
399
630
|
revisions.set(record.id, [clone(record)]);
|
|
400
631
|
return {
|
|
401
|
-
create: async (record) => {
|
|
632
|
+
create: async (record, newEvents = []) => {
|
|
402
633
|
if (records.has(record.id))
|
|
403
634
|
throw new Error(`Duplicate artifact id: ${record.id}`);
|
|
404
635
|
records.set(record.id, clone(record));
|
|
405
636
|
revisions.set(record.id, [clone(record)]);
|
|
637
|
+
for (const event of newEvents)
|
|
638
|
+
events.set(event.id, clone(event));
|
|
406
639
|
},
|
|
407
640
|
get: async (ownerId, artifactId) => {
|
|
408
641
|
const record = records.get(artifactId);
|
|
409
642
|
return record?.ownerId === ownerId ? clone(record) : null;
|
|
410
643
|
},
|
|
644
|
+
getIndexingState: async (ownerId, artifactId) => {
|
|
645
|
+
const record = records.get(artifactId);
|
|
646
|
+
if (record?.ownerId !== ownerId)
|
|
647
|
+
return null;
|
|
648
|
+
return clone(indexing.get(artifactId) ?? null);
|
|
649
|
+
},
|
|
411
650
|
getRevision: async (ownerId, artifactId, revision) => {
|
|
412
651
|
const current = records.get(artifactId);
|
|
413
652
|
if (current?.ownerId !== ownerId)
|
|
@@ -421,7 +660,27 @@ var createMemoryArtifactStore = (initial = []) => {
|
|
|
421
660
|
return [];
|
|
422
661
|
return (revisions.get(artifactId) ?? []).toSorted((left, right) => right.revision - left.revision).map(clone);
|
|
423
662
|
},
|
|
424
|
-
|
|
663
|
+
listEvents: async (query = {}) => [...events.values()].filter((event) => (query.processed === undefined || Boolean(event.processedAt) === query.processed) && (!query.type || event.type === query.type)).sort((left, right) => left.createdAt.localeCompare(right.createdAt)).slice(0, query.limit ?? Number.POSITIVE_INFINITY).map(clone),
|
|
664
|
+
listReferencedAssetIds: async () => [
|
|
665
|
+
...new Set([...revisions.values()].flat().flatMap((revision) => revision.assets.map((asset) => asset.id)))
|
|
666
|
+
],
|
|
667
|
+
markEventProcessed: async (eventId, processedAt) => {
|
|
668
|
+
const event = events.get(eventId);
|
|
669
|
+
if (!event)
|
|
670
|
+
return false;
|
|
671
|
+
events.set(eventId, { ...event, processedAt });
|
|
672
|
+
return true;
|
|
673
|
+
},
|
|
674
|
+
putIndexingState: async (ownerId, state, newEvents = []) => {
|
|
675
|
+
const record = records.get(state.artifactId);
|
|
676
|
+
if (record?.ownerId !== ownerId) {
|
|
677
|
+
throw new Error("Artifact not found");
|
|
678
|
+
}
|
|
679
|
+
indexing.set(state.artifactId, clone(state));
|
|
680
|
+
for (const event of newEvents)
|
|
681
|
+
events.set(event.id, clone(event));
|
|
682
|
+
},
|
|
683
|
+
save: async (record, expectedRevision, newEvents = []) => {
|
|
425
684
|
const current = records.get(record.id);
|
|
426
685
|
if (!current || current.ownerId !== record.ownerId || current.revision !== expectedRevision) {
|
|
427
686
|
return false;
|
|
@@ -431,6 +690,8 @@ var createMemoryArtifactStore = (initial = []) => {
|
|
|
431
690
|
...revisions.get(record.id) ?? [],
|
|
432
691
|
clone(record)
|
|
433
692
|
]);
|
|
693
|
+
for (const event of newEvents)
|
|
694
|
+
events.set(event.id, clone(event));
|
|
434
695
|
return true;
|
|
435
696
|
}
|
|
436
697
|
};
|
|
@@ -509,11 +770,14 @@ var createArtifactTools = (options) => ({
|
|
|
509
770
|
if (!artifactId || typeof input.published !== "boolean") {
|
|
510
771
|
return "Provide artifactId and published.";
|
|
511
772
|
}
|
|
512
|
-
const artifact = input.published ? await options.service.publish(options.ownerId, artifactId
|
|
773
|
+
const artifact = input.published ? await options.service.publish(options.ownerId, artifactId, {
|
|
774
|
+
mode: input.mode === "live" ? "live" : "pinned"
|
|
775
|
+
}) : await options.service.unpublish(options.ownerId, artifactId);
|
|
513
776
|
return JSON.stringify(artifact);
|
|
514
777
|
},
|
|
515
778
|
input: Type2.Object({
|
|
516
779
|
artifactId: Type2.String({ minLength: 1 }),
|
|
780
|
+
mode: Type2.Optional(Type2.Union([Type2.Literal("live"), Type2.Literal("pinned")])),
|
|
517
781
|
published: Type2.Boolean()
|
|
518
782
|
})
|
|
519
783
|
},
|
|
@@ -563,7 +827,9 @@ export {
|
|
|
563
827
|
createArtifactTools,
|
|
564
828
|
createArtifactService,
|
|
565
829
|
createArtifactRendererRegistry,
|
|
830
|
+
createArtifactGeneratorRegistry,
|
|
566
831
|
STANDARD_ARTIFACT_KIND_NAMES,
|
|
567
832
|
ArtifactError,
|
|
568
|
-
ARTIFACT_STATUSES
|
|
833
|
+
ARTIFACT_STATUSES,
|
|
834
|
+
ARTIFACT_EVENT_TYPES
|
|
569
835
|
};
|
package/dist/rag.js
CHANGED
|
@@ -39,6 +39,42 @@ var artifactToRAGUploads = async (artifact, reader, options = {}) => {
|
|
|
39
39
|
...uploads
|
|
40
40
|
];
|
|
41
41
|
};
|
|
42
|
+
var createArtifactRAGIndexCoordinator = (options) => ({
|
|
43
|
+
index: async (artifact) => {
|
|
44
|
+
const previous = await options.service.getIndexingState(artifact.ownerId, artifact.id);
|
|
45
|
+
await options.service.markIndexing(artifact.ownerId, artifact.id, {
|
|
46
|
+
documentIds: previous?.documentIds,
|
|
47
|
+
revision: artifact.revision,
|
|
48
|
+
status: "pending"
|
|
49
|
+
});
|
|
50
|
+
try {
|
|
51
|
+
const uploads = await artifactToRAGUploads(artifact, options.reader);
|
|
52
|
+
const indexed = await options.target.index(uploads, { artifact });
|
|
53
|
+
if (previous?.documentIds.length && options.target.remove) {
|
|
54
|
+
const currentIds = new Set(indexed.documentIds);
|
|
55
|
+
const obsoleteIds = previous.documentIds.filter((documentId) => !currentIds.has(documentId));
|
|
56
|
+
if (obsoleteIds.length) {
|
|
57
|
+
await options.target.remove(obsoleteIds, { artifact });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
await options.service.markIndexing(artifact.ownerId, artifact.id, {
|
|
61
|
+
documentIds: indexed.documentIds,
|
|
62
|
+
revision: artifact.revision,
|
|
63
|
+
status: "indexed"
|
|
64
|
+
});
|
|
65
|
+
return indexed;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
await options.service.markIndexing(artifact.ownerId, artifact.id, {
|
|
68
|
+
documentIds: previous?.documentIds,
|
|
69
|
+
error: error instanceof Error ? error.message : String(error),
|
|
70
|
+
revision: artifact.revision,
|
|
71
|
+
status: "failed"
|
|
72
|
+
});
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
42
77
|
export {
|
|
78
|
+
createArtifactRAGIndexCoordinator,
|
|
43
79
|
artifactToRAGUploads
|
|
44
80
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ArtifactAssetWriteInput, ArtifactBundleCreateInput, ArtifactProvenance, ArtifactRecord } from "./types";
|
|
2
|
+
export type ArtifactGenerationInput = {
|
|
3
|
+
createdBy: string;
|
|
4
|
+
input?: Record<string, unknown>;
|
|
5
|
+
kind: string;
|
|
6
|
+
ownerId: string;
|
|
7
|
+
prompt?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
};
|
|
10
|
+
export type ArtifactGenerationContext = {
|
|
11
|
+
ownerId: string;
|
|
12
|
+
};
|
|
13
|
+
export type ArtifactGenerationResult = {
|
|
14
|
+
assets?: ArtifactAssetWriteInput[];
|
|
15
|
+
content: unknown;
|
|
16
|
+
metadata?: Record<string, unknown>;
|
|
17
|
+
provenance?: ArtifactProvenance;
|
|
18
|
+
title?: string;
|
|
19
|
+
warnings?: string[];
|
|
20
|
+
};
|
|
21
|
+
export type ArtifactGenerator = {
|
|
22
|
+
generate(input: ArtifactGenerationInput, context: ArtifactGenerationContext): Promise<ArtifactGenerationResult>;
|
|
23
|
+
kind: string;
|
|
24
|
+
name: string;
|
|
25
|
+
};
|
|
26
|
+
export type ArtifactBundleCreator = {
|
|
27
|
+
createBundle(ownerId: string, input: ArtifactBundleCreateInput): Promise<ArtifactRecord>;
|
|
28
|
+
};
|
|
29
|
+
export declare const createArtifactGeneratorRegistry: (initial?: ArtifactGenerator[]) => {
|
|
30
|
+
generate: (service: ArtifactBundleCreator, input: ArtifactGenerationInput) => Promise<ArtifactRecord>;
|
|
31
|
+
kinds: () => string[];
|
|
32
|
+
register: (generator: ArtifactGenerator) => void;
|
|
33
|
+
};
|
|
34
|
+
export type ArtifactGeneratorRegistry = ReturnType<typeof createArtifactGeneratorRegistry>;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* retains authorization, persistence, URLs, UI, and delivery policy.
|
|
8
8
|
*/
|
|
9
9
|
export { defineArtifactRegistry, type ArtifactContent, type ArtifactKindDefinition, type ArtifactKindDefinitions, type ArtifactRegistry, } from "./registry";
|
|
10
|
+
export { createArtifactGeneratorRegistry, type ArtifactBundleCreator, type ArtifactGenerationContext, type ArtifactGenerationInput, type ArtifactGenerationResult, type ArtifactGenerator, type ArtifactGeneratorRegistry, } from "./generators";
|
|
10
11
|
export { STANDARD_ARTIFACT_KIND_NAMES, standardArtifactDefinitions, } from "./standardKinds";
|
|
11
12
|
export { createArtifactRendererRegistry, type ArtifactRenderer, type ArtifactRendererRegistry, type ArtifactRenderResult, } from "./renderers";
|
|
12
13
|
export { createArtifactService, type ArtifactPublisher, type ArtifactService, type ArtifactServiceOptions, } from "./service";
|
|
13
|
-
export { createMemoryArtifactStore, createMemoryArtifactAssetStore, type ArtifactAssetStore, type ArtifactStore, } from "./store";
|
|
14
|
+
export { createMemoryArtifactStore, createMemoryArtifactAssetStore, type ArtifactAssetStore, type ArtifactAssetTransaction, type ArtifactStore, } from "./store";
|
|
14
15
|
export { createArtifactTools, type ArtifactToolDefinition, type ArtifactToolMap, type ArtifactToolOptions, } from "./tools";
|
|
15
|
-
export { ARTIFACT_STATUSES, ArtifactError, type ArtifactAssetReference, type ArtifactAssetRole, type ArtifactAssetWriteInput, type ArtifactCapability, type ArtifactCreateInput, type ArtifactErrorCode, type ArtifactListQuery, type ArtifactProvenance, type ArtifactPublication, type ArtifactRecord, type ArtifactRevision, type ArtifactStatus, type ArtifactUpdateInput, } from "./types";
|
|
16
|
+
export { ARTIFACT_STATUSES, ARTIFACT_EVENT_TYPES, ArtifactError, type ArtifactAssetReference, type ArtifactAssetRole, type ArtifactAssetWriteInput, type ArtifactBundleCreateInput, type ArtifactCapability, type ArtifactCreateInput, type ArtifactErrorCode, type ArtifactEvent, type ArtifactEventQuery, type ArtifactEventType, type ArtifactGarbageCollectionResult, type ArtifactIndexingState, type ArtifactIndexingStatus, type ArtifactListQuery, type ArtifactLineageReference, type ArtifactLineageRelation, type ArtifactProvenance, type ArtifactPublication, type ArtifactPublishInput, type ArtifactRecord, type ArtifactRevision, type ArtifactRetentionCandidate, type ArtifactStatus, type ArtifactUpdateInput, } from "./types";
|
package/dist/src/manifest.d.ts
CHANGED
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
export declare const manifest: import("@absolutejs/manifest").PackageManifest<Record<string, never>, {
|
|
2
2
|
archive: (ownerId: string, artifactId: string) => Promise<import("./types").ArtifactRecord>;
|
|
3
3
|
attach: (ownerId: string, artifactId: string, input: import("./types").ArtifactAssetWriteInput, expectedRevision?: number) => Promise<import("./types").ArtifactRecord>;
|
|
4
|
+
attachBundle: (ownerId: string, artifactId: string, inputs: import("./types").ArtifactAssetWriteInput[], expectedRevision?: number) => Promise<import("./types").ArtifactRecord>;
|
|
5
|
+
collectAssetGarbage: (input: {
|
|
6
|
+
dryRun?: boolean;
|
|
7
|
+
minimumAgeMs?: number;
|
|
8
|
+
}) => Promise<import("./types").ArtifactGarbageCollectionResult>;
|
|
4
9
|
create: (ownerId: string, input: import("./types").ArtifactCreateInput) => Promise<import("./types").ArtifactRecord>;
|
|
10
|
+
createBundle: (ownerId: string, input: import("./types").ArtifactBundleCreateInput) => Promise<import("./types").ArtifactRecord>;
|
|
5
11
|
detach: (ownerId: string, artifactId: string, assetId: string, expectedRevision?: number) => Promise<import("./types").ArtifactRecord>;
|
|
6
12
|
get: (ownerId: string, artifactId: string) => Promise<import("./types").ArtifactRecord>;
|
|
13
|
+
getIndexingState: (ownerId: string, artifactId: string) => Promise<import("./types").ArtifactIndexingState | null>;
|
|
7
14
|
getRevision: (ownerId: string, artifactId: string, revision: number) => Promise<Readonly<import("./types").ArtifactRecord<unknown>>>;
|
|
8
15
|
list: (ownerId: string, query?: import("./types").ArtifactListQuery) => Promise<import("./types").ArtifactRecord[]>;
|
|
16
|
+
listEvents: (query?: import("./types").ArtifactEventQuery) => Promise<import("./types").ArtifactEvent[]>;
|
|
9
17
|
listRevisions: (ownerId: string, artifactId: string) => Promise<Readonly<import("./types").ArtifactRecord<unknown>>[]>;
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
markEventProcessed: (eventId: string, processedAt?: string) => Promise<boolean>;
|
|
19
|
+
markIndexing: (ownerId: string, artifactId: string, input: {
|
|
20
|
+
documentIds?: string[];
|
|
21
|
+
error?: string;
|
|
22
|
+
revision: number;
|
|
23
|
+
status: import("./types").ArtifactIndexingStatus;
|
|
24
|
+
}) => Promise<import("./types").ArtifactIndexingState>;
|
|
25
|
+
publish: (ownerId: string, artifactId: string, input?: import("./types").ArtifactPublishInput) => Promise<import("./types").ArtifactRecord>;
|
|
12
26
|
readAsset: (ownerId: string, artifactId: string, assetId: string) => Promise<{
|
|
13
27
|
asset: import("./types").ArtifactAssetReference;
|
|
14
28
|
data: Uint8Array<ArrayBufferLike>;
|
|
15
29
|
}>;
|
|
16
30
|
restore: (ownerId: string, artifactId: string, revision: number, expectedRevision?: number) => Promise<import("./types").ArtifactRecord>;
|
|
31
|
+
unpublish: (ownerId: string, artifactId: string) => Promise<import("./types").ArtifactRecord>;
|
|
17
32
|
update: (ownerId: string, artifactId: string, input: import("./types").ArtifactUpdateInput) => Promise<import("./types").ArtifactRecord>;
|
|
18
33
|
}>;
|
package/dist/src/rag.d.ts
CHANGED
|
@@ -8,8 +8,39 @@ export type ArtifactRAGAssetReader = {
|
|
|
8
8
|
export type ArtifactRAGUploadOptions = {
|
|
9
9
|
includeStructuredContent?: boolean;
|
|
10
10
|
};
|
|
11
|
+
export type ArtifactRAGIndexTarget = {
|
|
12
|
+
index(uploads: RAGDocumentUploadInput[], context: {
|
|
13
|
+
artifact: ArtifactRecord;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
documentIds: string[];
|
|
16
|
+
}>;
|
|
17
|
+
remove?(documentIds: string[], context: {
|
|
18
|
+
artifact: ArtifactRecord;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
export type ArtifactRAGIndexStateWriter = {
|
|
22
|
+
getIndexingState(ownerId: string, artifactId: string): Promise<{
|
|
23
|
+
documentIds: string[];
|
|
24
|
+
} | null>;
|
|
25
|
+
markIndexing(ownerId: string, artifactId: string, input: {
|
|
26
|
+
documentIds?: string[];
|
|
27
|
+
error?: string;
|
|
28
|
+
revision: number;
|
|
29
|
+
status: "failed" | "indexed" | "pending" | "stale";
|
|
30
|
+
}): Promise<unknown>;
|
|
31
|
+
};
|
|
11
32
|
/**
|
|
12
33
|
* Resolve an artifact revision into upload inputs accepted by @absolutejs/rag.
|
|
13
34
|
* Storage URIs remain opaque; only the supplied reader is allowed to access bytes.
|
|
14
35
|
*/
|
|
15
36
|
export declare const artifactToRAGUploads: (artifact: ArtifactRecord, reader: ArtifactRAGAssetReader, options?: ArtifactRAGUploadOptions) => Promise<RAGDocumentUploadInput[]>;
|
|
37
|
+
export declare const createArtifactRAGIndexCoordinator: (options: {
|
|
38
|
+
reader: ArtifactRAGAssetReader;
|
|
39
|
+
service: ArtifactRAGIndexStateWriter;
|
|
40
|
+
target: ArtifactRAGIndexTarget;
|
|
41
|
+
}) => {
|
|
42
|
+
index: (artifact: ArtifactRecord) => Promise<{
|
|
43
|
+
documentIds: string[];
|
|
44
|
+
}>;
|
|
45
|
+
};
|
|
46
|
+
export type ArtifactRAGIndexCoordinator = ReturnType<typeof createArtifactRAGIndexCoordinator>;
|
package/dist/src/service.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { ArtifactKindDefinitions, ArtifactRegistry } from "./registry";
|
|
2
2
|
import type { ArtifactAssetStore, ArtifactStore } from "./store";
|
|
3
|
-
import { type ArtifactAssetReference, type ArtifactAssetWriteInput, type ArtifactCreateInput, type ArtifactListQuery, type ArtifactRecord, type ArtifactUpdateInput } from "./types";
|
|
3
|
+
import { type ArtifactAssetReference, type ArtifactAssetWriteInput, type ArtifactBundleCreateInput, type ArtifactCreateInput, type ArtifactEvent, type ArtifactEventQuery, type ArtifactGarbageCollectionResult, type ArtifactIndexingState, type ArtifactIndexingStatus, type ArtifactListQuery, type ArtifactPublishInput, type ArtifactRecord, type ArtifactUpdateInput } from "./types";
|
|
4
4
|
export type ArtifactPublisher = {
|
|
5
5
|
publish(artifact: ArtifactRecord, options: {
|
|
6
6
|
idempotencyKey: string;
|
|
7
|
+
mode: "live" | "pinned";
|
|
8
|
+
revision: number;
|
|
7
9
|
}): Promise<{
|
|
8
10
|
id: string;
|
|
9
11
|
url: string;
|
|
@@ -15,6 +17,7 @@ export type ArtifactPublisher = {
|
|
|
15
17
|
export type ArtifactServiceOptions<TDefinitions extends ArtifactKindDefinitions = ArtifactKindDefinitions> = {
|
|
16
18
|
assetStore?: ArtifactAssetStore;
|
|
17
19
|
clock?: () => Date;
|
|
20
|
+
eventIdFactory?: () => string;
|
|
18
21
|
idFactory?: () => string;
|
|
19
22
|
publisher?: ArtifactPublisher;
|
|
20
23
|
registry: ArtifactRegistry<TDefinitions>;
|
|
@@ -24,18 +27,33 @@ export type ArtifactService = ReturnType<typeof createArtifactService>;
|
|
|
24
27
|
export declare const createArtifactService: <TDefinitions extends ArtifactKindDefinitions>(options: ArtifactServiceOptions<TDefinitions>) => {
|
|
25
28
|
archive: (ownerId: string, artifactId: string) => Promise<ArtifactRecord>;
|
|
26
29
|
attach: (ownerId: string, artifactId: string, input: ArtifactAssetWriteInput, expectedRevision?: number) => Promise<ArtifactRecord>;
|
|
30
|
+
attachBundle: (ownerId: string, artifactId: string, inputs: ArtifactAssetWriteInput[], expectedRevision?: number) => Promise<ArtifactRecord>;
|
|
31
|
+
collectAssetGarbage: (input: {
|
|
32
|
+
dryRun?: boolean;
|
|
33
|
+
minimumAgeMs?: number;
|
|
34
|
+
}) => Promise<ArtifactGarbageCollectionResult>;
|
|
27
35
|
create: (ownerId: string, input: ArtifactCreateInput) => Promise<ArtifactRecord>;
|
|
36
|
+
createBundle: (ownerId: string, input: ArtifactBundleCreateInput) => Promise<ArtifactRecord>;
|
|
28
37
|
detach: (ownerId: string, artifactId: string, assetId: string, expectedRevision?: number) => Promise<ArtifactRecord>;
|
|
29
38
|
get: (ownerId: string, artifactId: string) => Promise<ArtifactRecord>;
|
|
39
|
+
getIndexingState: (ownerId: string, artifactId: string) => Promise<ArtifactIndexingState | null>;
|
|
30
40
|
getRevision: (ownerId: string, artifactId: string, revision: number) => Promise<Readonly<ArtifactRecord<unknown>>>;
|
|
31
41
|
list: (ownerId: string, query?: ArtifactListQuery) => Promise<ArtifactRecord[]>;
|
|
42
|
+
listEvents: (query?: ArtifactEventQuery) => Promise<ArtifactEvent[]>;
|
|
32
43
|
listRevisions: (ownerId: string, artifactId: string) => Promise<Readonly<ArtifactRecord<unknown>>[]>;
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
markEventProcessed: (eventId: string, processedAt?: string) => Promise<boolean>;
|
|
45
|
+
markIndexing: (ownerId: string, artifactId: string, input: {
|
|
46
|
+
documentIds?: string[];
|
|
47
|
+
error?: string;
|
|
48
|
+
revision: number;
|
|
49
|
+
status: ArtifactIndexingStatus;
|
|
50
|
+
}) => Promise<ArtifactIndexingState>;
|
|
51
|
+
publish: (ownerId: string, artifactId: string, input?: ArtifactPublishInput) => Promise<ArtifactRecord>;
|
|
35
52
|
readAsset: (ownerId: string, artifactId: string, assetId: string) => Promise<{
|
|
36
53
|
asset: ArtifactAssetReference;
|
|
37
54
|
data: Uint8Array<ArrayBufferLike>;
|
|
38
55
|
}>;
|
|
39
56
|
restore: (ownerId: string, artifactId: string, revision: number, expectedRevision?: number) => Promise<ArtifactRecord>;
|
|
57
|
+
unpublish: (ownerId: string, artifactId: string) => Promise<ArtifactRecord>;
|
|
40
58
|
update: (ownerId: string, artifactId: string, input: ArtifactUpdateInput) => Promise<ArtifactRecord>;
|
|
41
59
|
};
|
package/dist/src/store.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import type { ArtifactAssetReference, ArtifactAssetWriteInput, ArtifactListQuery, ArtifactRecord, ArtifactRevision } from "./types";
|
|
1
|
+
import type { ArtifactAssetReference, ArtifactAssetWriteInput, ArtifactEvent, ArtifactEventQuery, ArtifactIndexingState, ArtifactListQuery, ArtifactRecord, ArtifactRevision, ArtifactRetentionCandidate } from "./types";
|
|
2
|
+
export type ArtifactAssetTransaction = {
|
|
3
|
+
commit(): Promise<void>;
|
|
4
|
+
references: ArtifactAssetReference[];
|
|
5
|
+
rollback(): Promise<void>;
|
|
6
|
+
};
|
|
2
7
|
export type ArtifactAssetStore = {
|
|
8
|
+
delete(reference: ArtifactAssetReference): Promise<void>;
|
|
9
|
+
listCandidates(): Promise<ArtifactRetentionCandidate[]>;
|
|
3
10
|
read(reference: ArtifactAssetReference, context: {
|
|
4
11
|
artifact: ArtifactRecord;
|
|
5
12
|
}): Promise<Uint8Array>;
|
|
@@ -7,16 +14,25 @@ export type ArtifactAssetStore = {
|
|
|
7
14
|
artifact: ArtifactRecord;
|
|
8
15
|
idempotencyKey: string;
|
|
9
16
|
}): Promise<ArtifactAssetReference>;
|
|
17
|
+
stage?(inputs: ArtifactAssetWriteInput[], context: {
|
|
18
|
+
artifact: ArtifactRecord;
|
|
19
|
+
idempotencyKey: string;
|
|
20
|
+
}): Promise<ArtifactAssetTransaction>;
|
|
10
21
|
};
|
|
11
22
|
export type ArtifactStore = {
|
|
12
23
|
/** Persist the current record and its first immutable revision atomically. */
|
|
13
|
-
create(record: ArtifactRecord): Promise<void>;
|
|
24
|
+
create(record: ArtifactRecord, events?: ArtifactEvent[]): Promise<void>;
|
|
25
|
+
getIndexingState(ownerId: string, artifactId: string): Promise<ArtifactIndexingState | null>;
|
|
14
26
|
get(ownerId: string, artifactId: string): Promise<ArtifactRecord | null>;
|
|
15
27
|
getRevision(ownerId: string, artifactId: string, revision: number): Promise<ArtifactRevision | null>;
|
|
16
28
|
list(ownerId: string, query?: ArtifactListQuery): Promise<ArtifactRecord[]>;
|
|
17
29
|
listRevisions(ownerId: string, artifactId: string): Promise<ArtifactRevision[]>;
|
|
30
|
+
listEvents(query?: ArtifactEventQuery): Promise<ArtifactEvent[]>;
|
|
31
|
+
listReferencedAssetIds(): Promise<string[]>;
|
|
32
|
+
markEventProcessed(eventId: string, processedAt: string): Promise<boolean>;
|
|
33
|
+
putIndexingState(ownerId: string, state: ArtifactIndexingState, events?: ArtifactEvent[]): Promise<void>;
|
|
18
34
|
/** Compare, replace current state, and append its revision atomically. */
|
|
19
|
-
save(record: ArtifactRecord, expectedRevision: number): Promise<boolean>;
|
|
35
|
+
save(record: ArtifactRecord, expectedRevision: number, events?: ArtifactEvent[]): Promise<boolean>;
|
|
20
36
|
};
|
|
21
37
|
export declare const createMemoryArtifactAssetStore: () => ArtifactAssetStore;
|
|
22
38
|
export declare const createMemoryArtifactStore: (initial?: ArtifactRecord[]) => ArtifactStore;
|
package/dist/src/types.d.ts
CHANGED
|
@@ -2,14 +2,24 @@ export declare const ARTIFACT_STATUSES: readonly ["draft", "published", "archive
|
|
|
2
2
|
export type ArtifactStatus = (typeof ARTIFACT_STATUSES)[number];
|
|
3
3
|
export type ArtifactCapability = "attach" | "archive" | "edit" | "export" | "preview" | "publish" | "refine";
|
|
4
4
|
export type ArtifactProvenance = {
|
|
5
|
+
lineage?: ArtifactLineageReference[];
|
|
5
6
|
model?: string;
|
|
6
7
|
sourceIds?: string[];
|
|
7
8
|
tool?: string;
|
|
8
9
|
traceId?: string;
|
|
9
10
|
};
|
|
11
|
+
export type ArtifactLineageRelation = "derived_from" | "generated_from" | "references" | "replaces";
|
|
12
|
+
export type ArtifactLineageReference = {
|
|
13
|
+
artifactId?: string;
|
|
14
|
+
relation: ArtifactLineageRelation;
|
|
15
|
+
revision?: number;
|
|
16
|
+
sourceId?: string;
|
|
17
|
+
};
|
|
10
18
|
export type ArtifactPublication = {
|
|
11
19
|
id: string;
|
|
20
|
+
mode: "live" | "pinned";
|
|
12
21
|
publishedAt: string;
|
|
22
|
+
revision: number;
|
|
13
23
|
url: string;
|
|
14
24
|
};
|
|
15
25
|
export type ArtifactAssetRole = "attachment" | "primary" | "preview" | "source";
|
|
@@ -19,6 +29,7 @@ export type ArtifactAssetReference = {
|
|
|
19
29
|
value: string;
|
|
20
30
|
};
|
|
21
31
|
id: string;
|
|
32
|
+
createdAt: string;
|
|
22
33
|
mediaType: string;
|
|
23
34
|
metadata?: Record<string, unknown>;
|
|
24
35
|
name: string;
|
|
@@ -52,6 +63,33 @@ export type ArtifactRecord<TContent = unknown> = {
|
|
|
52
63
|
title: string;
|
|
53
64
|
updatedAt: string;
|
|
54
65
|
};
|
|
66
|
+
export declare const ARTIFACT_EVENT_TYPES: readonly ["artifact.archived", "artifact.asset_attached", "artifact.asset_detached", "artifact.created", "artifact.generated", "artifact.indexing_changed", "artifact.published", "artifact.restored", "artifact.revised", "artifact.unpublished"];
|
|
67
|
+
export type ArtifactEventType = (typeof ARTIFACT_EVENT_TYPES)[number];
|
|
68
|
+
export type ArtifactEvent = {
|
|
69
|
+
artifactId: string;
|
|
70
|
+
createdAt: string;
|
|
71
|
+
id: string;
|
|
72
|
+
ownerId: string;
|
|
73
|
+
payload?: Record<string, unknown>;
|
|
74
|
+
processedAt?: string;
|
|
75
|
+
revision: number;
|
|
76
|
+
type: ArtifactEventType;
|
|
77
|
+
};
|
|
78
|
+
export type ArtifactEventQuery = {
|
|
79
|
+
limit?: number;
|
|
80
|
+
processed?: boolean;
|
|
81
|
+
type?: ArtifactEventType;
|
|
82
|
+
};
|
|
83
|
+
export type ArtifactIndexingStatus = "failed" | "indexed" | "pending" | "stale";
|
|
84
|
+
export type ArtifactIndexingState = {
|
|
85
|
+
artifactId: string;
|
|
86
|
+
documentIds: string[];
|
|
87
|
+
error?: string;
|
|
88
|
+
indexedAt?: string;
|
|
89
|
+
revision: number;
|
|
90
|
+
status: ArtifactIndexingStatus;
|
|
91
|
+
updatedAt: string;
|
|
92
|
+
};
|
|
55
93
|
/** An immutable point-in-time copy of an artifact record. */
|
|
56
94
|
export type ArtifactRevision<TContent = unknown> = Readonly<ArtifactRecord<TContent>>;
|
|
57
95
|
export type ArtifactListQuery = {
|
|
@@ -75,7 +113,21 @@ export type ArtifactUpdateInput = {
|
|
|
75
113
|
metadata?: Record<string, unknown>;
|
|
76
114
|
title?: string;
|
|
77
115
|
};
|
|
78
|
-
export type
|
|
116
|
+
export type ArtifactBundleCreateInput = Omit<ArtifactCreateInput, "assets"> & {
|
|
117
|
+
assets?: ArtifactAssetWriteInput[];
|
|
118
|
+
};
|
|
119
|
+
export type ArtifactPublishInput = {
|
|
120
|
+
mode?: "live" | "pinned";
|
|
121
|
+
};
|
|
122
|
+
export type ArtifactRetentionCandidate = {
|
|
123
|
+
createdAt: string;
|
|
124
|
+
reference: ArtifactAssetReference;
|
|
125
|
+
};
|
|
126
|
+
export type ArtifactGarbageCollectionResult = {
|
|
127
|
+
deleted: ArtifactAssetReference[];
|
|
128
|
+
retained: ArtifactAssetReference[];
|
|
129
|
+
};
|
|
130
|
+
export type ArtifactErrorCode = "asset_store_unavailable" | "asset_transaction_unavailable" | "conflict" | "generator_unavailable" | "invalid_content" | "not_found" | "publisher_unavailable" | "renderer_unavailable" | "unsupported_capability" | "unknown_kind";
|
|
79
131
|
export declare class ArtifactError extends Error {
|
|
80
132
|
readonly code: ArtifactErrorCode;
|
|
81
133
|
constructor(code: ArtifactErrorCode, message: string);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@absolutejs/artifacts",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Typed, versioned artifacts for AI products — schemas, lifecycle, storage, rendering, publishing, revisions, and agent tools without prescribing a database or host.",
|
|
5
5
|
"author": "Alex Kahn",
|
|
6
6
|
"license": "BUSL-1.1",
|