@tanstack/db 0.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.
- package/README.md +37 -0
- package/dist/cjs/SortedMap.cjs +140 -0
- package/dist/cjs/SortedMap.cjs.map +1 -0
- package/dist/cjs/SortedMap.d.cts +91 -0
- package/dist/cjs/collection.cjs +597 -0
- package/dist/cjs/collection.cjs.map +1 -0
- package/dist/cjs/collection.d.cts +176 -0
- package/dist/cjs/deferred.cjs +25 -0
- package/dist/cjs/deferred.cjs.map +1 -0
- package/dist/cjs/deferred.d.cts +20 -0
- package/dist/cjs/errors.cjs +10 -0
- package/dist/cjs/errors.cjs.map +1 -0
- package/dist/cjs/errors.d.cts +3 -0
- package/dist/cjs/index.cjs +33 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +9 -0
- package/dist/cjs/proxy.cjs +654 -0
- package/dist/cjs/proxy.cjs.map +1 -0
- package/dist/cjs/proxy.d.cts +59 -0
- package/dist/cjs/query/compiled-query.cjs +162 -0
- package/dist/cjs/query/compiled-query.cjs.map +1 -0
- package/dist/cjs/query/compiled-query.d.cts +22 -0
- package/dist/cjs/query/evaluators.cjs +146 -0
- package/dist/cjs/query/evaluators.cjs.map +1 -0
- package/dist/cjs/query/evaluators.d.cts +9 -0
- package/dist/cjs/query/extractors.cjs +122 -0
- package/dist/cjs/query/extractors.cjs.map +1 -0
- package/dist/cjs/query/extractors.d.cts +22 -0
- package/dist/cjs/query/functions.cjs +152 -0
- package/dist/cjs/query/functions.cjs.map +1 -0
- package/dist/cjs/query/functions.d.cts +21 -0
- package/dist/cjs/query/group-by.cjs +91 -0
- package/dist/cjs/query/group-by.cjs.map +1 -0
- package/dist/cjs/query/group-by.d.cts +40 -0
- package/dist/cjs/query/index.d.cts +5 -0
- package/dist/cjs/query/joins.cjs +155 -0
- package/dist/cjs/query/joins.cjs.map +1 -0
- package/dist/cjs/query/joins.d.cts +14 -0
- package/dist/cjs/query/key-by.cjs +43 -0
- package/dist/cjs/query/key-by.cjs.map +1 -0
- package/dist/cjs/query/key-by.d.cts +3 -0
- package/dist/cjs/query/order-by.cjs +229 -0
- package/dist/cjs/query/order-by.cjs.map +1 -0
- package/dist/cjs/query/order-by.d.cts +3 -0
- package/dist/cjs/query/pipeline-compiler.cjs +94 -0
- package/dist/cjs/query/pipeline-compiler.cjs.map +1 -0
- package/dist/cjs/query/pipeline-compiler.d.cts +9 -0
- package/dist/cjs/query/query-builder.cjs +314 -0
- package/dist/cjs/query/query-builder.cjs.map +1 -0
- package/dist/cjs/query/query-builder.d.cts +219 -0
- package/dist/cjs/query/schema.d.cts +98 -0
- package/dist/cjs/query/select.cjs +107 -0
- package/dist/cjs/query/select.cjs.map +1 -0
- package/dist/cjs/query/select.d.cts +3 -0
- package/dist/cjs/query/types.d.cts +188 -0
- package/dist/cjs/query/utils.cjs +154 -0
- package/dist/cjs/query/utils.cjs.map +1 -0
- package/dist/cjs/query/utils.d.cts +37 -0
- package/dist/cjs/transactions.cjs +137 -0
- package/dist/cjs/transactions.cjs.map +1 -0
- package/dist/cjs/transactions.d.cts +27 -0
- package/dist/cjs/types.d.cts +94 -0
- package/dist/cjs/utils.cjs +17 -0
- package/dist/cjs/utils.cjs.map +1 -0
- package/dist/cjs/utils.d.cts +3 -0
- package/dist/esm/SortedMap.d.ts +91 -0
- package/dist/esm/SortedMap.js +140 -0
- package/dist/esm/SortedMap.js.map +1 -0
- package/dist/esm/collection.d.ts +176 -0
- package/dist/esm/collection.js +597 -0
- package/dist/esm/collection.js.map +1 -0
- package/dist/esm/deferred.d.ts +20 -0
- package/dist/esm/deferred.js +25 -0
- package/dist/esm/deferred.js.map +1 -0
- package/dist/esm/errors.d.ts +3 -0
- package/dist/esm/errors.js +10 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +9 -0
- package/dist/esm/index.js +33 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/proxy.d.ts +59 -0
- package/dist/esm/proxy.js +654 -0
- package/dist/esm/proxy.js.map +1 -0
- package/dist/esm/query/compiled-query.d.ts +22 -0
- package/dist/esm/query/compiled-query.js +162 -0
- package/dist/esm/query/compiled-query.js.map +1 -0
- package/dist/esm/query/evaluators.d.ts +9 -0
- package/dist/esm/query/evaluators.js +146 -0
- package/dist/esm/query/evaluators.js.map +1 -0
- package/dist/esm/query/extractors.d.ts +22 -0
- package/dist/esm/query/extractors.js +122 -0
- package/dist/esm/query/extractors.js.map +1 -0
- package/dist/esm/query/functions.d.ts +21 -0
- package/dist/esm/query/functions.js +152 -0
- package/dist/esm/query/functions.js.map +1 -0
- package/dist/esm/query/group-by.d.ts +40 -0
- package/dist/esm/query/group-by.js +91 -0
- package/dist/esm/query/group-by.js.map +1 -0
- package/dist/esm/query/index.d.ts +5 -0
- package/dist/esm/query/joins.d.ts +14 -0
- package/dist/esm/query/joins.js +155 -0
- package/dist/esm/query/joins.js.map +1 -0
- package/dist/esm/query/key-by.d.ts +3 -0
- package/dist/esm/query/key-by.js +43 -0
- package/dist/esm/query/key-by.js.map +1 -0
- package/dist/esm/query/order-by.d.ts +3 -0
- package/dist/esm/query/order-by.js +229 -0
- package/dist/esm/query/order-by.js.map +1 -0
- package/dist/esm/query/pipeline-compiler.d.ts +9 -0
- package/dist/esm/query/pipeline-compiler.js +94 -0
- package/dist/esm/query/pipeline-compiler.js.map +1 -0
- package/dist/esm/query/query-builder.d.ts +219 -0
- package/dist/esm/query/query-builder.js +314 -0
- package/dist/esm/query/query-builder.js.map +1 -0
- package/dist/esm/query/schema.d.ts +98 -0
- package/dist/esm/query/select.d.ts +3 -0
- package/dist/esm/query/select.js +107 -0
- package/dist/esm/query/select.js.map +1 -0
- package/dist/esm/query/types.d.ts +188 -0
- package/dist/esm/query/utils.d.ts +37 -0
- package/dist/esm/query/utils.js +154 -0
- package/dist/esm/query/utils.js.map +1 -0
- package/dist/esm/transactions.d.ts +27 -0
- package/dist/esm/transactions.js +137 -0
- package/dist/esm/transactions.js.map +1 -0
- package/dist/esm/types.d.ts +94 -0
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.js +17 -0
- package/dist/esm/utils.js.map +1 -0
- package/package.json +57 -0
- package/src/SortedMap.ts +163 -0
- package/src/collection.ts +919 -0
- package/src/deferred.ts +47 -0
- package/src/errors.ts +6 -0
- package/src/index.ts +12 -0
- package/src/proxy.ts +1104 -0
- package/src/query/compiled-query.ts +193 -0
- package/src/query/evaluators.ts +222 -0
- package/src/query/extractors.ts +211 -0
- package/src/query/functions.ts +297 -0
- package/src/query/group-by.ts +137 -0
- package/src/query/index.ts +5 -0
- package/src/query/joins.ts +247 -0
- package/src/query/key-by.ts +61 -0
- package/src/query/order-by.ts +312 -0
- package/src/query/pipeline-compiler.ts +152 -0
- package/src/query/query-builder.ts +898 -0
- package/src/query/schema.ts +255 -0
- package/src/query/select.ts +173 -0
- package/src/query/types.ts +417 -0
- package/src/query/utils.ts +245 -0
- package/src/transactions.ts +198 -0
- package/src/types.ts +125 -0
- package/src/utils.ts +15 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { createDeferred } from "./deferred"
|
|
2
|
+
import type { Deferred } from "./deferred"
|
|
3
|
+
import type {
|
|
4
|
+
PendingMutation,
|
|
5
|
+
TransactionConfig,
|
|
6
|
+
TransactionState,
|
|
7
|
+
} from "./types"
|
|
8
|
+
|
|
9
|
+
function generateUUID() {
|
|
10
|
+
// Check if crypto.randomUUID is available (modern browsers and Node.js 15+)
|
|
11
|
+
if (
|
|
12
|
+
typeof crypto !== `undefined` &&
|
|
13
|
+
typeof crypto.randomUUID === `function`
|
|
14
|
+
) {
|
|
15
|
+
return crypto.randomUUID()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Fallback implementation for older environments
|
|
19
|
+
return `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function (c) {
|
|
20
|
+
const r = (Math.random() * 16) | 0
|
|
21
|
+
const v = c === `x` ? r : (r & 0x3) | 0x8
|
|
22
|
+
return v.toString(16)
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const transactions: Array<Transaction> = []
|
|
27
|
+
|
|
28
|
+
export function createTransaction(config: TransactionConfig): Transaction {
|
|
29
|
+
if (typeof config.mutationFn === `undefined`) {
|
|
30
|
+
throw `mutationFn is required when creating a transaction`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let transactionId = config.id
|
|
34
|
+
if (!transactionId) {
|
|
35
|
+
transactionId = generateUUID()
|
|
36
|
+
}
|
|
37
|
+
const newTransaction = new Transaction({ ...config, id: transactionId })
|
|
38
|
+
transactions.push(newTransaction)
|
|
39
|
+
|
|
40
|
+
return newTransaction
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let transactionStack: Array<Transaction> = []
|
|
44
|
+
|
|
45
|
+
export function getActiveTransaction(): Transaction | undefined {
|
|
46
|
+
if (transactionStack.length > 0) {
|
|
47
|
+
return transactionStack.slice(-1)[0]
|
|
48
|
+
} else {
|
|
49
|
+
return undefined
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function registerTransaction(tx: Transaction) {
|
|
54
|
+
transactionStack.push(tx)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function unregisterTransaction(tx: Transaction) {
|
|
58
|
+
transactionStack = transactionStack.filter((t) => t.id !== tx.id)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class Transaction {
|
|
62
|
+
public id: string
|
|
63
|
+
public state: TransactionState
|
|
64
|
+
public mutationFn
|
|
65
|
+
public mutations: Array<PendingMutation<any>>
|
|
66
|
+
public isPersisted: Deferred<Transaction>
|
|
67
|
+
public autoCommit: boolean
|
|
68
|
+
public createdAt: Date
|
|
69
|
+
public metadata: Record<string, unknown>
|
|
70
|
+
public error?: {
|
|
71
|
+
message: string
|
|
72
|
+
error: Error
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
constructor(config: TransactionConfig) {
|
|
76
|
+
this.id = config.id!
|
|
77
|
+
this.mutationFn = config.mutationFn
|
|
78
|
+
this.state = `pending`
|
|
79
|
+
this.mutations = []
|
|
80
|
+
this.isPersisted = createDeferred()
|
|
81
|
+
this.autoCommit = config.autoCommit ?? true
|
|
82
|
+
this.createdAt = new Date()
|
|
83
|
+
this.metadata = config.metadata ?? {}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setState(newState: TransactionState) {
|
|
87
|
+
this.state = newState
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
mutate(callback: () => void): Transaction {
|
|
91
|
+
if (this.state !== `pending`) {
|
|
92
|
+
throw `You can no longer call .mutate() as the transaction is no longer pending`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
registerTransaction(this)
|
|
96
|
+
try {
|
|
97
|
+
callback()
|
|
98
|
+
} finally {
|
|
99
|
+
unregisterTransaction(this)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (this.autoCommit) {
|
|
103
|
+
this.commit()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return this
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
applyMutations(mutations: Array<PendingMutation<any>>): void {
|
|
110
|
+
for (const newMutation of mutations) {
|
|
111
|
+
const existingIndex = this.mutations.findIndex(
|
|
112
|
+
(m) => m.key === newMutation.key
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if (existingIndex >= 0) {
|
|
116
|
+
// Replace existing mutation
|
|
117
|
+
this.mutations[existingIndex] = newMutation
|
|
118
|
+
} else {
|
|
119
|
+
// Insert new mutation
|
|
120
|
+
this.mutations.push(newMutation)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
rollback(config?: { isSecondaryRollback?: boolean }): Transaction {
|
|
126
|
+
const isSecondaryRollback = config?.isSecondaryRollback ?? false
|
|
127
|
+
if (this.state === `completed`) {
|
|
128
|
+
throw `You can no longer call .rollback() as the transaction is already completed`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.setState(`failed`)
|
|
132
|
+
|
|
133
|
+
// See if there's any other transactions w/ mutations on the same keys
|
|
134
|
+
// and roll them back as well.
|
|
135
|
+
if (!isSecondaryRollback) {
|
|
136
|
+
const mutationKeys = new Set()
|
|
137
|
+
this.mutations.forEach((m) => mutationKeys.add(m.key))
|
|
138
|
+
transactions.forEach(
|
|
139
|
+
(t) =>
|
|
140
|
+
t.state === `pending` &&
|
|
141
|
+
t.mutations.some((m) => mutationKeys.has(m.key)) &&
|
|
142
|
+
t.rollback({ isSecondaryRollback: true })
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Reject the promise
|
|
147
|
+
this.isPersisted.reject(this.error?.error)
|
|
148
|
+
|
|
149
|
+
this.touchCollection()
|
|
150
|
+
|
|
151
|
+
return this
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Tell collection that something has changed with the transaction
|
|
155
|
+
touchCollection(): void {
|
|
156
|
+
const hasCalled = new Set()
|
|
157
|
+
this.mutations.forEach((mutation) => {
|
|
158
|
+
if (!hasCalled.has(mutation.collection.id)) {
|
|
159
|
+
mutation.collection.transactions.setState((state) => state)
|
|
160
|
+
mutation.collection.commitPendingTransactions()
|
|
161
|
+
hasCalled.add(mutation.collection.id)
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async commit(): Promise<Transaction> {
|
|
167
|
+
if (this.state !== `pending`) {
|
|
168
|
+
throw `You can no longer call .commit() as the transaction is no longer pending`
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this.setState(`persisting`)
|
|
172
|
+
|
|
173
|
+
if (this.mutations.length === 0) {
|
|
174
|
+
this.setState(`completed`)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Run mutationFn
|
|
178
|
+
try {
|
|
179
|
+
await this.mutationFn({ transaction: this })
|
|
180
|
+
|
|
181
|
+
this.setState(`completed`)
|
|
182
|
+
this.touchCollection()
|
|
183
|
+
|
|
184
|
+
this.isPersisted.resolve(this)
|
|
185
|
+
} catch (error) {
|
|
186
|
+
// Update transaction with error information
|
|
187
|
+
this.error = {
|
|
188
|
+
message: error instanceof Error ? error.message : String(error),
|
|
189
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// rollback the transaction
|
|
193
|
+
return this.rollback()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return this
|
|
197
|
+
}
|
|
198
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Collection } from "./collection"
|
|
2
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec"
|
|
3
|
+
import type { Transaction } from "./transactions"
|
|
4
|
+
|
|
5
|
+
export type TransactionState = `pending` | `persisting` | `completed` | `failed`
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Represents a pending mutation within a transaction
|
|
9
|
+
* Contains information about the original and modified data, as well as metadata
|
|
10
|
+
*/
|
|
11
|
+
export interface PendingMutation<T extends object = Record<string, unknown>> {
|
|
12
|
+
mutationId: string
|
|
13
|
+
original: Record<string, unknown>
|
|
14
|
+
modified: Record<string, unknown>
|
|
15
|
+
changes: Record<string, unknown>
|
|
16
|
+
key: string
|
|
17
|
+
type: OperationType
|
|
18
|
+
metadata: unknown
|
|
19
|
+
syncMetadata: Record<string, unknown>
|
|
20
|
+
createdAt: Date
|
|
21
|
+
updatedAt: Date
|
|
22
|
+
collection: Collection<T>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Configuration options for creating a new transaction
|
|
27
|
+
*/
|
|
28
|
+
export type MutationFnParams = {
|
|
29
|
+
transaction: Transaction
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type MutationFn = (params: MutationFnParams) => Promise<any>
|
|
33
|
+
|
|
34
|
+
export interface TransactionConfig {
|
|
35
|
+
/** Unique identifier for the transaction */
|
|
36
|
+
id?: string
|
|
37
|
+
/* If the transaction should autocommit after a mutate call or should commit be called explicitly */
|
|
38
|
+
autoCommit?: boolean
|
|
39
|
+
mutationFn: MutationFn
|
|
40
|
+
/** Custom metadata to associate with the transaction */
|
|
41
|
+
metadata?: Record<string, unknown>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type { Transaction }
|
|
45
|
+
|
|
46
|
+
type Value<TExtensions = never> =
|
|
47
|
+
| string
|
|
48
|
+
| number
|
|
49
|
+
| boolean
|
|
50
|
+
| bigint
|
|
51
|
+
| null
|
|
52
|
+
| TExtensions
|
|
53
|
+
| Array<Value<TExtensions>>
|
|
54
|
+
| { [key: string]: Value<TExtensions> }
|
|
55
|
+
|
|
56
|
+
export type Row<TExtensions = never> = Record<string, Value<TExtensions>>
|
|
57
|
+
|
|
58
|
+
export type OperationType = `insert` | `update` | `delete`
|
|
59
|
+
|
|
60
|
+
export interface SyncConfig<T extends object = Record<string, unknown>> {
|
|
61
|
+
sync: (params: {
|
|
62
|
+
collection: Collection<T>
|
|
63
|
+
begin: () => void
|
|
64
|
+
write: (message: ChangeMessage<T>) => void
|
|
65
|
+
commit: () => void
|
|
66
|
+
}) => void
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the sync metadata for insert operations
|
|
70
|
+
* @returns Record containing primaryKey and relation information
|
|
71
|
+
*/
|
|
72
|
+
getSyncMetadata?: () => Record<string, unknown>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ChangeMessage<T extends object = Record<string, unknown>> {
|
|
76
|
+
key: string
|
|
77
|
+
value: T
|
|
78
|
+
previousValue?: T
|
|
79
|
+
type: OperationType
|
|
80
|
+
metadata?: Record<string, unknown>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface OptimisticChangeMessage<
|
|
84
|
+
T extends object = Record<string, unknown>,
|
|
85
|
+
> extends ChangeMessage<T> {
|
|
86
|
+
// Is this change message part of an active transaction. Only applies to optimistic changes.
|
|
87
|
+
isActive?: boolean
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The Standard Schema interface.
|
|
92
|
+
* This follows the standard-schema specification: https://github.com/standard-schema/standard-schema
|
|
93
|
+
*/
|
|
94
|
+
export type StandardSchema<T> = StandardSchemaV1 & {
|
|
95
|
+
"~standard": {
|
|
96
|
+
types?: {
|
|
97
|
+
input: T
|
|
98
|
+
output: T
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Type alias for StandardSchema
|
|
105
|
+
*/
|
|
106
|
+
export type StandardSchemaAlias<T = unknown> = StandardSchema<T>
|
|
107
|
+
|
|
108
|
+
export interface OperationConfig {
|
|
109
|
+
metadata?: Record<string, unknown>
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface InsertConfig {
|
|
113
|
+
key?: string | Array<string | undefined>
|
|
114
|
+
metadata?: Record<string, unknown>
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface CollectionConfig<T extends object = Record<string, unknown>> {
|
|
118
|
+
id: string
|
|
119
|
+
sync: SyncConfig<T>
|
|
120
|
+
schema?: StandardSchema<T>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type ChangesPayload<T extends object = Record<string, unknown>> = Array<
|
|
124
|
+
ChangeMessage<T>
|
|
125
|
+
>
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function getLockedObjects(): Set<string> {
|
|
2
|
+
// Stub implementation that returns an empty Set
|
|
3
|
+
return new Set()
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
let globalVersion = 0
|
|
7
|
+
|
|
8
|
+
export function getGlobalVersion(): number {
|
|
9
|
+
return globalVersion
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function advanceGlobalVersion(): number {
|
|
13
|
+
console.log(`==== advancing global version`, globalVersion + 1)
|
|
14
|
+
return globalVersion++
|
|
15
|
+
}
|