@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.
Files changed (51) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +74 -36
  3. package/esm/data-set-setting.d.ts +30 -44
  4. package/esm/data-set-setting.d.ts.map +1 -1
  5. package/esm/data-set.d.ts +17 -68
  6. package/esm/data-set.d.ts.map +1 -1
  7. package/esm/data-set.js +18 -69
  8. package/esm/data-set.js.map +1 -1
  9. package/esm/data-set.spec.d.ts +2 -0
  10. package/esm/data-set.spec.d.ts.map +1 -0
  11. package/esm/data-set.spec.js +348 -0
  12. package/esm/data-set.spec.js.map +1 -0
  13. package/esm/define-data-set.d.ts +71 -0
  14. package/esm/define-data-set.d.ts.map +1 -0
  15. package/esm/define-data-set.js +65 -0
  16. package/esm/define-data-set.js.map +1 -0
  17. package/esm/define-data-set.spec.d.ts +2 -0
  18. package/esm/define-data-set.spec.d.ts.map +1 -0
  19. package/esm/define-data-set.spec.js +134 -0
  20. package/esm/define-data-set.spec.js.map +1 -0
  21. package/esm/helpers.d.ts +12 -29
  22. package/esm/helpers.d.ts.map +1 -1
  23. package/esm/helpers.js +8 -28
  24. package/esm/helpers.js.map +1 -1
  25. package/esm/index.d.ts +2 -2
  26. package/esm/index.d.ts.map +1 -1
  27. package/esm/index.js +2 -2
  28. package/esm/index.js.map +1 -1
  29. package/package.json +5 -5
  30. package/src/data-set-setting.ts +32 -46
  31. package/src/data-set.spec.ts +397 -0
  32. package/src/data-set.ts +28 -72
  33. package/src/define-data-set.spec.ts +145 -0
  34. package/src/define-data-set.ts +122 -0
  35. package/src/helpers.ts +14 -33
  36. package/src/index.ts +2 -2
  37. package/esm/dataset.spec.d.ts +0 -2
  38. package/esm/dataset.spec.d.ts.map +0 -1
  39. package/esm/dataset.spec.js +0 -552
  40. package/esm/dataset.spec.js.map +0 -1
  41. package/esm/repository.d.ts +0 -15
  42. package/esm/repository.d.ts.map +0 -1
  43. package/esm/repository.js +0 -50
  44. package/esm/repository.js.map +0 -1
  45. package/esm/repository.spec.d.ts +0 -2
  46. package/esm/repository.spec.d.ts.map +0 -1
  47. package/esm/repository.spec.js +0 -18
  48. package/esm/repository.spec.js.map +0 -1
  49. package/src/dataset.spec.ts +0 -641
  50. package/src/repository.spec.ts +0 -19
  51. 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":"AAAA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD,MAAM,IAAI;CAGT;AAED,4EAA4E;AAC5E,2EAA2E;AAC3E,4EAA4E;AAC5E,MAAM,UAAU,GAAG,WAAW,CAAC;IAC7B,IAAI,EAAE,iBAAiB;IACvB,KAAK,EAAE,IAAI;IACX,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;CACpE,CAAC,CAAA;AACF,MAAM,cAAc,GAAG,WAAW,CAAC;IACjC,IAAI,EAAE,qBAAqB;IAC3B,KAAK,EAAE,IAAI;IACX,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;CACpE,CAAC,CAAA;AACF,MAAM,SAAS,GAAG,WAAW,CAAC;IAC5B,IAAI,EAAE,gBAAgB;IACtB,KAAK,EAAE,IAAI;IACX,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;CACpE,CAAC,CAAA;AACF,MAAM,UAAU,GAAG,WAAW,CAAC;IAC7B,IAAI,EAAE,iBAAiB;IACvB,KAAK,EAAE,IAAI;IACX,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;CACpE,CAAC,CAAA;AACF,MAAM,WAAW,GAAG,WAAW,CAAC;IAC9B,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,IAAI;IACX,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;CACpE,CAAC,CAAA;AACF,MAAM,YAAY,GAAG,WAAW,CAAC;IAC/B,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,IAAI;IACX,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;CACpE,CAAC,CAAA;AACF,MAAM,WAAW,GAAG,WAAW,CAAC;IAC9B,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,IAAI;IACX,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;CACpE,CAAC,CAAA;AAEF,MAAM,cAAc,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;AAC9B,MAAM,YAAY,GAAG,aAAa,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAA;AACpF,MAAM,gBAAgB,GAAG,aAAa,CAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAA;AAChG,MAAM,WAAW,GAAG,aAAa,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;AACjF,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAC9E,6EAA6E;AAC7E,4EAA4E;AAC5E,iFAAiF;AACjF,mEAAmE;AACnE,MAAM,YAAY,GAAG,aAAa,CAAC;IACjC,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,UAAU;IACjB,QAAQ,EAAE;QACR,WAAW,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;YAChC,cAAc,EAAE,CAAA;YAChB,OAAO,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAM,CAAC,WAAW,EAAE,EAAE,CAAA;QAC1D,CAAC;KACF;CACF,CAAC,CAAA;AACF,MAAM,aAAa,GAAG,aAAa,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;AACvF,MAAM,cAAc,GAAG,aAAa,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAA;AAC1F,MAAM,aAAa,GAAG,aAAa,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;AAEvF,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,UAAU,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC7C,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;YAC9B,MAAM,CAAC,EAAE,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAA;YAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAA;QAC3D,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,UAAU,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC7C,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAA;QAC/D,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACpC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,cAAc,CAAC,SAAS,EAAE,CAAA;QAC1B,MAAM,UAAU,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC7C,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;YAC9B,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAA;YACxC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;YACjC,MAAM,CAAC,cAAc,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAA;YAC/C,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACnC,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAChE,MAAM,UAAU,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC7C,MAAM,KAAK,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;YACrB,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;YAC/B,EAAE,CAAC,WAAW,CAAC,eAAe,EAAE,KAAK,CAAC,CAAA;YACtC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;YACtC,MAAM,CAAC,KAAK,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAA;QAChG,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,CAAC,GAAG,cAAc,EAAE,CAAA;QAC1B,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;QAChC,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;QACxB,EAAE,CAAC,WAAW,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAA;QACzC,MAAM,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAA;QAC9B,gEAAgE;QAChE,sCAAsC;QACtC,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAA;QACxE,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;IACzC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,MAAM,UAAU,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC7C,MAAM,WAAW,GAAG,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAA;YACxE,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,CAAA;YACtC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;YAC/B,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACrD,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
