pyric-admin 0.1.0-alpha.24 → 0.1.0-alpha.26

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.
Files changed (128) hide show
  1. package/README.md +1 -1
  2. package/dist/admin.d.ts +64 -12
  3. package/dist/admin.d.ts.map +1 -1
  4. package/dist/admin.js +53 -8
  5. package/dist/admin.js.map +1 -1
  6. package/dist/app/index.d.ts +14 -1
  7. package/dist/app/index.d.ts.map +1 -1
  8. package/dist/app/index.js +58 -16
  9. package/dist/app/index.js.map +1 -1
  10. package/dist/app-check/index.d.ts +13 -1
  11. package/dist/app-check/index.d.ts.map +1 -1
  12. package/dist/app-check/index.js +14 -9
  13. package/dist/app-check/index.js.map +1 -1
  14. package/dist/auth/index.d.ts.map +1 -1
  15. package/dist/auth/index.js +11 -0
  16. package/dist/auth/index.js.map +1 -1
  17. package/dist/data-connect/index.d.ts +23 -1
  18. package/dist/data-connect/index.d.ts.map +1 -1
  19. package/dist/data-connect/index.js +44 -7
  20. package/dist/data-connect/index.js.map +1 -1
  21. package/dist/database/index.d.ts +24 -19
  22. package/dist/database/index.d.ts.map +1 -1
  23. package/dist/database/index.js +133 -99
  24. package/dist/database/index.js.map +1 -1
  25. package/dist/deferred.d.ts +37 -13
  26. package/dist/deferred.d.ts.map +1 -1
  27. package/dist/deferred.js +57 -15
  28. package/dist/deferred.js.map +1 -1
  29. package/dist/eventarc/index.d.ts +13 -1
  30. package/dist/eventarc/index.d.ts.map +1 -1
  31. package/dist/eventarc/index.js +15 -9
  32. package/dist/eventarc/index.js.map +1 -1
  33. package/dist/extensions/index.d.ts +13 -1
  34. package/dist/extensions/index.d.ts.map +1 -1
  35. package/dist/extensions/index.js +15 -9
  36. package/dist/extensions/index.js.map +1 -1
  37. package/dist/firestore/index.d.ts +6 -0
  38. package/dist/firestore/index.d.ts.map +1 -1
  39. package/dist/firestore/index.js +25 -3
  40. package/dist/firestore/index.js.map +1 -1
  41. package/dist/functions/index.d.ts +13 -1
  42. package/dist/functions/index.d.ts.map +1 -1
  43. package/dist/functions/index.js +15 -9
  44. package/dist/functions/index.js.map +1 -1
  45. package/dist/installations/index.d.ts +13 -1
  46. package/dist/installations/index.d.ts.map +1 -1
  47. package/dist/installations/index.js +15 -9
  48. package/dist/installations/index.js.map +1 -1
  49. package/dist/instance-id/index.d.ts +13 -1
  50. package/dist/instance-id/index.d.ts.map +1 -1
  51. package/dist/instance-id/index.js +15 -9
  52. package/dist/instance-id/index.js.map +1 -1
  53. package/dist/machine-learning/index.d.ts +19 -1
  54. package/dist/machine-learning/index.d.ts.map +1 -1
  55. package/dist/machine-learning/index.js +23 -9
  56. package/dist/machine-learning/index.js.map +1 -1
  57. package/dist/messaging/index.d.ts.map +1 -1
  58. package/dist/messaging/index.js +1 -0
  59. package/dist/messaging/index.js.map +1 -1
  60. package/dist/phone-number-verification/index.d.ts +12 -1
  61. package/dist/phone-number-verification/index.d.ts.map +1 -1
  62. package/dist/phone-number-verification/index.js +14 -9
  63. package/dist/phone-number-verification/index.js.map +1 -1
  64. package/dist/project-management/index.d.ts +21 -1
  65. package/dist/project-management/index.d.ts.map +1 -1
  66. package/dist/project-management/index.js +23 -9
  67. package/dist/project-management/index.js.map +1 -1
  68. package/dist/remote-config/index.d.ts +21 -1
  69. package/dist/remote-config/index.d.ts.map +1 -1
  70. package/dist/remote-config/index.js +24 -9
  71. package/dist/remote-config/index.js.map +1 -1
  72. package/dist/security-rules/index.d.ts +23 -1
  73. package/dist/security-rules/index.d.ts.map +1 -1
  74. package/dist/security-rules/index.js +27 -9
  75. package/dist/security-rules/index.js.map +1 -1
  76. package/dist/storage/deferred-members.d.ts +14 -0
  77. package/dist/storage/deferred-members.d.ts.map +1 -0
  78. package/dist/storage/deferred-members.js +55 -0
  79. package/dist/storage/deferred-members.js.map +1 -0
  80. package/dist/storage/delete-files.d.ts +13 -0
  81. package/dist/storage/delete-files.d.ts.map +1 -0
  82. package/dist/storage/delete-files.js +39 -0
  83. package/dist/storage/delete-files.js.map +1 -0
  84. package/dist/storage/file-query.d.ts +52 -0
  85. package/dist/storage/file-query.d.ts.map +1 -0
  86. package/dist/storage/file-query.js +94 -0
  87. package/dist/storage/file-query.js.map +1 -0
  88. package/dist/storage/get-files.d.ts +15 -0
  89. package/dist/storage/get-files.d.ts.map +1 -0
  90. package/dist/storage/get-files.js +33 -0
  91. package/dist/storage/get-files.js.map +1 -0
  92. package/dist/storage/index.d.ts +77 -13
  93. package/dist/storage/index.d.ts.map +1 -1
  94. package/dist/storage/index.js +359 -160
  95. package/dist/storage/index.js.map +1 -1
  96. package/dist/storage/remote-names.d.ts +19 -0
  97. package/dist/storage/remote-names.d.ts.map +1 -0
  98. package/dist/storage/remote-names.js +26 -0
  99. package/dist/storage/remote-names.js.map +1 -0
  100. package/package.json +2 -2
  101. package/src/admin.ts +58 -8
  102. package/src/app/index.ts +71 -19
  103. package/src/app-check/index.ts +27 -9
  104. package/src/auth/auth.test.ts +27 -0
  105. package/src/auth/index.ts +8 -0
  106. package/src/data-connect/index.ts +70 -9
  107. package/src/database/database.test.ts +1 -0
  108. package/src/database/index.ts +182 -108
  109. package/src/deferred.ts +78 -15
  110. package/src/eventarc/index.ts +32 -9
  111. package/src/extensions/index.ts +32 -9
  112. package/src/firestore/index.ts +30 -3
  113. package/src/functions/index.ts +32 -9
  114. package/src/installations/index.ts +33 -10
  115. package/src/instance-id/index.ts +33 -9
  116. package/src/machine-learning/index.ts +46 -9
  117. package/src/messaging/index.ts +1 -0
  118. package/src/phone-number-verification/index.ts +31 -9
  119. package/src/project-management/index.ts +48 -8
  120. package/src/remote-config/index.ts +51 -8
  121. package/src/security-rules/index.ts +54 -9
  122. package/src/storage/deferred-members.ts +67 -0
  123. package/src/storage/delete-files.ts +40 -0
  124. package/src/storage/file-query.ts +135 -0
  125. package/src/storage/get-files.ts +40 -0
  126. package/src/storage/index.ts +484 -180
  127. package/src/storage/remote-names.ts +34 -0
  128. package/src/storage/storage.test.ts +78 -5
