@krischoichoi/channel-files 0.6.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.
Files changed (122) hide show
  1. package/README.md +79 -0
  2. package/lib/attachment-resolver.d.ts +24 -0
  3. package/lib/attachment-resolver.d.ts.map +1 -0
  4. package/lib/attachment-resolver.js +38 -0
  5. package/lib/attachment-resolver.js.map +1 -0
  6. package/lib/attachments/extractors/docx.d.ts +6 -0
  7. package/lib/attachments/extractors/docx.d.ts.map +1 -0
  8. package/lib/attachments/extractors/docx.js +43 -0
  9. package/lib/attachments/extractors/docx.js.map +1 -0
  10. package/lib/attachments/extractors/index.d.ts +10 -0
  11. package/lib/attachments/extractors/index.d.ts.map +1 -0
  12. package/lib/attachments/extractors/index.js +10 -0
  13. package/lib/attachments/extractors/index.js.map +1 -0
  14. package/lib/attachments/extractors/pdf.d.ts +4 -0
  15. package/lib/attachments/extractors/pdf.d.ts.map +1 -0
  16. package/lib/attachments/extractors/pdf.js +44 -0
  17. package/lib/attachments/extractors/pdf.js.map +1 -0
  18. package/lib/attachments/extractors/registry.d.ts +43 -0
  19. package/lib/attachments/extractors/registry.d.ts.map +1 -0
  20. package/lib/attachments/extractors/registry.js +82 -0
  21. package/lib/attachments/extractors/registry.js.map +1 -0
  22. package/lib/attachments/extractors/text.d.ts +17 -0
  23. package/lib/attachments/extractors/text.d.ts.map +1 -0
  24. package/lib/attachments/extractors/text.js +0 -0
  25. package/lib/attachments/extractors/text.js.map +1 -0
  26. package/lib/attachments/extractors/types.d.ts +62 -0
  27. package/lib/attachments/extractors/types.d.ts.map +1 -0
  28. package/lib/attachments/extractors/types.js +13 -0
  29. package/lib/attachments/extractors/types.js.map +1 -0
  30. package/lib/attachments/extractors/xlsx.d.ts +6 -0
  31. package/lib/attachments/extractors/xlsx.d.ts.map +1 -0
  32. package/lib/attachments/extractors/xlsx.js +83 -0
  33. package/lib/attachments/extractors/xlsx.js.map +1 -0
  34. package/lib/attachments/filename.d.ts +23 -0
  35. package/lib/attachments/filename.d.ts.map +1 -0
  36. package/lib/attachments/filename.js +62 -0
  37. package/lib/attachments/filename.js.map +1 -0
  38. package/lib/attachments/hash.d.ts +5 -0
  39. package/lib/attachments/hash.d.ts.map +1 -0
  40. package/lib/attachments/hash.js +13 -0
  41. package/lib/attachments/hash.js.map +1 -0
  42. package/lib/attachments/index.d.ts +18 -0
  43. package/lib/attachments/index.d.ts.map +1 -0
  44. package/lib/attachments/index.js +18 -0
  45. package/lib/attachments/index.js.map +1 -0
  46. package/lib/attachments/mime.d.ts +27 -0
  47. package/lib/attachments/mime.d.ts.map +1 -0
  48. package/lib/attachments/mime.js +162 -0
  49. package/lib/attachments/mime.js.map +1 -0
  50. package/lib/attachments/pipeline-extractor.d.ts +8 -0
  51. package/lib/attachments/pipeline-extractor.d.ts.map +1 -0
  52. package/lib/attachments/pipeline-extractor.js +62 -0
  53. package/lib/attachments/pipeline-extractor.js.map +1 -0
  54. package/lib/attachments/pipeline.d.ts +29 -0
  55. package/lib/attachments/pipeline.d.ts.map +1 -0
  56. package/lib/attachments/pipeline.js +59 -0
  57. package/lib/attachments/pipeline.js.map +1 -0
  58. package/lib/attachments/policy.d.ts +25 -0
  59. package/lib/attachments/policy.d.ts.map +1 -0
  60. package/lib/attachments/policy.js +21 -0
  61. package/lib/attachments/policy.js.map +1 -0
  62. package/lib/attachments/render.d.ts +16 -0
  63. package/lib/attachments/render.d.ts.map +1 -0
  64. package/lib/attachments/render.js +28 -0
  65. package/lib/attachments/render.js.map +1 -0
  66. package/lib/attachments/schema.d.ts +58 -0
  67. package/lib/attachments/schema.d.ts.map +1 -0
  68. package/lib/attachments/schema.js +50 -0
  69. package/lib/attachments/schema.js.map +1 -0
  70. package/lib/attachments/store.d.ts +69 -0
  71. package/lib/attachments/store.d.ts.map +1 -0
  72. package/lib/attachments/store.js +254 -0
  73. package/lib/attachments/store.js.map +1 -0
  74. package/lib/attachments/tool-read.d.ts +34 -0
  75. package/lib/attachments/tool-read.d.ts.map +1 -0
  76. package/lib/attachments/tool-read.js +182 -0
  77. package/lib/attachments/tool-read.js.map +1 -0
  78. package/lib/attachments/types.d.ts +96 -0
  79. package/lib/attachments/types.d.ts.map +1 -0
  80. package/lib/attachments/types.js +18 -0
  81. package/lib/attachments/types.js.map +1 -0
  82. package/lib/backends/harness-native.d.ts +45 -0
  83. package/lib/backends/harness-native.d.ts.map +1 -0
  84. package/lib/backends/harness-native.js +56 -0
  85. package/lib/backends/harness-native.js.map +1 -0
  86. package/lib/catalog/legacy-backfill.d.ts +34 -0
  87. package/lib/catalog/legacy-backfill.d.ts.map +1 -0
  88. package/lib/catalog/legacy-backfill.js +49 -0
  89. package/lib/catalog/legacy-backfill.js.map +1 -0
  90. package/lib/catalog/store.d.ts +121 -0
  91. package/lib/catalog/store.d.ts.map +1 -0
  92. package/lib/catalog/store.js +227 -0
  93. package/lib/catalog/store.js.map +1 -0
  94. package/lib/catalog/types.d.ts +83 -0
  95. package/lib/catalog/types.d.ts.map +1 -0
  96. package/lib/catalog/types.js +29 -0
  97. package/lib/catalog/types.js.map +1 -0
  98. package/lib/index.d.ts +12 -0
  99. package/lib/index.d.ts.map +1 -0
  100. package/lib/index.js +12 -0
  101. package/lib/index.js.map +1 -0
  102. package/lib/migration/migrate-on-read.d.ts +61 -0
  103. package/lib/migration/migrate-on-read.d.ts.map +1 -0
  104. package/lib/migration/migrate-on-read.js +61 -0
  105. package/lib/migration/migrate-on-read.js.map +1 -0
  106. package/lib/migration/verify.d.ts +20 -0
  107. package/lib/migration/verify.d.ts.map +1 -0
  108. package/lib/migration/verify.js +35 -0
  109. package/lib/migration/verify.js.map +1 -0
  110. package/lib/paths.d.ts +13 -0
  111. package/lib/paths.d.ts.map +1 -0
  112. package/lib/paths.js +23 -0
  113. package/lib/paths.js.map +1 -0
  114. package/lib/plugin.d.ts +4 -0
  115. package/lib/plugin.d.ts.map +1 -0
  116. package/lib/plugin.js +6 -0
  117. package/lib/plugin.js.map +1 -0
  118. package/lib/service.d.ts +27 -0
  119. package/lib/service.d.ts.map +1 -0
  120. package/lib/service.js +67 -0
  121. package/lib/service.js.map +1 -0
  122. package/package.json +59 -0
