@tstdl/base 0.92.132 → 0.92.134

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 (42) hide show
  1. package/api/server/api-request-token.provider.d.ts +3 -0
  2. package/api/server/api-request-token.provider.js +9 -0
  3. package/api/server/module.js +1 -1
  4. package/database/mongo/module.js +6 -6
  5. package/document-management/api/document-management.api.d.ts +20 -4
  6. package/document-management/api/document-management.api.js +9 -3
  7. package/document-management/server/api/document-management.api.d.ts +1 -0
  8. package/document-management/server/api/document-management.api.js +9 -2
  9. package/document-management/server/module.d.ts +1 -0
  10. package/document-management/server/module.js +1 -0
  11. package/document-management/server/services/document-file.service.d.ts +16 -0
  12. package/document-management/server/services/document-file.service.js +54 -24
  13. package/document-management/server/services/document-management-ancillary.service.d.ts +2 -2
  14. package/document-management/server/services/document.service.d.ts +5 -1
  15. package/document-management/server/services/document.service.js +12 -9
  16. package/document-management/service-models/document.service-model.d.ts +1 -0
  17. package/document-management/service-models/document.service-model.js +1 -0
  18. package/examples/document-management/main.d.ts +1 -1
  19. package/examples/document-management/main.js +17 -8
  20. package/http/client/adapters/undici.adapter.js +3 -3
  21. package/http/client/http-client.js +29 -30
  22. package/http/http-body.js +4 -4
  23. package/http/http.error.d.ts +5 -1
  24. package/http/http.error.js +6 -6
  25. package/http/utils.js +4 -4
  26. package/injector/decorators.d.ts +1 -1
  27. package/injector/injector.d.ts +1 -1
  28. package/injector/interfaces.d.ts +1 -1
  29. package/injector/provider.d.ts +4 -4
  30. package/object-storage/object-storage.d.ts +37 -2
  31. package/object-storage/s3/s3.object-storage-provider.js +1 -1
  32. package/object-storage/s3/s3.object-storage.d.ts +6 -3
  33. package/object-storage/s3/s3.object-storage.js +86 -14
  34. package/object-storage/s3/s3.object.js +2 -3
  35. package/package.json +1 -1
  36. package/search-index/elastic/module.js +5 -5
  37. package/utils/cryptography.js +18 -18
  38. package/utils/object/object.d.ts +3 -2
  39. package/utils/object/object.js +5 -2
  40. package/utils/stream/size-limited-stream.js +1 -1
  41. package/utils/type-guards.d.ts +7 -1
  42. package/utils/type-guards.js +13 -1