@@ -1,11 +1,48 @@
1
1
  /**
2
- * `pyric-admin/data-connect`: a deferred entry for `firebase-admin/data-connect`.
3
- *
4
- * The sandbox does not model Data Connect. Every value `firebase-admin/data-connect`
5
- * exports is present so imports link; using one throws `PyricDeferredApiError`.
6
- * See `../deferred.ts`.
2
+ * `pyric-admin/data-connect`: deferred service handle for `firebase-admin/data-connect`.
7
3
  */
8
- import { deferredAdminEntry } from '../deferred.js';
4
+ import { defineDeferredMembers } from 'pyric/app/internal';
5
+ import { DEFERRED_SERVICE_TOKEN, deferredAdminEntry, guardDeferredServiceApp, resolveActiveAdminApp, } from '../deferred.js';
9
6
  export { PyricDeferredApiError } from '../deferred.js';
10
- export const { DataConnect, getDataConnect, validateAdminArgs, } = deferredAdminEntry('data-connect');
7
+ export class DataConnect {
8
+ #app;
9
+ connectorConfig;
10
+ /** @internal */
11
+ constructor(...args) {
12
+ this.#app = guardDeferredServiceApp('data-connect', 'DataConnect', args);
13
+ this.connectorConfig = args[2];
14
+ }
15
+ get app() {
16
+ return this.#app;
17
+ }
18
+ }
19
+ defineDeferredMembers(DataConnect.prototype, 'pyric-admin/data-connect', 'DataConnect', {
20
+ async: [
21
+ 'executeGraphql',
22
+ 'executeGraphqlRead',
23
+ 'executeMutation',
24
+ 'executeQuery',
25
+ 'insert',
26
+ 'insertMany',
27
+ 'upsert',
28
+ 'upsertMany',
29
+ ],
30
+ });
31
+ const instancesByApp = new WeakMap();
32
+ export function getDataConnect(connectorConfig, app) {
33
+ const resolved = resolveActiveAdminApp(app);
34
+ let byId = instancesByApp.get(resolved);
35
+ if (!byId) {
36
+ byId = new Map();
37
+ instancesByApp.set(resolved, byId);
38
+ }
39
+ const id = `${connectorConfig.location}-${connectorConfig.serviceId}`;
40
+ const existing = byId.get(id);
41
+ if (existing !== undefined)
42
+ return existing;
43
+ const created = new DataConnect(DEFERRED_SERVICE_TOKEN, resolved, connectorConfig);
44
+ byId.set(id, created);
45
+ return created;
46
+ }
47
+ export const { validateAdminArgs } = deferredAdminEntry('data-connect');
11
48
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/data-connect/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,CAAC,MAAM,EACX,WAAW,EAAE,cAAc,EAAE,iBAAiB,GAC/C,GAAG,kBAAkB,CAAC,cAAc,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/data-connect/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAQvD,MAAM,OAAO,WAAW;IACb,IAAI,CAAgB;IACpB,eAAe,CAAkB;IAE1C,gBAAgB;IAChB,YAAY,GAAG,IAAe;QAC5B,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC,cAAc,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC;QACzE,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC,CAAoB,CAAC;IACpD,CAAC;IAED,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;CAUF;AAED,qBAAqB,CAAC,WAAW,CAAC,SAAS,EAAE,0BAA0B,EAAE,aAAa,EAAE;IACtF,KAAK,EAAE;QACL,gBAAgB;QAChB,oBAAoB;QACpB,iBAAiB;QACjB,cAAc;QACd,QAAQ;QACR,YAAY;QACZ,QAAQ;QACR,YAAY;KACb;CACF,CAAC,CAAC;AAEH,MAAM,cAAc,GAAG,IAAI,OAAO,EAA2C,CAAC;AAE9E,MAAM,UAAU,cAAc,CAAC,eAAgC,EAAE,GAAmB;IAClF,MAAM,QAAQ,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC;QACjB,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACrC,CAAC;IACD,MAAM,EAAE,GAAG,GAAG,eAAe,CAAC,QAAQ,IAAI,eAAe,CAAC,SAAS,EAAE,CAAC;IACtE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IAC5C,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,sBAAsB,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;IACnF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACtB,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,kBAAkB,CAAC,cAAc,CAAC,CAAC"}
@@ -40,11 +40,12 @@
40
40
  * backing rule state to expose.
