Skip to content

Commit 6794d9a

Browse files
committed
Implement everything except lens
1 parent a7cbe2a commit 6794d9a

File tree

5 files changed

+342
-193
lines changed

5 files changed

+342
-193
lines changed

packages/core/src/keyval.ts

Lines changed: 36 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,10 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
160160
// @ts-expect-error bad implementation
161161
let getKeyRaw;
162162
let shape: Shape;
163-
let props;
164163
if (typeof options === 'function') {
165164
create = options as any;
166165
} else {
167-
({ key: getKeyRaw, shape = {} as Shape, props, create } = options);
166+
({ key: getKeyRaw, shape = {} as Shape, create } = options);
168167
}
169168
type Enriched = Input & ModelEnhance;
170169
let kvModel:
@@ -181,20 +180,22 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
181180
Shape
182181
>
183182
| undefined;
184-
if (props && create) {
183+
if (create) {
185184
// @ts-expect-error typecast
186-
kvModel = model({ props, create });
185+
kvModel = model({ create });
187186
}
188187
type ListState = {
189188
items: Enriched[];
189+
// @ts-expect-error type mismatch
190190
instances: Array<InstanceOf<NonNullable<typeof kvModel>>>;
191191
keys: Array<string | number>;
192192
};
193-
const getKey =
194-
typeof getKeyRaw === 'function'
193+
const getKey = !kvModel
194+
? typeof getKeyRaw === 'function'
195195
? getKeyRaw
196196
: // @ts-expect-error bad implementation
197-
(entity: Input) => entity[getKeyRaw] as string | number;
197+
(entity: Input) => entity[getKeyRaw] as string | number
198+
: (entity: Input) => entity[kvModel.keyField] as string | number;
198199
const $entities = createStore<ListState>({
199200
items: [],
200201
instances: [],
@@ -250,19 +251,11 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
250251
// @ts-expect-error some issues with types
251252
const instance = spawn(kvModel, item1);
252253
withRegion(instance.region, () => {
253-
const storeOutputs = {} as Record<string, Store<any>>;
254-
for (const key in instance.props) {
255-
const value = instance.props[key];
256-
storeOutputs[key] = isKeyval(value)
257-
? value.$items
258-
: (value as Store<any>);
259-
}
260-
const $enriching = combine(storeOutputs);
261254
// obviosly dirty hack, wont make it way to release
262-
const enriching = $enriching.getState();
263-
freshState.items.push({ ...inputItem, ...enriching } as Enriched);
255+
const enriching = instance.output.getState();
256+
freshState.items.push(enriching as Enriched);
264257
sample({
265-
source: $enriching,
258+
source: instance.output,
266259
fn: (partial) => ({
267260
key,
268261
partial: partial as Partial<Enriched>,
@@ -317,18 +310,18 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
317310
freshState.items[idx] = newItem;
318311
if (kvModel) {
319312
const instance = freshState.instances[idx];
320-
for (const key in instance.inputs) {
321-
// @ts-expect-error cannot read newItem[key], but its ok
322-
const upd = newItem[key];
323-
// @ts-expect-error cannot read oldItem[key], but its ok
324-
if (upd !== oldItem[key]) {
325-
launch({
326-
target: instance.inputs[key],
327-
params: upd,
328-
defer: true,
329-
});
330-
}
331-
}
313+
// for (const key in instance.inputs) {
314+
// // @ts-expect-error cannot read newItem[key], but its ok
315+
// const upd = newItem[key];
316+
// // @ts-expect-error cannot read oldItem[key], but its ok
317+
// if (upd !== oldItem[key]) {
318+
// launch({
319+
// target: instance.inputs[key],
320+
// params: upd,
321+
// defer: true,
322+
// });
323+
// }
324+
// }
332325
}
333326
}
334327

@@ -373,7 +366,6 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
373366
if (kvModel) {
374367
for (const instance of oldState.instances) {
375368
clearNode(instance.region);
376-
instance.unmount();
377369
}
378370
}
379371
const state: ListState = {
@@ -384,19 +376,18 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
384376
for (const item of newItems) {
385377
const key = getKey(item);
386378
runNewItemInstance(state, key, item);
387-
/** new instance is always last */
388-
const instance = state.instances[state.instances.length - 1];
389-
for (const key in item) {
390-
if (key in writableOutputs) {
391-
launch({
392-
target:
393-
writableOutputs[key] === 'keyval'
394-
? (instance.props[key] as any).edit.replaceAll
395-
: // @ts-expect-error typescript is broken here
396-
instance.props[key],
397-
params: item[key],
398-
defer: true,
399-
});
379+
if (kvModel) {
380+
/** new instance is always last */
381+
const instance = state.instances[state.instances.length - 1];
382+
for (const field of kvModel.keyvalFields) {
383+
// @ts-expect-error type mismatch, item is iterable
384+
if (field in item) {
385+
launch({
386+
target: instance.keyvalShape[field].edit.replaceAll,
387+
params: (item as any)[field],
388+
defer: true,
389+
});
390+
}
400391
}
401392
}
402393
}
@@ -467,7 +458,6 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
467458
const [instance] = state.instances.splice(idx, 1);
468459
if (instance) {
469460
clearNode(instance.region);
470-
instance.unmount();
471461
}
472462
}
473463
return state;
@@ -503,7 +493,6 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
503493
});
504494

505495
const api = {} as Record<string, EventCallable<any>>;
506-
const writableOutputs = {} as Record<string, 'store' | 'keyval'>;
507496

508497
let structShape: any = null;
509498

@@ -519,12 +508,9 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
519508
initShape[key] = createEffect(() => {});
520509
}
521510
});
522-
const consoleError = console.error;
523-
console.error = () => {};
524511
// @ts-expect-error type issues
525512
const instance = spawn(kvModel, initShape);
526-
console.error = consoleError;
527-
instance.unmount();
513+
clearNode(instance.region);
528514
structShape = {
529515
type: 'structKeyval',
530516
getKey,
@@ -564,14 +550,6 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
564550
return state;
565551
});
566552
}
567-
for (const key in instance.props) {
568-
const value = instance.props[key];
569-
if (isKeyval(value)) {
570-
writableOutputs[key] = 'keyval';
571-
} else if (is.store(value) && is.targetable(value)) {
572-
writableOutputs[key] = 'store';
573-
}
574-
}
575553
}
576554

577555
return {
@@ -580,7 +558,6 @@ export function keyval<Input, ModelEnhance, Api, Shape>(
580558
// @ts-expect-error bad implementation
581559
__lens: shape,
582560
__struct: structShape,
583-
__getKey: getKey,
584561
$items: $entities.map(({ items }) => items),
585562
$keys: $entities.map(({ keys }) => keys),
586563
edit: {

packages/core/src/model.ts

Lines changed: 117 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
import { Store, Event, Effect, createStore, createEffect, is } from 'effector';
1+
import {
2+
Store,
3+
Event,
4+
Effect,
5+
createNode,
6+
withRegion,
7+
createEvent,
8+
clearNode,
9+
is,
10+
Node,
11+
} from 'effector';
212

313
import type {
414
Model,
@@ -8,8 +18,9 @@ import type {
818
Keyval,
919
Show,
1020
ConvertToLensShape,
21+
FactoryPathMap,
1122
} from './types';
12-
import { define, isDefine } from './define';
23+
import { define, isDefine, isKeyval } from './define';
1324

1425
export function model<
1526
Input extends {
@@ -60,23 +71,53 @@ export function model<
6071
// }
6172
> {
6273
const shape = {} as any;
63-
for (const key in props) {
64-
const value = props[key];
65-
if (isDefine.any(value)) {
66-
shape[key] = value;
67-
} else if (is.store(value)) {
68-
shape[key] = define.store<any>();
69-
} else if (is.event(value)) {
70-
shape[key] = define.event<any>();
71-
} else if (is.effect(value) || typeof value === 'function') {
72-
shape[key] = define.effect<any, any, any>();
73-
} else {
74-
shape[key] = define.store<any>();
75-
}
74+
const region = createNode({ regional: true });
75+
const {
76+
state = {} as Output,
77+
key,
78+
api = {} as Api,
79+
optional = [],
80+
...rest
81+
} = withRegion(region, () => {
82+
const onMount = createEvent();
83+
return create({ onMount });
84+
});
85+
86+
if (Object.keys(rest).length > 0) {
87+
throw Error(
88+
`create should return only fields 'key', 'state', 'api' or 'optional'`,
89+
);
90+
} else if (typeof key !== 'string') {
91+
throw Error(`key field should be a string`);
92+
} else if (!(key in state)) {
93+
throw Error(`key field "${key}" should be in state`);
94+
} else if (!is.store(state[key]) || !is.targetable(state[key])) {
95+
throw Error(`key field "${key}" should be writable store`);
96+
} else if (optional.includes(key)) {
97+
throw Error(`key field "${key}" cannot be optional`);
7698
}
99+
100+
const requiredStateFields = Object.keys(state).filter(
101+
(field: keyof Output) =>
102+
is.store(state[field]) &&
103+
is.targetable(state[field]) &&
104+
!optional.includes(field as string),
105+
) as Array<keyof Input>;
106+
107+
const factoryStatePaths = collectFactoryPaths(state, region);
108+
109+
const keyvalFields = Object.keys(state).filter((field: keyof Output) =>
110+
isKeyval(state[field]),
111+
) as Array<keyof Output>;
112+
113+
clearNode(region);
77114
return {
78115
type: 'model',
79116
create,
117+
keyField: key,
118+
requiredStateFields,
119+
keyvalFields,
120+
factoryStatePaths,
80121
propsConfig: props,
81122
output: null as unknown as any,
82123
// api: null as unknown as any,
@@ -85,3 +126,64 @@ export function model<
85126
__lens: {} as any,
86127
};
87128
}
129+
130+
// function withInitState(fn) {
131+
// const initRegion = createNode({ regional: true });
132+
133+
// const state = withRegion(initRegion, () => fn());
134+
// const factoryPathToStateKey = collectFactoryPaths(state, initRegion);
135+
// clearNode(initRegion);
136+
137+
// return (initState: Record<string, any> = {}) => {
138+
// const region = createNode({ regional: true });
139+
// installStateHooks(initState, region, factoryPathToStateKey);
140+
// return [withRegion(region, () => fn()), region];
141+
// };
142+
// }
143+
144+
function collectFactoryPaths(state: Record<string, any>, initRegion: Node) {
145+
const factoryPathToStateKey: FactoryPathMap = new Map();
146+
for (const key in state) {
147+
const value = state[key];
148+
if (is.store(value) && is.targetable(value)) {
149+
const path = findNodeInTree((value as any).graphite, initRegion);
150+
if (path) {
151+
let nestedFactoryPathMap = factoryPathToStateKey;
152+
for (let i = 0; i < path.length; i++) {
153+
const step = path[i];
154+
const isLastStep = i === path.length - 1;
155+
if (isLastStep) {
156+
nestedFactoryPathMap.set(step, key);
157+
} else {
158+
let childFactoryPathMap = nestedFactoryPathMap.get(step);
159+
if (!childFactoryPathMap) {
160+
childFactoryPathMap = new Map();
161+
nestedFactoryPathMap.set(step, childFactoryPathMap);
162+
}
163+
nestedFactoryPathMap = childFactoryPathMap as FactoryPathMap;
164+
}
165+
}
166+
}
167+
}
168+
}
169+
return factoryPathToStateKey;
170+
}
171+
172+
function findNodeInTree(
173+
searchNode: Node,
174+
currentNode: Node,
175+
path: number[] = [],
176+
): number[] | void {
177+
const idx = currentNode.family.links.findIndex((e) => e === searchNode);
178+
if (idx !== -1) {
179+
return [...path, idx];
180+
} else {
181+
for (let i = 0; i < currentNode.family.links.length; i++) {
182+
const linkNode = currentNode.family.links[i];
183+
if (linkNode.meta.isRegion) {
184+
const result = findNodeInTree(searchNode, linkNode, [...path, i]);
185+
if (result) return result;
186+
}
187+
}
188+
}
189+
}

0 commit comments

Comments
 (0)