@absolutejs/artifacts 0.2.0 → 0.2.1

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 CHANGED
@@ -24,6 +24,7 @@ routes, authorization, UI, or hosting.
24
24
  datasets, code, images, audio, video, email, archives, and generic files
25
25
  - An optional bridge to `@absolutejs/rag` ingestion
26
26
  - Provider-neutral generation registries with atomic multi-file bundles
27
+ - Staged, validate-before-commit multi-artifact batches with completion receipts
27
28
  - Revision-pinned or explicitly live publications
28
29
  - Durable lifecycle events designed for transactional outboxes
29
30
  - Per-revision RAG indexing state and an indexing coordinator
@@ -169,6 +170,45 @@ const report = await artifacts.createBundle("owner-123", {
169
170
  });
170
171
  ```
171
172
 
173
+ When one job must produce several artifacts together, stage the complete batch,
174
+ run domain validators before persistence, and commit it through a store that
175
+ implements `createBatch` (the package Drizzle and memory stores do):
176
+
177
+ ```ts
178
+ const staged = await artifacts.stageBatch(
179
+ "owner-123",
180
+ {
181
+ evidence: [{ sourceId: "research-run-123", sourceUrl: sourceUrl }],
182
+ items: [
183
+ { artifact: worksheetInput, key: "worksheet" },
184
+ { artifact: reportInput, key: "report" },
185
+ ],
186
+ provenance: { tool: "prospecting_mission", traceId: mission.id },
187
+ },
188
+ {
189
+ validators: [
190
+ ({ items }) =>
191
+ validateDomainEvidence(items).map((message) => ({
192
+ code: "evidence_invalid",
193
+ message,
194
+ })),
195
+ ],
196
+ },
197
+ );
198
+
199
+ const receipt = await staged.commit();
200
+ if (receipt.status !== "committed") throw new Error(receipt.error);
201
+ ```
202
+
203
+ Every artifact carries its batch id, item key, evidence, and provenance.
204
+ `commit()` returns the durable reconciliation receipt: artifact ids,
205
+ revisions, atomicity, validation outcome, and any archived compensation. The
206
+ default mode requires an atomic `ArtifactStore.createBatch`. Hosts that cannot
207
+ provide a transaction may explicitly select `archive_on_failure`; completed
208
+ earlier writes are then archived and reported as `rolled_back` or
209
+ `partial_failure`. Calling `rollback()` before commit releases all staged
210
+ asset transactions.
211
+
172
212
  ## Generation
173
213
 
174
214
  Generators are provider-neutral. They return validated structured content and
package/dist/drizzle.js CHANGED
@@ -145,6 +145,17 @@ var createDrizzleArtifactStore = (options) => ({
145
145
  if (events.length > 0)
146
146
  await transaction.insert(artifactEvents).values(eventRows(events));
147
147
  }),
148
+ createBatch: (entries) => options.db.transaction(async (transaction) => {
149
+ if (entries.length === 0)
150
+ return;
151
+ for (const { events: events2 = [], record } of entries)
152
+ assertEventsBelongToArtifact(record, events2);
153
+ await transaction.insert(artifactRecords).values(entries.map(({ record }) => recordRow(record)));
154
+ await transaction.insert(artifactRevisions).values(entries.map(({ record }) => revisionRow(record)));
155
+ const events = entries.flatMap((entry) => entry.events ?? []);
156
+ if (events.length > 0)
157
+ await transaction.insert(artifactEvents).values(eventRows(events));
158
+ }),
148
159
  get: async (ownerId, artifactId) => {
149
160
  const [row] = await options.db.select({ document: artifactRecords.document }).from(artifactRecords).where(and(eq(artifactRecords.id, artifactId), eq(artifactRecords.ownerId, ownerId))).limit(1);
150
161
  return row?.document ?? null;
@@ -235,18 +246,18 @@ var createDrizzleArtifactStore = (options) => ({
235
246
  })
236
247
  });
237
248
  export {
238
- createDrizzleArtifactStore,
239
- artifactRevisions,
240
- artifactRecords,
241
- artifactIndexingStates,
242
- artifactEvents,
243
- artifactDrizzleSchema,
244
- ArtifactRevisionSelectSchema,
245
- ArtifactRevisionInsertSchema,
246
- ArtifactRecordSelectSchema,
247
- ArtifactRecordInsertSchema,
248
- ArtifactIndexingStateSelectSchema,
249
- ArtifactIndexingStateInsertSchema,
249
+ ArtifactEventInsertSchema,
250
250
  ArtifactEventSelectSchema,
251
- ArtifactEventInsertSchema
251
+ ArtifactIndexingStateInsertSchema,
252
+ ArtifactIndexingStateSelectSchema,
253
+ ArtifactRecordInsertSchema,
254
+ ArtifactRecordSelectSchema,
255
+ ArtifactRevisionInsertSchema,
256
+ ArtifactRevisionSelectSchema,
257
+ artifactDrizzleSchema,
258
+ artifactEvents,
259
+ artifactIndexingStates,
260
+ artifactRecords,
261
+ artifactRevisions,
262
+ createDrizzleArtifactStore
252
263
  };
package/dist/index.js CHANGED
@@ -192,6 +192,7 @@ var createArtifactService = (options) => {
192
192
  const now = () => (options.clock ?? (() => new Date))().toISOString();
193
193
  const idFactory = options.idFactory ?? (() => crypto.randomUUID());
194
194
  const eventIdFactory = options.eventIdFactory ?? (() => crypto.randomUUID());
195
+ const batchIdFactory = options.batchIdFactory ?? (() => crypto.randomUUID());
195
196
  const event = (artifact, type, payload) => ({
196
197
  artifactId: artifact.id,
197
198
  createdAt: now(),
@@ -391,6 +392,231 @@ var createArtifactService = (options) => {
391
392
  throw error;
392
393
  }
393
394
  },
395
+ stageBatch: async (ownerId, input, stageOptions = {}) => {
396
+ if (input.items.length === 0) {
397
+ throw new ArtifactError("invalid_content", "An artifact batch must contain at least one artifact");
398
+ }
399
+ const keys = input.items.map((item) => item.key.trim());
400
+ if (keys.some((key) => key.length === 0) || new Set(keys).size !== keys.length) {
401
+ throw new ArtifactError("invalid_content", "Artifact batch item keys must be non-empty and unique");
402
+ }
403
+ const bundleId = input.bundleId ?? batchIdFactory();
404
+ const stagedAt = now();
405
+ const sharedEvidence = input.evidence ?? [];
406
+ const staged = [];
407
+ const evidenceJson = (evidence) => evidence.map((reference) => ({
408
+ ...reference.capturedAt ? { capturedAt: reference.capturedAt } : {},
409
+ ...reference.excerpt ? { excerpt: reference.excerpt } : {},
410
+ ...reference.metadata ? { metadata: reference.metadata } : {},
411
+ ...reference.sourceId ? { sourceId: reference.sourceId } : {},
412
+ ...reference.sourceUrl ? { sourceUrl: reference.sourceUrl } : {}
413
+ }));
414
+ try {
415
+ for (const [index, item] of input.items.entries()) {
416
+ const { assets: assetInputs = [], ...artifactInput } = item.artifact;
417
+ validateAssetInputs(item.artifact.kind, assetInputs);
418
+ const evidence = [...sharedEvidence, ...item.evidence ?? []];
419
+ const provisional = buildRecord(ownerId, {
420
+ ...artifactInput,
421
+ metadata: {
422
+ ...input.metadata,
423
+ ...artifactInput.metadata,
424
+ artifactBatch: {
425
+ bundleId,
426
+ evidence: evidenceJson(evidence),
427
+ itemKey: item.key
428
+ }
429
+ },
430
+ provenance: {
431
+ ...input.provenance,
432
+ ...artifactInput.provenance,
433
+ evidence,
434
+ sourceIds: [
435
+ ...new Set([
436
+ ...input.provenance?.sourceIds ?? [],
437
+ ...artifactInput.provenance?.sourceIds ?? [],
438
+ ...evidence.flatMap((reference) => reference.sourceId ? [reference.sourceId] : [])
439
+ ])
440
+ ]
441
+ }
442
+ }, []);
443
+ if (assetInputs.length === 0) {
444
+ staged.push({ evidence, key: item.key, record: provisional });
445
+ continue;
446
+ }
447
+ const assetStore = requireAssetTransactions();
448
+ const transaction = await assetStore.stage(assetInputs, {
449
+ artifact: provisional,
450
+ idempotencyKey: `artifact-batch:${bundleId}:${index}`
451
+ });
452
+ staged.push({
453
+ evidence,
454
+ key: item.key,
455
+ record: {
456
+ ...provisional,
457
+ assets: validateAssets(item.artifact.kind, transaction.references)
458
+ },
459
+ transaction
460
+ });
461
+ }
462
+ } catch (error) {
463
+ await Promise.allSettled(staged.map((item) => item.transaction?.rollback()));
464
+ throw error;
465
+ }
466
+ let validationIssues;
467
+ try {
468
+ validationIssues = (await Promise.all((stageOptions.validators ?? []).map((validate) => validate({
469
+ bundleId,
470
+ evidence: sharedEvidence,
471
+ items: staged,
472
+ ownerId
473
+ })))).flat();
474
+ } catch (error) {
475
+ await Promise.allSettled(staged.map((item) => item.transaction?.rollback()));
476
+ throw error;
477
+ }
478
+ const validation = validationIssues.length === 0 ? { valid: true } : { issues: validationIssues, valid: false };
479
+ let settled;
480
+ const receiptItems = () => staged.map(({ key, record }) => ({
481
+ artifactId: record.id,
482
+ key,
483
+ kind: record.kind,
484
+ revision: record.revision,
485
+ title: record.title
486
+ }));
487
+ const finish = (partial) => {
488
+ settled = {
489
+ bundleId,
490
+ completedAt: now(),
491
+ items: partial.items ?? receiptItems(),
492
+ ownerId,
493
+ stagedAt,
494
+ validation,
495
+ ...partial
496
+ };
497
+ return settled;
498
+ };
499
+ const rollbackTransactions = async (candidates = staged) => {
500
+ const failures = [];
501
+ for (const item of candidates) {
502
+ if (!item.transaction)
503
+ continue;
504
+ try {
505
+ await item.transaction.rollback();
506
+ } catch {
507
+ failures.push(item.key);
508
+ }
509
+ }
510
+ return failures;
511
+ };
512
+ if (!validation.valid) {
513
+ const failures = await rollbackTransactions();
514
+ finish({
515
+ archivedArtifactIds: [],
516
+ atomic: Boolean(options.store.createBatch),
517
+ error: failures.length === 0 ? "Artifact batch validation failed" : `Artifact batch validation failed; asset rollback failed for: ${failures.join(", ")}`,
518
+ status: failures.length === 0 ? "rolled_back" : "partial_failure"
519
+ });
520
+ }
521
+ return {
522
+ bundleId,
523
+ commit: async () => {
524
+ if (settled)
525
+ return settled;
526
+ const transactions = staged.flatMap((item) => item.transaction ? [item.transaction] : []);
527
+ const entries = staged.map(({ key, record }) => ({
528
+ events: [
529
+ event(record, "artifact.created", { bundleId, itemKey: key }),
530
+ event(record, "artifact.generated", { bundleId, itemKey: key })
531
+ ],
532
+ record
533
+ }));
534
+ if (options.store.createBatch) {
535
+ try {
536
+ for (const transaction of transactions)
537
+ await transaction.commit();
538
+ await options.store.createBatch(entries);
539
+ return finish({
540
+ archivedArtifactIds: [],
541
+ atomic: true,
542
+ status: "committed"
543
+ });
544
+ } catch (error) {
545
+ const failures = await rollbackTransactions();
546
+ return finish({
547
+ archivedArtifactIds: [],
548
+ atomic: true,
549
+ error: error instanceof Error ? error.message : String(error),
550
+ status: failures.length === 0 ? "rolled_back" : "partial_failure"
551
+ });
552
+ }
553
+ }
554
+ if ((input.commitMode ?? "require_atomic") === "require_atomic") {
555
+ const failures = await rollbackTransactions();
556
+ return finish({
557
+ archivedArtifactIds: [],
558
+ atomic: false,
559
+ error: "The configured artifact store does not support atomic batches",
560
+ status: failures.length === 0 ? "rolled_back" : "partial_failure"
561
+ });
562
+ }
563
+ const created = [];
564
+ try {
565
+ for (const transaction of transactions)
566
+ await transaction.commit();
567
+ for (const [index, entry] of entries.entries()) {
568
+ await options.store.create(entry.record, entry.events);
569
+ created.push(staged[index]);
570
+ }
571
+ return finish({
572
+ archivedArtifactIds: [],
573
+ atomic: false,
574
+ status: "committed"
575
+ });
576
+ } catch (error) {
577
+ const archivedArtifactIds = [];
578
+ const archiveFailures = [];
579
+ for (const item of created) {
580
+ try {
581
+ await service.archive(ownerId, item.record.id);
582
+ archivedArtifactIds.push(item.record.id);
583
+ } catch {
584
+ archiveFailures.push(item.record.id);
585
+ }
586
+ }
587
+ const uncreated = staged.filter((item) => !created.includes(item));
588
+ const rollbackFailures = await rollbackTransactions(uncreated);
589
+ const failed = [...archiveFailures, ...rollbackFailures];
590
+ return finish({
591
+ archivedArtifactIds,
592
+ atomic: false,
593
+ error: error instanceof Error ? error.message : String(error),
594
+ items: receiptItems().map((item) => ({
595
+ ...item,
596
+ ...archivedArtifactIds.includes(item.artifactId) ? { archived: true } : {}
597
+ })),
598
+ status: failed.length === 0 ? "rolled_back" : "partial_failure"
599
+ });
600
+ }
601
+ },
602
+ evidence: sharedEvidence,
603
+ items: staged,
604
+ ownerId,
605
+ rollback: async (reason = "Artifact batch rolled back before commit") => {
606
+ if (settled)
607
+ return settled;
608
+ const failures = await rollbackTransactions();
609
+ return finish({
610
+ archivedArtifactIds: [],
611
+ atomic: Boolean(options.store.createBatch),
612
+ error: failures.length === 0 ? reason : `${reason}; asset rollback failed for: ${failures.join(", ")}`,
613
+ status: failures.length === 0 ? "rolled_back" : "partial_failure"
614
+ });
615
+ },
616
+ stagedAt,
617
+ validation
618
+ };
619
+ },
394
620
  detach: async (ownerId, artifactId, assetId, expectedRevision) => {
395
621
  const current = await get(ownerId, artifactId);
396
622
  requireCapability(current, "attach");
@@ -650,6 +876,21 @@ var createMemoryArtifactStore = (initial = []) => {
650
876
  for (const event of newEvents)
651
877
  events.set(event.id, clone(event));
652
878
  },
879
+ createBatch: async (entries) => {
880
+ const ids = entries.map(({ record }) => record.id);
881
+ if (new Set(ids).size !== ids.length) {
882
+ throw new Error("Duplicate artifact id in batch");
883
+ }
884
+ const duplicate = ids.find((id) => records.has(id));
885
+ if (duplicate)
886
+ throw new Error(`Duplicate artifact id: ${duplicate}`);
887
+ for (const { events: newEvents = [], record } of entries) {
888
+ records.set(record.id, clone(record));
889
+ revisions.set(record.id, [clone(record)]);
890
+ for (const event of newEvents)
891
+ events.set(event.id, clone(event));
892
+ }
893
+ },
653
894
  get: async (ownerId, artifactId) => {
654
895
  const record = records.get(artifactId);
655
896
  return record?.ownerId === ownerId ? clone(record) : null;
@@ -855,17 +1096,17 @@ var createArtifactTools = (options) => ({
855
1096
  }
856
1097
  });
857
1098
  export {
858
- standardArtifactDefinitions,
859
- isJsonValue,
860
- defineArtifactRegistry,
861
- createMemoryArtifactStore,
862
- createMemoryArtifactAssetStore,
863
- createArtifactTools,
864
- createArtifactService,
865
- createArtifactRendererRegistry,
866
- createArtifactGeneratorRegistry,
867
- STANDARD_ARTIFACT_KIND_NAMES,
868
- ArtifactError,
1099
+ ARTIFACT_EVENT_TYPES,
869
1100
  ARTIFACT_STATUSES,
870
- ARTIFACT_EVENT_TYPES
1101
+ ArtifactError,
1102
+ STANDARD_ARTIFACT_KIND_NAMES,
1103
+ createArtifactGeneratorRegistry,
1104
+ createArtifactRendererRegistry,
1105
+ createArtifactService,
1106
+ createArtifactTools,
1107
+ createMemoryArtifactAssetStore,
1108
+ createMemoryArtifactStore,
1109
+ defineArtifactRegistry,
1110
+ isJsonValue,
1111
+ standardArtifactDefinitions
871
1112
  };
package/dist/manifest.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- // ../manifest/dist/index.js
2
+ // node_modules/@absolutejs/manifest/dist/index.js
3
3
  import { Type } from "typebox";
4
4
  import { Value } from "typebox/value";
5
5
  import { Type as Type2 } from "typebox";
package/dist/rag.js CHANGED
@@ -75,6 +75,6 @@ var createArtifactRAGIndexCoordinator = (options) => ({
75
75
  }
76
76
  });
77
77
  export {
78
- createArtifactRAGIndexCoordinator,
79
- artifactToRAGUploads
78
+ artifactToRAGUploads,
79
+ createArtifactRAGIndexCoordinator
80
80
  };
@@ -13,4 +13,4 @@ export { createArtifactRendererRegistry, type ArtifactRenderer, type ArtifactRen
13
13
  export { createArtifactService, type ArtifactPublisher, type ArtifactService, type ArtifactServiceOptions, } from "./service";
14
14
  export { createMemoryArtifactStore, createMemoryArtifactAssetStore, type ArtifactAssetStore, type ArtifactAssetTransaction, type ArtifactStore, } from "./store";
15
15
  export { createArtifactTools, type ArtifactToolDefinition, type ArtifactToolMap, type ArtifactToolOptions, } from "./tools";
16
- export { ARTIFACT_STATUSES, ARTIFACT_EVENT_TYPES, ArtifactError, isJsonValue, 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, type JsonObject, type JsonPrimitive, type JsonValue, } from "./types";
16
+ export { ARTIFACT_STATUSES, ARTIFACT_EVENT_TYPES, ArtifactError, isJsonValue, type ArtifactAssetReference, type ArtifactAssetRole, type ArtifactAssetWriteInput, type ArtifactBatchCommitMode, type ArtifactBatchCompletionReceipt, type ArtifactBatchCreateInput, type ArtifactBatchItemInput, type ArtifactBatchReceiptItem, type ArtifactBatchValidationIssue, type ArtifactBatchValidationResult, type ArtifactBatchValidator, type ArtifactBundleCreateInput, type ArtifactCapability, type ArtifactCreateInput, type ArtifactErrorCode, type ArtifactEvent, type ArtifactEventQuery, type ArtifactEventType, type ArtifactEvidenceReference, 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, type JsonObject, type JsonPrimitive, type JsonValue, type StagedArtifactBatch, } from "./types";
@@ -9,6 +9,9 @@ export declare const manifest: Omit<import("@absolutejs/manifest").PackageManife
9
9
  }) => Promise<import("./types").ArtifactGarbageCollectionResult>;
10
10
  create: (ownerId: string, input: import("./types").ArtifactCreateInput) => Promise<import("./types").ArtifactRecord>;
11
11
  createBundle: (ownerId: string, input: import("./types").ArtifactBundleCreateInput) => Promise<import("./types").ArtifactRecord>;
12
+ stageBatch: (ownerId: string, input: import("./types").ArtifactBatchCreateInput, stageOptions?: {
13
+ validators?: import("./types").ArtifactBatchValidator[];
14
+ }) => Promise<import("./types").StagedArtifactBatch>;
12
15
  detach: (ownerId: string, artifactId: string, assetId: string, expectedRevision?: number) => Promise<import("./types").ArtifactRecord>;
13
16
  get: (ownerId: string, artifactId: string) => Promise<import("./types").ArtifactRecord>;
14
17
  getIndexingState: (ownerId: string, artifactId: string) => Promise<import("./types").ArtifactIndexingState | null>;
@@ -44,6 +47,9 @@ export declare const manifest: Omit<import("@absolutejs/manifest").PackageManife
44
47
  }) => Promise<import("./types").ArtifactGarbageCollectionResult>;
45
48
  create: (ownerId: string, input: import("./types").ArtifactCreateInput) => Promise<import("./types").ArtifactRecord>;
46
49
  createBundle: (ownerId: string, input: import("./types").ArtifactBundleCreateInput) => Promise<import("./types").ArtifactRecord>;
50
+ stageBatch: (ownerId: string, input: import("./types").ArtifactBatchCreateInput, stageOptions?: {
51
+ validators?: import("./types").ArtifactBatchValidator[];
52
+ }) => Promise<import("./types").StagedArtifactBatch>;
47
53
  detach: (ownerId: string, artifactId: string, assetId: string, expectedRevision?: number) => Promise<import("./types").ArtifactRecord>;
48
54
  get: (ownerId: string, artifactId: string) => Promise<import("./types").ArtifactRecord>;
49
55
  getIndexingState: (ownerId: string, artifactId: string) => Promise<import("./types").ArtifactIndexingState | null>;
@@ -80,6 +86,9 @@ export declare const manifest: Omit<import("@absolutejs/manifest").PackageManife
80
86
  }) => Promise<import("./types").ArtifactGarbageCollectionResult>;
81
87
  create: (ownerId: string, input: import("./types").ArtifactCreateInput) => Promise<import("./types").ArtifactRecord>;
82
88
  createBundle: (ownerId: string, input: import("./types").ArtifactBundleCreateInput) => Promise<import("./types").ArtifactRecord>;
89
+ stageBatch: (ownerId: string, input: import("./types").ArtifactBatchCreateInput, stageOptions?: {
90
+ validators?: import("./types").ArtifactBatchValidator[];
91
+ }) => Promise<import("./types").StagedArtifactBatch>;
83
92
  detach: (ownerId: string, artifactId: string, assetId: string, expectedRevision?: number) => Promise<import("./types").ArtifactRecord>;
84
93
  get: (ownerId: string, artifactId: string) => Promise<import("./types").ArtifactRecord>;
85
94
  getIndexingState: (ownerId: string, artifactId: string) => Promise<import("./types").ArtifactIndexingState | null>;
@@ -1,6 +1,6 @@
1
1
  import type { ArtifactKindDefinitions, ArtifactRegistry } from "./registry";
2
2
  import type { ArtifactAssetStore, ArtifactStore } from "./store";
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";
3
+ import { type ArtifactAssetReference, type ArtifactAssetWriteInput, type ArtifactBatchCreateInput, type ArtifactBatchValidator, type ArtifactBundleCreateInput, type ArtifactCreateInput, type ArtifactEvent, type ArtifactEventQuery, type ArtifactGarbageCollectionResult, type ArtifactIndexingState, type ArtifactIndexingStatus, type ArtifactListQuery, type ArtifactPublishInput, type ArtifactRecord, type ArtifactUpdateInput, type StagedArtifactBatch } from "./types";
4
4
  export type ArtifactPublisher = {
5
5
  publish(artifact: ArtifactRecord, options: {
6
6
  idempotencyKey: string;
@@ -16,6 +16,7 @@ export type ArtifactPublisher = {
16
16
  };
17
17
  export type ArtifactServiceOptions<TDefinitions extends ArtifactKindDefinitions = ArtifactKindDefinitions> = {
18
18
  assetStore?: ArtifactAssetStore;
19
+ batchIdFactory?: () => string;
19
20
  clock?: () => Date;
20
21
  eventIdFactory?: () => string;
21
22
  idFactory?: () => string;
@@ -34,6 +35,9 @@ export declare const createArtifactService: <TDefinitions extends ArtifactKindDe
34
35
  }) => Promise<ArtifactGarbageCollectionResult>;
35
36
  create: (ownerId: string, input: ArtifactCreateInput) => Promise<ArtifactRecord>;
36
37
  createBundle: (ownerId: string, input: ArtifactBundleCreateInput) => Promise<ArtifactRecord>;
38
+ stageBatch: (ownerId: string, input: ArtifactBatchCreateInput, stageOptions?: {
39
+ validators?: ArtifactBatchValidator[];
40
+ }) => Promise<StagedArtifactBatch>;
37
41
  detach: (ownerId: string, artifactId: string, assetId: string, expectedRevision?: number) => Promise<ArtifactRecord>;
38
42
  get: (ownerId: string, artifactId: string) => Promise<ArtifactRecord>;
39
43
  getIndexingState: (ownerId: string, artifactId: string) => Promise<ArtifactIndexingState | null>;
@@ -22,6 +22,11 @@ export type ArtifactAssetStore = {
22
22
  export type ArtifactStore = {
23
23
  /** Persist the current record and its first immutable revision atomically. */
24
24
  create(record: ArtifactRecord, events?: ArtifactEvent[]): Promise<void>;
25
+ /** Persist multiple records, first revisions, and events in one transaction. */
26
+ createBatch?(entries: Array<{
27
+ events?: ArtifactEvent[];
28
+ record: ArtifactRecord;
29
+ }>): Promise<void>;
25
30
  getIndexingState(ownerId: string, artifactId: string): Promise<ArtifactIndexingState | null>;
26
31
  get(ownerId: string, artifactId: string): Promise<ArtifactRecord | null>;
27
32
  getRevision(ownerId: string, artifactId: string, revision: number): Promise<ArtifactRevision | null>;
@@ -8,12 +8,20 @@ export declare function isJsonValue(value: unknown): value is JsonValue;
8
8
  export type ArtifactStatus = (typeof ARTIFACT_STATUSES)[number];
9
9
  export type ArtifactCapability = "attach" | "archive" | "edit" | "export" | "preview" | "publish" | "refine";
10
10
  export type ArtifactProvenance = {
11
+ evidence?: ArtifactEvidenceReference[];
11
12
  lineage?: ArtifactLineageReference[];
12
13
  model?: string;
13
14
  sourceIds?: string[];
14
15
  tool?: string;
15
16
  traceId?: string;
16
17
  };
18
+ export type ArtifactEvidenceReference = {
19
+ capturedAt?: string;
20
+ excerpt?: string;
21
+ metadata?: JsonObject;
22
+ sourceId?: string;
23
+ sourceUrl?: string;
24
+ };
17
25
  export type ArtifactLineageRelation = "derived_from" | "generated_from" | "references" | "replaces";
18
26
  export type ArtifactLineageReference = {
19
27
  artifactId?: string;
@@ -122,6 +130,78 @@ export type ArtifactUpdateInput = {
122
130
  export type ArtifactBundleCreateInput = Omit<ArtifactCreateInput, "assets"> & {
123
131
  assets?: ArtifactAssetWriteInput[];
124
132
  };
133
+ export type ArtifactBatchItemInput = {
134
+ artifact: ArtifactBundleCreateInput;
135
+ evidence?: ArtifactEvidenceReference[];
136
+ /** Stable caller-defined key used to reconcile a receipt with requested output. */
137
+ key: string;
138
+ };
139
+ export type ArtifactBatchCommitMode = "archive_on_failure" | "require_atomic";
140
+ export type ArtifactBatchCreateInput = {
141
+ bundleId?: string;
142
+ commitMode?: ArtifactBatchCommitMode;
143
+ evidence?: ArtifactEvidenceReference[];
144
+ items: ArtifactBatchItemInput[];
145
+ metadata?: JsonObject;
146
+ provenance?: ArtifactProvenance;
147
+ };
148
+ export type ArtifactBatchValidationIssue = {
149
+ code: string;
150
+ itemKey?: string;
151
+ message: string;
152
+ path?: string;
153
+ };
154
+ export type ArtifactBatchValidationResult = {
155
+ issues?: never;
156
+ valid: true;
157
+ } | {
158
+ issues: ArtifactBatchValidationIssue[];
159
+ valid: false;
160
+ };
161
+ export type ArtifactBatchReceiptItem = {
162
+ artifactId: string;
163
+ archived?: boolean;
164
+ key: string;
165
+ kind: string;
166
+ revision: number;
167
+ title: string;
168
+ };
169
+ export type ArtifactBatchCompletionReceipt = {
170
+ archivedArtifactIds: string[];
171
+ atomic: boolean;
172
+ bundleId: string;
173
+ completedAt: string;
174
+ error?: string;
175
+ items: ArtifactBatchReceiptItem[];
176
+ ownerId: string;
177
+ stagedAt: string;
178
+ status: "committed" | "partial_failure" | "rolled_back";
179
+ validation: ArtifactBatchValidationResult;
180
+ };
181
+ export type ArtifactBatchValidator = (context: {
182
+ bundleId: string;
183
+ evidence: ArtifactEvidenceReference[];
184
+ items: ReadonlyArray<{
185
+ evidence: ArtifactEvidenceReference[];
186
+ key: string;
187
+ record: Readonly<ArtifactRecord>;
188
+ }>;
189
+ ownerId: string;
190
+ }) => ArtifactBatchValidationIssue[] | Promise<ArtifactBatchValidationIssue[]>;
191
+ export type StagedArtifactBatch = {
192
+ bundleId: string;
193
+ commit(): Promise<ArtifactBatchCompletionReceipt>;
194
+ evidence: ArtifactEvidenceReference[];
195
+ items: ReadonlyArray<{
196
+ evidence: ArtifactEvidenceReference[];
197
+ key: string;
198
+ record: Readonly<ArtifactRecord>;
199
+ }>;
200
+ ownerId: string;
201
+ rollback(reason?: string): Promise<ArtifactBatchCompletionReceipt>;
202
+ stagedAt: string;
203
+ validation: ArtifactBatchValidationResult;
204
+ };
125
205
  export type ArtifactPublishInput = {
126
206
  mode?: "live" | "pinned";
127
207
  };
@@ -133,7 +213,7 @@ export type ArtifactGarbageCollectionResult = {
133
213
  deleted: ArtifactAssetReference[];
134
214
  retained: ArtifactAssetReference[];
135
215
  };
136
- export type ArtifactErrorCode = "asset_store_unavailable" | "asset_transaction_unavailable" | "conflict" | "generator_unavailable" | "invalid_content" | "not_found" | "publisher_unavailable" | "renderer_unavailable" | "unsupported_capability" | "unknown_kind";
216
+ export type ArtifactErrorCode = "asset_store_unavailable" | "asset_transaction_unavailable" | "atomic_batch_unavailable" | "batch_validation_failed" | "conflict" | "generator_unavailable" | "invalid_content" | "not_found" | "publisher_unavailable" | "renderer_unavailable" | "unsupported_capability" | "unknown_kind";
137
217
  export declare class ArtifactError extends Error {
138
218
  readonly code: ArtifactErrorCode;
139
219
  constructor(code: ArtifactErrorCode, message: string);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/artifacts",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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",