@@ -5,3 +5,6 @@ export declare abstract class ApiRequestTokenProvider {
5
5
  export declare class NoopApiRequestTokenProvider extends ApiRequestTokenProvider {
6
6
  getToken<T>(): T;
7
7
  }
8
+ export declare class MockApiRequestTokenProvider extends ApiRequestTokenProvider {
9
+ getToken<T>(): T;
10
+ }
@@ -16,3 +16,12 @@ NoopApiRequestTokenProvider = __decorate([
16
16
  Singleton({ alias: ApiRequestTokenProvider })
17
17
  ], NoopApiRequestTokenProvider);
18
18
  export { NoopApiRequestTokenProvider };
19
+ let MockApiRequestTokenProvider = class MockApiRequestTokenProvider extends ApiRequestTokenProvider {
20
+ getToken() {
21
+ return {};
22
+ }
23
+ };
24
+ MockApiRequestTokenProvider = __decorate([
25
+ Singleton({ alias: ApiRequestTokenProvider })
26
+ ], MockApiRequestTokenProvider);
27
+ export { MockApiRequestTokenProvider };
@@ -4,7 +4,7 @@ import { ensureApiController } from './api-controller.js';
4
4
  import { ApiRequestTokenProvider } from './api-request-token.provider.js';
5
5
  import { API_CONTROLLER, API_MODULE_OPTIONS } from './tokens.js';
6
6
  export const apiModuleOptions = {
7
- controllers: []
7
+ controllers: [],
8
8
  };
9
9
  export function configureApiServer(options) {
10
10
  if (isDefined(options.controllers)) {
@@ -6,7 +6,7 @@ import { Collection, Database, MongoClient } from './classes.js';
6
6
  export const mongoModuleConfig = {
7
7
  defaultConnection: { url: 'mongodb://localhost:27017/test-db' },
8
8
  defaultDatabase: undefined,
9
- logPrefix: 'Mongo'
9
+ logPrefix: 'Mongo',
10
10
  };
11
11
  export function configureMongo(config) {
12
12
  mongoModuleConfig.defaultDatabase = config.defaultDatabase ?? mongoModuleConfig.defaultDatabase;
@@ -31,10 +31,10 @@ Injector.registerSingleton(MongoClient, {
31
31
  },
32
32
  async afterResolve(client, _argument, { cancellationSignal, data: { url, logger } }) {
33
33
  await connect(`mongo at ${url}`, async () => client.connect(), logger, cancellationSignal);
34
- }
34
+ },
35
35
  }, {
36
36
  defaultArgumentProvider: () => mongoModuleConfig.defaultConnection,
37
- argumentIdentityProvider: JSON.stringify
37
+ argumentIdentityProvider: JSON.stringify,
38
38
  });
39
39
  Injector.registerSingleton(Database, {
40
40
  useFactory: (argument, context) => {
@@ -45,7 +45,7 @@ Injector.registerSingleton(Database, {
45
45
  },
46
46
  defaultArgumentProvider: () => ({ database: mongoModuleConfig.defaultDatabase, connection: mongoModuleConfig.defaultConnection }),
47
47
  }, {
48
- argumentIdentityProvider: JSON.stringify
48
+ argumentIdentityProvider: JSON.stringify,
49
49
  });
50
50
  Injector.registerSingleton(Collection, {
51
51
  useFactory: (config, context) => {
@@ -62,7 +62,7 @@ Injector.registerSingleton(Collection, {
62
62
  }
63
63
  }
64
64
  await database.createCollection(config.collection);
65
- }
65
+ },
66
66
  }, {
67
- argumentIdentityProvider: JSON.stringify
67
+ argumentIdentityProvider: JSON.stringify,
68
68
  });
@@ -71,6 +71,15 @@ export declare const documentManagementApiDefinition: {
71
71
  result: typeof DocumentType;
72
72
  credentials: true;
73
73
  };
74
+ initiateDocumentUpload: {
75
+ resource: string;
76
+ method: "POST";
77
+ result: import("../../schema/index.js").ObjectSchema<{
78
+ uploadId: string;
79
+ uploadUrl: string;
80
+ }>;
81
+ credentials: true;
82
+ };
74
83
  createDocument: {
75
84
  resource: string;
76
85
  method: "POST";
@@ -94,6 +103,7 @@ export declare const documentManagementApiDefinition: {
94
103
  scope: string | string[];
95
104
  };
96
105
  };
106
+ uploadId: string;
97
107
  properties?: {
98
108
  propertyId: import("../../orm/schemas/uuid.js").Uuid;
99
109
  value: string | number | boolean | null;
@@ -105,8 +115,6 @@ export declare const documentManagementApiDefinition: {
105
115
  attributes: import("../../orm/types.js").HasDefault<import("../../orm/schemas/json.js").Json<import("../../orm/entity.js").EntityMetadataAttributes>>;
106
116
  }> | undefined;
107
117
  }>;
108
- body: Uint8ArrayConstructor;
109
- maxBytes: number;
110
118
  result: typeof Document;
111
119
  credentials: true;
112
120
  };
@@ -370,6 +378,15 @@ declare const _DocumentManagementApi: import("../../api/client/index.js").ApiCli
370
378
  result: typeof DocumentType;
371
379
  credentials: true;
372
380
  };
381
+ initiateDocumentUpload: {
382
+ resource: string;
383
+ method: "POST";
384
+ result: import("../../schema/index.js").ObjectSchema<{
385
+ uploadId: string;
386
+ uploadUrl: string;
387
+ }>;
388
+ credentials: true;
389
+ };
373
390
  createDocument: {
374
391
  resource: string;
375
392
  method: "POST";
@@ -393,6 +410,7 @@ declare const _DocumentManagementApi: import("../../api/client/index.js").ApiCli
393
410
  scope: string | string[];
394
411
  };
395
412
  };
413
+ uploadId: string;
396
414
  properties?: {
397
415
  propertyId: import("../../orm/schemas/uuid.js").Uuid;
398
416
  value: string | number | boolean | null;
@@ -404,8 +422,6 @@ declare const _DocumentManagementApi: import("../../api/client/index.js").ApiCli
404
422
  attributes: import("../../orm/types.js").HasDefault<import("../../orm/schemas/json.js").Json<import("../../orm/entity.js").EntityMetadataAttributes>>;
405
423
  }> | undefined;
406
424
  }>;
407
- body: Uint8ArrayConstructor;
408
- maxBytes: number;
409
425
  result: typeof Document;
410
426
  credentials: true;
411
427
  };
@@ -8,7 +8,6 @@ import { compileClient } from '../../api/client/index.js';
8
8
  import { defineApi } from '../../api/index.js';
9
9
  import { ReplaceClass } from '../../injector/decorators.js';
10
10
  import { array, boolean, literal, object, optional, string } from '../../schema/index.js';
11
- import { megabyte } from '../../utils/units.js';
12
11
  import { Document, DocumentCategory, DocumentRequest, DocumentRequestsTemplate, DocumentRequestTemplate, DocumentType } from '../models/index.js';
13
12
  import { addOrArchiveDocumentToOrFromCollectionParametersSchema, applyDocumentRequestsTemplateParametersSchema, createDocumentCategoryParametersSchema, createDocumentParametersSchema, createDocumentRequestParametersSchema, createDocumentRequestsTemplateParametersSchema, createDocumentRequestTemplateParametersSchema, createDocumentTypeParametersSchema, deleteDocumentRequestParametersSchema, deleteDocumentRequestsTemplateParametersSchema, deleteDocumentRequestTemplateParametersSchema, DocumentCategoryView, DocumentManagementData, DocumentRequestsTemplateData, loadDataParametersSchema, updateDocumentParametersSchema, updateDocumentRequestParametersSchema, updateDocumentRequestsTemplateParametersSchema, updateDocumentRequestTemplateParametersSchema } from '../service-models/index.js';
