@haneinfo/attachments 0.0.0-stage → 0.1.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 (53) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +75 -2
  3. package/agent-guide.md +86 -0
  4. package/dist/cleanup.worker.d.ts +13 -0
  5. package/dist/cleanup.worker.d.ts.map +1 -0
  6. package/dist/cleanup.worker.js +57 -0
  7. package/dist/cleanup.worker.js.map +1 -0
  8. package/dist/config.d.ts +38 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +74 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/controller.d.ts +45 -0
  13. package/dist/controller.d.ts.map +1 -0
  14. package/dist/controller.js +214 -0
  15. package/dist/controller.js.map +1 -0
  16. package/dist/index.d.ts +8 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +24 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/mcp-tools.d.ts +10 -0
  21. package/dist/mcp-tools.d.ts.map +1 -0
  22. package/dist/mcp-tools.js +74 -0
  23. package/dist/mcp-tools.js.map +1 -0
  24. package/dist/module.d.ts +6 -0
  25. package/dist/module.d.ts.map +1 -0
  26. package/dist/module.js +54 -0
  27. package/dist/module.js.map +1 -0
  28. package/dist/plugin.d.ts +44 -0
  29. package/dist/plugin.d.ts.map +1 -0
  30. package/dist/plugin.js +90 -0
  31. package/dist/plugin.js.map +1 -0
  32. package/dist/service.d.ts +123 -0
  33. package/dist/service.d.ts.map +1 -0
  34. package/dist/service.js +384 -0
  35. package/dist/service.js.map +1 -0
  36. package/dist/storage.d.ts +54 -0
  37. package/dist/storage.d.ts.map +1 -0
  38. package/dist/storage.js +239 -0
  39. package/dist/storage.js.map +1 -0
  40. package/dist/store.d.ts +61 -0
  41. package/dist/store.d.ts.map +1 -0
  42. package/dist/store.js +25 -0
  43. package/dist/store.js.map +1 -0
  44. package/dist/validation.d.ts +29 -0
  45. package/dist/validation.d.ts.map +1 -0
  46. package/dist/validation.js +297 -0
  47. package/dist/validation.js.map +1 -0
  48. package/haneinfo.plugin.json +72 -0
  49. package/package.json +80 -3
  50. package/plugin.d.ts +2 -0
  51. package/plugin.js +4 -0
  52. package/prisma/attachments.prisma +32 -0
  53. package/prisma/attachments.sql +13 -0