41
41
  *
42
42
  * Sandbox state lives on the underlying `Sandbox` via a `WeakMap`
43
- * keyed by the `Sandbox` instance — `sandbox.reset()` wipes it via
44
- * the sandbox's `session_boundary` event with `phase: 'reset'`.
45
- * Successive `getDatabase(app)` calls for the same sandbox return
46
- * handles that share data (matches firebase-admin's
47
- * singleton-per-app semantics).
43
+ * keyed by the `Sandbox` instance, with one tree per RTDB instance
44
+ * (the instance a database URL names). `sandbox.reset()` wipes every
45
+ * tree via the sandbox's `session_boundary` event.
46
+ * Successive `getDatabase(app)` calls for the same sandbox and instance
47
+ * return the same handle (matches firebase-admin's one database per
48
+ * app and URL).
48
49
  *
49
50
  * - **Remote sandbox arm** (sandbox target whose `Sandbox` carries the
50
51
  * `pyric/sandbox` remote brand — a Node-side handle onto the
@@ -52,7 +53,9 @@
52
53
  * `connectRemoteSandbox()`) — every `Reference` data operation routes
53
54
  * through the handle's worker-relay channel (`rtdb.get/set/update/
54
55
  * remove/push` ops with `actAs: { mode: 'admin' }` pinned — firebase-
55
- * admin's rules-bypass semantics), NOT into the process-local tree:
56
+ * admin's rules-bypass semantics, and the instance the database
57
+ * URL names on every op and value subscription), NOT into the
58
+ * process-local tree:
56
59
  * a local tree on a remote handle would be private server-side data
57
60
  * the browser never sees. Differences from the local arm, both
58
61
  * deliberate upgrades:
@@ -71,6 +74,9 @@
71
74
  * sends it with the `rtdb.push` op (the worker-protocol contract).
72
75
  */
73
76
  import { type PyricAdminApp } from '../app/index.js';
77
+ /** `firebase-admin/database`'s `enableLogging`. The sandbox does not model
78
+ * the client logger, so it fails with an error naming it. */
79
+ export declare const enableLogging: import("pyric/app/internal").DeferredApi;
74
80
  /** Mirror of `firebase-admin/database`'s `ServerValue` constant and sentinel constructors. */
75
81
  export declare const ServerValue: {
76
82
  readonly TIMESTAMP: object;
@@ -131,27 +137,26 @@ type AdminDatabase = Database;
131
137
  *
132
138
  * Signature mirrors `firebase-admin/database`'s `getDatabase(app?)`.
133
139
  *
134
- * - `getDatabase()` — default database for the DEFAULT app (resolved
140
+ * - `getDatabase()`: default database for the DEFAULT app (resolved
135
141
  * through `pyric-admin/app`'s registry, exactly like firebase-admin's
136
142
  * no-arg `getDatabase()`; throws `app/no-app` when no default app has
137
143
  * been initialized). Works for local and remote sandbox apps.
138
- * - `getDatabase(app)` — default database for the app.
139
- * - `getDatabase(app, url)` — legacy Pyric-only compatibility form. New
140
- * code should use the upstream-shaped {@link getDatabaseWithUrl} export.
144
+ * - `getDatabase(app)`: the database the app's `databaseURL` option names,
145
+ * else the default instance.
146
+ * - `getDatabase(app, url)`: the database `url` names, as
147
+ * {@link getDatabaseWithUrl} selects it.
141
148
  *
142
- * The sandbox brand returns the local or remote `Database` backed by the
143
- * per-`Sandbox` state described in the module-level docs.
149
+ * Each RTDB instance has its own data: one `Database` per sandbox and
150
+ * instance, backed by the per-`Sandbox` state described in the module-level
151
+ * docs. A URL that does not parse as a database URL throws the SDK's error.
144
152
  */
145
- export declare function getDatabase(app?: PyricAdminApp, _url?: string): AdminDatabase;
153
+ export declare function getDatabase(app?: PyricAdminApp, url?: string): AdminDatabase;
146
154
  /**
147
- * Returns the {@link AdminDatabase} service selected by an upstream-shaped
148
- * database URL.
155
+ * Returns the {@link AdminDatabase} service for the RTDB instance `url` names.
149
156
  *
150
157
  * This is the exact `firebase-admin/database` argument order used by the
151
- * Firebase Functions SDK: `getDatabaseWithUrl(url, app?)`. The first Pyric
152
- * Functions slice has one shared RTDB instance, so the URL selects that
153
- * instance rather than creating a second sandbox database.
158
+ * Firebase Functions SDK: `getDatabaseWithUrl(url, app?)`.
154
159
  */
155
- export declare function getDatabaseWithUrl(_url: string, app?: PyricAdminApp): AdminDatabase;
160
+ export declare function getDatabaseWithUrl(url: string, app?: PyricAdminApp): AdminDatabase;
156
161
  export {};
157
162
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/database/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/database/index.ts"],"names":[],"mappings":"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"}
@@ -40,11 +40,12 @@
40
40
  * backing rule state to expose.
41
41
  *
42
42
  * Sandbox state lives on the underlying `Sandbox` via a `WeakMap`
43
- * keyed by the `Sandbox` instance — `sandbox.reset()` wipes it via
44
- * the sandbox's `session_boundary` event with `phase: 'reset'`.
45
- * Successive `getDatabase(app)` calls for the same sandbox return
46
- * handles that share data (matches firebase-admin's
47
- * singleton-per-app semantics).
43
+ * keyed by the `Sandbox` instance, with one tree per RTDB instance
44
+ * (the instance a database URL names). `sandbox.reset()` wipes every
45
+ * tree via the sandbox's `session_boundary` event.
46
+ * Successive `getDatabase(app)` calls for the same sandbox and instance
47
+ * return the same handle (matches firebase-admin's one database per
48
+ * app and URL).
48
49
  *
49
50
  * - **Remote sandbox arm** (sandbox target whose `Sandbox` carries the
50
51
  * `pyric/sandbox` remote brand — a Node-side handle onto the
@@ -52,7 +53,9 @@
52
53
  * `connectRemoteSandbox()`) — every `Reference` data operation routes
53
54
  * through the handle's worker-relay channel (`rtdb.get/set/update/
