@furystack/repository 10.1.11 → 11.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +74 -36
- package/esm/data-set-setting.d.ts +30 -44
- package/esm/data-set-setting.d.ts.map +1 -1
- package/esm/data-set.d.ts +17 -68
- package/esm/data-set.d.ts.map +1 -1
- package/esm/data-set.js +18 -69
- package/esm/data-set.js.map +1 -1
- package/esm/data-set.spec.d.ts +2 -0
- package/esm/data-set.spec.d.ts.map +1 -0
- package/esm/data-set.spec.js +348 -0
- package/esm/data-set.spec.js.map +1 -0
- package/esm/define-data-set.d.ts +71 -0
- package/esm/define-data-set.d.ts.map +1 -0
- package/esm/define-data-set.js +65 -0
- package/esm/define-data-set.js.map +1 -0
- package/esm/define-data-set.spec.d.ts +2 -0
- package/esm/define-data-set.spec.d.ts.map +1 -0
- package/esm/define-data-set.spec.js +134 -0
- package/esm/define-data-set.spec.js.map +1 -0
- package/esm/helpers.d.ts +12 -29
- package/esm/helpers.d.ts.map +1 -1
- package/esm/helpers.js +8 -28
- package/esm/helpers.js.map +1 -1
- package/esm/index.d.ts +2 -2
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +2 -2
- package/esm/index.js.map +1 -1
- package/package.json +5 -5
- package/src/data-set-setting.ts +32 -46
- package/src/data-set.spec.ts +397 -0
- package/src/data-set.ts +28 -72
- package/src/define-data-set.spec.ts +145 -0
- package/src/define-data-set.ts +122 -0
- package/src/helpers.ts +14 -33
- package/src/index.ts +2 -2
- package/esm/dataset.spec.d.ts +0 -2
- package/esm/dataset.spec.d.ts.map +0 -1
- package/esm/dataset.spec.js +0 -552
- package/esm/dataset.spec.js.map +0 -1
- package/esm/repository.d.ts +0 -15
- package/esm/repository.d.ts.map +0 -1
- package/esm/repository.js +0 -50
- package/esm/repository.js.map +0 -1
- package/esm/repository.spec.d.ts +0 -2
- package/esm/repository.spec.d.ts.map +0 -1
- package/esm/repository.spec.js +0 -18
- package/esm/repository.spec.js.map +0 -1
- package/src/dataset.spec.ts +0 -641
- package/src/repository.spec.ts +0 -19
- package/src/repository.ts +0 -50
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { defineStore, InMemoryStore } from '@furystack/core';
|
|
2
|
+
import { createInjector } from '@furystack/inject';
|
|
3
|
+
import { usingAsync } from '@furystack/utils';
|
|
4
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { DataSet } from './data-set.js';
|
|
6
|
+
import { defineDataSet } from './define-data-set.js';
|
|
7
|
+
class Test {
|
|
8
|
+
}
|
|
9
|
+
// Store tokens are declared at module scope so TypeScript keeps the literal
|
|
10
|
+
// primary-key narrowing. Wrapping `defineStore` in an arrow function would
|
|
11
|
+
// widen the inferred `TPrimaryKey` back to `keyof Test` in the return type.
|
|
12
|
+
const BasicStore = defineStore({
|
|
13
|
+
name: 'test/BasicStore',
|
|
14
|
+
model: Test,
|
|
15
|
+
primaryKey: 'id',
|
|
16
|
+
factory: () => new InMemoryStore({ model: Test, primaryKey: 'id' }),
|
|
17
|
+
});
|
|
18
|
+
const SingletonStore = defineStore({
|
|
19
|
+
name: 'test/SingletonStore',
|
|
20
|
+
model: Test,
|
|
21
|
+
primaryKey: 'id',
|
|
22
|
+
factory: () => new InMemoryStore({ model: Test, primaryKey: 'id' }),
|
|
23
|
+
});
|
|
24
|
+
const MetaStore = defineStore({
|
|
25
|
+
name: 'test/MetaStore',
|
|
26
|
+
model: Test,
|
|
27
|
+
primaryKey: 'id',
|
|
28
|
+
factory: () => new InMemoryStore({ model: Test, primaryKey: 'id' }),
|
|
29
|
+
});
|
|
30
|
+
const HooksStore = defineStore({
|
|
31
|
+
name: 'test/HooksStore',
|
|
32
|
+
model: Test,
|
|
33
|
+
primaryKey: 'id',
|
|
34
|
+
factory: () => new InMemoryStore({ model: Test, primaryKey: 'id' }),
|
|
35
|
+
});
|
|
36
|
+
const EventsStore = defineStore({
|
|
37
|
+
name: 'test/EventsStore',
|
|
38
|
+
model: Test,
|
|
39
|
+
primaryKey: 'id',
|
|
40
|
+
factory: () => new InMemoryStore({ model: Test, primaryKey: 'id' }),
|
|
41
|
+
});
|
|
42
|
+
const DisposeStore = defineStore({
|
|
43
|
+
name: 'test/DisposeStore',
|
|
44
|
+
model: Test,
|
|
45
|
+
primaryKey: 'id',
|
|
46
|
+
factory: () => new InMemoryStore({ model: Test, primaryKey: 'id' }),
|
|
47
|
+
});
|
|
48
|
+
const RebindStore = defineStore({
|
|
49
|
+
name: 'test/RebindStore',
|
|
50
|
+
model: Test,
|
|
51
|
+
primaryKey: 'id',
|
|
52
|
+
factory: () => new InMemoryStore({ model: Test, primaryKey: 'id' }),
|
|
53
|
+
});
|
|
54
|
+
const modifyOnAddSpy = vi.fn();
|
|
55
|
+
const BasicDataSet = defineDataSet({ name: 'test/BasicDataSet', store: BasicStore });
|
|
56
|
+
const SingletonDataSet = defineDataSet({ name: 'test/SingletonDataSet', store: SingletonStore });
|
|
57
|
+
const MetaDataSet = defineDataSet({ name: 'test/MetaDataSet', store: MetaStore });
|
|
58
|
+
// The `settings` callback participates in bidirectional inference against the
|
|
59
|
+
// surrounding generics. Even with `NoInfer` on the settings position, inline
|
|
60
|
+
// callbacks currently widen `TPrimaryKey` to `keyof T` because TypeScript has
|
|
61
|
+
// to contextually type the callback before it can commit to the store-driven
|
|
62
|
+
// inference. The runtime value of `token.primaryKey` is still correct; only
|
|
63
|
+
// the entity parameter is typed as the wider `WithOptionalId<Test, keyof Test>`.
|
|
64
|
+
// The non-null assertion on `entity.value` reflects that widening.
|
|
65
|
+
const HooksDataSet = defineDataSet({
|
|
66
|
+
name: 'test/HooksDataSet',
|
|
67
|
+
store: HooksStore,
|
|
68
|
+
settings: {
|
|
69
|
+
modifyOnAdd: async ({ entity }) => {
|
|
70
|
+
modifyOnAddSpy();
|
|
71
|
+
return { ...entity, value: entity.value.toUpperCase() };
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
const EventsDataSet = defineDataSet({ name: 'test/EventsDataSet', store: EventsStore });
|
|
76
|
+
const DisposeDataSet = defineDataSet({ name: 'test/DisposeDataSet', store: DisposeStore });
|
|
77
|
+
const RebindDataSet = defineDataSet({ name: 'test/RebindDataSet', store: RebindStore });
|
|
78
|
+
describe('defineDataSet', () => {
|
|
79
|
+
it('resolves to a DataSet backed by the supplied store', async () => {
|
|
80
|
+
await usingAsync(createInjector(), async (i) => {
|
|
81
|
+
const ds = i.get(BasicDataSet);
|
|
82
|
+
expect(ds).toBeInstanceOf(DataSet);
|
|
83
|
+
expect(ds.settings.physicalStore).toBe(i.get(BasicStore));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
it('caches the DataSet as a singleton across resolutions', async () => {
|
|
87
|
+
await usingAsync(createInjector(), async (i) => {
|
|
88
|
+
expect(i.get(SingletonDataSet)).toBe(i.get(SingletonDataSet));
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
it('mirrors the model and primary key from the backing store token', () => {
|
|
92
|
+
expect(MetaDataSet.model).toBe(Test);
|
|
93
|
+
expect(MetaDataSet.primaryKey).toBe('id');
|
|
94
|
+
});
|
|
95
|
+
it('applies the configured authorizers and hooks', async () => {
|
|
96
|
+
modifyOnAddSpy.mockClear();
|
|
97
|
+
await usingAsync(createInjector(), async (i) => {
|
|
98
|
+
const ds = i.get(HooksDataSet);
|
|
99
|
+
await ds.add(i, { id: 1, value: 'asd' });
|
|
100
|
+
const stored = await ds.get(i, 1);
|
|
101
|
+
expect(modifyOnAddSpy).toHaveBeenCalledTimes(1);
|
|
102
|
+
expect(stored?.value).toBe('ASD');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
it('emits change events through the DataSet EventHub', async () => {
|
|
106
|
+
await usingAsync(createInjector(), async (i) => {
|
|
107
|
+
const added = vi.fn();
|
|
108
|
+
const ds = i.get(EventsDataSet);
|
|
109
|
+
ds.addListener('onEntityAdded', added);
|
|
110
|
+
await ds.add(i, { id: 1, value: 'x' });
|
|
111
|
+
expect(added).toHaveBeenCalledWith(expect.objectContaining({ entity: { id: 1, value: 'x' } }));
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
it('clears DataSet listeners when the injector is disposed', async () => {
|
|
115
|
+
const i = createInjector();
|
|
116
|
+
const ds = i.get(DisposeDataSet);
|
|
117
|
+
const listener = vi.fn();
|
|
118
|
+
ds.addListener('onEntityAdded', listener);
|
|
119
|
+
await i[Symbol.asyncDispose]();
|
|
120
|
+
// After disposal the DataSet's event subscriptions are cleared;
|
|
121
|
+
// emitting directly would be a no-op.
|
|
122
|
+
ds.emit('onEntityAdded', { injector: i, entity: { id: 1, value: 'x' } });
|
|
123
|
+
expect(listener).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
it('allows rebinding the backing store for tests', async () => {
|
|
126
|
+
await usingAsync(createInjector(), async (i) => {
|
|
127
|
+
const replacement = new InMemoryStore({ model: Test, primaryKey: 'id' });
|
|
128
|
+
i.bind(RebindStore, () => replacement);
|
|
129
|
+
const ds = i.get(RebindDataSet);
|
|
130
|
+
expect(ds.settings.physicalStore).toBe(replacement);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
//# sourceMappingURL=define-data-set.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-data-set.spec.js","sourceRoot":"","sources":["../src/define-data-set.spec.ts"],"names":[],"mappings":"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"}
|
package/esm/helpers.d.ts
CHANGED
|
@@ -1,41 +1,24 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Gets a DataSet for a specific model from the repository.
|
|
11
|
-
*
|
|
12
|
-
* The DataSet is the recommended way to perform all entity mutations (add, update, remove).
|
|
13
|
-
* Writing through the DataSet ensures that authorization, modification hooks, and change events
|
|
14
|
-
* are properly triggered -- which is required for features like entity sync.
|
|
15
|
-
*
|
|
16
|
-
* For server-side or background operations that don't originate from an HTTP request,
|
|
17
|
-
* use {@link useSystemIdentityContext} to create a scoped child injector with elevated privileges.
|
|
18
|
-
*
|
|
19
|
-
* @param injector The Injector instance
|
|
20
|
-
* @param model The Model
|
|
21
|
-
* @param primaryKey The Primary Key field
|
|
22
|
-
* @returns A Repository DataSet for a specific model
|
|
6
|
+
* Resolves the {@link DataSet} for `token` on `injector`. The preferred
|
|
7
|
+
* write gateway for application code — `furystack/no-direct-store-token`
|
|
8
|
+
* forbids resolving the underlying `StoreToken` directly. For server-side
|
|
9
|
+
* or background work, wrap `injector` with `useSystemIdentityContext` from
|
|
10
|
+
* `@furystack/core` to obtain an elevated identity before calling this.
|
|
23
11
|
*
|
|
24
12
|
* @example
|
|
25
13
|
* ```ts
|
|
26
|
-
* import { useSystemIdentityContext } from '@furystack/core'
|
|
27
|
-
* import { getDataSetFor } from '@furystack/repository'
|
|
28
|
-
* import { usingAsync } from '@furystack/utils'
|
|
29
|
-
*
|
|
30
|
-
* // In a background job or service
|
|
31
14
|
* await usingAsync(
|
|
32
15
|
* useSystemIdentityContext({ injector, username: 'background-job' }),
|
|
33
16
|
* async (systemInjector) => {
|
|
34
|
-
* const dataSet = getDataSetFor(systemInjector,
|
|
35
|
-
* await dataSet.add(systemInjector,
|
|
17
|
+
* const dataSet = getDataSetFor(systemInjector, UserDataSet)
|
|
18
|
+
* await dataSet.add(systemInjector, { username: 'alice', roles: [] })
|
|
36
19
|
* },
|
|
37
20
|
* )
|
|
38
21
|
* ```
|
|
39
22
|
*/
|
|
40
|
-
export declare const getDataSetFor: <T, TPrimaryKey extends keyof T
|
|
23
|
+
export declare const getDataSetFor: <T, TPrimaryKey extends keyof T, TWritableData = WithOptionalId<T, TPrimaryKey>>(injector: Injector, token: DataSetToken<T, TPrimaryKey, TWritableData>) => DataSet<T, TPrimaryKey, TWritableData>;
|
|
41
24
|
//# sourceMappingURL=helpers.d.ts.map
|
package/esm/helpers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAExD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,WAAW,SAAS,MAAM,CAAC,EAAE,aAAa,GAAG,cAAc,CAAC,CAAC,EAAE,WAAW,CAAC,EAC1G,UAAU,QAAQ,EAClB,OAAO,YAAY,CAAC,CAAC,EAAE,WAAW,EAAE,aAAa,CAAC,KACjD,OAAO,CAAC,CAAC,EAAE,WAAW,EAAE,aAAa,CAAwB,CAAA"}
|
package/esm/helpers.js
CHANGED
|
@@ -1,40 +1,20 @@
|
|
|
1
|
-
import { Repository } from './repository.js';
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Gets a DataSet for a specific model from the repository.
|
|
10
|
-
*
|
|
11
|
-
* The DataSet is the recommended way to perform all entity mutations (add, update, remove).
|
|
12
|
-
* Writing through the DataSet ensures that authorization, modification hooks, and change events
|
|
13
|
-
* are properly triggered -- which is required for features like entity sync.
|
|
14
|
-
*
|
|
15
|
-
* For server-side or background operations that don't originate from an HTTP request,
|
|
16
|
-
* use {@link useSystemIdentityContext} to create a scoped child injector with elevated privileges.
|
|
17
|
-
*
|
|
18
|
-
* @param injector The Injector instance
|
|
19
|
-
* @param model The Model
|
|
20
|
-
* @param primaryKey The Primary Key field
|
|
21
|
-
* @returns A Repository DataSet for a specific model
|
|
2
|
+
* Resolves the {@link DataSet} for `token` on `injector`. The preferred
|
|
3
|
+
* write gateway for application code — `furystack/no-direct-store-token`
|
|
4
|
+
* forbids resolving the underlying `StoreToken` directly. For server-side
|
|
5
|
+
* or background work, wrap `injector` with `useSystemIdentityContext` from
|
|
6
|
+
* `@furystack/core` to obtain an elevated identity before calling this.
|
|
22
7
|
*
|
|
23
8
|
* @example
|
|
24
9
|
* ```ts
|
|
25
|
-
* import { useSystemIdentityContext } from '@furystack/core'
|
|
26
|
-
* import { getDataSetFor } from '@furystack/repository'
|
|
27
|
-
* import { usingAsync } from '@furystack/utils'
|
|
28
|
-
*
|
|
29
|
-
* // In a background job or service
|
|
30
10
|
* await usingAsync(
|
|
31
11
|
* useSystemIdentityContext({ injector, username: 'background-job' }),
|
|
32
12
|
* async (systemInjector) => {
|
|
33
|
-
* const dataSet = getDataSetFor(systemInjector,
|
|
34
|
-
* await dataSet.add(systemInjector,
|
|
13
|
+
* const dataSet = getDataSetFor(systemInjector, UserDataSet)
|
|
14
|
+
* await dataSet.add(systemInjector, { username: 'alice', roles: [] })
|
|
35
15
|
* },
|
|
36
16
|
* )
|
|
37
17
|
* ```
|
|
38
18
|
*/
|
|
39
|
-
export const getDataSetFor = (injector,
|
|
19
|
+
export const getDataSetFor = (injector, token) => injector.get(token);
|
|
40
20
|
//# sourceMappingURL=helpers.js.map
|
package/esm/helpers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,QAAkB,EAClB,KAAkD,EACV,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA"}
|
package/esm/index.d.ts
CHANGED
package/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAA;AACrC,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,cAAc,CAAA"}
|
package/esm/index.js
CHANGED
package/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAA;AACrC,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,cAAc,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@furystack/repository",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.0.1",
|
|
4
4
|
"description": "Repository implementation for FuryStack",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
},
|
|
39
39
|
"homepage": "https://github.com/furystack/furystack",
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@furystack/core": "^
|
|
42
|
-
"@furystack/inject": "^
|
|
43
|
-
"@furystack/utils": "^
|
|
41
|
+
"@furystack/core": "^17.1.0",
|
|
42
|
+
"@furystack/inject": "^13.0.1",
|
|
43
|
+
"@furystack/utils": "^9.0.1"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"typescript": "^6.0.3",
|
|
47
|
-
"vitest": "^4.1.
|
|
47
|
+
"vitest": "^4.1.7"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": ">=22.0.0"
|
package/src/data-set-setting.ts
CHANGED
|
@@ -1,90 +1,76 @@
|
|
|
1
|
-
import type { FindOptions, PhysicalStore, WithOptionalId } from '@furystack/core'
|
|
1
|
+
import type { AuthorizationError, FindOptions, PhysicalStore, WithOptionalId } from '@furystack/core'
|
|
2
2
|
import type { Injector } from '@furystack/inject'
|
|
3
|
+
import type { DataSet } from './data-set.js'
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
* The result model returned by authorizers
|
|
6
|
-
*/
|
|
5
|
+
/** Discriminated union returned by authorizer callbacks. */
|
|
7
6
|
export type AuthorizationResult = SuccessfullyValidationResult | FailedValidationResult
|
|
8
7
|
|
|
9
8
|
export interface SuccessfullyValidationResult {
|
|
10
|
-
/**
|
|
11
|
-
* Indicates if the validation was successful
|
|
12
|
-
*/
|
|
13
9
|
isAllowed: true
|
|
14
10
|
}
|
|
15
11
|
|
|
16
12
|
export interface FailedValidationResult {
|
|
17
|
-
/**
|
|
18
|
-
* Indicates if the validation was successful
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
13
|
isAllowed: false
|
|
22
|
-
/**
|
|
23
|
-
* The error message string
|
|
24
|
-
*/
|
|
14
|
+
/** Surfaced as the message of the thrown {@link AuthorizationError}. */
|
|
25
15
|
message: string
|
|
26
16
|
}
|
|
27
17
|
|
|
28
18
|
/**
|
|
29
|
-
*
|
|
19
|
+
* Configuration for a {@link DataSet}: backing store + authorization +
|
|
20
|
+
* modification hooks + filter post-processing. Every callback is optional —
|
|
21
|
+
* an empty `DataSetSettings` performs no checks and forwards calls straight
|
|
22
|
+
* to the physical store.
|
|
23
|
+
*
|
|
24
|
+
* Authorization callbacks come in two flavours:
|
|
25
|
+
*
|
|
26
|
+
* - `authorizeX` — runs **before** the entity is loaded. Use for cheap,
|
|
27
|
+
* identity-only checks ("is the user logged in?", "is the user an admin?").
|
|
28
|
+
* When omitted, no pre-check is performed.
|
|
29
|
+
* - `authorizeXEntity` — runs **after** the entity is loaded, with the
|
|
30
|
+
* loaded entity in scope. Use for ownership / row-level checks. Missing
|
|
31
|
+
* entities skip the per-entity check entirely.
|
|
32
|
+
*
|
|
33
|
+
* A callback returning `{ isAllowed: false }` makes the operation throw
|
|
34
|
+
* {@link AuthorizationError}.
|
|
30
35
|
*/
|
|
31
36
|
export interface DataSetSettings<T, TPrimaryKey extends keyof T, TWritableData = WithOptionalId<T, TPrimaryKey>> {
|
|
32
|
-
/**
|
|
33
|
-
* An instance of a physical store
|
|
34
|
-
*/
|
|
35
37
|
physicalStore: PhysicalStore<T, TPrimaryKey, TWritableData>
|
|
36
38
|
|
|
37
|
-
/**
|
|
38
|
-
* Authorizes the entity creation
|
|
39
|
-
*/
|
|
39
|
+
/** Pre-persist authorization for `add`. */
|
|
40
40
|
authorizeAdd?: (options: { injector: Injector; entity: TWritableData }) => Promise<AuthorizationResult>
|
|
41
41
|
|
|
42
|
-
/**
|
|
43
|
-
* modifies an entity before persisting on creation
|
|
44
|
-
*/
|
|
42
|
+
/** Pre-persist mutation for `add` (default fields, hashing, normalisation). */
|
|
45
43
|
modifyOnAdd?: (options: { injector: Injector; entity: TWritableData }) => Promise<TWritableData>
|
|
46
44
|
|
|
47
|
-
/**
|
|
48
|
-
* Authorizes entity updates (before the entity gets loaded from the store)
|
|
49
|
-
*/
|
|
45
|
+
/** Pre-load authorization for `update`. Sees the change, not the entity. */
|
|
50
46
|
authorizeUpdate?: (options: { injector: Injector; change: Partial<T> }) => Promise<AuthorizationResult>
|
|
51
47
|
|
|
52
|
-
/**
|
|
53
|
-
* Authorizes entity updates per loaded entity
|
|
54
|
-
*/
|
|
48
|
+
/** Post-load authorization for `update`. Sees both the loaded entity and the change. */
|
|
55
49
|
authorizeUpdateEntity?: (options: {
|
|
56
50
|
injector: Injector
|
|
57
51
|
entity: T
|
|
58
52
|
change: Partial<T>
|
|
59
53
|
}) => Promise<AuthorizationResult>
|
|
60
54
|
|
|
61
|
-
/**
|
|
62
|
-
* modifies an entity before persisting on update
|
|
63
|
-
*/
|
|
55
|
+
/** Pre-persist mutation for `update`. */
|
|
64
56
|
modifyOnUpdate?: (options: { injector: Injector; id: T[keyof T]; entity: Partial<T> }) => Promise<Partial<T>>
|
|
65
57
|
|
|
66
|
-
/**
|
|
67
|
-
* Authorizes entity removal (before the entity gets loaded from the store)
|
|
68
|
-
*/
|
|
58
|
+
/** Pre-load authorization for `remove`. */
|
|
69
59
|
authorizeRemove?: (options: { injector: Injector }) => Promise<AuthorizationResult>
|
|
70
60
|
|
|
71
|
-
/**
|
|
72
|
-
* Authorizes entity removal per loaded entity
|
|
73
|
-
*/
|
|
61
|
+
/** Post-load authorization for `remove`, per loaded entity. */
|
|
74
62
|
authorizeRemoveEntity?: (options: { injector: Injector; entity: T }) => Promise<AuthorizationResult>
|
|
75
63
|
|
|
76
|
-
/**
|
|
77
|
-
* Authorizes entity retrieval without entity loading
|
|
78
|
-
*/
|
|
64
|
+
/** Pre-load authorization for `get` / `find` / `count`. No entity in scope. */
|
|
79
65
|
authorizeGet?: (options: { injector: Injector }) => Promise<AuthorizationResult>
|
|
80
66
|
|
|
81
|
-
/**
|
|
82
|
-
* Authorizes entity retrieval
|
|
83
|
-
*/
|
|
67
|
+
/** Post-load authorization for `get`, per loaded entity. */
|
|
84
68
|
authorizeGetEntity?: (options: { injector: Injector; entity: T }) => Promise<AuthorizationResult>
|
|
85
69
|
|
|
86
70
|
/**
|
|
87
|
-
*
|
|
71
|
+
* Filter post-processor. Receives the caller's {@link FindOptions} and
|
|
72
|
+
* returns a (potentially augmented) one. Use to inject mandatory clauses
|
|
73
|
+
* (tenant scoping, soft-delete masks) the caller cannot bypass.
|
|
88
74
|
*/
|
|
89
75
|
addFilter?: <TFields extends Array<keyof T>>(options: {
|
|
90
76
|
injector: Injector
|