pyric 0.1.0-alpha.21 → 0.1.0-alpha.22
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/storage/persistence.d.ts +81 -17
- package/dist/storage/persistence.d.ts.map +1 -1
- package/dist/storage/persistence.js +267 -50
- package/dist/storage/persistence.js.map +1 -1
- package/dist/storage/service.d.ts +2 -4
- package/dist/storage/service.d.ts.map +1 -1
- package/dist/storage/service.js +63 -21
- package/dist/storage/service.js.map +1 -1
- package/package.json +1 -1
- package/src/storage/persistence.ts +291 -63
- package/src/storage/service.ts +81 -27
package/src/storage/service.ts
CHANGED
|
@@ -121,10 +121,8 @@ export class StorageService {
|
|
|
121
121
|
/** Options for {@link getStorageSandbox}. */
|
|
122
122
|
export interface StorageOptions {
|
|
123
123
|
/**
|
|
124
|
-
* Bucket identifier recorded on uploaded metadata
|
|
125
|
-
*
|
|
126
|
-
* isolation — passing different values per call is accepted and
|
|
127
|
-
* round-trips in metadata, but the data store is shared.
|
|
124
|
+
* Bucket identifier recorded on uploaded metadata and used to isolate
|
|
125
|
+
* storage persistence across multiple buckets.
|
|
128
126
|
*/
|
|
129
127
|
bucket?: string;
|
|
130
128
|
/**
|
|
@@ -180,18 +178,44 @@ interface OpenStorageService {
|
|
|
180
178
|
/** One open storage service, with its opening configuration, per `Sandbox`. */
|
|
181
179
|
const OPEN_SERVICES = new WeakMap<Sandbox, OpenStorageService>();
|
|
182
180
|
|
|
183
|
-
/** One
|
|
184
|
-
const
|
|
181
|
+
/** One scoped `StorageService` per (Sandbox, bucket). */
|
|
182
|
+
const SCOPED_SERVICES = new WeakMap<Sandbox, Map<string, Promise<StorageService>>>();
|
|
183
|
+
|
|
184
|
+
function getOrCreateScopedService(
|
|
185
|
+
sandbox: Sandbox,
|
|
186
|
+
bucket: string,
|
|
187
|
+
rootServicePromise: Promise<StorageService>,
|
|
188
|
+
): Promise<StorageService> {
|
|
189
|
+
let bucketMap = SCOPED_SERVICES.get(sandbox);
|
|
190
|
+
if (!bucketMap) {
|
|
191
|
+
bucketMap = new Map();
|
|
192
|
+
SCOPED_SERVICES.set(sandbox, bucketMap);
|
|
193
|
+
}
|
|
194
|
+
let existing = bucketMap.get(bucket);
|
|
195
|
+
if (!existing) {
|
|
196
|
+
existing = rootServicePromise.then((rootService) => {
|
|
197
|
+
const backend = rootService.backend.scoped
|
|
198
|
+
? rootService.backend.scoped(bucket)
|
|
199
|
+
: rootService.backend;
|
|
200
|
+
return new StorageService(backend, rootService.rules, rootService.crossServiceIam);
|
|
201
|
+
});
|
|
202
|
+
bucketMap.set(bucket, existing);
|
|
203
|
+
}
|
|
204
|
+
return existing;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Sandbox `FirebaseStorage` handles cached per `(SandboxContext, bucket)`. */
|
|
208
|
+
const SANDBOX_HANDLES = new WeakMap<SandboxContext, Map<string, FirebaseStorage>>();
|
|
185
209
|
|
|
186
210
|
/**
|
|
187
|
-
*
|
|
211
|
+
* Anonymous handles cached per `(Sandbox, bucket)`. `Sandbox.withAuth(null)`
|
|
188
212
|
* mints a FRESH `SandboxContext` on every call, so the per-context
|
|
189
213
|
* cache above would miss for the bare-`Sandbox` convenience path —
|
|
190
214
|
* `getStorageSandbox(sandbox)` twice returned two different handles
|
|
191
215
|
* (ST-B3). Caching on the `Sandbox` keeps that path identity-stable,
|
|
192
216
|
* matching the documented "handle is identity-stable per Sandbox".
|
|
193
217
|
*/
|
|
194
|
-
const BARE_SANDBOX_HANDLES = new WeakMap<Sandbox, FirebaseStorage
|
|
218
|
+
const BARE_SANDBOX_HANDLES = new WeakMap<Sandbox, Map<string, FirebaseStorage>>();
|
|
195
219
|
|
|
196
220
|
/**
|
|
197
221
|
* Reject a late option that differs from the one the service is already
|
|
@@ -285,6 +309,7 @@ function ensureService(
|
|
|
285
309
|
const crossServiceIam = options.crossServiceIam ?? 'granted';
|
|
286
310
|
const servicePromise = openStorageBackend(
|
|
287
311
|
options.dbName ?? storageDbName(options.projectId),
|
|
312
|
+
options.bucket ?? DEFAULT_BUCKET,
|
|
288
313
|
).then(
|
|
289
314
|
(backend) => new StorageService(backend, rules, crossServiceIam),
|
|
290
315
|
);
|
|
@@ -336,24 +361,22 @@ export function getStorageSandbox(
|
|
|
336
361
|
// Open (or fetch) the ONE per-sandbox service FIRST — before any handle
|
|
337
362
|
// cache fast-path — so a late, differing `rules` option throws instead of
|
|
338
363
|
// being silently discarded (see ensureService).
|
|
339
|
-
const
|
|
364
|
+
const rootServicePromise = ensureService(sandbox, options, 'getStorageSandbox');
|
|
365
|
+
const bucket = options.bucket ?? DEFAULT_BUCKET;
|
|
340
366
|
|
|
341
367
|
// Fast path: a repeat bare-`Sandbox` call returns the cached
|
|
342
|
-
// anonymous handle
|
|
343
|
-
// because `withAuth(null)` mints a fresh context each time). Like
|
|
344
|
-
// the per-context path, bucket/dbName options are honored only on
|
|
345
|
-
// the FIRST call per Sandbox.
|
|
368
|
+
// anonymous handle for the requested bucket.
|
|
346
369
|
if (fromBareSandbox) {
|
|
347
|
-
const cachedBare = BARE_SANDBOX_HANDLES.get(sandbox);
|
|
370
|
+
const cachedBare = BARE_SANDBOX_HANDLES.get(sandbox)?.get(bucket);
|
|
348
371
|
if (cachedBare) return cachedBare;
|
|
349
372
|
}
|
|
350
373
|
|
|
351
374
|
const ctx = isContext(target) ? target : target.withAuth(null);
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
const cached = SANDBOX_HANDLES.get(ctx);
|
|
375
|
+
const cached = SANDBOX_HANDLES.get(ctx)?.get(bucket);
|
|
355
376
|
if (cached) return cached;
|
|
356
377
|
|
|
378
|
+
const servicePromise = getOrCreateScopedService(sandbox, bucket, rootServicePromise);
|
|
379
|
+
|
|
357
380
|
const sandboxTarget: SandboxTarget = {
|
|
358
381
|
kind: 'sandbox',
|
|
359
382
|
sandbox,
|
|
@@ -363,15 +386,29 @@ export function getStorageSandbox(
|
|
|
363
386
|
servicePromise,
|
|
364
387
|
};
|
|
365
388
|
const handle: FirebaseStorage = Object.freeze({ [TARGET_SYMBOL]: sandboxTarget });
|
|
366
|
-
|
|
367
|
-
|
|
389
|
+
|
|
390
|
+
let ctxMap = SANDBOX_HANDLES.get(ctx);
|
|
391
|
+
if (!ctxMap) {
|
|
392
|
+
ctxMap = new Map();
|
|
393
|
+
SANDBOX_HANDLES.set(ctx, ctxMap);
|
|
394
|
+
}
|
|
395
|
+
ctxMap.set(bucket, handle);
|
|
396
|
+
|
|
397
|
+
if (fromBareSandbox) {
|
|
398
|
+
let bareMap = BARE_SANDBOX_HANDLES.get(sandbox);
|
|
399
|
+
if (!bareMap) {
|
|
400
|
+
bareMap = new Map();
|
|
401
|
+
BARE_SANDBOX_HANDLES.set(sandbox, bareMap);
|
|
402
|
+
}
|
|
403
|
+
bareMap.set(bucket, handle);
|
|
404
|
+
}
|
|
368
405
|
return handle;
|
|
369
406
|
}
|
|
370
407
|
|
|
371
408
|
/** Admin handles are cached by bound context so Studio, agent, and
|
|
372
409
|
* unattributed callers can share storage state without sharing provenance. */
|
|
373
|
-
const ADMIN_CONTEXT_HANDLES = new WeakMap<SandboxContext, FirebaseStorage
|
|
374
|
-
const BARE_ADMIN_HANDLES = new WeakMap<Sandbox, FirebaseStorage
|
|
410
|
+
const ADMIN_CONTEXT_HANDLES = new WeakMap<SandboxContext, Map<string, FirebaseStorage>>();
|
|
411
|
+
const BARE_ADMIN_HANDLES = new WeakMap<Sandbox, Map<string, FirebaseStorage>>();
|
|
375
412
|
|
|
376
413
|
/**
|
|
377
414
|
* INTERNAL (exported via `pyric/storage/internal` only) — construct
|
|
@@ -395,10 +432,11 @@ export function getAdminStorageSandbox(
|
|
|
395
432
|
const fromBareSandbox = !isContext(target);
|
|
396
433
|
const sandbox = isContext(target) ? target.sandbox : target;
|
|
397
434
|
// Service first (late differing `rules` must throw, even on a cache hit).
|
|
398
|
-
const
|
|
435
|
+
const rootServicePromise = ensureService(sandbox, options, 'getAdminStorageSandbox');
|
|
436
|
+
const bucket = options.bucket ?? DEFAULT_BUCKET;
|
|
399
437
|
|
|
400
438
|
if (fromBareSandbox) {
|
|
401
|
-
const cachedBare = BARE_ADMIN_HANDLES.get(sandbox);
|
|
439
|
+
const cachedBare = BARE_ADMIN_HANDLES.get(sandbox)?.get(bucket);
|
|
402
440
|
if (cachedBare) return cachedBare;
|
|
403
441
|
}
|
|
404
442
|
|
|
@@ -412,20 +450,36 @@ export function getAdminStorageSandbox(
|
|
|
412
450
|
? {}
|
|
413
451
|
: { planId: baseContext.operationContext.planId }),
|
|
414
452
|
});
|
|
415
|
-
const cached = ADMIN_CONTEXT_HANDLES.get(baseContext);
|
|
453
|
+
const cached = ADMIN_CONTEXT_HANDLES.get(baseContext)?.get(bucket);
|
|
416
454
|
if (cached) return cached;
|
|
417
455
|
|
|
456
|
+
const servicePromise = getOrCreateScopedService(sandbox, bucket, rootServicePromise);
|
|
457
|
+
|
|
418
458
|
const sandboxTarget: SandboxTarget = {
|
|
419
459
|
kind: 'sandbox',
|
|
420
460
|
sandbox,
|
|
421
461
|
context,
|
|
422
|
-
bucket
|
|
462
|
+
bucket,
|
|
423
463
|
servicePromise,
|
|
424
464
|
admin: true,
|
|
425
465
|
};
|
|
426
466
|
const handle: FirebaseStorage = Object.freeze({ [TARGET_SYMBOL]: sandboxTarget });
|
|
427
|
-
|
|
428
|
-
|
|
467
|
+
|
|
468
|
+
let ctxMap = ADMIN_CONTEXT_HANDLES.get(baseContext);
|
|
469
|
+
if (!ctxMap) {
|
|
470
|
+
ctxMap = new Map();
|
|
471
|
+
ADMIN_CONTEXT_HANDLES.set(baseContext, ctxMap);
|
|
472
|
+
}
|
|
473
|
+
ctxMap.set(bucket, handle);
|
|
474
|
+
|
|
475
|
+
if (fromBareSandbox) {
|
|
476
|
+
let bareMap = BARE_ADMIN_HANDLES.get(sandbox);
|
|
477
|
+
if (!bareMap) {
|
|
478
|
+
bareMap = new Map();
|
|
479
|
+
BARE_ADMIN_HANDLES.set(sandbox, bareMap);
|
|
480
|
+
}
|
|
481
|
+
bareMap.set(bucket, handle);
|
|
482
|
+
}
|
|
429
483
|
return handle;
|
|
430
484
|
}
|
|
431
485
|
|