@absolutejs/artifacts 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -232,6 +232,27 @@ const deck = await generators.generate(artifacts, {
232
232
  });
233
233
  ```
234
234
 
235
+ Generators may expose a `validate` function. The bundled
236
+ `validateGeneratedArtifactFormats` validator checks CSV row structure, RFC 822
237
+ headers, ZIP readability, and PPTX package/XML integrity before persistence.
238
+ Generate several independent artifacts with one atomic receipt through the same
239
+ registry:
240
+
241
+ ```ts
242
+ const receipt = await generators.generateBatch(artifacts, {
243
+ ownerId: member.id,
244
+ items: [
245
+ { createdBy: "agent", key: "deck", kind: "presentation", title: "Deck" },
246
+ { createdBy: "agent", key: "email", kind: "email", title: "Email" },
247
+ ],
248
+ provenance: { tool: "campaign_generator" },
249
+ });
250
+ ```
251
+
252
+ Generation validators run after every output is staged and before anything is
253
+ committed. Validation failures return a rolled-back completion receipt keyed to
254
+ the invalid output.
255
+
235
256
  ## RAG ingestion
236
257
 
237
258
  The optional `@absolutejs/artifacts/rag` entry point resolves one current or
@@ -250,7 +271,10 @@ const upsert = await buildRAGUpsertInputFromUploads({ uploads });
250
271
 
251
272
  `createArtifactRAGIndexCoordinator` wraps that conversion with durable
252
273
  `pending`, `indexed`, and `failed` state. It removes document IDs from the
253
- previous indexed revision after the replacement succeeds.
274
+ previous indexed revision after the replacement succeeds. Set `failureMode` to
275
+ `"isolate_uploads"` to index structured content and assets independently. A bad
276
+ asset then produces a typed partial receipt while preserving successful
277
+ document ids; obsolete ids are removed only after a fully successful revision.
254
278
 
255
279
  ## Events and retention
256
280
 
package/dist/index.js CHANGED
@@ -58,35 +58,635 @@ var defineArtifactRegistry = (definitions) => ({
58
58
  // src/generators.ts
59
59
  var createArtifactGeneratorRegistry = (initial = []) => {
60
60
  const generators = new Map(initial.map((generator) => [generator.kind, generator]));
61
+ const generateResult = async (input) => {
62
+ const generator = generators.get(input.kind);
63
+ if (!generator) {
64
+ throw new ArtifactError("generator_unavailable", `No generator is registered for ${input.kind} artifacts`);
65
+ }
66
+ const context = { ownerId: input.ownerId };
67
+ const result = await generator.generate(input, context);
68
+ const issues = await generator.validate?.(result, input, context) ?? [];
69
+ if (issues.length > 0) {
70
+ throw new ArtifactError("batch_validation_failed", issues.map((issue) => `${issue.code}: ${issue.message}`).join("; "));
71
+ }
72
+ return result;
73
+ };
74
+ const bundleInput = (input, result) => ({
75
+ assets: result.assets,
76
+ content: result.content,
77
+ createdBy: input.createdBy,
78
+ kind: input.kind,
79
+ metadata: {
80
+ ...result.metadata,
81
+ ...result.warnings?.length ? { generationWarnings: result.warnings } : {}
82
+ },
83
+ provenance: result.provenance,
84
+ title: result.title ?? input.title ?? `Generated ${input.kind}`
85
+ });
61
86
  return {
62
87
  generate: async (service, input) => {
63
- const generator = generators.get(input.kind);
64
- if (!generator) {
65
- throw new ArtifactError("generator_unavailable", `No generator is registered for ${input.kind} artifacts`);
66
- }
67
- const result = await generator.generate(input, {
68
- ownerId: input.ownerId
69
- });
70
- const artifact = await service.createBundle(input.ownerId, {
71
- assets: result.assets,
72
- content: result.content,
73
- createdBy: input.createdBy,
74
- kind: input.kind,
75
- metadata: {
76
- ...result.metadata,
77
- ...result.warnings?.length ? { generationWarnings: result.warnings } : {}
78
- },
79
- provenance: result.provenance,
80
- title: result.title ?? input.title ?? `Generated ${input.kind}`
81
- });
88
+ const result = await generateResult(input);
89
+ const artifact = await service.createBundle(input.ownerId, bundleInput(input, result));
82
90
  return artifact;
83
91
  },
92
+ generateBatch: async (service, input) => {
93
+ const generated = await Promise.all(input.items.map(async (item) => {
94
+ const generationInput = {
95
+ createdBy: item.createdBy,
96
+ input: item.input,
97
+ kind: item.kind,
98
+ ownerId: input.ownerId,
99
+ prompt: item.prompt,
100
+ title: item.title
101
+ };
102
+ const generator = generators.get(item.kind);
103
+ if (!generator) {
104
+ throw new ArtifactError("generator_unavailable", `No generator is registered for ${item.kind} artifacts`);
105
+ }
106
+ const context = { ownerId: input.ownerId };
107
+ const result = await generator.generate(generationInput, context);
108
+ const issues = await generator.validate?.(result, generationInput, context) ?? [];
109
+ return {
110
+ artifact: bundleInput(generationInput, result),
111
+ evidence: item.evidence,
112
+ generationIssues: issues,
113
+ key: item.key
114
+ };
115
+ }));
116
+ const staged = await service.stageBatch(input.ownerId, {
117
+ bundleId: input.bundleId,
118
+ commitMode: input.commitMode,
119
+ evidence: input.evidence,
120
+ items: generated.map(({ generationIssues: _issues, ...item }) => item),
121
+ metadata: input.metadata,
122
+ provenance: input.provenance
123
+ }, {
124
+ validators: [
125
+ () => generated.flatMap((item) => item.generationIssues.map((generationIssue) => ({
126
+ ...generationIssue,
127
+ itemKey: item.key
128
+ }))),
129
+ ...input.validators ?? []
130
+ ]
131
+ });
132
+ return staged.commit();
133
+ },
84
134
  kinds: () => [...generators.keys()],
85
135
  register: (generator) => {
86
136
  generators.set(generator.kind, generator);
87
137
  }
88
138
  };
89
139
  };
140
+ // node_modules/fflate/esm/index.mjs
141
+ import { createRequire } from "module";
142
+ var require2 = createRequire("/");
143
+ var _a;
144
+ var Worker;
145
+ var isMarkedAsUntransferable;
146
+ try {
147
+ _a = require2("worker_threads"), Worker = _a.Worker, isMarkedAsUntransferable = _a.isMarkedAsUntransferable;
148
+ } catch (e) {}
149
+ var u8 = Uint8Array;
150
+ var u16 = Uint16Array;
151
+ var i32 = Int32Array;
152
+ var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0]);
153
+ var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0]);
154
+ var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
155
+ var freb = function(eb, start) {
156
+ var b = new u16(31);
157
+ for (var i = 0;i < 31; ++i) {
158
+ b[i] = start += 1 << eb[i - 1];
159
+ }
160
+ var r = new i32(b[30]);
161
+ for (var i = 1;i < 30; ++i) {
162
+ for (var j = b[i];j < b[i + 1]; ++j) {
163
+ r[j] = j - b[i] << 5 | i;
164
+ }
165
+ }
166
+ return { b, r };
167
+ };
168
+ var _a = freb(fleb, 2);
169
+ var fl = _a.b;
170
+ var revfl = _a.r;
171
+ fl[28] = 258, revfl[258] = 28;
172
+ var _b = freb(fdeb, 0);
173
+ var fd = _b.b;
174
+ var revfd = _b.r;
175
+ var rev = new u16(32768);
176
+ for (i = 0;i < 32768; ++i) {
177
+ x = (i & 43690) >> 1 | (i & 21845) << 1;
178
+ x = (x & 52428) >> 2 | (x & 13107) << 2;
179
+ x = (x & 61680) >> 4 | (x & 3855) << 4;
180
+ rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
181
+ }
182
+ var x;
183
+ var i;
184
+ var hMap = function(cd, mb, r) {
185
+ var s = cd.length;
186
+ var i2 = 0;
187
+ var l = new u16(mb);
188
+ for (;i2 < s; ++i2) {
189
+ if (cd[i2])
190
+ ++l[cd[i2] - 1];
191
+ }
192
+ var le = new u16(mb);
193
+ for (i2 = 1;i2 < mb; ++i2) {
194
+ le[i2] = le[i2 - 1] + l[i2 - 1] << 1;
195
+ }
196
+ var co;
197
+ if (r) {
198
+ co = new u16(1 << mb);
199
+ var rvb = 15 - mb;
200
+ for (i2 = 0;i2 < s; ++i2) {
201
+ if (cd[i2]) {
202
+ var sv = i2 << 4 | cd[i2];
203
+ var r_1 = mb - cd[i2];
204
+ var v = le[cd[i2] - 1]++ << r_1;
205
+ for (var m = v | (1 << r_1) - 1;v <= m; ++v) {
206
+ co[rev[v] >> rvb] = sv;
207
+ }
208
+ }
209
+ }
210
+ } else {
211
+ co = new u16(s);
212
+ for (i2 = 0;i2 < s; ++i2) {
213
+ if (cd[i2]) {
214
+ co[i2] = rev[le[cd[i2] - 1]++] >> 15 - cd[i2];
215
+ }
216
+ }
217
+ }
218
+ return co;
219
+ };
220
+ var flt = new u8(288);
221
+ for (i = 0;i < 144; ++i)
222
+ flt[i] = 8;
223
+ var i;
224
+ for (i = 144;i < 256; ++i)
225
+ flt[i] = 9;
226
+ var i;
227
+ for (i = 256;i < 280; ++i)
228
+ flt[i] = 7;
229
+ var i;
230
+ for (i = 280;i < 288; ++i)
231
+ flt[i] = 8;
232
+ var i;
233
+ var fdt = new u8(32);
234
+ for (i = 0;i < 32; ++i)
235
+ fdt[i] = 5;
236
+ var i;
237
+ var flrm = /* @__PURE__ */ hMap(flt, 9, 1);
238
+ var fdrm = /* @__PURE__ */ hMap(fdt, 5, 1);
239
+ var max = function(a) {
240
+ var m = a[0];
241
+ for (var i2 = 1;i2 < a.length; ++i2) {
242
+ if (a[i2] > m)
243
+ m = a[i2];
244
+ }
245
+ return m;
246
+ };
247
+ var bits = function(d, p, m) {
248
+ var o = p / 8 | 0;
249
+ return (d[o] | d[o + 1] << 8) >> (p & 7) & m;
250
+ };
251
+ var bits16 = function(d, p) {
252
+ var o = p / 8 | 0;
253
+ return (d[o] | d[o + 1] << 8 | d[o + 2] << 16) >> (p & 7);
254
+ };
255
+ var shft = function(p) {
256
+ return (p + 7) / 8 | 0;
257
+ };
258
+ var slc = function(v, s, e) {
259
+ if (s == null || s < 0)
260
+ s = 0;
261
+ if (e == null || e > v.length)
262
+ e = v.length;
263
+ return new u8(v.subarray(s, e));
264
+ };
265
+ var ec = [
266
+ "unexpected EOF",
267
+ "invalid block type",
268
+ "invalid length/literal",
269
+ "invalid distance",
270
+ "stream finished",
271
+ "no stream handler",
272
+ ,
273
+ "no callback",
274
+ "invalid UTF-8 data",
275
+ "extra field too long",
276
+ "date not in range 1980-2099",
277
+ "filename too long",
278
+ "stream finishing",
279
+ "invalid zip data"
280
+ ];
281
+ var err = function(ind, msg, nt) {
282
+ var e = new Error(msg || ec[ind]);
283
+ e.code = ind;
284
+ if (Error.captureStackTrace)
285
+ Error.captureStackTrace(e, err);
286
+ if (!nt)
287
+ throw e;
288
+ return e;
289
+ };
290
+ var inflt = function(dat, st, buf, dict) {
291
+ var sl = dat.length, dl = dict ? dict.length : 0;
292
+ if (!sl || st.f && !st.l)
293
+ return buf || new u8(0);
294
+ var noBuf = !buf;
295
+ var resize = noBuf || st.i != 2;
296
+ var noSt = st.i;
297
+ if (noBuf)
298
+ buf = new u8(sl * 3);
299
+ var cbuf = function(l2) {
300
+ var bl = buf.length;
301
+ if (l2 > bl) {
302
+ var nbuf = new u8(Math.max(bl * 2, l2));
303
+ nbuf.set(buf);
304
+ buf = nbuf;
305
+ }
306
+ };
307
+ var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n;
308
+ var tbts = sl * 8;
309
+ do {
310
+ if (!lm) {
311
+ final = bits(dat, pos, 1);
312
+ var type = bits(dat, pos + 1, 3);
313
+ pos += 3;
314
+ if (!type) {
315
+ var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
316
+ if (t > sl) {
317
+ if (noSt)
318
+ err(0);
319
+ break;
320
+ }
321
+ if (resize)
322
+ cbuf(bt + l);
323
+ buf.set(dat.subarray(s, t), bt);
324
+ st.b = bt += l, st.p = pos = t * 8, st.f = final;
325
+ continue;
326
+ } else if (type == 1)
327
+ lm = flrm, dm = fdrm, lbt = 9, dbt = 5;
328
+ else if (type == 2) {
329
+ var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;
330
+ var tl = hLit + bits(dat, pos + 5, 31) + 1;
331
+ pos += 14;
332
+ var ldt = new u8(tl);
333
+ var clt = new u8(19);
334
+ for (var i2 = 0;i2 < hcLen; ++i2) {
335
+ clt[clim[i2]] = bits(dat, pos + i2 * 3, 7);
336
+ }
337
+ pos += hcLen * 3;
338
+ var clb = max(clt), clbmsk = (1 << clb) - 1;
339
+ var clm = hMap(clt, clb, 1);
340
+ for (var i2 = 0;i2 < tl; ) {
341
+ var r = clm[bits(dat, pos, clbmsk)];
342
+ pos += r & 15;
343
+ var s = r >> 4;
344
+ if (s < 16) {
345
+ ldt[i2++] = s;
346
+ } else {
347
+ var c = 0, n = 0;
348
+ if (s == 16)
349
+ n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i2 - 1];
350
+ else if (s == 17)
351
+ n = 3 + bits(dat, pos, 7), pos += 3;
352
+ else if (s == 18)
353
+ n = 11 + bits(dat, pos, 127), pos += 7;
354
+ while (n--)
355
+ ldt[i2++] = c;
356
+ }
357
+ }
358
+ var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
359
+ lbt = max(lt);
360
+ dbt = max(dt);
361
+ lm = hMap(lt, lbt, 1);
362
+ dm = hMap(dt, dbt, 1);
363
+ } else
364
+ err(1);
365
+ if (pos > tbts) {
366
+ if (noSt)
367
+ err(0);
368
+ break;
369
+ }
370
+ }
371
+ if (resize)
372
+ cbuf(bt + 131072);
373
+ var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;
374
+ var lpos = pos;
375
+ for (;; lpos = pos) {
376
+ var c = lm[bits16(dat, pos) & lms], sym = c >> 4;
377
+ pos += c & 15;
378
+ if (pos > tbts) {
379
+ if (noSt)
380
+ err(0);
381
+ break;
382
+ }
383
+ if (!c)
384
+ err(2);
385
+ if (sym < 256)
386
+ buf[bt++] = sym;
387
+ else if (sym == 256) {
388
+ lpos = pos, lm = null;
389
+ break;
390
+ } else {
391
+ var add = sym - 254;
392
+ if (sym > 264) {
393
+ var i2 = sym - 257, b = fleb[i2];
394
+ add = bits(dat, pos, (1 << b) - 1) + fl[i2];
395
+ pos += b;
396
+ }
397
+ var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
398
+ if (!d)
399
+ err(3);
400
+ pos += d & 15;
401
+ var dt = fd[dsym];
402
+ if (dsym > 3) {
403
+ var b = fdeb[dsym];
404
+ dt += bits16(dat, pos) & (1 << b) - 1, pos += b;
405
+ }
406
+ if (pos > tbts) {
407
+ if (noSt)
408
+ err(0);
409
+ break;
410
+ }
411
+ if (resize)
412
+ cbuf(bt + 131072);
413
+ var end = bt + add;
414
+ if (bt < dt) {
415
+ var shift = dl - dt, dend = Math.min(dt, end);
416
+ if (shift + bt < 0)
417
+ err(3);
418
+ for (;bt < dend; ++bt)
419
+ buf[bt] = dict[shift + bt];
420
+ }
421
+ for (;bt < end; ++bt)
422
+ buf[bt] = buf[bt - dt];
423
+ }
424
+ }
425
+ st.l = lm, st.p = lpos, st.b = bt, st.f = final;
426
+ if (lm)
427
+ final = 1, st.m = lbt, st.d = dm, st.n = dbt;
428
+ } while (!final);
429
+ return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt);
430
+ };
431
+ var et = /* @__PURE__ */ new u8(0);
432
+ var b2 = function(d, b) {
433
+ return d[b] | d[b + 1] << 8;
434
+ };
435
+ var b4 = function(d, b) {
436
+ return (d[b] | d[b + 1] << 8 | d[b + 2] << 16 | d[b + 3] << 24) >>> 0;
437
+ };
438
+ var b8 = function(d, b) {
439
+ return b4(d, b) + b4(d, b + 4) * 4294967296;
440
+ };
441
+ function inflateSync(data, opts) {
442
+ return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary);
443
+ }
444
+ var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder;
445
+ var tds = 0;
446
+ try {
447
+ td.decode(et, { stream: true });
448
+ tds = 1;
449
+ } catch (e) {}
450
+ var dutf8 = function(d) {
451
+ for (var r = "", i2 = 0;; ) {
452
+ var c = d[i2++];
453
+ var eb = (c > 127) + (c > 223) + (c > 239);
454
+ if (i2 + eb > d.length)
455
+ return { s: r, r: slc(d, i2 - 1) };
456
+ if (!eb)
457
+ r += String.fromCharCode(c);
458
+ else if (eb == 3) {
459
+ c = ((c & 15) << 18 | (d[i2++] & 63) << 12 | (d[i2++] & 63) << 6 | d[i2++] & 63) - 65536, r += String.fromCharCode(55296 | c >> 10, 56320 | c & 1023);
460
+ } else if (eb & 1)
461
+ r += String.fromCharCode((c & 31) << 6 | d[i2++] & 63);
462
+ else
463
+ r += String.fromCharCode((c & 15) << 12 | (d[i2++] & 63) << 6 | d[i2++] & 63);
464
+ }
465
+ };
466
+ function strFromU8(dat, latin1) {
467
+ if (latin1) {
468
+ var r = "";
469
+ for (var i2 = 0;i2 < dat.length; i2 += 16384)
470
+ r += String.fromCharCode.apply(null, dat.subarray(i2, i2 + 16384));
471
+ return r;
472
+ } else if (td) {
473
+ return td.decode(dat);
474
+ } else {
475
+ var _a2 = dutf8(dat), s = _a2.s, r = _a2.r;
476
+ if (r.length)
477
+ err(8);
478
+ return s;
479
+ }
480
+ }
481
+ var slzh = function(d, b) {
482
+ return b + 30 + b2(d, b + 26) + b2(d, b + 28);
483
+ };
484
+ var zh = function(d, b, z) {
485
+ var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;
486
+ var _a2 = z64hs(d, es, efl, z, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
487
+ return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
488
+ };
489
+ var z64hs = function(d, b, l, z, sc, su, off) {
490
+ var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;
491
+ var nf = nsc + nsu + noff;
492
+ if (z && nf) {
493
+ for (;b + 4 < e; b += 4 + b2(d, b + 2)) {
494
+ if (b2(d, b) == 1) {
495
+ return [
496
+ nsc ? b8(d, b + 4 + 8 * nsu) : sc,
497
+ nsu ? b8(d, b + 4) : su,
498
+ noff ? b8(d, b + 4 + 8 * (nsu + nsc)) : off,
499
+ 1
500
+ ];
501
+ }
502
+ }
503
+ if (z < 2)
504
+ err(13);
505
+ }
506
+ return [sc, su, off, 0];
507
+ };
508
+ function unzipSync(data, opts) {
509
+ var files = {};
510
+ var e = data.length - 22;
511
+ for (;b4(data, e) != 101010256; --e) {
512
+ if (!e || data.length - e > 65558)
513
+ err(13);
514
+ }
515
+ var c = b2(data, e + 8);
516
+ if (!c)
517
+ return {};
518
+ var o = b4(data, e + 16);
519
+ var z = b4(data, e - 20) == 117853008;
520
+ if (z) {
521
+ var ze = b4(data, e - 12);
522
+ z = b4(data, ze) == 101075792;
523
+ if (z) {
524
+ c = b4(data, ze + 32);
525
+ o = b4(data, ze + 48);
526
+ }
527
+ }
528
+ var fltr = opts && opts.filter;
529
+ for (var i2 = 0;i2 < c; ++i2) {
530
+ var _a2 = zh(data, o, z), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
531
+ o = no;
532
+ if (!fltr || fltr({
533
+ name: fn,
534
+ size: sc,
535
+ originalSize: su,
536
+ compression: c_2
537
+ })) {
538
+ if (!c_2)
539
+ files[fn] = slc(data, b, b + sc);
540
+ else if (c_2 == 8)
541
+ files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) });
542
+ else
543
+ err(14, "unknown compression type " + c_2);
544
+ }
545
+ }
546
+ return files;
547
+ }
548
+
549
+ // src/validation.ts
550
+ var decode = (data) => new TextDecoder().decode(data);
551
+ var issue = (code, message, path) => ({
552
+ code,
553
+ message,
554
+ ...path ? { path } : {}
555
+ });
556
+ var parseCsvRows = (value) => {
557
+ const rows = [];
558
+ let row = [];
559
+ let field = "";
560
+ let quoted = false;
561
+ for (let index = 0;index < value.length; index += 1) {
562
+ const character = value[index];
563
+ if (character === '"') {
564
+ if (quoted && value[index + 1] === '"') {
565
+ field += '"';
566
+ index += 1;
567
+ } else {
568
+ quoted = !quoted;
569
+ }
570
+ } else if (character === "," && !quoted) {
571
+ row.push(field);
572
+ field = "";
573
+ } else if ((character === `
574
+ ` || character === "\r") && !quoted) {
575
+ if (character === "\r" && value[index + 1] === `
576
+ `)
577
+ index += 1;
578
+ row.push(field);
579
+ rows.push(row);
580
+ row = [];
581
+ field = "";
582
+ } else {
583
+ field += character;
584
+ }
585
+ }
586
+ if (quoted)
587
+ throw new Error("CSV contains an unterminated quoted field");
588
+ if (field.length > 0 || row.length > 0) {
589
+ row.push(field);
590
+ rows.push(row);
591
+ }
592
+ return rows.filter((candidate) => candidate.some((cell) => cell.length > 0));
593
+ };
594
+ var validateCsv = (data, path) => {
595
+ const rows = parseCsvRows(decode(data));
596
+ if (rows.length === 0)
597
+ return [issue("csv_empty", "CSV has no rows", path)];
598
+ const columns = rows[0].length;
599
+ const inconsistent = rows.findIndex((row) => row.length !== columns);
600
+ return inconsistent < 0 ? [] : [
601
+ issue("csv_column_mismatch", `CSV row ${inconsistent + 1} has ${rows[inconsistent].length} columns; expected ${columns}`, path)
602
+ ];
603
+ };
604
+ var validateEmail = (data, path) => {
605
+ const value = decode(data).replace(/\r\n?/g, `
606
+ `);
607
+ const separator = value.indexOf(`
608
+
609
+ `);
610
+ if (separator < 0) {
611
+ return [
612
+ issue("email_missing_body_separator", "Email must contain headers followed by a blank line and body", path)
613
+ ];
614
+ }
615
+ const headers = value.slice(0, separator);
616
+ const required = ["to", "subject"].filter((name) => !new RegExp(`^${name}:\\s*\\S+`, "imu").test(headers));
617
+ return required.map((name) => issue("email_missing_header", `Email is missing a non-empty ${name} header`, path));
618
+ };
619
+ var validateXml = (value) => {
620
+ const stack = [];
621
+ const withoutOpaqueSections = value.replace(/<!--[\s\S]*?-->/gu, "").replace(/<!\[CDATA\[[\s\S]*?\]\]>/gu, "").replace(/<\?[\s\S]*?\?>/gu, "");
622
+ for (const match of withoutOpaqueSections.matchAll(/<\s*(\/?)\s*([\w:.-]+)([^>]*)>/gu)) {
623
+ const closing = match[1] === "/";
624
+ const name = match[2];
625
+ const suffix = match[3] ?? "";
626
+ if (!closing && suffix.trimEnd().endsWith("/"))
627
+ continue;
628
+ if (!closing) {
629
+ stack.push(name);
630
+ continue;
631
+ }
632
+ const expected = stack.pop();
633
+ if (expected !== name) {
634
+ throw new Error(expected ? `expected closing tag for <${expected}> but found </${name}>` : `unexpected closing tag </${name}>`);
635
+ }
636
+ }
637
+ if (stack.length > 0)
638
+ throw new Error(`unclosed tag <${stack.at(-1)}>`);
639
+ };
640
+ var validateZip = (data, path, officePresentation) => {
641
+ let entries;
642
+ try {
643
+ entries = unzipSync(data);
644
+ } catch (error) {
645
+ return [
646
+ issue("zip_invalid", `ZIP archive could not be opened: ${error instanceof Error ? error.message : String(error)}`, path)
647
+ ];
648
+ }
649
+ const names = Object.keys(entries).filter((name) => !name.endsWith("/"));
650
+ if (names.length === 0) {
651
+ return [issue("zip_empty", "ZIP archive has no files", path)];
652
+ }
653
+ if (!officePresentation)
654
+ return [];
655
+ const required = ["[Content_Types].xml", "ppt/presentation.xml"];
656
+ const missing = required.filter((name) => !entries[name]);
657
+ if (!names.some((name) => /^ppt\/slides\/slide\d+\.xml$/u.test(name))) {
658
+ missing.push("ppt/slides/slide*.xml");
659
+ }
660
+ const issues = missing.map((name) => issue("presentation_entry_missing", `Presentation is missing ${name}`, path));
661
+ for (const name of names.filter((candidate) => candidate.endsWith(".xml") || candidate.endsWith(".rels"))) {
662
+ try {
663
+ validateXml(decode(entries[name]));
664
+ } catch (error) {
665
+ issues.push(issue("presentation_xml_invalid", `${name}: ${error instanceof Error ? error.message : String(error)}`, path));
666
+ }
667
+ }
668
+ return issues;
669
+ };
670
+ var validateGeneratedArtifactFormats = (result) => (result.assets ?? []).flatMap((asset, index) => {
671
+ const mediaType = asset.mediaType.toLowerCase();
672
+ const path = `assets[${index}](${asset.name})`;
673
+ try {
674
+ if (mediaType.includes("csv"))
675
+ return validateCsv(asset.data, path);
676
+ if (mediaType === "message/rfc822")
677
+ return validateEmail(asset.data, path);
678
+ if (mediaType.includes("presentationml.presentation")) {
679
+ return validateZip(asset.data, path, true);
680
+ }
681
+ if (mediaType.includes("zip"))
682
+ return validateZip(asset.data, path, false);
683
+ return [];
684
+ } catch (error) {
685
+ return [
686
+ issue("format_invalid", error instanceof Error ? error.message : String(error), path)
687
+ ];
688
+ }
689
+ });
90
690
  // src/standardKinds.ts
91
691
  import { Type } from "typebox";
92
692
  var FileArtifactContentSchema = Type.Object({
@@ -1108,5 +1708,6 @@ export {
1108
1708
  createMemoryArtifactStore,
1109
1709
  defineArtifactRegistry,
1110
1710
  isJsonValue,
1111
- standardArtifactDefinitions
1711
+ standardArtifactDefinitions,
1712
+ validateGeneratedArtifactFormats
1112
1713
  };
package/dist/rag.js CHANGED
@@ -1,6 +1,15 @@
1
1
  // @bun
2
2
  // src/rag.ts
3
3
  import { Buffer } from "buffer";
4
+
5
+ class ArtifactRAGPartialIndexError extends Error {
6
+ receipt;
7
+ constructor(receipt) {
8
+ super(`Artifact ${receipt.artifactId} indexed ${receipt.indexedUploads}/${receipt.totalUploads} uploads; ${receipt.failures.length} failed`);
9
+ this.name = "ArtifactRAGPartialIndexError";
10
+ this.receipt = receipt;
11
+ }
12
+ }
4
13
  var artifactMetadata = (artifact) => ({
5
14
  artifactId: artifact.id,
6
15
  artifactKind: artifact.kind,
@@ -49,6 +58,58 @@ var createArtifactRAGIndexCoordinator = (options) => ({
49
58
  });
50
59
  try {
51
60
  const uploads = await artifactToRAGUploads(artifact, options.reader);
61
+ if (options.failureMode === "isolate_uploads") {
62
+ const documentIds = [];
63
+ const failures = [];
64
+ let indexedUploads = 0;
65
+ for (const upload of uploads) {
66
+ try {
67
+ const indexed2 = await options.target.index([upload], { artifact });
68
+ documentIds.push(...indexed2.documentIds);
69
+ indexedUploads += 1;
70
+ } catch (error) {
71
+ failures.push({
72
+ contentType: upload.contentType ?? "application/octet-stream",
73
+ error: error instanceof Error ? error.message : String(error),
74
+ name: upload.name ?? "unnamed upload",
75
+ source: upload.source ?? `artifact:${artifact.id}`
76
+ });
77
+ }
78
+ }
79
+ const receipt = {
80
+ artifactId: artifact.id,
81
+ documentIds,
82
+ failures,
83
+ indexedUploads,
84
+ revision: artifact.revision,
85
+ status: failures.length === 0 ? "indexed" : indexedUploads > 0 ? "partial" : "failed",
86
+ totalUploads: uploads.length
87
+ };
88
+ if (failures.length > 0) {
89
+ await options.service.markIndexing(artifact.ownerId, artifact.id, {
90
+ documentIds: [
91
+ ...new Set([...previous?.documentIds ?? [], ...documentIds])
92
+ ],
93
+ error: JSON.stringify({ failures, receipt }),
94
+ revision: artifact.revision,
95
+ status: receipt.status
96
+ });
97
+ throw new ArtifactRAGPartialIndexError(receipt);
98
+ }
99
+ if (previous?.documentIds.length && options.target.remove) {
100
+ const currentIds = new Set(documentIds);
101
+ const obsoleteIds = previous.documentIds.filter((documentId) => !currentIds.has(documentId));
102
+ if (obsoleteIds.length) {
103
+ await options.target.remove(obsoleteIds, { artifact });
104
+ }
105
+ }
106
+ await options.service.markIndexing(artifact.ownerId, artifact.id, {
107
+ documentIds,
108
+ revision: artifact.revision,
109
+ status: "indexed"
110
+ });
111
+ return receipt;
112
+ }
52
113
  const indexed = await options.target.index(uploads, { artifact });
53
114
  if (previous?.documentIds.length && options.target.remove) {
54
115
  const currentIds = new Set(indexed.documentIds);
@@ -62,8 +123,18 @@ var createArtifactRAGIndexCoordinator = (options) => ({
62
123
  revision: artifact.revision,
63
124
  status: "indexed"
64
125
  });
65
- return indexed;
126
+ return {
127
+ artifactId: artifact.id,
128
+ documentIds: indexed.documentIds,
129
+ failures: [],
130
+ indexedUploads: uploads.length,
131
+ revision: artifact.revision,
132
+ status: "indexed",
133
+ totalUploads: uploads.length
134
+ };
66
135
  } catch (error) {
136
+ if (error instanceof ArtifactRAGPartialIndexError)
137
+ throw error;
67
138
  await options.service.markIndexing(artifact.ownerId, artifact.id, {
68
139
  documentIds: previous?.documentIds,
69
140
  error: error instanceof Error ? error.message : String(error),
@@ -75,6 +146,7 @@ var createArtifactRAGIndexCoordinator = (options) => ({
75
146
  }
76
147
  });
77
148
  export {
149
+ ArtifactRAGPartialIndexError,
78
150
  artifactToRAGUploads,
79
151
  createArtifactRAGIndexCoordinator
80
152
  };
@@ -1,4 +1,4 @@
1
- import type { ArtifactAssetWriteInput, ArtifactBundleCreateInput, ArtifactProvenance, ArtifactRecord, JsonObject, JsonValue } from "./types";
1
+ import type { ArtifactAssetWriteInput, ArtifactBatchCompletionReceipt, ArtifactBatchCreateInput, ArtifactBatchValidator, ArtifactBundleCreateInput, ArtifactEvidenceReference, ArtifactProvenance, ArtifactRecord, JsonObject, JsonValue } from "./types";
2
2
  export type ArtifactGenerationInput = {
3
3
  createdBy: string;
4
4
  input?: JsonObject;
@@ -22,12 +22,35 @@ export type ArtifactGenerator = {
22
22
  generate(input: ArtifactGenerationInput, context: ArtifactGenerationContext): Promise<ArtifactGenerationResult>;
23
23
  kind: string;
24
24
  name: string;
25
+ validate?(result: ArtifactGenerationResult, input: ArtifactGenerationInput, context: ArtifactGenerationContext): ArtifactGenerationValidationIssue[] | Promise<ArtifactGenerationValidationIssue[]>;
26
+ };
27
+ export type ArtifactGenerationValidationIssue = {
28
+ code: string;
29
+ message: string;
30
+ path?: string;
25
31
  };
26
32
  export type ArtifactBundleCreator = {
27
33
  createBundle(ownerId: string, input: ArtifactBundleCreateInput): Promise<ArtifactRecord>;
28
34
  };
35
+ export type ArtifactBatchGeneratorService = ArtifactBundleCreator & {
36
+ stageBatch(ownerId: string, input: ArtifactBatchCreateInput, options?: {
37
+ validators?: ArtifactBatchValidator[];
38
+ }): Promise<{
39
+ commit(): Promise<ArtifactBatchCompletionReceipt>;
40
+ }>;
41
+ };
42
+ export type ArtifactBatchGenerationItem = Omit<ArtifactGenerationInput, "ownerId"> & {
43
+ evidence?: ArtifactEvidenceReference[];
44
+ key: string;
45
+ };
46
+ export type ArtifactBatchGenerationInput = Omit<ArtifactBatchCreateInput, "items"> & {
47
+ items: ArtifactBatchGenerationItem[];
48
+ ownerId: string;
49
+ validators?: ArtifactBatchValidator[];
50
+ };
29
51
  export declare const createArtifactGeneratorRegistry: (initial?: ArtifactGenerator[]) => {
30
52
  generate: (service: ArtifactBundleCreator, input: ArtifactGenerationInput) => Promise<ArtifactRecord>;
53
+ generateBatch: (service: ArtifactBatchGeneratorService, input: ArtifactBatchGenerationInput) => Promise<ArtifactBatchCompletionReceipt>;
31
54
  kinds: () => string[];
32
55
  register: (generator: ArtifactGenerator) => void;
33
56
  };
@@ -7,7 +7,8 @@
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
+ export { createArtifactGeneratorRegistry, type ArtifactBundleCreator, type ArtifactGenerationContext, type ArtifactGenerationInput, type ArtifactGenerationResult, type ArtifactGenerator, type ArtifactGeneratorRegistry, type ArtifactBatchGenerationInput, type ArtifactBatchGenerationItem, type ArtifactBatchGeneratorService, type ArtifactGenerationValidationIssue, } from "./generators";
11
+ export { validateGeneratedArtifactFormats } from "./validation";
11
12
  export { STANDARD_ARTIFACT_KIND_NAMES, standardArtifactDefinitions, } from "./standardKinds";
12
13
  export { createArtifactRendererRegistry, type ArtifactRenderer, type ArtifactRendererRegistry, type ArtifactRenderResult, } from "./renderers";
13
14
  export { createArtifactService, type ArtifactPublisher, type ArtifactService, type ArtifactServiceOptions, } from "./service";
package/dist/src/rag.d.ts CHANGED
@@ -26,21 +26,39 @@ export type ArtifactRAGIndexStateWriter = {
26
26
  documentIds?: string[];
27
27
  error?: string;
28
28
  revision: number;
29
- status: "failed" | "indexed" | "pending" | "stale";
29
+ status: "failed" | "indexed" | "partial" | "pending" | "stale";
30
30
  }): Promise<unknown>;
31
31
  };
32
+ export type ArtifactRAGIndexFailure = {
33
+ contentType: string;
34
+ error: string;
35
+ name: string;
36
+ source: string;
37
+ };
38
+ export type ArtifactRAGIndexReceipt = {
39
+ artifactId: string;
40
+ documentIds: string[];
41
+ failures: ArtifactRAGIndexFailure[];
42
+ indexedUploads: number;
43
+ revision: number;
44
+ status: "failed" | "indexed" | "partial";
45
+ totalUploads: number;
46
+ };
47
+ export declare class ArtifactRAGPartialIndexError extends Error {
48
+ readonly receipt: ArtifactRAGIndexReceipt;
49
+ constructor(receipt: ArtifactRAGIndexReceipt);
50
+ }
32
51
  /**
33
52
  * Resolve an artifact revision into upload inputs accepted by @absolutejs/rag.
34
53
  * Storage URIs remain opaque; only the supplied reader is allowed to access bytes.
35
54
  */
36
55
  export declare const artifactToRAGUploads: (artifact: ArtifactRecord, reader: ArtifactRAGAssetReader, options?: ArtifactRAGUploadOptions) => Promise<RAGDocumentUploadInput[]>;
37
56
  export declare const createArtifactRAGIndexCoordinator: (options: {
57
+ failureMode?: "fail_fast" | "isolate_uploads";
38
58
  reader: ArtifactRAGAssetReader;
39
59
  service: ArtifactRAGIndexStateWriter;
40
60
  target: ArtifactRAGIndexTarget;
41
61
  }) => {
42
- index: (artifact: ArtifactRecord) => Promise<{
43
- documentIds: string[];
44
- }>;
62
+ index: (artifact: ArtifactRecord) => Promise<ArtifactRAGIndexReceipt>;
45
63
  };
46
64
  export type ArtifactRAGIndexCoordinator = ReturnType<typeof createArtifactRAGIndexCoordinator>;
@@ -94,7 +94,7 @@ export type ArtifactEventQuery = {
94
94
  processed?: boolean;
95
95
  type?: ArtifactEventType;
96
96
  };
97
- export type ArtifactIndexingStatus = "failed" | "indexed" | "pending" | "stale";
97
+ export type ArtifactIndexingStatus = "failed" | "indexed" | "partial" | "pending" | "stale";
98
98
  export type ArtifactIndexingState = {
99
99
  artifactId: string;
100
100
  documentIds: string[];
@@ -0,0 +1,3 @@
1
+ import type { ArtifactGenerationResult, ArtifactGenerationValidationIssue } from "./generators";
2
+ /** Validate common generated download formats before an artifact is committed. */
3
+ export declare const validateGeneratedArtifactFormats: (result: ArtifactGenerationResult) => ArtifactGenerationValidationIssue[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/artifacts",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
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",
@@ -60,6 +60,7 @@
60
60
  "@absolutejs/manifest": "^0.10.0",
61
61
  "@sinclair/typebox": "^0.34.0",
62
62
  "drizzle-typebox": "1.0.0-beta.14-a36c63d",
63
+ "fflate": "^0.8.2",
63
64
  "typebox": "^1.3.16"
64
65
  },
65
66
  "peerDependencies": {