@eik/core 2.1.78 → 2.1.80
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/CHANGELOG.md +14 -0
- package/lib/handlers/alias.put.js +12 -2
- package/lib/handlers/map.put.js +61 -17
- package/lib/handlers/pkg.put.js +71 -21
- package/lib/utils/utils.js +77 -0
- package/package.json +4 -4
- package/types/handlers/map.put.d.ts +13 -4
- package/types/handlers/pkg.put.d.ts +13 -4
- package/types/utils/utils.d.ts +27 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [2.1.80](https://github.com/eik-lib/core/compare/v2.1.79...v2.1.80) (2026-08-19)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **deps:** update eik packages ([#639](https://github.com/eik-lib/core/issues/639)) ([4e0aa6c](https://github.com/eik-lib/core/commit/4e0aa6caf9630a79af03dd2889392d60cf8b276f))
|
|
7
|
+
|
|
8
|
+
## [2.1.79](https://github.com/eik-lib/core/compare/v2.1.78...v2.1.79) (2026-08-19)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* use atomic sink writes to prevent concurrent upload races ([#638](https://github.com/eik-lib/core/issues/638)) ([cf29a68](https://github.com/eik-lib/core/commit/cf29a6838622360f8c80bc83c86d07536d580f33)), closes [#1](https://github.com/eik-lib/core/issues/1) [#2](https://github.com/eik-lib/core/issues/2) [#3](https://github.com/eik-lib/core/issues/3) [#6](https://github.com/eik-lib/core/issues/6)
|
|
14
|
+
|
|
1
15
|
## [2.1.78](https://github.com/eik-lib/core/compare/v2.1.77...v2.1.78) (2026-08-17)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
createFilePathToAlias,
|
|
9
9
|
createFilePathToAliasOrigin,
|
|
10
10
|
} from "../utils/path-builders-fs.js";
|
|
11
|
-
import { decodeUriComponent,
|
|
11
|
+
import { decodeUriComponent, writeJSONWithOptions } from "../utils/utils.js";
|
|
12
12
|
import { createURIToAlias } from "../utils/path-builders-uri.js";
|
|
13
13
|
import MultipartParser from "../multipart/parser.js";
|
|
14
14
|
import HttpIncoming from "../classes/http-incoming.js";
|
|
@@ -109,8 +109,18 @@ const AliasPut = class AliasPut {
|
|
|
109
109
|
);
|
|
110
110
|
|
|
111
111
|
try {
|
|
112
|
-
await
|
|
112
|
+
await writeJSONWithOptions(
|
|
113
|
+
this._sink,
|
|
114
|
+
path,
|
|
115
|
+
alias,
|
|
116
|
+
"application/json",
|
|
117
|
+
{ ifNotExists: true },
|
|
118
|
+
);
|
|
113
119
|
} catch (error) {
|
|
120
|
+
const err = /** @type {any} */ (error);
|
|
121
|
+
if (err.code === "ALREADY_EXISTS") {
|
|
122
|
+
throw new HttpError.Conflict();
|
|
123
|
+
}
|
|
114
124
|
this._log.error(
|
|
115
125
|
`alias:put - Failed writing alias to sink - Pathname: ${path}`,
|
|
116
126
|
);
|
package/lib/handlers/map.put.js
CHANGED
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
decodeUriComponent,
|
|
21
21
|
streamCollector,
|
|
22
22
|
writeJSON,
|
|
23
|
-
|
|
23
|
+
writeJSONWithOptions,
|
|
24
|
+
readJSONWithGeneration,
|
|
24
25
|
} from "../utils/utils.js";
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -172,7 +173,7 @@ const MapPut = class MapPut {
|
|
|
172
173
|
|
|
173
174
|
/**
|
|
174
175
|
* @param {any} incoming
|
|
175
|
-
* @returns {Promise<InstanceType<typeof Versions
|
|
176
|
+
* @returns {Promise<{ versions: InstanceType<typeof Versions>, generation: string }>}
|
|
176
177
|
*/
|
|
177
178
|
async _readVersions(incoming) {
|
|
178
179
|
const path = createFilePathToVersion(incoming);
|
|
@@ -186,30 +187,65 @@ const MapPut = class MapPut {
|
|
|
186
187
|
this._log.info(
|
|
187
188
|
`map:put - Version meta file did not exist in sink - Create new - Pathname: ${path}`,
|
|
188
189
|
);
|
|
189
|
-
return new Versions(incoming);
|
|
190
|
+
return { versions: new Versions(incoming), generation: "" };
|
|
190
191
|
}
|
|
191
192
|
|
|
192
193
|
// File exists. Any failure here must propagate rather than be
|
|
193
194
|
// swallowed — silently falling back to an empty Versions object
|
|
194
195
|
// would cause _writeVersions to overwrite the index and erase the
|
|
195
196
|
// entire version history.
|
|
196
|
-
const
|
|
197
|
+
const { data, generation } = await readJSONWithGeneration(this._sink, path);
|
|
197
198
|
this._log.info(
|
|
198
199
|
`map:put - Successfully read version meta file from sink - Pathname: ${path}`,
|
|
199
200
|
);
|
|
200
|
-
return new Versions(
|
|
201
|
+
return { versions: new Versions(data), generation };
|
|
201
202
|
}
|
|
202
203
|
|
|
203
204
|
/**
|
|
205
|
+
* Writes versions.json with compare-and-swap semantics when a generation
|
|
206
|
+
* token is available. On conflict the read-modify-write cycle is retried.
|
|
207
|
+
*
|
|
204
208
|
* @param {any} incoming
|
|
205
|
-
* @param {
|
|
209
|
+
* @param {InstanceType<typeof Versions>} versions
|
|
210
|
+
* @param {string} generation
|
|
211
|
+
* @param {string} pVersion
|
|
212
|
+
* @param {string} integrity
|
|
206
213
|
*/
|
|
207
|
-
async _writeVersions(incoming, versions) {
|
|
214
|
+
async _writeVersions(incoming, versions, generation, pVersion, integrity) {
|
|
208
215
|
const path = createFilePathToVersion(incoming);
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
216
|
+
const maxRetries = 3;
|
|
217
|
+
/** @type {{ ifGenerationMatch?: string | number }} */
|
|
218
|
+
let options = generation ? { ifGenerationMatch: generation } : {};
|
|
219
|
+
let currentVersions = versions;
|
|
220
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
221
|
+
try {
|
|
222
|
+
await writeJSONWithOptions(
|
|
223
|
+
this._sink,
|
|
224
|
+
path,
|
|
225
|
+
currentVersions,
|
|
226
|
+
"application/json",
|
|
227
|
+
options,
|
|
228
|
+
);
|
|
229
|
+
this._log.info(
|
|
230
|
+
`map:put - Successfully wrote version meta file to sink - Pathname: ${path}`,
|
|
231
|
+
);
|
|
232
|
+
return;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
const err = /** @type {any} */ (error);
|
|
235
|
+
if (err.code === "CONFLICT" && attempt < maxRetries) {
|
|
236
|
+
this._log.info(
|
|
237
|
+
`map:put - versions.json conflict on attempt ${attempt + 1}, retrying`,
|
|
238
|
+
);
|
|
239
|
+
const { versions: fresh, generation: newGen } =
|
|
240
|
+
await this._readVersions(incoming);
|
|
241
|
+
fresh.setVersion(pVersion, integrity);
|
|
242
|
+
currentVersions = fresh;
|
|
243
|
+
options = newGen ? { ifGenerationMatch: newGen } : {};
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
213
249
|
}
|
|
214
250
|
|
|
215
251
|
/**
|
|
@@ -258,11 +294,13 @@ const MapPut = class MapPut {
|
|
|
258
294
|
org,
|
|
259
295
|
});
|
|
260
296
|
|
|
261
|
-
const versions = await this._readVersions(incoming).catch(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
297
|
+
const { versions, generation } = await this._readVersions(incoming).catch(
|
|
298
|
+
() => {
|
|
299
|
+
const e = new HttpError.BadGateway();
|
|
300
|
+
end({ labels: { success: false, status: e.status, type: "map" } });
|
|
301
|
+
throw e;
|
|
302
|
+
},
|
|
303
|
+
);
|
|
266
304
|
|
|
267
305
|
if (!versions.check(pVersion)) {
|
|
268
306
|
this._log.info(
|
|
@@ -277,7 +315,13 @@ const MapPut = class MapPut {
|
|
|
277
315
|
versions.setVersion(pVersion, integrity);
|
|
278
316
|
|
|
279
317
|
try {
|
|
280
|
-
await this._writeVersions(
|
|
318
|
+
await this._writeVersions(
|
|
319
|
+
incoming,
|
|
320
|
+
versions,
|
|
321
|
+
generation,
|
|
322
|
+
pVersion,
|
|
323
|
+
integrity,
|
|
324
|
+
);
|
|
281
325
|
// eslint-disable-next-line no-unused-vars
|
|
282
326
|
} catch (error) {
|
|
283
327
|
const e = new HttpError.BadGateway();
|
package/lib/handlers/pkg.put.js
CHANGED
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
decodeUriComponent,
|
|
15
15
|
writeJSON,
|
|
16
|
-
|
|
16
|
+
writeJSONWithOptions,
|
|
17
|
+
readJSONWithGeneration,
|
|
17
18
|
readEikJson,
|
|
18
19
|
} from "../utils/utils.js";
|
|
19
20
|
import { createURIPathToPkgLog } from "../utils/path-builders-uri.js";
|
|
@@ -129,11 +130,12 @@ const PkgPut = class PkgPut {
|
|
|
129
130
|
type: incoming.type,
|
|
130
131
|
};
|
|
131
132
|
const eikJsonPath = createFilePathToEikJson(incoming);
|
|
132
|
-
const buffer = await
|
|
133
|
+
const buffer = await writeJSONWithOptions(
|
|
133
134
|
this._sink,
|
|
134
135
|
eikJsonPath,
|
|
135
136
|
content,
|
|
136
137
|
"application/json",
|
|
138
|
+
{ ifNotExists: true },
|
|
137
139
|
);
|
|
138
140
|
this._log.info(
|
|
139
141
|
`pkg:put - Successfully wrote eik.json to sink - Pathname: ${eikJsonPath}`,
|
|
@@ -155,7 +157,7 @@ const PkgPut = class PkgPut {
|
|
|
155
157
|
|
|
156
158
|
/**
|
|
157
159
|
* @param {any} incoming
|
|
158
|
-
* @returns {Promise<InstanceType<typeof Versions
|
|
160
|
+
* @returns {Promise<{ versions: InstanceType<typeof Versions>, generation: string }>}
|
|
159
161
|
*/
|
|
160
162
|
async _readVersions(incoming) {
|
|
161
163
|
const path = createFilePathToVersion(incoming);
|
|
@@ -169,18 +171,18 @@ const PkgPut = class PkgPut {
|
|
|
169
171
|
this._log.info(
|
|
170
172
|
`pkg:put - Version meta file did not exist in sink - Create new - Pathname: ${path}`,
|
|
171
173
|
);
|
|
172
|
-
return new Versions(incoming);
|
|
174
|
+
return { versions: new Versions(incoming), generation: "" };
|
|
173
175
|
}
|
|
174
176
|
|
|
175
177
|
// File exists. Any failure here (GCS error, corrupt JSON, network
|
|
176
178
|
// timeout) must propagate rather than be swallowed — silently falling
|
|
177
179
|
// back to an empty Versions object would cause _writeVersions to
|
|
178
180
|
// overwrite the index and erase the entire version history.
|
|
179
|
-
const
|
|
181
|
+
const { data, generation } = await readJSONWithGeneration(this._sink, path);
|
|
180
182
|
this._log.info(
|
|
181
183
|
`pkg:put - Successfully read version meta file from sink - Pathname: ${path}`,
|
|
182
184
|
);
|
|
183
|
-
return new Versions(
|
|
185
|
+
return { versions: new Versions(data), generation };
|
|
184
186
|
}
|
|
185
187
|
|
|
186
188
|
/**
|
|
@@ -205,15 +207,50 @@ const PkgPut = class PkgPut {
|
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
/**
|
|
210
|
+
* Writes versions.json with compare-and-swap semantics when a generation
|
|
211
|
+
* token is available. On conflict the read-modify-write cycle is retried.
|
|
212
|
+
*
|
|
208
213
|
* @param {any} incoming
|
|
209
|
-
* @param {
|
|
214
|
+
* @param {InstanceType<typeof Versions>} versions
|
|
215
|
+
* @param {string} generation
|
|
216
|
+
* @param {string} pVersion
|
|
217
|
+
* @param {string} integrity
|
|
210
218
|
*/
|
|
211
|
-
async _writeVersions(incoming, versions) {
|
|
219
|
+
async _writeVersions(incoming, versions, generation, pVersion, integrity) {
|
|
212
220
|
const path = createFilePathToVersion(incoming);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
221
|
+
const maxRetries = 3;
|
|
222
|
+
/** @type {{ ifGenerationMatch?: string | number }} */
|
|
223
|
+
let options = generation ? { ifGenerationMatch: generation } : {};
|
|
224
|
+
let currentVersions = versions;
|
|
225
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
226
|
+
try {
|
|
227
|
+
await writeJSONWithOptions(
|
|
228
|
+
this._sink,
|
|
229
|
+
path,
|
|
230
|
+
currentVersions,
|
|
231
|
+
"application/json",
|
|
232
|
+
options,
|
|
233
|
+
);
|
|
234
|
+
this._log.info(
|
|
235
|
+
`pkg:put - Successfully wrote version meta file to sink - Pathname: ${path}`,
|
|
236
|
+
);
|
|
237
|
+
return;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
const err = /** @type {any} */ (error);
|
|
240
|
+
if (err.code === "CONFLICT" && attempt < maxRetries) {
|
|
241
|
+
this._log.info(
|
|
242
|
+
`pkg:put - versions.json conflict on attempt ${attempt + 1}, retrying`,
|
|
243
|
+
);
|
|
244
|
+
const { versions: fresh, generation: newGen } =
|
|
245
|
+
await this._readVersions(incoming);
|
|
246
|
+
fresh.setVersion(pVersion, integrity);
|
|
247
|
+
currentVersions = fresh;
|
|
248
|
+
options = newGen ? { ifGenerationMatch: newGen } : {};
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
217
254
|
}
|
|
218
255
|
|
|
219
256
|
/**
|
|
@@ -275,11 +312,13 @@ const PkgPut = class PkgPut {
|
|
|
275
312
|
throw e;
|
|
276
313
|
}
|
|
277
314
|
|
|
278
|
-
const versions = await this._readVersions(incoming).catch(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
315
|
+
const { versions, generation } = await this._readVersions(incoming).catch(
|
|
316
|
+
() => {
|
|
317
|
+
const e = new HttpError.BadGateway();
|
|
318
|
+
end({ labels: { success: false, status: e.status, type } });
|
|
319
|
+
throw e;
|
|
320
|
+
},
|
|
321
|
+
);
|
|
283
322
|
|
|
284
323
|
const { pkg, eikJson } = await this._parser(incoming);
|
|
285
324
|
|
|
@@ -288,8 +327,15 @@ const PkgPut = class PkgPut {
|
|
|
288
327
|
let eikJsonAsset;
|
|
289
328
|
try {
|
|
290
329
|
eikJsonAsset = await this._writeEikJson(incoming, eikJson);
|
|
291
|
-
// eslint-disable-next-line no-unused-vars
|
|
292
330
|
} catch (error) {
|
|
331
|
+
const err = /** @type {any} */ (error);
|
|
332
|
+
if (err.code === "ALREADY_EXISTS") {
|
|
333
|
+
// Another replica committed this version while this upload was in
|
|
334
|
+
// flight. Treat it as a conflict identical to the early existence check.
|
|
335
|
+
const e = new HttpError.Conflict();
|
|
336
|
+
end({ labels: { success: false, status: e.status, type } });
|
|
337
|
+
throw e;
|
|
338
|
+
}
|
|
293
339
|
const e = new HttpError.BadGateway();
|
|
294
340
|
end({ labels: { success: false, status: e.status, type } });
|
|
295
341
|
throw e;
|
|
@@ -309,10 +355,14 @@ const PkgPut = class PkgPut {
|
|
|
309
355
|
throw e;
|
|
310
356
|
}
|
|
311
357
|
|
|
312
|
-
versions.setVersion(pVersion, pkg.integrity);
|
|
313
|
-
|
|
314
358
|
try {
|
|
315
|
-
await this._writeVersions(
|
|
359
|
+
await this._writeVersions(
|
|
360
|
+
incoming,
|
|
361
|
+
versions,
|
|
362
|
+
generation,
|
|
363
|
+
pVersion,
|
|
364
|
+
pkg.integrity,
|
|
365
|
+
);
|
|
316
366
|
// eslint-disable-next-line no-unused-vars
|
|
317
367
|
} catch (error) {
|
|
318
368
|
const e = new HttpError.BadGateway();
|
package/lib/utils/utils.js
CHANGED
|
@@ -107,9 +107,86 @@ const decodeUriComponent = (value) => {
|
|
|
107
107
|
return decodeURIComponent(value);
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Like readJSON but also returns the generation token from the ReadFile,
|
|
112
|
+
* which can be passed back to writeJSONWithOptions as ifGenerationMatch
|
|
113
|
+
* for compare-and-swap semantics.
|
|
114
|
+
*
|
|
115
|
+
* @param {any} sink
|
|
116
|
+
* @param {string} path
|
|
117
|
+
* @returns {Promise<{ data: any, generation: string }>}
|
|
118
|
+
*/
|
|
119
|
+
const readJSONWithGeneration = (sink, path) =>
|
|
120
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
121
|
+
new Promise(async (resolve, reject) => {
|
|
122
|
+
try {
|
|
123
|
+
/** @type {any[]} */
|
|
124
|
+
const buffer = [];
|
|
125
|
+
const from = await sink.read(path);
|
|
126
|
+
|
|
127
|
+
const to = new Writable({
|
|
128
|
+
objectMode: false,
|
|
129
|
+
write(chunk, encoding, callback) {
|
|
130
|
+
buffer.push(chunk);
|
|
131
|
+
callback();
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
pipeline(from.stream, to, (error) => {
|
|
136
|
+
if (error) return reject(error);
|
|
137
|
+
const str = Buffer.concat(buffer).toString("utf8");
|
|
138
|
+
try {
|
|
139
|
+
const obj = JSON.parse(str);
|
|
140
|
+
return resolve({ data: obj, generation: from.generation ?? "" });
|
|
141
|
+
} catch (err) {
|
|
142
|
+
return reject(err);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
} catch (error) {
|
|
146
|
+
reject(error);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Like writeJSON but accepts sink write options (e.g. { ifNotExists, ifGenerationMatch })
|
|
152
|
+
* for atomic conditional writes.
|
|
153
|
+
*
|
|
154
|
+
* @param {any} sink
|
|
155
|
+
* @param {string} path
|
|
156
|
+
* @param {any} obj
|
|
157
|
+
* @param {string} contentType
|
|
158
|
+
* @param {{ ifNotExists?: boolean, ifGenerationMatch?: string | number }} [options]
|
|
159
|
+
*/
|
|
160
|
+
const writeJSONWithOptions = (sink, path, obj, contentType, options = {}) =>
|
|
161
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
162
|
+
new Promise(async (resolve, reject) => {
|
|
163
|
+
try {
|
|
164
|
+
const buffer = Buffer.from(JSON.stringify(obj));
|
|
165
|
+
|
|
166
|
+
const from = new Readable({
|
|
167
|
+
objectMode: false,
|
|
168
|
+
read() {
|
|
169
|
+
this.push(buffer);
|
|
170
|
+
this.push(null);
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const to = await sink.write(path, contentType, options);
|
|
175
|
+
|
|
176
|
+
pipeline(from, to, (error) => {
|
|
177
|
+
if (error) return reject(error);
|
|
178
|
+
return resolve(buffer);
|
|
179
|
+
});
|
|
180
|
+
} catch (error) {
|
|
181
|
+
reject(error);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
110
185
|
export {
|
|
111
186
|
readJSON,
|
|
187
|
+
readJSONWithGeneration,
|
|
112
188
|
writeJSON,
|
|
189
|
+
writeJSONWithOptions,
|
|
113
190
|
streamCollector,
|
|
114
191
|
etagFromFsStat,
|
|
115
192
|
decodeUriComponent,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eik/core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.80",
|
|
4
4
|
"description": "Core server package",
|
|
5
5
|
"main": "lib/main.js",
|
|
6
6
|
"types": "./types/main.d.ts",
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"author": "",
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@eik/common": "5.
|
|
32
|
+
"@eik/common": "5.2.0",
|
|
33
33
|
"@eik/sink": "1.2.7",
|
|
34
|
-
"@eik/sink-file-system": "2.0.
|
|
34
|
+
"@eik/sink-file-system": "2.0.41",
|
|
35
35
|
"@eik/sink-memory": "2.0.37",
|
|
36
36
|
"@metrics/client": "2.5.5",
|
|
37
37
|
"abslog": "2.4.4",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@eik/eslint-config": "2.0.15",
|
|
48
48
|
"@eik/prettier-config": "1.0.2",
|
|
49
|
-
"@eik/semantic-release-config": "1.0.
|
|
49
|
+
"@eik/semantic-release-config": "1.0.19",
|
|
50
50
|
"@eik/typescript-config": "1.0.2",
|
|
51
51
|
"@types/readable-stream": "4.0.24",
|
|
52
52
|
"eslint": "10.4.1",
|
|
@@ -42,14 +42,23 @@ declare const MapPut: {
|
|
|
42
42
|
}): Promise<string>;
|
|
43
43
|
/**
|
|
44
44
|
* @param {any} incoming
|
|
45
|
-
* @returns {Promise<InstanceType<typeof Versions
|
|
45
|
+
* @returns {Promise<{ versions: InstanceType<typeof Versions>, generation: string }>}
|
|
46
46
|
*/
|
|
47
|
-
_readVersions(incoming: any): Promise<
|
|
47
|
+
_readVersions(incoming: any): Promise<{
|
|
48
|
+
versions: InstanceType<typeof Versions>;
|
|
49
|
+
generation: string;
|
|
50
|
+
}>;
|
|
48
51
|
/**
|
|
52
|
+
* Writes versions.json with compare-and-swap semantics when a generation
|
|
53
|
+
* token is available. On conflict the read-modify-write cycle is retried.
|
|
54
|
+
*
|
|
49
55
|
* @param {any} incoming
|
|
50
|
-
* @param {
|
|
56
|
+
* @param {InstanceType<typeof Versions>} versions
|
|
57
|
+
* @param {string} generation
|
|
58
|
+
* @param {string} pVersion
|
|
59
|
+
* @param {string} integrity
|
|
51
60
|
*/
|
|
52
|
-
_writeVersions(incoming: any, versions:
|
|
61
|
+
_writeVersions(incoming: any, versions: InstanceType<typeof Versions>, generation: string, pVersion: string, integrity: string): Promise<void>;
|
|
53
62
|
/**
|
|
54
63
|
* @param {any} req
|
|
55
64
|
* @param {any} user
|
|
@@ -82,18 +82,27 @@ declare const PkgPut: {
|
|
|
82
82
|
_writeEikJson(incoming: any, capturedEikJson: Record<string, any> | null): Promise<InstanceType<typeof Asset>>;
|
|
83
83
|
/**
|
|
84
84
|
* @param {any} incoming
|
|
85
|
-
* @returns {Promise<InstanceType<typeof Versions
|
|
85
|
+
* @returns {Promise<{ versions: InstanceType<typeof Versions>, generation: string }>}
|
|
86
86
|
*/
|
|
87
|
-
_readVersions(incoming: any): Promise<
|
|
87
|
+
_readVersions(incoming: any): Promise<{
|
|
88
|
+
versions: InstanceType<typeof Versions>;
|
|
89
|
+
generation: string;
|
|
90
|
+
}>;
|
|
88
91
|
/**
|
|
89
92
|
* @param {any} incoming
|
|
90
93
|
*/
|
|
91
94
|
_readVersion(incoming: any): Promise<boolean>;
|
|
92
95
|
/**
|
|
96
|
+
* Writes versions.json with compare-and-swap semantics when a generation
|
|
97
|
+
* token is available. On conflict the read-modify-write cycle is retried.
|
|
98
|
+
*
|
|
93
99
|
* @param {any} incoming
|
|
94
|
-
* @param {
|
|
100
|
+
* @param {InstanceType<typeof Versions>} versions
|
|
101
|
+
* @param {string} generation
|
|
102
|
+
* @param {string} pVersion
|
|
103
|
+
* @param {string} integrity
|
|
95
104
|
*/
|
|
96
|
-
_writeVersions(incoming: any, versions:
|
|
105
|
+
_writeVersions(incoming: any, versions: InstanceType<typeof Versions>, generation: string, pVersion: string, integrity: string): Promise<void>;
|
|
97
106
|
/**
|
|
98
107
|
* @param {any} req
|
|
99
108
|
* @param {any} user
|
package/types/utils/utils.d.ts
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
* @param {string} path
|
|
4
4
|
*/
|
|
5
5
|
export function readJSON(sink: any, path: string): Promise<any>;
|
|
6
|
+
/**
|
|
7
|
+
* Like readJSON but also returns the generation token from the ReadFile,
|
|
8
|
+
* which can be passed back to writeJSONWithOptions as ifGenerationMatch
|
|
9
|
+
* for compare-and-swap semantics.
|
|
10
|
+
*
|
|
11
|
+
* @param {any} sink
|
|
12
|
+
* @param {string} path
|
|
13
|
+
* @returns {Promise<{ data: any, generation: string }>}
|
|
14
|
+
*/
|
|
15
|
+
export function readJSONWithGeneration(sink: any, path: string): Promise<{
|
|
16
|
+
data: any;
|
|
17
|
+
generation: string;
|
|
18
|
+
}>;
|
|
6
19
|
/**
|
|
7
20
|
* @param {any} sink
|
|
8
21
|
* @param {string} path
|
|
@@ -10,6 +23,20 @@ export function readJSON(sink: any, path: string): Promise<any>;
|
|
|
10
23
|
* @param {string} contentType
|
|
11
24
|
*/
|
|
12
25
|
export function writeJSON(sink: any, path: string, obj: any, contentType: string): Promise<any>;
|
|
26
|
+
/**
|
|
27
|
+
* Like writeJSON but accepts sink write options (e.g. { ifNotExists, ifGenerationMatch })
|
|
28
|
+
* for atomic conditional writes.
|
|
29
|
+
*
|
|
30
|
+
* @param {any} sink
|
|
31
|
+
* @param {string} path
|
|
32
|
+
* @param {any} obj
|
|
33
|
+
* @param {string} contentType
|
|
34
|
+
* @param {{ ifNotExists?: boolean, ifGenerationMatch?: string | number }} [options]
|
|
35
|
+
*/
|
|
36
|
+
export function writeJSONWithOptions(sink: any, path: string, obj: any, contentType: string, options?: {
|
|
37
|
+
ifNotExists?: boolean;
|
|
38
|
+
ifGenerationMatch?: string | number;
|
|
39
|
+
}): Promise<any>;
|
|
13
40
|
/**
|
|
14
41
|
* @param {any} from
|
|
15
42
|
*/
|