54
55
  * remove/push` ops with `actAs: { mode: 'admin' }` pinned — firebase-
55
- * admin's rules-bypass semantics), NOT into the process-local tree:
56
+ * admin's rules-bypass semantics, and the instance the database
57
+ * URL names on every op and value subscription), NOT into the
58
+ * process-local tree:
56
59
  * a local tree on a remote handle would be private server-side data
57
60
  * the browser never sees. Differences from the local arm, both
58
61
  * deliberate upgrades:
@@ -71,9 +74,15 @@
71
74
  * sends it with the `rtdb.push` op (the worker-protocol contract).
72
75
  */
73
76
  import { isRemoteSandbox, } from 'pyric/sandbox';
77
+ import { createDatabaseInstanceRegistry, isCustomDatabaseHost, isDefaultDatabaseInstance, parseDatabaseLocationUrl, parseDatabaseUrl, } from 'pyric/sandbox/internal';
74
78
  import { ADMIN_APP_TARGET, getApp, } from '../app/index.js';
75
79
  import { assertAdminAppActive } from '../app/lifecycle.js';
76
80
  import { increment, serverTimestamp } from 'pyric/database';
81
+ import { valueAtChildPath, valueChildren } from 'pyric/database/internal';
82
+ import { deferredExport } from 'pyric/app/internal';
83
+ /** `firebase-admin/database`'s `enableLogging`. The sandbox does not model
84
+ * the client logger, so it fails with an error naming it. */
85
+ export const enableLogging = deferredExport('pyric-admin/database', 'enableLogging');
77
86
  /** Mirror of `firebase-admin/database`'s `ServerValue` constant and sentinel constructors. */
78
87
  export const ServerValue = {
79
88
  TIMESTAMP: serverTimestamp(),
@@ -84,18 +93,20 @@ export const ServerValue = {
84
93
  *
85
94
  * Signature mirrors `firebase-admin/database`'s `getDatabase(app?)`.
86
95
  *
87
- * - `getDatabase()` — default database for the DEFAULT app (resolved
96
+ * - `getDatabase()`: default database for the DEFAULT app (resolved
88
97
  * through `pyric-admin/app`'s registry, exactly like firebase-admin's
89
98
  * no-arg `getDatabase()`; throws `app/no-app` when no default app has
90
99
  * been initialized). Works for local and remote sandbox apps.
91
- * - `getDatabase(app)` — default database for the app.
92
- * - `getDatabase(app, url)` — legacy Pyric-only compatibility form. New
93
- * code should use the upstream-shaped {@link getDatabaseWithUrl} export.
100
+ * - `getDatabase(app)`: the database the app's `databaseURL` option names,
101
+ * else the default instance.
102
+ * - `getDatabase(app, url)`: the database `url` names, as
103
+ * {@link getDatabaseWithUrl} selects it.
94
104
  *
95
- * The sandbox brand returns the local or remote `Database` backed by the
96
- * per-`Sandbox` state described in the module-level docs.
105
+ * Each RTDB instance has its own data: one `Database` per sandbox and
106
+ * instance, backed by the per-`Sandbox` state described in the module-level
107
+ * docs. A URL that does not parse as a database URL throws the SDK's error.
97
108
  */
98
- export function getDatabase(app, _url) {
109
+ export function getDatabase(app, url) {
99
110
  if (app === undefined) {
100
111
  // No-arg mirror of firebase-admin's `getDatabase()` — resolve the
101
112
  // '[DEFAULT]' app from the registry (throws app/no-app on a miss).
@@ -103,59 +114,86 @@ export function getDatabase(app, _url) {
103
114
  }
104
115
  assertAdminAppActive(app);
105
116
  if (app[ADMIN_APP_TARGET] === 'sandbox') {
106
- return getSandboxDatabase(app.sandbox);
117
+ return getSandboxDatabase(app.sandbox, adminDatabaseInstance(app, url));
107
118
  }
108
119
  throw new TypeError('pyric-admin/database: getDatabase expected a PyricAdminApp ' +
109
120
  '(initialize via `initializeApp` from pyric-admin/app).');
110
121
  }
111
122
  /**
112
- * Returns the {@link AdminDatabase} service selected by an upstream-shaped
113
- * database URL.
123
+ * Returns the {@link AdminDatabase} service for the RTDB instance `url` names.
114
124
  *
115
125
  * This is the exact `firebase-admin/database` argument order used by the
116
- * Firebase Functions SDK: `getDatabaseWithUrl(url, app?)`. The first Pyric
117
- * Functions slice has one shared RTDB instance, so the URL selects that
118
- * instance rather than creating a second sandbox database.
126
+ * Firebase Functions SDK: `getDatabaseWithUrl(url, app?)`.
119
127
  */
120
- export function getDatabaseWithUrl(_url, app) {
121
- return getDatabase(app);
128
+ export function getDatabaseWithUrl(url, app) {
129
+ if (typeof url !== 'string' || url === '') {
130
+ throw invalidDatabaseUrl();
131
+ }
132
+ return getDatabase(app, url);
133
+ }
134
+ /** firebase-admin's error for a missing or empty database URL. */
135
+ function invalidDatabaseUrl() {
136
+ return Object.assign(new Error('Database URL must be a valid, non-empty URL string.'), { code: 'database/invalid-argument' });
137
+ }
138
+ /**
139
+ * The instance an admin database targets: `url`, else the app's `databaseURL`
140
+ * option, parsed as the production SDK parses it. `undefined` is the default
141
+ * instance, which the instance named `<projectId>-default-rtdb` or, for a
142
+ * project created before about 2020, `<projectId>` also is when the app's
143
+ * options name the project.
144
+ */
145
+ function adminDatabaseInstance(app, url) {
146
+ if (url === '')
147
+ throw invalidDatabaseUrl();
148
+ const source = url ?? app.options.databaseURL;
149
+ if (source === undefined)
150
+ return undefined;
151
+ const instance = parseDatabaseUrl(source);
152
+ const projectId = app.options.projectId;
153
+ const isProjectDefault = isDefaultDatabaseInstance(instance.name, projectId);
154
+ return isProjectDefault ? undefined : instance;
122
155
  }
123
- /** One backend per `Sandbox`. Successive `getDatabase(app)` calls for
124
- * the same sandbox return handles that share data — matches
125
- * firebase-admin's singleton-per-app semantics. */
126
- const stateBySandbox = new WeakMap();
127
- function getOrCreateState(sandbox) {
128
- let state = stateBySandbox.get(sandbox);
129
- if (state !== undefined)
130
- return state;
131
- state = { root: {} };
132
- stateBySandbox.set(sandbox, state);
133
- // Wire `sandbox.reset()` → wipe the tree. `session_boundary` fires
134
- // before the env swap, so consumer code that observes a reset sees
156
+ /** One registry of instances per `Sandbox`. Successive `getDatabase(app)`
157
+ * calls for the same sandbox and instance return the same handle, and
158
+ * each instance keeps its own tree, as firebase-admin keeps one database
159
+ * per URL. */
160
+ const localInstancesBySandbox = new WeakMap();
161
+ function localInstances(sandbox) {
162
+ const existing = localInstancesBySandbox.get(sandbox);
163
+ if (existing !== undefined)
164
+ return existing;
165
+ const registry = createDatabaseInstanceRegistry({
166
+ create: () => ({ state: { root: {} } }),
167
+ });
168
+ localInstancesBySandbox.set(sandbox, registry);
169
+ // Wire `sandbox.reset()` → wipe every instance's tree. `session_boundary`
170
+ // fires before the env swap, so consumer code that observes a reset sees
135
171
  // the freshly-cleared tree on the next read. `dispose` also fires a
136
172
  // boundary; treat it the same (the sandbox is being torn down — any
137
173
  // in-flight handle on the tree gets an empty view).
138
174
  sandbox.onEvent((event) => {
139
175
  if (event.kind === 'session_boundary') {
140
- state.root = {};
176
+ for (const [, entry] of registry.entries())
177
+ entry.state.root = {};
141
178
  }
142
179
  });
143
- return state;
180
+ return registry;
144
181
  }
145
- /** Build (or reuse) the sandbox Database handle for `sandbox`.
182
+ /** Build (or reuse) the sandbox Database handle for `sandbox` and `instance`
183
+ * (`undefined` is the default instance).
146
184
  *
147
185
  * REMOTE handles dispatch here, BEFORE any local state is touched: a
148
186
  * remote sandbox must never get a `SandboxState` (a private local tree)
149
187
  * or a `sandbox.onEvent` wire-up (which throws on remote handles). */
150
- function getSandboxDatabase(sandbox) {
188
+ function getSandboxDatabase(sandbox, instance) {
151
189
  if (isRemoteSandbox(sandbox)) {
152
- return getRemoteDatabase(sandbox);
190
+ return getRemoteDatabase(sandbox, instance);
153
191
  }
154
- const state = getOrCreateState(sandbox);
155
- return buildSandboxDatabase(state);
156
- }
157
- function buildSandboxDatabase(state) {
158
- return buildDatabaseShell((db, path) => buildSandboxRef(state, db, path));
192
+ const registry = localInstances(sandbox);
193
+ const entry = registry.getOrCreate(registry.keyOf(instance));
194
+ const state = entry.state;
195
+ entry.db ??= buildDatabaseShell(instance, (handle, path) => buildSandboxRef(state, handle, path));
196
+ return entry.db;
159
197
  }
160
198
  /**
161
199
  * The `Database`-level shell shared by the local and remote sandbox arms —
@@ -163,17 +201,22 @@ function buildSandboxDatabase(state) {
163
201
  * modeled on either arm; connection toggles are no-ops (the sandbox IS the
164
202
  * local emulator).
165
203
  */
166
- function buildDatabaseShell(refFactory) {
204
+ function buildDatabaseShell(instance, refFactory) {
167
205
  const db = {
168
206
  ref(path) {
169
207
  return refFactory(db, path ?? '/');
170
208
  },
209
+ /** As the SDK parses it: the URL names a location, and for a
210
+ * `firebaseio.com` database its host must be this database's host. */
171
211
  refFromURL(url) {
172
- // Best-effort: strip the `https://<host>` prefix and treat the
173
- // remainder as a path. The sandbox has no notion of multi-database
174
- // hosts, so the host portion is ignored.
175
- const u = url.replace(/^https?:\/\/[^/]+/, '');
176
- return refFactory(db, u || '/');
212
+ const location = parseDatabaseLocationUrl(url);
213
+ if (instance !== undefined) {
214
+ const expectedHost = parseDatabaseLocationUrl(instance.url).host;
215
+ if (!isCustomDatabaseHost(expectedHost) && location.host !== expectedHost) {
216
+ throw new Error(`FIREBASE FATAL ERROR: refFromURL: Host name does not match the current database: (found ${location.host} but expected ${expectedHost}) `);
217
+ }
218
+ }
219
+ return refFactory(db, location.path);
177
220
  },
