Skip to content

Commit 4c93b20

Browse files
committed
Add editField api for easier fields edit
1 parent 2646686 commit 4c93b20

File tree

4 files changed

+151
-5
lines changed

4 files changed

+151
-5
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { expect, test } from 'vitest';
2+
import { createStore, combine } from 'effector';
3+
import { keyval } from '@effector/model';
4+
5+
test('provide event for field update', () => {
6+
const entities = keyval(() => {
7+
const $id = createStore('');
8+
const $count = createStore(0);
9+
return {
10+
key: 'id',
11+
state: {
12+
id: $id,
13+
count: $count,
14+
},
15+
optional: ['count'],
16+
};
17+
});
18+
entities.edit.add({ id: 'foo' });
19+
entities.editField.count({ key: 'foo', data: 1 });
20+
expect(entities.$items.getState()).toEqual([{ id: 'foo', count: 1 }]);
21+
});
22+
23+
test('provide event for update multiple items at once', () => {
24+
const entities = keyval(() => {
25+
const $id = createStore('');
26+
const $count = createStore(0);
27+
return {
28+
key: 'id',
29+
state: {
30+
id: $id,
31+
count: $count,
32+
},
33+
optional: ['count'],
34+
};
35+
});
36+
entities.edit.add([{ id: 'foo' }, { id: 'bar' }]);
37+
entities.editField.count({ key: ['foo', 'bar'], data: [1, 2] });
38+
expect(entities.$items.getState()).toEqual([
39+
{ id: 'foo', count: 1 },
40+
{ id: 'bar', count: 2 },
41+
]);
42+
});
43+
44+
test('derived stores are omitted', () => {
45+
const entities = keyval(() => {
46+
const $id = createStore('');
47+
return {
48+
key: 'id',
49+
state: {
50+
id: $id,
51+
idSize: combine($id, (id) => id.length),
52+
},
53+
};
54+
});
55+
//@ts-expect-error
56+
expect(entities.editField.idSize).toBe(undefined);
57+
});
58+
59+
test.skip('inner keyval support', () => {
60+
const entities = keyval(() => {
61+
const $id = createStore('');
62+
const childs = keyval(() => {
63+
const $id = createStore('');
64+
const $count = createStore(0);
65+
return {
66+
key: 'id',
67+
state: {
68+
id: $id,
69+
count: $count,
70+
},
71+
};
72+
});
73+
return {
74+
key: 'id',
75+
state: {
76+
id: $id,
77+
childs,
78+
},
79+
};
80+
});
81+
entities.edit.add({
82+
id: 'foo',
83+
childs: [
84+
{ id: 'childA', count: 0 },
85+
{ id: 'childB', count: 1 },
86+
],
87+
});
88+
entities.editField.childs({ key: 'foo', data: [{ id: 'childC', count: 2 }] });
89+
expect(entities.$items.getState()).toEqual([
90+
{ id: 'foo', childs: [{ id: 'childC', count: 2 }] },
91+
]);
92+
});

packages/core/src/keyval.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {
2323
StructShape,
2424
OneOfShapeDef,
2525
EntityShapeDef,
26+
KeyOrKeys,
2627
} from './types';
2728
import { spawn } from './spawn';
2829
import { model } from './model';
@@ -531,7 +532,7 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
531532
const api = {} as Record<string, EventCallable<any>>;
532533

533534
let structShape: any = null;
534-
535+
const editField = {} as any;
535536
if (kvModel) {
536537
// @ts-expect-error type issues
537538
const instance = spawn(kvModel, {});
@@ -575,6 +576,42 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
575576
return state;
576577
});
577578
}
579+
//TODO add support for generated keys
580+
if (keyField) {
581+
const structShape = kvModel.__struct!.shape;
582+
for (const field in structShape) {
583+
const fieldStruct = structShape[field];
584+
if (fieldStruct.type === 'structUnit') {
585+
// derived stores are not supported
586+
if (fieldStruct.unit === 'store' && fieldStruct.derived) {
587+
continue;
588+
}
589+
const fieldEditor = updateSome.prepend(
590+
(upd: { key: KeyOrKeys; data: any }) => {
591+
const keySet = Array.isArray(upd.key) ? upd.key : [upd.key];
592+
const dataSet: Array<any> = Array.isArray(upd.key)
593+
? upd.data
594+
: [upd.data];
595+
const results = [] as Partial<Input>[];
596+
for (let i = 0; i < keySet.length; i++) {
597+
const keyValue = keySet[i];
598+
const dataValue = dataSet[i];
599+
const item = {} as Partial<Input>;
600+
//@ts-expect-error
601+
item[keyField] = keyValue;
602+
//@ts-expect-error
603+
item[field] = dataValue;
604+
results.push(item);
605+
}
606+
return results;
607+
},
608+
);
609+
editField[field] = fieldEditor;
610+
} else {
611+
// TODO keyval support
612+
}
613+
}
614+
}
578615
} else if (shape!) {
579616
const itemStructShape: StructKeyval['shape'] = {};
580617
structShape = {
@@ -612,6 +649,7 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
612649
remove: removeMany,
613650
map,
614651
},
652+
editField,
615653
};
616654
}
617655

packages/core/src/model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export function model<
126126
: {
127127
type: 'structUnit',
128128
unit: 'store',
129+
derived: !is.targetable(state[key] as any),
129130
};
130131
defaultState[key] = is.store(state[key]) ? state[key].getState() : [];
131132
}

packages/core/src/types.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,16 @@ export type LensEvent<T> = {
136136
};
137137

138138
/** internal representation of model structure, unit leaf */
139-
export type StructUnit = {
140-
type: 'structUnit';
141-
unit: 'store' | 'event' | 'effect';
142-
};
139+
export type StructUnit =
140+
| {
141+
type: 'structUnit';
142+
unit: 'event' | 'effect';
143+
}
144+
| {
145+
type: 'structUnit';
146+
unit: 'store';
147+
derived: boolean;
148+
};
143149

144150
/** internal representation of model structure, model shape */
145151
export type StructShape = {
@@ -213,6 +219,15 @@ export type Keyval<Input, Enriched, Api, Shape> = {
213219
upsert?: boolean;
214220
}>;
215221
};
222+
editField: {
223+
[K in keyof Input]-?: EventCallable<
224+
| { key: string | number; data: Exclude<Input[K], undefined> }
225+
| {
226+
key: Array<string | number>;
227+
data: Exclude<Input[K], undefined>[];
228+
}
229+
>;
230+
};
216231
// private
217232
__lens: Shape;
218233
// private

0 commit comments

Comments
 (0)