@dereekb/dbx-cli 13.37.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 +12672 -6861
- package/index.esm.js +12615 -6863
- 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
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { type CliFirestoreBinding } from '../firestore/firestore.models';
|
|
2
|
+
import { type CliReadSourceReason } from '../firestore/firestore.read';
|
|
3
|
+
import { type CliFirestoreSessionContext } from '../firestore/firestore.session';
|
|
4
|
+
import { type CliFirestoreQueryManifest, type CliModelManifest } from '../manifest/types';
|
|
5
|
+
import { type DoctorCheck } from './doctor.command.factory';
|
|
6
|
+
/**
|
|
7
|
+
* Name reported by the check {@link createFirestoreSessionDoctorCheck} builds.
|
|
8
|
+
*/
|
|
9
|
+
export declare const FIRESTORE_SESSION_DOCTOR_CHECK_NAME = "firestore-session";
|
|
10
|
+
/**
|
|
11
|
+
* An app-supplied read that proves the direct Firestore connection works end to end.
|
|
12
|
+
*
|
|
13
|
+
* The framework cannot know which collection is rules-protected in a given app, so the last hop —
|
|
14
|
+
* "App Check accepted and a rules-protected read succeeded" — is delegated. Point it at a collection
|
|
15
|
+
* an admin may list and a non-admin may not (e.g. `getDocs(query(collection(firestore, 'wk'), limit(1)))`).
|
|
16
|
+
*
|
|
17
|
+
* Return anything JSON-serializable; it is surfaced on the check's `detail.probe`.
|
|
18
|
+
*/
|
|
19
|
+
export type FirestoreSessionDoctorProbe = (context: CliFirestoreSessionContext) => Promise<unknown>;
|
|
20
|
+
export interface CreateFirestoreSessionDoctorCheckInput {
|
|
21
|
+
/**
|
|
22
|
+
* The rules-protected read that proves the session is genuinely usable. Strongly recommended —
|
|
23
|
+
* without it the check stops after sign-in and cannot tell whether App Check was accepted.
|
|
24
|
+
*/
|
|
25
|
+
readonly probe?: FirestoreSessionDoctorProbe;
|
|
26
|
+
/**
|
|
27
|
+
* Human-readable label for what the probe reads, surfaced in the check detail (e.g. `list /wk`).
|
|
28
|
+
*/
|
|
29
|
+
readonly probeName?: string;
|
|
30
|
+
/**
|
|
31
|
+
* The same `firestore` binding passed to `runCli`. Doctor checks run PRE-AUTH with no `CliContext`,
|
|
32
|
+
* so the binding cannot be discovered — pass it here and the check reports whether
|
|
33
|
+
* `CliContext.getFirestoreModels` would exist, and therefore whether `--via auto` can ever go direct.
|
|
34
|
+
*/
|
|
35
|
+
readonly firestore?: CliFirestoreBinding;
|
|
36
|
+
/**
|
|
37
|
+
* The same `modelManifest` passed to `runCli`, for the server-only model count.
|
|
38
|
+
*/
|
|
39
|
+
readonly modelManifest?: CliModelManifest;
|
|
40
|
+
/**
|
|
41
|
+
* The same `firestoreQueryManifest` passed to `runCli`, for the invocable query-entry count.
|
|
42
|
+
*/
|
|
43
|
+
readonly firestoreQueryManifest?: CliFirestoreQueryManifest;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The read-routing summary the check reports alongside the session handshake — the `--via auto`
|
|
47
|
+
* decision an operator would otherwise have to infer from three separate facts.
|
|
48
|
+
*/
|
|
49
|
+
export interface FirestoreSessionDoctorReadRouting {
|
|
50
|
+
/**
|
|
51
|
+
* Whether `CliContext.getFirestoreModels` exists — i.e. `runCli` was given a `firestore` binding.
|
|
52
|
+
*/
|
|
53
|
+
readonly getFirestoreModels: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* What `--via auto` would choose given the state this check just observed.
|
|
56
|
+
*/
|
|
57
|
+
readonly readPreference: 'firestore' | 'api';
|
|
58
|
+
readonly reason: CliReadSourceReason;
|
|
59
|
+
/**
|
|
60
|
+
* Query-catalog entries whose factory bound to a real runtime export, and are therefore runnable
|
|
61
|
+
* by `firestore-query`. A non-invocable entry is listed by `firestore-queries` with `INVOCABLE = no`.
|
|
62
|
+
*/
|
|
63
|
+
readonly invocableQueryEntries: number;
|
|
64
|
+
readonly totalQueryEntries: number;
|
|
65
|
+
/**
|
|
66
|
+
* Models the manifest marks `@dbxModelServerOnly` — refused on every `--via` value.
|
|
67
|
+
*/
|
|
68
|
+
readonly serverOnlyModels: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Summarizes the `--via auto` routing decision from what doctor can observe.
|
|
72
|
+
*
|
|
73
|
+
* @param input - The wired bindings/manifests plus whether the session actually opened.
|
|
74
|
+
* @param input.firestore - The `firestore` binding, when supplied.
|
|
75
|
+
* @param input.modelManifest - The model manifest, when supplied.
|
|
76
|
+
* @param input.firestoreQueryManifest - The query manifest, when supplied.
|
|
77
|
+
* @param input.firebaseConfigComplete - Whether the env carries a complete Firebase client config.
|
|
78
|
+
* @param input.sessionOpened - Whether the session handshake succeeded in this run.
|
|
79
|
+
* @returns The routing summary.
|
|
80
|
+
*
|
|
81
|
+
* @__NO_SIDE_EFFECTS__
|
|
82
|
+
*/
|
|
83
|
+
export declare function buildFirestoreSessionDoctorReadRouting(input: {
|
|
84
|
+
readonly firestore?: CliFirestoreBinding;
|
|
85
|
+
readonly modelManifest?: CliModelManifest;
|
|
86
|
+
readonly firestoreQueryManifest?: CliFirestoreQueryManifest;
|
|
87
|
+
readonly firebaseConfigComplete: boolean;
|
|
88
|
+
readonly sessionOpened: boolean;
|
|
89
|
+
}): FirestoreSessionDoctorReadRouting;
|
|
90
|
+
/**
|
|
91
|
+
* Builds the doctor check for the direct-Firestore session path.
|
|
92
|
+
*
|
|
93
|
+
* This is the **fail-loudly surface** for the feature: `CliContext.getFirestoreContext()` throws
|
|
94
|
+
* rather than falling back to the HTTP model API, so `doctor` is where an operator finds out which
|
|
95
|
+
* hop broke. It walks the whole chain in order:
|
|
96
|
+
*
|
|
97
|
+
* 1. the env carries a complete Firebase client config;
|
|
98
|
+
* 2. a usable access token is available (cached, env-supplied, or refreshed);
|
|
99
|
+
* 3. `GET /session/firestore` is reachable and mints a custom token (+ an App Check token when the
|
|
100
|
+
* API is configured with a web `appId`);
|
|
101
|
+
* 4. `signInWithCustomToken` succeeds against the configured project;
|
|
102
|
+
* 5. the app-supplied {@link FirestoreSessionDoctorProbe} performs one rules-protected read.
|
|
103
|
+
*
|
|
104
|
+
* Doctor checks run PRE-AUTH — `DoctorCheckInput` is only `{ cliName, envName, env, config }`, with no
|
|
105
|
+
* token and no `CliContext` — so this loads credentials itself via `buildCliPaths` +
|
|
106
|
+
* `createCliTokenCacheStore`, matching the built-in `token-cache-fresh` / `token-refresh-round-trip`
|
|
107
|
+
* checks.
|
|
108
|
+
*
|
|
109
|
+
* @param input - Optional probe configuration.
|
|
110
|
+
* @returns A {@link DoctorCheck} to append to a CLI's `doctor` check list.
|
|
111
|
+
* @__NO_SIDE_EFFECTS__
|
|
112
|
+
*/
|
|
113
|
+
export declare function createFirestoreSessionDoctorCheck(input?: CreateFirestoreSessionDoctorCheckInput): DoctorCheck;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CommandModule } from 'yargs';
|
|
2
|
+
/**
|
|
3
|
+
* Default command name for the direct-Firestore single-document read.
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_FIRESTORE_GET_COMMAND_NAME = "firestore-get";
|
|
6
|
+
/**
|
|
7
|
+
* Options accepted by {@link buildFirestoreGetCommand}.
|
|
8
|
+
*/
|
|
9
|
+
export interface BuildFirestoreGetCommandOptions {
|
|
10
|
+
readonly commandName?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Builds the top-level `firestore-get <modelOrKey> [key]` command.
|
|
14
|
+
*
|
|
15
|
+
* Reads one document over the direct Firestore connection, through security rules. Positional
|
|
16
|
+
* parsing goes through the same {@link parseGetArgs} the API-backed `get` uses, so inferred-model
|
|
17
|
+
* resolution behaves identically, and the emitted `{ key, data }` is byte-identical to
|
|
18
|
+
* `GetModelOverHttpResult`.
|
|
19
|
+
*
|
|
20
|
+
* There is no `--via` here — the command name IS the transport selection. `get --via firestore` is
|
|
21
|
+
* the routed equivalent for callers that want a fallback.
|
|
22
|
+
*
|
|
23
|
+
* @param options - Optional command-name override.
|
|
24
|
+
* @returns A yargs `CommandModule` for `runCli({ apiCommands })`.
|
|
25
|
+
*
|
|
26
|
+
* @__NO_SIDE_EFFECTS__
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildFirestoreGetCommand(options?: BuildFirestoreGetCommandOptions): CommandModule;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CommandModule } from 'yargs';
|
|
2
|
+
import { type CliFirestoreQueryManifest } from '../manifest/types';
|
|
3
|
+
/**
|
|
4
|
+
* Default command name for the Firestore query catalog command.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_FIRESTORE_QUERIES_COMMAND_NAME = "firestore-queries";
|
|
7
|
+
/**
|
|
8
|
+
* Options accepted by {@link buildFirestoreQueriesCommand}.
|
|
9
|
+
*/
|
|
10
|
+
export interface BuildFirestoreQueriesCommandOptions {
|
|
11
|
+
readonly commandName?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Builds the top-level `firestore-queries [query]` catalog command.
|
|
15
|
+
*
|
|
16
|
+
* Registered as a CONFIG command, not an API command — browsing the catalog is a documentation
|
|
17
|
+
* read, and demanding a login to see what queries exist would be the wrong trade. This mirrors the
|
|
18
|
+
* existing `model-info` (config, auth-bypassed) vs `model <m> get` (api, post-auth) split.
|
|
19
|
+
*
|
|
20
|
+
* @param manifest - The generated Firestore query manifest.
|
|
21
|
+
* @param options - Optional command-name override.
|
|
22
|
+
* @returns A yargs `CommandModule` for `runCli({ configCommands })`.
|
|
23
|
+
*
|
|
24
|
+
* @__NO_SIDE_EFFECTS__
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildFirestoreQueriesCommand(manifest: CliFirestoreQueryManifest, options?: BuildFirestoreQueriesCommandOptions): CommandModule;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CommandModule } from 'yargs';
|
|
2
|
+
import { type CliFirestoreQueryManifest } from '../manifest/types';
|
|
3
|
+
/**
|
|
4
|
+
* Default command name for the Firestore query execution command.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_FIRESTORE_QUERY_COMMAND_NAME = "firestore-query";
|
|
7
|
+
/**
|
|
8
|
+
* Options accepted by {@link buildFirestoreQueryCommand}.
|
|
9
|
+
*/
|
|
10
|
+
export interface BuildFirestoreQueryCommandOptions {
|
|
11
|
+
readonly commandName?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Builds the top-level `firestore-query <query>` command.
|
|
15
|
+
*
|
|
16
|
+
* Executes a catalog entry over the direct Firestore connection, through the app's security rules
|
|
17
|
+
* as the authenticated user.
|
|
18
|
+
*
|
|
19
|
+
* @param manifest - The generated Firestore query manifest.
|
|
20
|
+
* @param options - Optional command-name override.
|
|
21
|
+
* @returns A yargs `CommandModule` for `runCli({ apiCommands })`.
|
|
22
|
+
*
|
|
23
|
+
* @__NO_SIDE_EFFECTS__
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildFirestoreQueryCommand(manifest: CliFirestoreQueryManifest, options?: BuildFirestoreQueryCommandOptions): CommandModule;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type FirebaseAppModelContext, type FirebaseModelsService } from '@dereekb/firebase';
|
|
2
|
+
import { type Maybe } from '@dereekb/util';
|
|
3
|
+
import { type GetModelOverHttpResult, type GetMultipleModelsOverHttpResult } from '../api/call-model.client';
|
|
4
|
+
import { type CliContext } from '../context/cli.context';
|
|
5
|
+
import { type CliFirestoreBinding, type CliFirestoreBindingInput, type CliFirestoreModels } from './firestore.models';
|
|
6
|
+
/**
|
|
7
|
+
* A callable that resolves the app's TYPED direct-Firestore view for the current invocation, plus the
|
|
8
|
+
* binding to wire the CLI with.
|
|
9
|
+
*
|
|
10
|
+
* The one place an app names its `<X>FirestoreCollections`: `cliFirestoreAccessorFactory` captures
|
|
11
|
+
* both `C` and `Y` at the app site, and hands them back on every call — so an action reads through
|
|
12
|
+
* `collections.guestbookCollection` and `serviceFor('guestbook')` at the app's real types instead of
|
|
13
|
+
* rebuilding the collections itself to recover them.
|
|
14
|
+
*
|
|
15
|
+
* @template C - The app's collections type, e.g. `DemoFirestoreCollections`.
|
|
16
|
+
* @template Y - The app's `<app>FirebaseModelServices` type.
|
|
17
|
+
*/
|
|
18
|
+
export type CliFirestoreAccessorFactory<C extends object, Y extends FirebaseModelsService<any, any>> = ((context: CliContext) => Promise<CliFirestoreAccessor<C, Y>>) & {
|
|
19
|
+
/**
|
|
20
|
+
* The binding to hand `runCli({ firestore })`, `buildTestCliContext({ firestore })`, and
|
|
21
|
+
* `createFirestoreSessionDoctorCheck({ firestore })`.
|
|
22
|
+
*
|
|
23
|
+
* Sharing THIS object — rather than calling `cliFirestoreBinding` again with the same arguments —
|
|
24
|
+
* is what lets the accessor reuse the context's memoized collections instead of building a second
|
|
25
|
+
* copy: the reuse branch is an identity check, and two separate `cliFirestoreBinding` calls produce
|
|
26
|
+
* two non-identical bindings.
|
|
27
|
+
*/
|
|
28
|
+
readonly binding: CliFirestoreBinding<C>;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The typed direct-Firestore view an app's actions read through.
|
|
32
|
+
*
|
|
33
|
+
* A {@link CliFirestoreModels} carrying the app's real `C` / `Y`, plus the two read helpers, so an
|
|
34
|
+
* action never has to import `getModelOverFirestore` alongside it.
|
|
35
|
+
*
|
|
36
|
+
* @template C - The app's collections type.
|
|
37
|
+
* @template Y - The app's `<app>FirebaseModelServices` type.
|
|
38
|
+
*/
|
|
39
|
+
export type CliFirestoreAccessor<C extends object, Y extends FirebaseModelsService<any, any>> = CliFirestoreModels<C, Y> & {
|
|
40
|
+
/**
|
|
41
|
+
* Reads one document by model key, returning the same `{ key, data }` envelope
|
|
42
|
+
* `GET /model/<type>/get` returns.
|
|
43
|
+
*/
|
|
44
|
+
readonly readModel: <T = unknown>(modelType: string, key: string) => Promise<GetModelOverHttpResult<Maybe<T>>>;
|
|
45
|
+
/**
|
|
46
|
+
* Batch-reads documents by model key, returning the same `{ results, errors }` envelope the model
|
|
47
|
+
* API's `getMany` returns.
|
|
48
|
+
*/
|
|
49
|
+
readonly readMultipleModels: <T = unknown>(modelType: string, keys: ReadonlyArray<string>) => Promise<GetMultipleModelsOverHttpResult<Maybe<T>>>;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Registers an app's collections factory + model services once, and returns the accessor its actions
|
|
53
|
+
* read through.
|
|
54
|
+
*
|
|
55
|
+
* Resolution order on each call, three cases:
|
|
56
|
+
*
|
|
57
|
+
* 1. the context was wired with THIS binding (`models.binding === binding`) → reuse its memoized
|
|
58
|
+
* view. The identity check is what makes the `object` → `C` narrowing sound: `collections`
|
|
59
|
+
* provably came from `binding.collections`, which returns `C`.
|
|
60
|
+
* 2. the context has NO binding → build a view from `requireCliFirestoreSession(context)`, so an app
|
|
61
|
+
* can use the accessor in its actions without also passing `runCli({ firestore })`.
|
|
62
|
+
* 3. the context was wired with a DIFFERENT binding → build a second view and `verboseLog` about it,
|
|
63
|
+
* rather than silently asserting that someone else's `collections` is a `C`.
|
|
64
|
+
*
|
|
65
|
+
* @param input - The app's collections factory, model services, and optional per-model override.
|
|
66
|
+
* @returns The callable accessor, carrying {@link CliFirestoreAccessorFactory.binding}.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* export const demoCliFirestore = cliFirestoreAccessorFactory({
|
|
71
|
+
* collections: makeDemoFirestoreCollections,
|
|
72
|
+
* models: demoFirebaseModelServices
|
|
73
|
+
* });
|
|
74
|
+
*
|
|
75
|
+
* // wiring
|
|
76
|
+
* runCli({ firestore: demoCliFirestore.binding });
|
|
77
|
+
*
|
|
78
|
+
* // in an action
|
|
79
|
+
* const { collections, session } = await demoCliFirestore(input.context);
|
|
80
|
+
* const entries = await collections.guestbookCollection.queryDocument(...).getDocs();
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @__NO_SIDE_EFFECTS__
|
|
84
|
+
*/
|
|
85
|
+
export declare function cliFirestoreAccessorFactory<C extends object, Y extends FirebaseModelsService<any, FirebaseAppModelContext<C>>>(input: CliFirestoreBindingInput<C, Y>): CliFirestoreAccessorFactory<C, Y>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type FirestoreCollectionLike, type FirestoreModelKey, type FirestoreModelType } from '@dereekb/firebase';
|
|
2
|
+
import { type CliFirestoreQueryScope } from '../manifest/types';
|
|
3
|
+
import { type CliFirestoreModels } from './firestore.models';
|
|
4
|
+
/**
|
|
5
|
+
* Input for {@link cliFirestoreCollectionForQuery}.
|
|
6
|
+
*/
|
|
7
|
+
export interface CliFirestoreCollectionForQueryInput {
|
|
8
|
+
readonly models: CliFirestoreModels;
|
|
9
|
+
readonly modelType: FirestoreModelType;
|
|
10
|
+
/**
|
|
11
|
+
* The entry's declared scope. Together with {@link isNested} it decides whether `--parent` is
|
|
12
|
+
* optional, required, or rejected.
|
|
13
|
+
*/
|
|
14
|
+
readonly scope: CliFirestoreQueryScope;
|
|
15
|
+
/**
|
|
16
|
+
* Whether the model is a subcollection of some parent.
|
|
17
|
+
*/
|
|
18
|
+
readonly isNested: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The parent document key to narrow to, from `--parent`.
|
|
21
|
+
*/
|
|
22
|
+
readonly parentKey?: FirestoreModelKey;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolves the collection a catalog entry should execute against, applying `--parent` scoping.
|
|
26
|
+
*
|
|
27
|
+
* The `--parent` rules, and why:
|
|
28
|
+
*
|
|
29
|
+
* | entry | `--parent` |
|
|
30
|
+
* | --- | --- |
|
|
31
|
+
* | `COLLECTION_GROUP` + nested | optional — narrows the group to one parent |
|
|
32
|
+
* | `COLLECTION` + nested | **required** — the COLLECTION-scope composite index may not exist at group scope, so silently widening would turn a working query into a `FAILED_PRECONDITION` |
|
|
33
|
+
* | not nested | rejected — a root collection has no parent to scope to |
|
|
34
|
+
*
|
|
35
|
+
* Narrowing is derived generically rather than per-model: `FirestoreCollectionGroup.config` is
|
|
36
|
+
* public and `makeFirestoreCollectionGroup` accepts any `queryLike`, so re-pointing the group's
|
|
37
|
+
* `queryLike` at `firestoreContext.subcollection(parentRef, collectionName)` scopes it to one
|
|
38
|
+
* parent. `docAtPath` needs no parent modelType — the parent key IS the path.
|
|
39
|
+
*
|
|
40
|
+
* @param input - The models view, the entry's model/scope/nesting, and the optional parent key.
|
|
41
|
+
* @returns The collection to query.
|
|
42
|
+
* @throws {CliError} When `--parent` is missing where required, supplied where rejected, or the registered collection cannot be scoped.
|
|
43
|
+
*/
|
|
44
|
+
export declare function cliFirestoreCollectionForQuery(input: CliFirestoreCollectionForQueryInput): FirestoreCollectionLike<unknown>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type CliErrorMapper } from '../util/output';
|
|
2
|
+
/**
|
|
3
|
+
* Maps a client-SDK `FirebaseError` onto the CLI's error envelope.
|
|
4
|
+
*
|
|
5
|
+
* Without this a rules rejection falls through `buildErrorOutput` to a bare
|
|
6
|
+
* `{ ok: false, code: 'ERROR' }` — indistinguishable from any other failure, which is exactly the
|
|
7
|
+
* wrong answer for the one error the direct path exists to surface honestly.
|
|
8
|
+
*
|
|
9
|
+
* @param error - The thrown value.
|
|
10
|
+
* @returns The mapped envelope, or `undefined` to defer to the built-in branches.
|
|
11
|
+
*
|
|
12
|
+
* @__NO_SIDE_EFFECTS__
|
|
13
|
+
*/
|
|
14
|
+
export declare const cliFirestoreErrorMapper: CliErrorMapper;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { type FirebaseAppModelContext, type FirebaseModelServiceGetter, type FirebaseModelsService, type FirebaseModelsServiceTypes, type FirestoreCollectionLike, type FirestoreContext, type FirestoreDocument, type FirestoreModelKey, type FirestoreModelType, type InContextFirebaseModelCollectionLoader, type InContextFirebaseModelLoader } from '@dereekb/firebase';
|
|
2
|
+
import { type Maybe } from '@dereekb/util';
|
|
3
|
+
import { type CliContext } from '../context/cli.context';
|
|
4
|
+
import { type CliFirestoreSessionContext } from './firestore.session';
|
|
5
|
+
/**
|
|
6
|
+
* The slice of a `FirebaseModelService` the CLI's generic direct-Firestore reads need.
|
|
7
|
+
*
|
|
8
|
+
* Only the two members that never touch `roleMapForModel`: `dbx-cli` reads through Firestore
|
|
9
|
+
* security rules, not through the app's role map, so the permission half of the service is
|
|
10
|
+
* deliberately unused here.
|
|
11
|
+
*/
|
|
12
|
+
export interface CliFirestoreModelService {
|
|
13
|
+
readonly loadModelForKey: (key: FirestoreModelKey) => FirestoreDocument<unknown>;
|
|
14
|
+
readonly getFirestoreCollection: () => FirestoreCollectionLike<unknown>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The callable shape of an app's `<app>FirebaseModelServices`, erased to what the CLI needs.
|
|
18
|
+
*/
|
|
19
|
+
export type CliFirestoreModelsService = ((modelType: FirestoreModelType, context: {
|
|
20
|
+
readonly app: unknown;
|
|
21
|
+
}) => CliFirestoreModelService) & {
|
|
22
|
+
allTypes(): FirestoreModelType[];
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Input for a {@link CliFirestoreBinding.collectionForModel} override.
|
|
26
|
+
*/
|
|
27
|
+
export interface CliFirestoreCollectionForModelInput {
|
|
28
|
+
readonly collections: object;
|
|
29
|
+
readonly modelType: FirestoreModelType;
|
|
30
|
+
readonly parentKey?: FirestoreModelKey;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The one app-supplied hook that makes generic direct-Firestore commands possible.
|
|
34
|
+
*
|
|
35
|
+
* `dbx-cli` cannot import an app's collections factory — that is the whole reason there is no
|
|
36
|
+
* generic direct command without this. Supplying it wires `firestore-get` / `firestore-query`
|
|
37
|
+
* for EVERY registered model at once; there is no per-model codegen.
|
|
38
|
+
*
|
|
39
|
+
* `C` appears in exactly one position — the RETURN of {@link collections} — and that is deliberate.
|
|
40
|
+
* Because the use is purely covariant, `CliFirestoreBinding<DemoFirestoreCollections>` is assignable
|
|
41
|
+
* to `CliFirestoreBinding<object>`, which is what lets `CreateCliInput.firestore` keep accepting a
|
|
42
|
+
* typed binding with no cast and keeps generics out of `CliContext` / `runCli` entirely.
|
|
43
|
+
*
|
|
44
|
+
* Adding `C` (or the models generic `Y`) to {@link models} or to
|
|
45
|
+
* {@link CliFirestoreCollectionForModelInput} would put it in a PARAMETER position, and under
|
|
46
|
+
* `strictFunctionTypes` that destroys the assignability above — cascading generics through
|
|
47
|
+
* `CreateCliInput` → `createAuthMiddleware` → `createCliContext` → `CliContext`. Don't.
|
|
48
|
+
*
|
|
49
|
+
* @template C - The app's collections type, e.g. `DemoFirestoreCollections`.
|
|
50
|
+
*/
|
|
51
|
+
export interface CliFirestoreBinding<C extends object = object> {
|
|
52
|
+
/**
|
|
53
|
+
* The app's `make<App>FirestoreCollections`.
|
|
54
|
+
*/
|
|
55
|
+
readonly collections: (firestoreContext: FirestoreContext) => C;
|
|
56
|
+
/**
|
|
57
|
+
* The app's `<app>FirebaseModelServices`.
|
|
58
|
+
*/
|
|
59
|
+
readonly models: CliFirestoreModelsService;
|
|
60
|
+
/**
|
|
61
|
+
* Escape hatch for models whose registered collection cannot be parent-scoped generically
|
|
62
|
+
* (e.g. a paged-items collection). Return `undefined` to fall through to the derived scoping.
|
|
63
|
+
*/
|
|
64
|
+
readonly collectionForModel?: (input: CliFirestoreCollectionForModelInput) => Maybe<FirestoreCollectionLike<unknown>>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Input for {@link cliFirestoreBinding} and {@link cliFirestoreAccessorFactory}.
|
|
68
|
+
*
|
|
69
|
+
* `Y` sits in a PROPERTY position here, which is safe precisely because this is the INPUT type and
|
|
70
|
+
* never the binding: nothing assigns a `CliFirestoreBindingInput` to `CreateCliInput.firestore`, so
|
|
71
|
+
* the variance rule on {@link CliFirestoreBinding} does not apply. Capturing it is what lets
|
|
72
|
+
* `cliFirestoreAccessorFactory` hand back the app's real model types.
|
|
73
|
+
*
|
|
74
|
+
* @template C - The app's collections type, e.g. `DemoFirestoreCollections`.
|
|
75
|
+
* @template Y - The app's `<app>FirebaseModelServices` type.
|
|
76
|
+
*/
|
|
77
|
+
export interface CliFirestoreBindingInput<C extends object, Y extends FirebaseModelsService<any, FirebaseAppModelContext<C>> = FirebaseModelsService<any, FirebaseAppModelContext<C>>> {
|
|
78
|
+
readonly collections: (firestoreContext: FirestoreContext) => C;
|
|
79
|
+
readonly models: Y;
|
|
80
|
+
readonly collectionForModel?: (input: CliFirestoreCollectionForModelInput) => Maybe<FirestoreCollectionLike<unknown>>;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Builds a {@link CliFirestoreBinding} from an app's collections factory and model services.
|
|
84
|
+
*
|
|
85
|
+
* Typed so the app site needs no cast — `C` is carried through on {@link CliFirestoreBinding.collections},
|
|
86
|
+
* and the one erasure the CLI requires (the app's model-service union → {@link CliFirestoreModelService})
|
|
87
|
+
* is absorbed here rather than at every call site.
|
|
88
|
+
*
|
|
89
|
+
* The `{ app: collections }` context handed to the model service is COMPLETE for the two members
|
|
90
|
+
* the CLI uses: `FirebasePermissionContext`, `FirebasePermissionErrorContext` and
|
|
91
|
+
* `FirebaseAuthContext` are all-optional, `firebaseModelsService` self-injects `service`, and
|
|
92
|
+
* neither `loadModelForKey` nor `getFirestoreCollection` reads `auth`.
|
|
93
|
+
*
|
|
94
|
+
* Most apps should reach for {@link cliFirestoreAccessorFactory} instead, which calls this and ALSO
|
|
95
|
+
* hands back a typed accessor for the app's own actions. Use this directly only when the binding is
|
|
96
|
+
* all you need.
|
|
97
|
+
*
|
|
98
|
+
* @param input - The app's collections factory, model services, and optional per-model override.
|
|
99
|
+
* @returns The binding `runCli` accepts, still carrying `C`.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* runCli({
|
|
104
|
+
* firestore: cliFirestoreBinding({ collections: makeDemoFirestoreCollections, models: demoFirebaseModelServices })
|
|
105
|
+
* });
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* @__NO_SIDE_EFFECTS__
|
|
109
|
+
*/
|
|
110
|
+
export declare function cliFirestoreBinding<C extends object>(input: CliFirestoreBindingInput<C>): CliFirestoreBinding<C>;
|
|
111
|
+
/**
|
|
112
|
+
* The default `Y` for {@link CliFirestoreModels}: a registry admitting ANY `FirestoreModelType`, with
|
|
113
|
+
* every model erased to `unknown`.
|
|
114
|
+
*
|
|
115
|
+
* Chosen so the defaulted generic form reproduces the pre-`Y` interface member-for-member — which is
|
|
116
|
+
* why `CliContext.getFirestoreModels?: () => Promise<CliFirestoreModels>` needs no change, and why
|
|
117
|
+
* every string-dispatched caller inside `dbx-cli` still compiles.
|
|
118
|
+
*/
|
|
119
|
+
export type CliErasedFirebaseModelsService = FirebaseModelsService<Record<FirestoreModelType, FirebaseModelServiceGetter<any, unknown>>, any>;
|
|
120
|
+
/**
|
|
121
|
+
* Resolves the app's REAL `T` / `D` for one registered model type out of its `<app>FirebaseModelServices`.
|
|
122
|
+
*
|
|
123
|
+
* Deliberately only the loader half — {@link CliFirestoreModelService} explains why the permission
|
|
124
|
+
* half (`roleMapForModel`, `requireRole`, `use`) is excluded: `dbx-cli` authorizes through
|
|
125
|
+
* `firestore.rules`, not the app's role map, and the `{ app: collections }` context it hands the
|
|
126
|
+
* service carries no `auth` for a role map to read.
|
|
127
|
+
*
|
|
128
|
+
* @template Y - The app's `<app>FirebaseModelServices` type.
|
|
129
|
+
* @template K - The registered model type to resolve.
|
|
130
|
+
*/
|
|
131
|
+
export type CliFirestoreModelServiceForType<Y extends FirebaseModelsService<any, any>, K extends FirebaseModelsServiceTypes<Y>> = Y extends FirebaseModelsService<infer X, infer C> ? (K extends keyof X ? (X[K] extends FirebaseModelServiceGetter<C, infer T, infer D, any> ? InContextFirebaseModelLoader<T, D> & InContextFirebaseModelCollectionLoader<T, D> : never) : never) : never;
|
|
132
|
+
/**
|
|
133
|
+
* The per-invocation, session-bound view of an app's models over a direct Firestore connection.
|
|
134
|
+
*
|
|
135
|
+
* @template C - The app's collections type, e.g. `DemoFirestoreCollections`.
|
|
136
|
+
* @template Y - The app's `<app>FirebaseModelServices` type. Defaults to
|
|
137
|
+
* {@link CliErasedFirebaseModelsService}, which reproduces the erased pre-`Y` interface exactly.
|
|
138
|
+
*/
|
|
139
|
+
export interface CliFirestoreModels<C extends object = object, Y extends FirebaseModelsService<any, any> = CliErasedFirebaseModelsService> {
|
|
140
|
+
readonly session: CliFirestoreSessionContext;
|
|
141
|
+
/**
|
|
142
|
+
* The app's collections object, built against the session's `FirestoreContext`.
|
|
143
|
+
*/
|
|
144
|
+
readonly collections: C;
|
|
145
|
+
/**
|
|
146
|
+
* The binding the app supplied, for consumers that need `collectionForModel`.
|
|
147
|
+
*
|
|
148
|
+
* Typed `CliFirestoreBinding<C>`, NOT `CliFirestoreBinding<C, Y>` — `Y` must never reach the
|
|
149
|
+
* binding, or the binding stops being assignable to `CliFirestoreBinding<object>`. See the
|
|
150
|
+
* variance note on {@link CliFirestoreBinding}.
|
|
151
|
+
*/
|
|
152
|
+
readonly binding: CliFirestoreBinding<C>;
|
|
153
|
+
/**
|
|
154
|
+
* The app's `<app>FirebaseModelServices`, at its real type.
|
|
155
|
+
*
|
|
156
|
+
* Runtime-identical to `binding.models` — the same object — but honestly typed, so callers that
|
|
157
|
+
* hold a typed `CliFirestoreModels` can reach the full service (permissions included) when they
|
|
158
|
+
* genuinely want it, rather than the CLI-erased slice {@link serviceFor} hands back.
|
|
159
|
+
*/
|
|
160
|
+
readonly models: Y;
|
|
161
|
+
readonly allTypes: () => FirebaseModelsServiceTypes<Y>[];
|
|
162
|
+
/**
|
|
163
|
+
* Resolves the model service for `modelType`, validating against {@link allTypes} FIRST.
|
|
164
|
+
*
|
|
165
|
+
* The validation is load-bearing, not defensive: `firebaseModelsService` indexes its factory map
|
|
166
|
+
* and calls the result immediately, so an unregistered type surfaces as a bare `TypeError`
|
|
167
|
+
* instead of a `CliError` the CLI can render.
|
|
168
|
+
*
|
|
169
|
+
* An OVERLOAD PAIR, specific-first, and the order is what keeps this non-breaking. A literal
|
|
170
|
+
* (`serviceFor('guestbook')`) satisfies `K extends FirebaseModelsServiceTypes<Y>` and gets the app's
|
|
171
|
+
* real `GuestbookDocument`; a runtime `string` off argv does not, and falls through to the erased
|
|
172
|
+
* signature — which is why every string-dispatched caller in `dbx-cli` compiles untouched. Under
|
|
173
|
+
* the default erased `Y`, `FirebaseModelsServiceTypes<Y>` widens to `FirestoreModelType` and
|
|
174
|
+
* signature 1 collapses into signature 2.
|
|
175
|
+
*/
|
|
176
|
+
readonly serviceFor: {
|
|
177
|
+
<K extends FirebaseModelsServiceTypes<Y>>(modelType: K): CliFirestoreModelServiceForType<Y, K>;
|
|
178
|
+
(modelType: FirestoreModelType): CliFirestoreModelService;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Resolves a SHORT COLLECTION NAME (`gb`, `gbe`) to the `modelType` the app registered its service
|
|
182
|
+
* under (`guestbook`, `guestbookEntry`).
|
|
183
|
+
*
|
|
184
|
+
* The query catalog records the collection name, not the model type — that is what
|
|
185
|
+
* `firestore.indexes.json` keys on and what `CliModelManifestEntry.collectionPrefix` joins to — but
|
|
186
|
+
* `<app>FirebaseModelServices` is keyed by model type. The join is derived from the registered
|
|
187
|
+
* collections' own `modelIdentity` rather than from a manifest, so it works for every app with no
|
|
188
|
+
* extra wiring.
|
|
189
|
+
*
|
|
190
|
+
* Memoized: resolving walks `allTypes()` building each service's collection until it matches.
|
|
191
|
+
*/
|
|
192
|
+
readonly modelTypeForCollection: (collectionName: string) => FirebaseModelsServiceTypes<Y>;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Resolves the direct-Firestore model view for the current invocation, opening the session on
|
|
196
|
+
* first use.
|
|
197
|
+
*
|
|
198
|
+
* @param context - The live CLI context.
|
|
199
|
+
* @returns The session-bound {@link CliFirestoreModels}.
|
|
200
|
+
* @throws {CliError} When the CLI was not configured with a `firestore` binding, or the session cannot be opened.
|
|
201
|
+
*/
|
|
202
|
+
export declare function requireCliFirestoreModels(context: CliContext): Promise<CliFirestoreModels>;
|
|
203
|
+
/**
|
|
204
|
+
* Input for {@link createCliFirestoreModels}.
|
|
205
|
+
*/
|
|
206
|
+
export interface CreateCliFirestoreModelsInput<C extends object = object> {
|
|
207
|
+
readonly binding: CliFirestoreBinding<C>;
|
|
208
|
+
readonly session: CliFirestoreSessionContext;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Binds an app's {@link CliFirestoreBinding} to an open session.
|
|
212
|
+
*
|
|
213
|
+
* `Y` is inferred from the caller's annotation rather than from `input` — the binding erased its
|
|
214
|
+
* models to {@link CliFirestoreModelsService} on the way in (it must, for variance), so there is
|
|
215
|
+
* nothing left here to infer it from. {@link cliFirestoreAccessorFactory} is what re-attaches the
|
|
216
|
+
* app's real `Y`, having captured it at the app site.
|
|
217
|
+
*
|
|
218
|
+
* @param input - The binding and the open session.
|
|
219
|
+
* @returns The {@link CliFirestoreModels} view.
|
|
220
|
+
*
|
|
221
|
+
* @__NO_SIDE_EFFECTS__
|
|
222
|
+
*/
|
|
223
|
+
export declare function createCliFirestoreModels<C extends object = object, Y extends FirebaseModelsService<any, any> = CliErasedFirebaseModelsService>(input: CreateCliFirestoreModelsInput<C>): CliFirestoreModels<C, Y>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type CliFirestoreQueryManifestEntry } from '../manifest/types';
|
|
2
|
+
/**
|
|
3
|
+
* Input for {@link resolveCliFirestoreQueryArgs}.
|
|
4
|
+
*/
|
|
5
|
+
export interface ResolveCliFirestoreQueryArgsInput {
|
|
6
|
+
readonly entry: CliFirestoreQueryManifestEntry;
|
|
7
|
+
/**
|
|
8
|
+
* The raw `--params` string, if supplied.
|
|
9
|
+
*/
|
|
10
|
+
readonly params?: string;
|
|
11
|
+
/**
|
|
12
|
+
* When true, no date coercion is applied at any level.
|
|
13
|
+
*/
|
|
14
|
+
readonly rawParams?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolves the positional argument list to call a catalog entry's factory with.
|
|
18
|
+
*
|
|
19
|
+
* The contract is POSITIONAL-first, because the single-params-object convention is not universal:
|
|
20
|
+
* the workspace's `*.query.ts` files include zero-arg, single-scalar, two-positional,
|
|
21
|
+
* defaulted-positional, `Maybe<Date>` and `ArrayOrValue<T>` shapes.
|
|
22
|
+
*
|
|
23
|
+
* 1. **omitted** → call with no args; valid only when every param is optional.
|
|
24
|
+
* 2. **JSON array** → spread positionally. Works for every shape.
|
|
25
|
+
* 3. **JSON object** → when the factory takes exactly one param and the key set is not exactly that
|
|
26
|
+
* param's name, the whole object is passed as arg 0 (the single-params-object ergonomic path).
|
|
27
|
+
* Otherwise keys are mapped by param name into positional order.
|
|
28
|
+
* 4. **unparseable** → a `CliError` quoting the parse error and the received string.
|
|
29
|
+
*
|
|
30
|
+
* @param input - The catalog entry and the raw `--params` string.
|
|
31
|
+
* @returns The positional arguments to spread into the factory.
|
|
32
|
+
* @throws {CliError} On any arity, naming, JSON, or date-coercion failure.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveCliFirestoreQueryArgs(input: ResolveCliFirestoreQueryArgsInput): unknown[];
|