@sequenceholdings/storage 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -16,6 +16,15 @@ const result = await workspace.uploads.put({
16
16
  ```
17
17
 
18
18
  `uploads.start` returns an upload ID before transfer. Keep that ID for
19
+ recovery. Replaying an aborted session returns `409 upload_aborted` after
20
+ authorization and request-identity checks. For a new-file upload, that terminal
21
+ signal permits a new deterministic attempt key **and a new path** for the same
22
+ content (the aborted attempt's node/history remains reserved). Replacements
23
+ still require the file's current revision; the signal does not bypass that CAS.
24
+ Generic `409 conflict`, expired
25
+ but unreconciled sessions, permission failures and lost responses do **not**.
26
+ Completed sessions replay their original file/version even after expiry. Keep
27
+ the original source for
19
28
  `uploads.resume(id, originalSource)`. Source can be a Blob, Web ReadableStream or
20
29
  Node async iterable. The client retains fixed-size chunks, probes GCS offsets and
21
30
  retries transient failures. Supply `size` when known (up to 50 GiB); a stream without
@@ -26,7 +35,11 @@ to keep it and `resume` later. `put` generates an `idempotencyKey` unless you pa
26
35
  one and reuses it whenever it resends `start`, which `start` itself does after a
27
36
  timeout, a lost reply or a 5xx: the server replays a start that committed, so it
28
37
  never leaves an unnamed session holding one of your 20 pending uploads per
29
- workspace. Small Blobs can use
38
+ workspace (100 per workspace). `uploads.startBatch(items)` reserves up to 20 of
39
+ those sessions under one lock. `uploads.list()` returns the caller's live pending
40
+ sessions without bearer URLs, and `uploads.reconcile({ abortIncomplete })`
41
+ publishes sessions whose bytes already match or aborts the ones that never arrived.
42
+ A `429 uploads_busy` names how many sessions are live and the cap. Small Blobs can use
30
43
  `{ method: 'simple' }`; sequential resumable is the default. Parallel multipart is
31
44
  not included. Do not log the session URL: it is a bearer credential.
32
45
 
@@ -83,8 +96,8 @@ const file = await storage.workspace(workspaceId).getNode(fileId)
83
96
  User mode is the default. `{ mode: 'service' }` explicitly selects the manifest's
84
97
  attached platform service account, which needs its own storage grants. Declare
85
98
  `service_account`, `capabilities.storage` and hosted network egress to your Atlas
86
- host and `storage.googleapis.com`. Delegation expires after 15 minutes; resume in a
87
- fresh invocation when necessary. Signed download leases expire after 60 seconds.
99
+ host and `storage.googleapis.com`. Delegation expires shortly after the function's
100
+ `timeout_seconds` (never sooner than 15 minutes); resume in a fresh invocation when necessary. Signed download leases expire after 60 seconds.
88
101
 
89
102
  For local integration use the normal local Atlas environment, `seq-studio storage`
90
103
  and `functions deploy -e local`. File references in ORM remain application-owned;
package/dist/client.d.ts CHANGED
@@ -36,6 +36,15 @@ export declare function storageClient(transport: StorageTransport, { permissions
36
36
  capabilities: () => Promise<{
37
37
  canProvision: boolean;
38
38
  uploadMethods: string[];
39
+ pendingUploads: {
40
+ perPrincipal: number;
41
+ perWorkspace: number;
42
+ };
43
+ uploadBatchLimit: number;
44
+ importConcurrency: {
45
+ default: number;
46
+ max: number;
47
+ };
39
48
  }>;
40
49
  list: (options?: StorageListOptions) => Promise<{
41
50
  items: {
@@ -405,6 +414,73 @@ export declare function storageClient(transport: StorageTransport, { permissions
405
414
  versionId: string | null;
406
415
  maxBytes: number;
407
416
  }>;
417
+ startBatch: (items: StartStorageUpload[]) => Promise<{
418
+ items: {
419
+ id: string;
420
+ fileId: string;
421
+ uploadUrl: string | null;
422
+ expiresAt: string;
423
+ state: "pending" | "complete" | "aborted";
424
+ versionId: string | null;
425
+ maxBytes: number;
426
+ }[];
427
+ }>;
428
+ list: () => Promise<{
429
+ items: {
430
+ id: string;
431
+ fileId: string;
432
+ name: string;
433
+ state: "pending" | "complete" | "aborted";
434
+ expiresAt: string;
435
+ maxBytes: number;
436
+ expectedSize: number | null;
437
+ versionId: string | null;
438
+ }[];
439
+ limits: {
440
+ perPrincipal: number;
441
+ perWorkspace: number;
442
+ };
443
+ }>;
444
+ reconcile: (input?: {
445
+ abortIncomplete?: boolean;
446
+ }) => Promise<{
447
+ completed: {
448
+ id: string;
449
+ fileId: string;
450
+ name: string;
451
+ state: "pending" | "complete" | "aborted";
452
+ expiresAt: string;
453
+ maxBytes: number;
454
+ expectedSize: number | null;
455
+ versionId: string | null;
456
+ code: string;
457
+ message: string;
458
+ }[];
459
+ aborted: {
460
+ id: string;
461
+ fileId: string;
462
+ name: string;
463
+ state: "pending" | "complete" | "aborted";
464
+ expiresAt: string;
465
+ maxBytes: number;
466
+ expectedSize: number | null;
467
+ versionId: string | null;
468
+ code: string;
469
+ message: string;
470
+ }[];
471
+ pending: {
472
+ id: string;
473
+ fileId: string;
474
+ name: string;
475
+ state: "pending" | "complete" | "aborted";
476
+ expiresAt: string;
477
+ maxBytes: number;
478
+ expectedSize: number | null;
479
+ versionId: string | null;
480
+ code: string;
481
+ message: string;
482
+ }[];
483
+ }>;
408
484
  resume(id: string, source: StorageUploadSource, options?: StorageTransferOptions & {
409
485
  size?: number;
410
486
  }): Promise<{
package/dist/client.js CHANGED
@@ -133,6 +133,15 @@ function storageClient(transport, { permissionsRetry = {}, } = {}) {
133
133
  capabilities: () => replayable('GET', '/capabilities', zod_1.z.object({
134
134
  canProvision: zod_1.z.boolean(),
135
135
  uploadMethods: zod_1.z.array(zod_1.z.string()),
136
+ pendingUploads: zod_1.z.object({
137
+ perPrincipal: zod_1.z.number().int(),
138
+ perWorkspace: zod_1.z.number().int(),
139
+ }),
140
+ uploadBatchLimit: zod_1.z.number().int(),
141
+ importConcurrency: zod_1.z.object({
142
+ default: zod_1.z.number().int(),
143
+ max: zod_1.z.number().int(),
144
+ }),
136
145
  })),
137
146
  list: (options = {}) => replayable('GET', `/workspaces${query({ ...options })}`, (0, contracts_js_1.storagePage)(contracts_js_1.workspaceSchema)),
138
147
  shared: (options = {}) => replayable('GET', `/shared${query({ ...options })}`, (0, contracts_js_1.storagePage)(contracts_js_1.nodeSchema)),
@@ -161,6 +170,21 @@ function storageClient(transport, { permissionsRetry = {}, } = {}) {
161
170
  // Abort is idempotent: an aborted session stays aborted, and a completed
162
171
  // one deterministically rejects, so a replay cannot change the outcome.
163
172
  abort: (id) => replayable('DELETE', session(id), contracts_js_1.uploadSessionSchema),
173
+ startBatch: (items) => send({
174
+ method: 'POST',
175
+ path: `${root}/uploads/batch`,
176
+ schema: zod_1.z.object({ items: zod_1.z.array(contracts_js_1.uploadSessionSchema) }),
177
+ body: contracts_js_1.startUploadBatchSchema.parse({ items }),
178
+ retryable: (error) => isRetryableConflict(error) || isUnacknowledged(error),
179
+ }),
180
+ list: () => replayable('GET', `${root}/uploads`, contracts_js_1.uploadListSchema),
181
+ reconcile: (input = {}) => send({
182
+ method: 'POST',
183
+ path: `${root}/uploads/reconcile`,
184
+ schema: contracts_js_1.reconcileUploadsResultSchema,
185
+ body: contracts_js_1.reconcileUploadsSchema.parse(input),
186
+ retryable: isRetryableConflict,
187
+ }),
164
188
  async resume(id, source, options = {}) {
165
189
  const current = await upload.get(id);
166
190
  if (current.state === 'complete')
@@ -149,6 +149,17 @@ export declare const updateNodeSchema: z.ZodObject<{
149
149
  parentId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
150
150
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
151
151
  }, z.core.$strict>;
152
+ /**
153
+ * Live pending upload sessions. Published on the capabilities and upload-list
154
+ * responses so callers can size a batch instead of discovering the cap from a
155
+ * 429. A batch registers at most one principal's cap in a single request.
156
+ */
157
+ export declare const STORAGE_PENDING_UPLOADS_PER_PRINCIPAL = 20;
158
+ export declare const STORAGE_PENDING_UPLOADS_PER_WORKSPACE = 100;
159
+ export declare const STORAGE_UPLOAD_BATCH_LIMIT = 20;
160
+ /** Parallel byte transfers `seq-studio storage import` opens. */
161
+ export declare const STORAGE_IMPORT_CONCURRENCY_DEFAULT = 8;
162
+ export declare const STORAGE_IMPORT_CONCURRENCY_MAX = 16;
152
163
  /** CRC32C in GCS's encoding: base64 of the big-endian uint32. */
153
164
  export declare const storageChecksum: z.ZodString;
154
165
  export declare const startUploadSchema: z.ZodObject<{
@@ -166,6 +177,19 @@ export declare const completeUploadSchema: z.ZodObject<{
166
177
  crc32c: z.ZodOptional<z.ZodString>;
167
178
  }, z.core.$strict>;
168
179
  export type CompleteStorageUpload = z.infer<typeof completeUploadSchema>;
180
+ export declare const startUploadBatchSchema: z.ZodObject<{
181
+ items: z.ZodArray<z.ZodObject<{
182
+ parentId: z.ZodDefault<z.ZodNullable<z.ZodUUID>>;
183
+ path: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
184
+ fileId: z.ZodOptional<z.ZodUUID>;
185
+ expectedRevision: z.ZodOptional<z.ZodNumber>;
186
+ size: z.ZodOptional<z.ZodNumber>;
187
+ contentType: z.ZodDefault<z.ZodString>;
188
+ checksum: z.ZodOptional<z.ZodString>;
189
+ idempotencyKey: z.ZodString;
190
+ }, z.core.$strict>>;
191
+ }, z.core.$strict>;
192
+ export type StartStorageUploadBatch = z.infer<typeof startUploadBatchSchema>;
169
193
  export declare const uploadSessionSchema: z.ZodObject<{
170
194
  id: z.ZodUUID;
171
195
  fileId: z.ZodUUID;
@@ -180,6 +204,100 @@ export declare const uploadSessionSchema: z.ZodObject<{
180
204
  maxBytes: z.ZodNumber;
181
205
  }, z.core.$strip>;
182
206
  export type StorageUploadSession = z.infer<typeof uploadSessionSchema>;
207
+ /** A pending session without its bearer upload URL. */
208
+ export declare const uploadListItemSchema: z.ZodObject<{
209
+ id: z.ZodUUID;
210
+ fileId: z.ZodUUID;
211
+ name: z.ZodString;
212
+ state: z.ZodEnum<{
213
+ pending: "pending";
214
+ complete: "complete";
215
+ aborted: "aborted";
216
+ }>;
217
+ expiresAt: z.ZodString;
218
+ maxBytes: z.ZodNumber;
219
+ expectedSize: z.ZodNullable<z.ZodNumber>;
220
+ versionId: z.ZodNullable<z.ZodUUID>;
221
+ }, z.core.$strip>;
222
+ export type StorageUploadListItem = z.infer<typeof uploadListItemSchema>;
223
+ export declare const uploadLimitsSchema: z.ZodObject<{
224
+ perPrincipal: z.ZodNumber;
225
+ perWorkspace: z.ZodNumber;
226
+ }, z.core.$strip>;
227
+ export declare const uploadListSchema: z.ZodObject<{
228
+ items: z.ZodArray<z.ZodObject<{
229
+ id: z.ZodUUID;
230
+ fileId: z.ZodUUID;
231
+ name: z.ZodString;
232
+ state: z.ZodEnum<{
233
+ pending: "pending";
234
+ complete: "complete";
235
+ aborted: "aborted";
236
+ }>;
237
+ expiresAt: z.ZodString;
238
+ maxBytes: z.ZodNumber;
239
+ expectedSize: z.ZodNullable<z.ZodNumber>;
240
+ versionId: z.ZodNullable<z.ZodUUID>;
241
+ }, z.core.$strip>>;
242
+ limits: z.ZodObject<{
243
+ perPrincipal: z.ZodNumber;
244
+ perWorkspace: z.ZodNumber;
245
+ }, z.core.$strip>;
246
+ }, z.core.$strip>;
247
+ export declare const reconcileUploadsSchema: z.ZodObject<{
248
+ abortIncomplete: z.ZodDefault<z.ZodBoolean>;
249
+ }, z.core.$strict>;
250
+ export declare const reconcileUploadsResultSchema: z.ZodObject<{
251
+ completed: z.ZodArray<z.ZodObject<{
252
+ id: z.ZodUUID;
253
+ fileId: z.ZodUUID;
254
+ name: z.ZodString;
255
+ state: z.ZodEnum<{
256
+ pending: "pending";
257
+ complete: "complete";
258
+ aborted: "aborted";
259
+ }>;
260
+ expiresAt: z.ZodString;
261
+ maxBytes: z.ZodNumber;
262
+ expectedSize: z.ZodNullable<z.ZodNumber>;
263
+ versionId: z.ZodNullable<z.ZodUUID>;
264
+ code: z.ZodString;
265
+ message: z.ZodString;
266
+ }, z.core.$strip>>;
267
+ aborted: z.ZodArray<z.ZodObject<{
268
+ id: z.ZodUUID;
269
+ fileId: z.ZodUUID;
270
+ name: z.ZodString;
271
+ state: z.ZodEnum<{
272
+ pending: "pending";
273
+ complete: "complete";
274
+ aborted: "aborted";
275
+ }>;
276
+ expiresAt: z.ZodString;
277
+ maxBytes: z.ZodNumber;
278
+ expectedSize: z.ZodNullable<z.ZodNumber>;
279
+ versionId: z.ZodNullable<z.ZodUUID>;
280
+ code: z.ZodString;
281
+ message: z.ZodString;
282
+ }, z.core.$strip>>;
283
+ pending: z.ZodArray<z.ZodObject<{
284
+ id: z.ZodUUID;
285
+ fileId: z.ZodUUID;
286
+ name: z.ZodString;
287
+ state: z.ZodEnum<{
288
+ pending: "pending";
289
+ complete: "complete";
290
+ aborted: "aborted";
291
+ }>;
292
+ expiresAt: z.ZodString;
293
+ maxBytes: z.ZodNumber;
294
+ expectedSize: z.ZodNullable<z.ZodNumber>;
295
+ versionId: z.ZodNullable<z.ZodUUID>;
296
+ code: z.ZodString;
297
+ message: z.ZodString;
298
+ }, z.core.$strip>>;
299
+ }, z.core.$strip>;
300
+ export type ReconcileStorageUploads = z.infer<typeof reconcileUploadsSchema>;
183
301
  export declare const storagePrincipalSchema: z.ZodObject<{
184
302
  type: z.ZodEnum<{
185
303
  user: "user";
package/dist/contracts.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.storageErrorSchema = exports.storageAccessSchema = exports.updateStorageAccessSchema = exports.storagePrincipalSchema = exports.uploadSessionSchema = exports.completeUploadSchema = exports.startUploadSchema = exports.storageChecksum = exports.updateNodeSchema = exports.createFolderSchema = exports.listStorageSchema = exports.storagePage = exports.listStorageOperationsSchema = exports.storageOperationScope = exports.operationSchema = exports.versionSchema = exports.nodeSchema = exports.workspaceSchema = exports.workspaceManifestSchema = exports.storageMetadata = exports.storagePath = exports.storageName = exports.storageLifecycle = exports.storageRole = exports.storageId = void 0;
3
+ exports.storageErrorSchema = exports.storageAccessSchema = exports.updateStorageAccessSchema = exports.storagePrincipalSchema = exports.reconcileUploadsResultSchema = exports.reconcileUploadsSchema = exports.uploadListSchema = exports.uploadLimitsSchema = exports.uploadListItemSchema = exports.uploadSessionSchema = exports.startUploadBatchSchema = exports.completeUploadSchema = exports.startUploadSchema = exports.storageChecksum = exports.STORAGE_IMPORT_CONCURRENCY_MAX = exports.STORAGE_IMPORT_CONCURRENCY_DEFAULT = exports.STORAGE_UPLOAD_BATCH_LIMIT = exports.STORAGE_PENDING_UPLOADS_PER_WORKSPACE = exports.STORAGE_PENDING_UPLOADS_PER_PRINCIPAL = exports.updateNodeSchema = exports.createFolderSchema = exports.listStorageSchema = exports.storagePage = exports.listStorageOperationsSchema = exports.storageOperationScope = exports.operationSchema = exports.versionSchema = exports.nodeSchema = exports.workspaceSchema = exports.workspaceManifestSchema = exports.storageMetadata = exports.storagePath = exports.storageName = exports.storageLifecycle = exports.storageRole = exports.storageId = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const paths_js_1 = require("./paths.js");
6
6
  exports.storageId = zod_1.z.uuid();
@@ -124,6 +124,17 @@ exports.updateNodeSchema = zod_1.z.strictObject({
124
124
  parentId: exports.storageId.nullable().optional(),
125
125
  metadata: exports.storageMetadata.optional(),
126
126
  });
127
+ /**
128
+ * Live pending upload sessions. Published on the capabilities and upload-list
129
+ * responses so callers can size a batch instead of discovering the cap from a
130
+ * 429. A batch registers at most one principal's cap in a single request.
131
+ */
132
+ exports.STORAGE_PENDING_UPLOADS_PER_PRINCIPAL = 20;
133
+ exports.STORAGE_PENDING_UPLOADS_PER_WORKSPACE = 100;
134
+ exports.STORAGE_UPLOAD_BATCH_LIMIT = exports.STORAGE_PENDING_UPLOADS_PER_PRINCIPAL;
135
+ /** Parallel byte transfers `seq-studio storage import` opens. */
136
+ exports.STORAGE_IMPORT_CONCURRENCY_DEFAULT = 8;
137
+ exports.STORAGE_IMPORT_CONCURRENCY_MAX = 16;
127
138
  /** CRC32C in GCS's encoding: base64 of the big-endian uint32. */
128
139
  exports.storageChecksum = zod_1.z.string().regex(/^[A-Za-z0-9+/]{6}==$/);
129
140
  exports.startUploadSchema = zod_1.z
@@ -153,6 +164,9 @@ exports.completeUploadSchema = zod_1.z.strictObject({
153
164
  */
154
165
  crc32c: exports.storageChecksum.optional(),
155
166
  });
167
+ exports.startUploadBatchSchema = zod_1.z.strictObject({
168
+ items: zod_1.z.array(exports.startUploadSchema).min(1).max(exports.STORAGE_UPLOAD_BATCH_LIMIT),
169
+ });
156
170
  exports.uploadSessionSchema = zod_1.z.object({
157
171
  id: exports.storageId,
158
172
  fileId: exports.storageId,
@@ -162,6 +176,38 @@ exports.uploadSessionSchema = zod_1.z.object({
162
176
  versionId: exports.storageId.nullable(),
163
177
  maxBytes: zod_1.z.number().int(),
164
178
  });
179
+ /** A pending session without its bearer upload URL. */
180
+ exports.uploadListItemSchema = zod_1.z.object({
181
+ id: exports.storageId,
182
+ fileId: exports.storageId,
183
+ name: zod_1.z.string(),
184
+ state: zod_1.z.enum(['pending', 'complete', 'aborted']),
185
+ expiresAt: zod_1.z.string(),
186
+ maxBytes: zod_1.z.number().int(),
187
+ expectedSize: zod_1.z.number().int().nullable(),
188
+ versionId: exports.storageId.nullable(),
189
+ });
190
+ exports.uploadLimitsSchema = zod_1.z.object({
191
+ perPrincipal: zod_1.z.number().int(),
192
+ perWorkspace: zod_1.z.number().int(),
193
+ });
194
+ exports.uploadListSchema = zod_1.z.object({
195
+ items: zod_1.z.array(exports.uploadListItemSchema),
196
+ limits: exports.uploadLimitsSchema,
197
+ });
198
+ exports.reconcileUploadsSchema = zod_1.z.strictObject({
199
+ /** Abort sessions whose bytes never arrived. Terminal mismatches are already released. */
200
+ abortIncomplete: zod_1.z.boolean().default(false),
201
+ });
202
+ const reconcileEntrySchema = exports.uploadListItemSchema.extend({
203
+ code: zod_1.z.string(),
204
+ message: zod_1.z.string(),
205
+ });
206
+ exports.reconcileUploadsResultSchema = zod_1.z.object({
207
+ completed: zod_1.z.array(reconcileEntrySchema),
208
+ aborted: zod_1.z.array(reconcileEntrySchema),
209
+ pending: zod_1.z.array(reconcileEntrySchema),
210
+ });
165
211
  exports.storagePrincipalSchema = zod_1.z.strictObject({
166
212
  type: zod_1.z.enum(['user', 'group', 'organization', 'service_account']),
167
213
  id: zod_1.z.string().min(1).max(255),
package/dist/transfer.js CHANGED
@@ -128,9 +128,8 @@ async function transferStorageUpload({ uploadUrl, source, size, maxBytes, ...opt
128
128
  const url = checkedUploadUrl(uploadUrl);
129
129
  const fetcher = options.fetch ?? globalThis.fetch;
130
130
  const total = source instanceof Blob ? source.size : size;
131
- if (total !== undefined &&
132
- (!Number.isSafeInteger(total) || total < 0 || total > maxBytes))
133
- throw new StorageTransferError('Upload exceeds its reserved size');
131
+ if (total !== undefined && (!Number.isSafeInteger(total) || total < 0 || total > maxBytes))
132
+ throw new StorageTransferError(`Upload source is ${total} bytes, above the ${maxBytes}-byte reservation`);
134
133
  let chunkBytes = options.chunkBytes ?? 8 * 1024 * 1024;
135
134
  if (chunkBytes < 262144 ||
136
135
  chunkBytes > 32 * 1024 * 1024 ||
@@ -181,7 +180,7 @@ async function transferStorageUpload({ uploadUrl, source, size, maxBytes, ...opt
181
180
  }
182
181
  let offset = initial.offset;
183
182
  if (offset > maxBytes || (total !== undefined && offset > total))
184
- throw new StorageTransferError('Upload offset exceeds the source size');
183
+ throw new StorageTransferError(`Upload session offset is ${offset} bytes, past the source${total === undefined ? '' : ` of ${total} bytes`} and the ${maxBytes}-byte reservation`);
185
184
  options.onProgress?.(offset);
186
185
  const hash = new crc32c_js_1.Crc32c();
187
186
  const iterable = chunks(source, chunkBytes, offset, hash, options.signal);
@@ -197,7 +196,7 @@ async function transferStorageUpload({ uploadUrl, source, size, maxBytes, ...opt
197
196
  throw new StorageTransferError(`Upload stream exceeds its ${maxBytes}-byte reservation; declare \`size\` when starting the upload to send a larger file`);
198
197
  if (end > maxBytes ||
199
198
  (total !== undefined && (end > total || (last && end !== total))))
200
- throw new StorageTransferError('Upload source does not match its reserved size');
199
+ throw new StorageTransferError(`Upload source does not match its reserved size: session offset ${offset} bytes, read ${end} bytes, reservation ${maxBytes} bytes${total === undefined ? '' : `, declared ${total} bytes`}`);
201
200
  const length = total ?? (last ? end : '*');
202
201
  let acknowledged = offset;
203
202
  for (let attempt = 0;; attempt++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sequenceholdings/storage",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Atlas workspace storage contracts and streaming client",
5
5
  "license": "UNLICENSED",
6
6
  "type": "commonjs",