@fjell/core 4.4.48 → 4.4.50

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 (94) hide show
  1. package/README.md +92 -0
  2. package/dist/Coordinate.d.ts +7 -0
  3. package/dist/errors/ActionError.d.ts +51 -0
  4. package/dist/errors/BusinessLogicError.d.ts +4 -0
  5. package/dist/errors/DuplicateError.d.ts +4 -0
  6. package/dist/errors/NotFoundError.d.ts +4 -0
  7. package/dist/errors/PermissionError.d.ts +4 -0
  8. package/dist/errors/ValidationError.d.ts +4 -0
  9. package/dist/errors/index.d.ts +6 -0
  10. package/dist/event/emitter.d.ts +140 -0
  11. package/dist/event/events.d.ts +81 -0
  12. package/dist/event/index.d.ts +38 -0
  13. package/dist/event/matching.d.ts +54 -0
  14. package/dist/event/subscription.d.ts +74 -0
  15. package/dist/event/types.d.ts +186 -0
  16. package/dist/index.d.ts +13 -0
  17. package/dist/index.js +1584 -47
  18. package/dist/item/IUtils.d.ts +6 -3
  19. package/dist/operations/OperationContext.d.ts +10 -0
  20. package/dist/operations/Operations.d.ts +259 -0
  21. package/dist/operations/contained.d.ts +65 -0
  22. package/dist/operations/errorEnhancer.d.ts +79 -0
  23. package/dist/operations/index.d.ts +2 -0
  24. package/dist/operations/methods.d.ts +134 -0
  25. package/dist/operations/primary.d.ts +57 -0
  26. package/dist/operations/specialized.d.ts +41 -0
  27. package/dist/operations/wrappers/createActionWrapper.d.ts +28 -0
  28. package/dist/operations/wrappers/createAllActionWrapper.d.ts +28 -0
  29. package/dist/operations/wrappers/createAllFacetWrapper.d.ts +27 -0
  30. package/dist/operations/wrappers/createAllWrapper.d.ts +28 -0
  31. package/dist/operations/wrappers/createCreateWrapper.d.ts +28 -0
  32. package/dist/operations/wrappers/createFacetWrapper.d.ts +27 -0
  33. package/dist/operations/wrappers/createFindOneWrapper.d.ts +28 -0
  34. package/dist/operations/wrappers/createFindWrapper.d.ts +28 -0
  35. package/dist/operations/wrappers/createGetWrapper.d.ts +28 -0
  36. package/dist/operations/wrappers/createOneWrapper.d.ts +38 -0
  37. package/dist/operations/wrappers/createRemoveWrapper.d.ts +28 -0
  38. package/dist/operations/wrappers/createUpdateWrapper.d.ts +28 -0
  39. package/dist/operations/wrappers/createUpsertWrapper.d.ts +28 -0
  40. package/dist/operations/wrappers/index.d.ts +34 -0
  41. package/dist/operations/wrappers/types.d.ts +48 -0
  42. package/dist/validation/ItemValidator.d.ts +43 -0
  43. package/dist/validation/KeyValidator.d.ts +56 -0
  44. package/dist/validation/LocationValidator.d.ts +39 -0
  45. package/dist/validation/QueryValidator.d.ts +57 -0
  46. package/dist/validation/index.d.ts +15 -0
  47. package/dist/validation/index.js +501 -0
  48. package/dist/validation/types.d.ts +38 -0
  49. package/package.json +7 -2
  50. package/src/Coordinate.ts +35 -0
  51. package/src/errors/ActionError.ts +69 -0
  52. package/src/errors/BusinessLogicError.ts +24 -0
  53. package/src/errors/DuplicateError.ts +57 -0
  54. package/src/errors/NotFoundError.ts +24 -0
  55. package/src/errors/PermissionError.ts +31 -0
  56. package/src/errors/ValidationError.ts +27 -0
  57. package/src/errors/index.ts +7 -0
  58. package/src/event/emitter.ts +247 -0
  59. package/src/event/events.ts +178 -0
  60. package/src/event/index.ts +130 -0
  61. package/src/event/matching.ts +264 -0
  62. package/src/event/subscription.ts +181 -0
  63. package/src/event/types.ts +282 -0
  64. package/src/index.ts +57 -0
  65. package/src/item/IUtils.ts +9 -80
  66. package/src/operations/OperationContext.ts +12 -0
  67. package/src/operations/Operations.ts +357 -0
  68. package/src/operations/contained.ts +134 -0
  69. package/src/operations/errorEnhancer.ts +204 -0
  70. package/src/operations/index.ts +2 -0
  71. package/src/operations/methods.ts +363 -0
  72. package/src/operations/primary.ts +101 -0
  73. package/src/operations/specialized.ts +71 -0
  74. package/src/operations/wrappers/createActionWrapper.ts +108 -0
  75. package/src/operations/wrappers/createAllActionWrapper.ts +109 -0
  76. package/src/operations/wrappers/createAllFacetWrapper.ts +98 -0
  77. package/src/operations/wrappers/createAllWrapper.ts +103 -0
  78. package/src/operations/wrappers/createCreateWrapper.ts +117 -0
  79. package/src/operations/wrappers/createFacetWrapper.ts +97 -0
  80. package/src/operations/wrappers/createFindOneWrapper.ts +105 -0
  81. package/src/operations/wrappers/createFindWrapper.ts +105 -0
  82. package/src/operations/wrappers/createGetWrapper.ts +96 -0
  83. package/src/operations/wrappers/createOneWrapper.ts +128 -0
  84. package/src/operations/wrappers/createRemoveWrapper.ts +91 -0
  85. package/src/operations/wrappers/createUpdateWrapper.ts +106 -0
  86. package/src/operations/wrappers/createUpsertWrapper.ts +108 -0
  87. package/src/operations/wrappers/index.ts +39 -0
  88. package/src/operations/wrappers/types.ts +63 -0
  89. package/src/validation/ItemValidator.ts +131 -0
  90. package/src/validation/KeyValidator.ts +365 -0
  91. package/src/validation/LocationValidator.ts +136 -0
  92. package/src/validation/QueryValidator.ts +250 -0
  93. package/src/validation/index.ts +32 -0
  94. package/src/validation/types.ts +45 -0
