@contractspec/lib.files 1.57.0 → 1.58.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/dist/contracts/index.d.ts +1080 -1086
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +575 -854
- package/dist/docs/files.docblock.d.ts +2 -1
- package/dist/docs/files.docblock.d.ts.map +1 -0
- package/dist/docs/files.docblock.js +17 -22
- package/dist/docs/index.d.ts +2 -1
- package/dist/docs/index.d.ts.map +1 -0
- package/dist/docs/index.js +66 -1
- package/dist/entities/index.d.ts +134 -139
- package/dist/entities/index.d.ts.map +1 -1
- package/dist/entities/index.js +228 -257
- package/dist/events.d.ts +357 -363
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +217 -400
- package/dist/files.capability.d.ts +2 -7
- package/dist/files.capability.d.ts.map +1 -1
- package/dist/files.capability.js +29 -25
- package/dist/files.feature.d.ts +1 -6
- package/dist/files.feature.d.ts.map +1 -1
- package/dist/files.feature.js +50 -131
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1411 -8
- package/dist/node/contracts/index.js +576 -0
- package/dist/node/docs/files.docblock.js +65 -0
- package/dist/node/docs/index.js +65 -0
- package/dist/node/entities/index.js +235 -0
- package/dist/node/events.js +219 -0
- package/dist/node/files.capability.js +28 -0
- package/dist/node/files.feature.js +51 -0
- package/dist/node/index.js +1410 -0
- package/dist/node/storage/index.js +268 -0
- package/dist/storage/index.d.ts +163 -166
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +266 -266
- package/package.json +104 -30
- package/dist/contracts/index.js.map +0 -1
- package/dist/docs/files.docblock.js.map +0 -1
- package/dist/entities/index.js.map +0 -1
- package/dist/events.js.map +0 -1
- package/dist/files.capability.js.map +0 -1
- package/dist/files.feature.js.map +0 -1
- package/dist/storage/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,1411 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
// @bun
|
|
2
|
+
// src/contracts/index.ts
|
|
3
|
+
import { ScalarTypeEnum, defineSchemaModel } from "@contractspec/lib.schema";
|
|
4
|
+
import { defineCommand, defineQuery } from "@contractspec/lib.contracts";
|
|
5
|
+
var OWNERS = ["platform.files"];
|
|
6
|
+
var FileModel = defineSchemaModel({
|
|
7
|
+
name: "File",
|
|
8
|
+
description: "Represents an uploaded file",
|
|
9
|
+
fields: {
|
|
10
|
+
id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
11
|
+
name: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
12
|
+
mimeType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
13
|
+
size: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
14
|
+
storageProvider: {
|
|
15
|
+
type: ScalarTypeEnum.String_unsecure(),
|
|
16
|
+
isOptional: false
|
|
17
|
+
},
|
|
18
|
+
storagePath: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
19
|
+
checksum: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
20
|
+
status: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
21
|
+
isPublic: { type: ScalarTypeEnum.Boolean(), isOptional: false },
|
|
22
|
+
ownerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
23
|
+
orgId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
24
|
+
metadata: { type: ScalarTypeEnum.JSON(), isOptional: true },
|
|
25
|
+
width: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true },
|
|
26
|
+
height: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true },
|
|
27
|
+
createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
|
|
28
|
+
updatedAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
var FileVersionModel = defineSchemaModel({
|
|
32
|
+
name: "FileVersion",
|
|
33
|
+
description: "Represents a file version",
|
|
34
|
+
fields: {
|
|
35
|
+
id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
36
|
+
fileId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
37
|
+
version: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
38
|
+
size: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
39
|
+
storagePath: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
40
|
+
checksum: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
41
|
+
comment: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
42
|
+
createdBy: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
43
|
+
createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
var AttachmentModel = defineSchemaModel({
|
|
47
|
+
name: "Attachment",
|
|
48
|
+
description: "Represents an attachment",
|
|
49
|
+
fields: {
|
|
50
|
+
id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
51
|
+
fileId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
52
|
+
entityType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
53
|
+
entityId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
54
|
+
attachmentType: {
|
|
55
|
+
type: ScalarTypeEnum.String_unsecure(),
|
|
56
|
+
isOptional: true
|
|
57
|
+
},
|
|
58
|
+
name: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
59
|
+
description: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
60
|
+
order: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
61
|
+
metadata: { type: ScalarTypeEnum.JSON(), isOptional: true },
|
|
62
|
+
createdBy: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
63
|
+
createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
|
|
64
|
+
file: { type: FileModel, isOptional: true }
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
var PresignedUrlModel = defineSchemaModel({
|
|
68
|
+
name: "PresignedUrl",
|
|
69
|
+
description: "A presigned URL for file operations",
|
|
70
|
+
fields: {
|
|
71
|
+
url: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
72
|
+
fields: { type: ScalarTypeEnum.JSON(), isOptional: true },
|
|
73
|
+
expiresAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
|
|
74
|
+
fileId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
75
|
+
sessionId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true }
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
var UploadFileInput = defineSchemaModel({
|
|
79
|
+
name: "UploadFileInput",
|
|
80
|
+
description: "Input for uploading a file",
|
|
81
|
+
fields: {
|
|
82
|
+
name: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
83
|
+
mimeType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
84
|
+
size: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
85
|
+
content: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
86
|
+
orgId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
87
|
+
isPublic: { type: ScalarTypeEnum.Boolean(), isOptional: true },
|
|
88
|
+
metadata: { type: ScalarTypeEnum.JSON(), isOptional: true },
|
|
89
|
+
tags: { type: ScalarTypeEnum.JSON(), isOptional: true }
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
var UpdateFileInput = defineSchemaModel({
|
|
93
|
+
name: "UpdateFileInput",
|
|
94
|
+
description: "Input for updating a file",
|
|
95
|
+
fields: {
|
|
96
|
+
fileId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
97
|
+
name: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
98
|
+
isPublic: { type: ScalarTypeEnum.Boolean(), isOptional: true },
|
|
99
|
+
metadata: { type: ScalarTypeEnum.JSON(), isOptional: true },
|
|
100
|
+
tags: { type: ScalarTypeEnum.JSON(), isOptional: true }
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
var DeleteFileInput = defineSchemaModel({
|
|
104
|
+
name: "DeleteFileInput",
|
|
105
|
+
description: "Input for deleting a file",
|
|
106
|
+
fields: {
|
|
107
|
+
fileId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
var GetFileInput = defineSchemaModel({
|
|
111
|
+
name: "GetFileInput",
|
|
112
|
+
description: "Input for getting a file",
|
|
113
|
+
fields: {
|
|
114
|
+
fileId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
var ListFilesInput = defineSchemaModel({
|
|
118
|
+
name: "ListFilesInput",
|
|
119
|
+
description: "Input for listing files",
|
|
120
|
+
fields: {
|
|
121
|
+
orgId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
122
|
+
ownerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
123
|
+
mimeType: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
124
|
+
status: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
125
|
+
tags: { type: ScalarTypeEnum.JSON(), isOptional: true },
|
|
126
|
+
limit: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true },
|
|
127
|
+
offset: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true }
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
var ListFilesOutput = defineSchemaModel({
|
|
131
|
+
name: "ListFilesOutput",
|
|
132
|
+
description: "Output for listing files",
|
|
133
|
+
fields: {
|
|
134
|
+
files: { type: FileModel, isArray: true, isOptional: false },
|
|
135
|
+
total: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false }
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
var GetDownloadUrlInput = defineSchemaModel({
|
|
139
|
+
name: "GetDownloadUrlInput",
|
|
140
|
+
description: "Input for getting a download URL",
|
|
141
|
+
fields: {
|
|
142
|
+
fileId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
143
|
+
expiresInSeconds: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true }
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
var CreateVersionInput = defineSchemaModel({
|
|
147
|
+
name: "CreateVersionInput",
|
|
148
|
+
description: "Input for creating a file version",
|
|
149
|
+
fields: {
|
|
150
|
+
fileId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
151
|
+
content: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
152
|
+
comment: { type: ScalarTypeEnum.String_unsecure(), isOptional: true }
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
var GetVersionsInput = defineSchemaModel({
|
|
156
|
+
name: "GetVersionsInput",
|
|
157
|
+
description: "Input for getting file versions",
|
|
158
|
+
fields: {
|
|
159
|
+
fileId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
160
|
+
limit: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true },
|
|
161
|
+
offset: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true }
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
var GetVersionsOutput = defineSchemaModel({
|
|
165
|
+
name: "GetVersionsOutput",
|
|
166
|
+
description: "Output for getting file versions",
|
|
167
|
+
fields: {
|
|
168
|
+
versions: { type: FileVersionModel, isArray: true, isOptional: false },
|
|
169
|
+
total: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false }
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
var AttachFileInput = defineSchemaModel({
|
|
173
|
+
name: "AttachFileInput",
|
|
174
|
+
description: "Input for attaching a file to an entity",
|
|
175
|
+
fields: {
|
|
176
|
+
fileId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
177
|
+
entityType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
178
|
+
entityId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
179
|
+
attachmentType: {
|
|
180
|
+
type: ScalarTypeEnum.String_unsecure(),
|
|
181
|
+
isOptional: true
|
|
182
|
+
},
|
|
183
|
+
name: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
184
|
+
description: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
185
|
+
order: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true },
|
|
186
|
+
metadata: { type: ScalarTypeEnum.JSON(), isOptional: true }
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
var DetachFileInput = defineSchemaModel({
|
|
190
|
+
name: "DetachFileInput",
|
|
191
|
+
description: "Input for detaching a file",
|
|
192
|
+
fields: {
|
|
193
|
+
attachmentId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
var ListAttachmentsInput = defineSchemaModel({
|
|
197
|
+
name: "ListAttachmentsInput",
|
|
198
|
+
description: "Input for listing attachments",
|
|
199
|
+
fields: {
|
|
200
|
+
entityType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
201
|
+
entityId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
202
|
+
attachmentType: {
|
|
203
|
+
type: ScalarTypeEnum.String_unsecure(),
|
|
204
|
+
isOptional: true
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
var ListAttachmentsOutput = defineSchemaModel({
|
|
209
|
+
name: "ListAttachmentsOutput",
|
|
210
|
+
description: "Output for listing attachments",
|
|
211
|
+
fields: {
|
|
212
|
+
attachments: { type: AttachmentModel, isArray: true, isOptional: false },
|
|
213
|
+
total: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false }
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
var CreatePresignedUrlInput = defineSchemaModel({
|
|
217
|
+
name: "CreatePresignedUrlInput",
|
|
218
|
+
description: "Input for creating a presigned upload URL",
|
|
219
|
+
fields: {
|
|
220
|
+
fileName: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
221
|
+
mimeType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
222
|
+
size: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
223
|
+
orgId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
224
|
+
expiresInSeconds: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true }
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
var SuccessOutput = defineSchemaModel({
|
|
228
|
+
name: "SuccessOutput",
|
|
229
|
+
description: "Generic success output",
|
|
230
|
+
fields: {
|
|
231
|
+
success: { type: ScalarTypeEnum.Boolean(), isOptional: false }
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
var UploadFileContract = defineCommand({
|
|
235
|
+
meta: {
|
|
236
|
+
key: "file.upload",
|
|
237
|
+
version: "1.0.0",
|
|
238
|
+
stability: "stable",
|
|
239
|
+
owners: [...OWNERS],
|
|
240
|
+
tags: ["files", "upload"],
|
|
241
|
+
description: "Upload a new file.",
|
|
242
|
+
goal: "Store a file and create a file record.",
|
|
243
|
+
context: "Called when uploading files directly."
|
|
244
|
+
},
|
|
245
|
+
io: {
|
|
246
|
+
input: UploadFileInput,
|
|
247
|
+
output: FileModel,
|
|
248
|
+
errors: {
|
|
249
|
+
FILE_TOO_LARGE: {
|
|
250
|
+
description: "File exceeds size limit",
|
|
251
|
+
http: 413,
|
|
252
|
+
gqlCode: "FILE_TOO_LARGE",
|
|
253
|
+
when: "File size exceeds configured limit"
|
|
254
|
+
},
|
|
255
|
+
INVALID_MIME_TYPE: {
|
|
256
|
+
description: "MIME type not allowed",
|
|
257
|
+
http: 415,
|
|
258
|
+
gqlCode: "INVALID_MIME_TYPE",
|
|
259
|
+
when: "File type is not in allowed list"
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
policy: {
|
|
264
|
+
auth: "user"
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
var UpdateFileContract = defineCommand({
|
|
268
|
+
meta: {
|
|
269
|
+
key: "file.update",
|
|
270
|
+
version: "1.0.0",
|
|
271
|
+
stability: "stable",
|
|
272
|
+
owners: [...OWNERS],
|
|
273
|
+
tags: ["files", "update"],
|
|
274
|
+
description: "Update file metadata.",
|
|
275
|
+
goal: "Modify file properties without replacing content.",
|
|
276
|
+
context: "Called when renaming or updating file metadata."
|
|
277
|
+
},
|
|
278
|
+
io: {
|
|
279
|
+
input: UpdateFileInput,
|
|
280
|
+
output: FileModel,
|
|
281
|
+
errors: {
|
|
282
|
+
FILE_NOT_FOUND: {
|
|
283
|
+
description: "File does not exist",
|
|
284
|
+
http: 404,
|
|
285
|
+
gqlCode: "FILE_NOT_FOUND",
|
|
286
|
+
when: "File ID is invalid"
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
policy: {
|
|
291
|
+
auth: "user"
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
var DeleteFileContract = defineCommand({
|
|
295
|
+
meta: {
|
|
296
|
+
key: "file.delete",
|
|
297
|
+
version: "1.0.0",
|
|
298
|
+
stability: "stable",
|
|
299
|
+
owners: [...OWNERS],
|
|
300
|
+
tags: ["files", "delete"],
|
|
301
|
+
description: "Delete a file.",
|
|
302
|
+
goal: "Remove a file and all its versions and attachments.",
|
|
303
|
+
context: "Called when removing a file permanently."
|
|
304
|
+
},
|
|
305
|
+
io: {
|
|
306
|
+
input: DeleteFileInput,
|
|
307
|
+
output: SuccessOutput,
|
|
308
|
+
errors: {
|
|
309
|
+
FILE_NOT_FOUND: {
|
|
310
|
+
description: "File does not exist",
|
|
311
|
+
http: 404,
|
|
312
|
+
gqlCode: "FILE_NOT_FOUND",
|
|
313
|
+
when: "File ID is invalid"
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
policy: {
|
|
318
|
+
auth: "user"
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
var GetFileContract = defineQuery({
|
|
322
|
+
meta: {
|
|
323
|
+
key: "file.get",
|
|
324
|
+
version: "1.0.0",
|
|
325
|
+
stability: "stable",
|
|
326
|
+
owners: [...OWNERS],
|
|
327
|
+
tags: ["files", "get"],
|
|
328
|
+
description: "Get a file by ID.",
|
|
329
|
+
goal: "Retrieve file metadata.",
|
|
330
|
+
context: "Called to inspect file details."
|
|
331
|
+
},
|
|
332
|
+
io: {
|
|
333
|
+
input: GetFileInput,
|
|
334
|
+
output: FileModel,
|
|
335
|
+
errors: {
|
|
336
|
+
FILE_NOT_FOUND: {
|
|
337
|
+
description: "File does not exist",
|
|
338
|
+
http: 404,
|
|
339
|
+
gqlCode: "FILE_NOT_FOUND",
|
|
340
|
+
when: "File ID is invalid"
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
policy: {
|
|
345
|
+
auth: "user"
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
var ListFilesContract = defineQuery({
|
|
349
|
+
meta: {
|
|
350
|
+
key: "file.list",
|
|
351
|
+
version: "1.0.0",
|
|
352
|
+
stability: "stable",
|
|
353
|
+
owners: [...OWNERS],
|
|
354
|
+
tags: ["files", "list"],
|
|
355
|
+
description: "List files with filtering.",
|
|
356
|
+
goal: "Browse uploaded files.",
|
|
357
|
+
context: "Called to browse file library."
|
|
358
|
+
},
|
|
359
|
+
io: {
|
|
360
|
+
input: ListFilesInput,
|
|
361
|
+
output: ListFilesOutput
|
|
362
|
+
},
|
|
363
|
+
policy: {
|
|
364
|
+
auth: "user"
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
var GetDownloadUrlContract = defineQuery({
|
|
368
|
+
meta: {
|
|
369
|
+
key: "file.downloadUrl",
|
|
370
|
+
version: "1.0.0",
|
|
371
|
+
stability: "stable",
|
|
372
|
+
owners: [...OWNERS],
|
|
373
|
+
tags: ["files", "download"],
|
|
374
|
+
description: "Get a presigned download URL.",
|
|
375
|
+
goal: "Generate a temporary URL for downloading.",
|
|
376
|
+
context: "Called when user wants to download a file."
|
|
377
|
+
},
|
|
378
|
+
io: {
|
|
379
|
+
input: GetDownloadUrlInput,
|
|
380
|
+
output: PresignedUrlModel,
|
|
381
|
+
errors: {
|
|
382
|
+
FILE_NOT_FOUND: {
|
|
383
|
+
description: "File does not exist",
|
|
384
|
+
http: 404,
|
|
385
|
+
gqlCode: "FILE_NOT_FOUND",
|
|
386
|
+
when: "File ID is invalid"
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
policy: {
|
|
391
|
+
auth: "user"
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
var CreateVersionContract = defineCommand({
|
|
395
|
+
meta: {
|
|
396
|
+
key: "file.version.create",
|
|
397
|
+
version: "1.0.0",
|
|
398
|
+
stability: "stable",
|
|
399
|
+
owners: [...OWNERS],
|
|
400
|
+
tags: ["files", "version", "create"],
|
|
401
|
+
description: "Create a new version of a file.",
|
|
402
|
+
goal: "Upload a new version while preserving history.",
|
|
403
|
+
context: "Called when updating a document."
|
|
404
|
+
},
|
|
405
|
+
io: {
|
|
406
|
+
input: CreateVersionInput,
|
|
407
|
+
output: FileVersionModel,
|
|
408
|
+
errors: {
|
|
409
|
+
FILE_NOT_FOUND: {
|
|
410
|
+
description: "File does not exist",
|
|
411
|
+
http: 404,
|
|
412
|
+
gqlCode: "FILE_NOT_FOUND",
|
|
413
|
+
when: "File ID is invalid"
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
policy: {
|
|
418
|
+
auth: "user"
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
var GetVersionsContract = defineQuery({
|
|
422
|
+
meta: {
|
|
423
|
+
key: "file.version.list",
|
|
424
|
+
version: "1.0.0",
|
|
425
|
+
stability: "stable",
|
|
426
|
+
owners: [...OWNERS],
|
|
427
|
+
tags: ["files", "version", "list"],
|
|
428
|
+
description: "Get file version history.",
|
|
429
|
+
goal: "View all versions of a file.",
|
|
430
|
+
context: "Called to browse file history."
|
|
431
|
+
},
|
|
432
|
+
io: {
|
|
433
|
+
input: GetVersionsInput,
|
|
434
|
+
output: GetVersionsOutput,
|
|
435
|
+
errors: {
|
|
436
|
+
FILE_NOT_FOUND: {
|
|
437
|
+
description: "File does not exist",
|
|
438
|
+
http: 404,
|
|
439
|
+
gqlCode: "FILE_NOT_FOUND",
|
|
440
|
+
when: "File ID is invalid"
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
policy: {
|
|
445
|
+
auth: "user"
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
var AttachFileContract = defineCommand({
|
|
449
|
+
meta: {
|
|
450
|
+
key: "attachment.attach",
|
|
451
|
+
version: "1.0.0",
|
|
452
|
+
stability: "stable",
|
|
453
|
+
owners: [...OWNERS],
|
|
454
|
+
tags: ["files", "attachment", "attach"],
|
|
455
|
+
description: "Attach a file to an entity.",
|
|
456
|
+
goal: "Link a file to a business entity.",
|
|
457
|
+
context: "Called when associating files with entities."
|
|
458
|
+
},
|
|
459
|
+
io: {
|
|
460
|
+
input: AttachFileInput,
|
|
461
|
+
output: AttachmentModel,
|
|
462
|
+
errors: {
|
|
463
|
+
FILE_NOT_FOUND: {
|
|
464
|
+
description: "File does not exist",
|
|
465
|
+
http: 404,
|
|
466
|
+
gqlCode: "FILE_NOT_FOUND",
|
|
467
|
+
when: "File ID is invalid"
|
|
468
|
+
},
|
|
469
|
+
ATTACHMENT_EXISTS: {
|
|
470
|
+
description: "Attachment already exists",
|
|
471
|
+
http: 409,
|
|
472
|
+
gqlCode: "ATTACHMENT_EXISTS",
|
|
473
|
+
when: "File is already attached to this entity"
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
policy: {
|
|
478
|
+
auth: "user"
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
var DetachFileContract = defineCommand({
|
|
482
|
+
meta: {
|
|
483
|
+
key: "attachment.detach",
|
|
484
|
+
version: "1.0.0",
|
|
485
|
+
stability: "stable",
|
|
486
|
+
owners: [...OWNERS],
|
|
487
|
+
tags: ["files", "attachment", "detach"],
|
|
488
|
+
description: "Detach a file from an entity.",
|
|
489
|
+
goal: "Remove a file association.",
|
|
490
|
+
context: "Called when removing file from entity."
|
|
491
|
+
},
|
|
492
|
+
io: {
|
|
493
|
+
input: DetachFileInput,
|
|
494
|
+
output: SuccessOutput,
|
|
495
|
+
errors: {
|
|
496
|
+
ATTACHMENT_NOT_FOUND: {
|
|
497
|
+
description: "Attachment does not exist",
|
|
498
|
+
http: 404,
|
|
499
|
+
gqlCode: "ATTACHMENT_NOT_FOUND",
|
|
500
|
+
when: "Attachment ID is invalid"
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
policy: {
|
|
505
|
+
auth: "user"
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
var ListAttachmentsContract = defineQuery({
|
|
509
|
+
meta: {
|
|
510
|
+
key: "attachment.list",
|
|
511
|
+
version: "1.0.0",
|
|
512
|
+
stability: "stable",
|
|
513
|
+
owners: [...OWNERS],
|
|
514
|
+
tags: ["files", "attachment", "list"],
|
|
515
|
+
description: "List attachments for an entity.",
|
|
516
|
+
goal: "Get all files attached to an entity.",
|
|
517
|
+
context: "Called to display attached files."
|
|
518
|
+
},
|
|
519
|
+
io: {
|
|
520
|
+
input: ListAttachmentsInput,
|
|
521
|
+
output: ListAttachmentsOutput
|
|
522
|
+
},
|
|
523
|
+
policy: {
|
|
524
|
+
auth: "user"
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
var CreatePresignedUrlContract = defineCommand({
|
|
528
|
+
meta: {
|
|
529
|
+
key: "file.presignedUrl.create",
|
|
530
|
+
version: "1.0.0",
|
|
531
|
+
stability: "stable",
|
|
532
|
+
owners: [...OWNERS],
|
|
533
|
+
tags: ["files", "presigned", "upload"],
|
|
534
|
+
description: "Create a presigned URL for direct upload.",
|
|
535
|
+
goal: "Enable direct-to-storage uploads.",
|
|
536
|
+
context: "Called for large file uploads."
|
|
537
|
+
},
|
|
538
|
+
io: {
|
|
539
|
+
input: CreatePresignedUrlInput,
|
|
540
|
+
output: PresignedUrlModel,
|
|
541
|
+
errors: {
|
|
542
|
+
FILE_TOO_LARGE: {
|
|
543
|
+
description: "File exceeds size limit",
|
|
544
|
+
http: 413,
|
|
545
|
+
gqlCode: "FILE_TOO_LARGE",
|
|
546
|
+
when: "Requested file size exceeds limit"
|
|
547
|
+
},
|
|
548
|
+
INVALID_MIME_TYPE: {
|
|
549
|
+
description: "MIME type not allowed",
|
|
550
|
+
http: 415,
|
|
551
|
+
gqlCode: "INVALID_MIME_TYPE",
|
|
552
|
+
when: "File type is not in allowed list"
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
policy: {
|
|
557
|
+
auth: "user"
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// src/docs/files.docblock.ts
|
|
562
|
+
import { registerDocBlocks } from "@contractspec/lib.contracts/docs";
|
|
563
|
+
var filesDocBlocks = [
|
|
564
|
+
{
|
|
565
|
+
id: "docs.files.attachments",
|
|
566
|
+
title: "Files, Versions & Attachments",
|
|
567
|
+
summary: "Spec-first file management with storage adapters, versioning, presigned URLs, and polymorphic attachments for any entity.",
|
|
568
|
+
kind: "reference",
|
|
569
|
+
visibility: "public",
|
|
570
|
+
route: "/docs/files/attachments",
|
|
571
|
+
tags: ["files", "attachments", "storage", "versions"],
|
|
572
|
+
body: `## Capabilities
|
|
573
|
+
|
|
574
|
+
- **Entities**: File, FileVersion, Attachment, UploadSession with retention, checksum, ACLs.
|
|
575
|
+
- **Contracts**: upload/update/delete/list/get files; presigned upload/download; version create/list; attach/detach/list attachments.
|
|
576
|
+
- **Storage**: pluggable adapters (Local, S3 placeholder + interface), in-memory for tests.
|
|
577
|
+
- **Events**: file.uploaded/deleted, attachment.added/removed (see events export).
|
|
578
|
+
|
|
579
|
+
## Usage
|
|
580
|
+
|
|
581
|
+
1) Compose schema
|
|
582
|
+
- Include \`filesSchemaContribution\` in your schema composition.
|
|
583
|
+
|
|
584
|
+
2) Register contracts/events
|
|
585
|
+
- Import contracts and events from \`@contractspec/lib.files\` in your spec registry.
|
|
586
|
+
|
|
587
|
+
3) Wire storage
|
|
588
|
+
- Provide a \`StorageAdapter\` implementation (local/in-memory or S3 via custom impl).
|
|
589
|
+
- Use \`createStorageAdapter\` with config to instantiate.
|
|
590
|
+
|
|
591
|
+
4) Attach to domain entities
|
|
592
|
+
- Use \`attachment.attach\` with \`entityType/entityId\` to link files to deals, orders, runs, etc.
|
|
593
|
+
|
|
594
|
+
## Example
|
|
595
|
+
|
|
596
|
+
${"```"}ts
|
|
597
|
+
import {
|
|
598
|
+
UploadFileContract,
|
|
599
|
+
AttachFileContract,
|
|
600
|
+
InMemoryStorageAdapter,
|
|
601
|
+
} from '@contractspec/lib.files';
|
|
602
|
+
|
|
603
|
+
// storage
|
|
604
|
+
const storage = new InMemoryStorageAdapter();
|
|
605
|
+
|
|
606
|
+
// upload
|
|
607
|
+
const file = await storage.upload({
|
|
608
|
+
path: 'org-1/reports/r1.pdf',
|
|
609
|
+
content: Buffer.from(pdfBytes),
|
|
610
|
+
mimeType: 'application/pdf',
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// attach
|
|
614
|
+
await AttachFileContract; // register in spec to enable attach flows
|
|
615
|
+
${"```"},
|
|
616
|
+
|
|
617
|
+
## Guardrails
|
|
618
|
+
|
|
619
|
+
- Enforce size/MIME limits in your handlers; avoid storing PII in paths.
|
|
620
|
+
- Keep \`orgId\` scoped for multi-tenant isolation; prefer presigned URLs for public delivery.
|
|
621
|
+
- Persist checksums for integrity; emit audit + events for access/retention changes.
|
|
622
|
+
`
|
|
623
|
+
}
|
|
624
|
+
];
|
|
625
|
+
registerDocBlocks(filesDocBlocks);
|
|
626
|
+
// src/entities/index.ts
|
|
627
|
+
import {
|
|
628
|
+
defineEntity,
|
|
629
|
+
defineEntityEnum,
|
|
630
|
+
field,
|
|
631
|
+
index
|
|
632
|
+
} from "@contractspec/lib.schema";
|
|
633
|
+
var StorageProviderEnum = defineEntityEnum({
|
|
634
|
+
name: "StorageProvider",
|
|
635
|
+
values: ["LOCAL", "S3", "GCS", "AZURE", "CLOUDFLARE"],
|
|
636
|
+
schema: "lssm_files",
|
|
637
|
+
description: "Storage backend provider."
|
|
638
|
+
});
|
|
639
|
+
var FileStatusEnum = defineEntityEnum({
|
|
640
|
+
name: "FileStatus",
|
|
641
|
+
values: [
|
|
642
|
+
"PENDING",
|
|
643
|
+
"UPLOADED",
|
|
644
|
+
"PROCESSING",
|
|
645
|
+
"READY",
|
|
646
|
+
"ERROR",
|
|
647
|
+
"DELETED"
|
|
648
|
+
],
|
|
649
|
+
schema: "lssm_files",
|
|
650
|
+
description: "File processing status."
|
|
651
|
+
});
|
|
652
|
+
var FileEntity = defineEntity({
|
|
653
|
+
name: "File",
|
|
654
|
+
description: "An uploaded file.",
|
|
655
|
+
schema: "lssm_files",
|
|
656
|
+
map: "file",
|
|
657
|
+
fields: {
|
|
658
|
+
id: field.id({ description: "Unique file identifier" }),
|
|
659
|
+
name: field.string({ description: "Original file name" }),
|
|
660
|
+
mimeType: field.string({ description: "MIME type" }),
|
|
661
|
+
size: field.int({ description: "File size in bytes" }),
|
|
662
|
+
storageProvider: field.enum("StorageProvider", {
|
|
663
|
+
default: "LOCAL",
|
|
664
|
+
description: "Storage backend"
|
|
665
|
+
}),
|
|
666
|
+
storagePath: field.string({ description: "Path in storage backend" }),
|
|
667
|
+
storageKey: field.string({
|
|
668
|
+
isOptional: true,
|
|
669
|
+
description: "Storage key/bucket"
|
|
670
|
+
}),
|
|
671
|
+
checksum: field.string({
|
|
672
|
+
isOptional: true,
|
|
673
|
+
description: "SHA-256 checksum"
|
|
674
|
+
}),
|
|
675
|
+
etag: field.string({ isOptional: true, description: "Storage ETag" }),
|
|
676
|
+
status: field.enum("FileStatus", {
|
|
677
|
+
default: "PENDING",
|
|
678
|
+
description: "File status"
|
|
679
|
+
}),
|
|
680
|
+
isPublic: field.boolean({
|
|
681
|
+
default: false,
|
|
682
|
+
description: "Whether file is publicly accessible"
|
|
683
|
+
}),
|
|
684
|
+
expiresAt: field.dateTime({
|
|
685
|
+
isOptional: true,
|
|
686
|
+
description: "Auto-delete timestamp"
|
|
687
|
+
}),
|
|
688
|
+
ownerId: field.string({ description: "User who uploaded" }),
|
|
689
|
+
orgId: field.string({
|
|
690
|
+
isOptional: true,
|
|
691
|
+
description: "Organization scope"
|
|
692
|
+
}),
|
|
693
|
+
metadata: field.json({
|
|
694
|
+
isOptional: true,
|
|
695
|
+
description: "Additional metadata"
|
|
696
|
+
}),
|
|
697
|
+
tags: field.json({
|
|
698
|
+
isOptional: true,
|
|
699
|
+
description: "Tags for categorization"
|
|
700
|
+
}),
|
|
701
|
+
width: field.int({
|
|
702
|
+
isOptional: true,
|
|
703
|
+
description: "Image width in pixels"
|
|
704
|
+
}),
|
|
705
|
+
height: field.int({
|
|
706
|
+
isOptional: true,
|
|
707
|
+
description: "Image height in pixels"
|
|
708
|
+
}),
|
|
709
|
+
createdAt: field.createdAt(),
|
|
710
|
+
updatedAt: field.updatedAt(),
|
|
711
|
+
versions: field.hasMany("FileVersion"),
|
|
712
|
+
attachments: field.hasMany("Attachment")
|
|
713
|
+
},
|
|
714
|
+
indexes: [
|
|
715
|
+
index.on(["ownerId"]),
|
|
716
|
+
index.on(["orgId"]),
|
|
717
|
+
index.on(["status"]),
|
|
718
|
+
index.on(["mimeType"]),
|
|
719
|
+
index.on(["storageProvider", "storagePath"])
|
|
720
|
+
],
|
|
721
|
+
enums: [StorageProviderEnum, FileStatusEnum]
|
|
722
|
+
});
|
|
723
|
+
var FileVersionEntity = defineEntity({
|
|
724
|
+
name: "FileVersion",
|
|
725
|
+
description: "A version of a file.",
|
|
726
|
+
schema: "lssm_files",
|
|
727
|
+
map: "file_version",
|
|
728
|
+
fields: {
|
|
729
|
+
id: field.id({ description: "Unique version identifier" }),
|
|
730
|
+
fileId: field.foreignKey({ description: "Parent file" }),
|
|
731
|
+
version: field.int({ description: "Version number" }),
|
|
732
|
+
size: field.int({ description: "Version size in bytes" }),
|
|
733
|
+
storagePath: field.string({ description: "Path in storage backend" }),
|
|
734
|
+
checksum: field.string({
|
|
735
|
+
isOptional: true,
|
|
736
|
+
description: "SHA-256 checksum"
|
|
737
|
+
}),
|
|
738
|
+
comment: field.string({ isOptional: true, description: "Version comment" }),
|
|
739
|
+
changes: field.json({
|
|
740
|
+
isOptional: true,
|
|
741
|
+
description: "Change description"
|
|
742
|
+
}),
|
|
743
|
+
createdBy: field.string({ description: "User who created version" }),
|
|
744
|
+
createdAt: field.createdAt(),
|
|
745
|
+
file: field.belongsTo("File", ["fileId"], ["id"], { onDelete: "Cascade" })
|
|
746
|
+
},
|
|
747
|
+
indexes: [
|
|
748
|
+
index.on(["fileId", "version"]),
|
|
749
|
+
index.unique(["fileId", "version"], { name: "file_version_unique" })
|
|
750
|
+
]
|
|
751
|
+
});
|
|
752
|
+
var AttachmentEntity = defineEntity({
|
|
753
|
+
name: "Attachment",
|
|
754
|
+
description: "Links a file to an entity.",
|
|
755
|
+
schema: "lssm_files",
|
|
756
|
+
map: "attachment",
|
|
757
|
+
fields: {
|
|
758
|
+
id: field.id({ description: "Unique attachment identifier" }),
|
|
759
|
+
fileId: field.foreignKey({ description: "Attached file" }),
|
|
760
|
+
entityType: field.string({
|
|
761
|
+
description: "Target entity type (deal, listing, etc.)"
|
|
762
|
+
}),
|
|
763
|
+
entityId: field.string({ description: "Target entity ID" }),
|
|
764
|
+
attachmentType: field.string({
|
|
765
|
+
isOptional: true,
|
|
766
|
+
description: "Type of attachment (document, image, avatar, etc.)"
|
|
767
|
+
}),
|
|
768
|
+
name: field.string({
|
|
769
|
+
isOptional: true,
|
|
770
|
+
description: "Display name (overrides file name)"
|
|
771
|
+
}),
|
|
772
|
+
description: field.string({
|
|
773
|
+
isOptional: true,
|
|
774
|
+
description: "Attachment description"
|
|
775
|
+
}),
|
|
776
|
+
order: field.int({ default: 0, description: "Display order" }),
|
|
777
|
+
metadata: field.json({
|
|
778
|
+
isOptional: true,
|
|
779
|
+
description: "Attachment-specific metadata"
|
|
780
|
+
}),
|
|
781
|
+
createdBy: field.string({ description: "User who created attachment" }),
|
|
782
|
+
createdAt: field.createdAt(),
|
|
783
|
+
updatedAt: field.updatedAt(),
|
|
784
|
+
file: field.belongsTo("File", ["fileId"], ["id"], { onDelete: "Cascade" })
|
|
785
|
+
},
|
|
786
|
+
indexes: [
|
|
787
|
+
index.on(["entityType", "entityId"]),
|
|
788
|
+
index.on(["fileId"]),
|
|
789
|
+
index.on(["entityType", "entityId", "attachmentType"]),
|
|
790
|
+
index.unique(["fileId", "entityType", "entityId"], {
|
|
791
|
+
name: "attachment_unique"
|
|
792
|
+
})
|
|
793
|
+
]
|
|
794
|
+
});
|
|
795
|
+
var UploadSessionEntity = defineEntity({
|
|
796
|
+
name: "UploadSession",
|
|
797
|
+
description: "Tracks a multipart upload session.",
|
|
798
|
+
schema: "lssm_files",
|
|
799
|
+
map: "upload_session",
|
|
800
|
+
fields: {
|
|
801
|
+
id: field.id({ description: "Unique session identifier" }),
|
|
802
|
+
fileName: field.string({ description: "Target file name" }),
|
|
803
|
+
mimeType: field.string({ description: "Expected MIME type" }),
|
|
804
|
+
totalSize: field.int({ description: "Total file size" }),
|
|
805
|
+
uploadId: field.string({
|
|
806
|
+
isOptional: true,
|
|
807
|
+
description: "Storage upload ID"
|
|
808
|
+
}),
|
|
809
|
+
uploadedBytes: field.int({
|
|
810
|
+
default: 0,
|
|
811
|
+
description: "Bytes uploaded so far"
|
|
812
|
+
}),
|
|
813
|
+
uploadedParts: field.json({
|
|
814
|
+
isOptional: true,
|
|
815
|
+
description: "Completed part info"
|
|
816
|
+
}),
|
|
817
|
+
status: field.string({
|
|
818
|
+
default: '"pending"',
|
|
819
|
+
description: "Session status"
|
|
820
|
+
}),
|
|
821
|
+
error: field.string({
|
|
822
|
+
isOptional: true,
|
|
823
|
+
description: "Error message if failed"
|
|
824
|
+
}),
|
|
825
|
+
fileId: field.string({
|
|
826
|
+
isOptional: true,
|
|
827
|
+
description: "Resulting file ID"
|
|
828
|
+
}),
|
|
829
|
+
ownerId: field.string({ description: "User who initiated upload" }),
|
|
830
|
+
orgId: field.string({
|
|
831
|
+
isOptional: true,
|
|
832
|
+
description: "Organization scope"
|
|
833
|
+
}),
|
|
834
|
+
expiresAt: field.dateTime({ description: "Session expiry time" }),
|
|
835
|
+
createdAt: field.createdAt(),
|
|
836
|
+
updatedAt: field.updatedAt()
|
|
837
|
+
},
|
|
838
|
+
indexes: [index.on(["status", "expiresAt"]), index.on(["ownerId"])]
|
|
839
|
+
});
|
|
840
|
+
var fileEntities = [
|
|
841
|
+
FileEntity,
|
|
842
|
+
FileVersionEntity,
|
|
843
|
+
AttachmentEntity,
|
|
844
|
+
UploadSessionEntity
|
|
845
|
+
];
|
|
846
|
+
var filesSchemaContribution = {
|
|
847
|
+
moduleId: "@contractspec/lib.files",
|
|
848
|
+
entities: fileEntities,
|
|
849
|
+
enums: [StorageProviderEnum, FileStatusEnum]
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
// src/events.ts
|
|
853
|
+
import { ScalarTypeEnum as ScalarTypeEnum2, defineSchemaModel as defineSchemaModel2 } from "@contractspec/lib.schema";
|
|
854
|
+
import { defineEvent } from "@contractspec/lib.contracts";
|
|
855
|
+
var FileUploadedPayload = defineSchemaModel2({
|
|
856
|
+
name: "FileUploadedEventPayload",
|
|
857
|
+
description: "Payload when a file is uploaded",
|
|
858
|
+
fields: {
|
|
859
|
+
fileId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
860
|
+
name: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
861
|
+
mimeType: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
862
|
+
size: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: false },
|
|
863
|
+
storageProvider: {
|
|
864
|
+
type: ScalarTypeEnum2.String_unsecure(),
|
|
865
|
+
isOptional: false
|
|
866
|
+
},
|
|
867
|
+
ownerId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
868
|
+
orgId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
869
|
+
uploadedAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
var FileUpdatedPayload = defineSchemaModel2({
|
|
873
|
+
name: "FileUpdatedEventPayload",
|
|
874
|
+
description: "Payload when a file is updated",
|
|
875
|
+
fields: {
|
|
876
|
+
fileId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
877
|
+
name: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
878
|
+
changes: { type: ScalarTypeEnum2.JSON(), isOptional: false },
|
|
879
|
+
updatedBy: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
880
|
+
updatedAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
var FileDeletedPayload = defineSchemaModel2({
|
|
884
|
+
name: "FileDeletedEventPayload",
|
|
885
|
+
description: "Payload when a file is deleted",
|
|
886
|
+
fields: {
|
|
887
|
+
fileId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
888
|
+
name: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
889
|
+
storageProvider: {
|
|
890
|
+
type: ScalarTypeEnum2.String_unsecure(),
|
|
891
|
+
isOptional: false
|
|
892
|
+
},
|
|
893
|
+
storagePath: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
894
|
+
deletedBy: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
895
|
+
deletedAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
var FileVersionCreatedPayload = defineSchemaModel2({
|
|
899
|
+
name: "FileVersionCreatedEventPayload",
|
|
900
|
+
description: "Payload when a file version is created",
|
|
901
|
+
fields: {
|
|
902
|
+
fileId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
903
|
+
versionId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
904
|
+
version: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
905
|
+
size: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: false },
|
|
906
|
+
createdBy: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
907
|
+
comment: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
908
|
+
createdAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
var AttachmentAttachedPayload = defineSchemaModel2({
|
|
912
|
+
name: "AttachmentAttachedEventPayload",
|
|
913
|
+
description: "Payload when a file is attached to an entity",
|
|
914
|
+
fields: {
|
|
915
|
+
attachmentId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
916
|
+
fileId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
917
|
+
entityType: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
918
|
+
entityId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
919
|
+
attachmentType: {
|
|
920
|
+
type: ScalarTypeEnum2.String_unsecure(),
|
|
921
|
+
isOptional: true
|
|
922
|
+
},
|
|
923
|
+
attachedBy: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
924
|
+
attachedAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
var AttachmentDetachedPayload = defineSchemaModel2({
|
|
928
|
+
name: "AttachmentDetachedEventPayload",
|
|
929
|
+
description: "Payload when a file is detached from an entity",
|
|
930
|
+
fields: {
|
|
931
|
+
attachmentId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
932
|
+
fileId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
933
|
+
entityType: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
934
|
+
entityId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
935
|
+
detachedBy: { type: ScalarTypeEnum2.String_unsecure(), isOptional: true },
|
|
936
|
+
detachedAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
var UploadSessionStartedPayload = defineSchemaModel2({
|
|
940
|
+
name: "UploadSessionStartedEventPayload",
|
|
941
|
+
description: "Payload when an upload session starts",
|
|
942
|
+
fields: {
|
|
943
|
+
sessionId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
944
|
+
fileName: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
945
|
+
mimeType: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
946
|
+
totalSize: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: false },
|
|
947
|
+
ownerId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
948
|
+
startedAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
var UploadSessionCompletedPayload = defineSchemaModel2({
|
|
952
|
+
name: "UploadSessionCompletedEventPayload",
|
|
953
|
+
description: "Payload when an upload session completes",
|
|
954
|
+
fields: {
|
|
955
|
+
sessionId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
956
|
+
fileId: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
957
|
+
fileName: { type: ScalarTypeEnum2.String_unsecure(), isOptional: false },
|
|
958
|
+
size: { type: ScalarTypeEnum2.Int_unsecure(), isOptional: false },
|
|
959
|
+
completedAt: { type: ScalarTypeEnum2.DateTime(), isOptional: false }
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
var FileUploadedEvent = defineEvent({
|
|
963
|
+
meta: {
|
|
964
|
+
key: "file.uploaded",
|
|
965
|
+
version: "1.0.0",
|
|
966
|
+
description: "A file has been uploaded.",
|
|
967
|
+
stability: "stable",
|
|
968
|
+
owners: ["@platform.files"],
|
|
969
|
+
tags: ["files", "upload"]
|
|
970
|
+
},
|
|
971
|
+
payload: FileUploadedPayload
|
|
972
|
+
});
|
|
973
|
+
var FileUpdatedEvent = defineEvent({
|
|
974
|
+
meta: {
|
|
975
|
+
key: "file.updated",
|
|
976
|
+
version: "1.0.0",
|
|
977
|
+
description: "A file has been updated.",
|
|
978
|
+
stability: "stable",
|
|
979
|
+
owners: ["@platform.files"],
|
|
980
|
+
tags: ["files", "update"]
|
|
981
|
+
},
|
|
982
|
+
payload: FileUpdatedPayload
|
|
983
|
+
});
|
|
984
|
+
var FileDeletedEvent = defineEvent({
|
|
985
|
+
meta: {
|
|
986
|
+
key: "file.deleted",
|
|
987
|
+
version: "1.0.0",
|
|
988
|
+
description: "A file has been deleted.",
|
|
989
|
+
stability: "stable",
|
|
990
|
+
owners: ["@platform.files"],
|
|
991
|
+
tags: ["files", "delete"]
|
|
992
|
+
},
|
|
993
|
+
payload: FileDeletedPayload
|
|
994
|
+
});
|
|
995
|
+
var FileVersionCreatedEvent = defineEvent({
|
|
996
|
+
meta: {
|
|
997
|
+
key: "file.version_created",
|
|
998
|
+
version: "1.0.0",
|
|
999
|
+
description: "A new file version has been created.",
|
|
1000
|
+
stability: "stable",
|
|
1001
|
+
owners: ["@platform.files"],
|
|
1002
|
+
tags: ["files", "version", "create"]
|
|
1003
|
+
},
|
|
1004
|
+
payload: FileVersionCreatedPayload
|
|
1005
|
+
});
|
|
1006
|
+
var AttachmentAttachedEvent = defineEvent({
|
|
1007
|
+
meta: {
|
|
1008
|
+
key: "attachment.attached",
|
|
1009
|
+
version: "1.0.0",
|
|
1010
|
+
description: "A file has been attached to an entity.",
|
|
1011
|
+
stability: "stable",
|
|
1012
|
+
owners: ["@platform.files"],
|
|
1013
|
+
tags: ["files", "attachment", "attach"]
|
|
1014
|
+
},
|
|
1015
|
+
payload: AttachmentAttachedPayload
|
|
1016
|
+
});
|
|
1017
|
+
var AttachmentDetachedEvent = defineEvent({
|
|
1018
|
+
meta: {
|
|
1019
|
+
key: "attachment.detached",
|
|
1020
|
+
version: "1.0.0",
|
|
1021
|
+
description: "A file has been detached from an entity.",
|
|
1022
|
+
stability: "stable",
|
|
1023
|
+
owners: ["@platform.files"],
|
|
1024
|
+
tags: ["files", "attachment", "detach"]
|
|
1025
|
+
},
|
|
1026
|
+
payload: AttachmentDetachedPayload
|
|
1027
|
+
});
|
|
1028
|
+
var UploadSessionStartedEvent = defineEvent({
|
|
1029
|
+
meta: {
|
|
1030
|
+
key: "upload.session_started",
|
|
1031
|
+
version: "1.0.0",
|
|
1032
|
+
description: "An upload session has started.",
|
|
1033
|
+
stability: "stable",
|
|
1034
|
+
owners: ["@platform.files"],
|
|
1035
|
+
tags: ["files", "upload", "session", "start"]
|
|
1036
|
+
},
|
|
1037
|
+
payload: UploadSessionStartedPayload
|
|
1038
|
+
});
|
|
1039
|
+
var UploadSessionCompletedEvent = defineEvent({
|
|
1040
|
+
meta: {
|
|
1041
|
+
key: "upload.session_completed",
|
|
1042
|
+
version: "1.0.0",
|
|
1043
|
+
description: "An upload session has completed.",
|
|
1044
|
+
stability: "stable",
|
|
1045
|
+
owners: ["@platform.files"],
|
|
1046
|
+
tags: ["files", "upload", "session", "complete"]
|
|
1047
|
+
},
|
|
1048
|
+
payload: UploadSessionCompletedPayload
|
|
1049
|
+
});
|
|
1050
|
+
var FileEvents = {
|
|
1051
|
+
FileUploadedEvent,
|
|
1052
|
+
FileUpdatedEvent,
|
|
1053
|
+
FileDeletedEvent,
|
|
1054
|
+
FileVersionCreatedEvent,
|
|
1055
|
+
AttachmentAttachedEvent,
|
|
1056
|
+
AttachmentDetachedEvent,
|
|
1057
|
+
UploadSessionStartedEvent,
|
|
1058
|
+
UploadSessionCompletedEvent
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
// src/files.feature.ts
|
|
1062
|
+
import { defineFeature } from "@contractspec/lib.contracts";
|
|
1063
|
+
var FilesFeature = defineFeature({
|
|
1064
|
+
meta: {
|
|
1065
|
+
key: "files",
|
|
1066
|
+
version: "1.0.0",
|
|
1067
|
+
title: "File Management",
|
|
1068
|
+
description: "File storage, attachments, and media processing with presigned URLs",
|
|
1069
|
+
domain: "platform",
|
|
1070
|
+
owners: ["@platform.files"],
|
|
1071
|
+
tags: ["files", "upload", "attachments", "storage"],
|
|
1072
|
+
stability: "stable"
|
|
1073
|
+
},
|
|
1074
|
+
operations: [
|
|
1075
|
+
{ key: "file.upload", version: "1.0.0" },
|
|
1076
|
+
{ key: "file.update", version: "1.0.0" },
|
|
1077
|
+
{ key: "file.delete", version: "1.0.0" },
|
|
1078
|
+
{ key: "file.get", version: "1.0.0" },
|
|
1079
|
+
{ key: "file.list", version: "1.0.0" },
|
|
1080
|
+
{ key: "file.downloadUrl", version: "1.0.0" },
|
|
1081
|
+
{ key: "file.presignedUrl.create", version: "1.0.0" },
|
|
1082
|
+
{ key: "file.version.create", version: "1.0.0" },
|
|
1083
|
+
{ key: "file.version.list", version: "1.0.0" },
|
|
1084
|
+
{ key: "attachment.attach", version: "1.0.0" },
|
|
1085
|
+
{ key: "attachment.detach", version: "1.0.0" },
|
|
1086
|
+
{ key: "attachment.list", version: "1.0.0" }
|
|
1087
|
+
],
|
|
1088
|
+
events: [
|
|
1089
|
+
{ key: "file.uploaded", version: "1.0.0" },
|
|
1090
|
+
{ key: "file.updated", version: "1.0.0" },
|
|
1091
|
+
{ key: "file.deleted", version: "1.0.0" },
|
|
1092
|
+
{ key: "file.version_created", version: "1.0.0" },
|
|
1093
|
+
{ key: "attachment.attached", version: "1.0.0" },
|
|
1094
|
+
{ key: "attachment.detached", version: "1.0.0" },
|
|
1095
|
+
{ key: "upload.session_started", version: "1.0.0" },
|
|
1096
|
+
{ key: "upload.session_completed", version: "1.0.0" }
|
|
1097
|
+
],
|
|
1098
|
+
presentations: [],
|
|
1099
|
+
opToPresentation: [],
|
|
1100
|
+
presentationsTargets: [],
|
|
1101
|
+
capabilities: {
|
|
1102
|
+
provides: [
|
|
1103
|
+
{ key: "files", version: "1.0.0" },
|
|
1104
|
+
{ key: "attachments", version: "1.0.0" }
|
|
1105
|
+
],
|
|
1106
|
+
requires: [{ key: "identity", version: "1.0.0" }]
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
// src/storage/index.ts
|
|
1111
|
+
import * as fs from "fs/promises";
|
|
1112
|
+
import * as path from "path";
|
|
1113
|
+
import * as crypto from "crypto";
|
|
1114
|
+
|
|
1115
|
+
class LocalStorageAdapter {
|
|
1116
|
+
provider = "LOCAL";
|
|
1117
|
+
basePath;
|
|
1118
|
+
baseUrl;
|
|
1119
|
+
constructor(options) {
|
|
1120
|
+
this.basePath = options.basePath;
|
|
1121
|
+
this.baseUrl = options.baseUrl;
|
|
1122
|
+
}
|
|
1123
|
+
async upload(options) {
|
|
1124
|
+
const fullPath = path.join(this.basePath, options.path);
|
|
1125
|
+
const dir = path.dirname(fullPath);
|
|
1126
|
+
await fs.mkdir(dir, { recursive: true });
|
|
1127
|
+
const content = typeof options.content === "string" ? Buffer.from(options.content, "base64") : options.content;
|
|
1128
|
+
await fs.writeFile(fullPath, content);
|
|
1129
|
+
const checksum = crypto.createHash("sha256").update(content).digest("hex");
|
|
1130
|
+
return {
|
|
1131
|
+
path: options.path,
|
|
1132
|
+
size: content.length,
|
|
1133
|
+
mimeType: options.mimeType,
|
|
1134
|
+
checksum,
|
|
1135
|
+
metadata: options.metadata
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
async download(filePath) {
|
|
1139
|
+
const fullPath = path.join(this.basePath, filePath);
|
|
1140
|
+
return fs.readFile(fullPath);
|
|
1141
|
+
}
|
|
1142
|
+
async delete(filePath) {
|
|
1143
|
+
const fullPath = path.join(this.basePath, filePath);
|
|
1144
|
+
await fs.unlink(fullPath);
|
|
1145
|
+
}
|
|
1146
|
+
async exists(filePath) {
|
|
1147
|
+
const fullPath = path.join(this.basePath, filePath);
|
|
1148
|
+
try {
|
|
1149
|
+
await fs.access(fullPath);
|
|
1150
|
+
return true;
|
|
1151
|
+
} catch {
|
|
1152
|
+
return false;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
async getMetadata(filePath) {
|
|
1156
|
+
const fullPath = path.join(this.basePath, filePath);
|
|
1157
|
+
try {
|
|
1158
|
+
const stat2 = await fs.stat(fullPath);
|
|
1159
|
+
return {
|
|
1160
|
+
path: filePath,
|
|
1161
|
+
size: stat2.size,
|
|
1162
|
+
mimeType: "application/octet-stream"
|
|
1163
|
+
};
|
|
1164
|
+
} catch {
|
|
1165
|
+
return null;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
async list(options) {
|
|
1169
|
+
const dir = options?.prefix ? path.join(this.basePath, options.prefix) : this.basePath;
|
|
1170
|
+
try {
|
|
1171
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1172
|
+
const files = [];
|
|
1173
|
+
for (const entry of entries) {
|
|
1174
|
+
if (entry.isFile()) {
|
|
1175
|
+
const filePath = options?.prefix ? path.join(options.prefix, entry.name) : entry.name;
|
|
1176
|
+
const stat2 = await fs.stat(path.join(dir, entry.name));
|
|
1177
|
+
files.push({
|
|
1178
|
+
path: filePath,
|
|
1179
|
+
size: stat2.size,
|
|
1180
|
+
mimeType: "application/octet-stream"
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
const limit = options?.limit || files.length;
|
|
1185
|
+
return {
|
|
1186
|
+
files: files.slice(0, limit),
|
|
1187
|
+
hasMore: files.length > limit
|
|
1188
|
+
};
|
|
1189
|
+
} catch {
|
|
1190
|
+
return { files: [], hasMore: false };
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
async createPresignedUpload(options) {
|
|
1194
|
+
const expiresIn = options.expiresIn || 3600;
|
|
1195
|
+
const expiresAt = new Date(Date.now() + expiresIn * 1000);
|
|
1196
|
+
return {
|
|
1197
|
+
url: this.baseUrl ? `${this.baseUrl}/upload?path=${encodeURIComponent(options.path)}` : `/upload?path=${encodeURIComponent(options.path)}`,
|
|
1198
|
+
fields: {
|
|
1199
|
+
path: options.path,
|
|
1200
|
+
mimeType: options.mimeType
|
|
1201
|
+
},
|
|
1202
|
+
expiresAt
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
async createPresignedDownload(options) {
|
|
1206
|
+
const expiresIn = options.expiresIn || 3600;
|
|
1207
|
+
const expiresAt = new Date(Date.now() + expiresIn * 1000);
|
|
1208
|
+
return {
|
|
1209
|
+
url: this.baseUrl ? `${this.baseUrl}/download/${options.path}` : `/download/${options.path}`,
|
|
1210
|
+
expiresAt
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
getPublicUrl(filePath) {
|
|
1214
|
+
if (!this.baseUrl)
|
|
1215
|
+
return null;
|
|
1216
|
+
return `${this.baseUrl}/${filePath}`;
|
|
1217
|
+
}
|
|
1218
|
+
async copy(sourcePath, destinationPath) {
|
|
1219
|
+
const sourceFullPath = path.join(this.basePath, sourcePath);
|
|
1220
|
+
const destFullPath = path.join(this.basePath, destinationPath);
|
|
1221
|
+
const destDir = path.dirname(destFullPath);
|
|
1222
|
+
await fs.mkdir(destDir, { recursive: true });
|
|
1223
|
+
await fs.copyFile(sourceFullPath, destFullPath);
|
|
1224
|
+
const stat2 = await fs.stat(destFullPath);
|
|
1225
|
+
return {
|
|
1226
|
+
path: destinationPath,
|
|
1227
|
+
size: stat2.size,
|
|
1228
|
+
mimeType: "application/octet-stream"
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
class S3StorageAdapter {
|
|
1234
|
+
provider = "S3";
|
|
1235
|
+
config;
|
|
1236
|
+
constructor(options) {
|
|
1237
|
+
this.config = options;
|
|
1238
|
+
}
|
|
1239
|
+
async upload(_options) {
|
|
1240
|
+
throw new Error("S3 adapter requires @aws-sdk/client-s3. Install it and implement the upload method.");
|
|
1241
|
+
}
|
|
1242
|
+
async download(_filePath) {
|
|
1243
|
+
throw new Error("S3 adapter requires @aws-sdk/client-s3. Install it and implement the download method.");
|
|
1244
|
+
}
|
|
1245
|
+
async delete(_filePath) {
|
|
1246
|
+
throw new Error("S3 adapter requires @aws-sdk/client-s3. Install it and implement the delete method.");
|
|
1247
|
+
}
|
|
1248
|
+
async exists(_filePath) {
|
|
1249
|
+
throw new Error("S3 adapter requires @aws-sdk/client-s3. Install it and implement the exists method.");
|
|
1250
|
+
}
|
|
1251
|
+
async getMetadata(_filePath) {
|
|
1252
|
+
throw new Error("S3 adapter requires @aws-sdk/client-s3. Install it and implement the getMetadata method.");
|
|
1253
|
+
}
|
|
1254
|
+
async list(_options) {
|
|
1255
|
+
throw new Error("S3 adapter requires @aws-sdk/client-s3. Install it and implement the list method.");
|
|
1256
|
+
}
|
|
1257
|
+
async createPresignedUpload(_options) {
|
|
1258
|
+
throw new Error("S3 adapter requires @aws-sdk/client-s3. Install it and implement the createPresignedUpload method.");
|
|
1259
|
+
}
|
|
1260
|
+
async createPresignedDownload(_options) {
|
|
1261
|
+
throw new Error("S3 adapter requires @aws-sdk/client-s3. Install it and implement the createPresignedDownload method.");
|
|
1262
|
+
}
|
|
1263
|
+
getPublicUrl(filePath) {
|
|
1264
|
+
const { bucket, region, endpoint } = this.config;
|
|
1265
|
+
if (endpoint) {
|
|
1266
|
+
return `${endpoint}/${bucket}/${filePath}`;
|
|
1267
|
+
}
|
|
1268
|
+
return `https://${bucket}.s3.${region}.amazonaws.com/${filePath}`;
|
|
1269
|
+
}
|
|
1270
|
+
async copy(_sourcePath, _destinationPath) {
|
|
1271
|
+
throw new Error("S3 adapter requires @aws-sdk/client-s3. Install it and implement the copy method.");
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
class InMemoryStorageAdapter {
|
|
1276
|
+
provider = "LOCAL";
|
|
1277
|
+
files = new Map;
|
|
1278
|
+
async upload(options) {
|
|
1279
|
+
const content = typeof options.content === "string" ? Buffer.from(options.content, "base64") : options.content;
|
|
1280
|
+
const checksum = crypto.createHash("sha256").update(content).digest("hex");
|
|
1281
|
+
const metadata = {
|
|
1282
|
+
path: options.path,
|
|
1283
|
+
size: content.length,
|
|
1284
|
+
mimeType: options.mimeType,
|
|
1285
|
+
checksum,
|
|
1286
|
+
metadata: options.metadata
|
|
1287
|
+
};
|
|
1288
|
+
this.files.set(options.path, { content, metadata });
|
|
1289
|
+
return metadata;
|
|
1290
|
+
}
|
|
1291
|
+
async download(filePath) {
|
|
1292
|
+
const file = this.files.get(filePath);
|
|
1293
|
+
if (!file) {
|
|
1294
|
+
throw new Error(`File not found: ${filePath}`);
|
|
1295
|
+
}
|
|
1296
|
+
return file.content;
|
|
1297
|
+
}
|
|
1298
|
+
async delete(filePath) {
|
|
1299
|
+
this.files.delete(filePath);
|
|
1300
|
+
}
|
|
1301
|
+
async exists(filePath) {
|
|
1302
|
+
return this.files.has(filePath);
|
|
1303
|
+
}
|
|
1304
|
+
async getMetadata(filePath) {
|
|
1305
|
+
const file = this.files.get(filePath);
|
|
1306
|
+
return file?.metadata || null;
|
|
1307
|
+
}
|
|
1308
|
+
async list(options) {
|
|
1309
|
+
const prefix = options?.prefix || "";
|
|
1310
|
+
const files = [];
|
|
1311
|
+
for (const [path2, file] of this.files) {
|
|
1312
|
+
if (path2.startsWith(prefix)) {
|
|
1313
|
+
files.push(file.metadata);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
const limit = options?.limit || files.length;
|
|
1317
|
+
return {
|
|
1318
|
+
files: files.slice(0, limit),
|
|
1319
|
+
hasMore: files.length > limit
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
async createPresignedUpload(options) {
|
|
1323
|
+
const expiresAt = new Date(Date.now() + (options.expiresIn || 3600) * 1000);
|
|
1324
|
+
return {
|
|
1325
|
+
url: `/upload?path=${encodeURIComponent(options.path)}`,
|
|
1326
|
+
fields: { path: options.path },
|
|
1327
|
+
expiresAt
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
async createPresignedDownload(options) {
|
|
1331
|
+
const expiresAt = new Date(Date.now() + (options.expiresIn || 3600) * 1000);
|
|
1332
|
+
return {
|
|
1333
|
+
url: `/download/${options.path}`,
|
|
1334
|
+
expiresAt
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
getPublicUrl(filePath) {
|
|
1338
|
+
return `/files/${filePath}`;
|
|
1339
|
+
}
|
|
1340
|
+
async copy(sourcePath, destinationPath) {
|
|
1341
|
+
const source = this.files.get(sourcePath);
|
|
1342
|
+
if (!source) {
|
|
1343
|
+
throw new Error(`Source file not found: ${sourcePath}`);
|
|
1344
|
+
}
|
|
1345
|
+
const metadata = {
|
|
1346
|
+
...source.metadata,
|
|
1347
|
+
path: destinationPath
|
|
1348
|
+
};
|
|
1349
|
+
this.files.set(destinationPath, { content: source.content, metadata });
|
|
1350
|
+
return metadata;
|
|
1351
|
+
}
|
|
1352
|
+
clear() {
|
|
1353
|
+
this.files.clear();
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
function createStorageAdapter(config) {
|
|
1357
|
+
switch (config.provider) {
|
|
1358
|
+
case "LOCAL":
|
|
1359
|
+
if (!config.local) {
|
|
1360
|
+
throw new Error("Local storage configuration required");
|
|
1361
|
+
}
|
|
1362
|
+
return new LocalStorageAdapter(config.local);
|
|
1363
|
+
case "S3":
|
|
1364
|
+
if (!config.s3) {
|
|
1365
|
+
throw new Error("S3 storage configuration required");
|
|
1366
|
+
}
|
|
1367
|
+
return new S3StorageAdapter(config.s3);
|
|
1368
|
+
default:
|
|
1369
|
+
throw new Error(`Unsupported storage provider: ${config.provider}`);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
export {
|
|
1373
|
+
filesSchemaContribution,
|
|
1374
|
+
fileEntities,
|
|
1375
|
+
createStorageAdapter,
|
|
1376
|
+
UploadSessionStartedEvent,
|
|
1377
|
+
UploadSessionEntity,
|
|
1378
|
+
UploadSessionCompletedEvent,
|
|
1379
|
+
UploadFileContract,
|
|
1380
|
+
UpdateFileContract,
|
|
1381
|
+
StorageProviderEnum,
|
|
1382
|
+
S3StorageAdapter,
|
|
1383
|
+
PresignedUrlModel,
|
|
1384
|
+
LocalStorageAdapter,
|
|
1385
|
+
ListFilesContract,
|
|
1386
|
+
ListAttachmentsContract,
|
|
1387
|
+
InMemoryStorageAdapter,
|
|
1388
|
+
GetVersionsContract,
|
|
1389
|
+
GetFileContract,
|
|
1390
|
+
GetDownloadUrlContract,
|
|
1391
|
+
FilesFeature,
|
|
1392
|
+
FileVersionModel,
|
|
1393
|
+
FileVersionEntity,
|
|
1394
|
+
FileVersionCreatedEvent,
|
|
1395
|
+
FileUploadedEvent,
|
|
1396
|
+
FileUpdatedEvent,
|
|
1397
|
+
FileStatusEnum,
|
|
1398
|
+
FileModel,
|
|
1399
|
+
FileEvents,
|
|
1400
|
+
FileEntity,
|
|
1401
|
+
FileDeletedEvent,
|
|
1402
|
+
DetachFileContract,
|
|
1403
|
+
DeleteFileContract,
|
|
1404
|
+
CreateVersionContract,
|
|
1405
|
+
CreatePresignedUrlContract,
|
|
1406
|
+
AttachmentModel,
|
|
1407
|
+
AttachmentEntity,
|
|
1408
|
+
AttachmentDetachedEvent,
|
|
1409
|
+
AttachmentAttachedEvent,
|
|
1410
|
+
AttachFileContract
|
|
1411
|
+
};
|