pyric-admin 0.1.0-alpha.7

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.
@@ -0,0 +1,442 @@
1
+ /**
2
+ * `pyric-admin/storage` — Phase 3 + Phase 4b implementation.
3
+ *
4
+ * Mirrors `firebase-admin/storage` with sandbox + prod backends, both
5
+ * selected at {@link initializeApp} time on `pyric-admin/app` (ADR-001
6
+ * D6). Dispatch reads the {@link ADMIN_APP_TARGET} brand on the
7
+ * `PyricAdminApp` handle — no structural duck-typing, no per-call
8
+ * shape probing.
9
+ *
10
+ * - **Prod path** — delegates to `firebase-admin/storage`'s
11
+ * `getStorage(adminApp)`. The returned object is the genuine
12
+ * production `Storage`, so every method on `Storage` / `Bucket` /
13
+ * `File` (from `@google-cloud/storage`) is present unchanged.
14
+ *
15
+ * - **Remote sandbox path** — a handle branded by `pyric-tools`'
16
+ * `connectRemoteSandbox()`/`remoteSandbox()` relays every data
17
+ * operation over the bridge to the browser-hosted SharedWorker's
18
+ * object store (admin lens pinned — rules bypass). Single bucket;
19
+ * 8 MiB per-op byte cap; `getSignedUrl` stays the local stub. See
20
+ * the remote arm section below.
21
+ *
22
+ * - **Sandbox path** — returns an in-process {@link Storage} backed
23
+ * by an in-memory `Map<bucketName, Map<path, FileEntry>>`. State
24
+ * lives on the {@link Sandbox} via a `WeakMap`, so `sandbox.reset()`
25
+ * wipes it alongside Firestore / Auth state. Multi-bucket isolation
26
+ * is real — buckets are independent maps.
27
+ *
28
+ * Supported sandbox surface (the minimum a session-archive flow
29
+ * needs):
30
+ * - `storage.bucket(name?)` → {@link Bucket}-shaped handle
31
+ * - `bucket.file(path)` → {@link File}-shaped handle
32
+ * - `file.save(data, options?)` — `Buffer | string | Uint8Array`
33
+ * - `file.download(options?)` → `[Buffer]`
34
+ * - `file.delete()` — idempotent
35
+ * - `file.exists()` → `[boolean]`
36
+ * - `file.getSignedUrl(options)` → `['pyric-sandbox-storage://…']`
37
+ *
38
+ * **Deferred in the sandbox backend** (throws `"not implemented in
39
+ * pyric-admin/storage sandbox backend"`): streaming uploads
40
+ * (`createWriteStream`), resumable uploads, signed cookies, IAM
41
+ * policies, lifecycle rules, ACLs, copy/move, notifications. The
42
+ * prod path keeps every one of these — only the sandbox stub omits
43
+ * them.
44
+ */
45
+ import { getStorage as getProdStorage } from 'firebase-admin/storage';
46
+ import { isRemoteSandbox, } from 'pyric/sandbox';
47
+ import { ADMIN_APP_TARGET, getApp, } from '../app/index.js';
48
+ /**
49
+ * Get the {@link Storage} service for the given app.
50
+ *
51
+ * - **Prod app** → returns `firebase-admin/storage`'s `Storage` for
52
+ * the underlying `adminApp`. Drop-in compatible with production
53
+ * code that uses `firebase-admin/storage` directly.
54
+ * - **Sandbox app** → returns a sandbox-backed `Storage` whose state
55
+ * lives on the `Sandbox`. `sandbox.reset()` wipes it.
56
+ */
57
+ export function getStorage(app) {
58
+ // No-arg call resolves the default app; nothing initialized → captured
59
+ // `app/no-app` FirebaseAppError (see pyric-admin/app getApp).
60
+ const resolved = app === undefined ? getApp() : app;
61
+ if (resolved[ADMIN_APP_TARGET] === 'sandbox') {
62
+ // Remote brand checked BEFORE the local arm (same dispatch order as
63
+ // auth/database): the local arm's WeakMap state + `onEvent` reset hook
64
+ // must never touch a remote handle — local state keyed off a remote
65
+ // handle would be a private server-side store the browser never sees,
66
+ // and `onEvent` throws on remote handles by design.
67
+ if (isRemoteSandbox(resolved.sandbox)) {
68
+ return getRemoteStorage(resolved.sandbox);
69
+ }
70
+ return getSandboxStorage(resolved);
71
+ }
72
+ if (resolved[ADMIN_APP_TARGET] === 'prod') {
73
+ return getProdStorageHandle(resolved);
74
+ }
75
+ // Defensive: the union is closed at the type level. A runtime value
76
+ // that lands here means a caller forged a handle without going
77
+ // through `initializeApp`.
78
+ throw new TypeError('pyric-admin/storage: getStorage expected a PyricAdminApp from `initializeApp`; ' +
79
+ 'received a value with no recognized ADMIN_APP_TARGET brand.');
80
+ }
81
+ // ─── Prod path ──────────────────────────────────────────────────────────
82
+ /**
83
+ * Resolve the prod path. The returned `Storage` is the unmodified
84
+ * `firebase-admin/storage` handle — every `Bucket` / `File` method
85
+ * works exactly as documented in `@google-cloud/storage`.
86
+ *
87
+ * We assert-cast the result to our local {@link Storage} interface:
88
+ * structurally it's a superset (same `bucket(name?)` shape) and
89
+ * exposing the broader surface gives callers full access without
90
+ * forcing them back into a `firebase-admin/storage` import.
91
+ */
92
+ function getProdStorageHandle(app) {
93
+ const prod = getProdStorage(app.adminApp);
94
+ return prod;
95
+ }
96
+ // ─── Sandbox path ───────────────────────────────────────────────────────
97
+ /**
98
+ * Default sandbox bucket name. Matches the `pyric-default` used by the
99
+ * `pyric/storage` modular sandbox so consumers that switch between
100
+ * surfaces don't see an unexpected bucket name change.
101
+ */
102
+ const DEFAULT_SANDBOX_BUCKET = 'pyric-default';
103
+ /**
104
+ * State + reset-handler registry keyed on `Sandbox` so a single
105
+ * Sandbox shares its storage across every `getStorage` call. The
106
+ * `WeakMap` lets a discarded `Sandbox` (and its state) be GC'd
107
+ * naturally.
108
+ */
109
+ const SANDBOX_STATE = new WeakMap();
110
+ /**
111
+ * Reset-subscription bookkeeping. We subscribe to `sandbox.onEvent`
112
+ * once per Sandbox and re-create the bucket map on
113
+ * `session_boundary` events with `phase: 'reset'`. Without this,
114
+ * `sandbox.reset()` would wipe Firestore but leave storage untouched —
115
+ * a leak the sandbox model deliberately avoids.
116
+ */
117
+ const RESET_HOOKED = new WeakSet();
118
+ function ensureBucketMap(sandbox) {
119
+ let map = SANDBOX_STATE.get(sandbox);
120
+ if (!map) {
121
+ map = new Map();
122
+ SANDBOX_STATE.set(sandbox, map);
123
+ }
124
+ if (!RESET_HOOKED.has(sandbox)) {
125
+ RESET_HOOKED.add(sandbox);
126
+ sandbox.onEvent((event) => {
127
+ if (event.kind === 'session_boundary' && event.phase === 'reset') {
128
+ // Replace the map in place so existing Storage / Bucket / File
129
+ // handles keep working but observe an empty state.
130
+ const existing = SANDBOX_STATE.get(sandbox);
131
+ if (existing)
132
+ existing.clear();
133
+ }
134
+ });
135
+ }
136
+ return map;
137
+ }
138
+ function getSandboxStorage(app) {
139
+ const sandbox = app.sandbox;
140
+ const buckets = ensureBucketMap(sandbox);
141
+ return new SandboxStorage(buckets);
142
+ }
143
+ /**
144
+ * Sandbox `Storage` implementation. Holds a reference to the per-
145
+ * sandbox bucket map; each `bucket()` call returns a fresh `Bucket`
146
+ * handle bound to the same underlying map, mirroring how
147
+ * `@google-cloud/storage` returns lightweight per-call handles.
148
+ */
149
+ class SandboxStorage {
150
+ buckets;
151
+ constructor(buckets) {
152
+ this.buckets = buckets;
153
+ }
154
+ bucket(name) {
155
+ const bucketName = name ?? DEFAULT_SANDBOX_BUCKET;
156
+ let files = this.buckets.get(bucketName);
157
+ if (!files) {
158
+ files = new Map();
159
+ this.buckets.set(bucketName, files);
160
+ }
161
+ return new SandboxBucket(bucketName, files);
162
+ }
163
+ }
164
+ class SandboxBucket {
165
+ name;
166
+ files;
167
+ constructor(name, files) {
168
+ this.name = name;
169
+ this.files = files;
170
+ }
171
+ file(path) {
172
+ return new SandboxFile(path, this, this.files);
173
+ }
174
+ }
175
+ class SandboxFile {
176
+ name;
177
+ bucket;
178
+ files;
179
+ constructor(name, bucket, files) {
180
+ this.name = name;
181
+ this.bucket = bucket;
182
+ this.files = files;
183
+ }
184
+ async save(data, options = {}) {
185
+ if (options.resumable === true) {
186
+ throw new Error('not implemented in pyric-admin/storage sandbox backend: resumable uploads');
187
+ }
188
+ const bytes = toBytes(data);
189
+ const metadata = options.metadata ?? {};
190
+ const entry = {
191
+ data: bytes,
192
+ metadata,
193
+ ...(options.contentType !== undefined ? { contentType: options.contentType } : {}),
194
+ };
195
+ this.files.set(this.name, entry);
196
+ }
197
+ async download(_options = {}) {
198
+ const entry = this.files.get(this.name);
199
+ if (!entry) {
200
+ // Mirror the gcs/firebase-admin error message shape so consumer
201
+ // catch-blocks that string-match `No such object` keep working.
202
+ throw new Error(`No such object: ${this.bucket.name}/${this.name}`);
203
+ }
204
+ return [Buffer.from(entry.data)];
205
+ }
206
+ async delete() {
207
+ this.files.delete(this.name);
208
+ }
209
+ async exists() {
210
+ return [this.files.has(this.name)];
211
+ }
212
+ async getSignedUrl(options) {
213
+ return [stubSignedUrl(this.bucket.name, this.name, options)];
214
+ }
215
+ // ─── Deferred surface (declared so TS callers see a clear error) ────
216
+ /** @deprecated Streaming writes are deferred — see module header. */
217
+ createWriteStream() {
218
+ throw new Error('not implemented in pyric-admin/storage sandbox backend: createWriteStream');
219
+ }
220
+ /** @deprecated Streaming reads are deferred — see module header. */
221
+ createReadStream() {
222
+ throw new Error('not implemented in pyric-admin/storage sandbox backend: createReadStream');
223
+ }
224
+ }
225
+ // ─── Remote sandbox arm (remote sandbox, slice 2) ───────────────────────
226
+ //
227
+ // The app's `Sandbox` is a Node-side handle onto the browser-hosted
228
+ // SharedWorker sandbox. Every data operation relays over the handle's
229
+ // worker channel with `actAs: { mode: 'admin' }` pinned — firebase-admin's
230
+ // rules-bypass semantics against the ONE object store the app + Studio +
231
+ // agents share (the host resolves the lens to `pyric/storage/internal`'s
232
+ // admin plane). There is deliberately NO local state here: a `WeakMap`
233
+ // bucket map keyed off a remote handle would be private server-side data
234
+ // the browser never sees, and the local arm's `onEvent` reset hook throws
235
+ // on remote handles by design.
236
+ //
237
+ // Divergences from the local arm, all LOUD:
238
+ // - single bucket: the worker's `pyric/storage` store is single-bucket
239
+ // ("the data store is shared" — bucket names only round-trip in
240
+ // metadata), so `bucket('non-default')` throws instead of silently
241
+ // merging buckets. The default bucket name matches the local arm.
242
+ // - byte payloads are capped at 8 MiB per op (whole-object buffering
243
+ // over four relay hops; streaming stays unsupported on both arms).
244
+ // `getSignedUrl` does NOT relay: it stays the byte-identical local stub.
245
+ /** firebase-admin's rules-bypass lens, pinned on every relayed operation. */
246
+ const STORAGE_REMOTE_ADMIN_LENS = { mode: 'admin' };
247
+ /**
248
+ * Raw per-op byte cap for relayed storage payloads. MUST mirror
249
+ * `pyric-tools`' `MAX_STORAGE_OP_BYTES` (serve/worker/protocol.ts) — the
250
+ * worker host enforces the same cap on its end. Inlined (like the RTDB
251
+ * push-id generator) because `pyric-admin` deliberately does not depend on
252
+ * `pyric-tools`.
253
+ */
254
+ const MAX_REMOTE_STORAGE_OP_BYTES = 8 * 1024 * 1024;
255
+ /** One remote `Storage` per remote handle (handles only — never data). */
256
+ const remoteStorageBySandbox = new WeakMap();
257
+ function getRemoteStorage(sandbox) {
258
+ let storage = remoteStorageBySandbox.get(sandbox);
259
+ if (!storage) {
260
+ storage = new RemoteStorage(sandbox.channel);
261
+ remoteStorageBySandbox.set(sandbox, storage);
262
+ }
263
+ return storage;
264
+ }
265
+ class RemoteStorage {
266
+ channel;
267
+ constructor(channel) {
268
+ this.channel = channel;
269
+ }
270
+ bucket(name) {
271
+ // The worker's object store is single-bucket. A non-default name can't
272
+ // be faithfully relayed — throw loudly instead of silently merging
273
+ // buckets (the local arm has REAL multi-bucket isolation; this is the
274
+ // sharpest local/remote divergence, so it must be explicit).
275
+ if (name !== undefined && name !== DEFAULT_SANDBOX_BUCKET) {
276
+ throw new Error(`pyric-admin/storage: the remote (browser) sandbox has a single bucket — ` +
277
+ `bucket('${name}') cannot be isolated. Use bucket() (the default ` +
278
+ `'${DEFAULT_SANDBOX_BUCKET}' bucket) instead.`);
279
+ }
280
+ return new RemoteBucket(DEFAULT_SANDBOX_BUCKET, this.channel);
281
+ }
282
+ }
283
+ class RemoteBucket {
284
+ name;
285
+ channel;
286
+ constructor(name, channel) {
287
+ this.name = name;
288
+ this.channel = channel;
289
+ }
290
+ file(path) {
291
+ return new RemoteFile(path, this, this.channel);
292
+ }
293
+ }
294
+ class RemoteFile {
295
+ name;
296
+ bucket;
297
+ channel;
298
+ constructor(name, bucket, channel) {
299
+ this.name = name;
300
+ this.bucket = bucket;
301
+ this.channel = channel;
302
+ }
303
+ async save(data, options = {}) {
304
+ if (options.resumable === true) {
305
+ throw new Error('not implemented in pyric-admin/storage remote sandbox backend: resumable uploads');
306
+ }
307
+ const bytes = toBytes(data);
308
+ if (bytes.byteLength > MAX_REMOTE_STORAGE_OP_BYTES) {
309
+ throw payloadTooLarge(bytes.byteLength, `save() payload for '${this.name}'`);
310
+ }
311
+ await this.channel.op({
312
+ method: 'storage.putBytes',
313
+ path: this.name,
314
+ dataB64: Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('base64'),
315
+ ...(options.contentType !== undefined ? { contentType: options.contentType } : {}),
316
+ ...(options.metadata !== undefined ? { metadata: options.metadata } : {}),
317
+ actAs: STORAGE_REMOTE_ADMIN_LENS,
318
+ });
319
+ }
320
+ async download(_options = {}) {
321
+ let wire;
322
+ try {
323
+ wire = (await this.channel.op({
324
+ method: 'storage.getBytes',
325
+ path: this.name,
326
+ actAs: STORAGE_REMOTE_ADMIN_LENS,
327
+ }));
328
+ }
329
+ catch (err) {
330
+ if (isObjectNotFound(err)) {
331
+ // Mirror the gcs/firebase-admin (and local arm) message shape so
332
+ // consumer catch-blocks that string-match `No such object` work
333
+ // identically across arms.
334
+ throw new Error(`No such object: ${this.bucket.name}/${this.name}`);
335
+ }
336
+ throw err;
337
+ }
338
+ return [Buffer.from(wire.dataB64, 'base64')];
339
+ }
340
+ async delete() {
341
+ try {
342
+ await this.channel.op({
343
+ method: 'storage.deleteObject',
344
+ path: this.name,
345
+ actAs: STORAGE_REMOTE_ADMIN_LENS,
346
+ });
347
+ }
348
+ catch (err) {
349
+ // The worker store's delete is already idempotent, but swallow a
350
+ // not-found defensively so the local arm's idempotent-delete contract
351
+ // holds even if `pyric/storage` adopts the strict prod throw later.
352
+ if (isObjectNotFound(err))
353
+ return;
354
+ throw err;
355
+ }
356
+ }
357
+ async exists() {
358
+ try {
359
+ await this.channel.op({
360
+ method: 'storage.getMetadata',
361
+ path: this.name,
362
+ actAs: STORAGE_REMOTE_ADMIN_LENS,
363
+ });
364
+ return [true];
365
+ }
366
+ catch (err) {
367
+ if (isObjectNotFound(err))
368
+ return [false];
369
+ throw err;
370
+ }
371
+ }
372
+ /** Local stub — byte-identical to the local arm's (no data needed, so it
373
+ * never relays). The sandbox does NOT serve the URL. */
374
+ async getSignedUrl(options) {
375
+ return [stubSignedUrl(this.bucket.name, this.name, options)];
376
+ }
377
+ // ─── Deferred surface (remediating throws, remote-flavored) ─────────
378
+ createWriteStream() {
379
+ throw new Error('not implemented in pyric-admin/storage remote sandbox backend: createWriteStream — ' +
380
+ 'streams cannot span the bridge relay; use file.save(buffer) (≤ 8 MiB) instead.');
381
+ }
382
+ createReadStream() {
383
+ throw new Error('not implemented in pyric-admin/storage remote sandbox backend: createReadStream — ' +
384
+ 'streams cannot span the bridge relay; use file.download() (≤ 8 MiB) instead.');
385
+ }
386
+ }
387
+ /** Is this relayed error the worker's `storage/object-not-found`? */
388
+ function isObjectNotFound(err) {
389
+ return err?.code === 'storage/object-not-found';
390
+ }
391
+ /** Over-cap rejection (code `payload-too-large`) — mirrors the worker host's
392
+ * message shape and names the streaming gap. */
393
+ function payloadTooLarge(sizeBytes, what) {
394
+ const err = new Error(`pyric-admin/storage: ${what} is ${sizeBytes} bytes — over the ` +
395
+ `${MAX_REMOTE_STORAGE_OP_BYTES / (1024 * 1024)} MiB remote storage op cap. ` +
396
+ 'Streaming/resumable transfers are not supported on the sandbox backend; ' +
397
+ 'split the object or keep it under the cap.');
398
+ err.code = 'payload-too-large';
399
+ return err;
400
+ }
401
+ // ─── Helpers ────────────────────────────────────────────────────────────
402
+ /**
403
+ * The deterministic sandbox signed-URL stub, shared by the local and remote
404
+ * arms so their output is byte-identical (the URL is never served — it's a
405
+ * stable placeholder for logs/fixtures/replay).
406
+ */
407
+ function stubSignedUrl(bucketName, path, options) {
408
+ const expiresMs = normalizeExpires(options.expires);
409
+ return `pyric-sandbox-storage://${bucketName}/${path}?expires=${expiresMs}&action=${options.action}`;
410
+ }
411
+ /**
412
+ * Normalize `Buffer | string | Uint8Array` into a fresh `Uint8Array`.
413
+ * We copy on ingest so callers can mutate their input buffer without
414
+ * corrupting stored state — mirrors how `firebase-admin/storage` /
415
+ * `@google-cloud/storage` treat `save` inputs.
416
+ */
417
+ function toBytes(data) {
418
+ if (typeof data === 'string') {
419
+ return new TextEncoder().encode(data);
420
+ }
421
+ // Both Buffer (Node) and Uint8Array land here — copy into a new
422
+ // Uint8Array so the stored bytes are independent of the caller's
423
+ // reference. `slice()` produces a copy in both cases.
424
+ return new Uint8Array(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength));
425
+ }
426
+ /**
427
+ * Normalize the `expires` field into ms-since-epoch. Mirrors the
428
+ * accepted shapes from `@google-cloud/storage`'s `GetSignedUrlOptions`.
429
+ * The sandbox doesn't enforce expiration — the value is only embedded
430
+ * in the stub URL so consumers can round-trip it.
431
+ */
432
+ function normalizeExpires(expires) {
433
+ if (typeof expires === 'number')
434
+ return expires;
435
+ if (expires instanceof Date)
436
+ return expires.getTime();
437
+ // String form — accept anything `Date` parses. Bogus input becomes
438
+ // `NaN`, which is still embeddable in the URL; we don't enforce
439
+ // strictness because the prod path delegates this to GCS anyway.
440
+ return new Date(expires).getTime();
441
+ }
442
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "pyric-admin",
3
+ "version": "0.1.0-alpha.7",
4
+ "description": "Pyric admin-shape adapters (firestore, auth, database, storage) with swappable sandbox/prod backends. Mirrors firebase-admin.",
5
+ "type": "module",
6
+ "exports": {
7
+ "./app": {
8
+ "types": "./dist/app/index.d.ts",
9
+ "import": "./dist/app/index.js"
10
+ },
11
+ "./firestore": {
12
+ "types": "./dist/firestore/index.d.ts",
13
+ "import": "./dist/firestore/index.js"
14
+ },
15
+ "./auth": {
16
+ "types": "./dist/auth/index.d.ts",
17
+ "import": "./dist/auth/index.js"
18
+ },
19
+ "./database": {
20
+ "types": "./dist/database/index.d.ts",
21
+ "import": "./dist/database/index.js"
22
+ },
23
+ "./storage": {
24
+ "types": "./dist/storage/index.d.ts",
25
+ "import": "./dist/storage/index.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md"
31
+ ],
32
+ "scripts": {
33
+ "build": "tsc",
34
+ "test": "bun test",
35
+ "typecheck": "bun x tsc -p tsconfig.json --noEmit"
36
+ },
37
+ "dependencies": {
38
+ "pyric": "^0.1.0-alpha.7",
39
+ "firebase-admin": "^13.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/bun": "latest",
43
+ "typescript": "^5.7.0",
44
+ "fake-indexeddb": "^6.0.0"
45
+ },
46
+ "engines": {
47
+ "node": ">=22"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ }
52
+ }