@@ -0,0 +1,297 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createReadStream = exports.mimeTypes = exports.STAGING_ROOT = void 0;
7
+ exports.cleanFileName = cleanFileName;
8
+ exports.downloadHeaders = downloadHeaders;
9
+ exports.stageUpload = stageUpload;
10
+ exports.cleanupStaging = cleanupStaging;
11
+ const node_crypto_1 = require("node:crypto");
12
+ const node_fs_1 = require("node:fs");
13
+ Object.defineProperty(exports, "createReadStream", { enumerable: true, get: function () { return node_fs_1.createReadStream; } });
14
+ const promises_1 = require("node:fs/promises");
15
+ const node_os_1 = __importDefault(require("node:os"));
16
+ const node_path_1 = __importDefault(require("node:path"));
17
+ const node_stream_1 = require("node:stream");
18
+ const promises_2 = require("node:stream/promises");
19
+ const node_zlib_1 = require("node:zlib");
20
+ const plugin_api_1 = require("@haneinfo/plugin-api");
21
+ const config_1 = require("./config");
22
+ exports.STAGING_ROOT = node_path_1.default.join(node_os_1.default.tmpdir(), 'haneinfo-attachments-staging');
23
+ const dangerous = new Set([
24
+ 'exe',
25
+ 'dll',
26
+ 'com',
27
+ 'bat',
28
+ 'cmd',
29
+ 'ps1',
30
+ 'psm1',
31
+ 'sh',
32
+ 'bash',
33
+ 'js',
34
+ 'mjs',
35
+ 'cjs',
36
+ 'vbs',
37
+ 'vbe',
38
+ 'wsf',
39
+ 'wsh',
40
+ 'hta',
41
+ 'msi',
42
+ 'scr',
43
+ 'pif',
44
+ 'jar',
45
+ 'php',
46
+ 'py',
47
+ 'pl',
48
+ 'rb',
49
+ 'html',
50
+ 'htm',
51
+ 'xhtml',
52
+ 'svg',
53
+ 'asp',
54
+ 'aspx',
55
+ 'jsp',
56
+ 'lnk',
57
+ 'url',
58
+ ]);
59
+ exports.mimeTypes = {
60
+ pdf: 'application/pdf',
61
+ png: 'image/png',
62
+ jpeg: 'image/jpeg',
63
+ gif: 'image/gif',
64
+ webp: 'image/webp',
65
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
66
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
67
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
68
+ csv: 'text/csv',
69
+ txt: 'text/plain',
70
+ };
71
+ const canonical = (extension) => (extension === 'jpg' ? 'jpeg' : extension);
72
+ function cleanFileName(raw) {
73
+ // Keep user names as text only; storage keys never use them.
74
+ const name = raw
75
+ .replace(/[\uD800-\uDFFF]/gu, '')
76
+ .normalize('NFC')
77
+ .split(/[\\/]/)
78
+ .pop()
79
+ ?.replace(/[\p{Cc}\p{Cf}]/gu, '')
80
+ .trim() ?? '';
81
+ const parts = name.toLowerCase().split('.');
82
+ const extension = canonical(parts.pop() ?? '');
83
+ if (!name || !parts.some(Boolean) || !extension)
84
+ throw new plugin_api_1.MechanismError('ATTACHMENT_NAME_INVALID', 'A nonempty file name and extension are required');
85
+ if (dangerous.has(extension) ||
86
+ parts.some((part) => dangerous.has(part) || config_1.attachmentTypes.includes(canonical(part))))
87
+ throw new plugin_api_1.MechanismError('ATTACHMENT_TYPE_NOT_ALLOWED', 'Active content or double extensions are not allowed');
88
+ const suffix = name.slice(name.lastIndexOf('.'));
89
+ return {
90
+ fileName: [...name.slice(0, -suffix.length)].slice(0, 180 - [...suffix].length).join('') + suffix,
91
+ extension,
92
+ };
93
+ }
94
+ function downloadHeaders(record) {
95
+ const encoded = encodeURIComponent(record.fileName).replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
96
+ return {
97
+ 'Content-Disposition': `attachment; filename*=UTF-8''${encoded}`,
98
+ 'X-Content-Type-Options': 'nosniff',
99
+ 'Cache-Control': 'no-store',
100
+ 'Content-Type': record.detectedContentType,
101
+ 'Content-Length': String(record.size),
102
+ };
103
+ }
104
+ function signature(bytes) {
105
+ if (bytes.subarray(0, 5).toString() === '%PDF-')
106
+ return 'pdf';
107
+ if (bytes.subarray(0, 8).equals(Buffer.from('89504e470d0a1a0a', 'hex')))
108
+ return 'png';
109
+ if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)
110
+ return 'jpeg';
111
+ if (/^GIF8[79]a/.test(bytes.subarray(0, 6).toString()))
112
+ return 'gif';
113
+ if (bytes.subarray(0, 4).toString() === 'RIFF' && bytes.subarray(8, 12).toString() === 'WEBP')
114
+ return 'webp';
115
+ if (bytes.subarray(0, 4).equals(Buffer.from('504b0304', 'hex')))
116
+ return 'zip';
117
+ return null;
118
+ }
119
+ const mismatch = () => new plugin_api_1.MechanismError('ATTACHMENT_TYPE_MISMATCH', 'File contents do not match the allowed extension');
120
+ function checkPrefix(bytes, extension) {
121
+ const detected = signature(bytes);
122
+ if (['docx', 'xlsx', 'pptx'].includes(extension)) {
123
+ if (detected !== 'zip')
124
+ throw mismatch();
125
+ }
126
+ else if (extension === 'txt' || extension === 'csv') {
127
+ if (detected ||
128
+ /^(?:MZ|\x7fELF|#!)|^\s*(?:<!doctype\s+html|<html\b|<svg\b|<script\b|<\?php)/i.test(bytes.toString('utf8').replace(/^\uFEFF/, '')))
129
+ throw mismatch();
130
+ }
131
+ else if (detected !== extension)
132
+ throw mismatch();
133
+ }
134
+ /** Read bounded ZIP metadata and only the content-types XML, never unpack the document. */
135
+ async function verifyOffice(file, size, extension) {
136
+ const handle = await (0, promises_1.open)(file, 'r');
137
+ const read = async (position, length) => {
138
+ if (position < 0 || length < 0 || position + length > size || length > 128 * 1024)
139
+ throw mismatch();
140
+ const buffer = Buffer.alloc(length);
141
+ if ((await handle.read(buffer, 0, length, position)).bytesRead !== length)
142
+ throw mismatch();
143
+ return buffer;
144
+ };
145
+ try {
146
+ const tail = await read(Math.max(0, size - 65557), Math.min(size, 65557));
147
+ let end = tail.length - 22;
148
+ while (end >= 0 && tail.readUInt32LE(end) !== 0x06054b50)
149
+ end--;
150
+ if (end < 0 ||
151
+ end + 22 + tail.readUInt16LE(end + 20) !== tail.length ||
152
+ tail.readUInt16LE(end + 4) ||
153
+ tail.readUInt16LE(end + 6))
154
+ throw mismatch();
155
+ const entries = tail.readUInt16LE(end + 10), centralSize = tail.readUInt32LE(end + 12), centralOffset = tail.readUInt32LE(end + 16);
156
+ if (!entries ||
157
+ entries > 2000 ||
158
+ tail.readUInt16LE(end + 8) !== entries ||
159
+ centralOffset + centralSize !== size - tail.length + end)
160
+ throw mismatch();
161
+ const central = await read(centralOffset, centralSize);
162
+ const main = {
163
+ docx: 'word/document.xml',
164
+ xlsx: 'xl/workbook.xml',
165
+ pptx: 'ppt/presentation.xml',
166
+ }[extension];
167
+ let offset = 0, hasMain = false, types;
168
+ const names = new Set();
169
+ for (let index = 0; index < entries; index++) {
170
+ if (offset + 46 > central.length || central.readUInt32LE(offset) !== 0x02014b50)
171
+ throw mismatch();
172
+ const flags = central.readUInt16LE(offset + 8), method = central.readUInt16LE(offset + 10);
173
+ const compressed = central.readUInt32LE(offset + 20), expanded = central.readUInt32LE(offset + 24);
174
+ const nameLength = central.readUInt16LE(offset + 28), extra = central.readUInt16LE(offset + 30), comment = central.readUInt16LE(offset + 32);
175
+ const localOffset = central.readUInt32LE(offset + 42);
176
+ const endOffset = offset + 46 + nameLength + extra + comment;
177
+ if (endOffset > central.length || flags & 1 || ![0, 8].includes(method))
178
+ throw mismatch();
179
+ const name = central.subarray(offset + 46, offset + 46 + nameLength).toString('utf8');
180
+ if (names.has(name) || /(?:^|\/)\.\.(?:\/|$)|\\|vbaProject\.bin$/i.test(name))
181
+ throw mismatch();
182
+ names.add(name);
183
+ const header = await read(localOffset, 30);
184
+ if (header.readUInt32LE(0) !== 0x04034b50 ||
185
+ header.readUInt16LE(8) !== method ||
186
+ header.readUInt16LE(6) !== flags)
187
+ throw mismatch();
188
+ const localName = await read(localOffset + 30, header.readUInt16LE(26));
189
+ if (localName.toString('utf8') !== name)
190
+ throw mismatch();
191
+ const start = localOffset + 30 + localName.length + header.readUInt16LE(28);
192
+ if (start + compressed > centralOffset)
193
+ throw mismatch();
194
+ if (name === main)
195
+ hasMain = expanded > 0;
196
+ if (name === '[Content_Types].xml') {
197
+ if (expanded > 65536 || compressed > 65536)
198
+ throw mismatch();
199
+ const data = await read(start, compressed);
200
+ types = method === 8 ? (0, node_zlib_1.inflateRawSync)(data, { maxOutputLength: 65536 }) : data;
201
+ if (types.length !== expanded)
202
+ throw mismatch();
203
+ }
204
+ offset = endOffset;
205
+ }
206
+ if (offset !== central.length ||
207
+ !hasMain ||
208
+ !types ||
209
+ !types.toString('utf8').includes(`PartName="/${main}"`) ||
210
+ !types.toString('utf8').includes(`${exports.mimeTypes[extension]}.main+xml`))
211
+ throw mismatch();
212
+ }
213
+ catch {
214
+ throw mismatch();
215
+ }
216
+ finally {
217
+ await handle.close();
218
+ }
219
+ }
220
+ async function stageUpload(stream, id, extension, config, signal) {
221
+ await (0, promises_1.mkdir)(exports.STAGING_ROOT, { recursive: true, mode: 0o700 });
222
+ const file = node_path_1.default.join(exports.STAGING_ROOT, id);
223
+ let size = 0, prefix = Buffer.alloc(0), checked = false, textTail = '';
224
+ const hash = (0, node_crypto_1.createHash)('sha256');
225
+ const text = extension === 'txt' || extension === 'csv'
226
+ ? new TextDecoder('utf-8', { fatal: true })
227
+ : undefined;
228
+ const validator = new node_stream_1.Transform({
229
+ transform(chunk, _encoding, callback) {
230
+ try {
231
+ size += chunk.length;
232
+ if (size > config.maxFileBytes)
233
+ throw new plugin_api_1.MechanismError('ATTACHMENT_TOO_LARGE', 'The file exceeds maxFileBytes', 413);
234
+ if (prefix.length < 512)
235
+ prefix = Buffer.concat([prefix, chunk.subarray(0, 512 - prefix.length)]);
236
+ if (!checked && prefix.length >= 512) {
237
+ checkPrefix(prefix, extension);
238
+ checked = true;
239
+ }
240
+ if (text) {
241
+ if (chunk.includes(0))
242
+ throw mismatch();
243
+ const decoded = textTail + text.decode(chunk, { stream: true });
244
+ if (/<(?:html|svg|script)\b|<!doctype\s+html|<\?php/i.test(decoded))
245
+ throw mismatch();
246
+ textTail = decoded.slice(-64);
247
+ }
248
+ hash.update(chunk);
249
+ callback(null, chunk);
250
+ }
251
+ catch (error) {
252
+ callback(error instanceof plugin_api_1.MechanismError ? error : mismatch());
253
+ }
254
+ },
255
+ });
256
+ try {
257
+ await (0, promises_2.pipeline)(stream, validator, (0, node_fs_1.createWriteStream)(file, { flags: 'wx', mode: 0o600 }), {
258
+ signal,
259
+ });
260
+ if (!size)
261
+ throw mismatch();
262
+ checkPrefix(prefix, extension);
263
+ if (text) {
264
+ try {
265
+ text.decode();
266
+ }
267
+ catch {
268
+ throw mismatch();
269
+ }
270
+ }
271
+ if (['docx', 'xlsx', 'pptx'].includes(extension))
272
+ await verifyOffice(file, size, extension);
273
+ return { file, size, sha256: hash.digest('hex'), detectedContentType: exports.mimeTypes[extension] };
274
+ }
275
+ catch (error) {
276
+ await (0, promises_1.rm)(file, { force: true });
277
+ throw error;
278
+ }
279
+ }
280
+ async function cleanupStaging(before, limit) {
281
+ await (0, promises_1.mkdir)(exports.STAGING_ROOT, { recursive: true, mode: 0o700 });
282
+ let removed = 0;
283
+ const directory = await (0, promises_1.opendir)(exports.STAGING_ROOT);
284
+ for await (const entry of directory) {
285
+ if (removed >= limit)
286
+ break;
287
+ if (!entry.isFile() || !/^[0-9a-f-]{36}$/.test(entry.name))
288
+ continue;
289
+ const file = node_path_1.default.join(exports.STAGING_ROOT, entry.name);
290
+ if ((await (0, promises_1.stat)(file)).mtime < before) {
291
+ await (0, promises_1.rm)(file, { force: true });
292
+ removed++;
293
+ }
294
+ }
295
+ return removed;
296
+ }
297
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":";;;;;;AA6DA,sCAkCC;AACD,0CAgBC;AA4HD,kCA8DC;AAED,wCAcC;AA1TD,6CAAyC;AACzC,qCAA8D;AA0TrD,iGA1TA,0BAAgB,OA0TA;AAzTzB,+CAAkE;AAClE,sDAAyB;AACzB,0DAA6B;AAC7B,6CAAuD;AACvD,mDAAgD;AAChD,yCAA2C;AAC3C,qDAAsD;AACtD,qCAA2E;AAE9D,QAAA,YAAY,GAAG,mBAAI,CAAC,IAAI,CAAC,iBAAE,CAAC,MAAM,EAAE,EAAE,8BAA8B,CAAC,CAAC;AACnF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;IACxB,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,MAAM;IACN,IAAI;IACJ,MAAM;IACN,IAAI;IACJ,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,MAAM;IACN,KAAK;IACL,OAAO;IACP,KAAK;IACL,KAAK;IACL,MAAM;IACN,KAAK;IACL,KAAK;IACL,KAAK;CACN,CAAC,CAAC;AACU,QAAA,SAAS,GAA2B;IAC/C,GAAG,EAAE,iBAAiB;IACtB,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,yEAAyE;IAC/E,IAAI,EAAE,mEAAmE;IACzE,IAAI,EAAE,2EAA2E;IACjF,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,YAAY;CAClB,CAAC;AACF,MAAM,SAAS,GAAG,CAAC,SAAiB,EAAE,EAAE,CAAC,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AACpF,SAAgB,aAAa,CAAC,GAAW;IACvC,6DAA6D;IAC7D,MAAM,IAAI,GACR,GAAG;SACA,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,SAAS,CAAC,KAAK,CAAC;SAChB,KAAK,CAAC,OAAO,CAAC;SACd,GAAG,EAAE;QACN,EAAE,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;SAChC,IAAI,EAAE,IAAI,EAAE,CAAC;IAClB,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS;QAC7C,MAAM,IAAI,2BAAc,CACtB,yBAAyB,EACzB,iDAAiD,CAClD,CAAC;IACJ,IACE,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC;QACxB,KAAK,CAAC,IAAI,CACR,CAAC,IAAI,EAAE,EAAE,CACP,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAK,wBAAqC,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAC1F;QAED,MAAM,IAAI,2BAAc,CACtB,6BAA6B,EAC7B,qDAAqD,CACtD,CAAC;IACJ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;IACjD,OAAO;QACL,QAAQ,EACN,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,MAAM;QACzF,SAAS;KACV,CAAC;AACJ,CAAC;AACD,SAAgB,eAAe,CAAC,MAI/B;IACC,MAAM,OAAO,GAAG,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CACzD,SAAS,EACT,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,CACxD,CAAC;IACF,OAAO;QACL,qBAAqB,EAAE,gCAAgC,OAAO,EAAE;QAChE,wBAAwB,EAAE,SAAS;QACnC,eAAe,EAAE,UAAU;QAC3B,cAAc,EAAE,MAAM,CAAC,mBAAmB;QAC1C,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;KACtC,CAAC;AACJ,CAAC;AACD,SAAS,SAAS,CAAC,KAAa;IAC9B,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACtF,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAC/E,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAAE,OAAO,KAAK,CAAC;IACrE,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,KAAK,MAAM;QAC3F,OAAO,MAAM,CAAC;IAChB,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,OAAO,IAAI,CAAC;AACd,CAAC;AACD,MAAM,QAAQ,GAAG,GAAG,EAAE,CACpB,IAAI,2BAAc,CAChB,0BAA0B,EAC1B,kDAAkD,CACnD,CAAC;AACJ,SAAS,WAAW,CAAC,KAAa,EAAE,SAAiB;IACnD,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACjD,IAAI,QAAQ,KAAK,KAAK;YAAE,MAAM,QAAQ,EAAE,CAAC;IAC3C,CAAC;SAAM,IAAI,SAAS,KAAK,KAAK,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;QACtD,IACE,QAAQ;YACR,8EAA8E,CAAC,IAAI,CACjF,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAC9C;YAED,MAAM,QAAQ,EAAE,CAAC;IACrB,CAAC;SAAM,IAAI,QAAQ,KAAK,SAAS;QAAE,MAAM,QAAQ,EAAE,CAAC;AACtD,CAAC;AAED,2FAA2F;AAC3F,KAAK,UAAU,YAAY,CAAC,IAAY,EAAE,IAAY,EAAE,SAAiB;IACvE,MAAM,MAAM,GAAG,MAAM,IAAA,eAAI,EAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,EAAE,QAAgB,EAAE,MAAc,EAAE,EAAE;QACtD,IAAI,QAAQ,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,QAAQ,GAAG,MAAM,GAAG,IAAI,IAAI,MAAM,GAAG,GAAG,GAAG,IAAI;YAC/E,MAAM,QAAQ,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,KAAK,MAAM;YAAE,MAAM,QAAQ,EAAE,CAAC;QAC5F,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IACF,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,KAAK,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;QAC1E,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QAC3B,OAAO,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,UAAU;YAAE,GAAG,EAAE,CAAC;QAChE,IACE,GAAG,GAAG,CAAC;YACP,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,MAAM;YACtD,IAAI,CAAC,YAAY,CAAC,GAAG,GAAG,CAAC,CAAC;YAC1B,IAAI,CAAC,YAAY,CAAC,GAAG,GAAG,CAAC,CAAC;YAE1B,MAAM,QAAQ,EAAE,CAAC;QACnB,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,GAAG,EAAE,CAAC,EACzC,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,GAAG,EAAE,CAAC,EACzC,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC;QAC9C,IACE,CAAC,OAAO;YACR,OAAO,GAAG,IAAI;YACd,IAAI,CAAC,YAAY,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,OAAO;YACtC,aAAa,GAAG,WAAW,KAAK,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,GAAG;YAExD,MAAM,QAAQ,EAAE,CAAC;QACnB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG;YACX,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE,iBAAiB;YACvB,IAAI,EAAE,sBAAsB;SAC7B,CAAC,SAAS,CAAC,CAAC;QACb,IAAI,MAAM,GAAG,CAAC,EACZ,OAAO,GAAG,KAAK,EACf,KAAyB,CAAC;QAC5B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;QAChC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC;YAC7C,IAAI,MAAM,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,UAAU;gBAC7E,MAAM,QAAQ,EAAE,CAAC;YACnB,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,EAC5C,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;YAC7C,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,EAAE,CAAC,EAClD,QAAQ,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;YAC/C,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,EAAE,CAAC,EAClD,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,EAAE,CAAC,EACzC,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;YAC9C,MAAM,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;YACtD,MAAM,SAAS,GAAG,MAAM,GAAG,EAAE,GAAG,UAAU,GAAG,KAAK,GAAG,OAAO,CAAC;YAC7D,IAAI,SAAS,GAAG,OAAO,CAAC,MAAM,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAAE,MAAM,QAAQ,EAAE,CAAC;YAC1F,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,EAAE,EAAE,MAAM,GAAG,EAAE,GAAG,UAAU,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACtF,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,2CAA2C,CAAC,IAAI,CAAC,IAAI,CAAC;gBAC3E,MAAM,QAAQ,EAAE,CAAC;YACnB,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAChB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YAC3C,IACE,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,UAAU;gBACrC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,MAAM;gBACjC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,KAAK;gBAEhC,MAAM,QAAQ,EAAE,CAAC;YACnB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,WAAW,GAAG,EAAE,EAAE,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC;YACxE,IAAI,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,IAAI;gBAAE,MAAM,QAAQ,EAAE,CAAC;YAC1D,MAAM,KAAK,GAAG,WAAW,GAAG,EAAE,GAAG,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;YAC5E,IAAI,KAAK,GAAG,UAAU,GAAG,aAAa;gBAAE,MAAM,QAAQ,EAAE,CAAC;YACzD,IAAI,IAAI,KAAK,IAAI;gBAAE,OAAO,GAAG,QAAQ,GAAG,CAAC,CAAC;YAC1C,IAAI,IAAI,KAAK,qBAAqB,EAAE,CAAC;gBACnC,IAAI,QAAQ,GAAG,KAAK,IAAI,UAAU,GAAG,KAAK;oBAAE,MAAM,QAAQ,EAAE,CAAC;gBAC7D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;gBAC3C,KAAK,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAA,0BAAc,EAAC,IAAI,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC/E,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ;oBAAE,MAAM,QAAQ,EAAE,CAAC;YAClD,CAAC;YACD,MAAM,GAAG,SAAS,CAAC;QACrB,CAAC;QACD,IACE,MAAM,KAAK,OAAO,CAAC,MAAM;YACzB,CAAC,OAAO;YACR,CAAC,KAAK;YACN,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,cAAc,IAAI,GAAG,CAAC;YACvD,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,iBAAS,CAAC,SAAS,CAAC,WAAW,CAAC;YAEpE,MAAM,QAAQ,EAAE,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,QAAQ,EAAE,CAAC;IACnB,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,WAAW,CAC/B,MAAgB,EAChB,EAAU,EACV,SAAiB,EACjB,MAAiC,EACjC,MAAmB;IAEnB,MAAM,IAAA,gBAAK,EAAC,oBAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,mBAAI,CAAC,IAAI,CAAC,oBAAY,EAAE,EAAE,CAAC,CAAC;IACzC,IAAI,IAAI,GAAG,CAAC,EACV,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EACxB,OAAO,GAAG,KAAK,EACf,QAAQ,GAAG,EAAE,CAAC;IAChB,MAAM,IAAI,GAAG,IAAA,wBAAU,EAAC,QAAQ,CAAC,CAAC;IAClC,MAAM,IAAI,GACR,SAAS,KAAK,KAAK,IAAI,SAAS,KAAK,KAAK;QACxC,CAAC,CAAC,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAC3C,CAAC,CAAC,SAAS,CAAC;IAChB,MAAM,SAAS,GAAG,IAAI,uBAAS,CAAC;QAC9B,SAAS,CAAC,KAAa,EAAE,SAAS,EAAE,QAAQ;YAC1C,IAAI,CAAC;gBACH,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;gBACrB,IAAI,IAAI,GAAG,MAAM,CAAC,YAAY;oBAC5B,MAAM,IAAI,2BAAc,CAAC,sBAAsB,EAAE,+BAA+B,EAAE,GAAG,CAAC,CAAC;gBACzF,IAAI,MAAM,CAAC,MAAM,GAAG,GAAG;oBACrB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBAC3E,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;oBACrC,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;oBAC/B,OAAO,GAAG,IAAI,CAAC;gBACjB,CAAC;gBACD,IAAI,IAAI,EAAE,CAAC;oBACT,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;wBAAE,MAAM,QAAQ,EAAE,CAAC;oBACxC,MAAM,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;oBAChE,IAAI,iDAAiD,CAAC,IAAI,CAAC,OAAO,CAAC;wBAAE,MAAM,QAAQ,EAAE,CAAC;oBACtF,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;gBAChC,CAAC;gBACD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACnB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACxB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,QAAQ,CAAC,KAAK,YAAY,2BAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;KACF,CAAC,CAAC;IACH,IAAI,CAAC;QACH,MAAM,IAAA,mBAAQ,EAAC,MAAM,EAAE,SAAS,EAAE,IAAA,2BAAiB,EAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE;YACvF,MAAM;SACP,CAAC,CAAC;QACH,IAAI,CAAC,IAAI;YAAE,MAAM,QAAQ,EAAE,CAAC;QAC5B,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAC/B,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC;gBACH,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,QAAQ,EAAE,CAAC;YACnB,CAAC;QACH,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,MAAM,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QAC5F,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,mBAAmB,EAAE,iBAAS,CAAC,SAAS,CAAC,EAAE,CAAC;IAC/F,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAA,aAAE,EAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAChC,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,cAAc,CAAC,MAAY,EAAE,KAAa;IAC9D,MAAM,IAAA,gBAAK,EAAC,oBAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5D,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,MAAM,SAAS,GAAG,MAAM,IAAA,kBAAO,EAAC,oBAAY,CAAC,CAAC;IAC9C,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QACpC,IAAI,OAAO,IAAI,KAAK;YAAE,MAAM;QAC5B,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,SAAS;QACrE,MAAM,IAAI,GAAG,mBAAI,CAAC,IAAI,CAAC,oBAAY,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM,IAAA,eAAI,EAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,MAAM,EAAE,CAAC;YACtC,MAAM,IAAA,aAAE,EAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAChC,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,72 @@
1
+ {
2
+ "schemaVersion": "haneinfo.plugin/v1",
3
+ "id": "attachments",
4
+ "displayName": "Attachments",
5
+ "cardinality": "singleton",
6
+ "distribution": "official",
7
+ "engine": {
8
+ "haneinfoPluginApi": "^1.15.0",
9
+ "node": ">=22.0.0",
10
+ "frameworks": {
11
+ "@nestjs/common": "^11.0.5",
12
+ "@nestjs/core": "^11.0.5",
13
+ "@prisma/client": "^6.2.0"
14
+ }
15
+ },
16
+ "provides": ["haneinfo.file-storage"],
17
+ "requires": [
18
+ "haneinfo.prisma",
19
+ "haneinfo.principal-context",
20
+ "haneinfo.state-machine-runtime",
21
+ "haneinfo.background-jobs"
22
+ ],
23
+ "optionalRequires": [
24
+ "haneinfo.rbac-policy",
25
+ "haneinfo.identity-store",
26
+ "haneinfo.attachment-scanner"
27
+ ],
28
+ "configSchema": {
29
+ "configRef": "attachments"
30
+ },
31
+ "environment": [
32
+ {
33
+ "key": "ATTACHMENTS_S3_ACCESS_KEY_ID",
34
+ "required": false,
35
+ "secret": true
36
+ },
37
+ {
38
+ "key": "ATTACHMENTS_S3_SECRET_ACCESS_KEY",
39
+ "required": false,
40
+ "secret": true
41
+ },
42
+ {
43
+ "key": "ATTACHMENTS_S3_SESSION_TOKEN",
44
+ "required": false,
45
+ "secret": true
46
+ }
47
+ ],
48
+ "facets": {
49
+ "backend": {
50
+ "modulePath": "./dist/module.js",
51
+ "exportName": "AttachmentsModule",
52
+ "controllerRoutes": ["state-machine-instances/:id/attachments"],
53
+ "providerTokens": ["haneinfo.file-storage"],
54
+ "mcpTools": [
55
+ {
56
+ "name": "list_instance_attachments",
57
+ "requiredScopes": ["{namespace}:instance:read"]
58
+ }
59
+ ],
60
+ "workers": ["haneinfo.attachments.cleanup-enqueuer"],
61
+ "agentGuide": "./agent-guide.md"
62
+ },
63
+ "prisma": {
64
+ "owns": ["Attachment", "AttachmentGarbage"],
65
+ "schemaFragment": "prisma/attachments.prisma",
66
+ "schemaDigest": "sha256:c279fae0fd8881a58d3dc35f48fad41900ffcf86bdc66adbb830cb3fa04444ec"
67
+ },
68
+ "permissions": {
69
+ "definitions": []
70
+ }
71
+ }
72
+ }
package/package.json CHANGED
@@ -1,6 +1,83 @@
1
1
  {
2
2
  "name": "@haneinfo/attachments",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Private streaming instance attachments, storage drivers, scanning and retention.",
6
+ "files": [
7
+ "dist",
8
+ "prisma",
9
+ "agent-guide.md",
10
+ "haneinfo.plugin.json",
11
+ "plugin.js",
12
+ "plugin.d.ts",
13
+ "README.md"
14
+ ],
15
+ "type": "commonjs",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ },
23
+ "./plugin": {
24
+ "types": "./dist/plugin.d.ts",
25
+ "default": "./dist/plugin.js"
26
+ },
27
+ "./haneinfo.plugin.json": "./haneinfo.plugin.json",
28
+ "./agent-guide.md": "./agent-guide.md",
29
+ "./package.json": "./package.json",
30
+ "./prisma/attachments.prisma": "./prisma/attachments.prisma"
31
+ },
32
+ "dependencies": {
33
+ "busboy": "1.6.0",
34
+ "@haneinfo/plugin-api": "1.15.0"
35
+ },
36
+ "peerDependencies": {
37
+ "@haneinfo/common": "^0.3.4 || ^0.4.0",
38
+ "@haneinfo/plugin-host-nest": "^2.0.0",
39
+ "@nestjs/common": "^11.0.0",
40
+ "@nestjs/core": "^11.0.0",
41
+ "@prisma/client": "^6.19.0",
42
+ "zod": "^4.4.0",
43
+ "@haneinfo/plugin-api": "^1.15.0",
44
+ "@haneinfo/state-machine": "^0.11.0 || ^0.12.0",
45
+ "@aws-sdk/client-s3": "^3.1154.0",
46
+ "@aws-sdk/lib-storage": "^3.1154.0"
47
+ },
48
+ "devDependencies": {
49
+ "@nestjs/common": "^11.0.0",
50
+ "@nestjs/core": "^11.0.0",
51
+ "@prisma/client": "^6.19.0",
52
+ "@types/node": "^24.13.0",
53
+ "typescript": "^5.9.3",
54
+ "vitest": "^4.1.11",
55
+ "zod": "^4.4.0",
56
+ "@aws-sdk/client-s3": "3.1154.0",
57
+ "@aws-sdk/lib-storage": "3.1154.0",
58
+ "@types/busboy": "1.5.5",
59
+ "@haneinfo/plugin-host-nest": "2.1.0",
60
+ "@haneinfo/plugin-testkit": "2.3.0",
61
+ "@haneinfo/common": "0.4.0",
62
+ "@haneinfo/state-machine": "0.12.0"
63
+ },
64
+ "license": "UNLICENSED",
65
+ "publishConfig": {
66
+ "registry": "https://registry.npmjs.org/",
67
+ "access": "public"
68
+ },
69
+ "peerDependenciesMeta": {
70
+ "@aws-sdk/client-s3": {
71
+ "optional": true
72
+ },
73
+ "@aws-sdk/lib-storage": {
74
+ "optional": true
75
+ }
76
+ },
77
+ "scripts": {
78
+ "build": "tsc -p tsconfig.build.json",
79
+ "dev": "tsc -p tsconfig.build.json --watch",
80
+ "test": "vitest run",
81
+ "typecheck": "tsc --noEmit"
82
+ }
6
83
  }
