@dereekb/dbx-cli 13.39.0 → 13.41.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/package.json +3 -3
- package/firestore-query-manifest/main.js +291 -7
- package/firestore-query-manifest/package.json +3 -3
- package/generate-firestore-indexes/main.js +2 -2
- package/generate-firestore-indexes/package.json +2 -2
- package/generate-mcp-manifest/package.json +3 -3
- package/generate-route-manifest/package.json +2 -2
- package/index.esm.js +1631 -765
- package/lint-cache/package.json +2 -2
- package/manifest-extract/package.json +2 -2
- package/model-test/package.json +2 -2
- package/package.json +6 -6
- package/route/package.json +7 -7
- package/src/lib/doctor/firestore-session.check.d.ts +23 -2
- package/src/lib/firestore/firestore.collection.d.ts +29 -0
- package/src/lib/firestore/firestore.query.d.ts +1 -1
- package/src/lib/firestore/firestore.session.d.ts +40 -0
- package/src/lib/firestore/index.d.ts +1 -0
- package/src/lib/firestore/query-info-utils.d.ts +6 -1
- package/src/lib/firestore/query-mode.d.ts +120 -0
- package/src/lib/manifest/types.d.ts +109 -0
- package/src/lib/runner/index.d.ts +1 -0
- package/src/lib/runner/lifecycle.d.ts +85 -0
- package/src/lib/runner/run.d.ts +15 -1
- package/test/package.json +9 -9
- package/validate/package.json +3 -3
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.41.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.41.0",
|
|
12
12
|
"yargs": "^18.0.0"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli/manifest-extract",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.41.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"peerDependencies": {
|
|
7
7
|
"@dereekb/date": "13.15.0",
|
|
8
|
-
"@dereekb/dbx-cli": "13.
|
|
8
|
+
"@dereekb/dbx-cli": "13.41.0",
|
|
9
9
|
"@dereekb/firebase": "13.15.0",
|
|
10
10
|
"@dereekb/model": "13.15.0",
|
|
11
11
|
"@dereekb/nestjs": "13.15.0",
|
package/model-test/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli/model-test",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.41.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"peerDependencies": {
|
|
7
|
-
"@dereekb/util": "13.
|
|
7
|
+
"@dereekb/util": "13.41.0",
|
|
8
8
|
"ts-morph": "^21.0.0"
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.41.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"bin": {
|
|
@@ -66,11 +66,11 @@
|
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"@dereekb/date": "13.
|
|
70
|
-
"@dereekb/firebase": "13.
|
|
71
|
-
"@dereekb/model": "13.
|
|
72
|
-
"@dereekb/nestjs": "13.
|
|
73
|
-
"@dereekb/util": "13.
|
|
69
|
+
"@dereekb/date": "13.41.0",
|
|
70
|
+
"@dereekb/firebase": "13.41.0",
|
|
71
|
+
"@dereekb/model": "13.41.0",
|
|
72
|
+
"@dereekb/nestjs": "13.41.0",
|
|
73
|
+
"@dereekb/util": "13.41.0",
|
|
74
74
|
"@nestjs/common": "^11.1.19",
|
|
75
75
|
"arktype": "^2.2.0",
|
|
76
76
|
"firebase": "^12.12.1",
|
package/route/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli/route",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.41.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"peerDependencies": {
|
|
7
|
-
"@dereekb/date": "13.
|
|
8
|
-
"@dereekb/dbx-cli": "13.
|
|
9
|
-
"@dereekb/firebase": "13.
|
|
10
|
-
"@dereekb/model": "13.
|
|
11
|
-
"@dereekb/nestjs": "13.
|
|
12
|
-
"@dereekb/util": "13.
|
|
7
|
+
"@dereekb/date": "13.41.0",
|
|
8
|
+
"@dereekb/dbx-cli": "13.41.0",
|
|
9
|
+
"@dereekb/firebase": "13.41.0",
|
|
10
|
+
"@dereekb/model": "13.41.0",
|
|
11
|
+
"@dereekb/nestjs": "13.41.0",
|
|
12
|
+
"@dereekb/util": "13.41.0"
|
|
13
13
|
},
|
|
14
14
|
"exports": {
|
|
15
15
|
"./package.json": "./package.json",
|
|
@@ -57,11 +57,32 @@ export interface FirestoreSessionDoctorReadRouting {
|
|
|
57
57
|
readonly readPreference: 'firestore' | 'api';
|
|
58
58
|
readonly reason: CliReadSourceReason;
|
|
59
59
|
/**
|
|
60
|
-
* Query-catalog entries
|
|
61
|
-
*
|
|
60
|
+
* Query-catalog entries `firestore-query` can actually run: the factory bound to a real runtime
|
|
61
|
+
* export AND `firestore.rules` does not refuse the query at every scope.
|
|
62
|
+
*
|
|
63
|
+
* `unavailable` entries are deliberately EXCLUDED. Counting them was the bug this number had:
|
|
64
|
+
* an entry with a bound factory over a collection with no `/{path=**}/` rule reported as invocable
|
|
65
|
+
* and then failed with `permission-denied` on every call, so `invocableQueryEntries: 128/128` was
|
|
66
|
+
* a claim the CLI could not honour for about a third of the catalog.
|
|
62
67
|
*/
|
|
63
68
|
readonly invocableQueryEntries: number;
|
|
64
69
|
readonly totalQueryEntries: number;
|
|
70
|
+
/**
|
|
71
|
+
* Entries `firestore.rules` refuses at every scope (`queryMode: 'unavailable'`) — the query-level
|
|
72
|
+
* analogue of {@link serverOnlyModels}, surfaced the same way because it has the same consequence.
|
|
73
|
+
*/
|
|
74
|
+
readonly unavailableQueryEntries: number;
|
|
75
|
+
/**
|
|
76
|
+
* Entries that address ONE parent document's subcollection (`queryMode: 'parent-child'`), so they
|
|
77
|
+
* run only when scoped with `--parent`.
|
|
78
|
+
*/
|
|
79
|
+
readonly parentChildQueryEntries: number;
|
|
80
|
+
/**
|
|
81
|
+
* Whether the query manifest carries invocation modes at all — false when its generator ran
|
|
82
|
+
* without `--rules`, in which case the two counts above are structurally `0` rather than
|
|
83
|
+
* genuinely clean.
|
|
84
|
+
*/
|
|
85
|
+
readonly queryModesScanned: boolean;
|
|
65
86
|
/**
|
|
66
87
|
* Models the manifest marks `@dbxModelServerOnly` — refused on every `--via` value.
|
|
67
88
|
*/
|
|
@@ -20,6 +20,12 @@ export interface CliFirestoreCollectionForQueryInput {
|
|
|
20
20
|
* The parent document key to narrow to, from `--parent`.
|
|
21
21
|
*/
|
|
22
22
|
readonly parentKey?: FirestoreModelKey;
|
|
23
|
+
/**
|
|
24
|
+
* The parent-document path templates the rules declare for this collection, from
|
|
25
|
+
* `CliFirestoreQueryReachability.parentPaths` (e.g. `jl/{jobLocation}/jlj/{job}`). When supplied,
|
|
26
|
+
* a `--parent` whose collection chain does not match one of them is rejected locally.
|
|
27
|
+
*/
|
|
28
|
+
readonly parentPaths?: readonly string[];
|
|
23
29
|
}
|
|
24
30
|
/**
|
|
25
31
|
* Resolves the collection a catalog entry should execute against, applying `--parent` scoping.
|
|
@@ -42,3 +48,26 @@ export interface CliFirestoreCollectionForQueryInput {
|
|
|
42
48
|
* @throws {CliError} When `--parent` is missing where required, supplied where rejected, or the registered collection cannot be scoped.
|
|
43
49
|
*/
|
|
44
50
|
export declare function cliFirestoreCollectionForQuery(input: CliFirestoreCollectionForQueryInput): FirestoreCollectionLike<unknown>;
|
|
51
|
+
/**
|
|
52
|
+
* Validates a `--parent` value before it is turned into a document reference.
|
|
53
|
+
*
|
|
54
|
+
* Both checks exist because the failure they prevent is otherwise unreadable. An odd-segment
|
|
55
|
+
* `--parent` (a COLLECTION path, e.g. `jl/abc/jlj`) reaches `docAtPath` and comes back as a raw
|
|
56
|
+
* Firestore path assertion; a well-formed key for the WRONG ancestor chain (e.g. `gb/abc` for a
|
|
57
|
+
* collection that lives under `jl/{loc}/jlj/{job}`) silently produces an empty result set, which is
|
|
58
|
+
* indistinguishable from "no matching documents".
|
|
59
|
+
*
|
|
60
|
+
* The chain comparison is on COLLECTION NAMES only — document ids are the caller's data, and the
|
|
61
|
+
* rules templates carry `{wildcard}` placeholders there.
|
|
62
|
+
*
|
|
63
|
+
* @param input - The model type, the supplied parent key, and the rules-declared parent templates.
|
|
64
|
+
* @param input.modelType - The model being queried, for the message.
|
|
65
|
+
* @param input.parentKey - The raw `--parent` value.
|
|
66
|
+
* @param input.parentPaths - The rules-declared parent path templates, when known.
|
|
67
|
+
* @throws {CliError} `INVALID_ARGUMENT` when the key is not a document path, or names a chain the rules do not declare.
|
|
68
|
+
*/
|
|
69
|
+
export declare function assertCliFirestoreQueryParentKey(input: {
|
|
70
|
+
readonly modelType: FirestoreModelType;
|
|
71
|
+
readonly parentKey: FirestoreModelKey;
|
|
72
|
+
readonly parentPaths?: readonly string[];
|
|
73
|
+
}): void;
|
|
@@ -74,6 +74,6 @@ export declare function cliFirestoreQueryConstraints(input: {
|
|
|
74
74
|
*
|
|
75
75
|
* @param input - The models view, the entry, and the run options.
|
|
76
76
|
* @returns The query result envelope.
|
|
77
|
-
* @throws {CliError} On an argument, scoping, or invocability failure. Firestore's own errors (rules, missing index) surface via `cliFirestoreErrorMapper`.
|
|
77
|
+
* @throws {CliError} On an argument, scoping, mode, or invocability failure. Firestore's own errors (rules, missing index) surface via `cliFirestoreErrorMapper`.
|
|
78
78
|
*/
|
|
79
79
|
export declare function runCliFirestoreQuery(input: RunCliFirestoreQueryInput): Promise<CliFirestoreQueryResult>;
|
|
@@ -49,6 +49,20 @@ export interface CreateCliFirestoreSessionContextInput {
|
|
|
49
49
|
*/
|
|
50
50
|
readonly refreshSession?: boolean;
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* The Firebase app name a direct-Firestore session registers for a CLI + env pair.
|
|
54
|
+
*
|
|
55
|
+
* Deriving the name rather than tracking apps in a side registry is what makes ONE app per CLI + env
|
|
56
|
+
* per process a property of the code instead of a convention: {@link createCliFirestoreSessionContext}
|
|
57
|
+
* looks this name up in `getApps()` before initializing, so repeated session opens in one process —
|
|
58
|
+
* a doctor probe alongside a command's own session, an action that re-resolves the accessor — all
|
|
59
|
+
* share a single app, and {@link closeAllCliFirebaseApps} finds it again from the name alone.
|
|
60
|
+
*
|
|
61
|
+
* @param input - The CLI name and env name the session targets.
|
|
62
|
+
* @returns The Firebase app name for that pair.
|
|
63
|
+
* @__NO_SIDE_EFFECTS__
|
|
64
|
+
*/
|
|
65
|
+
export declare function cliFirebaseAppName(input: Pick<CreateCliFirestoreSessionContextInput, 'cliName' | 'envName'>): string;
|
|
52
66
|
/**
|
|
53
67
|
* Opens a direct Firestore connection as the authenticated CLI user.
|
|
54
68
|
*
|
|
@@ -95,3 +109,29 @@ export declare function createCliFirestoreSessionContext(input: CreateCliFiresto
|
|
|
95
109
|
* @param session - The session context to close.
|
|
96
110
|
*/
|
|
97
111
|
export declare function closeCliFirestoreSessionContext(session: CliFirestoreSessionContext): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Deletes every still-live Firebase app this CLI opened, whether or not a session was handed back.
|
|
114
|
+
*
|
|
115
|
+
* The CLI's last line of defence against a hang. `closeCliFirestoreSessionContext` covers the normal
|
|
116
|
+
* path, but it needs a session to be handed to it, and three cases never produce one:
|
|
117
|
+
*
|
|
118
|
+
* - a handshake that fails AFTER `initializeApp` — a rejected custom token, a failed App Check
|
|
119
|
+
* registration — throws, so the caller that catches it has an initialized app and no session;
|
|
120
|
+
* - a caller that opens its own session outside the context memo (the doctor probe) owns its own
|
|
121
|
+
* teardown, and forgetting it hangs the process;
|
|
122
|
+
* - a {@link CliContext} orphaned mid-invocation carries the only reference to its session memo.
|
|
123
|
+
*
|
|
124
|
+
* Each leaves an app whose `Firestore` and signed-in `Auth` hold the Node event loop open forever.
|
|
125
|
+
* `getApps()` already tracks every live app and `deleteApp` removes it from that list, so the app
|
|
126
|
+
* names {@link cliFirebaseAppName} derives are enough to find them again — no side registry to keep
|
|
127
|
+
* in sync, and idempotent by construction.
|
|
128
|
+
*
|
|
129
|
+
* Tolerant of failures for the same reason {@link closeCliFirestoreSessionContext} is: it runs after
|
|
130
|
+
* the result is already on stdout.
|
|
131
|
+
*
|
|
132
|
+
* @param input - The function inputs.
|
|
133
|
+
* @param input.cliName - The CLI whose apps should be closed. Apps belonging to other Firebase
|
|
134
|
+
* consumers in the same process are left alone.
|
|
135
|
+
* @returns Resolves once every matching app has been deleted.
|
|
136
|
+
*/
|
|
137
|
+
export declare function closeAllCliFirebaseApps(input: Pick<CreateCliFirestoreSessionContextInput, 'cliName'>): Promise<void>;
|
|
@@ -17,9 +17,14 @@ export interface CliFirestoreQueryListFilter {
|
|
|
17
17
|
readonly model?: string;
|
|
18
18
|
readonly category?: string;
|
|
19
19
|
readonly tag?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Drop the entries this CLI cannot run — an unbound factory, or a query `firestore.rules` refuses
|
|
22
|
+
* at every scope. A `parent-child` entry is KEPT: `--parent` runs it.
|
|
23
|
+
*/
|
|
24
|
+
readonly invocableOnly?: boolean;
|
|
20
25
|
}
|
|
21
26
|
/**
|
|
22
|
-
* Applies the `--model` / `--category` / `--tag` filters to the catalog.
|
|
27
|
+
* Applies the `--model` / `--category` / `--tag` / `--invocable-only` filters to the catalog.
|
|
23
28
|
*
|
|
24
29
|
* @param registry - The query catalog.
|
|
25
30
|
* @param filter - The active filters.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { type Maybe } from '@dereekb/util';
|
|
2
|
+
import { type CliFirestoreQueryManifestEntry, type CliFirestoreQueryMode, type CliFirestoreQueryRules, type CliFirestoreQueryRulesAccess, type CliFirestoreQueryScope } from '../manifest/types';
|
|
3
|
+
/**
|
|
4
|
+
* Input for {@link cliFirestoreQueryModeForRules}.
|
|
5
|
+
*
|
|
6
|
+
* Takes the rules facts as PLAIN values rather than a `FirestoreRulesCollectionEntry`, so the rule
|
|
7
|
+
* lives in the shipped package while the scanner stays in the in-repo source-only
|
|
8
|
+
* `@dereekb/dbx-cli/firestore-rules`. The generator is the one place that owns both.
|
|
9
|
+
*/
|
|
10
|
+
export interface CliFirestoreQueryModeForRulesInput {
|
|
11
|
+
readonly scope: CliFirestoreQueryScope;
|
|
12
|
+
readonly isNested: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* `FirestoreRulesCollectionEntry.collectionGroup` — whether a `/{path=**}/<collection>/{id}` block exists.
|
|
15
|
+
*/
|
|
16
|
+
readonly collectionGroup: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* `FirestoreRulesCollectionEntry.list` — the merged `list` posture for the collection.
|
|
19
|
+
*/
|
|
20
|
+
readonly list: CliFirestoreQueryRulesAccess;
|
|
21
|
+
/**
|
|
22
|
+
* Parent-document path templates read off the collection's non-recursive rules match paths.
|
|
23
|
+
*/
|
|
24
|
+
readonly parentPaths?: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The mode an entry resolves to, paired with the rules evidence behind it.
|
|
28
|
+
*/
|
|
29
|
+
export interface CliFirestoreQueryModeResult {
|
|
30
|
+
readonly mode: CliFirestoreQueryMode;
|
|
31
|
+
readonly rules: CliFirestoreQueryRules;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolves how a catalog entry must be invoked, from the rules facts about its collection.
|
|
35
|
+
*
|
|
36
|
+
* The rule, in the order it is applied:
|
|
37
|
+
*
|
|
38
|
+
* 1. `list` is not `allowed` — no block anywhere grants a list, so the entry is dead at EVERY
|
|
39
|
+
* scope. Checked first because no amount of `--parent` scoping rescues it.
|
|
40
|
+
* 2. `scope: 'COLLECTION_GROUP'` with no `/{path=**}/<collection>/{id}` block — the group shape is
|
|
41
|
+
* dead. A NESTED model degrades to `parent-child` (the path-scoped grant that satisfied step 1
|
|
42
|
+
* is what `--parent` runs against); a root collection has no parent to scope to, so
|
|
43
|
+
* `cliFirestoreCollectionForQuery` rejects `--parent` outright and the mode is `unavailable`.
|
|
44
|
+
* 3. `scope: 'COLLECTION'` over a subcollection — `--parent` is required by construction, whatever
|
|
45
|
+
* the rules say, so the mode is `parent-child` rather than `model`. This step is why the mode is
|
|
46
|
+
* NOT a pure permission verdict: the rules permit the read, and it still cannot be run
|
|
47
|
+
* unscoped.
|
|
48
|
+
* 4. Otherwise `model`.
|
|
49
|
+
*
|
|
50
|
+
* `unavailable` is a deliberate UNDER-approximation. `list` is merged across every match block that
|
|
51
|
+
* reaches the collection, so a recursive block that denies `list` while a nested block allows it
|
|
52
|
+
* reads as `allowed` and the entry is not marked dead. That direction is the safe one — the scanner
|
|
53
|
+
* can never make this CLI refuse a query that actually works, only miss one that does not.
|
|
54
|
+
*
|
|
55
|
+
* @param input - The entry's scope/nesting and the rules facts for its collection.
|
|
56
|
+
* @returns The mode plus the evidence, with a reason attached whenever the mode is not `model`.
|
|
57
|
+
*
|
|
58
|
+
* @__NO_SIDE_EFFECTS__
|
|
59
|
+
*/
|
|
60
|
+
export declare function cliFirestoreQueryModeForRules(input: CliFirestoreQueryModeForRulesInput): CliFirestoreQueryModeResult;
|
|
61
|
+
/**
|
|
62
|
+
* The mode an entry carries, normalized so an absent field reads as `unknown` rather than
|
|
63
|
+
* `undefined` — a consumer switching over {@link CliFirestoreQueryMode} stays total.
|
|
64
|
+
*
|
|
65
|
+
* @param entry - The catalog entry.
|
|
66
|
+
* @returns The entry's mode, or `unknown` when the manifest was generated without `--rules`.
|
|
67
|
+
*
|
|
68
|
+
* @__NO_SIDE_EFFECTS__
|
|
69
|
+
*/
|
|
70
|
+
export declare function cliFirestoreQueryMode(entry: CliFirestoreQueryManifestEntry): CliFirestoreQueryMode;
|
|
71
|
+
/**
|
|
72
|
+
* True when this CLI could actually run the entry — the factory bound AND the rules do not refuse
|
|
73
|
+
* it outright. Backs `firestore-queries --invocable-only`.
|
|
74
|
+
*
|
|
75
|
+
* A `parent-child` entry counts as invocable: `--parent` runs it. An `unknown` one counts too;
|
|
76
|
+
* nothing is known against it.
|
|
77
|
+
*
|
|
78
|
+
* @param entry - The catalog entry.
|
|
79
|
+
* @returns Whether the entry is worth offering.
|
|
80
|
+
*
|
|
81
|
+
* @__NO_SIDE_EFFECTS__
|
|
82
|
+
*/
|
|
83
|
+
export declare function isCliFirestoreQueryInvocable(entry: CliFirestoreQueryManifestEntry): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Error code used when a query is refused because `firestore.rules` can never authorize it.
|
|
86
|
+
*/
|
|
87
|
+
export declare const FIRESTORE_QUERY_UNAVAILABLE_CODE = "FIRESTORE_QUERY_UNAVAILABLE";
|
|
88
|
+
/**
|
|
89
|
+
* Error code used when a `parent-child` query was run without a `--parent`.
|
|
90
|
+
*/
|
|
91
|
+
export declare const FIRESTORE_QUERY_PARENT_REQUIRED_CODE = "FIRESTORE_QUERY_PARENT_REQUIRED";
|
|
92
|
+
/**
|
|
93
|
+
* Refuses a catalog entry that cannot run as invoked, BEFORE a transport is chosen.
|
|
94
|
+
*
|
|
95
|
+
* Mirrors `assertCliModelIsNotServerOnly`, one level finer: that flag is per-MODEL and cannot say
|
|
96
|
+
* "readable under its parent, dead as a collection group", which is exactly the shape this catches.
|
|
97
|
+
* Answering locally makes the reason legible and free instead of surfacing as a bare
|
|
98
|
+
* `Missing or insufficient permissions.` from the rules engine.
|
|
99
|
+
*
|
|
100
|
+
* An `unknown` entry passes: the manifest was generated without a `--rules` file, so nothing is
|
|
101
|
+
* known and a guess would be worse than a round trip.
|
|
102
|
+
*
|
|
103
|
+
* @param input - The catalog entry and the `--parent` key, when one was supplied.
|
|
104
|
+
* @param input.entry - The catalog entry about to run.
|
|
105
|
+
* @param input.parent - The `--parent` key, when supplied.
|
|
106
|
+
* @throws {CliError} `FIRESTORE_QUERY_UNAVAILABLE` when no client can run it, or `FIRESTORE_QUERY_PARENT_REQUIRED` when it needs a `--parent` that was not given.
|
|
107
|
+
*/
|
|
108
|
+
export declare function assertCliFirestoreQueryCanRun(input: {
|
|
109
|
+
readonly entry: CliFirestoreQueryManifestEntry;
|
|
110
|
+
readonly parent?: Maybe<string>;
|
|
111
|
+
}): void;
|
|
112
|
+
/**
|
|
113
|
+
* Renders the `Mode:` line of the `firestore-queries <query>` detail view.
|
|
114
|
+
*
|
|
115
|
+
* @param entry - The catalog entry.
|
|
116
|
+
* @returns The rendered line body, without a trailing newline.
|
|
117
|
+
*
|
|
118
|
+
* @__NO_SIDE_EFFECTS__
|
|
119
|
+
*/
|
|
120
|
+
export declare function describeCliFirestoreQueryMode(entry: CliFirestoreQueryManifestEntry): string;
|
|
@@ -562,6 +562,100 @@ export interface CliFirestoreQueryParam {
|
|
|
562
562
|
* single scalar, single params object, two positionals, ...).
|
|
563
563
|
*/
|
|
564
564
|
export type CliFirestoreQueryFactory = (...args: readonly any[]) => readonly FirestoreQueryConstraint[];
|
|
565
|
+
/**
|
|
566
|
+
* How the rules resolve one read operation on a collection, mirroring
|
|
567
|
+
* `FirestoreRulesAccess` from `@dereekb/dbx-cli/firestore-rules`.
|
|
568
|
+
*
|
|
569
|
+
* Restated here rather than imported: `firestore-rules` is an in-repo
|
|
570
|
+
* source-only package that cannot resolve to a built `.d.ts` during this
|
|
571
|
+
* package's declaration build, and the manifest is a SERIALIZED artifact — its
|
|
572
|
+
* types have to stand alone.
|
|
573
|
+
*
|
|
574
|
+
* - `allowed` — some `allow` covering the op has a condition that is not constant-`false`.
|
|
575
|
+
* - `denied` — the op is covered only by `allow`s whose condition is literally `false`.
|
|
576
|
+
* - `unmatched` — no `allow` covers the op at all, so Firestore's default-deny applies.
|
|
577
|
+
*/
|
|
578
|
+
export type CliFirestoreQueryRulesAccess = 'allowed' | 'denied' | 'unmatched';
|
|
579
|
+
/**
|
|
580
|
+
* How a catalog entry must be INVOKED, resolved from `firestore.rules` at generation time.
|
|
581
|
+
*
|
|
582
|
+
* This classifies on the axis a caller acts on — "how do I run this?" — rather than on the raw
|
|
583
|
+
* rules verdict. The two are not the same question: a `COLLECTION`-scope entry over a
|
|
584
|
+
* subcollection is fully permitted by the rules and still cannot be run without `--parent`, so a
|
|
585
|
+
* pure permission verdict would call it runnable-as-declared and be wrong about how to run it.
|
|
586
|
+
*
|
|
587
|
+
* - `model` — run it directly against the model's collection or collection group.
|
|
588
|
+
* - `parent-child` — it addresses ONE parent document's subcollection, so `--parent <parentKey>`
|
|
589
|
+
* is required. Either the entry is `COLLECTION`-scope over a subcollection, or its
|
|
590
|
+
* `COLLECTION_GROUP` shape has no `/{path=**}/<collection>/{id}` block behind it while the
|
|
591
|
+
* path-scoped read IS granted.
|
|
592
|
+
* - `unavailable` — no client can run it, on any transport or scoping.
|
|
593
|
+
* - `unknown` — the query manifest was generated without a `--rules` file, so nothing was
|
|
594
|
+
* resolved. Never stored; it is what {@link CliFirestoreQueryManifestEntry.queryMode} being
|
|
595
|
+
* absent means, so a consumer's switch over this union stays total.
|
|
596
|
+
*/
|
|
597
|
+
export type CliFirestoreQueryMode = 'model' | 'parent-child' | 'unavailable' | 'unknown';
|
|
598
|
+
/**
|
|
599
|
+
* Why an entry resolved to the {@link CliFirestoreQueryMode} it did.
|
|
600
|
+
*
|
|
601
|
+
* `denied` and `unmatched` are kept apart the way the rules scanner keeps them apart: a
|
|
602
|
+
* written-down `if false` is a deliberate no, an absence is an oversight, and a reviewer needs to
|
|
603
|
+
* know which one they are looking at.
|
|
604
|
+
*/
|
|
605
|
+
export type CliFirestoreQueryModeReason =
|
|
606
|
+
/**
|
|
607
|
+
* `scope: 'COLLECTION_GROUP'` with no `/{path=**}/<collection>/{id}` block in the rules. A
|
|
608
|
+
* collection-group query is evaluated against that block alone — a path-scoped block does not
|
|
609
|
+
* authorize it, no matter how permissive. `parent-child` when the model is nested (the
|
|
610
|
+
* path-scoped read is the way in), `unavailable` when it is a root collection with no parent to
|
|
611
|
+
* scope to.
|
|
612
|
+
*/
|
|
613
|
+
'no-collection-group-rule'
|
|
614
|
+
/**
|
|
615
|
+
* `scope: 'COLLECTION'` over a subcollection: the entry addresses one parent's subcollection by
|
|
616
|
+
* construction, independent of what the rules say.
|
|
617
|
+
*/
|
|
618
|
+
| 'nested-collection-scope'
|
|
619
|
+
/**
|
|
620
|
+
* Every `list` grant on the collection is a constant-`false`.
|
|
621
|
+
*/
|
|
622
|
+
| 'list-denied'
|
|
623
|
+
/**
|
|
624
|
+
* No `list` grant covers the collection at all.
|
|
625
|
+
*/
|
|
626
|
+
| 'list-unmatched';
|
|
627
|
+
/**
|
|
628
|
+
* The `firestore.rules` evidence behind an entry's {@link CliFirestoreQueryMode} — the WHY, kept
|
|
629
|
+
* beside the answer rather than inside it.
|
|
630
|
+
*
|
|
631
|
+
* Split from the mode deliberately: a caller (or an agent) needs only the mode to decide what to
|
|
632
|
+
* do, while a reviewer auditing a surprising mode needs these facts to check the reasoning without
|
|
633
|
+
* re-reading the rules file.
|
|
634
|
+
*
|
|
635
|
+
* Absent, like the mode, when the generator was not given a `--rules` file.
|
|
636
|
+
*/
|
|
637
|
+
export interface CliFirestoreQueryRules {
|
|
638
|
+
/**
|
|
639
|
+
* The rules' `list` posture for {@link CliFirestoreQueryManifestEntry.collection}, merged across
|
|
640
|
+
* every match block that reaches it.
|
|
641
|
+
*/
|
|
642
|
+
readonly list: CliFirestoreQueryRulesAccess;
|
|
643
|
+
/**
|
|
644
|
+
* True when the rules declare a `/{path=**}/<collection>/{id}` block — the only shape a
|
|
645
|
+
* collection-group query is authorized by.
|
|
646
|
+
*/
|
|
647
|
+
readonly collectionGroup: boolean;
|
|
648
|
+
/**
|
|
649
|
+
* Present whenever the mode is not `model`.
|
|
650
|
+
*/
|
|
651
|
+
readonly reason?: CliFirestoreQueryModeReason;
|
|
652
|
+
/**
|
|
653
|
+
* The parent-document path templates a `--parent` key must match, read off the non-recursive
|
|
654
|
+
* rules match paths that reach this collection (e.g. `jl/{jobLocation}/jlj/{job}`). Lets the CLI
|
|
655
|
+
* name the required `--parent` shape instead of saying only "pass --parent".
|
|
656
|
+
*/
|
|
657
|
+
readonly parentPaths?: readonly string[];
|
|
658
|
+
}
|
|
565
659
|
/**
|
|
566
660
|
* One entry in the generated per-model Firestore query catalog.
|
|
567
661
|
*
|
|
@@ -619,6 +713,21 @@ export interface CliFirestoreQueryManifestEntry {
|
|
|
619
713
|
* constraint sequence is empty by design. See {@link relatedSlugs}.
|
|
620
714
|
*/
|
|
621
715
|
readonly dispatcher?: boolean;
|
|
716
|
+
/**
|
|
717
|
+
* How this entry must be invoked, per `firestore.rules` — unlike the flags
|
|
718
|
+
* above, this one DOES affect callability.
|
|
719
|
+
*
|
|
720
|
+
* Absent when the generator ran without a `--rules` file, which reads as
|
|
721
|
+
* `unknown` rather than `model`: a guess here would be worse than no field.
|
|
722
|
+
* Present, the CLI refuses an `unavailable` entry locally rather than paying
|
|
723
|
+
* a round trip for the `permission-denied` the rules guarantee, and requires
|
|
724
|
+
* `--parent` for a `parent-child` one.
|
|
725
|
+
*/
|
|
726
|
+
readonly queryMode?: CliFirestoreQueryMode;
|
|
727
|
+
/**
|
|
728
|
+
* The rules evidence behind {@link queryMode}. Set and unset together with it.
|
|
729
|
+
*/
|
|
730
|
+
readonly rules?: CliFirestoreQueryRules;
|
|
622
731
|
/**
|
|
623
732
|
* Absent when the identifier is not exported from {@link module}'s barrel
|
|
624
733
|
* chain — the entry is listed but cannot be executed.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type Maybe, type PromiseOrValue } from '@dereekb/util';
|
|
2
|
+
import { type CliContext } from '../context/cli.context';
|
|
3
|
+
/**
|
|
4
|
+
* Input handed to a {@link CliSetupFunction} / {@link CliTeardownFunction}.
|
|
5
|
+
*/
|
|
6
|
+
export interface CliLifecycleHookInput {
|
|
7
|
+
readonly cliName: string;
|
|
8
|
+
/**
|
|
9
|
+
* The invocation's {@link CliContext}, when one was built.
|
|
10
|
+
*
|
|
11
|
+
* Absent for an auth-bypassed command (`auth login`, `env list`, `firestore-queries`), and for a
|
|
12
|
+
* teardown that runs after authentication itself failed. A hook that needs the context should say
|
|
13
|
+
* so by throwing from `setup`, which aborts the command with an error envelope — a `teardown` must
|
|
14
|
+
* tolerate its absence, since it runs on paths `setup` never reached.
|
|
15
|
+
*/
|
|
16
|
+
readonly context: Maybe<CliContext>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* App-supplied hook run once per invocation, after the command's arguments validate and the auth
|
|
20
|
+
* middleware has attached the {@link CliContext}, and before the command's handler.
|
|
21
|
+
*
|
|
22
|
+
* Runs inside the parser, so throwing aborts the command: the error is emitted as the standard error
|
|
23
|
+
* envelope and the process exits non-zero, with {@link CliTeardownFunction} still run. That makes it
|
|
24
|
+
* the right place for a precondition an app's commands all depend on.
|
|
25
|
+
*/
|
|
26
|
+
export type CliSetupFunction = (input: CliLifecycleHookInput) => PromiseOrValue<void>;
|
|
27
|
+
/**
|
|
28
|
+
* App-supplied hook run once per invocation after the parser has fully settled, on both the success
|
|
29
|
+
* and the failure path, and BEFORE the CLI closes its direct-Firestore session — so the hook can
|
|
30
|
+
* still read Firestore through `input.context`.
|
|
31
|
+
*
|
|
32
|
+
* Best-effort, exactly like the built-in session teardown it precedes: it runs after the command's
|
|
33
|
+
* result is already on stdout, so a throw is swallowed (surfaced under `--verbose`) rather than
|
|
34
|
+
* changing what the caller sees or the exit code they get.
|
|
35
|
+
*
|
|
36
|
+
* A teardown may run when {@link CliSetupFunction} did NOT — an invocation that failed validation or
|
|
37
|
+
* authentication never reaches setup — so it must not assume setup's side effects are in place.
|
|
38
|
+
*/
|
|
39
|
+
export type CliTeardownFunction = (input: CliLifecycleHookInput) => PromiseOrValue<void>;
|
|
40
|
+
/**
|
|
41
|
+
* The app-supplied lifecycle hooks accepted by `createCli` / `runCli`.
|
|
42
|
+
*/
|
|
43
|
+
export interface CliLifecycleHooks {
|
|
44
|
+
/**
|
|
45
|
+
* Run once before the command's handler. See {@link CliSetupFunction}.
|
|
46
|
+
*/
|
|
47
|
+
readonly setup?: Maybe<CliSetupFunction>;
|
|
48
|
+
/**
|
|
49
|
+
* Run once after the parser settles, before the direct-Firestore session is closed. See
|
|
50
|
+
* {@link CliTeardownFunction}.
|
|
51
|
+
*/
|
|
52
|
+
readonly teardown?: Maybe<CliTeardownFunction>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Input for {@link cliLifecycleRunner}.
|
|
56
|
+
*/
|
|
57
|
+
export interface CliLifecycleRunnerInput extends CliLifecycleHooks {
|
|
58
|
+
readonly cliName: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The once-per-invocation view of an app's {@link CliLifecycleHooks}.
|
|
62
|
+
*/
|
|
63
|
+
export interface CliLifecycleRunner {
|
|
64
|
+
/**
|
|
65
|
+
* Runs the app's `setup` hook, at most once, rethrowing whatever it throws.
|
|
66
|
+
*/
|
|
67
|
+
readonly runSetup: (context: Maybe<CliContext>) => Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Runs the app's `teardown` hook, at most once, swallowing whatever it throws.
|
|
70
|
+
*/
|
|
71
|
+
readonly runTeardown: (context: Maybe<CliContext>) => Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Wraps an app's lifecycle hooks so each runs at most once per process.
|
|
75
|
+
*
|
|
76
|
+
* The once-only guard is not a convenience: yargs re-runs a global middleware for every COMMAND
|
|
77
|
+
* LEVEL it parses, so a nested command (`action worker export`) enters the setup middleware three
|
|
78
|
+
* times. An app's `setup` is written as "prepare this invocation", not "prepare this command level",
|
|
79
|
+
* and a hook that opened a connection or wrote a file would do it three times over.
|
|
80
|
+
*
|
|
81
|
+
* @param input - The CLI name and the app's hooks.
|
|
82
|
+
* @returns The guarded runner.
|
|
83
|
+
* @__NO_SIDE_EFFECTS__
|
|
84
|
+
*/
|
|
85
|
+
export declare function cliLifecycleRunner(input: CliLifecycleRunnerInput): CliLifecycleRunner;
|
package/src/lib/runner/run.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { type CliContext } from '../context/cli.context';
|
|
|
5
5
|
import { type DoctorCheck } from '../doctor/doctor.command.factory';
|
|
6
6
|
import { type CliFirestoreQueryManifest, type CliModelManifest } from '../manifest/types';
|
|
7
7
|
import { type CliFirestoreBinding } from '../firestore/firestore.models';
|
|
8
|
+
import { type CliLifecycleHooks, type CliLifecycleRunner } from './lifecycle';
|
|
8
9
|
/**
|
|
9
10
|
* Names of the global options registered by {@link createCli} that are not
|
|
10
11
|
* specific to a single manifest command's payload. Manifest commands hide
|
|
@@ -12,7 +13,7 @@ import { type CliFirestoreBinding } from '../firestore/firestore.models';
|
|
|
12
13
|
* focuses on the schema sections.
|
|
13
14
|
*/
|
|
14
15
|
export declare const STANDARD_GLOBAL_OPTION_NAMES: readonly string[];
|
|
15
|
-
export interface CreateCliInput {
|
|
16
|
+
export interface CreateCliInput extends CliLifecycleHooks {
|
|
16
17
|
readonly cliName: string;
|
|
17
18
|
/**
|
|
18
19
|
* App-specific config/utility commands appended after the built-in `auth`, `env`, and `doctor` commands.
|
|
@@ -145,6 +146,16 @@ export interface CreateCliInput {
|
|
|
145
146
|
* bundler `define` or a generated module).
|
|
146
147
|
*/
|
|
147
148
|
readonly version?: string;
|
|
149
|
+
/**
|
|
150
|
+
* The lifecycle runner guarding this invocation's {@link CliLifecycleHooks}.
|
|
151
|
+
*
|
|
152
|
+
* Passed by {@link runCli} so the setup hook the parser runs and the teardown hook the exit path
|
|
153
|
+
* runs share ONE guard — two runners built from the same hooks would each track their own
|
|
154
|
+
* "already ran" flag, and the pairing between them is the whole contract.
|
|
155
|
+
*
|
|
156
|
+
* @internal Apps pass `setup` / `teardown`, not this.
|
|
157
|
+
*/
|
|
158
|
+
readonly lifecycle?: CliLifecycleRunner;
|
|
148
159
|
/**
|
|
149
160
|
* Optional shell-completion command name. When set, yargs registers
|
|
150
161
|
* `<cli> <completionCommandName>` (defaults to `completion`) that emits a bash/zsh script.
|
|
@@ -177,6 +188,9 @@ export interface CreateCliInput {
|
|
|
177
188
|
* if {@link CreateCliInput.modelManifest} is provided.
|
|
178
189
|
* @param input.firestore - The app-supplied direct-Firestore binding; enables `firestore-get` / `firestore-query`.
|
|
179
190
|
* @param input.firestoreQueryManifest - The generated Firestore query catalog; enables `firestore-queries`.
|
|
191
|
+
* @param input.setup - App hook run once before the command's handler; a throw aborts the command.
|
|
192
|
+
* @param input.teardown - App hook run once after the parser settles, before the Firestore session closes.
|
|
193
|
+
* Only {@link runCli} runs it — a caller that drives `createCli().parse()` itself owns its own teardown.
|
|
180
194
|
* @returns The configured yargs `Argv` ready to be `.parse()`-d.
|
|
181
195
|
* @__NO_SIDE_EFFECTS__
|
|
182
196
|
*/
|