@@ -103,6 +103,7 @@ const appendEventsRaw = async (
103
103
) : Promise < AppendEventResult > => {
104
104
let streamPosition ;
105
105
let globalPosition ;
106
+
106
107
try {
107
108
let expectedStreamVersion = options ?. expectedStreamVersion ?? null ;
108
109
@@ -114,56 +115,33 @@ const appendEventsRaw = async (
114
115
) ;
115
116
}
116
117
117
- const buildQuery = `INSERT INTO ${ eventsTable . name } (stream_id, stream_position, partition, event_data, event_metadata, event_schema_version, event_type, event_id, is_archived) VALUES ` ;
118
-
119
- const query = events . reduce (
120
- (
121
- queryBuilder : {
122
- sql : string [ ] ;
123
- values : Parameters [ ] ;
124
- } ,
125
- e : ReadEvent ,
126
- ) => {
127
- const streamPosition =
128
- e . metadata . streamPosition + expectedStreamVersion ;
129
-
130
- queryBuilder . sql . push ( `(?,?,?,?,?,?,?,?,?)` ) ;
131
- queryBuilder . values . push (
132
- streamId ,
133
- streamPosition . toString ( ) ,
134
- options ?. partition ?. toString ( ) ?? defaultTag ,
135
- JSONParser . stringify ( e . data ) ,
136
- JSONParser . stringify ( { streamType : streamType , ...e . metadata } ) ,
137
- expectedStreamVersion ?. toString ( ) ?? 0 ,
138
- e . type ,
139
- e . metadata . eventId ,
140
- false ,
141
- ) ;
142
-
143
- return queryBuilder ;
144
- } ,
145
- {
146
- sql : [ ] ,
147
- values : [ ] ,
148
- } ,
118
+ const { sqlString, values } = buildEventInsertQuery (
119
+ events ,
120
+ expectedStreamVersion ,
121
+ streamId ,
122
+ streamType ,
123
+ options ?. partition ?. toString ( ) ?? defaultTag ,
149
124
) ;
150
125
151
- const sqlString = buildQuery + query . sql . join ( ', ' ) ;
126
+ const returningId = await db . querySingle < { global_position : string } > (
127
+ sqlString ,
128
+ values ,
129
+ ) ;
152
130
153
- await db . command ( sqlString , query . values ) ;
131
+ if ( returningId ?. global_position == null ) {
132
+ throw new Error ( 'Could not find global position' ) ;
133
+ }
134
+
135
+ globalPosition = BigInt ( returningId . global_position ) ;
154
136
155
137
const positions = await db . querySingle < {
156
138
stream_position : string ;
157
- global_position : string ;
158
139
} | null > (
159
140
`
160
141
SELECT
161
- CAST(stream_position AS VARCHAR) AS stream_position,
162
- CAST(global_position AS VARCHAR) AS global_position
163
- FROM ${ eventsTable . name }
164
- WHERE stream_id = ?
165
- ORDER BY stream_position DESC
166
- LIMIT 1` ,
142
+ CAST(stream_position AS VARCHAR) AS stream_position
143
+ FROM ${ streamsTable . name }
144
+ WHERE stream_id = ?` ,
167
145
[ streamId ] ,
168
146
) ;
169
147
@@ -172,7 +150,16 @@ const appendEventsRaw = async (
172
150
}
173
151
174
152
streamPosition = BigInt ( positions . stream_position ) ;
175
- globalPosition = BigInt ( positions . global_position ) ;
153
+
154
+ if ( expectedStreamVersion != null ) {
155
+ const expectedStreamPositionAfterSave =
156
+ BigInt ( expectedStreamVersion ) + BigInt ( events . length ) ;
157
+ if ( streamPosition !== expectedStreamPositionAfterSave ) {
158
+ return {
159
+ success : false ,
160
+ } ;
161
+ }
162
+ }
176
163
} catch ( err : unknown ) {
177
164
if ( isSQLiteError ( err ) && isOptimisticConcurrencyError ( err ) ) {
178
165
return {
@@ -211,3 +198,60 @@ async function getLastStreamPosition(
211
198
}
212
199
return expectedStreamVersion ;
213
200
}
201
+
202
+ const buildEventInsertQuery = (
203
+ events : Event [ ] ,
204
+ expectedStreamVersion : bigint | null ,
205
+ streamId : string ,
206
+ streamType : string ,
207
+ partition : string | null | undefined ,
208
+ ) => {
209
+ const query = events . reduce (
210
+ (
211
+ queryBuilder : {
212
+ parameterMarkers : string [ ] ;
213
+ values : Parameters [ ] ;
214
+ } ,
215
+ e : ReadEvent ,
216
+ ) => {
217
+ const streamPosition = e . metadata . streamPosition + expectedStreamVersion ;
218
+
219
+ queryBuilder . parameterMarkers . push ( `(?,?,?,?,?,?,?,?,?)` ) ;
220
+ queryBuilder . values . push (
221
+ streamId ,
222
+ streamPosition . toString ( ) ,
223
+ partition ?? defaultTag ,
224
+ JSONParser . stringify ( e . data ) ,
225
+ JSONParser . stringify ( { streamType : streamType , ...e . metadata } ) ,
226
+ expectedStreamVersion ?. toString ( ) ?? 0 ,
227
+ e . type ,
228
+ e . metadata . eventId ,
229
+ false ,
230
+ ) ;
231
+
232
+ return queryBuilder ;
233
+ } ,
234
+ {
235
+ parameterMarkers : [ ] ,
236
+ values : [ ] ,
237
+ } ,
238
+ ) ;
239
+
240
+ const sqlString = `
241
+ INSERT INTO ${ eventsTable . name } (
242
+ stream_id,
243
+ stream_position,
244
+ partition,
245
+ event_data,
246
+ event_metadata,
247
+ event_schema_version,
248
+ event_type,
249
+ event_id,
250
+ is_archived
251
+ )
252
+ VALUES ${ query . parameterMarkers . join ( ', ' ) }
253
+ RETURNING
254
+ CAST(global_position as VARCHAR) AS global_position
255
+ ` ;
256
+ return { sqlString, values : query . values } ;
257
+ } ;
0 commit comments