14
13
  export const documentManagementApiDefinition = defineApi({
@@ -67,12 +66,19 @@ export const documentManagementApiDefinition = defineApi({
67
66
  result: DocumentType,
68
67
  credentials: true,
69
68
  },
69
+ initiateDocumentUpload: {
70
+ resource: 'document-uploads',
71
+ method: 'POST',
72
+ result: object({
73
+ uploadId: string(),
74
+ uploadUrl: string(),
75
+ }),
76
+ credentials: true,
77
+ },
70
78
  createDocument: {
71
79
  resource: 'documents',
72
80
  method: 'POST',
73
81
  parameters: createDocumentParametersSchema,
74
- body: Uint8Array,
75
- maxBytes: 50 * megabyte,
76
82
  result: Document,
77
83
  credentials: true,
78
84
  },
@@ -9,6 +9,7 @@ export declare class DocumentManagementApiController implements ApiController<Do
9
9
  getContentUrl(context: ApiRequestContext<DocumentManagementApiDefinition, 'getContentUrl'>): Promise<ApiServerResult<DocumentManagementApiDefinition, 'getContentUrl'>>;
10
10
  createCategory(context: ApiRequestContext<DocumentManagementApiDefinition, 'createCategory'>): Promise<ApiServerResult<DocumentManagementApiDefinition, 'createCategory'>>;
11
11
  createType(context: ApiRequestContext<DocumentManagementApiDefinition, 'createType'>): Promise<ApiServerResult<DocumentManagementApiDefinition, 'createType'>>;
12
+ initiateDocumentUpload(context: ApiRequestContext<DocumentManagementApiDefinition, 'initiateDocumentUpload'>): Promise<ApiServerResult<DocumentManagementApiDefinition, 'initiateDocumentUpload'>>;
12
13
  createDocument(context: ApiRequestContext<DocumentManagementApiDefinition, 'createDocument'>): Promise<ApiServerResult<DocumentManagementApiDefinition, 'createDocument'>>;
13
14
  createDocumentRequestsTemplate(context: ApiRequestContext<DocumentManagementApiDefinition, 'createDocumentRequestsTemplate'>): Promise<ApiServerResult<DocumentManagementApiDefinition, 'createDocumentRequestsTemplate'>>;
14
15
  updateDocumentRequestsTemplate(_context: ApiRequestContext<DocumentManagementApiDefinition, 'updateDocumentRequestsTemplate'>): Promise<ApiServerResult<DocumentManagementApiDefinition, 'updateDocumentRequestsTemplate'>>;
@@ -92,8 +92,15 @@ let DocumentManagementApiController = DocumentManagementApiController_1 = class
92
92
  }
93
93
  return await this.#documentCategoryTypeService.createType(context.parameters.label, context.parameters.categoryId);
94
94
  }
95
+ async initiateDocumentUpload(context) {
96
+ const token = await context.getToken();
97
+ const subject = await this.#ancillaryService.getSubject(token);
98
+ return await this.#documentFileService.initiateUpload({ key: subject });
99
+ }
95
100
  async createDocument(context) {
96
101
  const token = await context.getToken();
102
+ const subject = await this.#ancillaryService.getSubject(token);
103
+ const { uploadId, ...createParameters } = context.parameters;
97
104
  const [collectionIds, requiresAssign] = await match(context.parameters.assignment)
98
105
  .with({ collections: P.select() }, (collectionIds) => [toArray(collectionIds), true])
99
106
  .with({ request: P.select() }, async (requestId) => {
@@ -115,8 +122,8 @@ let DocumentManagementApiController = DocumentManagementApiController_1 = class
115
122
  }
116
123
  }
117
124
  }
118
- const actionUserId = await this.#ancillaryService.getUserId(token);
119
- return await this.#documentService.create(context.parameters, context.body, { createUserId: actionUserId });
125
+ const actionUserId = await this.#ancillaryService.getSubject(token);
126
+ return await this.#documentService.create(createParameters, { uploadId, uploadKey: subject }, { createUserId: actionUserId });
120
127
  }
