@lossless.org/client 2.4.1 → 2.6.0
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/.smartconfig.json +2 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/nosqldb/classes.exactpersistence.d.ts +36 -1
- package/dist_ts/nosqldb/classes.exactpersistence.js +76 -2
- package/dist_ts/nosqldb/engineidentity.d.ts +13 -0
- package/dist_ts/nosqldb/engineidentity.js +16 -1
- package/dist_ts/objectstorage/classes.bucket.d.ts +11 -4
- package/dist_ts/objectstorage/classes.bucket.js +12 -5
- package/dist_ts/objectstorage/classes.exactuploaderror.js +3 -3
- package/dist_ts/objectstorage/interfaces.d.ts +34 -5
- package/dist_ts/objectstorage/internal.exactupload.operations.js +54 -22
- package/package.json +4 -6
- package/readme.md +15 -15
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/nosqldb/classes.exactpersistence.ts +121 -1
- package/ts/nosqldb/engineidentity.ts +18 -0
- package/ts/objectstorage/classes.bucket.ts +11 -4
- package/ts/objectstorage/classes.exactuploaderror.ts +2 -2
- package/ts/objectstorage/interfaces.ts +34 -5
- package/ts/objectstorage/internal.exactupload.operations.ts +79 -26
|
@@ -9,6 +9,7 @@ import { registerCollectionReconnectHandler } from './classes.collectionlifecycl
|
|
|
9
9
|
import {
|
|
10
10
|
SmartdataSession,
|
|
11
11
|
acquireSmartdataSession,
|
|
12
|
+
isOrdinarySessionInTransaction,
|
|
12
13
|
needsSmartdataWriteTransaction,
|
|
13
14
|
readPreviousWriteRevisions,
|
|
14
15
|
reportSmartdataCollectionWrite,
|
|
@@ -19,6 +20,11 @@ import {
|
|
|
19
20
|
trackSmartdataRead,
|
|
20
21
|
} from './classes.session.js';
|
|
21
22
|
import { runSmartdataWrite } from './classes.readtracker.js';
|
|
23
|
+
import {
|
|
24
|
+
type ISmartdataEngineIdentity,
|
|
25
|
+
readFenceEngineRelease,
|
|
26
|
+
smartdataReadFence,
|
|
27
|
+
} from './engineidentity.js';
|
|
22
28
|
import {
|
|
23
29
|
type SmartdataOperationDeadline,
|
|
24
30
|
requireOperationSignal,
|
|
@@ -173,6 +179,22 @@ export type TExactDeleteResult =
|
|
|
173
179
|
deletedCount: 0;
|
|
174
180
|
};
|
|
175
181
|
|
|
182
|
+
/**
|
|
183
|
+
* The result of an exact read fence: the identity is fenced for the commit of
|
|
184
|
+
* the session's active transaction.
|
|
185
|
+
*/
|
|
186
|
+
export type TExactReadFenceResult = {
|
|
187
|
+
status: 'fenced';
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* A model's identity in the form `findStoredOne()` takes it: its `reconcileBy`
|
|
192
|
+
* field and the value, `{ [reconcileBy]: value }`.
|
|
193
|
+
*/
|
|
194
|
+
export type TExactIdentity<TPersisted extends object> = Partial<
|
|
195
|
+
Record<TExactIdentityKey<TPersisted>, string>
|
|
196
|
+
>;
|
|
197
|
+
|
|
176
198
|
export type TExactPersistenceErrorCode =
|
|
177
199
|
| 'ambiguous_write'
|
|
178
200
|
| 'invalid_configuration'
|
|
@@ -236,7 +258,7 @@ export interface IExactReadOptions extends IExactSessionOptions {
|
|
|
236
258
|
|
|
237
259
|
/**
|
|
238
260
|
* Options of every exact call: `insert()`, `transition()`, `delete()`,
|
|
239
|
-
* `count()`, `distinct()` and `findStoredOne()`, and through
|
|
261
|
+
* `readFence()`, `count()`, `distinct()` and `findStoredOne()`, and through
|
|
240
262
|
* `IFindStoredOptions` `findStored()`.
|
|
241
263
|
*/
|
|
242
264
|
export interface IExactBoundedOptions extends IExactReadOptions {
|
|
@@ -2132,8 +2154,106 @@ export class SmartdataExactCollection<
|
|
|
2132
2154
|
);
|
|
2133
2155
|
});
|
|
2134
2156
|
}
|
|
2157
|
+
|
|
2158
|
+
/**
|
|
2159
|
+
* Fences a model identity for the commit of the session's active
|
|
2160
|
+
* transaction, whether a document holds it or not: the engine refuses the
|
|
2161
|
+
* commit as a transient write conflict, so `withTransaction()` re-runs the
|
|
2162
|
+
* attempt, when after the transaction's snapshot a committed write inserted
|
|
2163
|
+
* a document with that identity, or changed, deleted or re-inserted the one
|
|
2164
|
+
* holding it. Fences claim nothing: two transactions that fence one
|
|
2165
|
+
* identity both commit, and a fence never makes a writer wait or fail.
|
|
2166
|
+
*
|
|
2167
|
+
* `identityArg` names the identity as `findStoredOne()` does,
|
|
2168
|
+
* `{ [reconcileBy]: value }`, and is sent as one key fence on the model's
|
|
2169
|
+
* unique `reconcileBy` index, which preparation creates. Refused with
|
|
2170
|
+
* `unsupported_operation` before any command is sent without a session in an
|
|
2171
|
+
* active transaction, for a collection that is not prepared, and on an
|
|
2172
|
+
* engine that is not identified as NoSQLDB 10.11.0 or later.
|
|
2173
|
+
*/
|
|
2174
|
+
public async readFence(
|
|
2175
|
+
identityArg: TExactIdentity<TPersisted>,
|
|
2176
|
+
sessionOptionsArg: IExactBoundedOptions = {},
|
|
2177
|
+
): Promise<TExactReadFenceResult> {
|
|
2178
|
+
const identity = this.readFenceIdentity(identityArg);
|
|
2179
|
+
const session = sessionOptionsArg.session;
|
|
2180
|
+
if (!session || !isOrdinarySessionInTransaction(session)) {
|
|
2181
|
+
throw new SmartdataExactPersistenceError(
|
|
2182
|
+
'unsupported_operation',
|
|
2183
|
+
'Exact readFence needs a session in an active transaction: a fence only guards a transaction commit.',
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
2186
|
+
const engine = this.collection.smartdataDb.engineIdentity;
|
|
2187
|
+
if (!smartdataReadFence(engine)) {
|
|
2188
|
+
throw new SmartdataExactPersistenceError(
|
|
2189
|
+
'unsupported_operation',
|
|
2190
|
+
`Exact readFence needs NoSQLDB ${readFenceEngineRelease} or later; `
|
|
2191
|
+
+ `the connected engine is ${describeEngine(engine)}.`,
|
|
2192
|
+
);
|
|
2193
|
+
}
|
|
2194
|
+
return this.runOperation(sessionOptionsArg, async (sessionArg, deadlineArg) => {
|
|
2195
|
+
// A transient error, such as an aborted transaction, reaches the
|
|
2196
|
+
// driver's withTransaction unchanged.
|
|
2197
|
+
await this.collection.smartdataDb.mongoDb.command(
|
|
2198
|
+
{
|
|
2199
|
+
readFence: this.collection.mongoDbCollection.collectionName,
|
|
2200
|
+
key: this.reconcileBy,
|
|
2201
|
+
values: [identity],
|
|
2202
|
+
},
|
|
2203
|
+
{ session: sessionArg, ...deadlineArg.commandOptions() },
|
|
2204
|
+
);
|
|
2205
|
+
return { status: 'fenced' };
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
/**
|
|
2210
|
+
* Validates `{ [reconcileBy]: value }` without invoking accessors or proxies:
|
|
2211
|
+
* exactly one enumerable data property, the model's `reconcileBy`, holding
|
|
2212
|
+
* a nonempty string.
|
|
2213
|
+
*/
|
|
2214
|
+
private readFenceIdentity(identityArg: unknown): string {
|
|
2215
|
+
if (
|
|
2216
|
+
typeof identityArg !== 'object' ||
|
|
2217
|
+
identityArg === null ||
|
|
2218
|
+
plugins.nodeUtil.types.isProxy(identityArg) ||
|
|
2219
|
+
!isPlainObject(identityArg)
|
|
2220
|
+
) {
|
|
2221
|
+
throw new SmartdataExactPersistenceError(
|
|
2222
|
+
'invalid_document',
|
|
2223
|
+
`Exact readFence takes the identity as { ${this.reconcileBy}: value }.`,
|
|
2224
|
+
);
|
|
2225
|
+
}
|
|
2226
|
+
const keys = Reflect.ownKeys(identityArg);
|
|
2227
|
+
const descriptor = Object.getOwnPropertyDescriptor(identityArg, this.reconcileBy);
|
|
2228
|
+
if (
|
|
2229
|
+
keys.length !== 1 ||
|
|
2230
|
+
keys[0] !== this.reconcileBy ||
|
|
2231
|
+
!descriptor ||
|
|
2232
|
+
!('value' in descriptor) ||
|
|
2233
|
+
descriptor.enumerable !== true ||
|
|
2234
|
+
typeof descriptor.value !== 'string' ||
|
|
2235
|
+
descriptor.value.trim().length === 0
|
|
2236
|
+
) {
|
|
2237
|
+
throw new SmartdataExactPersistenceError(
|
|
2238
|
+
'invalid_document',
|
|
2239
|
+
`Exact readFence takes the identity as one inert { ${this.reconcileBy}: nonempty string } property.`,
|
|
2240
|
+
);
|
|
2241
|
+
}
|
|
2242
|
+
return descriptor.value;
|
|
2243
|
+
}
|
|
2135
2244
|
}
|
|
2136
2245
|
|
|
2246
|
+
const describeEngine = (identityArg: ISmartdataEngineIdentity): string => {
|
|
2247
|
+
switch (identityArg.product) {
|
|
2248
|
+
case 'NoSQLDB':
|
|
2249
|
+
return `NoSQLDB ${identityArg.version ?? 'of an unstated version'}`;
|
|
2250
|
+
case 'MongoDB':
|
|
2251
|
+
return 'MongoDB';
|
|
2252
|
+
case 'unknown':
|
|
2253
|
+
return 'unidentified';
|
|
2254
|
+
}
|
|
2255
|
+
};
|
|
2256
|
+
|
|
2137
2257
|
export function exactPersistence<
|
|
2138
2258
|
TAssertion extends TExactDocumentAssertion<object>,
|
|
2139
2259
|
>(
|
|
@@ -38,6 +38,13 @@ const compositeGroupingEngineRelease = '10.5.0';
|
|
|
38
38
|
*/
|
|
39
39
|
const commitJournalEngineRelease = '10.9.0';
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* The NoSQLDB release that answers the `readFence` transaction statement:
|
|
43
|
+
* @lossless.org/nosqldb 10.11.0. A minimum release, like every gate here:
|
|
44
|
+
* every later release supports it.
|
|
45
|
+
*/
|
|
46
|
+
export const readFenceEngineRelease = '10.11.0';
|
|
47
|
+
|
|
41
48
|
const parseEngineVersion = (versionArg: string): readonly number[] | undefined => {
|
|
42
49
|
const parts = versionArg.split('.');
|
|
43
50
|
if (parts.length !== 3) {
|
|
@@ -107,6 +114,17 @@ export const smartdataCommitJournal = (identityArg: ISmartdataEngineIdentity): b
|
|
|
107
114
|
&& identityArg.version !== null
|
|
108
115
|
&& engineVersionReaches(identityArg.version, commitJournalEngineRelease);
|
|
109
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Whether the identified engine answers `readFence`: fences documents a
|
|
119
|
+
* transaction read so that its commit is refused when one of them changed
|
|
120
|
+
* after the transaction's snapshot. Only NoSQLDB does, from 10.11.0 on;
|
|
121
|
+
* MongoDB has no such statement, and an unidentified engine enables nothing.
|
|
122
|
+
*/
|
|
123
|
+
export const smartdataReadFence = (identityArg: ISmartdataEngineIdentity): boolean =>
|
|
124
|
+
identityArg.product === 'NoSQLDB'
|
|
125
|
+
&& identityArg.version !== null
|
|
126
|
+
&& engineVersionReaches(identityArg.version, readFenceEngineRelease);
|
|
127
|
+
|
|
110
128
|
export const composeSmartdataCapabilities = (
|
|
111
129
|
backendArg: TSmartdataEngineBackend,
|
|
112
130
|
compositeGroupingArg: TCompositeGrouping,
|
|
@@ -935,14 +935,21 @@ export class Bucket {
|
|
|
935
935
|
}
|
|
936
936
|
|
|
937
937
|
/**
|
|
938
|
-
*
|
|
939
|
-
* and create-only
|
|
938
|
+
* Stores an object from a stream while enforcing the declared byte length
|
|
939
|
+
* (or the maximum of an undeclared one) and the destination mode: create-only
|
|
940
|
+
* with a `capability` from probeExactUploadCapability(), or replace with
|
|
941
|
+
* `overwrite: true`, which sends no `If-None-Match` and needs no capability.
|
|
942
|
+
* A replaced object stays readable and unchanged until the final
|
|
943
|
+
* CompleteMultipartUpload (or the PutObject of an empty source) replaces it
|
|
944
|
+
* in one step.
|
|
940
945
|
*
|
|
941
946
|
* Non-empty inputs always use multipart upload and never fall back to a
|
|
942
|
-
* single streaming PutObject request
|
|
947
|
+
* single streaming PutObject request, so each part is its own request,
|
|
948
|
+
* bounded by `requestTimeoutMs` on its own. Memory is bounded by the returned
|
|
943
949
|
* multipart plan. Every source chunk must be no larger than the effective
|
|
944
950
|
* multipart part size (5 MiB by default, or requestedPartSize after protocol
|
|
945
|
-
* normalization).
|
|
951
|
+
* normalization). Any failure aborts the multipart upload instead of
|
|
952
|
+
* completing it.
|
|
946
953
|
*/
|
|
947
954
|
public fastPutStreamExact(
|
|
948
955
|
optionsArg: interfaces.IFastPutStreamExactOptions,
|
|
@@ -6,10 +6,10 @@ import type {
|
|
|
6
6
|
const phaseMessages: Record<TExactUploadFailurePhase, string> = {
|
|
7
7
|
capability: 'The exact upload capability is invalid or stale.',
|
|
8
8
|
source: 'The exact upload source could not be consumed.',
|
|
9
|
-
'put-zero': 'The
|
|
9
|
+
'put-zero': 'The zero-byte object upload failed.',
|
|
10
10
|
'create-multipart': 'The multipart upload could not be created.',
|
|
11
11
|
'upload-part': 'A multipart upload part failed.',
|
|
12
|
-
'complete-multipart': 'The
|
|
12
|
+
'complete-multipart': 'The multipart completion failed.',
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export class ExactUploadError extends Error {
|
|
@@ -214,7 +214,7 @@ declare const exactUploadCapabilityBrand: unique symbol;
|
|
|
214
214
|
|
|
215
215
|
/**
|
|
216
216
|
* Opaque proof that the current bucket provider enforces the conditional
|
|
217
|
-
* operations required by Bucket.fastPutStreamExact().
|
|
217
|
+
* operations required by a create-only Bucket.fastPutStreamExact().
|
|
218
218
|
*
|
|
219
219
|
* Capabilities can only be minted by Bucket.probeExactUploadCapability().
|
|
220
220
|
* They are bound to one exact Bucket object and its current SmartBucket,
|
|
@@ -262,7 +262,25 @@ export interface IExactUploadNativeObjectProperties {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
export interface IFastPutStreamExactOptions {
|
|
265
|
-
|
|
265
|
+
/**
|
|
266
|
+
* Create-only upload: a capability from
|
|
267
|
+
* Bucket.probeExactUploadCapability(). The PutObject and
|
|
268
|
+
* CompleteMultipartUpload are sent with `If-None-Match: *`, so the upload
|
|
269
|
+
* fails instead of replacing an existing object. Exactly one of
|
|
270
|
+
* `capability` and `overwrite: true` must be given.
|
|
271
|
+
*/
|
|
272
|
+
capability?: IExactUploadCapability;
|
|
273
|
+
/**
|
|
274
|
+
* Replace upload: `true` stores the object whether or not the key exists,
|
|
275
|
+
* with the same multipart plan, part bounds, buffering limit, failure
|
|
276
|
+
* phases and abort-on-failure cleanup as a create-only upload, but without
|
|
277
|
+
* `If-None-Match` and without a capability. An existing object stays
|
|
278
|
+
* readable and unchanged until the final CompleteMultipartUpload (or the
|
|
279
|
+
* PutObject of an empty source) replaces it in one step; a failure before
|
|
280
|
+
* that request leaves it as it was. Exactly one of `capability` and
|
|
281
|
+
* `overwrite: true` must be given.
|
|
282
|
+
*/
|
|
283
|
+
overwrite?: boolean;
|
|
266
284
|
path: string;
|
|
267
285
|
/**
|
|
268
286
|
* Every emitted chunk must be no larger than the effective multipart part
|
|
@@ -294,6 +312,10 @@ export interface IFastPutStreamExactOptions {
|
|
|
294
312
|
signal?: AbortSignal;
|
|
295
313
|
}
|
|
296
314
|
|
|
315
|
+
/**
|
|
316
|
+
* The step an exact upload failed in. A replace upload (`overwrite: true`)
|
|
317
|
+
* has no capability step and never fails in `'capability'`.
|
|
318
|
+
*/
|
|
297
319
|
export type TExactUploadFailurePhase =
|
|
298
320
|
| 'capability'
|
|
299
321
|
| 'source'
|
|
@@ -338,9 +360,16 @@ export interface IExactUploadFailureState {
|
|
|
338
360
|
maxContentLength?: number;
|
|
339
361
|
/**
|
|
340
362
|
* Whether this operation can prove that it did not publish the destination.
|
|
341
|
-
* A
|
|
342
|
-
*
|
|
343
|
-
*
|
|
363
|
+
* A PutObject or CompleteMultipartUpload response can be lost after the
|
|
364
|
+
* provider has committed the object, so those failures are intentionally
|
|
365
|
+
* reported as ambiguous. A create-only upload reports a definitive
|
|
366
|
+
* conditional rejection (HTTP 409 or 412) as not published.
|
|
367
|
+
*
|
|
368
|
+
* For a replace upload (`overwrite: true`), `'not-published'` means the
|
|
369
|
+
* destination is as it was before the upload, the old object included, and
|
|
370
|
+
* `'ambiguous'` means either the old object or the new one is visible; every
|
|
371
|
+
* failed PutObject or CompleteMultipartUpload that was sent is ambiguous,
|
|
372
|
+
* since no precondition was sent whose rejection would prove otherwise.
|
|
344
373
|
*/
|
|
345
374
|
destinationPublication: 'not-published' | 'ambiguous';
|
|
346
375
|
multipartUploadId?: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import { ExactUploadError } from './classes.exactuploaderror.js';
|
|
3
3
|
import type {
|
|
4
|
+
IExactUploadCapability,
|
|
4
5
|
IExactUploadCapabilityProbeOptions,
|
|
5
6
|
IExactUploadResult,
|
|
6
7
|
IFastPutStreamExactOptions,
|
|
@@ -10,6 +11,7 @@ import type {
|
|
|
10
11
|
TExactUploadMultipartCleanup,
|
|
11
12
|
} from './interfaces.js';
|
|
12
13
|
import {
|
|
14
|
+
beginBasicBucketOperation,
|
|
13
15
|
beginExactUploadCapabilityOperation,
|
|
14
16
|
beginExactUploadProbeOperation,
|
|
15
17
|
mintExactUploadCapability,
|
|
@@ -1345,10 +1347,48 @@ const planExactUpload = (optionsArg: IFastPutStreamExactOptions) => {
|
|
|
1345
1347
|
};
|
|
1346
1348
|
};
|
|
1347
1349
|
|
|
1348
|
-
|
|
1350
|
+
type TExactUploadDestinationMode =
|
|
1351
|
+
| {
|
|
1352
|
+
replace: false;
|
|
1353
|
+
capability: IExactUploadCapability;
|
|
1354
|
+
}
|
|
1355
|
+
| {
|
|
1356
|
+
replace: true;
|
|
1357
|
+
};
|
|
1358
|
+
|
|
1359
|
+
/**
|
|
1360
|
+
* Reads the destination mode: create-only with a capability, or replace with
|
|
1361
|
+
* `overwrite: true`. Exactly one of the two must be given.
|
|
1362
|
+
*/
|
|
1363
|
+
const resolveDestinationMode = (
|
|
1364
|
+
optionsArg: IFastPutStreamExactOptions,
|
|
1365
|
+
): TExactUploadDestinationMode => {
|
|
1366
|
+
if (
|
|
1367
|
+
optionsArg.overwrite !== undefined
|
|
1368
|
+
&& typeof optionsArg.overwrite !== 'boolean'
|
|
1369
|
+
) {
|
|
1370
|
+
throw new TypeError('overwrite must be a boolean.');
|
|
1371
|
+
}
|
|
1372
|
+
const replace = optionsArg.overwrite === true;
|
|
1373
|
+
if (replace === (optionsArg.capability !== undefined)) {
|
|
1374
|
+
throw new TypeError(
|
|
1375
|
+
'Exactly one of capability and overwrite: true must be given.',
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
return optionsArg.capability === undefined
|
|
1379
|
+
? { replace: true }
|
|
1380
|
+
: { replace: false, capability: optionsArg.capability };
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
/**
|
|
1384
|
+
* Sends the PutObject of an empty source, create-only with
|
|
1385
|
+
* `If-None-Match: *` unless the upload replaces.
|
|
1386
|
+
*/
|
|
1387
|
+
const sendZeroPut = (
|
|
1349
1388
|
operationArg: IExactUploadOperation,
|
|
1350
1389
|
optionsArg: IFastPutStreamExactOptions,
|
|
1351
1390
|
contentTypeArg: string | undefined,
|
|
1391
|
+
replaceArg: boolean,
|
|
1352
1392
|
onAttemptArg: () => void,
|
|
1353
1393
|
) => {
|
|
1354
1394
|
return sendOperationCommand(
|
|
@@ -1363,7 +1403,7 @@ const sendConditionalZeroPut = (
|
|
|
1363
1403
|
ContentLength: 0,
|
|
1364
1404
|
Metadata: optionsArg.nativeMetadata,
|
|
1365
1405
|
ContentType: contentTypeArg,
|
|
1366
|
-
IfNoneMatch: '*',
|
|
1406
|
+
...(replaceArg ? {} : { IfNoneMatch: '*' }),
|
|
1367
1407
|
}),
|
|
1368
1408
|
{
|
|
1369
1409
|
abortSignal: signalArg,
|
|
@@ -1380,31 +1420,43 @@ export const fastPutStreamExact = async (
|
|
|
1380
1420
|
if (typeof optionsArg.path !== 'string' || optionsArg.path.length === 0) {
|
|
1381
1421
|
throw new TypeError('path must be a non-empty string.');
|
|
1382
1422
|
}
|
|
1423
|
+
const destinationMode = resolveDestinationMode(optionsArg);
|
|
1424
|
+
const replace = destinationMode.replace;
|
|
1383
1425
|
const nativeObjectProperties = snapshotNativeObjectProperties(
|
|
1384
1426
|
optionsArg.nativeObjectProperties,
|
|
1385
1427
|
);
|
|
1386
1428
|
const { plan, lengthState } = planExactUpload(optionsArg);
|
|
1429
|
+
// Only a create-only upload sends a precondition, so only its definitive
|
|
1430
|
+
// rejection proves that the request did not publish the destination.
|
|
1431
|
+
const provesNotPublished = (errorArg: unknown): boolean =>
|
|
1432
|
+
!replace && isDefinitiveConditionalWriteRejection(errorArg);
|
|
1387
1433
|
|
|
1388
1434
|
let operation: IExactUploadOperation;
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1435
|
+
if (destinationMode.replace) {
|
|
1436
|
+
// A replace upload has no capability step: it starts like every other
|
|
1437
|
+
// bucket request, which refuses an invalid signal or a closed SmartBucket.
|
|
1438
|
+
operation = beginBasicBucketOperation(bucketRefArg, optionsArg.signal);
|
|
1439
|
+
} else {
|
|
1440
|
+
try {
|
|
1441
|
+
operation = beginExactUploadCapabilityOperation(
|
|
1442
|
+
bucketRefArg,
|
|
1443
|
+
destinationMode.capability,
|
|
1444
|
+
optionsArg.signal,
|
|
1445
|
+
);
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
throw new ExactUploadError(
|
|
1448
|
+
{
|
|
1449
|
+
phase: 'capability',
|
|
1450
|
+
destinationPath: optionsArg.path,
|
|
1451
|
+
...lengthState,
|
|
1452
|
+
destinationPublication: 'not-published',
|
|
1453
|
+
multipartCleanup: {
|
|
1454
|
+
status: 'not-required',
|
|
1455
|
+
},
|
|
1404
1456
|
},
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1457
|
+
error,
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1408
1460
|
}
|
|
1409
1461
|
|
|
1410
1462
|
let readableStream: ReadableStream<Uint8Array>;
|
|
@@ -1467,10 +1519,11 @@ export const fastPutStreamExact = async (
|
|
|
1467
1519
|
}
|
|
1468
1520
|
|
|
1469
1521
|
try {
|
|
1470
|
-
const putOutput = await
|
|
1522
|
+
const putOutput = await sendZeroPut(
|
|
1471
1523
|
operation,
|
|
1472
1524
|
optionsArg,
|
|
1473
1525
|
nativeObjectProperties?.contentType,
|
|
1526
|
+
replace,
|
|
1474
1527
|
() => {
|
|
1475
1528
|
putRequestAttempted = true;
|
|
1476
1529
|
},
|
|
@@ -1494,8 +1547,7 @@ export const fastPutStreamExact = async (
|
|
|
1494
1547
|
destinationPath: optionsArg.path,
|
|
1495
1548
|
...lengthState,
|
|
1496
1549
|
destinationPublication:
|
|
1497
|
-
putRequestAttempted &&
|
|
1498
|
-
!isDefinitiveConditionalWriteRejection(error)
|
|
1550
|
+
putRequestAttempted && !provesNotPublished(error)
|
|
1499
1551
|
? 'ambiguous'
|
|
1500
1552
|
: 'not-published',
|
|
1501
1553
|
multipartCleanup: {
|
|
@@ -1638,10 +1690,11 @@ export const fastPutStreamExact = async (
|
|
|
1638
1690
|
if (!multipartUploadCreation) {
|
|
1639
1691
|
// Only an empty source of undeclared length reaches this point.
|
|
1640
1692
|
phase = 'put-zero';
|
|
1641
|
-
const putOutput = await
|
|
1693
|
+
const putOutput = await sendZeroPut(
|
|
1642
1694
|
operation,
|
|
1643
1695
|
optionsArg,
|
|
1644
1696
|
nativeObjectProperties?.contentType,
|
|
1697
|
+
replace,
|
|
1645
1698
|
() => {
|
|
1646
1699
|
zeroPutAttempted = true;
|
|
1647
1700
|
},
|
|
@@ -1670,7 +1723,7 @@ export const fastPutStreamExact = async (
|
|
|
1670
1723
|
MultipartUpload: {
|
|
1671
1724
|
Parts: completedParts,
|
|
1672
1725
|
},
|
|
1673
|
-
IfNoneMatch: '*',
|
|
1726
|
+
...(replace ? {} : { IfNoneMatch: '*' }),
|
|
1674
1727
|
MpuObjectSize: consumptionResult.bytesConsumed,
|
|
1675
1728
|
}),
|
|
1676
1729
|
{
|
|
@@ -1789,7 +1842,7 @@ export const fastPutStreamExact = async (
|
|
|
1789
1842
|
(phase === 'complete-multipart' && completeRequestAttempted) ||
|
|
1790
1843
|
(phase === 'put-zero' && zeroPutAttempted)
|
|
1791
1844
|
) &&
|
|
1792
|
-
!
|
|
1845
|
+
!provesNotPublished(error)
|
|
1793
1846
|
? 'ambiguous'
|
|
1794
1847
|
: 'not-published',
|
|
1795
1848
|
multipartUploadId: uploadId,
|