@dereekb/dbx-cli 13.36.0 → 13.38.0
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/eslint/package.json +3 -3
- package/firebase-api-manifest/main.js +191 -166
- package/firebase-api-manifest/package.json +3 -3
- package/firestore-query-manifest/main.js +2063 -0
- package/firestore-query-manifest/package.json +13 -0
- package/firestore-rules/src/firestore-rules-scan.d.ts +89 -0
- package/firestore-rules/src/index.d.ts +1 -0
- package/generate-firestore-indexes/main.js +2 -2
- package/generate-firestore-indexes/package.json +2 -2
- package/generate-mcp-manifest/main.js +1 -0
- package/generate-mcp-manifest/package.json +3 -3
- package/generate-route-manifest/package.json +2 -2
- package/index.cjs.js +12675 -6864
- package/index.esm.js +12620 -6868
- package/lint-cache/package.json +2 -2
- package/manifest-extract/index.cjs.js +4 -1
- package/manifest-extract/index.esm.js +4 -1
- package/manifest-extract/package.json +2 -2
- package/manifest-extract/src/lib/types.d.ts +6 -0
- package/package.json +11 -6
- package/route/package.json +7 -7
- package/src/lib/api/firestore-session.client.d.ts +54 -0
- package/src/lib/api/get-args.helper.d.ts +4 -1
- package/src/lib/api/get-many.command.d.ts +8 -3
- package/src/lib/api/get.command.d.ts +13 -5
- package/src/lib/api/index.d.ts +1 -0
- package/src/lib/config/env.d.ts +106 -0
- package/src/lib/config/firestore-session.cache.d.ts +86 -0
- package/src/lib/config/index.d.ts +1 -0
- package/src/lib/config/paths.d.ts +3 -1
- package/src/lib/context/cli.context.d.ts +87 -3
- package/src/lib/doctor/firestore-session.check.d.ts +113 -0
- package/src/lib/doctor/index.d.ts +1 -0
- package/src/lib/firestore/firestore-get.command.d.ts +28 -0
- package/src/lib/firestore/firestore-queries.command.d.ts +26 -0
- package/src/lib/firestore/firestore-query.command.d.ts +25 -0
- package/src/lib/firestore/firestore.accessor.d.ts +85 -0
- package/src/lib/firestore/firestore.collection.d.ts +44 -0
- package/src/lib/firestore/firestore.error.d.ts +14 -0
- package/src/lib/firestore/firestore.models.d.ts +223 -0
- package/src/lib/firestore/firestore.query-params.d.ts +34 -0
- package/src/lib/firestore/firestore.query.d.ts +79 -0
- package/src/lib/firestore/firestore.read.d.ts +180 -0
- package/src/lib/firestore/firestore.session.d.ts +97 -0
- package/src/lib/firestore/index.d.ts +13 -0
- package/src/lib/firestore/query-info-utils.d.ts +48 -0
- package/src/lib/firestore/query-registry.d.ts +32 -0
- package/src/lib/index.d.ts +3 -0
- package/src/lib/manifest/types.d.ts +117 -0
- package/src/lib/mcp-scan/manifest/dbx-docs-ui-examples-schema.d.ts +2 -2
- package/src/lib/mcp-scan/manifest/model-snapshot-fields-schema.d.ts +2 -2
- package/src/lib/mcp-scan/manifest/pipes-schema.d.ts +2 -2
- package/src/lib/mcp-scan/manifest/ui-components-schema.d.ts +2 -2
- package/src/lib/mcp-scan/manifest/utils-schema.d.ts +2 -2
- package/src/lib/mcp-scan/scan/dbx-docs-ui-examples-extract.d.ts +1 -1
- package/src/lib/mcp-scan/scan/extract-models/types.d.ts +5 -0
- package/src/lib/mcp-scan/scan/ui-components-extract.d.ts +1 -1
- package/src/lib/middleware/auth.middleware.d.ts +6 -0
- package/src/lib/runner/run.d.ts +28 -1
- package/src/lib/scan-helpers/emit-generated-ts.d.ts +76 -0
- package/src/lib/scan-helpers/exported-from-package.d.ts +29 -0
- package/src/lib/util/index.d.ts +1 -0
- package/src/lib/util/output.d.ts +10 -0
- package/src/lib/util/stdin.d.ts +39 -2
- package/src/lib/util/table.d.ts +40 -0
- package/test/index.cjs.js +4 -2
- package/test/index.esm.js +4 -2
- package/test/package.json +9 -9
- package/test/src/lib/cli-test.d.ts +12 -2
package/lint-cache/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-lint-cache",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.38.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"devDependencies": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"eslint": "10.4.0"
|
|
9
9
|
},
|
|
10
10
|
"peerDependencies": {
|
|
11
|
-
"@dereekb/util": "13.
|
|
11
|
+
"@dereekb/util": "13.38.0",
|
|
12
12
|
"yargs": "^18.0.0"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -828,6 +828,7 @@ function buildInterface(decl) {
|
|
|
828
828
|
var jsDocs = decl.getJsDocs();
|
|
829
829
|
var hasDbxModelTag = jsDocsHaveTag(jsDocs, 'dbxModel');
|
|
830
830
|
var dbxModelRead = readDbxModelReadTag(jsDocs);
|
|
831
|
+
var dbxModelServerOnly = jsDocsHaveTag(jsDocs, 'dbxModelServerOnly');
|
|
831
832
|
var mcpToolNameSegment = readMcpToolNameSegmentTag(jsDocs);
|
|
832
833
|
var extendsNames = decl.getExtends().map(dbxCli.resolveExtendsName);
|
|
833
834
|
var props = [];
|
|
@@ -873,7 +874,9 @@ function buildInterface(decl) {
|
|
|
873
874
|
props: props
|
|
874
875
|
}, dbxModelRead === undefined ? {} : {
|
|
875
876
|
dbxModelRead: dbxModelRead
|
|
876
|
-
},
|
|
877
|
+
}, dbxModelServerOnly ? {
|
|
878
|
+
dbxModelServerOnly: true
|
|
879
|
+
} : {}, mcpToolNameSegment === undefined ? {} : {
|
|
877
880
|
mcpToolNameSegment: mcpToolNameSegment
|
|
878
881
|
});
|
|
879
882
|
}
|
|
@@ -826,6 +826,7 @@ function buildInterface(decl) {
|
|
|
826
826
|
var jsDocs = decl.getJsDocs();
|
|
827
827
|
var hasDbxModelTag = jsDocsHaveTag(jsDocs, 'dbxModel');
|
|
828
828
|
var dbxModelRead = readDbxModelReadTag(jsDocs);
|
|
829
|
+
var dbxModelServerOnly = jsDocsHaveTag(jsDocs, 'dbxModelServerOnly');
|
|
829
830
|
var mcpToolNameSegment = readMcpToolNameSegmentTag(jsDocs);
|
|
830
831
|
var extendsNames = decl.getExtends().map(resolveExtendsName);
|
|
831
832
|
var props = [];
|
|
@@ -871,7 +872,9 @@ function buildInterface(decl) {
|
|
|
871
872
|
props: props
|
|
872
873
|
}, dbxModelRead === undefined ? {} : {
|
|
873
874
|
dbxModelRead: dbxModelRead
|
|
874
|
-
},
|
|
875
|
+
}, dbxModelServerOnly ? {
|
|
876
|
+
dbxModelServerOnly: true
|
|
877
|
+
} : {}, mcpToolNameSegment === undefined ? {} : {
|
|
875
878
|
mcpToolNameSegment: mcpToolNameSegment
|
|
876
879
|
});
|
|
877
880
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli/manifest-extract",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.38.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@dereekb/date": "13.15.0",
|
|
7
|
-
"@dereekb/dbx-cli": "13.
|
|
7
|
+
"@dereekb/dbx-cli": "13.38.0",
|
|
8
8
|
"@dereekb/firebase": "13.15.0",
|
|
9
9
|
"@dereekb/model": "13.15.0",
|
|
10
10
|
"@dereekb/nestjs": "13.15.0",
|
|
@@ -156,6 +156,12 @@ export interface ModelExtractionInterface {
|
|
|
156
156
|
* / `permissions`). Absent when the interface omits the tag or declares an invalid value.
|
|
157
157
|
*/
|
|
158
158
|
readonly dbxModelRead?: 'system' | 'owner' | 'admin-only' | 'permissions';
|
|
159
|
+
/**
|
|
160
|
+
* True when the interface carries `@dbxModelServerOnly` — the model has no client read grant in
|
|
161
|
+
* `firestore.rules` and must be refused on the model API too. See
|
|
162
|
+
* `FirebaseModelServiceConfig.serverOnly`, the runtime half of the same declaration.
|
|
163
|
+
*/
|
|
164
|
+
readonly dbxModelServerOnly?: boolean;
|
|
159
165
|
/**
|
|
160
166
|
* Per-model MCP tool-name segment from `@dbxModelMcpToolNameSegment <segment>`. Replaces the model
|
|
161
167
|
* type in generated tool names (e.g. the collection prefix). Absent when the tag is omitted or invalid.
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.38.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"bin": {
|
|
7
7
|
"dbx-cli-generate-firebase-api-manifest": "firebase-api-manifest/main.js",
|
|
8
8
|
"dbx-cli-generate-firestore-indexes": "generate-firestore-indexes/main.js",
|
|
9
|
+
"dbx-cli-generate-firestore-query-manifest": "firestore-query-manifest/main.js",
|
|
9
10
|
"dbx-cli-generate-mcp-manifest": "generate-mcp-manifest/main.js",
|
|
10
11
|
"dbx-cli-generate-route-manifest": "generate-route-manifest/main.js",
|
|
11
12
|
"dbx-cli-lint-cache": "lint-cache/main.js"
|
|
@@ -20,6 +21,9 @@
|
|
|
20
21
|
"./firebase-api-manifest": {
|
|
21
22
|
"default": "./firebase-api-manifest/main.js"
|
|
22
23
|
},
|
|
24
|
+
"./firestore-query-manifest": {
|
|
25
|
+
"default": "./firestore-query-manifest/main.js"
|
|
26
|
+
},
|
|
23
27
|
"./generate-firestore-indexes": {
|
|
24
28
|
"default": "./generate-firestore-indexes/main.js"
|
|
25
29
|
},
|
|
@@ -66,13 +70,14 @@
|
|
|
66
70
|
}
|
|
67
71
|
},
|
|
68
72
|
"peerDependencies": {
|
|
69
|
-
"@dereekb/date": "13.
|
|
70
|
-
"@dereekb/firebase": "13.
|
|
71
|
-
"@dereekb/model": "13.
|
|
72
|
-
"@dereekb/nestjs": "13.
|
|
73
|
-
"@dereekb/util": "13.
|
|
73
|
+
"@dereekb/date": "13.38.0",
|
|
74
|
+
"@dereekb/firebase": "13.38.0",
|
|
75
|
+
"@dereekb/model": "13.38.0",
|
|
76
|
+
"@dereekb/nestjs": "13.38.0",
|
|
77
|
+
"@dereekb/util": "13.38.0",
|
|
74
78
|
"@nestjs/common": "^11.1.19",
|
|
75
79
|
"arktype": "^2.2.0",
|
|
80
|
+
"firebase": "^12.12.1",
|
|
76
81
|
"jiti": "2.6.1",
|
|
77
82
|
"prettier": "3.8.3",
|
|
78
83
|
"ts-morph": "^21.0.0",
|
package/route/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli/route",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.38.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"peerDependencies": {
|
|
6
|
-
"@dereekb/date": "13.
|
|
7
|
-
"@dereekb/dbx-cli": "13.
|
|
8
|
-
"@dereekb/firebase": "13.
|
|
9
|
-
"@dereekb/model": "13.
|
|
10
|
-
"@dereekb/nestjs": "13.
|
|
11
|
-
"@dereekb/util": "13.
|
|
6
|
+
"@dereekb/date": "13.38.0",
|
|
7
|
+
"@dereekb/dbx-cli": "13.38.0",
|
|
8
|
+
"@dereekb/firebase": "13.38.0",
|
|
9
|
+
"@dereekb/model": "13.38.0",
|
|
10
|
+
"@dereekb/nestjs": "13.38.0",
|
|
11
|
+
"@dereekb/util": "13.38.0"
|
|
12
12
|
},
|
|
13
13
|
"exports": {
|
|
14
14
|
"./package.json": "./package.json",
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path (relative to the API base URL) of the direct-Firestore session endpoint served by
|
|
3
|
+
* `@dereekb/firebase-server`'s `SessionApiController`.
|
|
4
|
+
*
|
|
5
|
+
* Duplicated here rather than imported so `dbx-cli` keeps no dependency on the server package —
|
|
6
|
+
* the same arrangement `CALL_MODEL_API_PATH` uses.
|
|
7
|
+
*/
|
|
8
|
+
export declare const FIRESTORE_SESSION_API_PATH = "/session/firestore";
|
|
9
|
+
/**
|
|
10
|
+
* The credential bundle `GET <apiBaseUrl>/session/firestore` returns.
|
|
11
|
+
*/
|
|
12
|
+
export interface CliFirestoreSession {
|
|
13
|
+
/**
|
|
14
|
+
* The uid the session was minted for.
|
|
15
|
+
*/
|
|
16
|
+
readonly uid: string;
|
|
17
|
+
/**
|
|
18
|
+
* A Firebase Auth custom token to exchange via `signInWithCustomToken`.
|
|
19
|
+
*/
|
|
20
|
+
readonly customToken: string;
|
|
21
|
+
/**
|
|
22
|
+
* An App Check attestation minted server-side for the project's registered web app. Absent when
|
|
23
|
+
* the API has no `appCheckAppId` configured (a project that does not enforce App Check).
|
|
24
|
+
*/
|
|
25
|
+
readonly appCheckToken?: string;
|
|
26
|
+
/**
|
|
27
|
+
* ISO timestamp at which the session's shortest-lived credential expires.
|
|
28
|
+
*/
|
|
29
|
+
readonly expiresAt: string;
|
|
30
|
+
}
|
|
31
|
+
export interface FetchFirestoreSessionInput {
|
|
32
|
+
/**
|
|
33
|
+
* The API base URL — typically `<host>/<project>/us-central1/api` or `https://<domain>/api`.
|
|
34
|
+
*
|
|
35
|
+
* The `/session/firestore` path is appended automatically.
|
|
36
|
+
*/
|
|
37
|
+
readonly apiBaseUrl: string;
|
|
38
|
+
readonly accessToken: string;
|
|
39
|
+
/**
|
|
40
|
+
* Custom fetch implementation for tests.
|
|
41
|
+
*/
|
|
42
|
+
readonly fetcher?: typeof fetch;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Fetches a direct-Firestore session from the API with the cached Bearer access token.
|
|
46
|
+
*
|
|
47
|
+
* The endpoint is admin-only and additionally gated on the `session.firestore` OIDC scope, so a 403
|
|
48
|
+
* here usually means the logged-in user is not an admin or logged in without that scope.
|
|
49
|
+
*
|
|
50
|
+
* @param input - The API target, access token, and optional fetch override.
|
|
51
|
+
* @returns The parsed {@link CliFirestoreSession}.
|
|
52
|
+
* @throws {CliError} When the endpoint answers non-2xx or returns an unusable body.
|
|
53
|
+
*/
|
|
54
|
+
export declare function fetchFirestoreSession(input: FetchFirestoreSessionInput): Promise<CliFirestoreSession>;
|
|
@@ -49,7 +49,10 @@ export declare function parseGetArgs(input: {
|
|
|
49
49
|
* `modelType` or a {@link CliError} is thrown (the backend route is single-modelType per call).
|
|
50
50
|
* 2. Otherwise `firstArg` is treated as the explicit `modelType` and `rest` are the keys.
|
|
51
51
|
*
|
|
52
|
-
* Always rejects empty key
|
|
52
|
+
* Always rejects an empty key list. There is deliberately NO length cap: `get-many -` reads its keys
|
|
53
|
+
* from stdin and is unbounded, and both read paths batch for themselves —
|
|
54
|
+
* `getMultipleModelsOverHttpChunked` chunks at 50 per request, and `getMultipleModelsOverFirestore`
|
|
55
|
+
* holds a sliding window of 50 in-flight reads.
|
|
53
56
|
*
|
|
54
57
|
* @param input - Positionals captured by yargs plus the optional model manifest.
|
|
55
58
|
* @param input.firstArg - The first positional from yargs.
|
|
@@ -4,12 +4,17 @@ import type { CommandModule } from 'yargs';
|
|
|
4
4
|
*
|
|
5
5
|
* Batch-reads Firestore documents by key. The first positional can either be an explicit
|
|
6
6
|
* modelType (followed by ≥1 keys) or a full key (followed by additional keys whose prefixes
|
|
7
|
-
* must resolve to the same modelType). Beyond 50 keys the request is automatically chunked
|
|
8
|
-
* via `context.getMultipleModels
|
|
7
|
+
* must resolve to the same modelType). Beyond 50 keys the API request is automatically chunked
|
|
8
|
+
* via `context.getMultipleModels`; the direct path has no per-request cap and issues the reads
|
|
9
|
+
* concurrently.
|
|
9
10
|
*
|
|
10
11
|
* Stdin: pass `-` as the only positional to read whitespace-separated keys from stdin
|
|
11
12
|
* (e.g. `cat keys.txt | <cli> get-many -`).
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
+
* Transport is chosen by `--via` (see {@link CLI_READ_VIA_EPILOGUE}). Both paths emit the identical
|
|
15
|
+
* `{ results, errors }` envelope, so `--via` is observable only through `meta.source`.
|
|
16
|
+
*
|
|
17
|
+
* Backends: `POST <apiBaseUrl>/model/<modelType>/get` with body `{ keys }`
|
|
18
|
+
* (ModelApiController.getMany), or direct Firestore reads through the app's security rules.
|
|
14
19
|
*/
|
|
15
20
|
export declare const GET_MANY_COMMAND: CommandModule;
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import type { CommandModule } from 'yargs';
|
|
2
|
+
/**
|
|
3
|
+
* Epilogue shared by the routed read commands, documenting what `--via` does and does not do.
|
|
4
|
+
*/
|
|
5
|
+
export declare const CLI_READ_VIA_EPILOGUE: string;
|
|
2
6
|
/**
|
|
3
7
|
* Top-level `get <modelOrKey> [key]` command.
|
|
4
8
|
*
|
|
5
|
-
* Reads a single Firestore document
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
+
* Reads a single Firestore document. The `model` arg is optional: when only one positional is
|
|
10
|
+
* supplied, the CLI resolves the modelType from the key's leading collection-name prefix via
|
|
11
|
+
* {@link decodeFirestoreModelKey}. The two-positional form passes the explicit `modelType` straight
|
|
12
|
+
* through.
|
|
13
|
+
*
|
|
14
|
+
* Transport is chosen by `--via` (see {@link CLI_READ_VIA_EPILOGUE}). Both paths emit the identical
|
|
15
|
+
* `{ key, data }` envelope, so `--via` is observable only through `meta.source`.
|
|
9
16
|
*
|
|
10
|
-
*
|
|
17
|
+
* Backends: `GET <apiBaseUrl>/model/<modelType>/get?key=<key>` (ModelApiController.getOne), or a
|
|
18
|
+
* direct Firestore read through the app's security rules as the authenticated user.
|
|
11
19
|
*/
|
|
12
20
|
export declare const GET_COMMAND: CommandModule;
|
package/src/lib/api/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './call-model.client';
|
|
|
2
2
|
export * from './call-model.command.factory';
|
|
3
3
|
export * from './call.passthrough.command';
|
|
4
4
|
export * from './expand-keys';
|
|
5
|
+
export * from './firestore-session.client';
|
|
5
6
|
export * from './get-args.helper';
|
|
6
7
|
export * from './get.command';
|
|
7
8
|
export * from './get-many.command';
|
package/src/lib/config/env.d.ts
CHANGED
|
@@ -102,6 +102,97 @@ export interface MergeCliEnvWithDefaultInput {
|
|
|
102
102
|
* @returns The merged {@link CliEnvConfig}, or `undefined` when both inputs are empty.
|
|
103
103
|
*/
|
|
104
104
|
export declare function mergeCliEnvWithDefault(input: MergeCliEnvWithDefaultInput): Maybe<CliEnvConfig>;
|
|
105
|
+
/**
|
|
106
|
+
* Merges a stored Firebase client config on top of a default one, field by field, so an env can
|
|
107
|
+
* override just the `projectId` of a registered default without restating the whole block.
|
|
108
|
+
*
|
|
109
|
+
* @param env - The user's persisted Firebase config, if any.
|
|
110
|
+
* @param defaultEnv - The registered default's Firebase config, if any.
|
|
111
|
+
* @returns The merged config, or `undefined` when neither side supplies one.
|
|
112
|
+
*/
|
|
113
|
+
export declare function mergeCliFirebaseConfig(env: Maybe<CliFirebaseConfig>, defaultEnv: Maybe<CliFirebaseConfig>): CliFirebaseConfig | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* Local Firebase emulator targets for a CLI env.
|
|
116
|
+
*
|
|
117
|
+
* Mirrors the semantics of `DbxFirebaseEmulatorsConfig` in `@dereekb/dbx-firebase` (whose parse
|
|
118
|
+
* helper is Angular-bound and not reusable here): the presence of this object means "use emulators"
|
|
119
|
+
* unless {@link useEmulators} is explicitly `false`.
|
|
120
|
+
*
|
|
121
|
+
* App Check is auto-disabled whenever emulators are in use — the emulators do not verify
|
|
122
|
+
* attestations, and `initializeAppCheck` against a fake project only gets in the way.
|
|
123
|
+
*/
|
|
124
|
+
export interface CliFirebaseEmulatorsConfig {
|
|
125
|
+
/**
|
|
126
|
+
* Set `false` to keep the emulator targets configured but inactive. Defaults to `true`.
|
|
127
|
+
*/
|
|
128
|
+
readonly useEmulators?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Host the emulators are reachable at. Defaults to {@link DEFAULT_CLI_FIREBASE_EMULATOR_HOST}.
|
|
131
|
+
*/
|
|
132
|
+
readonly host?: string;
|
|
133
|
+
/**
|
|
134
|
+
* Port of the Auth emulator. When unset, Auth is not redirected to an emulator.
|
|
135
|
+
*/
|
|
136
|
+
readonly authPort?: number;
|
|
137
|
+
/**
|
|
138
|
+
* Port of the Firestore emulator. When unset, Firestore is not redirected to an emulator.
|
|
139
|
+
*/
|
|
140
|
+
readonly firestorePort?: number;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Firebase client-SDK configuration for a CLI env, used only by the direct-Firestore session
|
|
144
|
+
* (`CliContext.getFirestoreContext`). Everything else the CLI does goes over the model HTTP API and
|
|
145
|
+
* needs none of this.
|
|
146
|
+
*
|
|
147
|
+
* These are the same public values the app's browser client initializes with — copy them from the
|
|
148
|
+
* target app's environment file. `appId` in particular must be the registered **web** app, since the
|
|
149
|
+
* server mints its App Check attestation for that app.
|
|
150
|
+
*/
|
|
151
|
+
export interface CliFirebaseConfig {
|
|
152
|
+
/**
|
|
153
|
+
* The Firebase web API key.
|
|
154
|
+
*/
|
|
155
|
+
readonly apiKey?: string;
|
|
156
|
+
/**
|
|
157
|
+
* The project's auth domain (e.g. `my-project.firebaseapp.com`).
|
|
158
|
+
*/
|
|
159
|
+
readonly authDomain?: string;
|
|
160
|
+
/**
|
|
161
|
+
* The Firebase project id.
|
|
162
|
+
*/
|
|
163
|
+
readonly projectId?: string;
|
|
164
|
+
/**
|
|
165
|
+
* The registered **web** app id (e.g. `1:1234567890:web:abcdef`).
|
|
166
|
+
*/
|
|
167
|
+
readonly appId?: string;
|
|
168
|
+
/**
|
|
169
|
+
* Optional emulator targets for local development.
|
|
170
|
+
*/
|
|
171
|
+
readonly emulators?: CliFirebaseEmulatorsConfig;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Default host used for Firebase emulator connections when a {@link CliFirebaseEmulatorsConfig}
|
|
175
|
+
* omits one.
|
|
176
|
+
*/
|
|
177
|
+
export declare const DEFAULT_CLI_FIREBASE_EMULATOR_HOST = "localhost";
|
|
178
|
+
/**
|
|
179
|
+
* Returns true when the env carries the minimum Firebase client config needed to open a direct
|
|
180
|
+
* Firestore session.
|
|
181
|
+
*
|
|
182
|
+
* Deliberately separate from {@link isCliEnvConfigComplete}: the Firebase config is optional, and
|
|
183
|
+
* folding it into the general completeness check would break every CLI that only uses the model API.
|
|
184
|
+
*
|
|
185
|
+
* @param firebase - The env's Firebase client config, if any.
|
|
186
|
+
* @returns `true` when `apiKey`, `projectId`, and `appId` are all present and non-empty.
|
|
187
|
+
*/
|
|
188
|
+
export declare function isCliFirebaseConfigComplete(firebase: Maybe<CliFirebaseConfig>): firebase is Required<Pick<CliFirebaseConfig, 'apiKey' | 'projectId' | 'appId'>> & CliFirebaseConfig;
|
|
189
|
+
/**
|
|
190
|
+
* Returns true when the env's emulator config is present and active.
|
|
191
|
+
*
|
|
192
|
+
* @param firebase - The env's Firebase client config, if any.
|
|
193
|
+
* @returns `true` when emulators are configured and not explicitly disabled.
|
|
194
|
+
*/
|
|
195
|
+
export declare function cliFirebaseEmulatorsInUse(firebase: Maybe<CliFirebaseConfig>): boolean;
|
|
105
196
|
/**
|
|
106
197
|
* Environment-targeting config for a CLI invocation.
|
|
107
198
|
*
|
|
@@ -154,6 +245,12 @@ export interface CliEnvConfig {
|
|
|
154
245
|
* Space-separated OAuth scopes to request. Defaults to {@link DEFAULT_CLI_OIDC_SCOPES}.
|
|
155
246
|
*/
|
|
156
247
|
readonly scopes?: string;
|
|
248
|
+
/**
|
|
249
|
+
* Optional Firebase client config enabling the direct-Firestore session
|
|
250
|
+
* (`CliContext.getFirestoreContext()`). Optional by design — a CLI that only calls the model API
|
|
251
|
+
* never needs it, and requiring it would break every existing consumer.
|
|
252
|
+
*/
|
|
253
|
+
readonly firebase?: CliFirebaseConfig;
|
|
157
254
|
}
|
|
158
255
|
/**
|
|
159
256
|
* Resolves the active env name from a flag, env-var, or the persisted config default.
|
|
@@ -189,6 +286,15 @@ export declare function resolveActiveEnvName(input: ResolveActiveEnvInput): Mayb
|
|
|
189
286
|
* - `DEMO_CLI_CLIENT_SECRET`
|
|
190
287
|
* - `DEMO_CLI_REDIRECT_URI`
|
|
191
288
|
* - `DEMO_CLI_SCOPES`
|
|
289
|
+
*
|
|
290
|
+
* Plus the optional direct-Firestore session config:
|
|
291
|
+
* - `DEMO_CLI_FIREBASE_API_KEY`
|
|
292
|
+
* - `DEMO_CLI_FIREBASE_AUTH_DOMAIN`
|
|
293
|
+
* - `DEMO_CLI_FIREBASE_PROJECT_ID`
|
|
294
|
+
* - `DEMO_CLI_FIREBASE_APP_ID`
|
|
295
|
+
* - `DEMO_CLI_FIREBASE_EMULATOR_HOST`
|
|
296
|
+
* - `DEMO_CLI_FIREBASE_AUTH_EMULATOR_PORT`
|
|
297
|
+
* - `DEMO_CLI_FIREBASE_FIRESTORE_EMULATOR_PORT`
|
|
192
298
|
*/
|
|
193
299
|
export interface EnvVarOverrideInput {
|
|
194
300
|
readonly cliName: string;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type AsyncKeyedValueCache, type Maybe } from '@dereekb/util';
|
|
2
|
+
import { type CliFirestoreSession } from '../api/firestore-session.client';
|
|
3
|
+
/**
|
|
4
|
+
* Hard ceiling on how long a minted direct-Firestore session may be reused, regardless of what the
|
|
5
|
+
* API reported in `expiresAt`.
|
|
6
|
+
*
|
|
7
|
+
* One hour, because that is the Firebase ceiling the credentials themselves sit under: a custom
|
|
8
|
+
* token is exchangeable for one hour, and the ID token it mints lives one hour. Holding a session
|
|
9
|
+
* past that buys nothing — the sign-in would fail — and re-minting is one HTTP round-trip.
|
|
10
|
+
*/
|
|
11
|
+
export declare const CLI_FIRESTORE_SESSION_MAX_CACHE_MS: number;
|
|
12
|
+
/**
|
|
13
|
+
* Default skew/latency buffer applied when deciding whether a cached session is still usable.
|
|
14
|
+
*/
|
|
15
|
+
export declare const CLI_FIRESTORE_SESSION_EXPIRY_BUFFER_MS = 60000;
|
|
16
|
+
/**
|
|
17
|
+
* A cached direct-Firestore session for a single env.
|
|
18
|
+
*
|
|
19
|
+
* Stores the credential envelope the API minted, not the live Firebase objects — those are
|
|
20
|
+
* per-process and cannot be serialized. A cache hit still signs in; it just skips the
|
|
21
|
+
* `GET /session/firestore` round-trip.
|
|
22
|
+
*/
|
|
23
|
+
export interface CliFirestoreSessionEntry {
|
|
24
|
+
/**
|
|
25
|
+
* The credential bundle returned by `GET /session/firestore`.
|
|
26
|
+
*/
|
|
27
|
+
readonly session: CliFirestoreSession;
|
|
28
|
+
/**
|
|
29
|
+
* Unix epoch milliseconds at which the entry was written.
|
|
30
|
+
*/
|
|
31
|
+
readonly cachedAt: number;
|
|
32
|
+
/**
|
|
33
|
+
* The uid the entry was minted for, denormalized so a stale entry belonging to a different user
|
|
34
|
+
* can be detected without parsing the custom token.
|
|
35
|
+
*/
|
|
36
|
+
readonly uid: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Firestore session cache shape on disk — keyed by env name.
|
|
40
|
+
*/
|
|
41
|
+
export type CliFirestoreSessionCache = Record<string, CliFirestoreSessionEntry>;
|
|
42
|
+
/**
|
|
43
|
+
* Session cache store keyed by env name.
|
|
44
|
+
*
|
|
45
|
+
* Backed by a single JSON file with per-process in-memory memoization, exactly like the token
|
|
46
|
+
* cache — see {@link createMemoizedJsonFileAsyncKeyedValueCache}.
|
|
47
|
+
*/
|
|
48
|
+
export type CliFirestoreSessionCacheStore = AsyncKeyedValueCache<CliFirestoreSessionEntry>;
|
|
49
|
+
export interface CreateCliFirestoreSessionCacheStoreInput {
|
|
50
|
+
readonly firestoreSessionCachePath: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Creates a per-env direct-Firestore session cache store backed by a single JSON file.
|
|
54
|
+
*
|
|
55
|
+
* Entries are written with mode 0o600 — they hold a Firebase custom token, which is a bearer
|
|
56
|
+
* credential for the user it was minted for.
|
|
57
|
+
*
|
|
58
|
+
* @param input - The cache store inputs.
|
|
59
|
+
* @param input.firestoreSessionCachePath - Absolute path to the JSON file backing the cache.
|
|
60
|
+
* @returns A {@link CliFirestoreSessionCacheStore} keyed by env name.
|
|
61
|
+
* @__NO_SIDE_EFFECTS__
|
|
62
|
+
*/
|
|
63
|
+
export declare function createCliFirestoreSessionCacheStore(input: CreateCliFirestoreSessionCacheStoreInput): CliFirestoreSessionCacheStore;
|
|
64
|
+
/**
|
|
65
|
+
* Resolves the epoch-millis instant at which a cached session stops being usable.
|
|
66
|
+
*
|
|
67
|
+
* The effective expiry is the EARLIER of the API-reported `expiresAt` and
|
|
68
|
+
* {@link CLI_FIRESTORE_SESSION_MAX_CACHE_MS} past the write. Taking the earlier of the two means a
|
|
69
|
+
* server that reports an over-long (or unparsable) window still cannot push a session past the
|
|
70
|
+
* Firebase credential ceiling.
|
|
71
|
+
*
|
|
72
|
+
* @param entry - The cached entry.
|
|
73
|
+
* @returns The effective expiry in unix epoch milliseconds.
|
|
74
|
+
*
|
|
75
|
+
* @__NO_SIDE_EFFECTS__
|
|
76
|
+
*/
|
|
77
|
+
export declare function cliFirestoreSessionEntryExpiresAt(entry: CliFirestoreSessionEntry): number;
|
|
78
|
+
/**
|
|
79
|
+
* Returns true when the cached session is at or near its effective expiry.
|
|
80
|
+
*
|
|
81
|
+
* @param entry - The cached entry (`null`/`undefined` is treated as expired).
|
|
82
|
+
* @param nowMs - The current time in unix epoch milliseconds. Defaults to `Date.now()`.
|
|
83
|
+
* @param bufferMs - Skew/latency buffer; the entry is treated as expired this far ahead of its effective expiry.
|
|
84
|
+
* @returns `true` when the entry is unusable, otherwise `false`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function isCliFirestoreSessionExpired(entry: Maybe<CliFirestoreSessionEntry>, nowMs?: number, bufferMs?: number): boolean;
|
|
@@ -5,6 +5,7 @@ export interface CliPaths {
|
|
|
5
5
|
readonly configDir: string;
|
|
6
6
|
readonly configFilePath: string;
|
|
7
7
|
readonly tokenCachePath: string;
|
|
8
|
+
readonly firestoreSessionCachePath: string;
|
|
8
9
|
}
|
|
9
10
|
export interface CliPathsConfig {
|
|
10
11
|
/**
|
|
@@ -24,11 +25,12 @@ export interface CliPathsConfig {
|
|
|
24
25
|
* Layout:
|
|
25
26
|
* - `<configDir>/config.json` — the persistent CLI config (envs, output settings)
|
|
26
27
|
* - `<configDir>/.tokens.json` — per-env access/refresh token cache (mode 0600)
|
|
28
|
+
* - `<configDir>/.firestore-sessions.json` — per-env direct-Firestore session cache (mode 0600)
|
|
27
29
|
*
|
|
28
30
|
* @param config - The path-building inputs.
|
|
29
31
|
* @param config.cliName - The CLI's binary name; the default config dir is `~/.<cliName>`.
|
|
30
32
|
* @param config.configDirOverride - Optional override that replaces the default config directory verbatim (used by tests).
|
|
31
|
-
* @returns The {@link CliPaths} pointing at `configDir`, the config file, and the
|
|
33
|
+
* @returns The {@link CliPaths} pointing at `configDir`, the config file, the token cache file, and the Firestore session cache file.
|
|
32
34
|
* @__NO_SIDE_EFFECTS__
|
|
33
35
|
*/
|
|
34
36
|
export declare function buildCliPaths(config: CliPathsConfig): CliPaths;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import type { OnCallTypedModelParams } from '@dereekb/firebase';
|
|
1
|
+
import type { FirestoreContext, OnCallTypedModelParams } from '@dereekb/firebase';
|
|
2
|
+
import type { Maybe } from '@dereekb/util';
|
|
2
3
|
import { type CliEnvConfig } from '../config/env';
|
|
4
|
+
import { type CliFirestoreSessionCacheStore } from '../config/firestore-session.cache';
|
|
3
5
|
import { type GetModelOverHttpResult, type GetMultipleModelsOverHttpResult } from '../api/call-model.client';
|
|
6
|
+
import { type CliFirestoreBinding, type CliFirestoreModels } from '../firestore/firestore.models';
|
|
7
|
+
import { type CliFirestoreSessionContext } from '../firestore/firestore.session';
|
|
4
8
|
import { type CliModelManifest } from '../manifest/types';
|
|
5
9
|
/**
|
|
6
10
|
* The CLI context attached to argv by the auth middleware.
|
|
@@ -23,9 +27,76 @@ export interface CliContext {
|
|
|
23
27
|
readonly getModel: <TResult = unknown>(modelType: string, key: string) => Promise<GetModelOverHttpResult<TResult>>;
|
|
24
28
|
readonly getMultipleModels: <TResult = unknown>(modelType: string, keys: ReadonlyArray<string>) => Promise<GetMultipleModelsOverHttpResult<TResult>>;
|
|
25
29
|
readonly modelManifest?: CliModelManifest;
|
|
30
|
+
/**
|
|
31
|
+
* Opens (once per invocation) a direct Firestore connection as the authenticated user and returns
|
|
32
|
+
* the `FirestoreContext` an app's `make<App>FirestoreCollections(context)` factory consumes — the
|
|
33
|
+
* same object the Angular app builds, so the same queries run through the same security rules.
|
|
34
|
+
*
|
|
35
|
+
* Lazy on purpose: the handshake costs an HTTP round-trip plus a sign-in, and the auth middleware
|
|
36
|
+
* builds a context on EVERY invocation, including ones that never touch Firestore. Memoized within
|
|
37
|
+
* the invocation, and — when the runner supplies a session cache — the minted credential envelope
|
|
38
|
+
* is reused ACROSS invocations for up to an hour, so only the sign-in is repaid.
|
|
39
|
+
*
|
|
40
|
+
* OPTIONAL so that a hand-built test context (see `createPassthroughAuthMiddleware`) stays valid.
|
|
41
|
+
* Calling it throws when the env carries no Firebase client config — there is no fallback to the
|
|
42
|
+
* HTTP model API by design.
|
|
43
|
+
*/
|
|
44
|
+
readonly getFirestoreContext?: () => Promise<FirestoreContext>;
|
|
45
|
+
/**
|
|
46
|
+
* The full session behind {@link CliContext.getFirestoreContext} — the signed-in `Auth`, the raw
|
|
47
|
+
* `Firestore`, and the minted credential envelope. Memoized alongside it, so calling both opens one
|
|
48
|
+
* session.
|
|
49
|
+
*/
|
|
50
|
+
readonly getFirestoreSession?: () => Promise<CliFirestoreSessionContext>;
|
|
51
|
+
/**
|
|
52
|
+
* The app's models bound to the direct-Firestore session — what `firestore-get` /
|
|
53
|
+
* `firestore-query` dispatch through. Layered on the {@link CliContext.getFirestoreSession} memo,
|
|
54
|
+
* so all three thunks share ONE session, and separately memoized itself, so the app's collections
|
|
55
|
+
* object is built ONCE per invocation rather than once per call.
|
|
56
|
+
*
|
|
57
|
+
* Present only when the CLI was configured with a `firestore` binding (`runCli({ firestore })`).
|
|
58
|
+
*/
|
|
59
|
+
readonly getFirestoreModels?: () => Promise<CliFirestoreModels>;
|
|
60
|
+
/**
|
|
61
|
+
* Releases the direct-Firestore session opened during this invocation, if any.
|
|
62
|
+
*
|
|
63
|
+
* {@link runCli} calls this once the command has finished. Without it the CLI PRINTS ITS RESULT AND
|
|
64
|
+
* THEN HANGS: the signed-in `Auth` and live `Firestore` keep the Node event loop alive, so the
|
|
65
|
+
* process never exits and a caller piping the output waits forever.
|
|
66
|
+
*
|
|
67
|
+
* Never opens a session in order to close one — a command that never touched Firestore resolves
|
|
68
|
+
* immediately. Safe to call more than once.
|
|
69
|
+
*/
|
|
70
|
+
readonly closeFirestoreSession?: () => Promise<void>;
|
|
26
71
|
}
|
|
27
|
-
|
|
28
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Returns the context's {@link CliFirestoreSessionContext} thunk result, or throws a {@link CliError}
|
|
74
|
+
* when the context has none.
|
|
75
|
+
*
|
|
76
|
+
* {@link CliContext.getFirestoreSession} is optional so a hand-built test context stays valid, which
|
|
77
|
+
* would otherwise leave every consumer action writing the same guard. Every context built by
|
|
78
|
+
* {@link createCliContext} does provide it, so reaching this error in practice means the action ran
|
|
79
|
+
* against a context that was assembled by hand.
|
|
80
|
+
*
|
|
81
|
+
* @param context - The live CLI context.
|
|
82
|
+
* @returns The opened (and memoized) direct-Firestore session.
|
|
83
|
+
* @throws {CliError} When the context does not support direct-Firestore sessions.
|
|
84
|
+
*/
|
|
85
|
+
export declare function requireCliFirestoreSession(context: CliContext): Promise<CliFirestoreSessionContext>;
|
|
86
|
+
/**
|
|
87
|
+
* Returns the client `FirestoreContext` for the current invocation, opening the direct-Firestore
|
|
88
|
+
* session on first use.
|
|
89
|
+
*
|
|
90
|
+
* The returned context is the exact analogue of the server's, so an app's
|
|
91
|
+
* `make<App>FirestoreCollections(context)` accepts it unchanged.
|
|
92
|
+
*
|
|
93
|
+
* @param context - The live CLI context.
|
|
94
|
+
* @returns The client `FirestoreContext` an app's collections factory consumes.
|
|
95
|
+
* @throws {CliError} When the context does not support direct-Firestore sessions.
|
|
96
|
+
*/
|
|
97
|
+
export declare function requireCliFirestoreContext(context: CliContext): Promise<FirestoreContext>;
|
|
98
|
+
export declare const setCliContext: (value: Maybe<CliContext>) => void;
|
|
99
|
+
export declare const getCliContext: () => Maybe<CliContext>;
|
|
29
100
|
/**
|
|
30
101
|
* Returns the current {@link CliContext} or throws — for use in command handlers that require auth.
|
|
31
102
|
*/
|
|
@@ -40,6 +111,17 @@ export interface CreateCliContextInput {
|
|
|
40
111
|
* (e.g. `get <key>`) can resolve `prefix/id` keys to a `modelType` via `decodeFirestoreModelKey`.
|
|
41
112
|
*/
|
|
42
113
|
readonly modelManifest?: CliModelManifest;
|
|
114
|
+
/**
|
|
115
|
+
* Optional on-disk direct-Firestore session cache. When supplied, the session opened by
|
|
116
|
+
* {@link CliContext.getFirestoreSession} is reused across invocations for up to an hour instead of
|
|
117
|
+
* costing a `GET /session/firestore` round-trip every time.
|
|
118
|
+
*/
|
|
119
|
+
readonly firestoreSessionCache?: CliFirestoreSessionCacheStore;
|
|
120
|
+
/**
|
|
121
|
+
* The app-supplied Firestore binding (`cliFirestoreBinding({ collections, models })`). When
|
|
122
|
+
* present, the context exposes {@link CliContext.getFirestoreModels}.
|
|
123
|
+
*/
|
|
124
|
+
readonly firestore?: CliFirestoreBinding;
|
|
43
125
|
}
|
|
44
126
|
/**
|
|
45
127
|
* Builds a {@link CliContext} for the current invocation.
|
|
@@ -53,6 +135,8 @@ export interface CreateCliContextInput {
|
|
|
53
135
|
* @param input.env - The resolved {@link CliEnvConfig} for the active env.
|
|
54
136
|
* @param input.accessToken - The Bearer access token to include on outgoing API calls.
|
|
55
137
|
* @param input.modelManifest - Optional generated {@link CliModelManifest} for key→modelType resolution.
|
|
138
|
+
* @param input.firestoreSessionCache - Optional on-disk direct-Firestore session cache shared across invocations.
|
|
139
|
+
* @param input.firestore - Optional app-supplied Firestore binding enabling the generic direct-read commands.
|
|
56
140
|
* @returns The constructed {@link CliContext}.
|
|
57
141
|
* @__NO_SIDE_EFFECTS__
|
|
58
142
|
*/
|