@furystack/repository 10.1.10 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +74 -36
  3. package/esm/data-set.spec.d.ts +2 -0
  4. package/esm/data-set.spec.d.ts.map +1 -0
  5. package/esm/data-set.spec.js +348 -0
  6. package/esm/data-set.spec.js.map +1 -0
  7. package/esm/define-data-set.d.ts +89 -0
  8. package/esm/define-data-set.d.ts.map +1 -0
  9. package/esm/define-data-set.js +75 -0
  10. package/esm/define-data-set.js.map +1 -0
  11. package/esm/define-data-set.spec.d.ts +2 -0
  12. package/esm/define-data-set.spec.d.ts.map +1 -0
  13. package/esm/define-data-set.spec.js +134 -0
  14. package/esm/define-data-set.spec.js.map +1 -0
  15. package/esm/helpers.d.ts +21 -22
  16. package/esm/helpers.d.ts.map +1 -1
  17. package/esm/helpers.js +17 -21
  18. package/esm/helpers.js.map +1 -1
  19. package/esm/index.d.ts +2 -2
  20. package/esm/index.d.ts.map +1 -1
  21. package/esm/index.js +2 -2
  22. package/esm/index.js.map +1 -1
  23. package/package.json +6 -6
  24. package/src/data-set.spec.ts +396 -0
  25. package/src/define-data-set.spec.ts +145 -0
  26. package/src/define-data-set.ts +140 -0
  27. package/src/helpers.ts +23 -26
  28. package/src/index.ts +2 -2
  29. package/esm/dataset.spec.d.ts +0 -2
  30. package/esm/dataset.spec.d.ts.map +0 -1
  31. package/esm/dataset.spec.js +0 -552
  32. package/esm/dataset.spec.js.map +0 -1
  33. package/esm/repository.d.ts +0 -15
  34. package/esm/repository.d.ts.map +0 -1
  35. package/esm/repository.js +0 -50
  36. package/esm/repository.js.map +0 -1
  37. package/esm/repository.spec.d.ts +0 -2
  38. package/esm/repository.spec.d.ts.map +0 -1
  39. package/esm/repository.spec.js +0 -18
  40. package/esm/repository.spec.js.map +0 -1
  41. package/src/dataset.spec.ts +0 -641
  42. package/src/repository.spec.ts +0 -19
  43. package/src/repository.ts +0 -50
package/src/helpers.ts CHANGED
@@ -1,27 +1,26 @@
1
- import type { Constructable, Injector } from '@furystack/inject'
2
- import { Repository } from './repository.js'
1
+ import type { WithOptionalId } from '@furystack/core'
2
+ import type { Injector } from '@furystack/inject'
3
+ import type { DataSet } from './data-set.js'
4
+ import type { DataSetToken } from './define-data-set.js'
3
5
 
4
6
  /**
5
- * Returns a Repository on an injector
6
- * @param injector The Injector instance
7
- * @returns The Repository instance
8
- */
9
- export const getRepository = (injector: Injector) => injector.getInstance(Repository)
10
-
11
- /**
12
- * Gets a DataSet for a specific model from the repository.
7
+ * Resolves the {@link DataSet} identified by the supplied {@link DataSetToken}
8
+ * on the given injector.
13
9
  *
14
- * The DataSet is the recommended way to perform all entity mutations (add, update, remove).
15
- * Writing through the DataSet ensures that authorization, modification hooks, and change events
16
- * are properly triggered -- which is required for features like entity sync.
10
+ * Writing through the returned DataSet ensures authorization, modification
11
+ * hooks and change events (required for features such as entity sync) are
12
+ * triggered. Prefer this over direct physical-store access in application
13
+ * code.
17
14
  *
18
- * For server-side or background operations that don't originate from an HTTP request,
19
- * use {@link useSystemIdentityContext} to create a scoped child injector with elevated privileges.
15
+ * For server-side or background operations that don't originate from an HTTP
16
+ * request, use
17
+ * {@link import('@furystack/core').useSystemIdentityContext | useSystemIdentityContext}
18
+ * to create a scoped child injector with elevated privileges before calling
19
+ * this helper.
20
20
  *
21
- * @param injector The Injector instance
22
- * @param model The Model
23
- * @param primaryKey The Primary Key field
24
- * @returns A Repository DataSet for a specific model
21
+ * @param injector - Any injector in the scope chain.
22
+ * @param token - The data-set token created by `defineDataSet`.
23
+ * @returns The cached {@link DataSet} instance.
25
24
  *
26
25
  * @example
27
26
  * ```ts
@@ -29,18 +28,16 @@ export const getRepository = (injector: Injector) => injector.getInstance(Reposi
29
28
  * import { getDataSetFor } from '@furystack/repository'
30
29
  * import { usingAsync } from '@furystack/utils'
31
30
  *
32
- * // In a background job or service
33
31
  * await usingAsync(
34
32
  * useSystemIdentityContext({ injector, username: 'background-job' }),
35
33
  * async (systemInjector) => {
36
- * const dataSet = getDataSetFor(systemInjector, MyModel, 'id')
37
- * await dataSet.add(systemInjector, newEntity)
34
+ * const dataSet = getDataSetFor(systemInjector, UserDataSet)
35
+ * await dataSet.add(systemInjector, { username: 'alice', roles: [] })
38
36
  * },
39
37
  * )
40
38
  * ```
41
39
  */
