@tstdl/base 0.92.138 → 0.92.140

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 (52) hide show
  1. package/ai/ai.service.js +1 -1
  2. package/css/css-variables.d.ts +14 -0
  3. package/css/css-variables.js +55 -0
  4. package/css/index.d.ts +1 -0
  5. package/css/index.js +1 -0
  6. package/document-management/api/document-management.api.d.ts +59 -0
  7. package/document-management/api/document-management.api.js +28 -0
  8. package/document-management/server/api/document-management.api.d.ts +4 -1
  9. package/document-management/server/api/document-management.api.js +61 -28
  10. package/document-management/server/configure.d.ts +2 -0
  11. package/document-management/server/configure.js +9 -0
  12. package/document-management/server/drizzle/{0000_magical_madame_hydra.sql → 0000_moaning_luckman.sql} +4 -15
  13. package/document-management/server/drizzle/meta/0000_snapshot.json +25 -96
  14. package/document-management/server/drizzle/meta/_journal.json +2 -2
  15. package/document-management/server/index.d.ts +1 -0
  16. package/document-management/server/index.js +1 -0
  17. package/document-management/server/module.d.ts +3 -2
  18. package/document-management/server/module.js +2 -6
  19. package/document-management/server/services/document-category-type.service.d.ts +0 -1
  20. package/document-management/server/services/document-category-type.service.js +7 -7
  21. package/document-management/server/services/document-file.service.js +2 -2
  22. package/document-management/server/services/document-management-ai.service.js +5 -1
  23. package/document-management/server/services/document-management-ancillary.service.d.ts +2 -83
  24. package/document-management/server/services/document-management-ancillary.service.js +1 -23
  25. package/document-management/server/services/document-management-authorization.service.d.ts +85 -0
  26. package/document-management/server/services/document-management-authorization.service.js +28 -0
  27. package/document-management/server/services/document-management.service.d.ts +10 -2
  28. package/document-management/server/services/document-management.service.js +69 -6
  29. package/document-management/server/services/document-property.service.d.ts +7 -3
  30. package/document-management/server/services/document-property.service.js +8 -4
  31. package/document-management/server/services/document-workflow.service.js +2 -2
  32. package/document-management/server/services/enum-type-key.d.ts +1 -0
  33. package/document-management/server/services/enum-type-key.js +1 -0
  34. package/document-management/server/services/index.d.ts +1 -0
  35. package/document-management/server/services/index.js +1 -0
  36. package/document-management/service-models/enriched/enriched-document-category.view.d.ts +1 -0
  37. package/document-management/service-models/enriched/enriched-document-category.view.js +8 -0
  38. package/document-management/service-models/enriched/enriched-document.view.d.ts +3 -1
  39. package/document-management/service-models/enriched/enriched-document.view.js +2 -0
  40. package/examples/api/streaming.js +8 -8
  41. package/examples/document-management/categories-and-types.d.ts +357 -312
  42. package/examples/document-management/categories-and-types.js +690 -350
  43. package/examples/document-management/main.d.ts +18 -16
  44. package/examples/document-management/main.js +29 -20
  45. package/file/mime-type.d.ts +2 -1
  46. package/file/mime-type.js +10 -18
  47. package/file/mime-types.js +1 -2
  48. package/http/server/http-server-response.js +2 -2
  49. package/package.json +5 -3
  50. package/schema/converters/openapi-converter.js +15 -12
  51. package/sse/server-sent-events-source.js +2 -2
  52. package/utils/object/object.js +1 -1
@@ -1,4 +1,5 @@
1
1
  export * from './api/index.js';
2
+ export * from './configure.js';
2
3
  export * from './module.js';
3
4
  export * as schemas from './schemas.js';
4
5
  export * from './services/index.js';
@@ -1,4 +1,5 @@
1
1
  export * from './api/index.js';
2
+ export * from './configure.js';
2
3
  export * from './module.js';
3
4
  export * as schemas from './schemas.js';
4
5
  export * from './services/index.js';
@@ -1,14 +1,15 @@
1
1
  import './schemas.js';
2
2
  import { type InjectionToken } from '../../injector/index.js';
3
3
  import type { DatabaseConfig } from '../../orm/server/module.js';
4
- import { DocumentManagementAncillaryService } from './services/document-management-ancillary.service.js';
4
+ import type { DocumentManagementAncillaryService } from './services/document-management-ancillary.service.js';
5
+ import type { DocumentManagementAuthorizationService } from './services/document-management-authorization.service.js';
5
6
  export declare class DocumentManagementConfig {
6
7
  ancillaryService: InjectionToken<DocumentManagementAncillaryService>;
8
+ authorizationService: InjectionToken<DocumentManagementAuthorizationService>;
7
9
  fileObjectStorageModule: string;
8
10
  fileUploadObjectStorageModule: string;
9
11
  filePreviewObjectStorageModule: string;
10
12
  database?: DatabaseConfig;
11
13
  maxFileSize?: number;
12
14
  }
13
- export declare function configureDocumentManagement(config: DocumentManagementConfig): void;
14
15
  export declare function migrateDocumentManagementSchema(): Promise<void>;
@@ -1,9 +1,9 @@
1
1
  import './schemas.js';
2
- import { inject, Injector } from '../../injector/index.js';
2
+ import { inject } from '../../injector/index.js';
3
3
  import { Database, migrate } from '../../orm/server/database.js';