package/plugin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ // See plugin.js for why this file exists (classic/node10 moduleResolution compatibility shim).
2
+ export * from './dist/plugin';
package/plugin.js ADDED
@@ -0,0 +1,4 @@
1
+ // Compatibility shim for consumers whose TypeScript config uses "classic"/"node10"
2
+ // moduleResolution, which resolves `@haneinfo/attachments/plugin` by looking for a real file
3
+ // at the package root -- it does not consult package.json's "exports" map at all.
4
+ module.exports = require('./dist/plugin');
@@ -0,0 +1,32 @@
1
+ /// Immutable private file metadata. Add attachments Attachment[] to StateMachineInstance in the App.
2
+ model Attachment {
3
+ id String @id @default(uuid())
4
+ instanceId String @map("instance_id")
5
+ instance StateMachineInstance @relation(fields: [instanceId], references: [id], onDelete: Cascade)
6
+ fileName String @map("file_name")
7
+ declaredContentType String @map("declared_content_type")
8
+ detectedContentType String @map("detected_content_type")
9
+ size BigInt
10
+ sha256 String
11
+ storageKey String @unique @map("storage_key")
12
+ /// READY, PENDING_SCAN or REJECTED.
13
+ status String
14
+ uploadedBy String @map("uploaded_by")
15
+ createdAt DateTime @default(now()) @map("created_at")
16
+ deletedAt DateTime? @map("deleted_at")
17
+ deletedBy String? @map("deleted_by")
18
+
19
+ @@index([instanceId, deletedAt])
20
+ @@index([deletedAt])
21
+ @@index([status, createdAt])
22
+ @@map("attachments")
23
+ }
24
+
25
+ /// Write-ahead recovery marker. Created before storage writes and removed with metadata commit.
26
+ model AttachmentGarbage {
27
+ storageKey String @id @map("storage_key")
28
+ notBefore DateTime @map("not_before")
29
+
30
+ @@index([notBefore])
31
+ @@map("attachment_garbage")
32
+ }
@@ -0,0 +1,13 @@
1
+ CREATE TABLE attachments (
2
+ id TEXT PRIMARY KEY, instance_id TEXT NOT NULL REFERENCES state_machine_instances(id) ON DELETE CASCADE,
3
+ file_name TEXT NOT NULL, declared_content_type TEXT NOT NULL, detected_content_type TEXT NOT NULL,
4
+ size BIGINT NOT NULL CHECK (size >= 0), sha256 TEXT NOT NULL, storage_key TEXT NOT NULL UNIQUE,
5
+ status TEXT NOT NULL CHECK (status IN ('READY','PENDING_SCAN','REJECTED')),
6
+ uploaded_by TEXT NOT NULL, created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
7
+ deleted_at TIMESTAMP(3), deleted_by TEXT
8
+ );
9
+ CREATE INDEX attachments_instance_id_deleted_at_idx ON attachments(instance_id, deleted_at);
10
+ CREATE INDEX attachments_deleted_at_idx ON attachments(deleted_at);
11
+ CREATE INDEX attachments_status_created_at_idx ON attachments(status, created_at);
12
+ CREATE TABLE attachment_garbage (storage_key TEXT PRIMARY KEY, not_before TIMESTAMP(3) NOT NULL);
13
+ CREATE INDEX attachment_garbage_not_before_idx ON attachment_garbage(not_before);