Skip to content

Commit acfbca6

Browse files
committed
Fixed a race condition when removing multiple groups at once
1 parent e1bd40b commit acfbca6

File tree

9 files changed

+63
-47
lines changed

9 files changed

+63
-47
lines changed

.trunk/trunk.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
version: 0.1
22
cli:
3-
version: 1.14.0
3+
version: 1.15.0
44
plugins:
55
sources:
66
- id: trunk
7-
ref: v1.1.0
7+
ref: v1.2.1
88
uri: https://github.com/trunk-io/plugins
99
lint:
1010
threshold:

packages/plexus-core/src/collection/collection.ts

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ interface PlexusCollectionStore<DataType extends Record<string, any>> {
105105
export type PlexusCollectionInstance<
106106
DataType extends Record<string, any> = Record<string, any>,
107107
Groups extends GroupMap<DataType> = GroupMap<DataType>,
108-
Selectors extends SelectorMap<DataType> = SelectorMap<DataType>,
108+
Selectors extends SelectorMap<DataType> = SelectorMap<DataType>
109109
> = CollectionInstance<DataType, Groups, Selectors>
110110
/**
111111
* A Collection Instance
@@ -114,7 +114,7 @@ export type PlexusCollectionInstance<
114114
export class CollectionInstance<
115115
DataTypeInput extends Record<string, any>,
116116
Groups extends GroupMap<DataTypeInput>,
117-
Selectors extends SelectorMap<DataTypeInput>,
117+
Selectors extends SelectorMap<DataTypeInput>
118118
// ForeignRefs extends boolean = this['config']['foreignKeys'] extends {} ? true : false
119119
> {
120120
private _internalStore: PlexusCollectionStore<DataTypeInput>
@@ -305,7 +305,7 @@ export class CollectionInstance<
305305
return Array.from(addedKeys.values())
306306
}
307307
const collectFn = (
308-
data_: typeof data,
308+
dataToCollect: typeof data,
309309
groups?: KeyOfMap<Groups>[] | KeyOfMap<Groups>,
310310
startedFromInnerBatch?: boolean
311311
) => {
@@ -325,13 +325,13 @@ export class CollectionInstance<
325325
'debug',
326326
`Batched collect call fulfilled for collection ${this.instanceId}`
327327
)
328-
return collectFn(data_, groups, true)
328+
return collectFn(dataToCollect, groups, true)
329329
})
330330
return this
331331
}
332332

333333
const addedKeys: any[] = collectItems(
334-
Array.isArray(data_) ? data_ : [data_]
334+
Array.isArray(dataToCollect) ? dataToCollect : [dataToCollect]
335335
)
336336

337337
const defaultGroupName =
@@ -788,10 +788,9 @@ export class CollectionInstance<
788788
this._internalStore._data.delete(key)
789789
}
790790
// if an array, iterate through the keys and remove them each
791-
if (Array.isArray(keys)) {
792-
keys.forEach(rm)
793-
} else {
794-
rm(keys)
791+
keys = Array.isArray(keys) ? keys : [keys]
792+
for (const key of keys) {
793+
rm(key)
795794
}
796795
this.mount()
797796
return this
@@ -819,28 +818,29 @@ export class CollectionInstance<
819818
keys: DataKey | DataKey[],
820819
groups: KeyOfMap<Groups> | KeyOfMap<Groups>[]
821820
): this {
821+
// normalize
822+
keys = Array.isArray(keys) ? keys : [keys]
823+
groups = Array.isArray(groups) ? groups : [groups]
824+
825+
// abort conditions
826+
if (!keys.length || !groups.length) return this
827+
822828
this.mount()
823829
const rm = (key) => {
824830
key = `${key}`
825-
if (Array.isArray(groups)) {
826-
for (let groupName of groups) {
827-
if (this.isCreatedGroup(groupName)) {
828-
this._internalStore._groups.get(groupName)?.remove(key)
829-
}
830-
}
831-
} else if (typeof groups === 'string') {
832-
if (this.isCreatedGroup(groups)) {
833-
this._internalStore._groups.get(groups)?.remove(key)
831+
for (let groupName of groups) {
832+
if (this.isCreatedGroup(groupName)) {
833+
this._internalStore._groups.get(groupName)?.remove(key)
834834
}
835835
}
836836
}
837837

838838
// if an array, iterate through the keys and remove them from each associated group
839-
if (Array.isArray(keys)) {
840-
keys.forEach(rm)
841-
} else {
842-
rm(keys)
843-
}
839+
this.instance().runtime.batch(() => {
840+
for (let key of keys) {
841+
rm(key)
842+
}
843+
})
844844
return this
845845
// ! This is commented out because the user may still want to keep the data in the collection. If they want to completely delete the data, they should use `.delete()`
846846
// if it's removed from all groups, delete the data entirely
@@ -856,13 +856,9 @@ export class CollectionInstance<
856856
clear(groupNames?: KeyOfMap<Groups> | KeyOfMap<Groups>[]): this {
857857
// this means we want to clear a group, not the whole collection
858858
if (groupNames) {
859-
if (Array.isArray(groupNames)) {
860-
groupNames.forEach(
861-
(groupName) =>
862-
this.isCreatedGroup(groupName) && this.getGroup(groupName).clear()
863-
)
864-
} else {
865-
this.isCreatedGroup(groupNames) && this.getGroup(groupNames).clear()
859+
groupNames = Array.isArray(groupNames) ? groupNames : [groupNames]
860+
for (const groupName of groupNames) {
861+
this.isCreatedGroup(groupName) && this.getGroup(groupName).clear()
866862
}
867863
} else {
868864
this.delete(Array.from(this._internalStore._data.keys()))
@@ -1078,7 +1074,7 @@ export class CollectionInstance<
10781074
export function _collection<
10791075
DataType extends { [key: string]: any },
10801076
Groups extends GroupMap<DataType> = GroupMap<DataType>,
1081-
Selectors extends SelectorMap<DataType> = SelectorMap<DataType>,
1077+
Selectors extends SelectorMap<DataType> = SelectorMap<DataType>
10821078
>(
10831079
instance: () => PlexusInstance,
10841080
_config: PlexusCollectionConfig<DataType> = { primaryKey: 'id' } as const

packages/plexus-core/src/collection/data.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ interface PlexusDataStore {
2525
}
2626

2727
export type PlexusDataInstance<
28-
DataType extends Record<string, any> = Record<string, any>,
28+
DataType extends Record<string, any> = Record<string, any>
2929
> = CollectionData<DataType>
3030
export type DataKey = string
3131

@@ -38,7 +38,7 @@ type DataObjectType<PK extends string = 'id'> = Record<string, any> & {
3838
*/
3939
export class CollectionData<
4040
DataType extends DataObjectType<PK> = any,
41-
PK extends string = string,
41+
PK extends string = string
4242
> extends WatchableMutable<DataType> {
4343
private primaryKey: PK
4444
readonly key: string

packages/plexus-core/src/computed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export class ComputedStateInstance<
9595

9696
this.instance().runtime.log(
9797
'info',
98-
`Mounting Dependencies (${this.deps
98+
`Mounting computed dependencies (${this.deps
9999
.map((v) => v?.id ?? 'unknown')
100100
.join(', ')}) to Computed state ${this.instanceId}`
101101
)

packages/plexus-core/src/instance/engine.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@ export class EventEngine {
1919
*/
2020
halt() {
2121
this.batching = true
22-
return () => {
23-
this.batching = false
24-
this.pendingEventPayloads.forEach((args, eventId) => {
25-
this.emit(eventId, args)
26-
})
27-
this.pendingEventPayloads.clear()
28-
}
22+
return () => this.release()
23+
}
24+
/**
25+
* Emit all stored concatenated events and resume normal event emitting.
26+
*/
27+
release() {
28+
this.batching = false
29+
this.pendingEventPayloads.forEach((args, eventId) => {
30+
this.emit(eventId, args)
31+
})
32+
this.pendingEventPayloads.clear()
2933
}
3034

3135
on(eventId: string, listener: PlexusInternalWatcher, origin?: string) {

packages/plexus-core/src/instance/instance.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,7 @@ export function instance(
202202
'info',
203203
'Instance initialized'
204204
)
205-
} else if (
206-
config &&
207-
!isEqual(config, getPlexusInstance(config?.id || '').settings)
208-
) {
205+
} else if (config) {
209206
getPlexusInstance(config?.id || '').settings = deepMerge(
210207
getPlexusInstance(config?.id || '').settings,
211208
config

tests/basic.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,19 @@ describe('State Core Functionality', () => {
6767
})
6868

6969
test('Checking state().set()', () => {
70+
objectState.reset()
7071
// check .set(value: object)
7172
objectState.set({ a: { b: false } })
7273
// check if the object is actually merged and children props do get overwritten
7374
expect(objectState.value.a?.b).toBe(false)
7475
})
7576

7677
test('Checking state().patch()', () => {
78+
objectState.reset()
79+
console.log(objectState.value)
7780
// can the object deep merge?
7881
objectState.patch({ a: { b: false } })
82+
console.log(objectState.value)
7983
expect(objectState.value.a?.a).toBe(true)
8084
// check that other value is still there
8185
expect(objectState.value.b).toBe(true)

tests/computed.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ describe('Testing Computed State Function', () => {
127127

128128
expect(core.numOfReading.value).toBe(0)
129129
})
130+
// currently, this test fails because the default group updates BEFORE the custom group updates. This means we need to update the engine to only emit the event after all the groups have been updated
130131
test('Computed can watch a default collection group', () => {
131132
instance({ logLevel: 'debug' })
132133
collections.books.getGroup('READING')?.watch((v) => {
@@ -146,7 +147,17 @@ describe('Testing Computed State Function', () => {
146147

147148
expect(core.numOfReadingReactiveToAll.value).toBe(1)
148149

150+
console.log(
151+
`(t6sawo4bjhkv47839d3) groups pre-delete: ${core.collections.books.getGroupsOf(
152+
't6sawo4bjhkv47839d3'
153+
)}`
154+
)
149155
core.collections.books.delete('t6sawo4bjhkv47839d3')
156+
console.log(
157+
`(t6sawo4bjhkv47839d3) groups post-delete: ${core.collections.books.getGroupsOf(
158+
't6sawo4bjhkv47839d3'
159+
)}`
160+
)
150161
console.log(
151162
'numOfReadingReactiveToAll.value',
152163
core.numOfReadingReactiveToAll.value,

tests/state.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,8 @@ describe('Testing State Function', () => {
173173
stateWithFetchFnTest.fetch()
174174
expect(stateWithFetchFnTest.value).toBe('a new string!' + 'new value')
175175
})
176+
test('persist', () => {
177+
const value = state(1).persist('test')
178+
expect(value.value).toBe(1)
179+
})
176180
})

0 commit comments

Comments
 (0)