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.
- package/README.md +49 -0
- package/dist/app/index.d.ts +158 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/index.js +297 -0
- package/dist/app/index.js.map +1 -0
- package/dist/auth/index.d.ts +146 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +785 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/database/index.d.ts +119 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +965 -0
- package/dist/database/index.js.map +1 -0
- package/dist/firestore/index.d.ts +32 -0
- package/dist/firestore/index.d.ts.map +1 -0
- package/dist/firestore/index.js +53 -0
- package/dist/firestore/index.js.map +1 -0
- package/dist/storage/index.d.ts +178 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +442 -0
- package/dist/storage/index.js.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# pyric-admin
|
|
2
|
+
|
|
3
|
+
Firebase Admin-shaped adapters that can run against either a Pyric sandbox or
|
|
4
|
+
real Firebase Admin SDK backends.
|
|
5
|
+
|
|
6
|
+
Use `pyric-admin` when you want server/admin ergonomics while keeping the same
|
|
7
|
+
backend-selection model as the client package.
|
|
8
|
+
|
|
9
|
+
> **Alpha.** This package is an early alpha. The admin-shaped subpaths are
|
|
10
|
+
> best-effort mirror contracts of `firebase-admin` — not guaranteed parity.
|
|
11
|
+
> Any exported surface beyond the mirrored shapes is experimental public-alpha
|
|
12
|
+
> and may change without notice.
|
|
13
|
+
|
|
14
|
+
## Subpaths
|
|
15
|
+
|
|
16
|
+
| Subpath | Surface |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `pyric-admin/app` | Admin app initialization for sandbox or production |
|
|
19
|
+
| `pyric-admin/firestore` | Admin Firestore shape over sandbox or `firebase-admin/firestore` |
|
|
20
|
+
| `pyric-admin/auth` | Admin Auth shape over sandbox or `firebase-admin/auth` |
|
|
21
|
+
| `pyric-admin/database` | Admin Realtime Database shape over sandbox or `firebase-admin/database` |
|
|
22
|
+
| `pyric-admin/storage` | Admin Storage shape over sandbox or `firebase-admin/storage` |
|
|
23
|
+
|
|
24
|
+
## Example
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { initializeApp } from 'pyric-admin/app';
|
|
28
|
+
import { initializeSandbox } from 'pyric/sandbox';
|
|
29
|
+
import { getFirestore } from 'pyric-admin/firestore';
|
|
30
|
+
|
|
31
|
+
const app = initializeApp({ sandbox: initializeSandbox() });
|
|
32
|
+
const db = getFirestore(app);
|
|
33
|
+
|
|
34
|
+
await db.collection('posts').doc('hello').set({ title: 'Hello' });
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Production apps pass normal Firebase Admin credentials:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { initializeApp, cert } from 'pyric-admin/app';
|
|
41
|
+
|
|
42
|
+
const app = initializeApp({
|
|
43
|
+
credential: cert(serviceAccount),
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Docs
|
|
48
|
+
|
|
49
|
+
- [Firestore docs](docs/firestore/)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pyric-admin/app` — Phase 3 initializeApp surface (ADR-001 D6) plus the
|
|
3
|
+
* default-app registry and AMBIENT INIT (adoption experience, layer 3).
|
|
4
|
+
*
|
|
5
|
+
* Mirror shape: at cutover this becomes `pyric-admin/app` and is the
|
|
6
|
+
* single entry point where the sandbox-vs-prod choice is made for the
|
|
7
|
+
* admin surface. Every `pyric-admin/*` subpath inspects the handle's
|
|
8
|
+
* brand for backend dispatch.
|
|
9
|
+
*
|
|
10
|
+
* Current shadow scope:
|
|
11
|
+
* - `initializeApp({ credential, ... })` — prod-backed (live;
|
|
12
|
+
* delegates to `firebase-admin/app.initializeApp`).
|
|
13
|
+
* - `initializeApp({ sandbox })` — sandbox-backed (live).
|
|
14
|
+
* - `initializeApp()` — BARE, zero pyric identifiers in app code. The
|
|
15
|
+
* environment decides the backend (see "Ambient init" below).
|
|
16
|
+
* - Per-subpath adapter dispatch via the brand: implemented in
|
|
17
|
+
* `pyric-admin/{auth,database,storage}` via structural
|
|
18
|
+
* checks on the `app` argument. Phase 4 commits use property
|
|
19
|
+
* probing today; cutover swaps to brand-symbol reads.
|
|
20
|
+
*
|
|
21
|
+
* ── Default-app registry ──────────────────────────────────────────────
|
|
22
|
+
*
|
|
23
|
+
* Mirrors `firebase-admin/app`'s lifecycle exactly (oracle:
|
|
24
|
+
* `firebase-admin/lib/app/lifecycle.js`):
|
|
25
|
+
*
|
|
26
|
+
* - `initializeApp(config?, name?)` registers under `'[DEFAULT]'` when
|
|
27
|
+
* unnamed; `getApp(name?)` / `getApps()` / `deleteApp(app)` read it.
|
|
28
|
+
* - Duplicate-name errors carry firebase-admin's codes and message
|
|
29
|
+
* text: `app/duplicate-app` for a config'd re-init with a different
|
|
30
|
+
* configuration, `app/invalid-app-options` for a bare-vs-config'd
|
|
31
|
+
* mismatch (firebase-admin's autoInit mismatch), `app/no-app` from
|
|
32
|
+
* `getApp` on a missing name, `app/invalid-app-name` for a
|
|
33
|
+
* non-string/empty name. Errors reuse firebase-admin's own
|
|
34
|
+
* `FirebaseAppError` class, so `instanceof`, `constructor.name`,
|
|
35
|
+
* `.code`, and message text match production byte-for-byte
|
|
36
|
+
* (oracle: `admin-app-*` observations).
|
|
37
|
+
* - Idempotency mirror: a bare `initializeApp()` repeated for the same
|
|
38
|
+
* name returns the existing auto-initialized app (firebase-admin's
|
|
39
|
+
* autoInit path); `initializeApp({ sandbox })` repeated with the
|
|
40
|
+
* SAME `Sandbox` reference returns the existing app (the reference
|
|
41
|
+
* is our deep-equal analog — sandboxes have identity, not value
|
|
42
|
+
* equality); prod re-inits delegate the equality decision to
|
|
43
|
+
* firebase-admin itself (deep-equal options → same app; credential /
|
|
44
|
+
* httpAgent present → `app/invalid-app-options`, exactly upstream).
|
|
45
|
+
*
|
|
46
|
+
* ── Ambient init (bare `initializeApp()`) ─────────────────────────────
|
|
47
|
+
*
|
|
48
|
+
* The user's server code contains ZERO pyric identifiers — bare
|
|
49
|
+
* `initializeApp()` + no-arg `getDatabase()` / `getAuth()`. The
|
|
50
|
+
* environment picks the backend:
|
|
51
|
+
*
|
|
52
|
+
* - `PYRIC_SANDBOX` unset → exactly firebase-admin's behavior:
|
|
53
|
+
* delegate to `firebase-admin/app.initializeApp()` (FIREBASE_CONFIG
|
|
54
|
+
* env + application-default credentials), register the prod arm.
|
|
55
|
+
* - `PYRIC_SANDBOX=remote` or `remote:<url>` → obtain a remote sandbox
|
|
56
|
+
* handle from the factory installed by `pyric-tools/register` at
|
|
57
|
+
* `globalThis[REMOTE_SANDBOX_FACTORY]`
|
|
58
|
+
* (`Symbol.for('pyric.remote.sandboxFactory')`) and register the
|
|
59
|
+
* sandbox arm. One activation line is logged to stderr. If the env
|
|
60
|
+
* is set but no factory is installed, throw with remediation (run
|
|
61
|
+
* under `pyric dev`, or add `--import pyric-tools/register` to
|
|
62
|
+
* NODE_OPTIONS).
|
|
63
|
+
* - Production guard: refuses to route to a sandbox when
|
|
64
|
+
* `NODE_ENV === 'production'`, unless `PYRIC_SANDBOX_FORCE=1`.
|
|
65
|
+
*
|
|
66
|
+
* Ambient resolution happens ONLY on the bare call — any explicit
|
|
67
|
+
* config (`{ sandbox }` or prod `AppOptions`, even `{}`) bypasses the
|
|
68
|
+
* env entirely, so pyric-aware code keeps full control.
|
|
69
|
+
*/
|
|
70
|
+
import { type Sandbox } from 'pyric/sandbox';
|
|
71
|
+
import { type App as AdminApp, type AppOptions } from 'firebase-admin/app';
|
|
72
|
+
/**
|
|
73
|
+
* Brand on every PyricAdminApp. Matches the symbol used in
|
|
74
|
+
* `pyric/app` so a future shared `pyric/runtime` package can
|
|
75
|
+
* unify dispatch helpers across both packages.
|
|
76
|
+
*/
|
|
77
|
+
export declare const ADMIN_APP_TARGET: unique symbol;
|
|
78
|
+
export type PyricAdminAppTarget = 'sandbox' | 'prod';
|
|
79
|
+
/** firebase-admin's default app name — `'[DEFAULT]'`. */
|
|
80
|
+
export declare const DEFAULT_APP_NAME = "[DEFAULT]";
|
|
81
|
+
export interface SandboxAdminApp {
|
|
82
|
+
readonly [ADMIN_APP_TARGET]: 'sandbox';
|
|
83
|
+
readonly sandbox: Sandbox;
|
|
84
|
+
/** Registry name (mirrors firebase-admin `App.name`). */
|
|
85
|
+
readonly name: string;
|
|
86
|
+
}
|
|
87
|
+
export interface ProdAdminApp {
|
|
88
|
+
readonly [ADMIN_APP_TARGET]: 'prod';
|
|
89
|
+
readonly adminApp: AdminApp;
|
|
90
|
+
/** Registry name (mirrors firebase-admin `App.name`). */
|
|
91
|
+
readonly name: string;
|
|
92
|
+
}
|
|
93
|
+
export type PyricAdminApp = SandboxAdminApp | ProdAdminApp;
|
|
94
|
+
export type InitializeAdminAppConfig = {
|
|
95
|
+
sandbox: Sandbox;
|
|
96
|
+
} | AppOptions;
|
|
97
|
+
/**
|
|
98
|
+
* App-lifecycle errors reuse `firebase-admin`'s own exported
|
|
99
|
+
* `FirebaseAppError` so the thrown error's class, `constructor.name`,
|
|
100
|
+
* `.code` (`app/no-app`, `app/duplicate-app`, `app/invalid-app-options`,
|
|
101
|
+
* `app/invalid-app-name`, `app/invalid-argument`), and message text match
|
|
102
|
+
* production byte-for-byte (oracle: `admin-app-*` observations assert
|
|
103
|
+
* `errorName: "FirebaseAppError"`). The published typings declare only the
|
|
104
|
+
* base `Error` constructor, but the real runtime class is
|
|
105
|
+
* `new FirebaseAppError(code, message)` where `code` becomes `app/<code>` —
|
|
106
|
+
* the typed cast recovers it.
|
|
107
|
+
*/
|
|
108
|
+
declare const AdminAppError: new (code: string, message: string) => Error & {
|
|
109
|
+
readonly code: string;
|
|
110
|
+
};
|
|
111
|
+
/** @deprecated Alias kept for pre-merge call sites; errors ARE `FirebaseAppError`. */
|
|
112
|
+
export declare const PyricAdminAppError: new (code: string, message: string) => Error & {
|
|
113
|
+
readonly code: string;
|
|
114
|
+
};
|
|
115
|
+
export type PyricAdminAppError = InstanceType<typeof AdminAppError>;
|
|
116
|
+
/**
|
|
117
|
+
* Initialize a Pyric admin app and register it under `name`
|
|
118
|
+
* (default `'[DEFAULT]'`), mirroring `firebase-admin/app.initializeApp`.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* // Ambient (zero pyric identifiers — environment decides)
|
|
123
|
+
* import { initializeApp } from 'pyric-admin/app';
|
|
124
|
+
* const app = initializeApp();
|
|
125
|
+
*
|
|
126
|
+
* // Prod-backed
|
|
127
|
+
* import { applicationDefault } from 'firebase-admin/app';
|
|
128
|
+
* const app = initializeApp({ credential: applicationDefault() });
|
|
129
|
+
*
|
|
130
|
+
* // Sandbox-backed
|
|
131
|
+
* import { initializeSandbox } from 'pyric/sandbox';
|
|
132
|
+
* const app = initializeApp({ sandbox: initializeSandbox() });
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export declare function initializeApp(config?: InitializeAdminAppConfig, name?: string): PyricAdminApp;
|
|
136
|
+
/**
|
|
137
|
+
* Return the registered app for `name` (default `'[DEFAULT]'`).
|
|
138
|
+
* Mirrors `firebase-admin/app.getApp` — including the exact `app/no-app`
|
|
139
|
+
* error text on a miss.
|
|
140
|
+
*/
|
|
141
|
+
export declare function getApp(name?: string): PyricAdminApp;
|
|
142
|
+
/**
|
|
143
|
+
* A copy of the list of all registered apps.
|
|
144
|
+
* Mirrors `firebase-admin/app.getApps`.
|
|
145
|
+
*/
|
|
146
|
+
export declare function getApps(): PyricAdminApp[];
|
|
147
|
+
/**
|
|
148
|
+
* Remove `app` from the registry, mirroring `firebase-admin/app.deleteApp`.
|
|
149
|
+
* Prod-backed apps also delete the underlying firebase-admin app (freeing
|
|
150
|
+
* its resources and its slot in firebase-admin's own registry, so the
|
|
151
|
+
* name can be re-initialized). Sandbox-backed apps only deregister — the
|
|
152
|
+
* `Sandbox` handle's lifetime belongs to its creator.
|
|
153
|
+
*/
|
|
154
|
+
export declare function deleteApp(app: PyricAdminApp): Promise<void>;
|
|
155
|
+
export declare function isSandboxAdminApp(app: PyricAdminApp): app is SandboxAdminApp;
|
|
156
|
+
export declare function isProdAdminApp(app: PyricAdminApp): app is ProdAdminApp;
|
|
157
|
+
export {};
|
|
158
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/app/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAEH,OAAO,EAIL,KAAK,OAAO,EACb,MAAM,eAAe,CAAC;AACvB,OAAO,EAIL,KAAK,GAAG,IAAI,QAAQ,EACpB,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAE5B;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,eAAuC,CAAC;AAErE,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,MAAM,CAAC;AAErD,yDAAyD;AACzD,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAE5C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG,YAAY,CAAC;AAE3D,MAAM,MAAM,wBAAwB,GAAG;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,UAAU,CAAC;AAEzE;;;;;;;;;;GAUG;AACH,QAAA,MAAM,aAAa,EAAkC,KACnD,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,KACZ,KAAK,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,sFAAsF;AACtF,eAAO,MAAM,kBAAkB,aALvB,MAAM,WACH,MAAM,KACZ,KAAK,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAGW,CAAC;AAChD,MAAM,MAAM,kBAAkB,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC;AA8BpE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAC3B,MAAM,CAAC,EAAE,wBAAwB,EACjC,IAAI,GAAE,MAAyB,GAC9B,aAAa,CA8Df;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,IAAI,GAAE,MAAyB,GAAG,aAAa,CAcrE;AAED;;;GAGG;AACH,wBAAgB,OAAO,IAAI,aAAa,EAAE,CAEzC;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAW3D;AA0FD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,GAAG,IAAI,eAAe,CAE5E;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,GAAG,IAAI,YAAY,CAEtE"}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pyric-admin/app` — Phase 3 initializeApp surface (ADR-001 D6) plus the
|
|
3
|
+
* default-app registry and AMBIENT INIT (adoption experience, layer 3).
|
|
4
|
+
*
|
|
5
|
+
* Mirror shape: at cutover this becomes `pyric-admin/app` and is the
|
|
6
|
+
* single entry point where the sandbox-vs-prod choice is made for the
|
|
7
|
+
* admin surface. Every `pyric-admin/*` subpath inspects the handle's
|
|
8
|
+
* brand for backend dispatch.
|
|
9
|
+
*
|
|
10
|
+
* Current shadow scope:
|
|
11
|
+
* - `initializeApp({ credential, ... })` — prod-backed (live;
|
|
12
|
+
* delegates to `firebase-admin/app.initializeApp`).
|
|
13
|
+
* - `initializeApp({ sandbox })` — sandbox-backed (live).
|
|
14
|
+
* - `initializeApp()` — BARE, zero pyric identifiers in app code. The
|
|
15
|
+
* environment decides the backend (see "Ambient init" below).
|
|
16
|
+
* - Per-subpath adapter dispatch via the brand: implemented in
|
|
17
|
+
* `pyric-admin/{auth,database,storage}` via structural
|
|
18
|
+
* checks on the `app` argument. Phase 4 commits use property
|
|
19
|
+
* probing today; cutover swaps to brand-symbol reads.
|
|
20
|
+
*
|
|
21
|
+
* ── Default-app registry ──────────────────────────────────────────────
|
|
22
|
+
*
|
|
23
|
+
* Mirrors `firebase-admin/app`'s lifecycle exactly (oracle:
|
|
24
|
+
* `firebase-admin/lib/app/lifecycle.js`):
|
|
25
|
+
*
|
|
26
|
+
* - `initializeApp(config?, name?)` registers under `'[DEFAULT]'` when
|
|
27
|
+
* unnamed; `getApp(name?)` / `getApps()` / `deleteApp(app)` read it.
|
|
28
|
+
* - Duplicate-name errors carry firebase-admin's codes and message
|
|
29
|
+
* text: `app/duplicate-app` for a config'd re-init with a different
|
|
30
|
+
* configuration, `app/invalid-app-options` for a bare-vs-config'd
|
|
31
|
+
* mismatch (firebase-admin's autoInit mismatch), `app/no-app` from
|
|
32
|
+
* `getApp` on a missing name, `app/invalid-app-name` for a
|
|
33
|
+
* non-string/empty name. Errors reuse firebase-admin's own
|
|
34
|
+
* `FirebaseAppError` class, so `instanceof`, `constructor.name`,
|
|
35
|
+
* `.code`, and message text match production byte-for-byte
|
|
36
|
+
* (oracle: `admin-app-*` observations).
|
|
37
|
+
* - Idempotency mirror: a bare `initializeApp()` repeated for the same
|
|
38
|
+
* name returns the existing auto-initialized app (firebase-admin's
|
|
39
|
+
* autoInit path); `initializeApp({ sandbox })` repeated with the
|
|
40
|
+
* SAME `Sandbox` reference returns the existing app (the reference
|
|
41
|
+
* is our deep-equal analog — sandboxes have identity, not value
|
|
42
|
+
* equality); prod re-inits delegate the equality decision to
|
|
43
|
+
* firebase-admin itself (deep-equal options → same app; credential /
|
|
44
|
+
* httpAgent present → `app/invalid-app-options`, exactly upstream).
|
|
45
|
+
*
|
|
46
|
+
* ── Ambient init (bare `initializeApp()`) ─────────────────────────────
|
|
47
|
+
*
|
|
48
|
+
* The user's server code contains ZERO pyric identifiers — bare
|
|
49
|
+
* `initializeApp()` + no-arg `getDatabase()` / `getAuth()`. The
|
|
50
|
+
* environment picks the backend:
|
|
51
|
+
*
|
|
52
|
+
* - `PYRIC_SANDBOX` unset → exactly firebase-admin's behavior:
|
|
53
|
+
* delegate to `firebase-admin/app.initializeApp()` (FIREBASE_CONFIG
|
|
54
|
+
* env + application-default credentials), register the prod arm.
|
|
55
|
+
* - `PYRIC_SANDBOX=remote` or `remote:<url>` → obtain a remote sandbox
|
|
56
|
+
* handle from the factory installed by `pyric-tools/register` at
|
|
57
|
+
* `globalThis[REMOTE_SANDBOX_FACTORY]`
|
|
58
|
+
* (`Symbol.for('pyric.remote.sandboxFactory')`) and register the
|
|
59
|
+
* sandbox arm. One activation line is logged to stderr. If the env
|
|
60
|
+
* is set but no factory is installed, throw with remediation (run
|
|
61
|
+
* under `pyric dev`, or add `--import pyric-tools/register` to
|
|
62
|
+
* NODE_OPTIONS).
|
|
63
|
+
* - Production guard: refuses to route to a sandbox when
|
|
64
|
+
* `NODE_ENV === 'production'`, unless `PYRIC_SANDBOX_FORCE=1`.
|
|
65
|
+
*
|
|
66
|
+
* Ambient resolution happens ONLY on the bare call — any explicit
|
|
67
|
+
* config (`{ sandbox }` or prod `AppOptions`, even `{}`) bypasses the
|
|
68
|
+
* env entirely, so pyric-aware code keeps full control.
|
|
69
|
+
*/
|
|
70
|
+
import { REMOTE_SANDBOX_FACTORY, } from 'pyric/sandbox';
|
|
71
|
+
import { initializeApp as initializeFirebaseAdminApp, deleteApp as deleteFirebaseAdminApp, FirebaseAppError, } from 'firebase-admin/app';
|
|
72
|
+
/**
|
|
73
|
+
* Brand on every PyricAdminApp. Matches the symbol used in
|
|
74
|
+
* `pyric/app` so a future shared `pyric/runtime` package can
|
|
75
|
+
* unify dispatch helpers across both packages.
|
|
76
|
+
*/
|
|
77
|
+
export const ADMIN_APP_TARGET = Symbol.for('pyric.admin.app.target');
|
|
78
|
+
/** firebase-admin's default app name — `'[DEFAULT]'`. */
|
|
79
|
+
export const DEFAULT_APP_NAME = '[DEFAULT]';
|
|
80
|
+
/**
|
|
81
|
+
* App-lifecycle errors reuse `firebase-admin`'s own exported
|
|
82
|
+
* `FirebaseAppError` so the thrown error's class, `constructor.name`,
|
|
83
|
+
* `.code` (`app/no-app`, `app/duplicate-app`, `app/invalid-app-options`,
|
|
84
|
+
* `app/invalid-app-name`, `app/invalid-argument`), and message text match
|
|
85
|
+
* production byte-for-byte (oracle: `admin-app-*` observations assert
|
|
86
|
+
* `errorName: "FirebaseAppError"`). The published typings declare only the
|
|
87
|
+
* base `Error` constructor, but the real runtime class is
|
|
88
|
+
* `new FirebaseAppError(code, message)` where `code` becomes `app/<code>` —
|
|
89
|
+
* the typed cast recovers it.
|
|
90
|
+
*/
|
|
91
|
+
const AdminAppError = FirebaseAppError;
|
|
92
|
+
/** @deprecated Alias kept for pre-merge call sites; errors ARE `FirebaseAppError`. */
|
|
93
|
+
export const PyricAdminAppError = AdminAppError;
|
|
94
|
+
// ─── Registry ───────────────────────────────────────────────────────────
|
|
95
|
+
//
|
|
96
|
+
// Module-level, matching firebase-admin's `defaultAppStore` (one store per
|
|
97
|
+
// module instance). `autoInitApps` tracks which registered apps came from a
|
|
98
|
+
// BARE `initializeApp()` — firebase-admin's `autoInit` flag — so the
|
|
99
|
+
// bare-vs-config'd mismatch throws exactly like upstream.
|
|
100
|
+
const appRegistry = new Map();
|
|
101
|
+
const autoInitApps = new WeakSet();
|
|
102
|
+
/** firebase-admin's app-name validation, verbatim message. */
|
|
103
|
+
function validateAppName(name) {
|
|
104
|
+
if (typeof name !== 'string' || name === '') {
|
|
105
|
+
throw new AdminAppError('invalid-app-name', `Invalid Firebase app name "${String(name)}" provided. App name must be a non-empty string.`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** firebase-admin's message for every same-name/different-config case. */
|
|
109
|
+
function alreadyExists(name, code) {
|
|
110
|
+
return new AdminAppError(code, `A Firebase app named "${name}" already exists with a different configuration.`);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Initialize a Pyric admin app and register it under `name`
|
|
114
|
+
* (default `'[DEFAULT]'`), mirroring `firebase-admin/app.initializeApp`.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* // Ambient (zero pyric identifiers — environment decides)
|
|
119
|
+
* import { initializeApp } from 'pyric-admin/app';
|
|
120
|
+
* const app = initializeApp();
|
|
121
|
+
*
|
|
122
|
+
* // Prod-backed
|
|
123
|
+
* import { applicationDefault } from 'firebase-admin/app';
|
|
124
|
+
* const app = initializeApp({ credential: applicationDefault() });
|
|
125
|
+
*
|
|
126
|
+
* // Sandbox-backed
|
|
127
|
+
* import { initializeSandbox } from 'pyric/sandbox';
|
|
128
|
+
* const app = initializeApp({ sandbox: initializeSandbox() });
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export function initializeApp(config, name = DEFAULT_APP_NAME) {
|
|
132
|
+
validateAppName(name);
|
|
133
|
+
const existing = appRegistry.get(name);
|
|
134
|
+
// ── Bare call: ambient resolution ────────────────────────────────────
|
|
135
|
+
if (config === undefined) {
|
|
136
|
+
if (existing !== undefined) {
|
|
137
|
+
// firebase-admin: two autoInit calls return the same app; an
|
|
138
|
+
// autoInit call against a config'd app is an options mismatch.
|
|
139
|
+
if (autoInitApps.has(existing))
|
|
140
|
+
return existing;
|
|
141
|
+
throw alreadyExists(name, 'invalid-app-options');
|
|
142
|
+
}
|
|
143
|
+
const app = initializeAmbientApp(name);
|
|
144
|
+
appRegistry.set(name, app);
|
|
145
|
+
autoInitApps.add(app);
|
|
146
|
+
return app;
|
|
147
|
+
}
|
|
148
|
+
// ── Explicit sandbox config ──────────────────────────────────────────
|
|
149
|
+
if (isSandboxConfig(config)) {
|
|
150
|
+
if (existing !== undefined) {
|
|
151
|
+
// Reference identity is the deep-equal analog for sandboxes.
|
|
152
|
+
if (!autoInitApps.has(existing) &&
|
|
153
|
+
existing[ADMIN_APP_TARGET] === 'sandbox' &&
|
|
154
|
+
existing.sandbox === config.sandbox) {
|
|
155
|
+
return existing;
|
|
156
|
+
}
|
|
157
|
+
throw alreadyExists(name, autoInitApps.has(existing) ? 'invalid-app-options' : 'duplicate-app');
|
|
158
|
+
}
|
|
159
|
+
const app = {
|
|
160
|
+
[ADMIN_APP_TARGET]: 'sandbox',
|
|
161
|
+
sandbox: config.sandbox,
|
|
162
|
+
name,
|
|
163
|
+
};
|
|
164
|
+
appRegistry.set(name, app);
|
|
165
|
+
return app;
|
|
166
|
+
}
|
|
167
|
+
// ── Explicit prod options ────────────────────────────────────────────
|
|
168
|
+
if (existing !== undefined) {
|
|
169
|
+
if (existing[ADMIN_APP_TARGET] === 'prod' && !autoInitApps.has(existing)) {
|
|
170
|
+
// Delegate the equality decision to firebase-admin itself: deep-
|
|
171
|
+
// equal options return the same AdminApp; credential / httpAgent
|
|
172
|
+
// re-inits throw app/invalid-app-options; different options throw
|
|
173
|
+
// app/duplicate-app — all with upstream's exact messages.
|
|
174
|
+
const adminApp = initializeFirebaseAdminApp(config, name);
|
|
175
|
+
if (adminApp === existing.adminApp)
|
|
176
|
+
return existing;
|
|
177
|
+
}
|
|
178
|
+
throw alreadyExists(name, autoInitApps.has(existing) ? 'invalid-app-options' : 'duplicate-app');
|
|
179
|
+
}
|
|
180
|
+
const adminApp = initializeFirebaseAdminApp(config, name);
|
|
181
|
+
const app = { [ADMIN_APP_TARGET]: 'prod', adminApp, name };
|
|
182
|
+
appRegistry.set(name, app);
|
|
183
|
+
return app;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Return the registered app for `name` (default `'[DEFAULT]'`).
|
|
187
|
+
* Mirrors `firebase-admin/app.getApp` — including the exact `app/no-app`
|
|
188
|
+
* error text on a miss.
|
|
189
|
+
*/
|
|
190
|
+
export function getApp(name = DEFAULT_APP_NAME) {
|
|
191
|
+
validateAppName(name);
|
|
192
|
+
const app = appRegistry.get(name);
|
|
193
|
+
if (app === undefined) {
|
|
194
|
+
const lead = name === DEFAULT_APP_NAME
|
|
195
|
+
? 'The default Firebase app does not exist. '
|
|
196
|
+
: `Firebase app named "${name}" does not exist. `;
|
|
197
|
+
throw new AdminAppError('no-app', lead + 'Make sure you call initializeApp() before using any of the Firebase services.');
|
|
198
|
+
}
|
|
199
|
+
return app;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* A copy of the list of all registered apps.
|
|
203
|
+
* Mirrors `firebase-admin/app.getApps`.
|
|
204
|
+
*/
|
|
205
|
+
export function getApps() {
|
|
206
|
+
return Array.from(appRegistry.values());
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Remove `app` from the registry, mirroring `firebase-admin/app.deleteApp`.
|
|
210
|
+
* Prod-backed apps also delete the underlying firebase-admin app (freeing
|
|
211
|
+
* its resources and its slot in firebase-admin's own registry, so the
|
|
212
|
+
* name can be re-initialized). Sandbox-backed apps only deregister — the
|
|
213
|
+
* `Sandbox` handle's lifetime belongs to its creator.
|
|
214
|
+
*/
|
|
215
|
+
export function deleteApp(app) {
|
|
216
|
+
if (typeof app !== 'object' || app === null || !(ADMIN_APP_TARGET in app)) {
|
|
217
|
+
throw new AdminAppError('invalid-argument', 'Invalid app argument.');
|
|
218
|
+
}
|
|
219
|
+
// Make sure the given app is actually registered (throws app/no-app).
|
|
220
|
+
const existing = getApp(app.name);
|
|
221
|
+
appRegistry.delete(existing.name);
|
|
222
|
+
if (existing[ADMIN_APP_TARGET] === 'prod') {
|
|
223
|
+
return deleteFirebaseAdminApp(existing.adminApp);
|
|
224
|
+
}
|
|
225
|
+
return Promise.resolve();
|
|
226
|
+
}
|
|
227
|
+
// ─── Ambient init ───────────────────────────────────────────────────────
|
|
228
|
+
/**
|
|
229
|
+
* Resolve a bare `initializeApp()` from the environment. See the
|
|
230
|
+
* module-level "Ambient init" docs for the full contract.
|
|
231
|
+
*/
|
|
232
|
+
function initializeAmbientApp(name) {
|
|
233
|
+
const env = process.env.PYRIC_SANDBOX;
|
|
234
|
+
if (env === undefined || env.trim() === '') {
|
|
235
|
+
// Unset → exactly today's prod behavior: firebase-admin's autoInit
|
|
236
|
+
// (FIREBASE_CONFIG env options + application-default credentials).
|
|
237
|
+
const adminApp = initializeFirebaseAdminApp(undefined, name);
|
|
238
|
+
return { [ADMIN_APP_TARGET]: 'prod', adminApp, name };
|
|
239
|
+
}
|
|
240
|
+
const opts = parsePyricSandboxEnv(env);
|
|
241
|
+
if (process.env.NODE_ENV === 'production' &&
|
|
242
|
+
process.env.PYRIC_SANDBOX_FORCE !== '1') {
|
|
243
|
+
throw new Error('pyric-admin: PYRIC_SANDBOX is set but NODE_ENV is "production" — ' +
|
|
244
|
+
'refusing to route firebase-admin to a development sandbox. ' +
|
|
245
|
+
'Unset PYRIC_SANDBOX in production, or set PYRIC_SANDBOX_FORCE=1 ' +
|
|
246
|
+
'if this routing is intentional.');
|
|
247
|
+
}
|
|
248
|
+
const factory = globalThis[REMOTE_SANDBOX_FACTORY];
|
|
249
|
+
if (typeof factory !== 'function') {
|
|
250
|
+
throw new Error(`pyric-admin: PYRIC_SANDBOX=${env} is set but no remote sandbox ` +
|
|
251
|
+
'factory is installed (globalThis[Symbol.for(' +
|
|
252
|
+
"'pyric.remote.sandboxFactory')] is absent). Run your server " +
|
|
253
|
+
'under `pyric dev`, or add `--import pyric-tools/register` to ' +
|
|
254
|
+
'NODE_OPTIONS.');
|
|
255
|
+
}
|
|
256
|
+
const sandbox = factory(opts);
|
|
257
|
+
process.stderr.write(`pyric: firebase-admin routed to sandbox${opts.url !== undefined ? ` at ${opts.url}` : ''}\n`);
|
|
258
|
+
return { [ADMIN_APP_TARGET]: 'sandbox', sandbox, name };
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Parse the `PYRIC_SANDBOX` activator value.
|
|
262
|
+
*
|
|
263
|
+
* - `remote` → `{}` (factory discovers the running `pyric dev`)
|
|
264
|
+
* - `remote:<url>` → `{ url }` (everything after the FIRST colon —
|
|
265
|
+
* URLs contain colons, so only the mode prefix is split off)
|
|
266
|
+
*
|
|
267
|
+
* Anything else throws: an unrecognized activator must never silently
|
|
268
|
+
* fall through to prod (mode-by-side-effect in the failure direction).
|
|
269
|
+
*/
|
|
270
|
+
function parsePyricSandboxEnv(env) {
|
|
271
|
+
const value = env.trim();
|
|
272
|
+
if (value === 'remote')
|
|
273
|
+
return {};
|
|
274
|
+
if (value.startsWith('remote:')) {
|
|
275
|
+
const url = value.slice('remote:'.length).trim();
|
|
276
|
+
if (url === '') {
|
|
277
|
+
throw new Error('pyric-admin: PYRIC_SANDBOX=remote: has an empty url. Use ' +
|
|
278
|
+
'PYRIC_SANDBOX=remote to auto-discover the running `pyric dev`, ' +
|
|
279
|
+
'or PYRIC_SANDBOX=remote:<url> with the host url.');
|
|
280
|
+
}
|
|
281
|
+
return { url };
|
|
282
|
+
}
|
|
283
|
+
throw new Error(`pyric-admin: unrecognized PYRIC_SANDBOX value "${env}". Supported ` +
|
|
284
|
+
'values: "remote" (auto-discover the running `pyric dev`) or ' +
|
|
285
|
+
'"remote:<url>".');
|
|
286
|
+
}
|
|
287
|
+
// ─── Guards ─────────────────────────────────────────────────────────────
|
|
288
|
+
function isSandboxConfig(config) {
|
|
289
|
+
return typeof config === 'object' && config !== null && 'sandbox' in config;
|
|
290
|
+
}
|
|
291
|
+
export function isSandboxAdminApp(app) {
|
|
292
|
+
return app[ADMIN_APP_TARGET] === 'sandbox';
|
|
293
|
+
}
|
|
294
|
+
export function isProdAdminApp(app) {
|
|
295
|
+
return app[ADMIN_APP_TARGET] === 'prod';
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/app/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pyric-admin/auth` — Phase 3 (branded-app dispatch) + Phase 4b
|
|
3
|
+
* (minimal in-memory sandbox backend).
|
|
4
|
+
*
|
|
5
|
+
* Mirrors `firebase-admin/auth` for a useful subset of methods. The
|
|
6
|
+
* `app` argument is the branded handle from `pyric-admin/app`
|
|
7
|
+
* ({@link PyricAdminApp}); the brand symbol picks the backend:
|
|
8
|
+
*
|
|
9
|
+
* - **Prod path** (`app[ADMIN_APP_TARGET] === 'prod'`) — delegates to
|
|
10
|
+
* `firebase-admin/auth`'s `getAuth(app.adminApp)` and returns the
|
|
11
|
+
* production `Auth` instance unchanged. Drop-in replacement for
|
|
12
|
+
* `firebase-admin/auth` so agents already calling it keep their
|
|
13
|
+
* exact behavior (tenants, providers, MFA, session cookies, action
|
|
14
|
+
* codes, all present on the returned handle).
|
|
15
|
+
*
|
|
16
|
+
* - **Sandbox path** (`app[ADMIN_APP_TARGET] === 'sandbox'`) — backed
|
|
17
|
+
* by an in-memory store keyed off `app.sandbox`. Implements the
|
|
18
|
+
* core user-management subset listed below. Tokens are NOT real
|
|
19
|
+
* JWTs — they're deterministic strings the same sandbox backend
|
|
20
|
+
* parses on `verifyIdToken`. The sandbox backend is intentionally
|
|
21
|
+
* minimal: enough to exercise agent code paths that use
|
|
22
|
+
* `createUser` / `getUser` / `setCustomUserClaims` /
|
|
23
|
+
* `createCustomToken` / `verifyIdToken`, not enough to model a
|
|
24
|
+
* real identity platform.
|
|
25
|
+
*
|
|
26
|
+
* - **Remote sandbox arm** — when the sandbox carries `pyric/sandbox`'s
|
|
27
|
+
* remote brand (a Node-side handle onto the browser-hosted
|
|
28
|
+
* SharedWorker sandbox from `pyric-tools`' `connectRemoteSandbox()`),
|
|
29
|
+
* user CRUD relays over the handle's worker channel instead of the
|
|
30
|
+
* in-memory store, so server-created users land in the ONE user pool
|
|
31
|
+
* the browser app + Studio share. See the "Remote sandbox arm"
|
|
32
|
+
* section below for the details (including the extra methods it
|
|
33
|
+
* supports: `updateUser`, `listUsers`).
|
|
34
|
+
*
|
|
35
|
+
* Surface scope on the sandbox backend (what works):
|
|
36
|
+
*
|
|
37
|
+
* - {@link getAuth}
|
|
38
|
+
* - `Auth.createCustomToken(uid, claims?)` — returns a deterministic
|
|
39
|
+
* `pyric-sandbox-custom:${uid}:${json}` string; no signing.
|
|
40
|
+
* - `Auth.verifyIdToken(token)` — parses tokens minted by
|
|
41
|
+
* `createCustomToken`; returns a {@link DecodedIdToken}-shaped
|
|
42
|
+
* object.
|
|
43
|
+
* - `Auth.createUser(properties)` — stores a {@link UserRecord}
|
|
44
|
+
* in an in-memory `Map<uid, UserRecord>`. Auto-generates a `uid`
|
|
45
|
+
* when one is not supplied.
|
|
46
|
+
* - `Auth.getUser(uid)` — Map lookup.
|
|
47
|
+
* - `Auth.getUserByEmail(email)` — linear scan.
|
|
48
|
+
* - `Auth.deleteUser(uid)` — Map delete.
|
|
49
|
+
* - `Auth.setCustomUserClaims(uid, claims)` — updates the stored
|
|
50
|
+
* `UserRecord.customClaims`.
|
|
51
|
+
*
|
|
52
|
+
* Sandbox backend — explicitly NOT implemented (throws
|
|
53
|
+
* `'not implemented in pyric-admin/auth sandbox backend'` so callers
|
|
54
|
+
* get a clear remediation message):
|
|
55
|
+
*
|
|
56
|
+
* - Tenancy: `tenantManager` and any per-tenant call.
|
|
57
|
+
* - Identity providers: `createProviderConfig`, `getProviderConfig`,
|
|
58
|
+
* `listProviderConfigs`, `updateProviderConfig`,
|
|
59
|
+
* `deleteProviderConfig`.
|
|
60
|
+
* - Multi-factor: `MultiFactorSettings` on UserRecord is always
|
|
61
|
+
* `undefined`; MFA enrollment is unsupported.
|
|
62
|
+
* - Session cookies: `createSessionCookie`, `verifySessionCookie`.
|
|
63
|
+
* - Action codes / password reset / email link sign-in:
|
|
64
|
+
* `generatePasswordResetLink`, `generateEmailVerificationLink`,
|
|
65
|
+
* `generateSignInWithEmailLink`, `generateVerifyAndChangeEmailLink`.
|
|
66
|
+
* - Bulk operations: `listUsers`, `getUsers`, `deleteUsers`,
|
|
67
|
+
* `importUsers`.
|
|
68
|
+
* - Revocation: `revokeRefreshTokens`.
|
|
69
|
+
* - `getUserByPhoneNumber`, `getUserByProviderUid`.
|
|
70
|
+
* - `updateUser` — not required by the brief.
|
|
71
|
+
*
|
|
72
|
+
* Real `firebase-admin` types are imported with `import type` so
|
|
73
|
+
* signatures stay identical to upstream — there's no shape drift to
|
|
74
|
+
* keep in sync.
|
|
75
|
+
*/
|
|
76
|
+
import type { Auth as AdminAuth, CreateRequest, DecodedIdToken, UserRecord } from 'firebase-admin/auth';
|
|
77
|
+
import { type PyricAdminApp } from '../app/index.js';
|
|
78
|
+
/**
|
|
79
|
+
* The handle returned by {@link getAuth}. On the prod path this is
|
|
80
|
+
* literally `firebase-admin/auth`'s `Auth` — every method, every
|
|
81
|
+
* tenant/project manager, every shape detail. On the sandbox path it is
|
|
82
|
+
* a structurally-compatible `Auth` whose method set is the explicit
|
|
83
|
+
* subset documented above; non-implemented methods throw a clear
|
|
84
|
+
* remediation error rather than silently returning bad data.
|
|
85
|
+
*/
|
|
86
|
+
export type Auth = AdminAuth;
|
|
87
|
+
export type { CreateRequest, DecodedIdToken, UserRecord };
|
|
88
|
+
/**
|
|
89
|
+
* Token format minted by `createCustomToken` and parsed by
|
|
90
|
+
* `verifyIdToken`. Exported as a constant so tests can lock the shape.
|
|
91
|
+
*
|
|
92
|
+
* Layout: `pyric-sandbox-custom:${uid}:${jsonClaims}`
|
|
93
|
+
*
|
|
94
|
+
* - The prefix lets `verifyIdToken` reject foreign tokens with a clear
|
|
95
|
+
* "not a sandbox token" error rather than NaN'ing out.
|
|
96
|
+
* - `uid` is colon-free per the auto-uid format above.
|
|
97
|
+
* - `jsonClaims` is the JSON-stringified developer claims (or `{}` when
|
|
98
|
+
* none were provided). Round-trips losslessly through `JSON.parse`.
|
|
99
|
+
*
|
|
100
|
+
* NOT a JWT. NOT signed. Do not use this token format to talk to any
|
|
101
|
+
* real Firebase service — it only round-trips through this same
|
|
102
|
+
* sandbox backend.
|
|
103
|
+
*/
|
|
104
|
+
export declare const SANDBOX_TOKEN_PREFIX = "pyric-sandbox-custom";
|
|
105
|
+
/**
|
|
106
|
+
* Return an `Auth` handle for the given app — or for the DEFAULT app when
|
|
107
|
+
* called with no argument (mirrors firebase-admin's no-arg `getAuth()`:
|
|
108
|
+
* resolves `'[DEFAULT]'` through `pyric-admin/app`'s registry and throws
|
|
109
|
+
* `app/no-app` when nothing has been initialized). Works on all three
|
|
110
|
+
* arms — local sandbox, remote sandbox, prod — since dispatch happens on
|
|
111
|
+
* whatever brand the resolved app carries.
|
|
112
|
+
*
|
|
113
|
+
* Dispatches on the brand symbol set by `pyric-admin/app`'s
|
|
114
|
+
* `initializeApp`:
|
|
115
|
+
* - `'prod'` → delegates to `firebase-admin/auth`'s `getAuth` against
|
|
116
|
+
* `app.adminApp`. The returned `Auth` is the production object,
|
|
117
|
+
* unmodified.
|
|
118
|
+
* - `'sandbox'` → returns an in-memory `Auth` handle backed by an
|
|
119
|
+
* {@link AuthStore} keyed off `app.sandbox`. Repeat calls for the
|
|
120
|
+
* same sandbox share the store, so writes are visible across
|
|
121
|
+
* handles (matches upstream `getAuth(app)` idempotency).
|
|
122
|
+
*
|
|
123
|
+
* The firebase-admin import is dynamic (`require` at call time) so the
|
|
124
|
+
* module's top-level evaluation stays cheap and so sandbox-only
|
|
125
|
+
* consumers do not pay the firebase-admin initialization cost. Mirrors
|
|
126
|
+
* how `pyric/firestore` defers its firebase init: backends are
|
|
127
|
+
* pay-for-what-you-use.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* import { initializeApp } from 'pyric-admin/app';
|
|
132
|
+
* import { initializeSandbox } from 'pyric/sandbox';
|
|
133
|
+
* import { getAuth } from 'pyric-admin/auth';
|
|
134
|
+
*
|
|
135
|
+
* const sandbox = initializeSandbox();
|
|
136
|
+
* const app = initializeApp({ sandbox });
|
|
137
|
+
* const auth = getAuth(app);
|
|
138
|
+
*
|
|
139
|
+
* const user = await auth.createUser({ uid: 'alice', email: 'a@e.com' });
|
|
140
|
+
* const token = await auth.createCustomToken(user.uid, { role: 'admin' });
|
|
141
|
+
* const decoded = await auth.verifyIdToken(token);
|
|
142
|
+
* console.log(decoded.uid, decoded.role); // 'alice' 'admin'
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export declare function getAuth(app?: PyricAdminApp): Auth;
|
|
146
|
+
//# sourceMappingURL=index.d.ts.map
|