121
128
  async createDocumentRequestsTemplate(context) {
122
129
  const token = await context.getToken();
@@ -5,6 +5,7 @@ import { DocumentManagementAncillaryService } from './services/document-manageme
5
5
  export declare class DocumentManagementConfig {
6
6
  ancillaryService: InjectionToken<DocumentManagementAncillaryService>;
7
7
  fileObjectStorageModule: string;
8
+ fileUploadObjectStorageModule: string;
8
9
  filePreviewObjectStorageModule: string;
9
10
  database?: DatabaseConfig;
10
11
  }
@@ -5,6 +5,7 @@ import { DocumentManagementAncillaryService } from './services/document-manageme
5
5
  export class DocumentManagementConfig {
6
6
  ancillaryService;
7
7
  fileObjectStorageModule;
8
+ fileUploadObjectStorageModule;
8
9
  filePreviewObjectStorageModule;
9
10
  database;
10
11
  }
@@ -4,7 +4,23 @@ import { DocumentFile } from '../../models/index.js';
4
4
  export declare class DocumentFileService extends Transactional {
5
5
  #private;
6
6
  load(id: string): Promise<DocumentFile>;
7
+ /**
8
+ * Initiates a file upload
9
+ * @param key - key which can be used to authorize the creation of the file. Same key must be provided to {@link create} method, or it will throw an error.
10
+ * The key could be the user id from the request token. This ensures that the file can only be created by the user who initiated the upload.
11
+ * @returns upload information
12
+ */
13
+ initiateUpload({ key }: {
14
+ key: string;
15
+ }): Promise<{
16
+ uploadId: string;
17
+ uploadUrl: string;
18
+ }>;
7
19
  create(content: Uint8Array | ReadableStream<Uint8Array>, originalFileName: string | null): Promise<DocumentFile>;
20
+ create(content: {
21
+ uploadId: string;
22
+ uploadKey: string;
23
+ }, originalFileName: string | null): Promise<[DocumentFile, Uint8Array]>;
8
24
  getContent(fileId: string): Promise<Uint8Array>;
9
25
  getContentStream(fileId: string): ReadableStream<Uint8Array>;
10
26
  getContentUrl(fileId: string, title: string | null, download?: boolean): Promise<string>;
@@ -61,6 +61,7 @@ var DocumentFileService_1;
61
61
  import sharp, {} from 'sharp';
62
62
  import { match } from 'ts-pattern';
63
63
  import { AiService } from '../../../ai/ai.service.js';
64
+ import { ForbiddenError } from '../../../errors/forbidden.error.js';
64
65
  import { NotImplementedError } from '../../../errors/not-implemented.error.js';
65
66
  import { getMimeType, getMimeTypeExtensions } from '../../../file/index.js';
66
67
  import { TemporaryFile } from '../../../file/server/index.js';
@@ -70,30 +71,55 @@ import { ObjectStorage } from '../../../object-storage/index.js';
70
71
  import { Transactional } from '../../../orm/server/index.js';
71
72
  import { injectRepository } from '../../../orm/server/repository.js';
72
73
  import { pdfToImage } from '../../../pdf/index.js';
74
+ import { Alphabet } from '../../../utils/alphabet.js';
73
75
  import { digest } from '../../../utils/cryptography.js';
74
76
  import { currentTimestamp } from '../../../utils/date-time.js';
77
+ import { getRandomString } from '../../../utils/random.js';
75
78
  import { readableStreamFromPromise, readBinaryStream } from '../../../utils/stream/index.js';
76
- import { isDefined, isUint8Array } from '../../../utils/type-guards.js';
77
- import { millisecondsPerMinute } from '../../../utils/units.js';
79
+ import { isDefined, isNotReadableStream, isNotUint8Array, isUint8Array } from '../../../utils/type-guards.js';
80
+ import { millisecondsPerMinute, secondsPerMinute } from '../../../utils/units.js';
78
81
  import { Document, DocumentFile } from '../../models/index.js';
79
82
  import { DocumentManagementConfig } from '../module.js';
80
83
  import { DocumentManagementSingleton } from './singleton.js';
81
84
  let DocumentFileService = DocumentFileService_1 = class DocumentFileService extends Transactional {
85
+ #config = inject(DocumentManagementConfig);
82
86
  #documentFileRepository = injectRepository(DocumentFile);
83
87
  #documentRepository = injectRepository(Document);
84
88
  #aiService = inject(AiService);
85
- #fileObjectStorage = inject(ObjectStorage, inject(DocumentManagementConfig).fileObjectStorageModule);
86
- #filePreviewObjectStorage = inject(ObjectStorage, inject(DocumentManagementConfig).filePreviewObjectStorageModule);
89
+ #fileObjectStorage = inject(ObjectStorage, this.#config.fileObjectStorageModule);
90
+ #filePreviewObjectStorage = inject(ObjectStorage, this.#config.filePreviewObjectStorageModule);
91
+ #fileUploadObjectStorage = inject(ObjectStorage, { module: this.#config.fileUploadObjectStorageModule, configuration: { lifecycle: { expiration: { after: 5 * secondsPerMinute } } } });
87
92
  #logger = inject(Logger, DocumentFileService_1.name);
88
93
  #aiFilePartCache = new Map();
89
94
  async load(id) {
90
- return this.#documentFileRepository.load(id);
95
+ return await this.#documentFileRepository.load(id);
96
+ }
97
+ /**
98
+ * Initiates a file upload
99
+ * @param key - key which can be used to authorize the creation of the file. Same key must be provided to {@link create} method, or it will throw an error.
100
+ * The key could be the user id from the request token. This ensures that the file can only be created by the user who initiated the upload.
101
+ * @returns upload information
102
+ */
103
+ async initiateUpload({ key }) {
104
+ const id = getRandomString(64, Alphabet.LowerUpperCaseNumbers);
105
+ const url = await this.#fileUploadObjectStorage.getUploadUrl(id, currentTimestamp() + (5 * millisecondsPerMinute), { metadata: { uploadKey: key } });
106
+ return { uploadId: id, uploadUrl: url };
91
107
  }
92
108
  async create(content, originalFileName) {
93
- const contentAsUint8Array = isUint8Array(content) ? content : await readBinaryStream(content);
109
+ const isUpload = isNotUint8Array(content) && isNotReadableStream(content);
110
+ if (isUpload) {
111
+ const object = await this.#fileUploadObjectStorage.getObject(content.uploadId);
112
+ const objectMetadata = await object.getMetadata();
113
+ if (content.uploadKey != objectMetadata['uploadKey']) {
114
+ throw new ForbiddenError(`Invalid upload key`);
115
+ }
116
+ }
117
+ const contentAsUint8Array = isUpload
118
+ ? await this.#fileUploadObjectStorage.getContent(content.uploadId)
119
+ : (isUint8Array(content) ? content : await readBinaryStream(content));
94
120
  const hash = await digest('SHA-256', contentAsUint8Array).toHex();
95
121
  const mimeType = await getMimeType(contentAsUint8Array);
96
- return this.transaction(async (tx) => {
122
+ return await this.transaction(async (tx) => {
97
123
  const documentFile = await this.#documentFileRepository
98
124
  .withTransaction(tx)
99
125
  .insert({
@@ -102,39 +128,43 @@ let DocumentFileService = DocumentFileService_1 = class DocumentFileService exte
102
128
  hash,
103
129
  size: contentAsUint8Array.length,
104
130
  });
105
- const key = getDocumentFileKey(documentFile.id);
106
- await this.#fileObjectStorage.uploadObject(key, contentAsUint8Array);
131
+ const objectKey = getDocumentFileKey(documentFile.id);
132
+ if (isUpload) {
133
+ await this.#fileUploadObjectStorage.moveObject(content.uploadId, [this.#fileObjectStorage, objectKey]);
134
+ return [documentFile, contentAsUint8Array];
135
+ }
136
+ await this.#fileObjectStorage.uploadObject(objectKey, contentAsUint8Array);
107
137
  return documentFile;
108
138
  });
109
139
  }
110
140
  async getContent(fileId) {
111
- const key = getDocumentFileKey(fileId);
112
- return this.#fileObjectStorage.getContent(key);
141
+ const objectKey = getDocumentFileKey(fileId);
142
+ return await this.#fileObjectStorage.getContent(objectKey);
113
143
  }
114
144
  getContentStream(fileId) {
115
- const key = getDocumentFileKey(fileId);
116
- return this.#fileObjectStorage.getContentStream(key);
145
+ const objectKey = getDocumentFileKey(fileId);
146
+ return this.#fileObjectStorage.getContentStream(objectKey);
117
147
  }
118
148
  async getContentUrl(fileId, title, download = false) {
119
149
  const file = await this.#documentFileRepository.load(fileId);
120
- return this.getDocumentFileContentObjectUrl(title ?? fileId, file, download);
150
+ return await this.getDocumentFileContentObjectUrl(title ?? fileId, file, download);
121
151
  }
122
152
  async getPreview(fileId, page = 1) {
123
- const key = getDocumentFileKey(fileId);
153
+ const objectKey = getDocumentFileKey(fileId);
124
154
  await this.createPreviewIfNotExists(fileId, page);
125
- return this.#filePreviewObjectStorage.getContent(key);
155
+ return await this.#filePreviewObjectStorage.getContent(objectKey);
126
156
  }
127
157
  getPreviewStream(fileId, page = 1) {
128
158
  return readableStreamFromPromise(async () => {
129
- const key = getDocumentFileKey(fileId);
159
+ const objectKey = getDocumentFileKey(fileId);
130
160
  await this.createPreviewIfNotExists(fileId, page);
131
- return this.#filePreviewObjectStorage.getContentStream(key);
161
+ return this.#filePreviewObjectStorage.getContentStream(objectKey);
132
162
  });
133
163
  }
134
164
  async getPreviewUrl(fileId, page = 1) {
135
- const key = getDocumentFileKey(fileId);
165
+ const objectKey = getDocumentFileKey(fileId);
136
166
  await this.createPreviewIfNotExists(fileId, page);
137
- return this.#filePreviewObjectStorage.getDownloadUrl(key, currentTimestamp() + (5 * millisecondsPerMinute), {
167
+ return await this.#filePreviewObjectStorage.getDownloadUrl(objectKey, currentTimestamp() + (5 * millisecondsPerMinute), {
138
168
  'Response-Content-Type': 'image/jpeg',
139
169
  });
140
170
  }
@@ -175,9 +205,9 @@ let DocumentFileService = DocumentFileService_1 = class DocumentFileService exte
175
205
  const image = await match(file.mimeType)
176
206
  .with('application/pdf', async () => {
177
207
  const imageBytes = await pdfToImage(content, page, 768, 'jpeg');
178
- return imageToPreview(imageBytes);
208
+ return await imageToPreview(imageBytes);
179
209
  })
180
- .with('image/*', async () => imageToPreview(content))
210
+ .with('image/*', async () => await imageToPreview(content))
181
211
  .otherwise(() => { throw new NotImplementedError('Preview generation is not implemented for this file type.'); });
182
212
  await this.#filePreviewObjectStorage.uploadObject(key, image, { contentLength: image.length, metadata: { 'Content-Type': 'image/jpeg' } });
183
213
  }
@@ -187,7 +217,7 @@ let DocumentFileService = DocumentFileService_1 = class DocumentFileService exte
187
217
  const fileExtension = getMimeTypeExtensions(file.mimeType)[0] ?? 'bin';
188
218
  const disposition = download ? 'attachment' : 'inline';
189
219
  const filename = `${title}.${fileExtension}`;
190
- return this.#fileObjectStorage.getDownloadUrl(key, currentTimestamp() + (5 * millisecondsPerMinute), {
220
+ return await this.#fileObjectStorage.getDownloadUrl(key, currentTimestamp() + (5 * millisecondsPerMinute), {
191
221
  'Response-Content-Type': file.mimeType,
192
222
  'Response-Content-Disposition': `${disposition}; filename = "${encodeURIComponent(filename)}"`,
193
223
  });