@@ -0,0 +1,59 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { sanitizeFilename } from './filename.js';
3
+ function initialExtraction(kind) {
4
+ return kind === 'file' ? { status: 'not-needed' } : { status: 'unsupported' };
5
+ }
6
+ /** Store one channel-neutral file/audio/video part and optionally extract it. */
7
+ export async function storeBinaryPart(store, context, part, options = {}) {
8
+ if (!part.localData || part.localData.byteLength === 0)
9
+ return undefined;
10
+ const asset = await store.put({
11
+ attachmentId: 'att-' + randomUUID(),
12
+ sessionId: context.sessionId,
13
+ channelId: context.channelId,
14
+ accountId: context.accountId,
15
+ conversationId: context.conversationId,
16
+ ...(context.conversationType ? { conversationType: context.conversationType } : {}),
17
+ ...(context.threadId ? { threadId: context.threadId } : {}),
18
+ messageId: context.messageId,
19
+ kind: part.type,
20
+ name: sanitizeFilename(part.name),
21
+ mimeType: part.mimeType,
22
+ data: part.localData,
23
+ extraction: initialExtraction(part.type),
24
+ });
25
+ let extraction = asset.extraction;
26
+ if (part.type === 'file' && options.extractor && asset.mimeType) {
27
+ try {
28
+ extraction = await options.extractor.extract({
29
+ store,
30
+ asset,
31
+ data: part.localData,
32
+ signal: options.signal,
33
+ });
34
+ }
35
+ catch {
36
+ if (store.recordExtraction) {
37
+ try {
38
+ await store.recordExtraction(asset.attachmentId, {
39
+ status: 'failed',
40
+ errorCode: 'parser-error',
41
+ });
42
+ extraction = { status: 'failed', errorCode: 'parser-error' };
43
+ }
44
+ catch {
45
+ // File delivery remains best-effort even if extraction metadata fails.
46
+ }
47
+ }
48
+ }
49
+ }
50
+ return {
51
+ attachmentId: asset.attachmentId,
52
+ name: asset.name,
53
+ mimeType: asset.mimeType,
54
+ bytes: asset.bytes,
55
+ durable: true,
56
+ readable: extraction.status === 'ready',
57
+ };
58
+ }
59
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/attachments/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAgCjD,SAAS,iBAAiB,CAAC,IAAsB;IAC/C,OAAO,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;AAChF,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,KAA+B,EAC/B,OAA0B,EAC1B,IAAgE,EAChE,UAAkC,EAAE;IAEpC,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC,UAAU,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzE,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC;QAC5B,YAAY,EAAE,MAAM,GAAG,UAAU,EAAE;QACnC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnF,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;QACjC,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,IAAI,EAAE,IAAI,CAAC,SAAS;QACpB,UAAU,EAAE,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;KACzC,CAAC,CAAC;IAEH,IAAI,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IAClC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QAChE,IAAI,CAAC;YACH,UAAU,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC;gBAC3C,KAAK;gBACL,KAAK;gBACL,IAAI,EAAE,IAAI,CAAC,SAAS;gBACpB,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,KAAK,CAAC,gBAAgB,EAAE,CAAC;gBAC3B,IAAI,CAAC;oBACH,MAAM,KAAK,CAAC,gBAAgB,CAAC,KAAK,CAAC,YAAY,EAAE;wBAC/C,MAAM,EAAE,QAAQ;wBAChB,SAAS,EAAE,cAAc;qBAC1B,CAAC,CAAC;oBACH,UAAU,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;gBAC/D,CAAC;gBAAC,MAAM,CAAC;oBACP,uEAAuE;gBACzE,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,UAAU,CAAC,MAAM,KAAK,OAAO;KACxC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Byte caps for the private channel asset store and its (M4) text extractor
3
+ * (plan \u00a749). Transport, parser-input and extracted-output bounds are
4
+ * separated so a large inbound file does not silently blow up a parser or the
5
+ * model surface.
6
+ */
7
+ export interface AttachmentPolicy {
8
+ /** Hard cap for a single inbound stored asset (transport max). */
9
+ maxInboundBytes: number;
10
+ extract: {
11
+ /** Cap on the raw bytes handed to an extractor (parser input). */
12
+ maxInputBytes: number;
13
+ /** Cap on the extracted text bytes surfaced to the model. */
14
+ maxOutputBytes: number;
15
+ };
16
+ }
17
+ /** Defaults per plan \u00a749 (100 MB inbound, 32 MiB parser input, 5 MiB output). */
18
+ export declare const DEFAULT_ATTACHMENT_POLICY: AttachmentPolicy;
19
+ /** Whether raw `bytes` may be written to a binder-bound asset store. */
20
+ export declare function isInboundWithinLimit(bytes: number, policy: AttachmentPolicy): boolean;
21
+ /** The wider read cap for `readRaw` (bounded by the transport cap). */
22
+ export declare function rawReadLimit(policy: AttachmentPolicy): number;
23
+ /** The read cap for `readExtracted` (bounded by the extractor output cap). */
24
+ export declare function extractedReadLimit(policy: AttachmentPolicy): number;
25
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/attachments/policy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kEAAkE;IAClE,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE;QACP,kEAAkE;QAClE,aAAa,EAAE,MAAM,CAAC;QACtB,6DAA6D;QAC7D,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,sFAAsF;AACtF,eAAO,MAAM,yBAAyB,EAAE,gBAMvC,CAAC;AAEF,wEAAwE;AACxE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAErF;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAE7D;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAEnE"}
@@ -0,0 +1,21 @@
1
+ /** Defaults per plan \u00a749 (100 MB inbound, 32 MiB parser input, 5 MiB output). */
2
+ export const DEFAULT_ATTACHMENT_POLICY = {
3
+ maxInboundBytes: 104857600,
4
+ extract: {
5
+ maxInputBytes: 33554432,
6
+ maxOutputBytes: 5242880,
7
+ },
8
+ };
9
+ /** Whether raw `bytes` may be written to a binder-bound asset store. */
10
+ export function isInboundWithinLimit(bytes, policy) {
11
+ return bytes <= policy.maxInboundBytes;
12
+ }
13
+ /** The wider read cap for `readRaw` (bounded by the transport cap). */
14
+ export function rawReadLimit(policy) {
15
+ return policy.maxInboundBytes;
16
+ }
17
+ /** The read cap for `readExtracted` (bounded by the extractor output cap). */
18
+ export function extractedReadLimit(policy) {
19
+ return policy.extract.maxOutputBytes;
20
+ }
21
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/attachments/policy.ts"],"names":[],"mappings":"AAiBA,sFAAsF;AACtF,MAAM,CAAC,MAAM,yBAAyB,GAAqB;IACzD,eAAe,EAAE,SAAS;IAC1B,OAAO,EAAE;QACP,aAAa,EAAE,QAAQ;QACvB,cAAc,EAAE,OAAO;KACxB;CACF,CAAC;AAEF,wEAAwE;AACxE,MAAM,UAAU,oBAAoB,CAAC,KAAa,EAAE,MAAwB;IAC1E,OAAO,KAAK,IAAI,MAAM,CAAC,eAAe,CAAC;AACzC,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,MAAwB;IACnD,OAAO,MAAM,CAAC,eAAe,CAAC;AAChC,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,kBAAkB,CAAC,MAAwB;IACzD,OAAO,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC;AACvC,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * User-visible descriptor rendering (plan \u00a754).
3
+ *
4
+ * Each stored asset appears on the model surface ONLY as a compact line: the
5
+ * attachment id, filename, MIME, size and whether the extracted text is ready.
6
+ * The extracted text itself is NEVER embedded here (M4 exposes it through the
7
+ * `read_channel_attachment` tool instead).
8
+ */
9
+ import type { StoredAssetDescriptor } from './types.js';
10
+ /** Human/readable size, e.g. "1.2 MB" or "340 B". */
11
+ export declare function humanSize(bytes: number): string;
12
+ /** Compact declarative descriptor (attachment id, name, mime, size, readable). */
13
+ export declare function renderDescriptor(asset: StoredAssetDescriptor): string;
14
+ /** Structured object form (the non-raw shape M4 can extend). */
15
+ export declare function descriptorObject(asset: StoredAssetDescriptor): Record<string, unknown>;
16
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/attachments/render.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM/C;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,CAIrE;AAED,gEAAgE;AAChE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAStF"}
@@ -0,0 +1,28 @@
1
+ /** Human/readable size, e.g. "1.2 MB" or "340 B". */
2
+ export function humanSize(bytes) {
3
+ if (bytes < 1024)
4
+ return bytes + ' B';
5
+ const kb = bytes / 1024;
6
+ if (kb < 1024)
7
+ return kb.toFixed(1) + ' KB';
8
+ const mb = kb / 1024;
9
+ return mb.toFixed(1) + ' MB';
10
+ }
11
+ /** Compact declarative descriptor (attachment id, name, mime, size, readable). */
12
+ export function renderDescriptor(asset) {
13
+ const mime = asset.mimeType ? ' ' + asset.mimeType : '';
14
+ const readable = asset.readable ? ' readable' : '';
15
+ return `[attachment ${asset.attachmentId} ${asset.name}${mime} ${humanSize(asset.bytes)}${readable}]`;
16
+ }
17
+ /** Structured object form (the non-raw shape M4 can extend). */
18
+ export function descriptorObject(asset) {
19
+ return {
20
+ attachmentId: asset.attachmentId,
21
+ name: asset.name,
22
+ mimeType: asset.mimeType,
23
+ bytes: asset.bytes,
24
+ durable: asset.durable,
25
+ readable: asset.readable,
26
+ };
27
+ }
28
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/attachments/render.ts"],"names":[],"mappings":"AAUA,qDAAqD;AACrD,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,KAAK,GAAG,IAAI,CAAC;IACtC,MAAM,EAAE,GAAG,KAAK,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,GAAG,IAAI;QAAE,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;IAC5C,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;AAC/B,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,gBAAgB,CAAC,KAA4B;IAC3D,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IACxD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IACnD,OAAO,eAAe,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,IAAI,GAAG,IAAI,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,QAAQ,GAAG,CAAC;AACxG,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,gBAAgB,CAAC,KAA4B;IAC3D,OAAO;QACL,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;KACzB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Zod schema for `meta.json` — used to validate a `StoredChannelAsset` when it
3
+ * is read back from disk (plan \u00a745 / \u00a747). Anything that fails this
4
+ * schema is treated as missing/corrupt rather than partially trusted.
5
+ */
6
+ import { z } from 'zod';
7
+ import { type StoredChannelAsset } from './types.js';
8
+ export declare const StoredChannelAssetSchema: z.ZodObject<{
9
+ schemaVersion: z.ZodLiteral<1>;
10
+ attachmentId: z.ZodString;
11
+ sessionId: z.ZodString;
12
+ channelId: z.ZodString;
13
+ accountId: z.ZodString;
14
+ conversationId: z.ZodString;
15
+ conversationType: z.ZodOptional<z.ZodEnum<{
16
+ dm: "dm";
17
+ group: "group";
18
+ }>>;
19
+ threadId: z.ZodOptional<z.ZodString>;
20
+ messageId: z.ZodString;
21
+ kind: z.ZodEnum<{
22
+ file: "file";
23
+ audio: "audio";
24
+ video: "video";
25
+ image: "image";
26
+ }>;
27
+ name: z.ZodString;
28
+ mimeType: z.ZodOptional<z.ZodString>;
29
+ bytes: z.ZodNumber;
30
+ sha256: z.ZodString;
31
+ extraction: z.ZodObject<{
32
+ status: z.ZodEnum<{
33
+ "not-needed": "not-needed";
34
+ ready: "ready";
35
+ unsupported: "unsupported";
36
+ failed: "failed";
37
+ "too-large": "too-large";
38
+ }>;
39
+ format: z.ZodOptional<z.ZodEnum<{
40
+ text: "text";
41
+ markdown: "markdown";
42
+ }>>;
43
+ bytes: z.ZodOptional<z.ZodNumber>;
44
+ errorCode: z.ZodOptional<z.ZodString>;
45
+ }, z.core.$strip>;
46
+ createdAt: z.ZodNumber;
47
+ }, z.core.$strip>;
48
+ /** Parse and validate a stored asset from parsed JSON meta. */
49
+ export declare function parseStoredChannelAsset(value: unknown): StoredChannelAsset;
50
+ /** Type-safe structured parse (throws ZodError on mismatch). */
51
+ export declare function parseStoredChannelAssetSafe(value: unknown): {
52
+ ok: true;
53
+ value: StoredChannelAsset;
54
+ } | {
55
+ ok: false;
56
+ error: unknown;
57
+ };
58
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/attachments/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAiB3E,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiBnC,CAAC;AAEH,+DAA+D;AAC/D,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,CAE1E;AAED,gEAAgE;AAChE,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,GACb;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,kBAAkB,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAIzE"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Zod schema for `meta.json` — used to validate a `StoredChannelAsset` when it
3
+ * is read back from disk (plan \u00a745 / \u00a747). Anything that fails this
4
+ * schema is treated as missing/corrupt rather than partially trusted.
5
+ */
6
+ import { z } from 'zod';
7
+ import { ASSET_SCHEMA_VERSION } from './types.js';
8
+ const extractionStatusSchema = z.enum([
9
+ 'not-needed',
10
+ 'ready',
11
+ 'unsupported',
12
+ 'failed',
13
+ 'too-large',
14
+ ]);
15
+ const extractionSchema = z.object({
16
+ status: extractionStatusSchema,
17
+ format: z.enum(['text', 'markdown']).optional(),
18
+ bytes: z.number().int().nonnegative().optional(),
19
+ errorCode: z.string().optional(),
20
+ });
21
+ export const StoredChannelAssetSchema = z.object({
22
+ schemaVersion: z.literal(ASSET_SCHEMA_VERSION),
23
+ attachmentId: z.string().min(1),
24
+ sessionId: z.string().min(1),
25
+ channelId: z.string().min(1),
26
+ accountId: z.string().min(1),
27
+ conversationId: z.string().min(1),
28
+ conversationType: z.enum(['dm', 'group']).optional(),
29
+ threadId: z.string().optional(),
30
+ messageId: z.string().min(1),
31
+ kind: z.enum(['file', 'audio', 'video', 'image']),
32
+ name: z.string().min(0),
33
+ mimeType: z.string().optional(),
34
+ bytes: z.number().int().nonnegative(),
35
+ sha256: z.string().regex(/^[0-9a-f]{64}$/),
36
+ extraction: extractionSchema,
37
+ createdAt: z.number(),
38
+ });
39
+ /** Parse and validate a stored asset from parsed JSON meta. */
40
+ export function parseStoredChannelAsset(value) {
41
+ return StoredChannelAssetSchema.parse(value);
42
+ }
43
+ /** Type-safe structured parse (throws ZodError on mismatch). */
44
+ export function parseStoredChannelAssetSafe(value) {
45
+ const result = StoredChannelAssetSchema.safeParse(value);
46
+ if (result.success)
47
+ return { ok: true, value: result.data };
48
+ return { ok: false, error: result.error };
49
+ }
50
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/attachments/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,oBAAoB,EAA2B,MAAM,YAAY,CAAC;AAE3E,MAAM,sBAAsB,GAAG,CAAC,CAAC,IAAI,CAAC;IACpC,YAAY;IACZ,OAAO;IACP,aAAa;IACb,QAAQ;IACR,WAAW;CACZ,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChC,MAAM,EAAE,sBAAsB;IAC9B,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC/C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IAChD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACjC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC;IAC9C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACjC,gBAAgB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE;IACpD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACjD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACrC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC;IAC1C,UAAU,EAAE,gBAAgB;IAC5B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;CACtB,CAAC,CAAC;AAEH,+DAA+D;AAC/D,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACpD,OAAO,wBAAwB,CAAC,KAAK,CAAC,KAAK,CAAuB,CAAC;AACrE,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,2BAA2B,CACzC,KAAc;IAEd,MAAM,MAAM,GAAG,wBAAwB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACzD,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,IAA0B,EAAE,CAAC;IAClF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,69 @@
1
+ import { type StoredChannelAsset, type PutChannelAssetInput, type StoredAssetExtractionInput, type AssetExtraction } from './types.js';
2
+ import { type AttachmentPolicy } from './policy.js';
3
+ /** Errors the store raises for security / integrity boundaries. */
4
+ export declare class AssetStoreError extends Error {
5
+ readonly code: 'ASSET_NOT_FOUND' | 'ASSET_TOO_LARGE' | 'ASSET_OVER_MAX_INBOUND' | 'ASSET_CORRUPT_META';
6
+ constructor(code: AssetStoreError['code'], message: string);
7
+ }
8
+ export interface PutChannelAssetContext {
9
+ root?: string;
10
+ policy?: AttachmentPolicy;
11
+ }
12
+ /** The store contract (plan \u00a743). */
13
+ export interface ChannelInboundAssetStore {
14
+ put(input: PutChannelAssetInput): Promise<StoredChannelAsset>;
15
+ putExtracted(attachmentId: string, extraction: StoredAssetExtractionInput): Promise<StoredChannelAsset | undefined>;
16
+ /**
17
+ * Record a NON-ready extraction status (unsupported / failed / too-large)
18
+ * for an already-stored asset, updating only meta.json. No extracted.md is
19
+ * written. No-op when the asset does not exist.
20
+ */
21
+ recordExtraction?(attachmentId: string, extraction: AssetExtraction): Promise<void>;
22
+ get(attachmentId: string): Promise<StoredChannelAsset | undefined>;
23
+ readRaw(attachmentId: string, options: {
24
+ maxBytes: number;
25
+ signal?: AbortSignal;
26
+ }): Promise<Uint8Array>;
27
+ readExtracted(attachmentId: string, options: {
28
+ maxBytes: number;
29
+ signal?: AbortSignal;
30
+ }): Promise<string | undefined>;
31
+ }
32
+ /**
33
+ * Filesystem-backed `ChannelInboundAssetStore`. Loads the durable
34
+ * `index.json` lazily on first read, keeps an in-memory cache in sync, and
35
+ * serializes all publish writes through a promise chain.
36
+ */
37
+ export declare class FileChannelInboundAssetStore implements ChannelInboundAssetStore {
38
+ private readonly root;
39
+ private readonly policy;
40
+ private index;
41
+ private indexLoaded;
42
+ private writeChain;
43
+ constructor(options?: PutChannelAssetContext);
44
+ /** Per-asset directory rooted at THIS store's root, not the global default. */
45
+ private assetDir;
46
+ /** Run a publish write serialized onto the chain (returns after this op). */
47
+ private enqueue;
48
+ put(input: PutChannelAssetInput): Promise<StoredChannelAsset>;
49
+ putExtracted(attachmentId: string, extraction: StoredAssetExtractionInput): Promise<StoredChannelAsset | undefined>;
50
+ recordExtraction(attachmentId: string, extraction: AssetExtraction): Promise<void>;
51
+ get(attachmentId: string): Promise<StoredChannelAsset | undefined>;
52
+ readRaw(attachmentId: string, options: {
53
+ maxBytes: number;
54
+ signal?: AbortSignal;
55
+ }): Promise<Uint8Array>;
56
+ readExtracted(attachmentId: string, options: {
57
+ maxBytes: number;
58
+ signal?: AbortSignal;
59
+ }): Promise<string | undefined>;
60
+ /** Atomic directory publish (plan \u00a744): stage, then rename over target. */
61
+ private publishDir;
62
+ private readBounded;
63
+ private ensureIndex;
64
+ private persistIndex;
65
+ private idempotentIndex;
66
+ private readMeta;
67
+ private resolveAttachmentId;
68
+ }
69
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/attachments/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Private channel inbound asset store (plan \u00a742-\u00a747).
3
+ *
4
+ * Disk layout (plan \u00a742):
5
+ *
6
+ * \`\`\`
7
+ * <attachmentsRoot>/
8
+ * sessions/
9
+ * <sessionId>/
10
+ * <messageId>/
11
+ * <attachmentId>/
12
+ * meta.json # StoredChannelAsset, schemaVersion: 1
13
+ * raw.bin # verbatim stored bytes
14
+ * extracted.md # written only when extraction is ready
15
+ * index.json # store-owned attachmentId -> {sessionId,messageId}
16
+ * .staging/<uuid>/ # transient write area; never observed by readers
17
+ * \`\`\`
18
+ *
19
+ * PUBLISH IS ATOMIC (plan \u00a744): `put` writes the whole asset into a fresh
20
+ * `.staging/<uuid>` directory and then renames it over the final directory.
21
+ * A reader never observes a half-written asset. `meta.json` records the final
22
+ * `sha256` / `bytes` computed by the store (never caller-supplied truth), and
23
+ * an adapter `mimeType` is only a hint that is re-verified by magic sniffing
24
+ * (plan \u00a747).
25
+ *
26
+ * Extraction is two-phase: `put` stores the raw bytes + an initial extraction
27
+ * status, and `putExtracted` atomically publishes the extracted text and flips
28
+ * `meta.json` to `ready`. If `meta.json` is unreadable, `get` returns
29
+ * undefined — corrupt metadata is never partially trusted.
30
+ */
31
+ import { randomUUID } from 'node:crypto';
32
+ import { mkdir, open, readFile, rename, rm, writeFile, } from 'node:fs/promises';
33
+ import { join } from 'node:path';
34
+ import { resolveAttachmentsRoot } from '../paths.js';
35
+ import { sanitizeFilename, normalizeMimeHint } from './filename.js';
36
+ import { verifiedMime } from './mime.js';
37
+ import { sha256Hex } from './hash.js';
38
+ import { assetPathSegment } from '../paths.js';
39
+ import { parseStoredChannelAsset } from './schema.js';
40
+ import { ASSET_SCHEMA_VERSION, } from './types.js';
41
+ import { DEFAULT_ATTACHMENT_POLICY, isInboundWithinLimit, } from './policy.js';
42
+ /** Errors the store raises for security / integrity boundaries. */
43
+ export class AssetStoreError extends Error {
44
+ code;
45
+ constructor(code, message) {
46
+ super(message);
47
+ this.name = 'AssetStoreError';
48
+ this.code = code;
49
+ }
50
+ }
51
+ const INDEX_FILE = 'index.json';
52
+ /**
53
+ * Filesystem-backed `ChannelInboundAssetStore`. Loads the durable
54
+ * `index.json` lazily on first read, keeps an in-memory cache in sync, and
55
+ * serializes all publish writes through a promise chain.
56
+ */
57
+ export class FileChannelInboundAssetStore {
58
+ root;
59
+ policy;
60
+ index = new Map();
61
+ indexLoaded = false;
62
+ writeChain = Promise.resolve();
63
+ constructor(options = {}) {
64
+ this.root = options.root ?? resolveAttachmentsRoot();
65
+ this.policy = options.policy ?? DEFAULT_ATTACHMENT_POLICY;
66
+ }
67
+ /** Per-asset directory rooted at THIS store's root, not the global default. */
68
+ assetDir(attachmentId, sessionId, messageId) {
69
+ return join(this.root, 'sessions', sessionId, messageId, assetPathSegment(attachmentId));
70
+ }
71
+ /** Run a publish write serialized onto the chain (returns after this op). */
72
+ enqueue(op) {
73
+ this.writeChain = this.writeChain.then(op);
74
+ return this.writeChain;
75
+ }
76
+ async put(input) {
77
+ if (!isInboundWithinLimit(input.data.byteLength, this.policy)) {
78
+ throw new AssetStoreError('ASSET_OVER_MAX_INBOUND', "asset '" + input.attachmentId + "' is " + input.data.byteLength
79
+ + ' bytes, over the inbound cap ' + this.policy.maxInboundBytes);
80
+ }
81
+ const attachmentId = this.resolveAttachmentId(input.attachmentId);
82
+ const asset = {
83
+ schemaVersion: ASSET_SCHEMA_VERSION,
84
+ attachmentId,
85
+ sessionId: input.sessionId,
86
+ channelId: input.channelId,
87
+ accountId: input.accountId,
88
+ conversationId: input.conversationId,
89
+ ...(input.conversationType ? { conversationType: input.conversationType } : {}),
90
+ ...(input.threadId ? { threadId: input.threadId } : {}),
91
+ messageId: input.messageId,
92
+ kind: input.kind,
93
+ name: sanitizeFilename(input.name),
94
+ mimeType: verifiedMime(input.data, normalizeMimeHint(input.mimeType)),
95
+ bytes: input.data.byteLength,
96
+ sha256: sha256Hex(input.data),
97
+ extraction: input.extraction ?? { status: 'not-needed' },
98
+ createdAt: input.createdAt ?? Date.now(),
99
+ };
100
+ await this.publishDir(input.sessionId, input.messageId, attachmentId, asset, input.data);
101
+ return asset;
102
+ }
103
+ async putExtracted(attachmentId, extraction) {
104
+ const location = this.index.get(attachmentId);
105
+ if (!location)
106
+ throw new AssetStoreError('ASSET_NOT_FOUND', "no stored asset for '" + attachmentId + "'");
107
+ const dir = this.assetDir(attachmentId, location.sessionId, location.messageId);
108
+ const meta = await this.readMeta(dir);
109
+ if (!meta)
110
+ throw new AssetStoreError('ASSET_NOT_FOUND', "no meta for '" + attachmentId + "'");
111
+ const outBytes = extraction.bytes ?? new TextEncoder().encode(extraction.text).byteLength;
112
+ const updated = {
113
+ ...meta,
114
+ extraction: { status: 'ready', format: extraction.format, bytes: outBytes },
115
+ };
116
+ // Atomically replace extracted.md (staging file -> final name), then flip
117
+ // meta.json. A reader either sees both, or neither flipped.
118
+ await this.enqueue(async () => {
119
+ const stagingDir = join(this.root, '.staging', randomUUID());
120
+ await mkdir(stagingDir, { recursive: true });
121
+ const stagedExtracted = join(stagingDir, 'extracted.md');
122
+ await writeFile(stagedExtracted, extraction.text, 'utf8');
123
+ await rename(stagedExtracted, join(dir, 'extracted.md'));
124
+ // Replace meta.json atomically (temp file + rename within the asset dir).
125
+ const metaTmp = join(dir, 'meta.json.' + randomUUID() + '.tmp');
126
+ await writeFile(metaTmp, JSON.stringify(updated, null, 2), 'utf8');
127
+ await rename(metaTmp, join(dir, 'meta.json'));
128
+ await rm(stagingDir, { recursive: true, force: true });
129
+ });
130
+ return updated;
131
+ }
132
+ async recordExtraction(attachmentId, extraction) {
133
+ const location = this.index.get(attachmentId);
134
+ if (!location)
135
+ return;
136
+ const dir = this.assetDir(attachmentId, location.sessionId, location.messageId);
137
+ const meta = await this.readMeta(dir);
138
+ if (!meta)
139
+ return;
140
+ const updated = { ...meta, extraction };
141
+ // Replace meta.json atomically (temp file + rename within the asset dir).
142
+ await this.enqueue(async () => {
143
+ const metaTmp = join(dir, 'meta.json.' + randomUUID() + '.tmp');
144
+ await writeFile(metaTmp, JSON.stringify(updated, null, 2), 'utf8');
145
+ await rename(metaTmp, join(dir, 'meta.json'));
146
+ });
147
+ }
148
+ async get(attachmentId) {
149
+ await this.ensureIndex();
150
+ const location = this.index.get(attachmentId);
151
+ if (!location)
152
+ return undefined;
153
+ return this.readMeta(this.assetDir(attachmentId, location.sessionId, location.messageId));
154
+ }
155
+ async readRaw(attachmentId, options) {
156
+ await this.ensureIndex();
157
+ const location = this.index.get(attachmentId);
158
+ if (!location)
159
+ throw new AssetStoreError('ASSET_NOT_FOUND', "no stored asset for '" + attachmentId + "'");
160
+ const file = join(this.assetDir(attachmentId, location.sessionId, location.messageId), 'raw.bin');
161
+ return this.readBounded(file, options.maxBytes, options.signal);
162
+ }
163
+ async readExtracted(attachmentId, options) {
164
+ await this.ensureIndex();
165
+ const location = this.index.get(attachmentId);
166
+ if (!location)
167
+ return undefined;
168
+ const dir = this.assetDir(attachmentId, location.sessionId, location.messageId);
169
+ const meta = await this.readMeta(dir);
170
+ if (!meta || meta.extraction.status !== 'ready')
171
+ return undefined;
172
+ const bytes = await this.readBounded(join(dir, 'extracted.md'), options.maxBytes, options.signal);
173
+ return new TextDecoder('utf-8').decode(bytes);
174
+ }
175
+ /** Atomic directory publish (plan \u00a744): stage, then rename over target. */
176
+ async publishDir(sessionId, messageId, attachmentId, asset, data) {
177
+ const finalDir = this.assetDir(attachmentId, sessionId, messageId);
178
+ const staging = join(this.root, '.staging', randomUUID());
179
+ await mkdir(staging, { recursive: true });
180
+ await writeFile(join(staging, 'raw.bin'), data);
181
+ await writeFile(join(staging, 'meta.json'), JSON.stringify(asset, null, 2), 'utf8');
182
+ await mkdir(join(finalDir, '..'), { recursive: true });
183
+ await this.enqueue(async () => {
184
+ await rename(staging, finalDir);
185
+ });
186
+ // Keep the lookup index durable. A publish that wrote bytes but failed to
187
+ // record the index is recoverable by re-putting; we prefer correctness.
188
+ this.idempotentIndex(attachmentId, { sessionId, messageId });
189
+ await this.persistIndex();
190
+ }
191
+ async readBounded(file, maxBytes, signal) {
192
+ if (signal?.aborted)
193
+ throw new AssetStoreError('ASSET_CORRUPT_META', 'aborted');
194
+ const handle = await open(file, 'r');
195
+ try {
196
+ const stat = await handle.stat();
197
+ if (stat.size > maxBytes) {
198
+ throw new AssetStoreError('ASSET_TOO_LARGE', 'read over cap ' + maxBytes);
199
+ }
200
+ const buffer = Buffer.alloc(stat.size);
201
+ const { bytesRead } = await handle.read(buffer, 0, stat.size, 0);
202
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, bytesRead);
203
+ }
204
+ finally {
205
+ await handle.close();
206
+ }
207
+ }
208
+ async ensureIndex() {
209
+ if (this.indexLoaded)
210
+ return;
211
+ this.indexLoaded = true;
212
+ try {
213
+ const text = await readFile(join(this.root, INDEX_FILE), 'utf8');
214
+ const parsed = JSON.parse(text);
215
+ if (parsed && typeof parsed === 'object') {
216
+ this.index = new Map(Object.entries(parsed));
217
+ }
218
+ }
219
+ catch {
220
+ this.index = new Map();
221
+ }
222
+ }
223
+ async persistIndex() {
224
+ await mkdir(this.root, { recursive: true });
225
+ const payload = JSON.stringify(Object.fromEntries(this.index), null, 2);
226
+ await this.enqueue(async () => {
227
+ const tmp = join(this.root, '.' + INDEX_FILE + '.' + randomUUID() + '.tmp');
228
+ await writeFile(tmp, payload, 'utf8');
229
+ await rename(tmp, join(this.root, INDEX_FILE));
230
+ });
231
+ }
232
+ idempotentIndex(attachmentId, location) {
233
+ this.index.set(attachmentId, location);
234
+ }
235
+ async readMeta(dir) {
236
+ let text;
237
+ try {
238
+ text = await readFile(join(dir, 'meta.json'), 'utf8');
239
+ }
240
+ catch {
241
+ return undefined;
242
+ }
243
+ try {
244
+ return parseStoredChannelAsset(JSON.parse(text));
245
+ }
246
+ catch {
247
+ return undefined;
248
+ }
249
+ }
250
+ resolveAttachmentId(input) {
251
+ return input && input.trim().length > 0 ? input : 'att-' + randomUUID();
252
+ }
253
+ }
254
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/attachments/store.ts"],"names":[],"mappings":"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"}