@lossless.org/client 2.0.0 → 2.2.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/nosqldb/classes.persistence.d.ts +21 -1
- package/dist_ts/nosqldb/classes.persistence.js +23 -1
- package/dist_ts/nosqldb/classes.session.d.ts +8 -0
- package/dist_ts/nosqldb/classes.session.js +27 -2
- package/dist_ts/nosqldb/index.d.ts +1 -1
- package/dist_ts/nosqldb/index.js +2 -2
- package/dist_ts/objectstorage/interfaces.d.ts +16 -2
- package/dist_ts/objectstorage/internal.exactupload.operations.js +148 -77
- package/dist_ts/objectstorage/internal.multipart.planner.d.ts +8 -2
- package/dist_ts/objectstorage/internal.multipart.planner.js +30 -1
- package/dist_ts/objectstorage/internal.multipart.stream.js +68 -19
- package/dist_ts/objectstorage/internal.multipart.types.d.ts +32 -3
- package/package.json +1 -1
- package/readme.md +2 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/nosqldb/classes.persistence.ts +30 -1
- package/ts/nosqldb/classes.session.ts +26 -0
- package/ts/nosqldb/index.ts +1 -1
- package/ts/objectstorage/interfaces.ts +16 -2
- package/ts/objectstorage/internal.exactupload.operations.ts +190 -97
- package/ts/objectstorage/internal.multipart.planner.ts +46 -2
- package/ts/objectstorage/internal.multipart.stream.ts +79 -21
- package/ts/objectstorage/internal.multipart.types.ts +35 -2
|
@@ -60,6 +60,7 @@ const failureMessages: Record<TMultipartStreamFailureKind, string> = {
|
|
|
60
60
|
'The multipart source emitted too many consecutive empty chunks.',
|
|
61
61
|
'source-short': 'The multipart source ended before the declared content length.',
|
|
62
62
|
'source-extra': 'The multipart source exceeded the declared content length.',
|
|
63
|
+
'source-over-maximum': 'The multipart source exceeded the maximum content length.',
|
|
63
64
|
'part-callback': 'A multipart part callback failed.',
|
|
64
65
|
};
|
|
65
66
|
const failureSettlementMaximumTimeoutMs = 30_000;
|
|
@@ -96,13 +97,6 @@ export class MultipartStreamConsumptionError extends Error {
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
const validatePlan = (planArg: TMultipartUploadPlan): void => {
|
|
99
|
-
if (
|
|
100
|
-
!Number.isSafeInteger(planArg.objectSize) ||
|
|
101
|
-
planArg.objectSize < 0 ||
|
|
102
|
-
planArg.objectSize > MULTIPART_MAX_OBJECT_SIZE
|
|
103
|
-
) {
|
|
104
|
-
throw new TypeError('plan.objectSize is invalid.');
|
|
105
|
-
}
|
|
106
100
|
if (
|
|
107
101
|
!Number.isSafeInteger(planArg.concurrency) ||
|
|
108
102
|
planArg.concurrency < 1 ||
|
|
@@ -110,6 +104,32 @@ const validatePlan = (planArg: TMultipartUploadPlan): void => {
|
|
|
110
104
|
) {
|
|
111
105
|
throw new TypeError('plan.concurrency is invalid.');
|
|
112
106
|
}
|
|
107
|
+
if (planArg.classification === 'bounded') {
|
|
108
|
+
if (
|
|
109
|
+
!Number.isSafeInteger(planArg.maxObjectSize) ||
|
|
110
|
+
planArg.maxObjectSize < 1 ||
|
|
111
|
+
planArg.maxObjectSize > MULTIPART_MAX_OBJECT_SIZE ||
|
|
112
|
+
!Number.isSafeInteger(planArg.partSize) ||
|
|
113
|
+
planArg.partSize < MULTIPART_MIN_PART_SIZE ||
|
|
114
|
+
planArg.partSize > MULTIPART_MAX_PART_SIZE ||
|
|
115
|
+
planArg.maxPartCount !== Math.ceil(planArg.maxObjectSize / planArg.partSize) ||
|
|
116
|
+
planArg.maxPartCount > MULTIPART_MAX_PART_COUNT ||
|
|
117
|
+
planArg.concurrency > planArg.maxPartCount ||
|
|
118
|
+
planArg.maximumBufferedBytes !==
|
|
119
|
+
Math.min(planArg.maxObjectSize, planArg.partSize * planArg.concurrency) +
|
|
120
|
+
Math.min(planArg.maxObjectSize, planArg.partSize)
|
|
121
|
+
) {
|
|
122
|
+
throw new TypeError('The bounded multipart plan is inconsistent.');
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (
|
|
127
|
+
!Number.isSafeInteger(planArg.objectSize) ||
|
|
128
|
+
planArg.objectSize < 0 ||
|
|
129
|
+
planArg.objectSize > MULTIPART_MAX_OBJECT_SIZE
|
|
130
|
+
) {
|
|
131
|
+
throw new TypeError('plan.objectSize is invalid.');
|
|
132
|
+
}
|
|
113
133
|
if (planArg.classification === 'zero') {
|
|
114
134
|
if (
|
|
115
135
|
planArg.objectSize !== 0 ||
|
|
@@ -165,6 +185,14 @@ export const consumeExactMultipartStream = async (
|
|
|
165
185
|
);
|
|
166
186
|
}
|
|
167
187
|
|
|
188
|
+
const plan = optionsArg.plan;
|
|
189
|
+
// A bounded plan reads parts until the source ends and refuses more than
|
|
190
|
+
// its maximum; an exact plan reads exactly the declared length.
|
|
191
|
+
const bounded = plan.classification === 'bounded';
|
|
192
|
+
const sizeLimit = bounded ? plan.maxObjectSize : plan.objectSize;
|
|
193
|
+
const overLimitKind: TMultipartStreamFailureKind = bounded
|
|
194
|
+
? 'source-over-maximum'
|
|
195
|
+
: 'source-extra';
|
|
168
196
|
const reader = optionsArg.readableStream.getReader();
|
|
169
197
|
const callbackAbortController = new AbortController();
|
|
170
198
|
const inFlightParts = new Map<symbol, IInFlightPart>();
|
|
@@ -289,7 +317,11 @@ export const consumeExactMultipartStream = async (
|
|
|
289
317
|
}
|
|
290
318
|
};
|
|
291
319
|
|
|
292
|
-
const dispatchPart = (
|
|
320
|
+
const dispatchPart = (
|
|
321
|
+
dataArg: Uint8Array,
|
|
322
|
+
partNumberArg: number,
|
|
323
|
+
isLastPartArg: boolean,
|
|
324
|
+
): void => {
|
|
293
325
|
const token = Symbol(`multipart-part-${partNumberArg}`);
|
|
294
326
|
partCallbacksStarted++;
|
|
295
327
|
currentPartBytes = 0;
|
|
@@ -299,8 +331,8 @@ export const consumeExactMultipartStream = async (
|
|
|
299
331
|
.then(() =>
|
|
300
332
|
optionsArg.onPart({
|
|
301
333
|
partNumber: partNumberArg,
|
|
302
|
-
byteOffset: (partNumberArg - 1) *
|
|
303
|
-
isLastPart:
|
|
334
|
+
byteOffset: (partNumberArg - 1) * plan.partSize,
|
|
335
|
+
isLastPart: isLastPartArg,
|
|
304
336
|
data: dataArg,
|
|
305
337
|
signal: callbackAbortController.signal,
|
|
306
338
|
}),
|
|
@@ -347,15 +379,19 @@ export const consumeExactMultipartStream = async (
|
|
|
347
379
|
|
|
348
380
|
for (
|
|
349
381
|
let partNumber = 1;
|
|
350
|
-
partNumber <=
|
|
382
|
+
bounded ? !sourceClosed : partNumber <= plan.partCount;
|
|
351
383
|
partNumber++
|
|
352
384
|
) {
|
|
353
385
|
await waitForCapacity();
|
|
354
386
|
const partByteLength = Math.min(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
(partNumber - 1) * optionsArg.plan.partSize,
|
|
387
|
+
plan.partSize,
|
|
388
|
+
sizeLimit - (partNumber - 1) * plan.partSize,
|
|
358
389
|
);
|
|
390
|
+
if (partByteLength <= 0) {
|
|
391
|
+
// A bounded source filled its maximum, which ends at or inside the
|
|
392
|
+
// previous part; the trailing read below refuses any further byte.
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
359
395
|
if (
|
|
360
396
|
inFlightBytes + partByteLength >
|
|
361
397
|
optionsArg.plan.maximumBufferedBytes
|
|
@@ -396,6 +432,9 @@ export const consumeExactMultipartStream = async (
|
|
|
396
432
|
const readResult = await readWhileObservingCallbacks();
|
|
397
433
|
if (readResult.done) {
|
|
398
434
|
sourceClosed = true;
|
|
435
|
+
if (bounded) {
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
399
438
|
throw {
|
|
400
439
|
kind: 'source-short',
|
|
401
440
|
} satisfies IPrimaryFailure;
|
|
@@ -410,16 +449,16 @@ export const consumeExactMultipartStream = async (
|
|
|
410
449
|
continue;
|
|
411
450
|
}
|
|
412
451
|
observedBytes += readResult.value.byteLength;
|
|
413
|
-
if (observedBytes >
|
|
452
|
+
if (observedBytes > sizeLimit) {
|
|
414
453
|
throw {
|
|
415
|
-
kind:
|
|
454
|
+
kind: overLimitKind,
|
|
416
455
|
} satisfies IPrimaryFailure;
|
|
417
456
|
}
|
|
418
|
-
if (readResult.value.byteLength >
|
|
457
|
+
if (readResult.value.byteLength > plan.partSize) {
|
|
419
458
|
throw {
|
|
420
459
|
kind: 'source-buffer-limit',
|
|
421
460
|
cause: new RangeError(
|
|
422
|
-
`Source chunks must not exceed ${
|
|
461
|
+
`Source chunks must not exceed ${plan.partSize} bytes.`,
|
|
423
462
|
),
|
|
424
463
|
} satisfies IPrimaryFailure;
|
|
425
464
|
}
|
|
@@ -428,7 +467,24 @@ export const consumeExactMultipartStream = async (
|
|
|
428
467
|
observeBufferedBytes();
|
|
429
468
|
}
|
|
430
469
|
|
|
431
|
-
|
|
470
|
+
if (partOffset === 0) {
|
|
471
|
+
// A bounded source ended at a part boundary.
|
|
472
|
+
currentPartBytes = 0;
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
if (partOffset < partByteLength) {
|
|
476
|
+
// Only a bounded source ends inside a part; its last part keeps the
|
|
477
|
+
// bytes read, as an exact copy.
|
|
478
|
+
dispatchPart(partData.slice(0, partOffset), partNumber, true);
|
|
479
|
+
} else {
|
|
480
|
+
dispatchPart(
|
|
481
|
+
partData,
|
|
482
|
+
partNumber,
|
|
483
|
+
bounded
|
|
484
|
+
? sourceClosed || observedBytes === sizeLimit
|
|
485
|
+
: partNumber === plan.partCount,
|
|
486
|
+
);
|
|
487
|
+
}
|
|
432
488
|
}
|
|
433
489
|
|
|
434
490
|
while (!sourceClosed) {
|
|
@@ -448,7 +504,7 @@ export const consumeExactMultipartStream = async (
|
|
|
448
504
|
}
|
|
449
505
|
observedBytes += readResult.value.byteLength;
|
|
450
506
|
throw {
|
|
451
|
-
kind:
|
|
507
|
+
kind: overLimitKind,
|
|
452
508
|
} satisfies IPrimaryFailure;
|
|
453
509
|
}
|
|
454
510
|
|
|
@@ -571,7 +627,9 @@ export const consumeExactMultipartStream = async (
|
|
|
571
627
|
completionAuthorized: false,
|
|
572
628
|
kind: failure.kind,
|
|
573
629
|
cause: failure.cause,
|
|
574
|
-
|
|
630
|
+
...(bounded
|
|
631
|
+
? { maxBytes: plan.maxObjectSize }
|
|
632
|
+
: { expectedBytes: plan.objectSize }),
|
|
575
633
|
observedBytes,
|
|
576
634
|
partCallbacksStarted,
|
|
577
635
|
completedPartNumbers: [...completedPartNumbers].sort(
|
|
@@ -4,6 +4,12 @@ export interface IMultipartPlanningOptions {
|
|
|
4
4
|
requestedConcurrency?: number;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
export interface IMultipartBoundedPlanningOptions {
|
|
8
|
+
maxObjectSize: number;
|
|
9
|
+
requestedPartSize?: number;
|
|
10
|
+
requestedConcurrency?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
interface IMultipartUploadPlanBase {
|
|
8
14
|
objectSize: number;
|
|
9
15
|
concurrency: number;
|
|
@@ -24,11 +30,34 @@ export interface IMultipartDataUploadPlan extends IMultipartUploadPlanBase {
|
|
|
24
30
|
partCount: number;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
/**
|
|
34
|
+
* A plan for a source of undeclared length: full parts are read until the
|
|
35
|
+
* source ends, and a source longer than `maxObjectSize` fails. The part size
|
|
36
|
+
* is chosen so that `maxObjectSize` fits into the multipart part limit.
|
|
37
|
+
*/
|
|
38
|
+
export interface IMultipartBoundedUploadPlan {
|
|
39
|
+
classification: 'bounded';
|
|
40
|
+
maxObjectSize: number;
|
|
41
|
+
partSize: number;
|
|
42
|
+
/** Parts that `maxObjectSize` needs; the source may need fewer. */
|
|
43
|
+
maxPartCount: number;
|
|
44
|
+
concurrency: number;
|
|
45
|
+
maximumBufferedBytes: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A plan for a source of declared length. */
|
|
49
|
+
export type TMultipartDeclaredUploadPlan = IMultipartZeroUploadPlan | IMultipartDataUploadPlan;
|
|
50
|
+
|
|
51
|
+
export type TMultipartUploadPlan = TMultipartDeclaredUploadPlan | IMultipartBoundedUploadPlan;
|
|
28
52
|
|
|
29
53
|
export interface IMultipartStreamPart {
|
|
30
54
|
partNumber: number;
|
|
31
55
|
byteOffset: number;
|
|
56
|
+
/**
|
|
57
|
+
* Under a bounded plan a part is only known to be the last when the source
|
|
58
|
+
* ended inside it or it reached the maximum; a source that ends exactly at
|
|
59
|
+
* a part boundary leaves its last part marked false.
|
|
60
|
+
*/
|
|
32
61
|
isLastPart: boolean;
|
|
33
62
|
/**
|
|
34
63
|
* An exact, exclusively owned copy. The consumer never mutates or reuses it.
|
|
@@ -61,6 +90,7 @@ export type TMultipartStreamFailureKind =
|
|
|
61
90
|
| 'source-empty-chunk-limit'
|
|
62
91
|
| 'source-short'
|
|
63
92
|
| 'source-extra'
|
|
93
|
+
| 'source-over-maximum'
|
|
64
94
|
| 'part-callback';
|
|
65
95
|
|
|
66
96
|
export type TMultipartOwnedReaderCleanup =
|
|
@@ -97,7 +127,10 @@ export interface IMultipartStreamFailureState {
|
|
|
97
127
|
completionAuthorized: false;
|
|
98
128
|
kind: TMultipartStreamFailureKind;
|
|
99
129
|
cause?: unknown;
|
|
100
|
-
|
|
130
|
+
/** The declared length; absent under a bounded plan. */
|
|
131
|
+
expectedBytes?: number;
|
|
132
|
+
/** The maximum length of a bounded plan. */
|
|
133
|
+
maxBytes?: number;
|
|
101
134
|
observedBytes: number;
|
|
102
135
|
partCallbacksStarted: number;
|
|
103
136
|
completedPartNumbers: number[];
|