@livestore/livestore 0.0.55-dev.0 → 0.0.55-dev.2
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/dist/.tsbuildinfo +1 -1
- package/dist/store-devtools.d.ts +18 -0
- package/dist/store-devtools.d.ts.map +1 -0
- package/dist/store-devtools.js +145 -0
- package/dist/store-devtools.js.map +1 -0
- package/dist/store.d.ts +1 -10
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +48 -189
- package/dist/store.js.map +1 -1
- package/dist/utils/data-structures.d.ts +10 -0
- package/dist/utils/data-structures.d.ts.map +1 -0
- package/dist/utils/data-structures.js +32 -0
- package/dist/utils/data-structures.js.map +1 -0
- package/package.json +5 -5
- package/src/store-devtools.ts +213 -0
- package/src/store.ts +64 -255
- package/src/utils/data-structures.ts +36 -0
package/src/store.ts
CHANGED
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
BootDb,
|
|
3
3
|
BootStatus,
|
|
4
|
-
DebugInfo,
|
|
5
4
|
ParamsObject,
|
|
6
5
|
PreparedBindValues,
|
|
7
6
|
ResetMode,
|
|
8
7
|
StoreAdapter,
|
|
9
8
|
StoreAdapterFactory,
|
|
10
9
|
} from '@livestore/common'
|
|
11
|
-
import {
|
|
12
|
-
Devtools,
|
|
13
|
-
getExecArgsFromMutation,
|
|
14
|
-
liveStoreVersion,
|
|
15
|
-
prepareBindValues,
|
|
16
|
-
UnexpectedError,
|
|
17
|
-
} from '@livestore/common'
|
|
10
|
+
import { Devtools, getExecArgsFromMutation, prepareBindValues, UnexpectedError } from '@livestore/common'
|
|
18
11
|
import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
19
|
-
import { makeMutationEventSchemaMemo } from '@livestore/common/schema'
|
|
20
|
-
import { assertNever, makeNoopTracer, shouldNeverHappen
|
|
12
|
+
import { makeMutationEventSchemaMemo, SCHEMA_META_TABLE, SCHEMA_MUTATIONS_META_TABLE } from '@livestore/common/schema'
|
|
13
|
+
import { assertNever, makeNoopTracer, shouldNeverHappen } from '@livestore/utils'
|
|
21
14
|
import { cuid } from '@livestore/utils/cuid'
|
|
22
15
|
import {
|
|
16
|
+
BrowserChannel,
|
|
23
17
|
Effect,
|
|
18
|
+
Either,
|
|
24
19
|
Exit,
|
|
25
20
|
FiberSet,
|
|
26
21
|
Inspectable,
|
|
@@ -38,11 +33,12 @@ import * as otel from '@opentelemetry/api'
|
|
|
38
33
|
import type { GraphQLSchema } from 'graphql'
|
|
39
34
|
|
|
40
35
|
import { globalReactivityGraph } from './global-state.js'
|
|
41
|
-
import {
|
|
36
|
+
import { MainDatabaseWrapper } from './MainDatabaseWrapper.js'
|
|
42
37
|
import type { StackInfo } from './react/utils/stack-info.js'
|
|
43
38
|
import type { DebugRefreshReasonBase, Ref } from './reactive.js'
|
|
44
|
-
import { NOT_REFRESHED_YET } from './reactive.js'
|
|
45
39
|
import type { LiveQuery, QueryContext, ReactivityGraph } from './reactiveQueries/base-class.js'
|
|
40
|
+
import { connectStoreToDevtools } from './store-devtools.js'
|
|
41
|
+
import { ReferenceCountedSet } from './utils/data-structures.js'
|
|
46
42
|
import { downloadBlob } from './utils/dev.js'
|
|
47
43
|
import { getDurationMsFromSpan } from './utils/otel.js'
|
|
48
44
|
|
|
@@ -154,6 +150,7 @@ export class Store<
|
|
|
154
150
|
|
|
155
151
|
readonly __mutationEventSchema
|
|
156
152
|
|
|
153
|
+
// #region constructor
|
|
157
154
|
private constructor({
|
|
158
155
|
adapter,
|
|
159
156
|
schema,
|
|
@@ -201,8 +198,20 @@ export class Store<
|
|
|
201
198
|
queriesSpanContext: otelQueriesSpanContext,
|
|
202
199
|
}
|
|
203
200
|
|
|
201
|
+
// TODO find a better way to detect if we're running LiveStore in the LiveStore devtools
|
|
202
|
+
// But for now this is a good enough approximation with little downsides
|
|
203
|
+
const isRunningInDevtools = disableDevtools === true
|
|
204
|
+
|
|
204
205
|
// Need a set here since `schema.tables` might contain duplicates and some componentStateTables
|
|
205
|
-
const allTableNames = new Set(
|
|
206
|
+
const allTableNames = new Set(
|
|
207
|
+
// NOTE we're excluding the LiveStore schema and mutations tables as they are not user-facing
|
|
208
|
+
// unless LiveStore is running in the devtools
|
|
209
|
+
isRunningInDevtools
|
|
210
|
+
? this.schema.tables.keys()
|
|
211
|
+
: Array.from(this.schema.tables.keys()).filter(
|
|
212
|
+
(_) => _ !== SCHEMA_META_TABLE && _ !== SCHEMA_MUTATIONS_META_TABLE,
|
|
213
|
+
),
|
|
214
|
+
)
|
|
206
215
|
const existingTableRefs = new Map(
|
|
207
216
|
Array.from(this.reactivityGraph.atoms.values())
|
|
208
217
|
.filter((_): _ is Ref<any, any, any> => _._tag === 'ref' && _.label?.startsWith('tableRef:') === true)
|
|
@@ -248,6 +257,8 @@ export class Store<
|
|
|
248
257
|
}).pipe(Effect.scoped, Effect.withSpan('LiveStore:store-constructor'), FiberSet.run(fiberSet), runEffectFork)
|
|
249
258
|
}
|
|
250
259
|
|
|
260
|
+
// #endregion constructor
|
|
261
|
+
|
|
251
262
|
static createStore = <TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema = LiveStoreSchema>(
|
|
252
263
|
storeOptions: StoreOptions<TGraphQLContext, TSchema>,
|
|
253
264
|
parentSpan: otel.Span,
|
|
@@ -314,6 +325,7 @@ export class Store<
|
|
|
314
325
|
await FiberSet.clear(this.fiberSet).pipe(Effect.withSpan('Store:destroy'), runEffectPromise)
|
|
315
326
|
}
|
|
316
327
|
|
|
328
|
+
// #region mutate
|
|
317
329
|
mutate: {
|
|
318
330
|
<const TMutationArg extends ReadonlyArray<MutationEvent.ForSchema<TSchema>>>(...list: TMutationArg): void
|
|
319
331
|
(
|
|
@@ -549,6 +561,7 @@ export class Store<
|
|
|
549
561
|
},
|
|
550
562
|
)
|
|
551
563
|
}
|
|
564
|
+
// #endregion mutate
|
|
552
565
|
|
|
553
566
|
/**
|
|
554
567
|
* Directly execute a SQL query on the Store.
|
|
@@ -578,218 +591,48 @@ export class Store<
|
|
|
578
591
|
})
|
|
579
592
|
|
|
580
593
|
// #region devtools
|
|
581
|
-
// TODO shutdown behaviour
|
|
582
594
|
private bootDevtools = () =>
|
|
583
595
|
Effect.gen(this, function* () {
|
|
584
|
-
const
|
|
585
|
-
message: typeof Devtools.DevtoolsWindowMessage.MessageForContentscript.Type,
|
|
586
|
-
) => {
|
|
587
|
-
window.postMessage(Schema.encodeSync(Devtools.DevtoolsWindowMessage.MessageForContentscript)(message), '*')
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.LoadIframe.make({}))
|
|
596
|
+
// const webBridgeBroadcastChannel = yield* Devtools.WebBridge.makeBroadcastChannel()
|
|
591
597
|
|
|
598
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias, unicorn/no-this-assignment
|
|
599
|
+
const store = this
|
|
592
600
|
const channelId = this.adapter.coordinator.devtools.channelId
|
|
593
601
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const reactivityGraphSubcriptions = new Map<RequestId, Unsub>()
|
|
614
|
-
const liveQueriesSubscriptions = new Map<RequestId, Unsub>()
|
|
615
|
-
const debugInfoHistorySubscriptions = new Map<RequestId, Unsub>()
|
|
616
|
-
|
|
617
|
-
this.adapter.coordinator.devtools
|
|
618
|
-
.connect({ port: message.port, connectionId: this.devtoolsConnectionId })
|
|
619
|
-
.pipe(
|
|
620
|
-
Effect.tapSync(({ storeMessagePort }) => {
|
|
621
|
-
// console.log('storeMessagePort', storeMessagePort)
|
|
622
|
-
storeMessagePort.addEventListener('message', (event) => {
|
|
623
|
-
const decodedMessage = Schema.decodeUnknownSync(Devtools.MessageToAppHostStore)(event.data)
|
|
624
|
-
// console.log('storeMessagePort message', decodedMessage)
|
|
625
|
-
|
|
626
|
-
if (decodedMessage.channelId !== this.adapter.coordinator.devtools.channelId) {
|
|
627
|
-
// console.log(`Unknown message`, event)
|
|
628
|
-
return
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
const requestId = decodedMessage.requestId
|
|
632
|
-
const sendToDevtools = (message: Devtools.MessageFromAppHostStore) =>
|
|
633
|
-
storeMessagePort.postMessage(Schema.encodeSync(Devtools.MessageFromAppHostStore)(message))
|
|
634
|
-
|
|
635
|
-
const requestIdleCallback = window.requestIdleCallback ?? ((cb: Function) => cb())
|
|
636
|
-
|
|
637
|
-
switch (decodedMessage._tag) {
|
|
638
|
-
case 'LSD.ReactivityGraphSubscribe': {
|
|
639
|
-
const includeResults = decodedMessage.includeResults
|
|
640
|
-
|
|
641
|
-
const send = () =>
|
|
642
|
-
// In order to not add more work to the current tick, we use requestIdleCallback
|
|
643
|
-
// to send the reactivity graph updates to the devtools
|
|
644
|
-
requestIdleCallback(
|
|
645
|
-
() =>
|
|
646
|
-
sendToDevtools(
|
|
647
|
-
Devtools.ReactivityGraphRes.make({
|
|
648
|
-
reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults }),
|
|
649
|
-
requestId,
|
|
650
|
-
liveStoreVersion,
|
|
651
|
-
}),
|
|
652
|
-
),
|
|
653
|
-
{ timeout: 500 },
|
|
654
|
-
)
|
|
655
|
-
|
|
656
|
-
send()
|
|
657
|
-
|
|
658
|
-
// In some cases, there can be A LOT of reactivity graph updates in a short period of time
|
|
659
|
-
// so we throttle the updates to avoid sending too much data
|
|
660
|
-
// This might need to be tweaked further and possibly be exposed to the user in some way.
|
|
661
|
-
const throttledSend = throttle(send, 20)
|
|
662
|
-
|
|
663
|
-
reactivityGraphSubcriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
|
|
664
|
-
|
|
665
|
-
break
|
|
666
|
-
}
|
|
667
|
-
case 'LSD.DebugInfoReq': {
|
|
668
|
-
sendToDevtools(
|
|
669
|
-
Devtools.DebugInfoRes.make({
|
|
670
|
-
debugInfo: this.mainDbWrapper.debugInfo,
|
|
671
|
-
requestId,
|
|
672
|
-
liveStoreVersion,
|
|
673
|
-
}),
|
|
674
|
-
)
|
|
675
|
-
break
|
|
676
|
-
}
|
|
677
|
-
case 'LSD.DebugInfoHistorySubscribe': {
|
|
678
|
-
const buffer: DebugInfo[] = []
|
|
679
|
-
let hasStopped = false
|
|
680
|
-
let rafHandle: number | undefined
|
|
681
|
-
|
|
682
|
-
const tick = () => {
|
|
683
|
-
buffer.push(this.mainDbWrapper.debugInfo)
|
|
684
|
-
|
|
685
|
-
// NOTE this resets the debug info, so all other "readers" e.g. in other `requestAnimationFrame` loops,
|
|
686
|
-
// will get the empty debug info
|
|
687
|
-
// TODO We need to come up with a more graceful way to do this. Probably via a single global
|
|
688
|
-
// `requestAnimationFrame` loop that is passed in somehow.
|
|
689
|
-
this.mainDbWrapper.debugInfo = makeEmptyDebugInfo()
|
|
690
|
-
|
|
691
|
-
if (buffer.length > 10) {
|
|
692
|
-
sendToDevtools(
|
|
693
|
-
Devtools.DebugInfoHistoryRes.make({
|
|
694
|
-
debugInfoHistory: buffer,
|
|
695
|
-
requestId,
|
|
696
|
-
liveStoreVersion,
|
|
697
|
-
}),
|
|
698
|
-
)
|
|
699
|
-
buffer.length = 0
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
if (hasStopped === false) {
|
|
703
|
-
rafHandle = requestAnimationFrame(tick)
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
rafHandle = requestAnimationFrame(tick)
|
|
708
|
-
|
|
709
|
-
const unsub = () => {
|
|
710
|
-
hasStopped = true
|
|
711
|
-
if (rafHandle !== undefined) {
|
|
712
|
-
cancelAnimationFrame(rafHandle)
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
debugInfoHistorySubscriptions.set(requestId, unsub)
|
|
717
|
-
|
|
718
|
-
break
|
|
719
|
-
}
|
|
720
|
-
case 'LSD.DebugInfoHistoryUnsubscribe': {
|
|
721
|
-
debugInfoHistorySubscriptions.get(requestId)!()
|
|
722
|
-
debugInfoHistorySubscriptions.delete(requestId)
|
|
723
|
-
break
|
|
724
|
-
}
|
|
725
|
-
case 'LSD.DebugInfoResetReq': {
|
|
726
|
-
this.mainDbWrapper.debugInfo.slowQueries.clear()
|
|
727
|
-
sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, liveStoreVersion }))
|
|
728
|
-
break
|
|
729
|
-
}
|
|
730
|
-
case 'LSD.DebugInfoRerunQueryReq': {
|
|
731
|
-
const { queryStr, bindValues, queriedTables } = decodedMessage
|
|
732
|
-
this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
|
|
733
|
-
sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }))
|
|
734
|
-
break
|
|
735
|
-
}
|
|
736
|
-
case 'LSD.ReactivityGraphUnsubscribe': {
|
|
737
|
-
reactivityGraphSubcriptions.get(requestId)!()
|
|
738
|
-
break
|
|
739
|
-
}
|
|
740
|
-
case 'LSD.LiveQueriesSubscribe': {
|
|
741
|
-
const send = () =>
|
|
742
|
-
requestIdleCallback(
|
|
743
|
-
() =>
|
|
744
|
-
sendToDevtools(
|
|
745
|
-
Devtools.LiveQueriesRes.make({
|
|
746
|
-
liveQueries: [...this.activeQueries].map((q) => ({
|
|
747
|
-
_tag: q._tag,
|
|
748
|
-
id: q.id,
|
|
749
|
-
label: q.label,
|
|
750
|
-
runs: q.runs,
|
|
751
|
-
executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
|
|
752
|
-
lastestResult:
|
|
753
|
-
q.results$.previousResult === NOT_REFRESHED_YET
|
|
754
|
-
? 'SYMBOL_NOT_REFRESHED_YET'
|
|
755
|
-
: q.results$.previousResult,
|
|
756
|
-
activeSubscriptions: Array.from(q.activeSubscriptions),
|
|
757
|
-
})),
|
|
758
|
-
requestId,
|
|
759
|
-
liveStoreVersion,
|
|
760
|
-
}),
|
|
761
|
-
),
|
|
762
|
-
{ timeout: 500 },
|
|
763
|
-
)
|
|
764
|
-
|
|
765
|
-
send()
|
|
766
|
-
|
|
767
|
-
// Same as in the reactivity graph subscription case above, we need to throttle the updates
|
|
768
|
-
const throttledSend = throttle(send, 20)
|
|
769
|
-
|
|
770
|
-
liveQueriesSubscriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
|
|
771
|
-
|
|
772
|
-
break
|
|
773
|
-
}
|
|
774
|
-
case 'LSD.LiveQueriesUnsubscribe': {
|
|
775
|
-
liveQueriesSubscriptions.get(requestId)!()
|
|
776
|
-
liveQueriesSubscriptions.delete(requestId)
|
|
777
|
-
break
|
|
778
|
-
}
|
|
779
|
-
// No default
|
|
780
|
-
}
|
|
781
|
-
})
|
|
602
|
+
// Chrome extension bridge
|
|
603
|
+
{
|
|
604
|
+
const windowChannel = yield* BrowserChannel.windowChannel({
|
|
605
|
+
window,
|
|
606
|
+
listenSchema: Devtools.DevtoolsWindowMessage.MessageForStore,
|
|
607
|
+
sendSchema: Devtools.DevtoolsWindowMessage.MessageForContentscript,
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
yield* windowChannel.send(Devtools.DevtoolsWindowMessage.LoadIframe.make({}))
|
|
611
|
+
|
|
612
|
+
yield* windowChannel.listen.pipe(
|
|
613
|
+
Stream.filterMap(Either.getRight),
|
|
614
|
+
Stream.tap((message) =>
|
|
615
|
+
Effect.gen(function* () {
|
|
616
|
+
if (message._tag === 'LSD.WindowMessage.ContentscriptListening') {
|
|
617
|
+
// Send message to contentscript via window (which the contentscript iframe is listening to)
|
|
618
|
+
yield* windowChannel.send(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }))
|
|
619
|
+
return
|
|
620
|
+
}
|
|
782
621
|
|
|
783
|
-
|
|
784
|
-
}),
|
|
785
|
-
Runtime.runFork(runtime),
|
|
786
|
-
)
|
|
622
|
+
if (message.channelId !== channelId) return
|
|
787
623
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
624
|
+
if (message._tag === 'LSD.WindowMessage.MessagePortForStore') {
|
|
625
|
+
yield* connectStoreToDevtools({ port: message.port, store })
|
|
626
|
+
}
|
|
627
|
+
}),
|
|
628
|
+
),
|
|
629
|
+
Stream.runDrain,
|
|
630
|
+
Effect.tapCauseLogPretty,
|
|
631
|
+
Effect.forkScoped,
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
yield* windowChannel.send(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }))
|
|
635
|
+
}
|
|
793
636
|
})
|
|
794
637
|
// #endregion devtools
|
|
795
638
|
|
|
@@ -806,6 +649,7 @@ export class Store<
|
|
|
806
649
|
// TODO allow for graceful store reset without requiring a full page reload (which should also call .boot)
|
|
807
650
|
dangerouslyResetStorage = (mode: ResetMode) => this.adapter.coordinator.dangerouslyReset(mode).pipe(runEffectPromise)
|
|
808
651
|
|
|
652
|
+
// NOTE This is needed because when booting a Store via Effect it seems to call `toJSON` in the error path
|
|
809
653
|
toJSON = () => {
|
|
810
654
|
return {
|
|
811
655
|
_tag: 'Store',
|
|
@@ -1006,43 +850,7 @@ export const createStore = <
|
|
|
1006
850
|
}
|
|
1007
851
|
// #endregion createStore
|
|
1008
852
|
|
|
1009
|
-
// TODO
|
|
1010
|
-
class ReferenceCountedSet<T> {
|
|
1011
|
-
private map: Map<T, number>
|
|
1012
|
-
|
|
1013
|
-
constructor() {
|
|
1014
|
-
this.map = new Map<T, number>()
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
add = (key: T) => {
|
|
1018
|
-
const count = this.map.get(key) ?? 0
|
|
1019
|
-
this.map.set(key, count + 1)
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
remove = (key: T) => {
|
|
1023
|
-
const count = this.map.get(key) ?? 0
|
|
1024
|
-
if (count === 1) {
|
|
1025
|
-
this.map.delete(key)
|
|
1026
|
-
} else {
|
|
1027
|
-
this.map.set(key, count - 1)
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
has = (key: T) => {
|
|
1032
|
-
return this.map.has(key)
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
get size() {
|
|
1036
|
-
return this.map.size
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
*[Symbol.iterator]() {
|
|
1040
|
-
for (const key of this.map.keys()) {
|
|
1041
|
-
yield key
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
|
|
853
|
+
// TODO propagate runtime
|
|
1046
854
|
const runEffectFork = <A, E>(effect: Effect.Effect<A, E, never>) =>
|
|
1047
855
|
effect.pipe(
|
|
1048
856
|
Effect.tapCauseLogPretty,
|
|
@@ -1052,6 +860,7 @@ const runEffectFork = <A, E>(effect: Effect.Effect<A, E, never>) =>
|
|
|
1052
860
|
Effect.runFork,
|
|
1053
861
|
)
|
|
1054
862
|
|
|
863
|
+
// TODO propagate runtime
|
|
1055
864
|
const runEffectPromise = <A, E>(effect: Effect.Effect<A, E, never>) =>
|
|
1056
865
|
effect.pipe(
|
|
1057
866
|
Effect.tapCauseLogPretty,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// TODO consider replacing with Effect's RC data structures
|
|
2
|
+
export class ReferenceCountedSet<T> {
|
|
3
|
+
private map: Map<T, number>
|
|
4
|
+
|
|
5
|
+
constructor() {
|
|
6
|
+
this.map = new Map<T, number>()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
add = (key: T) => {
|
|
10
|
+
const count = this.map.get(key) ?? 0
|
|
11
|
+
this.map.set(key, count + 1)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
remove = (key: T) => {
|
|
15
|
+
const count = this.map.get(key) ?? 0
|
|
16
|
+
if (count === 1) {
|
|
17
|
+
this.map.delete(key)
|
|
18
|
+
} else {
|
|
19
|
+
this.map.set(key, count - 1)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
has = (key: T) => {
|
|
24
|
+
return this.map.has(key)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get size() {
|
|
28
|
+
return this.map.size
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
*[Symbol.iterator]() {
|
|
32
|
+
for (const key of this.map.keys()) {
|
|
33
|
+
yield key
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|