@@ -0,0 +1,357 @@
1
+ import { Item } from "../items";
2
+ import { ComKey, LocKeyArray, PriKey } from "../keys";
3
+ import { ItemQuery } from "../item/ItemQuery";
4
+
5
+ /**
6
+ * Standard operation parameters type
7
+ */
8
+ export type OperationParams = Record<
9
+ string,
10
+ string | number | boolean | Date | Array<string | number | boolean | Date>
11
+ >;
12
+
13
+ /**
14
+ * Type for affected keys returned by actions
15
+ */
16
+ export type AffectedKeys = Array<
17
+ PriKey<any> | ComKey<any, any, any, any, any, any> | LocKeyArray<any, any, any, any, any>
18
+ >;
19
+
20
+ /**
21
+ * Options for create operation
22
+ */
23
+ export type CreateOptions<
24
+ S extends string,
25
+ L1 extends string = never,
26
+ L2 extends string = never,
27
+ L3 extends string = never,
28
+ L4 extends string = never,
29
+ L5 extends string = never
30
+ > = {
31
+ key: PriKey<S> | ComKey<S, L1, L2, L3, L4, L5>,
32
+ locations?: never;
33
+ } | {
34
+ key?: never;
35
+ locations: LocKeyArray<L1, L2, L3, L4, L5>,
36
+ };
37
+
38
+ /**
39
+ * Core Operations interface for Item-based data access.
40
+ * This interface defines the standard contract for all fjell libraries
41
+ * that operate on Items.
42
+ *
43
+ * @template V - The Item type
44
+ * @template S - The primary key type
45
+ * @template L1-L5 - Location key types (optional, for contained items)
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * // Primary item operations
50
+ * const userOps: Operations<User, 'user'> = ...;
51
+ * const users = await userOps.all();
52
+ *
53
+ * // Contained item operations
54
+ * const commentOps: Operations<Comment, 'comment', 'post'> = ...;
55
+ * const comments = await commentOps.all({}, [{kt: 'post', lk: 'post-123'}]);
56
+ * ```
57
+ */
58
+ export interface Operations<
59
+ V extends Item<S, L1, L2, L3, L4, L5>,
60
+ S extends string,
61
+ L1 extends string = never,
62
+ L2 extends string = never,
63
+ L3 extends string = never,
64
+ L4 extends string = never,
65
+ L5 extends string = never,
66
+ > {
67
+ /**
68
+ * Retrieves all items matching the query.
69
+ *
70
+ * @param query - Optional query to filter items
71
+ * @param locations - Optional location hierarchy to scope the query
72
+ * @returns Array of items matching the query
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * // Get all users
77
+ * const users = await operations.all();
78
+ *
79
+ * // Get users with filter
80
+ * const activeUsers = await operations.all({ filter: { status: 'active' } });
81
+ *
82
+ * // Get items in specific location
83
+ * const comments = await operations.all({}, [{kt: 'post', lk: 'post-123'}]);
84
+ * ```
85
+ */
86
+ all(
87
+ query?: ItemQuery,
88
+ locations?: LocKeyArray<L1, L2, L3, L4, L5> | []
89
+ ): Promise<V[]>;
90
+
91
+ /**
92
+ * Retrieves the first item matching the query.
93
+ *
94
+ * @param query - Optional query to filter items
95
+ * @param locations - Optional location hierarchy to scope the query
96
+ * @returns Single item or null if not found
97
+ */
98
+ one(
99
+ query?: ItemQuery,
100
+ locations?: LocKeyArray<L1, L2, L3, L4, L5> | []
101
+ ): Promise<V | null>;
102
+
103
+ /**
104
+ * Creates a new item.
105
+ *
106
+ * @param item - Partial item properties for creation
107
+ * @param options - Optional key or locations for the new item
108
+ * @returns The created item
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * // Create with auto-generated key
113
+ * const user = await operations.create({ name: 'Alice' });
114
+ *
115
+ * // Create with specific key
116
+ * const user = await operations.create(
117
+ * { name: 'Alice' },
118
+ * { key: { kt: 'user', pk: 'alice-123' } }
119
+ * );
120
+ *
121
+ * // Create in specific location
122
+ * const comment = await operations.create(
123
+ * { text: 'Great post!' },
124
+ * { locations: [{kt: 'post', lk: 'post-123'}] }
125
+ * );
126
+ * ```
127
+ */
128
+ create(
129
+ item: Partial<Item<S, L1, L2, L3, L4, L5>>,
130
+ options?: CreateOptions<S, L1, L2, L3, L4, L5>
131
+ ): Promise<V>;
132
+
133
+ /**
134
+ * Retrieves a single item by its key.
135
+ *
136
+ * @param key - Primary or composite key
137
+ * @returns The item or null if not found
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * // Get by primary key
142
+ * const user = await operations.get({ kt: 'user', pk: 'user-123' });
143
+ *
144
+ * // Get by composite key
145
+ * const comment = await operations.get({
146
+ * kt: 'comment',
147
+ * pk: 'comment-456',
148
+ * loc: [{kt: 'post', lk: 'post-123'}]
149
+ * });
150
+ * ```
151
+ */
152
+ get(
153
+ key: PriKey<S> | ComKey<S, L1, L2, L3, L4, L5>
154
+ ): Promise<V | null>;
155
+
156
+ /**
157
+ * Updates an existing item.
158
+ *
159
+ * @param key - Primary or composite key
160
+ * @param item - Partial item properties to update
161
+ * @returns The updated item
162
+ */
163
+ update(
164
+ key: PriKey<S> | ComKey<S, L1, L2, L3, L4, L5>,
165
+ item: Partial<Item<S, L1, L2, L3, L4, L5>>
166
+ ): Promise<V>;
167
+
168
+ /**
169
+ * Updates an item if it exists, creates it if it doesn't.
170
+ *
171
+ * @param key - Primary or composite key
172
+ * @param item - Partial item properties
173
+ * @param locations - Optional locations for creation
174
+ * @returns The upserted item
175
+ */
176
+ upsert(
177
+ key: PriKey<S> | ComKey<S, L1, L2, L3, L4, L5>,
178
+ item: Partial<Item<S, L1, L2, L3, L4, L5>>,
179
+ locations?: LocKeyArray<L1, L2, L3, L4, L5>
180
+ ): Promise<V>;
181
+
182
+ /**
183
+ * Removes an item.
184
+ *
185
+ * @param key - Primary or composite key
186
+ * @returns The removed item or void
187
+ */
188
+ remove(
189
+ key: PriKey<S> | ComKey<S, L1, L2, L3, L4, L5>
190
+ ): Promise<V | void>;
191
+
192
+ /**
193
+ * Executes a finder method by name.
194
+ *
195
+ * @param finder - Name of the finder method
196
+ * @param params - Parameters for the finder
197
+ * @param locations - Optional location hierarchy to scope the query
198
+ * @returns Array of items found
199
+ *
200
+ * @example
201
+ * ```typescript
202
+ * // Find users by email
203
+ * const users = await operations.find('byEmail', { email: 'alice@example.com' });
204
+ *
205
+ * // Find in specific location
206
+ * const comments = await operations.find(
207
+ * 'byAuthor',
208
+ * { author: 'alice' },
209
+ * [{kt: 'post', lk: 'post-123'}]
210
+ * );
211
+ * ```
212
+ */
213
+ find(
214
+ finder: string,
215
+ params?: OperationParams,
216
+ locations?: LocKeyArray<L1, L2, L3, L4, L5> | []
217
+ ): Promise<V[]>;
218
+
219
+ /**
220
+ * Executes a finder method and returns the first result.
221
+ *
222
+ * @param finder - Name of the finder method
223
+ * @param params - Parameters for the finder
224
+ * @param locations - Optional location hierarchy to scope the query
225
+ * @returns Single item or null
226
+ */
227
+ findOne(
228
+ finder: string,
229
+ params?: OperationParams,
230
+ locations?: LocKeyArray<L1, L2, L3, L4, L5> | []
231
+ ): Promise<V | null>;
232
+
233
+ /**
234
+ * Executes an action on a specific item.
235
+ * Actions are operations that may have side effects or modify the item.
236
+ *
237
+ * @param key - Primary or composite key
238
+ * @param action - Name of the action
239
+ * @param params - Parameters for the action
240
+ * @returns Tuple of [updated item, affected keys for cache invalidation]
241
+ *
242
+ * @example
243
+ * ```typescript
244
+ * const [user, affectedKeys] = await operations.action(
245
+ * { kt: 'user', pk: 'user-123' },
246
+ * 'promote',
247
+ * { role: 'admin' }
248
+ * );
249
+ * ```
250
+ */
251
+ action(
252
+ key: PriKey<S> | ComKey<S, L1, L2, L3, L4, L5>,
253
+ action: string,
254
+ params?: OperationParams
255
+ ): Promise<[V, AffectedKeys]>;
256
+
257
+ /**
258
+ * Executes an action on all items matching criteria.
259
+ *
260
+ * @param action - Name of the action
261
+ * @param params - Parameters for the action
262
+ * @param locations - Optional location hierarchy to scope the action
263
+ * @returns Tuple of [updated items, affected keys for cache invalidation]
264
+ */
265
+ allAction(
266
+ action: string,
267
+ params?: OperationParams,
268
+ locations?: LocKeyArray<L1, L2, L3, L4, L5> | []
269
+ ): Promise<[V[], AffectedKeys]>;
270
+
271
+ /**
272
+ * Executes a facet query on a specific item.
273
+ * Facets are read-only computed views of item data.
274
+ *
275
+ * @param key - Primary or composite key
276
+ * @param facet - Name of the facet
277
+ * @param params - Parameters for the facet
278
+ * @returns Facet result data
279
+ *
280
+ * @example
281
+ * ```typescript
282
+ * const stats = await operations.facet(
283
+ * { kt: 'user', pk: 'user-123' },
284
+ * 'statistics',
285
+ * { period: 'month' }
286
+ * );
287
+ * ```
288
+ */
289
+ facet(
290
+ key: PriKey<S> | ComKey<S, L1, L2, L3, L4, L5>,
291
+ facet: string,
292
+ params?: OperationParams
293
+ ): Promise<any>;
294
+
295
+ /**
296
+ * Executes a facet query on all items matching criteria.
297
+ * Facets are read-only computed views of item data.
298
+ *
299
+ * @param facet - Name of the facet
300
+ * @param params - Parameters for the facet
301
+ * @param locations - Optional location hierarchy to scope the query
302
+ * @returns Facet result data
303
+ */
304
+ allFacet(
305
+ facet: string,
306
+ params?: OperationParams,
307
+ locations?: LocKeyArray<L1, L2, L3, L4, L5> | []
308
+ ): Promise<any>;
309
+ }
310
+
311
+ /**
312
+ * Type guard to check if key is a PriKey
313
+ *
314
+ * @param key - Key to check
315
+ * @returns true if key is a PriKey
316
+ *
317
+ * @example
318
+ * ```typescript
319
+ * if (isPriKey(key)) {
320
+ * // key is PriKey<S>
321
+ * console.log(key.kt, key.pk);
322
+ * }
323
+ * ```
324
+ */
325
+ export function isPriKey<S extends string>(
326
+ key: PriKey<S> | ComKey<S, any, any, any, any, any>
327
+ ): key is PriKey<S> {
328
+ return !('loc' in key) || !key.loc;
329
+ }
330
+
331
+ /**
332
+ * Type guard to check if key is a ComKey
333
+ *
334
+ * @param key - Key to check
335
+ * @returns true if key is a ComKey
336
+ *
337
+ * @example
338
+ * ```typescript
339
+ * if (isComKey(key)) {
340
+ * // key is ComKey<S, L1, L2, L3, L4, L5>
341
+ * console.log(key.kt, key.pk, key.loc);
342
+ * }
343
+ * ```
344
+ */
345
+ export function isComKey<
346
+ S extends string,
347
+ L1 extends string = never,
348
+ L2 extends string = never,
349
+ L3 extends string = never,
350
+ L4 extends string = never,
351
+ L5 extends string = never
352
+ >(
353
+ key: PriKey<S> | ComKey<S, L1, L2, L3, L4, L5>
354
+ ): key is ComKey<S, L1, L2, L3, L4, L5> {
355
+ return 'loc' in key && key.loc && Array.isArray(key.loc) && key.loc.length > 0;
356
+ }
357
+
@@ -0,0 +1,134 @@
1
+ import { Item } from "../items";
2
+ import { ComKey, LocKeyArray } from "../keys";
3
+ import { ItemQuery } from "../item/ItemQuery";
4
+ import { AffectedKeys, OperationParams, Operations } from "./Operations";
5
+
6
+ /**
7
+ * Contained Operations interface - specialized for contained (hierarchical) items only.
8
+ *
9
+ * This interface narrows the generic Operations interface to work exclusively
10
+ * with ComKey and requires locations for collection operations.
11
+ *
12
+ * Contained items exist within a location hierarchy and belong to parent items.
13
+ * Examples: Comments (in Posts), Annotations (in Documents), Tasks (in Projects)
14
+ *
15
+ * Use this for:
16
+ * - Libraries that store contained items
17
+ * - Caches for contained items
18
+ * - API clients for contained endpoints
19
+ * - Any operations that work with hierarchical data
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * // Define a contained item type
24
+ * interface Comment extends Item<'comment', 'post'> {
25
+ * text: string;
26
+ * author: string;
27
+ * }
28
+ *
29
+ * // Create operations for contained items
30
+ * const commentOps: ContainedOperations<Comment, 'comment', 'post'> = createCommentOperations();
31
+ *
32
+ * // Only ComKey allowed - includes location hierarchy
33
+ * await commentOps.get({
34
+ * kt: 'comment',
35
+ * pk: '123',
36
+ * loc: [{ kt: 'post', lk: 'post-1' }]
37
+ * });
38
+ *
39
+ * await commentOps.update(
40
+ * { kt: 'comment', pk: '123', loc: [{ kt: 'post', lk: 'post-1' }] },
41
+ * { text: 'Updated comment' }
42
+ * );
43
+ *
44
+ * // Locations required on collection methods
45
+ * await commentOps.all({}, [{ kt: 'post', lk: 'post-1' }]);
46
+ * await commentOps.find('recent', {}, [{ kt: 'post', lk: 'post-1' }]);
47
+ * await commentOps.allAction('archive', {}, [{ kt: 'post', lk: 'post-1' }]);
48
+ * ```
49
+ */
50
+ export interface ContainedOperations<
51
+ V extends Item<S, L1, L2, L3, L4, L5>,
52
+ S extends string,
53
+ L1 extends string,
54
+ L2 extends string = never,
55
+ L3 extends string = never,
56
+ L4 extends string = never,
57
+ L5 extends string = never
58
+ > extends Omit<Operations<V, S, L1, L2, L3, L4, L5>,
59
+ 'get' | 'update' | 'remove' | 'upsert' | 'create' |
60
+ 'action' | 'facet' | 'allAction' | 'allFacet' |
61
+ 'all' | 'one' | 'find' | 'findOne'> {
62
+
63
+ // Collection query operations - locations required
64
+ all(
65
+ query: ItemQuery | undefined,
66
+ locations: LocKeyArray<L1, L2, L3, L4, L5> | []
67
+ ): Promise<V[]>;
68
+
69
+ one(
70
+ query: ItemQuery | undefined,
71
+ locations: LocKeyArray<L1, L2, L3, L4, L5> | []
72
+ ): Promise<V | null>;
73
+
74
+ // Finder operations - locations required
75
+ find(
76
+ finder: string,
77
+ params: OperationParams | undefined,
78
+ locations: LocKeyArray<L1, L2, L3, L4, L5> | []
79
+ ): Promise<V[]>;
80
+
81
+ findOne(
82
+ finder: string,
83
+ params: OperationParams | undefined,
84
+ locations: LocKeyArray<L1, L2, L3, L4, L5> | []
85
+ ): Promise<V | null>;
86
+
87
+ // Create operation - locations for context
88
+ create(
89
+ item: Partial<Item<S, L1, L2, L3, L4, L5>>,
90
+ options?: { locations?: LocKeyArray<L1, L2, L3, L4, L5> }
91
+ ): Promise<V>;
92
+
93
+ // CRUD operations - ComKey only
94
+ get(key: ComKey<S, L1, L2, L3, L4, L5>): Promise<V | null>;
95
+
96
+ update(
97
+ key: ComKey<S, L1, L2, L3, L4, L5>,
98
+ item: Partial<Item<S, L1, L2, L3, L4, L5>>
99
+ ): Promise<V>;
100
+
101
+ upsert(
102
+ key: ComKey<S, L1, L2, L3, L4, L5>,
103
+ item: Partial<Item<S, L1, L2, L3, L4, L5>>
104
+ ): Promise<V>;
105
+
106
+ remove(key: ComKey<S, L1, L2, L3, L4, L5>): Promise<V | void>;
107
+
108
+ // Action operations - ComKey for instance, locations for collections
109
+ action(
110
+ key: ComKey<S, L1, L2, L3, L4, L5>,
111
+ action: string,
112
+ params?: OperationParams
113
+ ): Promise<[V, AffectedKeys]>;
114
+
115
+ allAction(
116
+ action: string,
117
+ params: OperationParams | undefined,
118
+ locations: LocKeyArray<L1, L2, L3, L4, L5> | []
119
+ ): Promise<[V[], AffectedKeys]>;
120
+
121
+ // Facet operations - ComKey for instance, locations for collections
122
+ facet(
123
+ key: ComKey<S, L1, L2, L3, L4, L5>,
124
+ facet: string,
125
+ params?: OperationParams
126
+ ): Promise<any>;
127
+
128
+ allFacet(
129
+ facet: string,
130
+ params: OperationParams | undefined,
131
+ locations: LocKeyArray<L1, L2, L3, L4, L5> | []
132
+ ): Promise<any>;
133
+ }
134
+
@@ -0,0 +1,204 @@
1
+ import { ActionError, ErrorInfo } from "../errors/ActionError";
2
+ import { OperationContext } from "./OperationContext";
3
+ import { ComKey, PriKey } from "../keys";
4
+
5
+ /**
6
+ * Executes an async operation and enhances any ActionError thrown with operation context.
7
+ *
8
+ * This is the primary utility for wrapping operations across all fjell packages.
9
+ *
10
+ * @param operation - The async operation to execute
11
+ * @param context - The operation context to add to any errors
12
+ * @returns The result of the operation
13
+ * @throws Enhanced ActionError or original error
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // In fjell/lib
18
+ * return executeWithContext(
19
+ * () => toWrap.get(key),
20
+ * {
21
+ * itemType: 'user',
22
+ * operationType: 'get',
23
+ * operationName: 'get',
24
+ * params: { key },
25
+ * key
26
+ * }
27
+ * );
28
+ *
29
+ * // In express-router
30
+ * return executeWithContext(
31
+ * () => operations.action('approve', params, key),
32
+ * {
33
+ * itemType: 'invoice',
34
+ * operationType: 'action',
35
+ * operationName: 'approve',
36
+ * params,
37
+ * key
38
+ * }
39
+ * );
40
+ * ```
41
+ */
42
+ export async function executeWithContext<T>(
43
+ operation: () => Promise<T>,
44
+ context: OperationContext
45
+ ): Promise<T> {
46
+ try {
47
+ return await operation();
48
+ } catch (error) {
49
+ throw enhanceError(error as Error, context);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Synchronous version of executeWithContext for non-async operations.
55
+ *
56
+ * @param operation - The sync operation to execute
57
+ * @param context - The operation context to add to any errors
58
+ * @returns The result of the operation
59
+ * @throws Enhanced ActionError or original error
60
+ */
61
+ export function executeWithContextSync<T>(
62
+ operation: () => T,
63
+ context: OperationContext
64
+ ): T {
65
+ try {
66
+ return operation();
67
+ } catch (error) {
68
+ throw enhanceError(error as Error, context);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Enhances an error with operation context if it's an ActionError.
74
+ * If it's not an ActionError, returns the original error unchanged.
75
+ *
76
+ * This allows database implementations and business logic to throw
77
+ * ActionErrors with minimal context, and have that context filled in
78
+ * by the operation wrapper.
79
+ *
80
+ * @param error - The error to enhance
81
+ * @param context - The operation context to add
82
+ * @returns The enhanced or original error
83
+ */
84
+ export function enhanceError(error: Error, context: OperationContext): Error {
85
+ if (!(error instanceof ActionError)) {
86
+ // Not an ActionError, return as-is
87
+ return error;
88
+ }
89
+
90
+ // Fill in operation context
91
+ error.errorInfo.operation = {
92
+ type: context.operationType,
93
+ name: context.operationName,
94
+ params: context.params
95
+ };
96
+
97
+ // Fill in item type (allow override if already set)
98
+ if (!error.errorInfo.context.itemType) {
99
+ error.errorInfo.context.itemType = context.itemType;
100
+ }
101
+
102
+ // Add key context if available and not already set (or if existing key has no meaningful data)
103
+ if (context.key) {
104
+ const existingKey = error.errorInfo.context.key;
105
+ const hasNoPrimaryKey = !existingKey || typeof existingKey.primary === 'undefined';
106
+ const hasNoCompositeKey = !existingKey || !existingKey.composite;
107
+ const shouldOverride = hasNoPrimaryKey && hasNoCompositeKey;
108
+
109
+ if (shouldOverride) {
110
+ error.errorInfo.context.key = extractKeyInfo(context.key);
111
+ }
112
+ }
113
+
114
+ // Add location context if available and not already set
115
+ if (context.locations && context.locations.length > 0 && !error.errorInfo.context.parentLocation) {
116
+ error.errorInfo.context.parentLocation = {
117
+ id: context.locations[0].lk,
118
+ type: context.locations[0].kt
119
+ };
120
+ }
121
+
122
+ return error;
123
+ }
124
+
125
+ /**
126
+ * Extracts key information from a PriKey or ComKey for error context.
127
+ *
128
+ * @param key - The key to extract information from
129
+ * @returns The extracted key information
130
+ */
131
+ function extractKeyInfo(
132
+ key: PriKey<any> | ComKey<any, any, any, any, any, any>
133
+ ): NonNullable<ErrorInfo['context']['key']> {
134
+ // Check if it's a ComKey (has loc property)
135
+ if ('loc' in key) {
136
+ // Composite key - convert from ComKey format to error info format
137
+ const ktaArray = Array.isArray(key.kt) ? key.kt : [key.kt];
138
+ const locations = Array.isArray(key.loc) ? key.loc : [];
139
+
140
+ return {
141
+ composite: {
142
+ sk: key.pk,
143
+ kta: ktaArray,
144
+ locations: locations.map(loc => ({
145
+ lk: loc.lk,
146
+ kt: loc.kt
147
+ }))
148
+ }
149
+ };
150
+ } else if ('pk' in key) {
151
+ // Primary key
152
+ return { primary: key.pk };
153
+ }
154
+
155
+ // Fallback for unknown key structure
156
+ return { primary: JSON.stringify(key) };
157
+ }
158
+
159
+ /**
160
+ * Type guard to check if an error is an ActionError.
161
+ *
162
+ * @param error - The error to check
163
+ * @returns True if the error is an ActionError
164
+ */
165
+ export function isActionError(error: unknown): error is ActionError {
166
+ return error instanceof ActionError;
167
+ }
168
+
169
+ /**
170
+ * Extracts error info from an ActionError, or creates a generic error info
171
+ * for non-ActionErrors.
172
+ *
173
+ * Useful for logging and error reporting.
174
+ *
175
+ * @param error - The error to extract info from
176
+ * @returns The error info
177
+ */
178
+ export function getErrorInfo(error: unknown): any {
179
+ if (isActionError(error)) {
180
+ return error.toJSON();
181
+ }
182
+
183
+ // For non-ActionErrors, create a minimal error info
184
+ if (error instanceof Error) {
185
+ return {
186
+ code: 'UNKNOWN_ERROR',
187
+ message: error.message,
188
+ technical: {
189
+ timestamp: new Date().toISOString(),
190
+ stackTrace: error.stack
191
+ }
192
+ };
193
+ }
194
+
195
+ // For non-Error objects
196
+ return {
197
+ code: 'UNKNOWN_ERROR',
198
+ message: String(error),
199
+ technical: {
200
+ timestamp: new Date().toISOString()
201
+ }
202
+ };
203
+ }
204
+
@@ -0,0 +1,2 @@
1
+ export * from './OperationContext';
2
+ export * from './errorEnhancer';