@midsummer-ai/sdk 0.0.0-stage → 0.1.0-next.1305
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/LICENSE +177 -0
- package/README.md +4 -2
- package/agents/contract.d.mts +9 -0
- package/agents/contract.mjs +221 -0
- package/agents/index.d.mts +128 -0
- package/agents/index.mjs +203 -0
- package/agents/stand-in.d.mts +35 -0
- package/agents/stand-in.mjs +149 -0
- package/ai/contract.d.mts +10 -0
- package/ai/contract.mjs +77 -0
- package/ai/index.d.mts +94 -0
- package/ai/index.mjs +193 -0
- package/ai/stand-in.d.mts +7 -0
- package/ai/stand-in.mjs +41 -0
- package/build-SQ44jwSa.d.mts +41 -0
- package/client.d.mts +45 -0
- package/client.mjs +106 -0
- package/connection-CxD_Nry5.mjs +1911 -0
- package/contract.d.mts +10 -0
- package/contract.mjs +469 -0
- package/database/build.d.mts +2 -0
- package/database/build.mjs +579 -0
- package/database/client.d.mts +60 -0
- package/database/client.mjs +133 -0
- package/database/contract.d.mts +11 -0
- package/database/contract.mjs +164 -0
- package/database/errors.d.mts +23 -0
- package/database/errors.mjs +13 -0
- package/database/server.d.mts +3 -0
- package/database/server.mjs +320 -0
- package/database/stand-in.d.mts +99 -0
- package/database/stand-in.mjs +430 -0
- package/email/client.d.mts +34 -0
- package/email/client.mjs +131 -0
- package/email/contract.d.mts +11 -0
- package/email/contract.mjs +99 -0
- package/email/index.d.mts +74 -0
- package/email/index.mjs +59 -0
- package/email/smtp.d.mts +22 -0
- package/email/smtp.mjs +296 -0
- package/email/stand-in.d.mts +13 -0
- package/email/stand-in.mjs +51 -0
- package/index-DdoK0y7D.d.mts +192 -0
- package/index-sgdyoA2P.d.mts +212 -0
- package/index.d.mts +2 -0
- package/index.mjs +137 -0
- package/names-DfCBKupJ.mjs +115 -0
- package/package.json +152 -4
- package/predict-Drfm_8pO.mjs +179 -0
- package/queues/contract.d.mts +13 -0
- package/queues/contract.mjs +278 -0
- package/queues/index.d.mts +184 -0
- package/queues/index.mjs +126 -0
- package/queues/stand-in.d.mts +7 -0
- package/queues/stand-in.mjs +110 -0
- package/queues/wire.d.mts +9 -0
- package/queues/wire.mjs +199 -0
- package/s3.d.mts +43 -0
- package/s3.mjs +1003 -0
- package/schema-BF-Y1EVu.mjs +142 -0
- package/schema-DS8x1Dn8.mjs +115 -0
- package/schema-f7E9uzUn.d.mts +74 -0
- package/server-C7O7CICU.d.mts +182 -0
- package/socket.d.mts +67 -0
- package/socket.mjs +263 -0
- package/stand-in.d.mts +7 -0
- package/stand-in.mjs +185 -0
- package/store-DDuOr-_F.mjs +237 -0
- package/tabs-4FLTfg1c.mjs +457 -0
- package/vector/contract.d.mts +10 -0
- package/vector/contract.mjs +635 -0
- package/vector/index.d.mts +2 -0
- package/vector/index.mjs +108 -0
- package/vector/stand-in.d.mts +11 -0
- package/vector/stand-in.mjs +169 -0
- package/vector/wire.d.mts +25 -0
- package/vector/wire.mjs +175 -0
- package/wire.d.mts +54 -0
- package/wire.mjs +731 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/vector/errors.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Why a Vector DB call failed. `invalid`: a name, Vector, filter or option is wrong, or a Vector's
|
|
5
|
+
* dimension is not its namespace's. `not_found`: the Vector namespace does not exist. `conflict`:
|
|
6
|
+
* a namespace by that name exists with another dimension or metric. `forbidden`: the caller may not
|
|
7
|
+
* do this. `unavailable`: try again.
|
|
8
|
+
*/
|
|
9
|
+
type VectorErrorCode = "invalid" | "not_found" | "conflict" | "forbidden" | "unavailable";
|
|
10
|
+
declare const vectorErrorCodes: ReadonlyArray<VectorErrorCode>;
|
|
11
|
+
declare class VectorError extends Error {
|
|
12
|
+
readonly code: VectorErrorCode;
|
|
13
|
+
constructor(code: VectorErrorCode, why: string);
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/vector/schema.d.ts
|
|
17
|
+
/** Most numbers a Vector holds. */
|
|
18
|
+
declare const MAX_DIMENSION = 4096;
|
|
19
|
+
/** Most Vectors one upsert takes, and most ids one fetch or delete names. */
|
|
20
|
+
declare const MAX_BATCH = 1000;
|
|
21
|
+
/** The most matches one query returns. */
|
|
22
|
+
declare const MAX_TOP_K = 100;
|
|
23
|
+
/** Most characters a text to embed has. */
|
|
24
|
+
declare const MAX_TEXT = 30000;
|
|
25
|
+
/** Most bytes a Vector's metadata takes, as JSON. */
|
|
26
|
+
declare const MAX_METADATA_BYTES = 10240;
|
|
27
|
+
/**
|
|
28
|
+
* The largest value a Vector holds. Engines keep values and scores as 32-bit floats, and within it
|
|
29
|
+
* every score of 4,096 values is one, finite: a squared distance is at most 4,096 · (2e17)², about
|
|
30
|
+
* 1.6e38, under a 32-bit float's 3.4e38. Scores are exact to a 32-bit float's precision for values
|
|
31
|
+
* from 1e-17 up.
|
|
32
|
+
*/
|
|
33
|
+
declare const MAX_VALUE = 100000000000000000;
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/vector/index.d.ts
|
|
36
|
+
/** How closeness is measured: fixed when a Vector namespace is made. */
|
|
37
|
+
type Metric = "cosine" | "euclidean" | "dot";
|
|
38
|
+
/** A metadata value: a string, a finite number or a boolean. */
|
|
39
|
+
type MetadataValue = string | number | boolean;
|
|
40
|
+
/** One Vector: its id in its namespace, its numbers, and the caller's own fields to filter on. */
|
|
41
|
+
interface Vector {
|
|
42
|
+
/** 1 to 512 characters. */
|
|
43
|
+
readonly id: string;
|
|
44
|
+
/** Exactly the namespace's dimension of finite numbers. With `cosine`, not all zero. */
|
|
45
|
+
readonly values: ReadonlyArray<number>;
|
|
46
|
+
/** Up to 10 KiB as JSON; field names do not start with `$`. */
|
|
47
|
+
readonly metadata?: Readonly<Record<string, MetadataValue>>;
|
|
48
|
+
}
|
|
49
|
+
/** A Vector given as text, for a namespace made with an embedding model: its values are the text's embedding. */
|
|
50
|
+
interface TextVector {
|
|
51
|
+
/** 1 to 512 characters. */
|
|
52
|
+
readonly id: string;
|
|
53
|
+
/** 1 to 30,000 characters, not all of them spaces. */
|
|
54
|
+
readonly text: string;
|
|
55
|
+
readonly metadata?: Readonly<Record<string, MetadataValue>>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* What a field's metadata must be. A bare value is `$eq`. `$gt`, `$gte`, `$lt` and `$lte` compare a
|
|
59
|
+
* number with a number and a string with a string, in code-unit order; anything else fails them.
|
|
60
|
+
* A Vector without the field fails every condition but `$ne` and `$nin`.
|
|
61
|
+
*/
|
|
62
|
+
type Condition = MetadataValue | {
|
|
63
|
+
readonly $eq?: MetadataValue;
|
|
64
|
+
readonly $ne?: MetadataValue;
|
|
65
|
+
readonly $in?: ReadonlyArray<MetadataValue>;
|
|
66
|
+
readonly $nin?: ReadonlyArray<MetadataValue>;
|
|
67
|
+
readonly $gt?: number | string;
|
|
68
|
+
readonly $gte?: number | string;
|
|
69
|
+
readonly $lt?: number | string;
|
|
70
|
+
readonly $lte?: number | string;
|
|
71
|
+
};
|
|
72
|
+
/** Each field's condition; a Vector matches when all of them hold. */
|
|
73
|
+
type Filter = Readonly<Record<string, Condition>>;
|
|
74
|
+
interface QueryOptions {
|
|
75
|
+
/** How many matches, closest first: 1 to 100, default 10. */
|
|
76
|
+
readonly topK?: number;
|
|
77
|
+
readonly filter?: Filter;
|
|
78
|
+
/** Answer each match's values too. */
|
|
79
|
+
readonly includeValues?: boolean;
|
|
80
|
+
/** Answer each match's metadata too. */
|
|
81
|
+
readonly includeMetadata?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* `strong` (the default): every write made before the query is seen, and it runs in the namespace's
|
|
84
|
+
* home region. `eventual`: it runs where it is asked, on what the engine has indexed, which lags
|
|
85
|
+
* writes by up to about 15 seconds.
|
|
86
|
+
*/
|
|
87
|
+
readonly consistency?: Consistency;
|
|
88
|
+
}
|
|
89
|
+
/** Whether a query sees every write made before it, or runs nearest and may lag them. */
|
|
90
|
+
type Consistency = "strong" | "eventual";
|
|
91
|
+
/**
|
|
92
|
+
* One Vector a query found. `score` is the metric's own number: the cosine similarity (1 is the
|
|
93
|
+
* same direction), the dot product, or the euclidean distance (0 is the same point). Matches come
|
|
94
|
+
* closest first, so highest score first except for `euclidean`, lowest first; equal scores in id order.
|
|
95
|
+
*/
|
|
96
|
+
interface Match {
|
|
97
|
+
readonly id: string;
|
|
98
|
+
readonly score: number;
|
|
99
|
+
readonly values?: ReadonlyArray<number>;
|
|
100
|
+
readonly metadata?: Readonly<Record<string, MetadataValue>>;
|
|
101
|
+
}
|
|
102
|
+
/** A Vector namespace as it is now. */
|
|
103
|
+
interface NamespaceInfo {
|
|
104
|
+
readonly dimension: number;
|
|
105
|
+
readonly metric: Metric;
|
|
106
|
+
/** How many Vectors it holds. */
|
|
107
|
+
readonly count: number;
|
|
108
|
+
}
|
|
109
|
+
/** The Vectors of one Vector namespace. */
|
|
110
|
+
interface Namespace {
|
|
111
|
+
readonly name: string;
|
|
112
|
+
/** Stores each Vector under its id, replacing what was there; one given as text is embedded first. The next query anywhere sees it. */
|
|
113
|
+
upsert(vectors: ReadonlyArray<Vector | TextVector>): Promise<{
|
|
114
|
+
readonly upserted: number;
|
|
115
|
+
}>;
|
|
116
|
+
/** The `topK` Vectors closest to `vector`, or to a text's embedding, that match `filter`, closest first. */
|
|
117
|
+
query(vector: ReadonlyArray<number> | string, options?: QueryOptions): Promise<ReadonlyArray<Match>>;
|
|
118
|
+
/** The Vectors with these ids, in the order asked, values and metadata included; ids it does not hold are left out. */
|
|
119
|
+
fetch(ids: ReadonlyArray<string>): Promise<ReadonlyArray<Vector>>;
|
|
120
|
+
/** Removes the Vectors with these ids. Removing one that is not there is not an error. */
|
|
121
|
+
delete(ids: ReadonlyArray<string>): Promise<void>;
|
|
122
|
+
describe(): Promise<NamespaceInfo>;
|
|
123
|
+
}
|
|
124
|
+
interface VectorDb {
|
|
125
|
+
/** A Vector namespace by name: 1 to 63 lowercase letters, digits and hyphens, starting with a letter or digit. */
|
|
126
|
+
namespace(name: string): Namespace;
|
|
127
|
+
}
|
|
128
|
+
/** A query as an engine takes it: checked, with its defaults filled in; by a vector, or a text to embed. */
|
|
129
|
+
type EngineQuery = ({
|
|
130
|
+
readonly vector: ReadonlyArray<number>;
|
|
131
|
+
} | {
|
|
132
|
+
readonly text: string;
|
|
133
|
+
}) & {
|
|
134
|
+
readonly topK: number;
|
|
135
|
+
readonly filter?: Filter;
|
|
136
|
+
readonly includeValues: boolean;
|
|
137
|
+
readonly includeMetadata: boolean;
|
|
138
|
+
readonly consistency?: Consistency;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Where Vectors live: the Seam under `vectorDb()`. The Stand-in and the real Capability are its
|
|
142
|
+
* Adapters. A call arrives checked: names, ids, values, filters and limits valid. The engine checks
|
|
143
|
+
* what only it knows: that the namespace exists, and that each Vector has its dimension. An engine
|
|
144
|
+
* that keeps only values takes text through `embedded`.
|
|
145
|
+
*/
|
|
146
|
+
interface VectorEngine {
|
|
147
|
+
describe(namespace: string): Promise<NamespaceInfo>;
|
|
148
|
+
upsert(namespace: string, vectors: ReadonlyArray<Vector | TextVector>): Promise<{
|
|
149
|
+
readonly upserted: number;
|
|
150
|
+
}>;
|
|
151
|
+
query(namespace: string, query: EngineQuery): Promise<ReadonlyArray<Match>>;
|
|
152
|
+
fetch(namespace: string, ids: ReadonlyArray<string>): Promise<ReadonlyArray<Vector>>;
|
|
153
|
+
delete(namespace: string, ids: ReadonlyArray<string>): Promise<void>;
|
|
154
|
+
}
|
|
155
|
+
/** An engine that keeps only values, as most do: `VectorEngine` without text, which `embedded` gives it. */
|
|
156
|
+
interface ValuesEngine {
|
|
157
|
+
describe(namespace: string): Promise<NamespaceInfo>;
|
|
158
|
+
upsert(namespace: string, vectors: ReadonlyArray<Vector>): Promise<{
|
|
159
|
+
readonly upserted: number;
|
|
160
|
+
}>;
|
|
161
|
+
query(namespace: string, query: Extract<EngineQuery, {
|
|
162
|
+
readonly vector: ReadonlyArray<number>;
|
|
163
|
+
}>): Promise<ReadonlyArray<Match>>;
|
|
164
|
+
fetch(namespace: string, ids: ReadonlyArray<string>): Promise<ReadonlyArray<Vector>>;
|
|
165
|
+
delete(namespace: string, ids: ReadonlyArray<string>): Promise<void>;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Making and dropping Vector namespaces: the platform's, never a Site's. `create` of one that exists
|
|
169
|
+
* with the same settings does nothing, and with others is `conflict`; `drop` of one that does not
|
|
170
|
+
* exist does nothing.
|
|
171
|
+
*/
|
|
172
|
+
interface VectorControl {
|
|
173
|
+
/** `embed` is the embedding model whose vectors the namespace holds; without one, it takes values only. */
|
|
174
|
+
create(namespace: string, settings: {
|
|
175
|
+
readonly dimension: number;
|
|
176
|
+
readonly metric: Metric;
|
|
177
|
+
readonly embed?: string;
|
|
178
|
+
}): Promise<void>;
|
|
179
|
+
drop(namespace: string): Promise<void>;
|
|
180
|
+
}
|
|
181
|
+
/** Vector DB over `engine`. */
|
|
182
|
+
declare function vectorDb(engine: VectorEngine): VectorDb;
|
|
183
|
+
/**
|
|
184
|
+
* `engine`, which keeps only values, taking text too: the texts of each call are embedded by `embed`,
|
|
185
|
+
* given the namespace and the texts in order, before `engine` sees the call. `embed` returns
|
|
186
|
+
* `undefined` for a namespace made without an embedding model, which is `invalid`.
|
|
187
|
+
*
|
|
188
|
+
* const site = embedded(engine, (namespace, texts) => vector.embed(namespace, texts))
|
|
189
|
+
*/
|
|
190
|
+
declare function embedded(engine: ValuesEngine, embed: (namespace: string, texts: ReadonlyArray<string>) => Promise<ReadonlyArray<ReadonlyArray<number>> | undefined>): VectorEngine;
|
|
191
|
+
//#endregion
|
|
192
|
+
export { MAX_TOP_K as C, vectorErrorCodes as D, VectorErrorCode as E, MAX_TEXT as S, VectorError as T, embedded as _, Match as a, MAX_DIMENSION as b, Namespace as c, TextVector as d, ValuesEngine as f, VectorEngine as g, VectorDb as h, Filter as i, NamespaceInfo as l, VectorControl as m, Consistency as n, MetadataValue as o, Vector as p, EngineQuery as r, Metric as s, Condition as t, QueryOptions as u, vectorDb as v, MAX_VALUE as w, MAX_METADATA_BYTES as x, MAX_BATCH as y };
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
//#region src/errors.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Why a Storage call failed. `invalid`: a name, range or option is wrong. `not_found`: what the
|
|
4
|
+
* call needs is missing. `conflict`: a condition on the call did not hold. `forbidden`: the
|
|
5
|
+
* caller may not do this. `quota`: over the Project's plan. `unavailable`: try again.
|
|
6
|
+
*/
|
|
7
|
+
type StorageErrorCode = "invalid" | "not_found" | "conflict" | "forbidden" | "quota" | "unavailable";
|
|
8
|
+
/**
|
|
9
|
+
* Which rule a failure broke, where a caller acts on it: `range` (it starts past the end),
|
|
10
|
+
* `upload` (no such upload, or it is finished), `part` (a part to complete is missing or its etag
|
|
11
|
+
* differs), `part-order` (the parts to complete are not in ascending order), `part-too-small`
|
|
12
|
+
* (a part other than the last is under 5 MiB).
|
|
13
|
+
*/
|
|
14
|
+
type StorageErrorReason = "range" | "upload" | "part" | "part-order" | "part-too-small";
|
|
15
|
+
declare class StorageError extends Error {
|
|
16
|
+
readonly code: StorageErrorCode;
|
|
17
|
+
readonly reason?: StorageErrorReason | undefined;
|
|
18
|
+
constructor(code: StorageErrorCode, why: string, reason?: StorageErrorReason | undefined);
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/index.d.ts
|
|
22
|
+
/** What a Bucket holds under one key. */
|
|
23
|
+
interface StoredObject {
|
|
24
|
+
readonly key: string;
|
|
25
|
+
readonly size: number;
|
|
26
|
+
/** The SHA-256 of the bytes, in hex. The same bytes have the same etag in every Bucket: it identifies the content, not a version. */
|
|
27
|
+
readonly etag: string;
|
|
28
|
+
readonly contentType: string;
|
|
29
|
+
/** The caller's own pairs, as put: lowercase keys, at most 2 KiB in all. */
|
|
30
|
+
readonly metadata: Readonly<Record<string, string>>;
|
|
31
|
+
/** When this content was put, in milliseconds since the epoch. */
|
|
32
|
+
readonly uploadedAt: number;
|
|
33
|
+
}
|
|
34
|
+
/** An Object read: its description and the bytes asked for. */
|
|
35
|
+
interface ObjectRead extends StoredObject {
|
|
36
|
+
/** The whole Object, or only the range asked for. */
|
|
37
|
+
readonly body: Uint8Array;
|
|
38
|
+
/** Set on a range read: where `body` sits in the Object, whose whole size is `size`. */
|
|
39
|
+
readonly range?: {
|
|
40
|
+
readonly offset: number;
|
|
41
|
+
readonly length: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Bytes to put. A string is stored as its UTF-8 bytes. */
|
|
45
|
+
type Body = Uint8Array | ArrayBuffer | string | Blob;
|
|
46
|
+
/**
|
|
47
|
+
* Part of an Object: from `offset`, `length` bytes or to the end; or the last `suffix` bytes. A
|
|
48
|
+
* range that runs past the end is cut at the end. One that starts at or past it is `invalid`.
|
|
49
|
+
*/
|
|
50
|
+
type Range = {
|
|
51
|
+
readonly offset: number;
|
|
52
|
+
readonly length?: number;
|
|
53
|
+
} | {
|
|
54
|
+
readonly suffix: number;
|
|
55
|
+
};
|
|
56
|
+
interface PutOptions {
|
|
57
|
+
/** Defaults to `application/octet-stream`. */
|
|
58
|
+
readonly contentType?: string;
|
|
59
|
+
readonly metadata?: Readonly<Record<string, string>>;
|
|
60
|
+
/** `"*"`: put only if nothing is stored under the key, else `conflict`. */
|
|
61
|
+
readonly ifNoneMatch?: "*";
|
|
62
|
+
/** Put only if the key holds content with this etag, else `conflict`. */
|
|
63
|
+
readonly ifMatch?: string;
|
|
64
|
+
}
|
|
65
|
+
interface ListOptions {
|
|
66
|
+
/** Only keys that start with this. */
|
|
67
|
+
readonly prefix?: string;
|
|
68
|
+
/** Keys with this after the prefix are folded into one entry of `prefixes`, up to and including it. */
|
|
69
|
+
readonly delimiter?: string;
|
|
70
|
+
/** Start after this key or folded prefix: the `next` of the page before. */
|
|
71
|
+
readonly after?: string;
|
|
72
|
+
/** At most this many entries, Objects and prefixes together: 1 to 1,000, default 1,000. */
|
|
73
|
+
readonly limit?: number;
|
|
74
|
+
}
|
|
75
|
+
/** One page of a listing, in UTF-8 byte order of key. `next` is set when there is more. */
|
|
76
|
+
interface Listing {
|
|
77
|
+
readonly objects: ReadonlyArray<StoredObject>;
|
|
78
|
+
readonly prefixes: ReadonlyArray<string>;
|
|
79
|
+
readonly next?: string;
|
|
80
|
+
}
|
|
81
|
+
/** The Objects of one Bucket. */
|
|
82
|
+
interface Bucket {
|
|
83
|
+
readonly name: string;
|
|
84
|
+
/** Stores `body` under `key`, replacing what was there. The next read anywhere sees it. */
|
|
85
|
+
put(key: string, body: Body, options?: PutOptions): Promise<StoredObject>;
|
|
86
|
+
/** The Object under `key`, or the range asked for; `null` if nothing is stored under the key. */
|
|
87
|
+
get(key: string, options?: {
|
|
88
|
+
readonly range?: Range;
|
|
89
|
+
}): Promise<ObjectRead | null>;
|
|
90
|
+
/** The Object's description without its bytes; `null` if nothing is stored under the key. */
|
|
91
|
+
head(key: string): Promise<StoredObject | null>;
|
|
92
|
+
/** Removes what `key` holds. Removing nothing is not an error. */
|
|
93
|
+
delete(key: string): Promise<void>;
|
|
94
|
+
list(options?: ListOptions): Promise<Listing>;
|
|
95
|
+
/** Puts the content of `from` under `to` as well, with its content type and metadata. `not_found` if nothing is stored under `from`. */
|
|
96
|
+
copy(from: string, to: string, options?: {
|
|
97
|
+
readonly ifNoneMatch?: "*";
|
|
98
|
+
}): Promise<StoredObject>;
|
|
99
|
+
/**
|
|
100
|
+
* Starts putting `key` in parts, for an Object too big for one call or sent in parallel. The key
|
|
101
|
+
* keeps what it holds until the upload completes. `ifNoneMatch` is checked when it completes.
|
|
102
|
+
*/
|
|
103
|
+
createUpload(key: string, options?: Omit<PutOptions, "ifMatch">): Promise<{
|
|
104
|
+
readonly uploadId: string;
|
|
105
|
+
}>;
|
|
106
|
+
/**
|
|
107
|
+
* Sends part `partNumber` (1 to 10,000) of an upload; sending a number again replaces that part.
|
|
108
|
+
* Every part except the last must have the same size and contain at least 5 MiB. The last part
|
|
109
|
+
* must be no larger than the first. Checked on completion.
|
|
110
|
+
*/
|
|
111
|
+
uploadPart(key: string, uploadId: string, partNumber: number, body: Body): Promise<Part>;
|
|
112
|
+
/**
|
|
113
|
+
* Joins the parts named, in ascending part number, into the Object under `key`, whose etag is the
|
|
114
|
+
* SHA-256 of the whole. Pass each `partNumber` and `etag` returned by `uploadPart`. A completed
|
|
115
|
+
* upload takes no more parts; one refused stays open, to be completed again.
|
|
116
|
+
*/
|
|
117
|
+
completeUpload(key: string, uploadId: string, parts: ReadonlyArray<Pick<Part, "partNumber" | "etag">>): Promise<StoredObject>;
|
|
118
|
+
/** Drops an upload and its parts. Aborting one that is finished or unknown is `not_found`. */
|
|
119
|
+
abortUpload(key: string, uploadId: string): Promise<void>;
|
|
120
|
+
}
|
|
121
|
+
/** One uploaded part. Its etag identifies that upload of the part; pass it unchanged to `completeUpload`. */
|
|
122
|
+
interface Part {
|
|
123
|
+
readonly partNumber: number;
|
|
124
|
+
readonly etag: string;
|
|
125
|
+
readonly size: number;
|
|
126
|
+
}
|
|
127
|
+
interface Storage {
|
|
128
|
+
/** A Bucket by name: 3 to 63 lowercase letters, digits and hyphens, starting and ending with a letter or digit. */
|
|
129
|
+
bucket(name: string): Bucket;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Where Objects live: the Seam under `storage()`. The Stand-in and the real Capability are its
|
|
133
|
+
* Adapters. A call arrives checked and normalized: names valid, bytes as `Uint8Array`, defaults
|
|
134
|
+
* filled in.
|
|
135
|
+
*/
|
|
136
|
+
/** What a Bucket holds now: its Objects and their bytes, a copy counted as its own. */
|
|
137
|
+
interface Usage {
|
|
138
|
+
readonly objects: number;
|
|
139
|
+
readonly bytes: number;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Calls made on a Bucket: `writes` put, copy, delete and the steps of an upload, a call for each
|
|
143
|
+
* Object; `reads` get, head and list, a call for each Object read or page listed.
|
|
144
|
+
*/
|
|
145
|
+
interface Operations {
|
|
146
|
+
readonly reads: number;
|
|
147
|
+
readonly writes: number;
|
|
148
|
+
}
|
|
149
|
+
interface StorageEngine {
|
|
150
|
+
put(bucket: string, key: string, body: Uint8Array, options: EnginePut): Promise<StoredObject>;
|
|
151
|
+
/**
|
|
152
|
+
* With no range and a `limit`, an Object larger than `limit` bytes returns its first `limit` bytes,
|
|
153
|
+
* as though that range had been asked for: a client that cannot carry it whole reads on by range.
|
|
154
|
+
*/
|
|
155
|
+
get(bucket: string, key: string, range?: Range, limit?: number): Promise<ObjectRead | null>;
|
|
156
|
+
head(bucket: string, key: string): Promise<StoredObject | null>;
|
|
157
|
+
delete(bucket: string, key: string): Promise<void>;
|
|
158
|
+
list(bucket: string, options: EngineList): Promise<Listing>;
|
|
159
|
+
copy(bucket: string, from: string, to: string, options: {
|
|
160
|
+
readonly ifNoneMatch?: "*";
|
|
161
|
+
}): Promise<StoredObject>;
|
|
162
|
+
createUpload(bucket: string, key: string, options: Omit<EnginePut, "ifMatch">): Promise<{
|
|
163
|
+
readonly uploadId: string;
|
|
164
|
+
}>;
|
|
165
|
+
uploadPart(bucket: string, key: string, uploadId: string, partNumber: number, body: Uint8Array): Promise<Part>;
|
|
166
|
+
completeUpload(bucket: string, key: string, uploadId: string, parts: ReadonlyArray<Pick<Part, "partNumber" | "etag">>): Promise<StoredObject>;
|
|
167
|
+
abortUpload(bucket: string, key: string, uploadId: string): Promise<void>;
|
|
168
|
+
/** What the Bucket stores, for metering. A Bucket never written to stores nothing. */
|
|
169
|
+
usage(bucket: string): Promise<Usage>;
|
|
170
|
+
/** The calls made on the Bucket in the hours from `from` up to `to` (milliseconds), for metering. */
|
|
171
|
+
operations(bucket: string, from: number, to: number): Promise<Operations>;
|
|
172
|
+
/**
|
|
173
|
+
* The streaming forms of `get`, `put` and `uploadPart`, for bodies too large to hold: an engine that
|
|
174
|
+
* holds bodies whole, such as the Stand-in, leaves them out and callers fall back to the whole forms.
|
|
175
|
+
* `sha256`, when given, is the hex hash the body must have; one that does not is `invalid` and stored
|
|
176
|
+
* nowhere. A streamed part's etag is unique to that upload of it, not its bytes' hash.
|
|
177
|
+
*/
|
|
178
|
+
stream?(bucket: string, key: string, range?: Range): Promise<ObjectStream | null>;
|
|
179
|
+
putStream?(bucket: string, key: string, body: ReadableStream<Uint8Array>, size: number, options: EnginePut & {
|
|
180
|
+
readonly sha256?: string;
|
|
181
|
+
}): Promise<StoredObject>;
|
|
182
|
+
uploadPartStream?(bucket: string, key: string, uploadId: string, partNumber: number, body: ReadableStream<Uint8Array>, size: number, sha256?: string): Promise<Part>;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The streaming methods of `engine` an engine wrapping it passes on, with each Bucket named as `read`
|
|
186
|
+
* (for `stream`) and `write` (for the rest) turn it; none where `engine` has none.
|
|
187
|
+
*/
|
|
188
|
+
declare function streamsOf(engine: StorageEngine, read: (bucket: string) => string, write: (bucket: string) => string): Pick<StorageEngine, "stream" | "putStream" | "uploadPartStream">;
|
|
189
|
+
/** An Object read as a stream: its description and the bytes asked for, `range.length` of them or its whole size. */
|
|
190
|
+
interface ObjectStream extends StoredObject {
|
|
191
|
+
readonly body: ReadableStream<Uint8Array>;
|
|
192
|
+
readonly range?: {
|
|
193
|
+
readonly offset: number;
|
|
194
|
+
readonly length: number;
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
interface EnginePut {
|
|
198
|
+
readonly contentType: string;
|
|
199
|
+
readonly metadata: Readonly<Record<string, string>>;
|
|
200
|
+
readonly ifNoneMatch?: "*";
|
|
201
|
+
readonly ifMatch?: string;
|
|
202
|
+
}
|
|
203
|
+
interface EngineList {
|
|
204
|
+
readonly prefix: string;
|
|
205
|
+
readonly delimiter?: string;
|
|
206
|
+
readonly after?: string;
|
|
207
|
+
readonly limit: number;
|
|
208
|
+
}
|
|
209
|
+
/** Storage over `engine`. */
|
|
210
|
+
declare function storage(engine: StorageEngine): Storage;
|
|
211
|
+
//#endregion
|
|
212
|
+
export { storage as _, ListOptions as a, StorageErrorCode as b, ObjectStream as c, PutOptions as d, Range as f, Usage as g, StoredObject as h, EnginePut as i, Operations as l, StorageEngine as m, Bucket as n, Listing as o, Storage as p, EngineList as r, ObjectRead as s, Body as t, Part as u, streamsOf as v, StorageErrorReason as x, StorageError as y };
|
package/index.d.mts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { _ as storage, a as ListOptions, b as StorageErrorCode, c as ObjectStream, d as PutOptions, f as Range, g as Usage, h as StoredObject, i as EnginePut, l as Operations, m as StorageEngine, n as Bucket, o as Listing, p as Storage, r as EngineList, s as ObjectRead, t as Body, u as Part, v as streamsOf, x as StorageErrorReason, y as StorageError } from "./index-sgdyoA2P.mjs";
|
|
2
|
+
export { Body, Bucket, EngineList, EnginePut, ListOptions, Listing, ObjectRead, ObjectStream, Operations, Part, PutOptions, Range, Storage, StorageEngine, StorageError, type StorageErrorCode, type StorageErrorReason, StoredObject, Usage, storage, streamsOf };
|
package/index.mjs
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { a as checkMetadata, c as checkRange, d as invalid, h as StorageError, i as checkListLimit, l as checkUploadId, n as checkBucketName, o as checkPartNumber, r as checkKey, s as checkParts } from "./names-DfCBKupJ.mjs";
|
|
2
|
+
//#region src/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* The SDK: the one package your project's code imports to use Midsummer's products. This entry is
|
|
5
|
+
* storage: your team's files, kept in buckets.
|
|
6
|
+
*
|
|
7
|
+
* In your deployment's server, the environment's storage arrives as `context.storage`. A bucket is
|
|
8
|
+
* named by the name the environment binds it under (`midsummer env binding set`), so `avatars` can
|
|
9
|
+
* be one bucket in production and another in Preview:
|
|
10
|
+
*
|
|
11
|
+
* import { storage } from "@midsummer-ai/sdk"
|
|
12
|
+
* import { remote } from "@midsummer-ai/sdk/wire"
|
|
13
|
+
*
|
|
14
|
+
* export default {
|
|
15
|
+
* async fetch(request: Request, context: { storage: { send(frame: Uint8Array): Promise<Uint8Array> } }) {
|
|
16
|
+
* const avatars = storage(remote(context.storage)).bucket("avatars")
|
|
17
|
+
* await avatars.put("ada.png", new Uint8Array(await request.arrayBuffer()), { contentType: "image/png" })
|
|
18
|
+
* const page = await avatars.list({ prefix: "2026/", delimiter: "/" })
|
|
19
|
+
* return Response.json(page)
|
|
20
|
+
* },
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* Code that runs outside Midsummer connects with a storage key (`midsummer storage key create`):
|
|
24
|
+
*
|
|
25
|
+
* import { storage } from "@midsummer-ai/sdk"
|
|
26
|
+
* import { client } from "@midsummer-ai/sdk/client"
|
|
27
|
+
*
|
|
28
|
+
* const files = storage(client("https://api.midsummer.new", { key, secret }))
|
|
29
|
+
*
|
|
30
|
+
* In tests, `standIn` from `@midsummer-ai/sdk/stand-in` keeps files in memory. All three pass the same
|
|
31
|
+
* tests, in `@midsummer-ai/sdk/contract`, and only what those tests check is guaranteed.
|
|
32
|
+
*
|
|
33
|
+
* Bucket names and keys are checked before a request is sent. Every failure is a `StorageError`,
|
|
34
|
+
* and its `code` shows which kind. Reading a key with nothing stored returns `null`, not an error.
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* The streaming methods of `engine` an engine wrapping it passes on, with each Bucket named as `read`
|
|
38
|
+
* (for `stream`) and `write` (for the rest) turn it; none where `engine` has none.
|
|
39
|
+
*/
|
|
40
|
+
function streamsOf(engine, read, write) {
|
|
41
|
+
const { stream, putStream, uploadPartStream } = engine;
|
|
42
|
+
return {
|
|
43
|
+
...stream ? { stream: (bucket, key, range) => stream.call(engine, read(bucket), key, range) } : {},
|
|
44
|
+
...putStream ? { putStream: (bucket, key, body, size, options) => putStream.call(engine, write(bucket), key, body, size, options) } : {},
|
|
45
|
+
...uploadPartStream ? { uploadPartStream: (bucket, key, uploadId, partNumber, body, size, sha256) => uploadPartStream.call(engine, write(bucket), key, uploadId, partNumber, body, size, sha256) } : {}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Storage over `engine`. */
|
|
49
|
+
function storage(engine) {
|
|
50
|
+
return { bucket(name) {
|
|
51
|
+
checkBucketName(name);
|
|
52
|
+
return {
|
|
53
|
+
name,
|
|
54
|
+
async put(key, body, options = {}) {
|
|
55
|
+
checkKey(key);
|
|
56
|
+
const metadata = options.metadata ?? {};
|
|
57
|
+
checkMetadata(metadata);
|
|
58
|
+
if (options.ifMatch !== void 0 && !/^[0-9a-f]{64}$/.test(options.ifMatch)) invalid("ifMatch is not an etag");
|
|
59
|
+
return engine.put(name, key, body instanceof Blob ? await bytesOfBlob(body) : bytesOf(body), {
|
|
60
|
+
contentType: options.contentType ?? "application/octet-stream",
|
|
61
|
+
metadata,
|
|
62
|
+
...options.ifNoneMatch ? { ifNoneMatch: options.ifNoneMatch } : {},
|
|
63
|
+
...options.ifMatch !== void 0 ? { ifMatch: options.ifMatch } : {}
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
async get(key, options = {}) {
|
|
67
|
+
checkKey(key);
|
|
68
|
+
if (options.range) checkRange(options.range);
|
|
69
|
+
return engine.get(name, key, options.range);
|
|
70
|
+
},
|
|
71
|
+
async head(key) {
|
|
72
|
+
checkKey(key);
|
|
73
|
+
return engine.head(name, key);
|
|
74
|
+
},
|
|
75
|
+
async delete(key) {
|
|
76
|
+
checkKey(key);
|
|
77
|
+
return engine.delete(name, key);
|
|
78
|
+
},
|
|
79
|
+
async list(options = {}) {
|
|
80
|
+
const limit = options.limit ?? 1e3;
|
|
81
|
+
checkListLimit(limit);
|
|
82
|
+
if (options.delimiter === "") invalid("the delimiter is empty");
|
|
83
|
+
return engine.list(name, {
|
|
84
|
+
prefix: options.prefix ?? "",
|
|
85
|
+
limit,
|
|
86
|
+
...options.delimiter !== void 0 ? { delimiter: options.delimiter } : {},
|
|
87
|
+
...options.after !== void 0 ? { after: options.after } : {}
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
async copy(from, to, options = {}) {
|
|
91
|
+
checkKey(from);
|
|
92
|
+
checkKey(to);
|
|
93
|
+
return engine.copy(name, from, to, options.ifNoneMatch ? { ifNoneMatch: options.ifNoneMatch } : {});
|
|
94
|
+
},
|
|
95
|
+
async createUpload(key, options = {}) {
|
|
96
|
+
checkKey(key);
|
|
97
|
+
const metadata = options.metadata ?? {};
|
|
98
|
+
checkMetadata(metadata);
|
|
99
|
+
return engine.createUpload(name, key, {
|
|
100
|
+
contentType: options.contentType ?? "application/octet-stream",
|
|
101
|
+
metadata,
|
|
102
|
+
...options.ifNoneMatch ? { ifNoneMatch: options.ifNoneMatch } : {}
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
async uploadPart(key, uploadId, partNumber, body) {
|
|
106
|
+
checkKey(key);
|
|
107
|
+
checkUploadId(uploadId);
|
|
108
|
+
checkPartNumber(partNumber);
|
|
109
|
+
return engine.uploadPart(name, key, uploadId, partNumber, body instanceof Blob ? await bytesOfBlob(body) : bytesOf(body));
|
|
110
|
+
},
|
|
111
|
+
async completeUpload(key, uploadId, parts) {
|
|
112
|
+
checkKey(key);
|
|
113
|
+
checkUploadId(uploadId);
|
|
114
|
+
checkParts(parts);
|
|
115
|
+
return engine.completeUpload(name, key, uploadId, parts.map((p) => ({
|
|
116
|
+
partNumber: p.partNumber,
|
|
117
|
+
etag: p.etag
|
|
118
|
+
})));
|
|
119
|
+
},
|
|
120
|
+
async abortUpload(key, uploadId) {
|
|
121
|
+
checkKey(key);
|
|
122
|
+
checkUploadId(uploadId);
|
|
123
|
+
return engine.abortUpload(name, key, uploadId);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
} };
|
|
127
|
+
}
|
|
128
|
+
function bytesOf(body) {
|
|
129
|
+
if (typeof body === "string") return new TextEncoder().encode(body);
|
|
130
|
+
if (body instanceof Uint8Array) return body;
|
|
131
|
+
return new Uint8Array(body);
|
|
132
|
+
}
|
|
133
|
+
async function bytesOfBlob(body) {
|
|
134
|
+
return new Uint8Array(await body.arrayBuffer());
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
export { StorageError, storage, streamsOf };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
//#region src/errors.ts
|
|
2
|
+
const storageErrorCodes = [
|
|
3
|
+
"invalid",
|
|
4
|
+
"not_found",
|
|
5
|
+
"conflict",
|
|
6
|
+
"forbidden",
|
|
7
|
+
"quota",
|
|
8
|
+
"unavailable"
|
|
9
|
+
];
|
|
10
|
+
const storageErrorReasons = [
|
|
11
|
+
"range",
|
|
12
|
+
"upload",
|
|
13
|
+
"part",
|
|
14
|
+
"part-order",
|
|
15
|
+
"part-too-small"
|
|
16
|
+
];
|
|
17
|
+
var StorageError = class extends Error {
|
|
18
|
+
code;
|
|
19
|
+
reason;
|
|
20
|
+
constructor(code, why, reason) {
|
|
21
|
+
super(why);
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.reason = reason;
|
|
24
|
+
this.name = "StorageError";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/names.ts
|
|
29
|
+
function invalid(why) {
|
|
30
|
+
throw new StorageError("invalid", why);
|
|
31
|
+
}
|
|
32
|
+
function checkBucketName(name) {
|
|
33
|
+
if (!/^[a-z0-9](?:[a-z0-9-]{1,61})[a-z0-9]$/.test(name)) invalid(`"${name}" is not a Bucket name: 3 to 63 lowercase letters, digits and hyphens, starting and ending with a letter or digit`);
|
|
34
|
+
}
|
|
35
|
+
const encoder = new TextEncoder();
|
|
36
|
+
function checkKey(key) {
|
|
37
|
+
const bytes = encoder.encode(key).length;
|
|
38
|
+
if (bytes === 0) invalid("the key is empty");
|
|
39
|
+
if (bytes > 1024) invalid(`the key is ${bytes} bytes; at most 1,024`);
|
|
40
|
+
}
|
|
41
|
+
function checkMetadata(metadata) {
|
|
42
|
+
let bytes = 0;
|
|
43
|
+
for (const [name, value] of Object.entries(metadata)) {
|
|
44
|
+
if (!/^[a-z0-9][a-z0-9-]{0,127}$/.test(name)) invalid(`"${name}" is not a metadata name: lowercase letters, digits and hyphens`);
|
|
45
|
+
if (!/^[\x20-\x7e]*$/.test(value)) invalid(`metadata "${name}" is not printable ASCII`);
|
|
46
|
+
bytes += name.length + value.length;
|
|
47
|
+
}
|
|
48
|
+
if (bytes > 2048) invalid(`metadata is ${bytes} bytes; at most 2,048`);
|
|
49
|
+
}
|
|
50
|
+
const count = (n) => Number.isSafeInteger(n);
|
|
51
|
+
function checkRange(range) {
|
|
52
|
+
if ("suffix" in range) {
|
|
53
|
+
if (!count(range.suffix) || range.suffix < 1) invalid("a suffix range is a whole number of bytes, at least 1");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!count(range.offset) || range.offset < 0) invalid("a range offset is a whole number of bytes, at least 0");
|
|
57
|
+
if (range.length !== void 0 && (!count(range.length) || range.length < 1)) invalid("a range length is a whole number of bytes, at least 1");
|
|
58
|
+
}
|
|
59
|
+
/** Where `range` falls in an Object of `size` bytes: cut at the end; `invalid` if it starts at or past it. */
|
|
60
|
+
function resolveRange(range, size) {
|
|
61
|
+
if ("suffix" in range) {
|
|
62
|
+
if (size === 0) invalid("the Object is empty");
|
|
63
|
+
const length = Math.min(range.suffix, size);
|
|
64
|
+
return {
|
|
65
|
+
offset: size - length,
|
|
66
|
+
length
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (range.offset >= size) throw new StorageError("invalid", `the range starts at byte ${range.offset}; the Object has ${size}`, "range");
|
|
70
|
+
const end = range.length === void 0 ? size : Math.min(range.offset + range.length, size);
|
|
71
|
+
return {
|
|
72
|
+
offset: range.offset,
|
|
73
|
+
length: end - range.offset
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Every part of an upload but the last is at least this many bytes. */
|
|
77
|
+
const MIN_PART = 5 << 20;
|
|
78
|
+
function checkPartNumber(partNumber) {
|
|
79
|
+
if (!count(partNumber) || partNumber < 1 || partNumber > 1e4) invalid("a part number is 1 to 10,000");
|
|
80
|
+
}
|
|
81
|
+
function checkUploadId(uploadId) {
|
|
82
|
+
if (!/^[A-Za-z0-9._-]{1,256}$/.test(uploadId)) invalid("that is not an upload id");
|
|
83
|
+
}
|
|
84
|
+
/** The parts to complete: 1 to 10,000 of them, each named once with an etag, in ascending part number. */
|
|
85
|
+
function checkParts(parts) {
|
|
86
|
+
if (parts.length < 1 || parts.length > 1e4) invalid("an upload completes with 1 to 10,000 parts");
|
|
87
|
+
parts.forEach((part, i) => {
|
|
88
|
+
checkPartNumber(part.partNumber);
|
|
89
|
+
if (!/^[0-9a-f]{64}$/.test(part.etag)) throw new StorageError("invalid", `part ${part.partNumber}'s etag is not a SHA-256`, "part");
|
|
90
|
+
if (i > 0 && part.partNumber <= parts[i - 1].partNumber) throw new StorageError("invalid", "the parts are not in ascending part number", "part-order");
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function checkListLimit(limit) {
|
|
94
|
+
if (!count(limit) || limit < 1 || limit > 1e3) invalid("a listing's limit is 1 to 1,000");
|
|
95
|
+
}
|
|
96
|
+
/** Keys compare in UTF-8 byte order, the order listings promise; JavaScript's own order is UTF-16. */
|
|
97
|
+
function compareKeys(a, b) {
|
|
98
|
+
const x = encoder.encode(a), y = encoder.encode(b);
|
|
99
|
+
for (let i = 0; i < Math.min(x.length, y.length); i += 1) if (x[i] !== y[i]) return x[i] - y[i];
|
|
100
|
+
return x.length - y.length;
|
|
101
|
+
}
|
|
102
|
+
async function sha256(bytes) {
|
|
103
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
104
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Refuses parts whose sizes an upload cannot be stored in: every part but the last the size of the
|
|
108
|
+
* first, and the last no larger. Uploads are stored part for part, and parts of one size are what
|
|
109
|
+
* that storage takes.
|
|
110
|
+
*/
|
|
111
|
+
function sameSizes(sizes, named) {
|
|
112
|
+
for (let i = 1; i < sizes.length; i += 1) if (i < sizes.length - 1 ? sizes[i] !== sizes[0] : sizes[i] > sizes[0]) throw new StorageError("invalid", `part ${named[i].partNumber} is ${sizes[i]} bytes: every part but the last must be ${sizes[0]} bytes, as the first is, and the last no larger`, "part");
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
export { storageErrorReasons as _, checkMetadata as a, checkRange as c, invalid as d, resolveRange as f, storageErrorCodes as g, StorageError as h, checkListLimit as i, checkUploadId as l, sha256 as m, checkBucketName as n, checkPartNumber as o, sameSizes as p, checkKey as r, checkParts as s, MIN_PART as t, compareKeys as u };
|