42
- export const getDataSetFor = <T, TPrimaryKey extends keyof T>(
40
+ export const getDataSetFor = <T, TPrimaryKey extends keyof T, TWritableData = WithOptionalId<T, TPrimaryKey>>(
43
41
  injector: Injector,
44
- model: Constructable<T>,
45
- primaryKey: TPrimaryKey,
46
- ) => injector.getInstance(Repository).getDataSetFor(model, primaryKey)
42
+ token: DataSetToken<T, TPrimaryKey, TWritableData>,
43
+ ): DataSet<T, TPrimaryKey, TWritableData> => injector.get(token)
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from './repository.js'
2
- export * from './data-set.js'
3
1
  export * from './data-set-setting.js'
2
+ export * from './data-set.js'
3
+ export * from './define-data-set.js'
4
4
  export * from './helpers.js'
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=dataset.spec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dataset.spec.d.ts","sourceRoot":"","sources":["../src/dataset.spec.ts"],"names":[],"mappings":""}
@@ -1,552 +0,0 @@
1
- import { AuthorizationError, InMemoryStore, addStore } from '@furystack/core';
2
- import { Injector } from '@furystack/inject';
3
- import { usingAsync } from '@furystack/utils';
4
- import { describe, expect, it, vi } from 'vitest';
5
- import { getDataSetFor, getRepository } from './helpers.js';
6
- import { Repository } from './repository.js';
7
- class TestClass {
8
- id = 1;
9
- value = '';
10
- }
11
- describe('DataSet', () => {
12
- describe('Construction', () => {
13
- it('can be retrieved from an extension method with class', async () => {
14
- await usingAsync(new Injector(), async (i) => {
15
- addStore(i, new InMemoryStore({
16
- model: TestClass,
17
- primaryKey: 'id',
18
- }));
19
- getRepository(i).createDataSet(TestClass, 'id');
20
- const dataSet = getDataSetFor(i, TestClass, 'id');
21
- expect(dataSet.settings.physicalStore.model).toBe(TestClass);
22
- });
23
- });
24
- it('can be retrieved from an extension method with string', async () => {
25
- await usingAsync(new Injector(), async (i) => {
26
- addStore(i, new InMemoryStore({
27
- model: TestClass,
28
- primaryKey: 'id',
29
- }));
30
- getRepository(i).createDataSet(TestClass, 'id');
31
- const dataSet = getDataSetFor(i, TestClass, 'id');
32
- expect(dataSet.settings.physicalStore.model).toBe(TestClass);
33
- });
34
- });
35
- it('Should throw if dataset is not registered through extension', async () => {
36
- await usingAsync(new Injector(), async (i) => {
37
- expect(() => getDataSetFor(i, TestClass, 'id')).toThrow(/No DataSet found for 'class TestClass/);
38
- });
39
- });
40
- it('Should throw if dataset is not registered through service', async () => {
41
- await usingAsync(new Injector(), async (i) => {
42
- expect(() => i.getInstance(Repository).getDataSetFor(TestClass, 'id')).toThrowError(/No DataSet found for 'class TestClass/);
43
- });
44
- });
45
- it('Should throw error on mismatched primary key', async () => {
46
- await usingAsync(new Injector(), async (i) => {
47
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
48
- getRepository(i).createDataSet(TestClass, 'id', {});
49
- expect(() => getDataSetFor(i, TestClass, 'name')).toThrowError();
50
- });
51
- });
52
- });
53
- describe('Authorizers', () => {
54
- describe('Add', () => {
55
- it('should add an entity if no settings are provided', async () => {
56
- await usingAsync(new Injector(), async (i) => {
57
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
58
- getRepository(i).createDataSet(TestClass, 'id');
59
- const dataSet = getDataSetFor(i, TestClass, 'id');
60
- await dataSet.add(i, { id: 1, value: 'asd' });
61
- const result = await dataSet.get(i, 1);
62
- expect(result && result.value).toBe('asd');
63
- });
64
- });
65
- it('should call the add async authorizer and add the entity on pass', async () => {
66
- await usingAsync(new Injector(), async (i) => {
67
- const authorizeAdd = vi.fn(async () => ({ isAllowed: true }));
68
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
69
- getRepository(i).createDataSet(TestClass, 'id', { authorizeAdd });
70
- const dataSet = getDataSetFor(i, TestClass, 'id');
71
- await dataSet.add(i, { id: 1, value: 'asd' });
72
- expect(authorizeAdd).toBeCalled();
73
- const added = await dataSet.get(i, 1);
74
- expect(added && added.value).toBe('asd');
75
- });
76
- });
77
- it('should throw if the add authorizer returns a non-valid result and should not add a value to the store', async () => {
78
- await usingAsync(new Injector(), async (i) => {
79
- const authorizeAdd = vi.fn(async () => ({ isAllowed: false, message: '...' }));
80
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
81
- getRepository(i).createDataSet(TestClass, 'id', { authorizeAdd });
82
- const dataSet = getDataSetFor(i, TestClass, 'id');
83
- try {
84
- await dataSet.add(i, { id: 1, value: 'asd' });
85
- throw Error('Should throw');
86
- }
87
- catch (error) {
88
- /** */
89
- expect(authorizeAdd).toBeCalled();
90
- const added = await dataSet.get(i, 1);
91
- expect(added).toBeUndefined();
92
- }
93
- });
94
- });
95
- it('should modify an entity on add, if modifyOnAdd is provided', async () => {
96
- await usingAsync(new Injector(), async (i) => {
97
- const modifyOnAdd = vi.fn(async (options) => ({
98
- ...options.entity,
99
- value: options.entity.value.toUpperCase(),
100
- }));
101
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
102
- getRepository(i).createDataSet(TestClass, 'id', { modifyOnAdd });
103
- const dataSet = getDataSetFor(i, TestClass, 'id');
104
- await dataSet.add(i, { id: 1, value: 'asd' });
105
- const result = await dataSet.get(i, 1);
106
- expect(modifyOnAdd).toBeCalled();
107
- expect(result && result.value).toBe('ASD');
108
- });
109
- });
110
- it('should call the onEntityAdded callback if an entity has been added', async () => {
111
- await usingAsync(new Injector(), async (i) => {
112
- expect.assertions(1);
113
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
114
- getRepository(i).createDataSet(TestClass, 'id', {});
115
- getRepository(i)
116
- .getDataSetFor(TestClass, 'id')
117
- .subscribe('onEntityAdded', ({ entity }) => {
118
- expect(entity.value).toBe('asd');
119
- });
120
- const dataSet = getDataSetFor(i, TestClass, 'id');
121
- await dataSet.add(i, { id: 1, value: 'asd' });
122
- });
123
- });
124
- });
125
- describe('Update', () => {
126
- it('should update an entity if no settings are provided', async () => {
127
- await usingAsync(new Injector(), async (i) => {
128
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
129
- getRepository(i).createDataSet(TestClass, 'id');
130
- const dataSet = getDataSetFor(i, TestClass, 'id');
131
- await dataSet.add(i, { id: 1, value: 'asd' });
132
- await dataSet.update(i, 1, { id: 1, value: 'asd2' });
133
- const result = await dataSet.get(i, 1);
134
- expect(result && result.value).toBe('asd2');
135
- });
136
- });
137
- it('should call the authorizeUpdate authorizer and add the entity on pass', async () => {
138
- await usingAsync(new Injector(), async (i) => {
139
- const authorizeUpdate = vi.fn(async () => ({ isAllowed: true }));
140
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
141
- getRepository(i).createDataSet(TestClass, 'id', { authorizeUpdate });
142
- const dataSet = getDataSetFor(i, TestClass, 'id');
143
- await dataSet.add(i, { id: 1, value: 'asd' });
144
- await dataSet.update(i, 1, { id: 1, value: 'asd2' });
145
- expect(authorizeUpdate).toBeCalled();
146
- const added = await dataSet.get(i, 1);
147
- expect(added && added.value).toBe('asd2');
148
- });
149
- });
150
- it('should throw if the authorizeUpdateEntity returns a non-valid result and should not update a value to the store', async () => {
151
- await usingAsync(new Injector(), async (i) => {
152
- const authorizeUpdateEntity = vi.fn(async () => ({ isAllowed: false, message: '...' }));
153
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
154
- getRepository(i).createDataSet(TestClass, 'id', { authorizeUpdateEntity });
155
- const dataSet = getDataSetFor(i, TestClass, 'id');
156
- try {
157
- await dataSet.add(i, { id: 1, value: 'asd' });
158
- await dataSet.update(i, 1, { id: 1, value: 'asd2' });
159
- throw Error('Should throw');
160
- }
161
- catch (error) {
162
- /** */
163
- expect(authorizeUpdateEntity).toBeCalled();
164
- const added = await dataSet.get(i, 1);
165
- expect(added && added.value).toBe('asd');
166
- }
167
- });
168
- });
169
- it('should call the authorizeUpdateEntity authorizer and add the entity on pass', async () => {
170
- await usingAsync(new Injector(), async (i) => {
171
- const authorizeUpdateEntity = vi.fn(async () => ({ isAllowed: true }));
172
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
173
- getRepository(i).createDataSet(TestClass, 'id', { authorizeUpdateEntity });
174
- const dataSet = getDataSetFor(i, TestClass, 'id');
175
- await dataSet.add(i, { id: 1, value: 'asd' });
176
- await dataSet.update(i, 1, { id: 1, value: 'asd2' });
177
- expect(authorizeUpdateEntity).toBeCalled();
178
- const added = await dataSet.get(i, 1);
179
- expect(added && added.value).toBe('asd2');
180
- });
181
- });
182
- it('should throw if the authorizeUpdate returns a non-valid result and should not update a value to the store', async () => {
183
- await usingAsync(new Injector(), async (i) => {
184
- const authorizeUpdate = vi.fn(async () => ({ isAllowed: false, message: '...' }));
185
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
186
- getRepository(i).createDataSet(TestClass, 'id', { authorizeUpdate });
187
- const dataSet = getDataSetFor(i, TestClass, 'id');
188
- try {
189
- await dataSet.add(i, { id: 1, value: 'asd' });
190
- await dataSet.update(i, 1, { id: 1, value: 'asd2' });
191
- throw Error('Should throw');
192
- }
193
- catch (error) {
194
- /** */
195
- expect(authorizeUpdate).toBeCalled();
196
- const added = await dataSet.get(i, 1);
197
- expect(added && added.value).toBe('asd');
198
- }
199
- });
200
- });
201
- it('should modify an entity on update, if modifyOnAdd is provided', async () => {
202
- await usingAsync(new Injector(), async (i) => {
203
- const modifyOnUpdate = vi.fn(async (options) => ({
204
- ...options.entity,
205
- value: options.entity.value?.toUpperCase(),
206
- }));
207
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
208
- getRepository(i).createDataSet(TestClass, 'id', { modifyOnUpdate });
209
- const dataSet = getDataSetFor(i, TestClass, 'id');
210
- await dataSet.add(i, { id: 1, value: 'asd' });
211
- await dataSet.update(i, 1, { id: 1, value: 'asd2' });
212
- const result = await dataSet.get(i, 1);
213
- expect(modifyOnUpdate).toBeCalled();
214
- expect(result && result.value).toBe('ASD2');
215
- });
216
- });
217
- it('should publish to the onEntityUpdated observable if an entity has been updated', async () => {
218
- await usingAsync(new Injector(), async (i) => {
219
- expect.assertions(1);
220
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
221
- getRepository(i).createDataSet(TestClass, 'id');
222
- getRepository(i)
223
- .getDataSetFor(TestClass, 'id')
224
- .subscribe('onEntityUpdated', ({ change }) => {
225
- expect(change).toEqual({ id: 1, value: 'asd2' });
226
- });
227
- const dataSet = getDataSetFor(i, TestClass, 'id');
228
- await dataSet.add(i, { id: 1, value: 'asd' });
229
- await dataSet.update(i, 1, { id: 1, value: 'asd2' });
230
- });
231
- });
232
- });
233
- describe('Count', () => {
234
- it('should return the count if no settings are provided', async () => {
235
- await usingAsync(new Injector(), async (i) => {
236
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
237
- getRepository(i).createDataSet(TestClass, 'id');
238
- const dataSet = getDataSetFor(i, TestClass, 'id');
239
- await dataSet.add(i, { id: 1, value: 'asd' });
240
- const result = await dataSet.count(i);
241
- expect(result).toBe(1);
242
- });
243
- });
244
- it('should return the count if authorizeGet returns valid result', async () => {
245
- await usingAsync(new Injector(), async (i) => {
246
- const authorizeGet = vi.fn(async () => ({ isAllowed: true, message: '' }));
247
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
248
- getRepository(i).createDataSet(TestClass, 'id', { authorizeGet });
249
- const dataSet = getDataSetFor(i, TestClass, 'id');
250
- await dataSet.add(i, { id: 1, value: 'asd' });
251
- const result = await dataSet.count(i);
252
- expect(result).toBe(1);
253
- });
254
- });
255
- it('should throw if authorizeGet returns invalid result', async () => {
256
- await usingAsync(new Injector(), async (i) => {
257
- const authorizeGet = vi.fn(async () => ({ isAllowed: false, message: ':(' }));
258
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
259
- getRepository(i).createDataSet(TestClass, 'id', { authorizeGet });
260
- const dataSet = getDataSetFor(i, TestClass, 'id');
261
- await dataSet.add(i, { id: 1, value: 'asd' });
262
- try {
263
- await dataSet.count(i);
264
- throw Error('Should throw');
265
- }
266
- catch (error) {
267
- /** */
268
- }
269
- });
270
- });
271
- });
272
- });
273
- describe('filter', () => {
274
- it('should return the unfiltered result if no settings are provided', async () => {
275
- await usingAsync(new Injector(), async (i) => {
276
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
277
- getRepository(i).createDataSet(TestClass, 'id');
278
- const dataSet = getDataSetFor(i, TestClass, 'id');
279
- await dataSet.add(i, { id: 1, value: 'asd' });
280
- const result = await dataSet.find(i, {});
281
- expect(result.length).toBe(1);
282
- });
283
- });
284
- it('should return the unfiltered result if authorizeGet returns valid result', async () => {
285
- await usingAsync(new Injector(), async (i) => {
286
- const authorizeGet = vi.fn(async () => ({ isAllowed: true, message: '' }));
287
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
288
- getRepository(i).createDataSet(TestClass, 'id', { authorizeGet });
289
- const dataSet = getDataSetFor(i, TestClass, 'id');
290
- await dataSet.add(i, { id: 1, value: 'asd' });
291
- const result = await dataSet.find(i, {});
292
- expect(result.length).toBe(1);
293
- });
294
- });
295
- it('should throw if authorizeGet returns invalid result', async () => {
296
- await usingAsync(new Injector(), async (i) => {
297
- const authorizeGet = vi.fn(async () => ({ isAllowed: false, message: ':(' }));
298
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
299
- getRepository(i).createDataSet(TestClass, 'id', { authorizeGet });
300
- const dataSet = getDataSetFor(i, TestClass, 'id');
301
- await dataSet.add(i, { id: 1, value: 'asd' });
302
- try {
303
- await dataSet.find(i, {});
304
- throw Error('Should throw');
305
- }
306
- catch (error) {
307
- /** */
308
- }
309
- });
310
- });
311
- });
312
- describe('get', () => {
313
- it('should return the entity if no settings are provided', async () => {
314
- await usingAsync(new Injector(), async (i) => {
315
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
316
- getRepository(i).createDataSet(TestClass, 'id');
317
- const dataSet = getDataSetFor(i, TestClass, 'id');
318
- await dataSet.add(i, { id: 1, value: 'asd' });
319
- const result = await dataSet.get(i, 1);
320
- expect(result && result.id).toBe(1);
321
- });
322
- });
323
- it('should return the entity if authorizeGet returns valid result', async () => {
324
- await usingAsync(new Injector(), async (i) => {
325
- const authorizeGet = vi.fn(async () => ({ isAllowed: true, message: '' }));
326
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
327
- getRepository(i).createDataSet(TestClass, 'id', { authorizeGet });
328
- const dataSet = getDataSetFor(i, TestClass, 'id');
329
- await dataSet.add(i, { id: 1, value: 'asd' });
330
- const result = await dataSet.get(i, 1);
331
- expect(result && result.id).toBe(1);
332
- });
333
- });
334
- it('should throw if authorizeGet returns invalid result', async () => {
335
- await usingAsync(new Injector(), async (i) => {
336
- const authorizeGet = vi.fn(async () => ({ isAllowed: false, message: ':(' }));
337
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
338
- getRepository(i).createDataSet(TestClass, 'id', { authorizeGet });
339
- const dataSet = getDataSetFor(i, TestClass, 'id');
340
- await dataSet.add(i, { id: 1, value: 'asd' });
341
- try {
342
- await dataSet.get(i, 1);
343
- throw Error('Should throw');
344
- }
345
- catch (error) {
346
- /** */
347
- }
348
- });
349
- });
350
- it('should return the entity if authorizeGetEntity returns valid result', async () => {
351
- await usingAsync(new Injector(), async (i) => {
352
- const authorizeGetEntity = vi.fn(async () => ({ isAllowed: true, message: '' }));
353
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
354
- getRepository(i).createDataSet(TestClass, 'id', { authorizeGetEntity });
355
- const dataSet = getDataSetFor(i, TestClass, 'id');
356
- await dataSet.add(i, { id: 1, value: 'asd' });
357
- const result = await dataSet.get(i, 1);
358
- expect(result && result.id).toBe(1);
359
- });
360
- });
361
- it('should throw if authorizeGetEntity returns invalid result', async () => {
362
- await usingAsync(new Injector(), async (i) => {
363
- const authorizeGetEntity = vi.fn(async () => ({ isAllowed: false, message: ':(' }));
364
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
365
- getRepository(i).createDataSet(TestClass, 'id', { authorizeGetEntity });
366
- const dataSet = getDataSetFor(i, TestClass, 'id');
367
- await dataSet.add(i, { id: 1, value: 'asd' });
368
- try {
369
- await dataSet.get(i, 1);
370
- throw Error('Should throw');
371
- }
372
- catch (error) {
373
- /** */
374
- }
375
- });
376
- });
377
- it('should pass the full entity to authorizeGetEntity even when select is used', async () => {
378
- await usingAsync(new Injector(), async (i) => {
379
- const authorizeGetEntity = vi.fn(async () => ({ isAllowed: true, message: '' }));
380
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
381
- getRepository(i).createDataSet(TestClass, 'id', { authorizeGetEntity });
382
- const dataSet = getDataSetFor(i, TestClass, 'id');
383
- await dataSet.add(i, { id: 1, value: 'asd' });
384
- const result = await dataSet.get(i, 1, ['value']);
385
- expect(authorizeGetEntity).toHaveBeenCalledTimes(1);
386
- const calls = authorizeGetEntity.mock.calls;
387
- expect(calls[0][0].entity).toEqual({ id: 1, value: 'asd' });
388
- expect(result).toEqual({ value: 'asd' });
389
- expect(result).not.toHaveProperty('id');
390
- });
391
- });
392
- it('should return undefined when authorizeGetEntity is set and entity does not exist', async () => {
393
- await usingAsync(new Injector(), async (i) => {
394
- const authorizeGetEntity = vi.fn(async () => ({ isAllowed: true, message: '' }));
395
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
396
- getRepository(i).createDataSet(TestClass, 'id', { authorizeGetEntity });
397
- const dataSet = getDataSetFor(i, TestClass, 'id');
398
- const result = await dataSet.get(i, 999);
399
- expect(result).toBeUndefined();
400
- expect(authorizeGetEntity).not.toHaveBeenCalled();
401
- });
402
- });
403
- });
404
- describe('remove', () => {
405
- it('should remove the entity if no settings are provided', async () => {
406
- await usingAsync(new Injector(), async (i) => {
407
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
408
- getRepository(i).createDataSet(TestClass, 'id');
409
- const dataSet = getDataSetFor(i, TestClass, 'id');
410
- await dataSet.add(i, { id: 1, value: 'asd' });
411
- await dataSet.remove(i, 1);
412
- const countValue = await dataSet.count(i);
413
- expect(countValue).toBe(0);
414
- });
415
- });
416
- it('should remove the entity if authorizeRemove returns valid result', async () => {
417
- await usingAsync(new Injector(), async (i) => {
418
- const authorizeRemove = vi.fn(async () => ({ isAllowed: true, message: '' }));
419
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
420
- getRepository(i).createDataSet(TestClass, 'id', { authorizeRemove });
421
- const dataSet = getDataSetFor(i, TestClass, 'id');
422
- await dataSet.add(i, { id: 1, value: 'asd' });
423
- await dataSet.remove(i, 1);
424
- const count = await dataSet.count(i);
425
- expect(count).toBe(0);
426
- });
427
- });
428
- it('should throw if authorizeRemove returns invalid result', async () => {
429
- await usingAsync(new Injector(), async (i) => {
430
- const authorizeRemove = vi.fn(async () => ({ isAllowed: false, message: ':(' }));
431
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
432
- getRepository(i).createDataSet(TestClass, 'id', { authorizeRemove });
433
- const dataSet = getDataSetFor(i, TestClass, 'id');
434
- await dataSet.add(i, { id: 1, value: 'asd' });
435
- try {
436
- await dataSet.remove(i, 1);
437
- throw Error('Should throw');
438
- }
439
- catch (error) {
440
- /** */
441
- }
442
- const count = await dataSet.count(i);
443
- expect(count).toBe(1);
444
- });
445
- });
446
- it('should remove the entity if authorizeRemoveEntity returns valid result', async () => {
447
- await usingAsync(new Injector(), async (i) => {
448
- const authorizeRemoveEntity = vi.fn(async () => ({ isAllowed: true, message: '' }));
449
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
450
- getRepository(i).createDataSet(TestClass, 'id', { authorizeRemoveEntity });
451
- const dataSet = getDataSetFor(i, TestClass, 'id');
452
- await dataSet.add(i, { id: 1, value: 'asd' });
453
- await dataSet.remove(i, 1);
454
- const count = await dataSet.count(i);
455
- expect(count).toBe(0);
456
- });
457
- });
458
- it('should throw if authorizeRemoveEntity returns invalid result', async () => {
459
- await usingAsync(new Injector(), async (i) => {
460
- const authorizeRemoveEntity = vi.fn(async () => ({ isAllowed: false, message: ':(' }));
461
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
462
- getRepository(i).createDataSet(TestClass, 'id', { authorizeRemoveEntity });
463
- const dataSet = getDataSetFor(i, TestClass, 'id');
464
- await dataSet.add(i, { id: 1, value: 'asd' });
465
- try {
466
- await dataSet.remove(i, 1);
467
- throw Error('Should throw');
468
- }
469
- catch (error) {
470
- /** */
471
- }
472
- const count = await dataSet.count(i);
473
- expect(count).toBe(1);
474
- });
475
- });
476
- it('should publish to the onEntityRemoved observable if an entity has been removed', async () => {
477
- await usingAsync(new Injector(), async (i) => {
478
- expect.assertions(1);
479
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
480
- getRepository(i).createDataSet(TestClass, 'id');
481
- getRepository(i)
482
- .getDataSetFor(TestClass, 'id')
483
- .subscribe('onEntityRemoved', ({ key }) => {
484
- expect(key).toEqual(1);
485
- });
486
- const dataSet = getDataSetFor(i, TestClass, 'id');
487
- await dataSet.add(i, { id: 1, value: 'asd' });
488
- await dataSet.remove(i, 1);
489
- });
490
- });
491
- it('should remove multiple entities at once', async () => {
492
- await usingAsync(new Injector(), async (i) => {
493
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
494
- getRepository(i).createDataSet(TestClass, 'id');
495
- const dataSet = getDataSetFor(i, TestClass, 'id');
496
- await dataSet.add(i, { id: 1, value: 'a' }, { id: 2, value: 'b' }, { id: 3, value: 'c' });
497
- await dataSet.remove(i, 1, 3);
498
- const count = await dataSet.count(i);
499
- expect(count).toBe(1);
500
- const remaining = await dataSet.get(i, 2);
501
- expect(remaining?.value).toBe('b');
502
- });
503
- });
504
- it('should emit onEntityRemoved for each key when removing multiple entities', async () => {
505
- await usingAsync(new Injector(), async (i) => {
506
- const removedKeys = [];
507
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
508
- getRepository(i).createDataSet(TestClass, 'id');
509
- getRepository(i)
510
- .getDataSetFor(TestClass, 'id')
511
- .subscribe('onEntityRemoved', ({ key }) => {
512
- removedKeys.push(key);
513
- });
514
- const dataSet = getDataSetFor(i, TestClass, 'id');
515
- await dataSet.add(i, { id: 1, value: 'a' }, { id: 2, value: 'b' });
516
- await dataSet.remove(i, 1, 2);
517
- expect(removedKeys).toEqual([1, 2]);
518
- });
519
- });
520
- it('should authorize each entity when removing multiple with authorizeRemoveEntity', async () => {
521
- await usingAsync(new Injector(), async (i) => {
522
- const authorizeRemoveEntity = vi.fn(async () => ({ isAllowed: true, message: '' }));
523
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
524
- getRepository(i).createDataSet(TestClass, 'id', { authorizeRemoveEntity });
525
- const dataSet = getDataSetFor(i, TestClass, 'id');
526
- await dataSet.add(i, { id: 1, value: 'a' }, { id: 2, value: 'b' });
527
- await dataSet.remove(i, 1, 2);
528
- expect(authorizeRemoveEntity).toHaveBeenCalledTimes(2);
529
- const count = await dataSet.count(i);
530
- expect(count).toBe(0);
531
- });
532
- });
533
- it('should not remove any entity if authorizeRemoveEntity fails for one (all-or-nothing)', async () => {
534
- await usingAsync(new Injector(), async (i) => {
535
- const authorizeRemoveEntity = vi.fn(async ({ entity }) => {
536
- if (entity.id === 2) {
537
- return { isAllowed: false, message: 'forbidden' };
538
- }
539
- return { isAllowed: true };
540
- });
541
- addStore(i, new InMemoryStore({ model: TestClass, primaryKey: 'id' }));
542
- getRepository(i).createDataSet(TestClass, 'id', { authorizeRemoveEntity });
543
- const dataSet = getDataSetFor(i, TestClass, 'id');
544
- await dataSet.add(i, { id: 1, value: 'a' }, { id: 2, value: 'b' }, { id: 3, value: 'c' });
545
- await expect(dataSet.remove(i, 1, 2, 3)).rejects.toThrow(AuthorizationError);
546
- const count = await dataSet.count(i);
547
- expect(count).toBe(3);
548
- });
549
- });
550
- });
551
- });
552
- //# sourceMappingURL=dataset.spec.js.map