4
- import { DocumentManagementAncillaryService } from './services/document-management-ancillary.service.js';
5
4
  export class DocumentManagementConfig {
6
5
  ancillaryService;
6
+ authorizationService;
7
7
  fileObjectStorageModule;
8
8
  fileUploadObjectStorageModule;
9
9
  filePreviewObjectStorageModule;
@@ -11,10 +11,6 @@ export class DocumentManagementConfig {
11
11
  maxFileSize;
12
12
  }
13
13
  ;
14
- export function configureDocumentManagement(config) {
15
- Injector.register(DocumentManagementConfig, { useValue: config });
16
- Injector.register(DocumentManagementAncillaryService, { useToken: config.ancillaryService });
17
- }
18
14
  export async function migrateDocumentManagementSchema() {
19
15
  const connection = inject(DocumentManagementConfig, undefined, { optional: true })?.database?.connection;
20
16
  const database = inject(Database, connection);
@@ -1,7 +1,6 @@
1
1
  import { Transactional } from '../../../orm/server/index.js';
2
2
  import { DocumentCategory, DocumentType } from '../../models/index.js';
3
3
  import type { DocumentCategoryView } from '../../service-models/index.js';
4
- export declare const enumTypeKey = "enum-type";
5
4
  export declare class DocumentCategoryTypeService extends Transactional {
6
5
  #private;
7
6
  readonly categoryRepository: import("../../../orm/server/repository.js").EntityRepository<DocumentCategory>;
@@ -13,29 +13,29 @@ import { injectRepository } from '../../../orm/server/repository.js';
13
13
  import { groupToMap } from '../../../utils/iterable-helpers/group-to-map.js';
14
14
  import { isUndefined } from '../../../utils/type-guards.js';
15
15
  import { DocumentCategory, DocumentType } from '../../models/index.js';
16
+ import { enumTypeKey } from './enum-type-key.js';
16
17
  import { DocumentManagementSingleton } from './singleton.js';
17
- export const enumTypeKey = 'enum-type';
18
18
  let DocumentCategoryTypeService = DocumentCategoryTypeService_1 = class DocumentCategoryTypeService extends Transactional {
19
19
  #logger = inject(Logger, DocumentCategoryTypeService_1.name);
20
20
  categoryRepository = injectRepository(DocumentCategory);
21
21
  typeRepository = injectRepository(DocumentType);
22
22
  async loadCategory(id) {
23
- return this.categoryRepository.load(id);
23
+ return await this.categoryRepository.load(id);
24
24
  }
25
25
  async loadType(id) {
26
- return this.typeRepository.load(id);
26
+ return await this.typeRepository.load(id);
27
27
  }
28
28
  async createCategory(label, parentId, enumKey) {
29
- return this.categoryRepository.insert({ label, parentId, metadata: { attributes: { [enumTypeKey]: enumKey } } });
29
+ return await this.categoryRepository.insert({ label, parentId, metadata: { attributes: { [enumTypeKey]: enumKey } } });
30
30
  }
31
31
  async createType(label, categoryId, enumKey) {
32
- return this.typeRepository.insert({ label, categoryId, metadata: { attributes: { [enumTypeKey]: enumKey } } });
32
+ return await this.typeRepository.insert({ label, categoryId, metadata: { attributes: { [enumTypeKey]: enumKey } } });
33
33
  }
34
34
  async updateCategory(id, update) {
35
- return this.categoryRepository.update(id, update);
35
+ return await this.categoryRepository.update(id, update);
36
36
  }
37
37
  async updateType(id, update) {
38
- return this.typeRepository.update(id, update);
38
+ return await this.typeRepository.update(id, update);
39
39
  }
40
40
  async loadCategoryGraph(categoryId) {
41
41
  const category = await this.categoryRepository.load(categoryId);
@@ -63,7 +63,7 @@ import { match } from 'ts-pattern';
63
63
  import { AiService } from '../../../ai/ai.service.js';
64
64
  import { ForbiddenError } from '../../../errors/forbidden.error.js';
65
65
  import { NotImplementedError } from '../../../errors/not-implemented.error.js';
66
- import { getMimeType, getMimeTypeExtensions } from '../../../file/index.js';
66
+ import { getMimeType, getMimeTypeExtensions, mimeTypes } from '../../../file/index.js';
67
67
  import { TemporaryFile } from '../../../file/server/index.js';
68
68
  import { inject } from '../../../injector/inject.js';
69
69
  import { Logger } from '../../../logger/logger.js';
@@ -122,7 +122,7 @@ let DocumentFileService = DocumentFileService_1 = class DocumentFileService exte
122
122
  ? await this.#fileUploadObjectStorage.getContent(content.uploadId)
123
123
  : (isUint8Array(content) ? content : await readBinaryStream(content));
124
124
  const hash = await digest('SHA-256', contentAsUint8Array).toHex();
125
- const mimeType = await getMimeType(contentAsUint8Array);
125
+ const mimeType = await getMimeType(contentAsUint8Array, 'application/octet-stream');
126
126
  const metadata = {
127
127
  mimeType,
128
128
  size: contentAsUint8Array.length,
@@ -188,6 +188,7 @@ let DocumentManagementAiService = DocumentManagementAiService_1 = class Document
188
188
  {
189
189
  text: `Extrahiere den Inhalt des Dokuments in das angegebenen JSON Schema.
190
190
 
191
+ Vermeide es, den Titel im Untertitel zu wiederholen.
191
192
  Gib in der summary ausführlich an, welche Informationen in dem Dokument vorkommen (ohne konkrete Werte).
192
193
  Erstelle bis zu 7 möglichst spezifische Tags.
193
194
  Antworte auf deutsch.`,
@@ -346,5 +347,8 @@ DocumentManagementAiService = DocumentManagementAiService_1 = __decorate([
346
347
  ], DocumentManagementAiService);
347
348
  export { DocumentManagementAiService };
348
349
  function getDescriptiveTypeLabels(categories, prefix = 'Category: ') {
349
- return categories.flatMap((category) => [...category.types.map((type) => ({ id: type.id, label: `${prefix}${category.label} | Type: ${type.label}` }), ...getDescriptiveTypeLabels(category.children, `${prefix}${category.label} -> `))]);
350
+ return categories.flatMap((category) => [
351
+ ...category.types.map((type) => ({ id: type.id, label: `${prefix}${category.label} | Type: ${type.label}` })),
352
+ ...getDescriptiveTypeLabels(category.children, `${prefix}${category.label} -> `),
353
+ ]);
350
354
  }
@@ -1,4 +1,4 @@
1
- import { DocumentCollection, type DocumentWorkflowStep } from '../../models/index.js';
1
+ import { DocumentCollection } from '../../models/index.js';
2
2
  export type DocumentCollectionMetadata = {
3
3
  /**
4
4
  * User-friendly name of the collection
@@ -13,97 +13,16 @@ export type DocumentCollectionMetadata = {
13
13
  */
14
14
  group: string | null;
15
15
  };
16
- export declare abstract class DocumentManagementAncillaryService<Token = unknown> {
16
+ export declare abstract class DocumentManagementAncillaryService {
17
17
  #private;
18
18
  resolveMetadata<const T extends (DocumentCollection | string)[]>(...collectionsOrIds: T): Promise<{
19
19
  [K in keyof T]: DocumentCollectionMetadata;
20
20
  }>;
21
21
  resolveMetadataMap(...collectionsOrIds: (DocumentCollection | string)[]): Promise<Record<string, DocumentCollectionMetadata>>;
22
- canReadDocument(documentId: string, token?: Token): Promise<boolean>;
23
- canManageRequest(requestId: string, token?: Token): Promise<boolean>;
24
22
  /**
25
23
  * Resolves application-specific metadata for a list of document collections.
26
24
  * @param collections An array of DocumentCollection entities.
27
25
  * @returns A promise that resolves to an array of DocumentCollectionMetadata, corresponding to the input collections.
28
26
  */
29
27
  abstract _resolveMetadata(collections: DocumentCollection[]): DocumentCollectionMetadata[] | Promise<DocumentCollectionMetadata[]>;
30
- /**
31
- * Gets the subject from the request token.
32
- * @param token The token of the request
33
- */
34
- abstract getSubject(token?: Token): string | Promise<string>;
35
- /**
36
- * Checks if a user can read/view a specific document collection and its documents.
37
- * @param collectionId The ID of the document collection.
38
- * @param token The token of the request
39
- */
40
- abstract canReadCollection(collectionId: string, token?: Token): boolean | Promise<boolean>;
41
- /**
42
- * Checks if a user can create new *unassigned* documents (lands in "inbox", which then require assigning to request/collection) in a specific collection.
43
- * @param collectionId The ID of the document collection.
44
- * @param token The token of the request.
45
- */
46
- abstract canCreateDocuments(collectionId: string, token?: Token): boolean | Promise<boolean>;
47
- abstract canUpdateDocuments(collectionId: string, token?: Token): boolean | Promise<boolean>;
48
- abstract canDeleteDocuments(collectionId: string, token?: Token): boolean | Promise<boolean>;
49
- /**
50
- * Checks if a user can assign documents to requests in the collection or directly to the collection.
51
- * @param collectionId The ID of the document collection.
52
- * @param token The token of the request.
53
- */
54
- abstract canAssignDocuments(collectionId: string, token?: Token): boolean | Promise<boolean>;
55
- /**
56
- * Checks if a user can approve a specific document. This implicitly allows fulfilling document requests by approving their linked document.
57
- * This is usually a privileged action for staff members.
58
- * @param documentId The ID of the document.
59
- * @param token The token of the request
60
- * @returns A promise that resolves to true if approval is allowed, false otherwise.
61
- */
62
- abstract canApproveDocument(documentId: string, token?: Token): boolean | Promise<boolean>;
63
- /**
64
- * Checks if a user can reject a specific document.
65
- * This is usually a privileged action for staff members, often an alternative to approval.
66
- * @param documentId The ID of the document.
67
- * @param token The token of the request.
68
- * @returns A promise that resolves to true if rejection is allowed, false otherwise.
69
- */
70
- abstract canRejectDocument(documentId: string, token?: Token): boolean | Promise<boolean>;
71
- /**
72
- * Checks if a user can create, update, delete requests and assign documents to them in a collection.
73
- * @param collectionId The ID of the document collection.
74
- * @param token The token of the request
75
- */
76
- abstract canManageRequests(collectionId: string, token?: Token): boolean | Promise<boolean>;
77
- /**
78
- * Checks if a user can manage document categories, types and their properties (create, update, delete).
79
- * Typically an administrator function.
80
- * @param token The token of the request.
81
- */
82
- abstract canManageCategoriesAndTypes(token?: Token): boolean | Promise<boolean>;
83
- /**
84
- * Checks if a user can read document request templates.
85
- * @param token The token of the request.
86
- */
87
- abstract canReadDocumentRequestsTemplates(token?: Token): boolean | Promise<boolean>;
88
- /**
89
- * Checks if a user can manage document request templates (create, update, delete).
90
- * Typically an administrator function.
91
- * @param token The token of the request.
92
- */
93
- abstract canManageDocumentRequestsTemplates(token?: Token): boolean | Promise<boolean>;
94
- /**
95
- * Checks if a user can manage document validation definitions and their assignment to types.
96
- * Typically an administrator function.
97
- * @param token The token of the request.
98
- */
99
- abstract canManageValidationDefinitions(token?: Token): boolean | Promise<boolean>;
100
- /**
101
- * Checks if a user can progress a document through its workflow.
102
- * This implies reviewing the current step's output, making corrections if necessary,
103
- * and then confirming to proceed to the next phase or finalization.
104
- * @param documentId The ID of the document.
105
- * @param currentWorkflowStep The current step of the workflow the user is interacting with.
106
- * @param token The token of the request.
107
- */
108
- abstract canProgressDocumentWorkflow(documentId: string, currentWorkflowStep: DocumentWorkflowStep, token?: Token): boolean | Promise<boolean>;
109
28
  }
@@ -1,11 +1,9 @@
1
1
  import { injectRepository } from '../../../orm/server/index.js';
2
2
  import { fromEntries } from '../../../utils/object/index.js';
3
3
  import { assertDefinedPass, isString } from '../../../utils/type-guards.js';
4
- import { DocumentCollection, DocumentCollectionAssignment, DocumentRequestCollectionAssignment } from '../../models/index.js';
4
+ import { DocumentCollection } from '../../models/index.js';
5
5
  export class DocumentManagementAncillaryService {
6
6
  #documentCollectionRepository = injectRepository(DocumentCollection);
7
- #documentCollectionAssignmentRepository = injectRepository(DocumentCollectionAssignment);
8
- #documentRequestCollectionAssignmentRepository = injectRepository(DocumentRequestCollectionAssignment);
9
7
  async resolveMetadata(...collectionsOrIds) {
10
8
  if (collectionsOrIds.length == 0) {
11
9
  return [];
@@ -25,24 +23,4 @@ export class DocumentManagementAncillaryService {
25
23
  const entries = collectionsOrIds.map((collectionOrId, index) => [isString(collectionOrId) ? collectionOrId : collectionOrId.id, names[index]]);
26
24
  return fromEntries(entries);
27
25
  }
28
- async canReadDocument(documentId, token) {
29
- const assignments = await this.#documentCollectionAssignmentRepository.loadManyByQuery({ documentId });
30
- for (const assignment of assignments) {
31
- const canReadCollection = await this.canReadCollection(assignment.collectionId, token);
32
- if (canReadCollection) {
33
- return true;
34
- }
35
- }
36
- return false;
37
- }
38
- async canManageRequest(requestId, token) {
39
- const assignments = await this.#documentRequestCollectionAssignmentRepository.loadManyByQuery({ requestId });
40
- for (const assignment of assignments) {
41
- const canManageRequest = await this.canManageRequests(assignment.collectionId, token);
42
- if (!canManageRequest) {
43
- return false;
44
- }
45
- }
46
- return true;
47
- }
48
26
  }
@@ -0,0 +1,85 @@
1
+ import { type DocumentWorkflowStep } from '../../models/index.js';
2
+ export declare abstract class DocumentManagementAuthorizationService<Token = unknown> {
3
+ #private;
4
+ canReadDocument(documentId: string, token?: Token): Promise<boolean>;
5
+ canManageRequest(requestId: string, token?: Token): Promise<boolean>;
6
+ /**
7
+ * Gets the subject from the request token.
8
+ * @param token The token of the request
9
+ */
10
+ abstract getSubject(token?: Token): string | Promise<string>;
11
+ /**
12
+ * Checks if a user can read/view a specific document collection and its documents.
13
+ * @param collectionId The ID of the document collection.
14
+ * @param token The token of the request
15
+ */
16
+ abstract canReadCollection(collectionId: string, token?: Token): boolean | Promise<boolean>;
17
+ /**
18
+ * Checks if a user can create new *unassigned* documents (lands in "inbox", which then require assigning to request/collection) in a specific collection.
19
+ * @param collectionId The ID of the document collection.
20
+ * @param token The token of the request.
21
+ */
22
+ abstract canCreateDocuments(collectionId: string, token?: Token): boolean | Promise<boolean>;
23
+ abstract canUpdateDocuments(collectionId: string, token?: Token): boolean | Promise<boolean>;
24
+ abstract canDeleteDocuments(collectionId: string, token?: Token): boolean | Promise<boolean>;
25
+ /**
26
+ * Checks if a user can assign documents to requests in the collection or directly to the collection.
27
+ * @param collectionId The ID of the document collection.
28
+ * @param token The token of the request.
29
+ */
30
+ abstract canAssignDocuments(collectionId: string, token?: Token): boolean | Promise<boolean>;
31
+ /**
32
+ * Checks if a user can approve a specific document. This implicitly allows fulfilling document requests by approving their linked document.
33
+ * This is usually a privileged action for staff members.
34
+ * @param documentId The ID of the document.
35
+ * @param token The token of the request
36
+ * @returns A promise that resolves to true if approval is allowed, false otherwise.
37
+ */
38
+ abstract canApproveDocument(documentId: string, token?: Token): boolean | Promise<boolean>;
39
+ /**
40
+ * Checks if a user can reject a specific document.
41
+ * This is usually a privileged action for staff members, often an alternative to approval.
42
+ * @param documentId The ID of the document.
43
+ * @param token The token of the request.
44
+ * @returns A promise that resolves to true if rejection is allowed, false otherwise.
45
+ */
46
+ abstract canRejectDocument(documentId: string, token?: Token): boolean | Promise<boolean>;
47
+ /**
48
+ * Checks if a user can create, update, delete requests and assign documents to them in a collection.
49
+ * @param collectionId The ID of the document collection.
50
+ * @param token The token of the request
51
+ */
52
+ abstract canManageRequests(collectionId: string, token?: Token): boolean | Promise<boolean>;
53
+ /**
54
+ * Checks if a user can manage document categories, types and their properties (create, update, delete).
55
+ * Typically an administrator function.
56
+ * @param token The token of the request.
57
+ */
58
+ abstract canManageCategoriesAndTypes(token?: Token): boolean | Promise<boolean>;
59
+ /**
60
+ * Checks if a user can read document request templates.
61
+ * @param token The token of the request.
62
+ */
63
+ abstract canReadDocumentRequestsTemplates(token?: Token): boolean | Promise<boolean>;
64
+ /**
65
+ * Checks if a user can manage document request templates (create, update, delete).
66
+ * Typically an administrator function.
67
+ * @param token The token of the request.
68
+ */
69
+ abstract canManageDocumentRequestsTemplates(token?: Token): boolean | Promise<boolean>;
70
+ /**
71
+ * Checks if a user can manage document validation definitions and their assignment to types.
72
+ * Typically an administrator function.
73
+ * @param token The token of the request.
74
+ */
75
+ abstract canManageValidationDefinitions(token?: Token): boolean | Promise<boolean>;
76
+ /**
77
+ * Checks if a user can progress a document through its workflow.
78
+ * This implies reviewing the current step's output, making corrections if necessary,
79
+ * and then confirming to proceed to the next phase or finalization.
80
+ * @param documentId The ID of the document.
81
+ * @param currentWorkflowStep The current step of the workflow the user is interacting with.
82
+ * @param token The token of the request.
83
+ */
84
+ abstract canProgressDocumentWorkflow(documentId: string, currentWorkflowStep: DocumentWorkflowStep, token?: Token): boolean | Promise<boolean>;
85
+ }
@@ -0,0 +1,28 @@
1
+ import { inject } from '../../../injector/inject.js';
2
+ import { injectRepository } from '../../../orm/server/index.js';
3
+ import { DocumentRequestCollectionAssignment } from '../../models/index.js';
4
+ import { DocumentManagementService } from './document-management.service.js';
5
+ export class DocumentManagementAuthorizationService {
6
+ #documentManagementService = inject(DocumentManagementService);
7
+ #documentRequestCollectionAssignmentRepository = injectRepository(DocumentRequestCollectionAssignment);
8
+ async canReadDocument(documentId, token) {
9
+ const relevantCollectionIds = await this.#documentManagementService.getRelevantDocumentCollectionIds(documentId);
10
+ for (const collectionId of relevantCollectionIds) {
11
+ const canReadCollection = await this.canReadCollection(collectionId, token);
12
+ if (canReadCollection) {
13
+ return true;
14
+ }
15
+ }
16
+ return false;
17
+ }
18
+ async canManageRequest(requestId, token) {
19
+ const assignments = await this.#documentRequestCollectionAssignmentRepository.loadManyByQuery({ requestId });
20
+ for (const assignment of assignments) {
21
+ const canManageRequest = await this.canManageRequests(assignment.collectionId, token);
22
+ if (!canManageRequest) {
23
+ return false;
24
+ }
25
+ }
26
+ return true;
27
+ }
28
+ }
@@ -1,13 +1,21 @@
1
1
  import { Transactional } from '../../../orm/server/index.js';
2
2
  import type { Record } from '../../../types.js';
3
- import { DocumentCategory, DocumentType } from '../../models/index.js';
3
+ import { DocumentCategory, DocumentType, type DocumentProperty, type DocumentPropertyDataType } from '../../models/index.js';
4
4
  import type { DocumentManagementData, DocumentRequestsTemplateData } from '../../service-models/index.js';
5
5
  export declare class DocumentManagementService extends Transactional {
6
6
  #private;
7
+ /**
8
+ * Get all relevant document collection IDs for a given document. This includes direct assignments, request assignments, and assignment scopes.
9
+ * This method is used to determine which collections a document is associated with, either directly or through requests and assignments.
10
+ * @param documentId The ID of the document to retrieve collection IDs for.
11
+ */
12
+ getRelevantDocumentCollectionIds(documentId: string): Promise<string[]>;
13
+ loadDataStream(collectionIds: string[]): ReadableStream<DocumentManagementData>;
7
14
  loadData(collectionIds: string[]): Promise<DocumentManagementData>;
8
15
  loadDocumentRequestsTemplateData(): Promise<DocumentRequestsTemplateData>;
9
- initializeCategoriesAndTypes<CategoryKey extends string, TypeKey extends string>(categoryLabels: Record<CategoryKey, string>, categoryParents: Record<CategoryKey, CategoryKey | null>, typeLabels: Record<TypeKey, string>, typeCategories: Record<TypeKey, CategoryKey>): Promise<{
16
+ initializeCategoriesAndTypes<CategoryKey extends string, TypeKey extends string, DocumentPropertyKey extends string>(categoryLabels: Record<CategoryKey, string>, categoryParents: Record<CategoryKey, CategoryKey | null>, typeLabels: Record<TypeKey, string>, typeCategories: Record<TypeKey, CategoryKey>, propertyKeys: Record<DocumentPropertyKey, [DocumentPropertyDataType, string]>, typeProperties: Record<TypeKey, DocumentPropertyKey[]>): Promise<{
10
17
  categories: Record<CategoryKey, DocumentCategory>;
11
18
  types: Record<TypeKey, DocumentType>;
19
+ properties: Record<DocumentPropertyKey, DocumentProperty>;
12
20
  }>;
13
21
  }
@@ -4,6 +4,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
+ import { eq } from 'drizzle-orm';
8
+ import { union } from 'drizzle-orm/pg-core';
7
9
  import { Enumerable } from '../../../enumerable/index.js';
8
10
  import { inject } from '../../../injector/index.js';
9
11
  import { Transactional, injectRepository } from '../../../orm/server/index.js';
@@ -11,13 +13,16 @@ import { distinct } from '../../../utils/array/index.js';
11
13
  import { compareByValueSelectionToOrder } from '../../../utils/comparison.js';
12
14
  import { groupToMap, groupToSingleMap } from '../../../utils/iterable-helpers/index.js';
13
15
  import { fromEntries, objectEntries } from '../../../utils/object/index.js';
16
+ import { timeout } from '../../../utils/timing.js';
14
17
  import { assertDefinedPass, isDefined, isNotNull, isNotNullOrUndefined, isNull, isUndefined } from '../../../utils/type-guards.js';
15
- import { DocumentApproval, DocumentAssignmentScope, DocumentAssignmentTask, DocumentCategory, DocumentCollection, DocumentCollectionAssignment, DocumentRequest, DocumentRequestCollectionAssignment, DocumentRequestTemplate, DocumentRequestsTemplate, DocumentType, DocumentValidationExecution, DocumentWorkflowStep } from '../../models/index.js';
16
- import { DocumentCategoryTypeService, enumTypeKey } from './document-category-type.service.js';
18
+ import { DocumentApproval, DocumentAssignmentScope, DocumentAssignmentTask, DocumentCategory, DocumentCollection, DocumentCollectionAssignment, DocumentRequest, DocumentRequestCollectionAssignment, DocumentRequestTemplate, DocumentRequestsTemplate, DocumentType, DocumentTypeProperty, DocumentValidationExecution, DocumentWorkflowStep } from '../../models/index.js';
19
+ import { documentAssignmentScope, documentAssignmentTask, documentCollectionAssignment, documentRequest, documentRequestCollectionAssignment } from '../schemas.js';
20
+ import { DocumentCategoryTypeService } from './document-category-type.service.js';
17
21
  import { DocumentManagementAncillaryService } from './document-management-ancillary.service.js';
18
22
  import { DocumentPropertyService } from './document-property.service.js';
19
23
  import { DocumentWorkflowService } from './document-workflow.service.js';
20
24
  import { DocumentService } from './document.service.js';
25
+ import { enumTypeKey } from './enum-type-key.js';
21
26
  import { DocumentManagementSingleton } from './singleton.js';
22
27
  let DocumentManagementService = class DocumentManagementService extends Transactional {
23
28
  #ancillaryService = inject(DocumentManagementAncillaryService);
@@ -34,8 +39,41 @@ let DocumentManagementService = class DocumentManagementService extends Transact
34
39
  #documentRequestsTemplateRepository = injectRepository(DocumentRequestsTemplate);
35
40
  #documentRequestTemplateRepository = injectRepository(DocumentRequestTemplate);
36
41
  #documentTypeRepository = injectRepository(DocumentType);
42
+ #documentTypePropertyRepository = injectRepository(DocumentTypeProperty);
37
43
  #documentWorkflowService = inject(DocumentWorkflowService);
38
44
  #documentValidationExecutionRepository = injectRepository(DocumentValidationExecution);
45
+ /**
46
+ * Get all relevant document collection IDs for a given document. This includes direct assignments, request assignments, and assignment scopes.
47
+ * This method is used to determine which collections a document is associated with, either directly or through requests and assignments.
48
+ * @param documentId The ID of the document to retrieve collection IDs for.
49
+ */
50
+ async getRelevantDocumentCollectionIds(documentId) {
51
+ const directAssignments = this.#documentCollectionRepository.session.$with('directAssignments').as((qb) => qb
52
+ .select({ collectionId: documentCollectionAssignment.id })
53
+ .from(documentCollectionAssignment)
54
+ .where(eq(documentCollectionAssignment.documentId, documentId)));
55
+ const requestAssignments = this.#documentRequestCollectionAssignmentRepository.session.$with('requestAssignments').as((qb) => qb
56
+ .select({ collectionId: documentRequestCollectionAssignment.collectionId })
57
+ .from(documentRequest)
58
+ .innerJoin(documentRequestCollectionAssignment, eq(documentRequestCollectionAssignment.requestId, documentRequest.id))
59
+ .where(eq(documentRequest.documentId, documentId)));
60
+ const assignmentScopes = this.#documentAssignmentScopeRepository.session.$with('assignmentScopes').as((qb) => qb
61
+ .select({ collectionId: documentAssignmentScope.collectionId })
62
+ .from(documentAssignmentTask)
63
+ .innerJoin(documentAssignmentScope, eq(documentAssignmentScope.taskId, documentAssignmentTask.id))
64
+ .where(eq(documentAssignmentTask.documentId, documentId)));
65
+ const result = await union(this.#documentService.session.with(directAssignments).selectDistinct().from(directAssignments), this.#documentService.session.with(requestAssignments).selectDistinct().from(requestAssignments), this.#documentService.session.with(assignmentScopes).selectDistinct().from(assignmentScopes));
66
+ return result.map((row) => row.collectionId);
67
+ }
68
+ loadDataStream(collectionIds) {
69
+ return new ReadableStream({
70
+ pull: async (controller) => {
71
+ const data = await this.loadData(collectionIds);
72
+ controller.enqueue(data);
73
+ await timeout(1000);
74
+ },
75
+ });
76
+ }
39
77
  async loadData(collectionIds) {
40
78
  return await this.transaction(async (tx) => {
41
79
  const [collections, collectionsMetadataMap, documentCollectionAssignments, requestAssignments, assignmentScopes, categories, types] = await Promise.all([
@@ -96,7 +134,7 @@ let DocumentManagementService = class DocumentManagementService extends Transact
96
134
  });
97
135
  const requestViews = requests.map((request) => ({
98
136
  ...request,
99
- collectionIds: requestAssignments.filter((requestCollection) => requestCollection.requestId == request.id).map((requestCollection) => requestCollection.collectionId)
137
+ collectionIds: requestAssignments.filter((requestCollection) => requestCollection.requestId == request.id).map((requestCollection) => requestCollection.collectionId),
100
138
  }));
101
139
  return {
102
140
  collections: collectionViews,
@@ -120,15 +158,19 @@ let DocumentManagementService = class DocumentManagementService extends Transact
120
158
  return { templates };
121
159
  });
122
160
  }
123
- async initializeCategoriesAndTypes(categoryLabels, categoryParents, typeLabels, typeCategories) {
161
+ async initializeCategoriesAndTypes(categoryLabels, categoryParents, typeLabels, typeCategories, propertyKeys, typeProperties) {
124
162
  const categoryEntries = objectEntries(categoryLabels);
125
163
  const typeEntries = objectEntries(typeLabels);
126
- const { categoryMap, typeMap } = await this.transaction(async (tx) => {
164
+ const propertyEntries = objectEntries(propertyKeys);
165
+ const { categoryMap, typeMap, propertyMap } = await this.transaction(async (tx) => {
127
166
  const { categories: dbCategories, types: dbTypes } = await this.#documentCategoryTypeService.withTransaction(tx).loadCategoriesAndTypes();
167
+ const dbProperties = await this.#documentPropertyService.withTransaction(tx).repository.loadAll();
128
168
  const categories = dbCategories.filter((category) => isNotNullOrUndefined(category.metadata.attributes[enumTypeKey]));
129
169
  const types = dbTypes.filter((type) => isNotNullOrUndefined(type.metadata.attributes[enumTypeKey]));
170
+ const properties = dbProperties.filter((property) => isNotNullOrUndefined(property.metadata.attributes[enumTypeKey]));
130
171
  const enumKeyCategoryMap = groupToSingleMap(categories, (category) => category.metadata.attributes[enumTypeKey]);
131
172
  const enumKeyTypeMap = groupToSingleMap(types, (type) => type.metadata.attributes[enumTypeKey]);
173
+ const enumKeyPropertyMap = groupToSingleMap(properties, (property) => property.metadata.attributes[enumTypeKey]);
132
174
  for (const [key, label] of categoryEntries) {
133
175
  const category = enumKeyCategoryMap.get(key);
134
176
  const parentKey = assertDefinedPass(categoryParents[key], `Parent category not defined for ${key}`);
@@ -156,13 +198,34 @@ let DocumentManagementService = class DocumentManagementService extends Transact
156
198
  enumKeyTypeMap.set(key, updatedType);
157
199
  }
158
200
  }
159
- return { categoryMap: enumKeyCategoryMap, typeMap: enumKeyTypeMap };
201
+ for (const [key, [dataType, label]] of propertyEntries) {
202
+ const property = enumKeyPropertyMap.get(key);
203
+ if (isUndefined(property)) {
204
+ const newProperty = await this.#documentPropertyService.withTransaction(tx).createProperty(label, dataType, key);
205
+ enumKeyPropertyMap.set(key, newProperty);
206
+ }
207
+ else if ((property.label != label) || (property.dataType != dataType)) {
208
+ const updatedProperty = await this.#documentPropertyService.withTransaction(tx).updateProperty(property.id, { label, dataType });
209
+ enumKeyPropertyMap.set(key, updatedProperty);
210
+ }
211
+ }
212
+ for (const [typeKey, propertyKeys] of objectEntries(typeProperties)) {
213
+ const type = assertDefinedPass(enumKeyTypeMap.get(typeKey), `Type ${typeKey} not found.`);
214
+ const newEntities = propertyKeys.map((propertyKey) => ({
215
+ typeId: type.id,
216
+ propertyId: assertDefinedPass(enumKeyPropertyMap.get(propertyKey), 'Could not get property').id,
217
+ }));
218
+ await this.#documentTypePropertyRepository.withTransaction(tx).upsertMany(['typeId', 'propertyId'], newEntities);
219
+ }
220
+ return { categoryMap: enumKeyCategoryMap, typeMap: enumKeyTypeMap, propertyMap: enumKeyPropertyMap };
160
221
  });
161
222
  const mappedCategories = categoryEntries.map(([key]) => [key, assertDefinedPass(categoryMap.get(key), 'Could not map document category.')]);
162
223
  const mappedTypes = typeEntries.map(([key]) => [key, assertDefinedPass(typeMap.get(key), 'Could not map document type.')]);
224
+ const mappedProperties = propertyEntries.map(([key]) => [key, assertDefinedPass(propertyMap.get(key), 'Could not map document property.')]);
163
225
  return {
164
226
  categories: fromEntries(mappedCategories),
165
227
  types: fromEntries(mappedTypes),
228
+ properties: fromEntries(mappedProperties),
166
229
  };
167
230
  }
168
231
  };
@@ -1,10 +1,10 @@
1
1
  import { Transactional } from '../../../orm/server/index.js';
2
2
  import type { OneOrMany } from '../../../types.js';
3
- import { Document, DocumentProperty, DocumentPropertyDataType } from '../../models/index.js';
3
+ import { DocumentProperty, DocumentPropertyDataType } from '../../models/index.js';
4
4
  import type { DocumentPropertyValueView, SetDocumentPropertyParameters } from '../../service-models/index.js';
5
5
  export declare class DocumentPropertyService extends Transactional {
6
6
  #private;
7
- readonly repository: import("../../../orm/server/repository.js").EntityRepository<Document>;
7
+ readonly repository: import("../../../orm/server/repository.js").EntityRepository<DocumentProperty>;
8
8
  readonly documentProperties: import("drizzle-orm/pg-core").WithSubqueryWithSelection<{
9
9
  documentId: import("drizzle-orm").SQL.Aliased<string>;
10
10
  propertyId: import("drizzle-orm").SQL.Aliased<string>;
@@ -44,7 +44,11 @@ export declare class DocumentPropertyService extends Transactional {
44
44
  }, {}, {}>;
45
45
  value: import("drizzle-orm").SQL.Aliased<string | number | boolean | null>;
46
46
  }, "documentProperties">;
47
- createProperty(label: string, dataType: DocumentPropertyDataType): Promise<DocumentProperty>;
47
+ createProperty(label: string, dataType: DocumentPropertyDataType, enumKey?: string): Promise<DocumentProperty>;
48
+ updateProperty(id: string, update: {
49
+ label?: string;
50
+ dataType?: DocumentPropertyDataType;
51
+ }): Promise<DocumentProperty>;
48
52
  assignPropertyToType(typeId: string, propertyId: string): Promise<void>;
49
53
  loadDocumentProperties(documentId: OneOrMany<string>, includeNulls?: boolean): Promise<DocumentPropertyValueView[]>;
50
54
  setPropertyValues(documentId: string, propertyValues: SetDocumentPropertyParameters[]): Promise<void>;
@@ -11,8 +11,9 @@ import { Transactional } from '../../../orm/server/index.js';
11
11
  import { injectRepository } from '../../../orm/server/repository.js';
12
12
  import { toArray } from '../../../utils/array/index.js';
13
13
  import { assertBooleanPass, assertDefinedPass, assertNumberPass, assertStringPass, isBoolean, isNotNull, isNull, isNumber, isString } from '../../../utils/type-guards.js';
14
- import { Document, DocumentProperty, DocumentPropertyDataType, DocumentPropertyValue, DocumentTypeProperty } from '../../models/index.js';
14
+ import { DocumentProperty, DocumentPropertyDataType, DocumentPropertyValue, DocumentTypeProperty } from '../../models/index.js';
15
15
  import { document, documentProperty, documentPropertyValue, documentType, documentTypeProperty } from '../schemas.js';
16
+ import { enumTypeKey } from './enum-type-key.js';
16
17
  import { DocumentManagementSingleton } from './singleton.js';
17
18
  const documentPropertyValueValidators = {
18
19
  [DocumentPropertyDataType.Text]: (value) => isString(value) || isNull(value),
@@ -25,7 +26,7 @@ let DocumentPropertyService = class DocumentPropertyService extends Transactiona
25
26
  #documentPropertyRepository = injectRepository(DocumentProperty);
26
27
  #documentPropertyValueRepository = injectRepository(DocumentPropertyValue);
27
28
  #documentTypePropertyRepository = injectRepository(DocumentTypeProperty);
28
- repository = injectRepository(Document).withSession(this.session);
29
+ repository = injectRepository(DocumentProperty).withSession(this.session);
29
30
  documentProperties = this.session.$with('documentProperties').as((qb) => qb
30
31
  .select({
31
32
  documentId: autoAlias(document.id),
@@ -39,8 +40,11 @@ let DocumentPropertyService = class DocumentPropertyService extends Transactiona
39
40
  .innerJoin(documentTypeProperty, eq(documentTypeProperty.typeId, documentType.id))
40
41
  .innerJoin(documentProperty, eq(documentProperty.id, documentTypeProperty.propertyId))
41
42
  .leftJoin(documentPropertyValue, and(eq(documentPropertyValue.documentId, document.id), eq(documentPropertyValue.propertyId, documentProperty.id))));
42
- async createProperty(label, dataType) {
43
- return await this.#documentPropertyRepository.insert({ label, dataType });
43
+ async createProperty(label, dataType, enumKey) {
44
+ return await this.#documentPropertyRepository.insert({ label, dataType, metadata: { attributes: { [enumTypeKey]: enumKey } } });
45
+ }
46
+ async updateProperty(id, update) {
47
+ return await this.#documentPropertyRepository.update(id, update);
44
48
  }
45
49
  async assignPropertyToType(typeId, propertyId) {
46
50
  await this.#documentTypePropertyRepository.insert({ typeId, propertyId });