package/esm/helpers.d.ts CHANGED
@@ -1,41 +1,24 @@
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
- * Returns a Repository on an injector
5
- * @param injector The Injector instance
6
- * @returns The Repository instance
7
- */
8
- export declare const getRepository: (injector: Injector) => Repository;
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, MyModel, 'id')
35
- * await dataSet.add(systemInjector, newEntity)
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>(injector: Injector, model: Constructable<T>, primaryKey: TPrimaryKey) => import("./data-set.js").DataSet<T, TPrimaryKey, import("@furystack/core").WithOptionalId<T, TPrimaryKey>>;
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
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE5C;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,UAAU,QAAQ,eAAqC,CAAA;AAErF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,WAAW,SAAS,MAAM,CAAC,EAC1D,UAAU,QAAQ,EAClB,OAAO,aAAa,CAAC,CAAC,CAAC,EACvB,YAAY,WAAW,8GAC6C,CAAA"}
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
- * Returns a Repository on an injector
4
- * @param injector The Injector instance
5
- * @returns The Repository instance
6
- */
7
- export const getRepository = (injector) => injector.getInstance(Repository);
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, MyModel, 'id')
34
- * await dataSet.add(systemInjector, newEntity)
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, model, primaryKey) => injector.getInstance(Repository).getDataSetFor(model, primaryKey);
19
+ export const getDataSetFor = (injector, token) => injector.get(token);
40
20
  //# sourceMappingURL=helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE5C;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,QAAkB,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,UAAU,CAAC,CAAA;AAErF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,QAAkB,EAClB,KAAuB,EACvB,UAAuB,EACvB,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,UAAU,CAAC,CAAA"}
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
@@ -1,5 +1,5 @@
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';
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA"}
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
@@ -1,5 +1,5 @@
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';
5
5
  //# sourceMappingURL=index.js.map
package/esm/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA"}
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": "10.1.11",
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": "^16.0.4",
42
- "@furystack/inject": "^12.0.36",
43
- "@furystack/utils": "^8.2.5"
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.4"
47
+ "vitest": "^4.1.7"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=22.0.0"
@@ -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
- * Model for authorizers
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
- * Additional filter parsing to be appended per filter / query
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