178
221
  // Admin-only metadata methods — not modeled in the sandbox. The
179
222
  // sandbox is rule-bypass by construction; surfacing rule JSON would
@@ -578,6 +621,9 @@ function buildSandboxSnap(refAt, path, val) {
578
621
  const segs = pathSegments(path);
579
622
  const key = segs.length === 0 ? null : segs[segs.length - 1];
580
623
  const exists = val !== null;
624
+ // An array value still has children: `val()` renders integer keys as an
625
+ // array, and the SDK iterates them as '0', '1', ... (#1110).
626
+ const children = valueChildren(val);
581
627
  const snap = {
582
628
  key,
583
629
  get ref() {
@@ -591,35 +637,21 @@ function buildSandboxSnap(refAt, path, val) {
591
637
  },
592
638
  child(p) {
593
639
  const childSegs = pathSegments(p);
594
- let cur = val;
595
- for (const s of childSegs) {
596
- if (cur === null || typeof cur !== 'object' || Array.isArray(cur)) {
597
- cur = null;
598
- break;
599
- }
600
- cur = cur[s] ?? null;
601
- }
602
- return buildSandboxSnap(refAt, joinPath([...segs, ...childSegs]), cur);
640
+ const childVal = valueAtChildPath(val, childSegs);
641
+ return buildSandboxSnap(refAt, joinPath([...segs, ...childSegs]), childVal);
603
642
  },
604
643
  hasChild(p) {
605
644
  return snap.child(p).exists();
606
645
  },
607
646
  hasChildren() {
608
- return (val !== null &&
609
- typeof val === 'object' &&
610
- !Array.isArray(val) &&
611
- Object.keys(val).length > 0);
647
+ return children.length > 0;
612
648
  },
613
649
  numChildren() {
614
- if (val === null || typeof val !== 'object' || Array.isArray(val))
615
- return 0;
616
- return Object.keys(val).length;
650
+ return children.length;
617
651
  },
618
652
  forEach(cb) {
619
- if (val === null || typeof val !== 'object' || Array.isArray(val))
620
- return false;
621
- for (const [k, v] of Object.entries(val)) {
622
- const childSnap = buildSandboxSnap(refAt, joinPath([...segs, k]), v);
653
+ for (const { key: childKey, value } of children) {
654
+ const childSnap = buildSandboxSnap(refAt, joinPath([...segs, childKey]), value);
623
655
  if (cb(childSnap) === true)
624
656
  return true;
625
657
  }
@@ -655,22 +687,35 @@ function buildSandboxSnap(refAt, path, val) {
655
687
  // `onEvent` throws on remote handles by design.
656
688
  /** firebase-admin's rules-bypass lens, pinned on every relayed operation. */
657
689
  const REMOTE_ADMIN_LENS = { mode: 'admin' };
658
- /** One `Database` per remote handle — successive `getDatabase(app)` calls
659
- * share the listener registry (matches the local arm's singleton-per-
660
- * sandbox semantics). Keyed off the handle object; the data itself lives
661
- * in the browser worker. */
662
- const remoteDbBySandbox = new WeakMap();
663
- function getRemoteDatabase(sandbox) {
664
- let db = remoteDbBySandbox.get(sandbox);
665
- if (db !== undefined)
666
- return db;
690
+ /** One `Database` per remote handle and instance. Successive
691
+ * `getDatabase(app)` calls for an instance share the listener registry
692
+ * (matches the local arm's singleton-per-instance semantics). Keyed off
693
+ * the handle object; the data itself lives in the host. */
694
+ const remoteDbsBySandbox = new WeakMap();
695
+ function getRemoteDatabase(sandbox, instance) {
696
+ let registry = remoteDbsBySandbox.get(sandbox);
697
+ if (registry === undefined) {
698
+ registry = createDatabaseInstanceRegistry({ create: () => ({}) });
699
+ remoteDbsBySandbox.set(sandbox, registry);
700
+ }
701
+ const entry = registry.getOrCreate(registry.keyOf(instance));
702
+ if (entry.db !== undefined)
703
+ return entry.db;
667
704
  const state = {
668
705
  channel: sandbox.channel,
706
+ instance: instance?.name,
669
707
  listeners: new Map(),
670
708
  };
671
- db = buildDatabaseShell((dbHandle, path) => buildRemoteRef(state, dbHandle, path));
672
- remoteDbBySandbox.set(sandbox, db);
673
- return db;
709
+ entry.db = buildDatabaseShell(instance, (dbHandle, path) => buildRemoteRef(state, dbHandle, path));
710
+ return entry.db;
711
+ }
712
+ /** Relay `op` on the state's instance, under firebase-admin's rules-bypass lens. */
713
+ function relayRtdbOp(state, op) {
714
+ return state.channel.op({ ...op, instance: state.instance, actAs: REMOTE_ADMIN_LENS });
715
+ }
716
+ /** Subscribe to the value at `path` on the state's instance, under the admin lens. */
717
+ function subscribeRtdbValue(state, path, onSnap, onError) {
718
+ return state.channel.subscribe({ target: { service: 'rtdb', instance: state.instance, path }, actAs: REMOTE_ADMIN_LENS }, onSnap, onError);
674
719
  }
675
720
  /**
676
721
  * Build a remote `Reference` at `path`. Same load-bearing surface as the
@@ -704,19 +749,17 @@ function buildRemoteRef(state, db, path) {
704
749
  // ─── Data-plane methods (relayed worker ops) ─────────────────────
705
750
  /** Set `value` at this path. `null` deletes. Relays `rtdb.set`. */
706
751
  async set(value) {
707
- await state.channel.op({
752
+ await relayRtdbOp(state, {
708
753
  method: 'rtdb.set',
709
754
  path: canonical,
710
755
  value: value ?? null,
711
- actAs: REMOTE_ADMIN_LENS,
712
756
  });
713
757
  },
714
758
  /** Read this path (`rtdb.get`). Resolves to a `DataSnapshot`. */
715
759
  async get() {
716
- const wire = (await state.channel.op({
760
+ const wire = (await relayRtdbOp(state, {
717
761
  method: 'rtdb.get',
718
762
  path: canonical,
719
- actAs: REMOTE_ADMIN_LENS,
720
763
  }));
721
764
  return snapFromWire(wire);
722
765
  },
@@ -730,7 +773,7 @@ function buildRemoteRef(state, db, path) {
730
773
  return new Promise((resolve, reject) => {
731
774
  let detach = null;
732
775
  let settled = false;
733
- detach = state.channel.subscribe({ target: { service: 'rtdb', path: canonical }, actAs: REMOTE_ADMIN_LENS }, (value) => {
776
+ detach = subscribeRtdbValue(state, canonical, (value) => {
734
777
  if (settled)
735
778
  return;
736
779
  settled = true;
@@ -756,19 +799,17 @@ function buildRemoteRef(state, db, path) {
756
799
  if (values === null || typeof values !== 'object') {
757
800
  throw new TypeError('pyric-admin/database sandbox: update expected an object.');
758
801
  }
759
- await state.channel.op({
802
+ await relayRtdbOp(state, {
760
803
  method: 'rtdb.update',
761
804
  path: canonical,
762
805
  values: values,
763
- actAs: REMOTE_ADMIN_LENS,
764
806
  });
765
807
  },
766
808
  /** Delete the subtree at this path (`rtdb.remove`). */
767
809
  async remove() {
768
- await state.channel.op({
810
+ await relayRtdbOp(state, {
769
811
  method: 'rtdb.remove',
770
812
  path: canonical,
771
- actAs: REMOTE_ADMIN_LENS,
772
813
  });
773
814
  },
774
815
  /**
@@ -785,14 +826,7 @@ function buildRemoteRef(state, db, path) {
785
826
  const childPath = joinPath([...segs, id]);
786
827
  const write = value === undefined
787
828
  ? Promise.resolve()
788
- : state.channel
789
- .op({
790
- method: 'rtdb.push',
791
- path: canonical,
792
- key: id,
793
- value,
794
- actAs: REMOTE_ADMIN_LENS,
795
- })
829
+ : relayRtdbOp(state, { method: 'rtdb.push', path: canonical, key: id, value })
796
830
  .then(() => undefined);
797
831
  // Surface completion without forcing the caller to await: `.then`'s
798
832
  // rejection handler also keeps a fire-and-forget push from becoming
@@ -836,7 +870,7 @@ function buildRemoteRef(state, db, path) {
836
870
  const cancelCallback = typeof cancelCallbackOrContext === 'function'
837
871
  ? cancelCallbackOrContext
838
872
  : undefined;
839
- const detach = state.channel.subscribe({ target: { service: 'rtdb', path: canonical }, actAs: REMOTE_ADMIN_LENS }, (value) => {
873
+ const detach = subscribeRtdbValue(state, canonical, (value) => {
840
874
  callback(snapFromWire(value));
841
875
  }, (err) => {
842
876
  detachListener(state, canonical, callback);