@@ -201,7 +231,7 @@ function getDocumentFileKey(id) {
201
231
  return `${id.slice(0, 2)}/${id.slice(0, 4)}/${id}`;
202
232
  }
203
233
  async function imageToPreview(input) {
204
- return sharp(input)
234
+ return await sharp(input)
205
235
  .resize({
206
236
  width: 768,
207
237
  height: 768,
@@ -28,10 +28,10 @@ export declare abstract class DocumentManagementAncillaryService<Token = unknown
28
28
  */
29
29
  abstract _resolveMetadata(collections: DocumentCollection[]): DocumentCollectionMetadata[] | Promise<DocumentCollectionMetadata[]>;
30
30
  /**
31
- * Gets the user ID from the request token.
31
+ * Gets the subject from the request token.
32
32
  * @param token The token of the request
33
33
  */
34
- abstract getUserId(token?: Token): string | Promise<string>;
34
+ abstract getSubject(token?: Token): string | Promise<string>;
35
35
  /**
36
36
  * Checks if a user can read/view a specific document collection and its documents.
37
37
  * @param collectionId The ID of the document collection.
@@ -1,11 +1,15 @@
1
1
  import { type UpdatableDocumentProperties } from '../../../document-management/models/index.js';
2
2
  import type { CreateDocumentParameters, SetDocumentPropertyParameters } from '../../../document-management/service-models/index.js';
3
3
  import { Transactional } from '../../../orm/server/index.js';
4
+ import type { TypedOmit } from '../../../types.js';
4
5
  import { Document } from '../../models/index.js';
5
6
  export declare class DocumentService extends Transactional {
6
7
  #private;
7
8
  readonly repository: import("../../../orm/server/repository.js").EntityRepository<Document>;
8
- create({ typeId, title, subtitle, date, summary, tags, approval, comment, originalFileName, assignment, properties, metadata }: CreateDocumentParameters, content: Uint8Array | ReadableStream<Uint8Array>, { createUserId }: {
9
+ create({ typeId, title, subtitle, date, summary, tags, approval, comment, originalFileName, assignment, properties, metadata }: TypedOmit<CreateDocumentParameters, 'uploadId'>, contentSource: Uint8Array | ReadableStream<Uint8Array> | {
10
+ uploadId: string;
11
+ uploadKey: string;
12
+ }, { createUserId }: {
9
13
  createUserId?: string;
10
14
  }): Promise<Document>;
11
15
  update(id: string, update: Partial<Pick<Document, UpdatableDocumentProperties>> & {
@@ -18,7 +18,7 @@ import { toArray } from '../../../utils/array/index.js';
18
18
  import { objectKeys } from '../../../utils/object/object.js';
19
19
  import { readableStreamFromPromise } from '../../../utils/stream/from-promise.js';
20
20
  import { tryIgnoreLogAsync } from '../../../utils/try-ignore.js';
21
- import { isDefined, isString, isUndefined } from '../../../utils/type-guards.js';
21
+ import { isDefined, isNotReadableStream, isNotUint8Array, isString, isUndefined } from '../../../utils/type-guards.js';
22
22
  import { Document } from '../../models/index.js';
23
23
  import { DocumentCollectionService } from './document-collection.service.js';
24
24
  import { DocumentFileService } from './document-file.service.js';
@@ -36,10 +36,13 @@ let DocumentService = DocumentService_1 = class DocumentService extends Transact
36
36
  #documentAssignmentScopeRepository = injectRepository(DocumentAssignmentScope);
37
37
  #logger = inject(Logger, DocumentService_1.name);
38
38
  repository = injectRepository(Document).withSession(this.session);
39
- async create({ typeId, title, subtitle, date, summary, tags, approval, comment, originalFileName, assignment, properties, metadata }, content, { createUserId }) {
39
+ async create({ typeId, title, subtitle, date, summary, tags, approval, comment, originalFileName, assignment, properties, metadata }, contentSource, { createUserId }) {
40
40
  const document = await this.transaction(async (tx) => {
41
- const documentFile = await this.#documentFileService.withTransaction(tx).create(content, originalFileName);
42
- const pages = documentFile.mimeType.includes('pdf') ? await tryIgnoreLogAsync(this.#logger, async () => getPdfPageCount(content), null) : null;
41
+ const isUpload = isNotUint8Array(contentSource) && isNotReadableStream(contentSource);
42
+ const [documentFile, content] = isUpload
43
+ ? await this.#documentFileService.withTransaction(tx).create(contentSource, originalFileName)
44
+ : [await this.#documentFileService.withTransaction(tx).create(contentSource, originalFileName), contentSource];
45
+ const pages = documentFile.mimeType.includes('pdf') ? await tryIgnoreLogAsync(this.#logger, async () => await getPdfPageCount(content), null) : null;
43
46
  const document = await this.repository.withTransaction(tx).insert({
44
47
  fileId: documentFile.id,
45
48
  typeId: typeId ?? null,
@@ -80,7 +83,7 @@ let DocumentService = DocumentService_1 = class DocumentService extends Transact
80
83
  }
81
84
  async getContent(documentOrId) {
82
85
  const document = isString(documentOrId) ? await this.repository.load(documentOrId) : documentOrId;
83
- return this.#documentFileService.getContent(document.fileId);
86
+ return await this.#documentFileService.getContent(document.fileId);
84
87
  }
85
88
  getContentStream(documentOrId) {
86
89
  return readableStreamFromPromise(async () => {
@@ -90,11 +93,11 @@ let DocumentService = DocumentService_1 = class DocumentService extends Transact
90
93
  }
91
94
  async getContentUrl(documentOrId, download = false) {
92
95
  const document = isString(documentOrId) ? await this.repository.load(documentOrId) : documentOrId;
93
- return this.#documentFileService.getContentUrl(document.fileId, document.title, download);
96
+ return await this.#documentFileService.getContentUrl(document.fileId, document.title, download);
94
97
  }
95
98
  async getPreview(documentOrId, page = 1) {
96
99
  const document = isString(documentOrId) ? await this.repository.load(documentOrId) : documentOrId;
97
- return this.#documentFileService.getPreview(document.fileId, page);
100
+ return await this.#documentFileService.getPreview(document.fileId, page);
98
101
  }
99
102
  getPreviewStream(documentOrId, page = 1) {
100
103
  return readableStreamFromPromise(async () => {
@@ -104,13 +107,13 @@ let DocumentService = DocumentService_1 = class DocumentService extends Transact
104
107
  }
105
108
  async getPreviewUrl(documentOrId, page = 1) {
106
109
  const document = isString(documentOrId) ? await this.repository.load(documentOrId) : documentOrId;
107
- return this.#documentFileService.getPreviewUrl(document.fileId, page);
110
+ return await this.#documentFileService.getPreviewUrl(document.fileId, page);
108
111
  }
109
112
  /**
110
113
  * @returns collectionIds from either direct assignment or automatic assignment scope
111
114
  */
112
115
  async createAssignment(documentId, assignment, transaction) {
113
- return match(assignment)
116
+ return await match(assignment)
114
117
  .with({ collections: P.select() }, async (collectionIds) => {
115
118
  const collectionIdsArray = toArray(collectionIds);
116
119
  await this.#documentCollectionService.withTransaction(transaction).assignDocument(documentId, collectionIdsArray);
@@ -34,6 +34,7 @@ export declare const createDocumentParametersSchema: import("../../schema/index.
34
34
  scope: string | string[];
35
35
  };
36
36
  };
37
+ uploadId: string;
37
38
  properties?: {
38
39
  propertyId: import("../../orm/schemas/uuid.js").Uuid;
39
40
  value: string | number | boolean | null;
@@ -5,6 +5,7 @@ export const metadataParameterSchema = optional(partial(pick(EntityMetadata, 'at
5
5
  export const metadataParameterObjectSchema = object({ metadata: metadataParameterSchema });
6
6
  export const setDocumentPropertyParametersSchema = assign(pick(DocumentPropertyValue, ['propertyId']), object({ value: union(string(), number(), boolean(), nullable(never())) }), metadataParameterObjectSchema);
7
7
  export const createDocumentParametersSchema = assign(partial(pick(Document, ['typeId', 'title', 'subtitle', 'date', 'summary', 'tags', 'approval', 'comment'])), pick(DocumentFile, ['originalFileName']), object({
8
+ uploadId: string(),
8
9
  assignment: union(object({ collections: oneOrMany(string(), { minimum: 1 }) }), object({ request: string() }), object({
9
10
  automatic: object({
10
11
  /** collection ids to assign in */
@@ -3,7 +3,7 @@ import type { DocumentCollection, DocumentWorkflowStep } from '../../document-ma
3
3
  import { DocumentManagementAncillaryService, type DocumentCollectionMetadata } from '../../document-management/server/index.js';
4
4
  export declare class ExampleDocumentManagementAncillaryService extends DocumentManagementAncillaryService {
5
5
  _resolveMetadata(collections: DocumentCollection[]): DocumentCollectionMetadata[];
6
- getUserId(_token?: unknown): string;
6
+ getSubject(_token?: unknown): string;
7
7
  canCreateDocuments(_collectionId: string, _token?: unknown): boolean;
8
8
  canUpdateDocuments(_collectionId: string, _token?: unknown): boolean;
9
9
  canDeleteDocuments(_collectionId: string, _token?: unknown): boolean;
@@ -6,15 +6,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  };
7
7
  import '../../polyfills.js';
8
8
  import { configureAiService } from '../../ai/index.js';
9
+ import { MockApiRequestTokenProvider } from '../../api/server/api-request-token.provider.js';
9
10
  import { configureApiServer } from '../../api/server/module.js';
10
11
  import { Application } from '../../application/application.js';
11
- import { AuthenticationApiRequestTokenProvider } from '../../authentication/server/authentication-api-request-token.provider.js';
12
12
  import { DocumentCategoryTypeService, DocumentCollectionService, DocumentManagementAncillaryService, DocumentManagementApiController, DocumentRequestService } from '../../document-management/server/index.js';
13
13
  import { configureDocumentManagement, migrateDocumentManagementSchema } from '../../document-management/server/module.js';
14
14
  import { DocumentManagementService } from '../../document-management/server/services/document-management.service.js';
15
+ import { configureNodeHttpServer } from '../../http/server/node/module.js';
15
16
  import { Injector, Singleton } from '../../injector/index.js';
16
17
  import { inject, injectManyAsync, runInInjectionContext } from '../../injector/inject.js';
17
18
  import { configureLocalMessageBus } from '../../message-bus/index.js';
19
+ import { WebServerModule } from '../../module/index.js';
18
20
  import { configureS3ObjectStorage } from '../../object-storage/index.js';
19
21
  import { configureOrm } from '../../orm/server/index.js';
20
22
  import { configurePostgresQueue, migratePostgresQueueSchema } from '../../queue/postgres/index.js';
@@ -47,9 +49,9 @@ const config = {
47
49
  };
48
50
  let ExampleDocumentManagementAncillaryService = class ExampleDocumentManagementAncillaryService extends DocumentManagementAncillaryService {
49
51
  _resolveMetadata(collections) {
50
- return collections.map((collection) => ({ name: collection.id, group: null }));
52
+ return collections.map((collection) => ({ name: collection.id.split('-')[0], group: null }));
51
53
  }
52
- getUserId(_token) { return 'user-id'; }
54
+ getSubject(_token) { return 'user-id'; }
53
55
  canCreateDocuments(_collectionId, _token) { return true; }
54
56
  canUpdateDocuments(_collectionId, _token) { return true; }
55
57
  canDeleteDocuments(_collectionId, _token) { return true; }
@@ -70,6 +72,7 @@ ExampleDocumentManagementAncillaryService = __decorate([
70
72
  export { ExampleDocumentManagementAncillaryService };
71
73
  async function bootstrap() {
72
74
  const injector = inject(Injector);
75
+ configureNodeHttpServer();
73
76
  configurePostgresQueue();
74
77
  configureLocalMessageBus();
75
78
  configureOrm({
@@ -84,6 +87,7 @@ async function bootstrap() {
84
87
  configureDocumentManagement({
85
88
  ancillaryService: ExampleDocumentManagementAncillaryService,
86
89
  fileObjectStorageModule: 'documents',
90
+ fileUploadObjectStorageModule: 'document-uploads',
87
91
  filePreviewObjectStorageModule: 'document-previews',
88
92
  });
89
93
  configureS3ObjectStorage({
@@ -95,7 +99,7 @@ async function bootstrap() {
95
99
  });
96
100
  configureApiServer({
97
101
  controllers: [DocumentManagementApiController],
98
- requestTokenProvider: AuthenticationApiRequestTokenProvider,
102
+ requestTokenProvider: MockApiRequestTokenProvider,
99
103
  gatewayOptions: {
100
104
  prefix: null,
101
105
  cors: {
@@ -116,8 +120,13 @@ async function bootstrap() {
116
120
  async function main() {
117
121
  const [documentManagementService, documentCollectionService] = await injectManyAsync(DocumentManagementService, DocumentCollectionService, DocumentCategoryTypeService, DocumentRequestService);
118
122
  const { categories, types } = await documentManagementService.initializeCategoriesAndTypes(TstdlDocumentCategoryLabels, TstdlCategoryParents, TstdlDocumentTypeLabels, TstdlDocumentTypeCategories);
119
- const collection = await documentCollectionService.createCollection(null);
120
- const data = await documentManagementService.loadData([collection.id]);
121
- console.log(data);
123
+ const collectionCount = await documentCollectionService.repository.count();
124
+ for (let i = 0; i < (3 - collectionCount); i++) {
125
+ await documentCollectionService.createCollection(null);
126
+ }
127
+ const collections = await documentCollectionService.repository.loadAll();
128
+ for (const collection of collections) {
129
+ console.log(`Collection: ${collection.id}`);
130
+ }
122
131
  }
123
- Application.run({ bootstrap }, main);
132
+ Application.run({ bootstrap }, main, WebServerModule);
@@ -56,7 +56,7 @@ let UndiciHttpClientAdapter = class UndiciHttpClientAdapter extends HttpClientAd
56
56
  body,
57
57
  headersTimeout: httpClientRequest.timeout,
58
58
  bodyTimeout: httpClientRequest.timeout,
59
- dispatcher: this.options.dispatcher
59
+ dispatcher: this.options.dispatcher,
60
60
  });
61
61
  const httpClientResponse = new HttpClientResponse({
62
62
  request: httpClientRequest,
@@ -64,14 +64,14 @@ let UndiciHttpClientAdapter = class UndiciHttpClientAdapter extends HttpClientAd
64
64
  statusMessage: '?',
65
65
  headers: new HttpHeaders(response.headers),
66
66
  body: response.body,
67
- closeHandler: () => response.body.destroy()
67
+ closeHandler: () => response.body.destroy(),
68
68
  });
69
69
  return httpClientResponse;
70
70
  }
71
71
  catch (error) {
72
72
  if (error instanceof undiciErrors.UndiciError) {
73
73
  const reason = getHttpErrorReason(error);
74
- throw new HttpError(reason, httpClientRequest, undefined, undefined, error);
74
+ throw new HttpError(reason, httpClientRequest, { cause: error });
75
75
  }
76
76
  throw error;
77
77
  }