@dereekb/dbx-cli 14.1.0 → 14.3.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.
Files changed (38) hide show
  1. package/eslint/index.esm.js +0 -195
  2. package/eslint/package.json +4 -3
  3. package/firebase-api-manifest/main.js +124 -13
  4. package/firebase-api-manifest/package.json +4 -3
  5. package/firestore-indexes/src/model-firebase-index-schema.d.ts +4 -4
  6. package/firestore-query-manifest/main.js +4 -3
  7. package/firestore-query-manifest/package.json +4 -3
  8. package/generate-firestore-indexes/main.js +3 -2
  9. package/generate-firestore-indexes/package.json +3 -2
  10. package/generate-mcp-manifest/package.json +4 -3
  11. package/generate-route-manifest/package.json +3 -2
  12. package/index.esm.js +4228 -1344
  13. package/lint-cache/package.json +3 -2
  14. package/manifest-extract/index.esm.js +113 -7
  15. package/manifest-extract/package.json +8 -8
  16. package/manifest-extract/src/lib/types.d.ts +10 -0
  17. package/model-test/package.json +2 -2
  18. package/package.json +6 -6
  19. package/route/package.json +7 -7
  20. package/src/lib/auth/index.d.ts +1 -0
  21. package/src/lib/auth/oidc.loopback.d.ts +127 -0
  22. package/src/lib/cache/cache.command.factory.d.ts +44 -0
  23. package/src/lib/cache/data-cache.codec.d.ts +36 -0
  24. package/src/lib/cache/data-cache.d.ts +272 -0
  25. package/src/lib/cache/data-cache.fingerprint.d.ts +109 -0
  26. package/src/lib/cache/data-cache.options.d.ts +98 -0
  27. package/src/lib/cache/index.d.ts +5 -0
  28. package/src/lib/config/env.d.ts +43 -7
  29. package/src/lib/config/env.resolve.d.ts +4 -3
  30. package/src/lib/config/paths.d.ts +11 -1
  31. package/src/lib/firestore/firestore-query.command.d.ts +25 -1
  32. package/src/lib/index.d.ts +1 -0
  33. package/src/lib/runner/run.d.ts +11 -0
  34. package/src/lib/util/browser.d.ts +23 -0
  35. package/src/lib/util/index.d.ts +1 -0
  36. package/src/lib/util/interactive.d.ts +10 -1
  37. package/test/package.json +10 -9
  38. package/validate/package.json +3 -3
@@ -0,0 +1,272 @@
1
+ import { type ArrayOrValue, type Maybe, type Milliseconds } from '@dereekb/util';
2
+ import { type CliDataCacheOptions } from './data-cache.options';
3
+ /**
4
+ * Version of the on-disk index and payload envelopes.
5
+ *
6
+ * Bumped when the envelope shape itself changes. A payload written under a different schema version
7
+ * is a miss, not an error — see {@link CliDataCache.loadData}.
8
+ */
9
+ export declare const CLI_DATA_CACHE_SCHEMA_VERSION = 1;
10
+ /**
11
+ * Name of the index file inside the cache directory.
12
+ */
13
+ export declare const CLI_DATA_CACHE_INDEX_FILE_NAME = "index.json";
14
+ /**
15
+ * One recorded build of one dataset, as held in the index.
16
+ *
17
+ * The payload lives in its own file (see {@link file}) so listing the cache never has to read the
18
+ * data — mirroring the lint cache's per-project files plus an index roll-up.
19
+ */
20
+ export interface CliDataCacheEntry {
21
+ /**
22
+ * Identifier of the cached pipeline stage, e.g. `worker.lineDetails`.
23
+ */
24
+ readonly dataset: string;
25
+ /**
26
+ * The stage version this build was produced by. An entry whose version no longer matches the
27
+ * caller's is a miss.
28
+ */
29
+ readonly datasetVersion: number;
30
+ /**
31
+ * The env the data was read from.
32
+ */
33
+ readonly env: string;
34
+ readonly fingerprint: string;
35
+ /**
36
+ * The NORMALIZED filter this build covers, stored verbatim so `cache list` can show what an entry
37
+ * is a build OF rather than just its digest.
38
+ */
39
+ readonly filter: unknown;
40
+ /**
41
+ * When the build was recorded, as an ISO string.
42
+ */
43
+ readonly builtAt: string;
44
+ /**
45
+ * Number of rows, when the cached value was an array. Absent for a non-array payload.
46
+ */
47
+ readonly itemCount?: Maybe<number>;
48
+ /**
49
+ * Size of the payload file in bytes.
50
+ */
51
+ readonly bytes: number;
52
+ /**
53
+ * Absolute path of the payload file.
54
+ */
55
+ readonly file: string;
56
+ /**
57
+ * The CLI build that produced the entry, when known.
58
+ *
59
+ * Recorded but deliberately NOT part of the fingerprint: fingerprinting on it would invalidate the
60
+ * whole cache on every rebuild of the CLI, which is exactly the workflow this feature is meant to
61
+ * speed up. `cache list` surfaces it so an entry from a different build is visible.
62
+ */
63
+ readonly cliBuildStamp?: Maybe<string>;
64
+ }
65
+ /**
66
+ * The persisted payload envelope.
67
+ */
68
+ export interface CliDataCachePayloadFile {
69
+ readonly schemaVersion: number;
70
+ readonly dataset: string;
71
+ readonly datasetVersion: number;
72
+ readonly env: string;
73
+ readonly fingerprint: string;
74
+ readonly builtAt: string;
75
+ /**
76
+ * The cached value, encoded by the entry's codec.
77
+ */
78
+ readonly payload: unknown;
79
+ }
80
+ /**
81
+ * How a cached value is converted to and from the JSON written on disk.
82
+ *
83
+ * Defaults to the tagged structured codec, which needs no per-dataset work. A dataset whose value
84
+ * holds something the tagged codec cannot represent — a class instance whose identity matters —
85
+ * supplies its own.
86
+ */
87
+ export interface CliDataCacheCodec<T> {
88
+ readonly toJson: (data: T) => unknown;
89
+ readonly fromJson: (raw: unknown) => T;
90
+ }
91
+ /**
92
+ * The default codec: the tagged structured JSON walk.
93
+ */
94
+ export declare const DEFAULT_CLI_DATA_CACHE_CODEC: CliDataCacheCodec<any>;
95
+ /**
96
+ * Narrowing filter accepted by {@link CliDataCache.listEntries} and {@link CliDataCache.removeEntries}.
97
+ */
98
+ export interface CliDataCacheEntryFilter {
99
+ readonly env?: Maybe<string>;
100
+ readonly dataset?: Maybe<string>;
101
+ readonly fingerprint?: Maybe<string>;
102
+ /**
103
+ * Keep only entries built longer ago than this. Used by `cache prune`.
104
+ */
105
+ readonly olderThanMs?: Maybe<Milliseconds>;
106
+ }
107
+ /**
108
+ * Input for {@link CliDataCache.saveData}.
109
+ */
110
+ export interface SaveCliDataCacheInput<T> {
111
+ readonly dataset: string;
112
+ readonly datasetVersion: number;
113
+ readonly env: string;
114
+ readonly filter?: Maybe<unknown>;
115
+ readonly data: T;
116
+ readonly codec?: Maybe<CliDataCacheCodec<T>>;
117
+ }
118
+ /**
119
+ * Input for {@link CliDataCache.loadData}.
120
+ */
121
+ export interface LoadCliDataCacheInput<T> {
122
+ readonly dataset: string;
123
+ readonly datasetVersion: number;
124
+ readonly env: string;
125
+ readonly filter?: Maybe<unknown>;
126
+ readonly codec?: Maybe<CliDataCacheCodec<T>>;
127
+ /**
128
+ * Reject an entry built longer ago than this. Omit for no age limit.
129
+ */
130
+ readonly maxAgeMs?: Maybe<Milliseconds>;
131
+ }
132
+ /**
133
+ * A hit returned by {@link CliDataCache.loadData}.
134
+ */
135
+ export interface CliDataCacheHit<T> {
136
+ readonly data: T;
137
+ readonly entry: CliDataCacheEntry;
138
+ }
139
+ /**
140
+ * On-disk store of recorded dataset builds.
141
+ */
142
+ export interface CliDataCache {
143
+ /**
144
+ * The directory holding the index and every payload file.
145
+ */
146
+ readonly dataCacheDir: string;
147
+ /**
148
+ * Returns every recorded entry matching `filter`, newest build first.
149
+ */
150
+ listEntries(filter?: Maybe<CliDataCacheEntryFilter>): Promise<CliDataCacheEntry[]>;
151
+ /**
152
+ * Reads a recorded build, or `undefined` when there is no usable one.
153
+ *
154
+ * Any reason the entry cannot be used — absent, wrong `datasetVersion`, wrong schema version, past
155
+ * `maxAgeMs`, payload file missing, payload unparsable — is reported the same way: a miss, so the
156
+ * caller rebuilds. A cache is never allowed to turn into a failure.
157
+ */
158
+ loadData<T>(input: LoadCliDataCacheInput<T>): Promise<Maybe<CliDataCacheHit<T>>>;
159
+ /**
160
+ * Records a build, replacing any previous build of the same dataset + filter.
161
+ */
162
+ saveData<T>(input: SaveCliDataCacheInput<T>): Promise<CliDataCacheEntry>;
163
+ /**
164
+ * Removes every entry matching `filter` (and its payload file). Returns what was removed.
165
+ */
166
+ removeEntries(filter?: Maybe<CliDataCacheEntryFilter>): Promise<CliDataCacheEntry[]>;
167
+ }
168
+ export interface CreateCliDataCacheInput {
169
+ /**
170
+ * Directory holding the index and payload files, e.g. `~/.<cliName>/cache`.
171
+ */
172
+ readonly dataCacheDir: string;
173
+ /**
174
+ * Optional stamp identifying the CLI build, recorded on every entry it writes.
175
+ */
176
+ readonly cliBuildStamp?: Maybe<string>;
177
+ }
178
+ /**
179
+ * Creates the on-disk dataset cache.
180
+ *
181
+ * Layout, mirroring the lint cache's per-key files plus a versioned index roll-up:
182
+ *
183
+ * ```
184
+ * <dataCacheDir>/index.json the CliDataCacheEntry roll-up
185
+ * <dataCacheDir>/<env>/<dataset>/<fingerprint>.json one CliDataCachePayloadFile per build
186
+ * ```
187
+ *
188
+ * Everything is written mode 0600. A cached export holds production rows — worker names, emails,
189
+ * billing lines — so the files are readable by the owning user only, the same posture as the token
190
+ * and Firestore-session caches next to them.
191
+ *
192
+ * @param input - The cache inputs.
193
+ * @param input.dataCacheDir - Directory holding the index and payload files.
194
+ * @param input.cliBuildStamp - Optional stamp identifying the CLI build, recorded on written entries.
195
+ * @returns The cache.
196
+ *
197
+ * @__NO_SIDE_EFFECTS__
198
+ */
199
+ export declare function createCliDataCache(input: CreateCliDataCacheInput): CliDataCache;
200
+ /**
201
+ * Whether a recorded build is too old to satisfy a read.
202
+ *
203
+ * @param entry - The entry to test.
204
+ * @param maxAgeMs - The age limit. `null`/`undefined` means no limit.
205
+ * @returns Whether the entry is past the limit.
206
+ *
207
+ * @__NO_SIDE_EFFECTS__
208
+ */
209
+ export declare function isEntryExpired(entry: CliDataCacheEntry, maxAgeMs: Maybe<Milliseconds>): boolean;
210
+ /**
211
+ * The outcome of {@link loadOrBuildCliCachedData}.
212
+ */
213
+ export interface CliCachedDataResult<T> {
214
+ readonly data: T;
215
+ /**
216
+ * Whether `data` came off disk rather than from `build()`.
217
+ */
218
+ readonly fromCache: boolean;
219
+ readonly dataset: string;
220
+ readonly fingerprint: string;
221
+ readonly builtAt: Date;
222
+ /**
223
+ * How old the data is. Zero for a fresh build.
224
+ */
225
+ readonly ageMs: Milliseconds;
226
+ readonly itemCount?: Maybe<number>;
227
+ }
228
+ /**
229
+ * Input for {@link loadOrBuildCliCachedData}.
230
+ */
231
+ export interface LoadOrBuildCliCachedDataInput<T> {
232
+ readonly cache: CliDataCache;
233
+ readonly dataset: string;
234
+ readonly datasetVersion: number;
235
+ readonly env: string;
236
+ /**
237
+ * The inputs this stage's contents depend on — and ONLY those. Anything applied after the stage
238
+ * (output format, export flavour, a row filter the pipeline applies downstream) must be left out,
239
+ * or changing it will needlessly miss.
240
+ */
241
+ readonly filter?: Maybe<unknown>;
242
+ /**
243
+ * The invocation's cache policy. Defaults to {@link DEFAULT_CLI_DATA_CACHE_OPTIONS} (record only).
244
+ */
245
+ readonly options?: Maybe<CliDataCacheOptions>;
246
+ readonly codec?: Maybe<CliDataCacheCodec<T>>;
247
+ /**
248
+ * Produces the data when no recorded build satisfies the run. Only called on a miss, which is what
249
+ * makes nesting these calls resolve a stage chain back-to-front: a hit on a late stage never runs
250
+ * the earlier stages at all.
251
+ */
252
+ readonly build: () => Promise<T>;
253
+ }
254
+ /**
255
+ * Reads a recorded build of a dataset, or produces and records one.
256
+ *
257
+ * @param input - The lookup + build inputs.
258
+ * @returns The data plus where it came from.
259
+ */
260
+ export declare function loadOrBuildCliCachedData<T>(input: LoadOrBuildCliCachedDataInput<T>): Promise<CliCachedDataResult<T>>;
261
+ /**
262
+ * Builds the `cache` provenance block for an output envelope's `meta`.
263
+ *
264
+ * Emitting it on every cached command is what lets a reader tell whether the bytes in front of them
265
+ * came off disk, and how old they are, without re-running anything.
266
+ *
267
+ * @param results - One result, or the per-stage results of a pipeline.
268
+ * @returns The meta block: one object for a single result, an array for several.
269
+ *
270
+ * @__NO_SIDE_EFFECTS__
271
+ */
272
+ export declare function cliDataCacheMeta(results: ArrayOrValue<CliCachedDataResult<unknown>>): Record<string, unknown>;
@@ -0,0 +1,109 @@
1
+ import { type Maybe } from '@dereekb/util';
2
+ /**
3
+ * Number of hex characters of the SHA-256 digest kept as a fingerprint.
4
+ *
5
+ * 16 hex chars is 64 bits of the digest. The namespace being distinguished is "the filters one
6
+ * developer ran against one env for one dataset", so 64 bits is far past the point where a collision
7
+ * is a practical concern, and a short fingerprint keeps a cache path readable.
8
+ */
9
+ export declare const CLI_DATA_CACHE_FINGERPRINT_LENGTH = 16;
10
+ /**
11
+ * Normalizes a filter into its canonical comparison form.
12
+ *
13
+ * The point is that two filters a human would call "the same" produce the SAME fingerprint:
14
+ *
15
+ * - `null`, `undefined`, `''`, and empty arrays are DROPPED, so `{}`, `{ agentId: undefined }`, and
16
+ * `{ tg: [] }` all normalize to `{}`. That is what makes the very common unfiltered export share
17
+ * one cache entry no matter which flags were left off.
18
+ * - Object keys are sorted, so key order in a literal never matters.
19
+ * - Arrays are sorted, because every filter array in practice names a SET (tags, regions, uids,
20
+ * requirement keys). A filter whose array order is meaningful must not be normalized through here.
21
+ * - `Date`s become ISO strings, so a `Date` and the string it serializes to agree.
22
+ *
23
+ * @param filter - The filter value to normalize.
24
+ * @returns The canonical form, or `undefined` when the value drops out entirely.
25
+ *
26
+ * @__NO_SIDE_EFFECTS__
27
+ */
28
+ export declare function normalizeCliCacheFilter(filter: unknown): unknown;
29
+ /**
30
+ * Renders a normalized value as deterministic JSON.
31
+ *
32
+ * Object keys are sorted here as well as in {@link normalizeCliCacheFilter} so the function is
33
+ * order-stable on its own — it is also used to sort array members, which happens before the
34
+ * enclosing object's keys have been walked.
35
+ *
36
+ * @param value - The value to render. Expected to already be normalized.
37
+ * @returns The canonical JSON rendering.
38
+ *
39
+ * @__NO_SIDE_EFFECTS__
40
+ */
41
+ export declare function canonicalCliCacheJson(value: unknown): string;
42
+ /**
43
+ * Input for {@link cliCacheFingerprint}.
44
+ */
45
+ export interface CliCacheFingerprintInput {
46
+ /**
47
+ * Identifier of the cached pipeline stage, e.g. `worker.lineDetails`.
48
+ */
49
+ readonly dataset: string;
50
+ /**
51
+ * The stage's shape/behavior version. Bumping it invalidates every entry for that dataset in one
52
+ * shot — required whenever the stage's output shape OR the code that builds it changes, because a
53
+ * stage rebuilt by newer code is a wrong-output bug rather than a slow one.
54
+ */
55
+ readonly datasetVersion: number;
56
+ /**
57
+ * The env the data was read from. Never omitted: the same filter against staging and prod are
58
+ * different data.
59
+ */
60
+ readonly env: string;
61
+ /**
62
+ * The inputs the stage's contents depend on. MUST exclude anything applied after the stage —
63
+ * output format, export flavour, destination file, and any row filter the pipeline applies in
64
+ * memory downstream.
65
+ */
66
+ readonly filter?: Maybe<unknown>;
67
+ }
68
+ /**
69
+ * Builds the fingerprint identifying one build of one dataset.
70
+ *
71
+ * @param input - The fingerprint inputs.
72
+ * @param input.dataset - Identifier of the cached pipeline stage.
73
+ * @param input.datasetVersion - The stage's shape/behavior version.
74
+ * @param input.env - The env the data was read from.
75
+ * @param input.filter - The inputs the stage's contents depend on.
76
+ * @returns The first {@link CLI_DATA_CACHE_FINGERPRINT_LENGTH} hex characters of the SHA-256 digest.
77
+ *
78
+ * @__NO_SIDE_EFFECTS__
79
+ */
80
+ export declare function cliCacheFingerprint(input: CliCacheFingerprintInput): string;
81
+ /**
82
+ * Builds the index key one cache entry is stored under.
83
+ *
84
+ * @param input - The key parts.
85
+ * @param input.env - The env the data was read from.
86
+ * @param input.dataset - Identifier of the cached pipeline stage.
87
+ * @param input.fingerprint - The fingerprint from {@link cliCacheFingerprint}.
88
+ * @returns The `<env>/<dataset>/<fingerprint>` index key.
89
+ *
90
+ * @__NO_SIDE_EFFECTS__
91
+ */
92
+ export declare function cliDataCacheKey(input: {
93
+ readonly env: string;
94
+ readonly dataset: string;
95
+ readonly fingerprint: string;
96
+ }): string;
97
+ /**
98
+ * Sanitizes one path segment of a cache file path.
99
+ *
100
+ * A dataset id is developer-authored (`worker.lineDetails`, `firestore-query:workers-query`), so it
101
+ * can carry characters — a `:` most notably — that are illegal in a filename on some platforms.
102
+ * Mirrors what the lint cache does to a project name.
103
+ *
104
+ * @param segment - The raw segment.
105
+ * @returns The segment with every character outside `[A-Za-z0-9._-]` replaced by `_`.
106
+ *
107
+ * @__NO_SIDE_EFFECTS__
108
+ */
109
+ export declare function cliDataCachePathSegment(segment: string): string;
@@ -0,0 +1,98 @@
1
+ import { type Hours, type Maybe, type Milliseconds } from '@dereekb/util';
2
+ /**
3
+ * Max age applied by a bare `--cache` with no explicit hour count.
4
+ *
5
+ * A day, because the exports this cache exists for are reporting reads whose underlying data moves
6
+ * on a human timescale — and because a default that is obviously conservative is easier to reason
7
+ * about than one tuned per dataset.
8
+ */
9
+ export declare const DEFAULT_CLI_DATA_CACHE_MAX_AGE_HOURS: Hours;
10
+ /**
11
+ * Names of the global cache options registered by `createCli`, so manifest commands can hide them
12
+ * from a focused `--help` alongside the other standard globals.
13
+ */
14
+ export declare const CLI_DATA_CACHE_GLOBAL_OPTION_NAMES: readonly string[];
15
+ /**
16
+ * The resolved cache policy for one CLI invocation.
17
+ *
18
+ * Reads and writes are separate on purpose: recording a build is what makes "when was this data last
19
+ * built" automatic and free, while READING a recorded build is opt-in, so no plain command ever
20
+ * silently returns data that is not live.
21
+ */
22
+ export interface CliDataCacheOptions {
23
+ /**
24
+ * Whether a recorded build may satisfy this run.
25
+ */
26
+ readonly read: boolean;
27
+ /**
28
+ * Whether this run records what it builds.
29
+ */
30
+ readonly write: boolean;
31
+ /**
32
+ * How old a recorded build may be and still satisfy this run. `undefined` means no age limit —
33
+ * any recorded build is acceptable.
34
+ */
35
+ readonly maxAgeMs?: Maybe<Milliseconds>;
36
+ }
37
+ /**
38
+ * The default policy: record every build, read none of them back.
39
+ */
40
+ export declare const DEFAULT_CLI_DATA_CACHE_OPTIONS: CliDataCacheOptions;
41
+ /**
42
+ * The policy `--no-cache` selects: neither read nor record.
43
+ */
44
+ export declare const DISABLED_CLI_DATA_CACHE_OPTIONS: CliDataCacheOptions;
45
+ /**
46
+ * Publishes the resolved cache policy for the current invocation.
47
+ *
48
+ * Called from the output middleware, which runs for config and API commands alike.
49
+ *
50
+ * @param options - The resolved policy.
51
+ */
52
+ export declare function configureCliDataCacheOptions(options: CliDataCacheOptions): void;
53
+ /**
54
+ * Returns the cache policy for the current invocation.
55
+ *
56
+ * @returns The resolved policy, or {@link DEFAULT_CLI_DATA_CACHE_OPTIONS} when the middleware has
57
+ * not run (a programmatic caller, or a test driving a handler directly).
58
+ */
59
+ export declare function cliDataCacheOptions(): CliDataCacheOptions;
60
+ /**
61
+ * The shape the `--cache` / `--refresh` flags parse into.
62
+ *
63
+ * `cache` is declared to yargs as a string so `--cache` and `--cache=<hours>` are both accepted;
64
+ * yargs' boolean negation then turns `--no-cache` into `false` on the same key.
65
+ */
66
+ export interface CliDataCacheArgv {
67
+ readonly cache?: Maybe<string | false>;
68
+ readonly refresh?: Maybe<boolean>;
69
+ }
70
+ /**
71
+ * Validates the `--cache` flag at parse time.
72
+ *
73
+ * Registered as a yargs `.check` rather than left to the middleware because a bare `--cache`
74
+ * immediately before a positional swallows it (`firestore-query --cache workers-query` parses as
75
+ * `cache: 'workers-query'` with no query). Failing loudly on a non-numeric value is what turns that
76
+ * into a clear message instead of a mysteriously empty result.
77
+ *
78
+ * @param argv - The parsed argv.
79
+ * @returns `true` when the flag is well-formed.
80
+ * @throws {Error} When `--cache` carries a value that is not a non-negative number.
81
+ */
82
+ export declare function checkCliDataCacheArgv(argv: CliDataCacheArgv): boolean;
83
+ /**
84
+ * Resolves the cache policy from parsed argv.
85
+ *
86
+ * Precedence, highest first:
87
+ * 1. `--refresh` — rebuild and overwrite. Beats `--cache`, so a wrapper script that always passes
88
+ * `--cache` can still be forced fresh from the command line.
89
+ * 2. `--no-cache` — neither read nor record.
90
+ * 3. `--cache[=<hours>]` — read a build within the age limit. `--cache=0` accepts any age.
91
+ * 4. Nothing — record only.
92
+ *
93
+ * @param argv - The parsed argv.
94
+ * @returns The resolved policy.
95
+ *
96
+ * @__NO_SIDE_EFFECTS__
97
+ */
98
+ export declare function resolveCliDataCacheOptions(argv: CliDataCacheArgv): CliDataCacheOptions;
@@ -0,0 +1,5 @@
1
+ export * from './cache.command.factory';
2
+ export * from './data-cache';
3
+ export * from './data-cache.codec';
4
+ export * from './data-cache.fingerprint';
5
+ export * from './data-cache.options';
@@ -1,4 +1,4 @@
1
- import { type Maybe } from '@dereekb/util';
1
+ import { type Maybe, type OidcClientAuthMethod } from '@dereekb/util';
2
2
  import { type CliTokenEntry } from './token.cache';
3
3
  /**
4
4
  * The default OAuth/OIDC scopes requested by the CLI when none are configured.
@@ -15,7 +15,12 @@ export declare const MODEL_WRITE_OIDC_SCOPES: readonly ["model.create", "model.u
15
15
  /**
16
16
  * The default redirect URI used by the CLI.
17
17
  *
18
- * Opens up to nothing in the browser so the user can copy/paste the resulting token url back into the CLI.
18
+ * The `0` port is a placeholder, not a bindable port: the redirect resolves to nothing in the
19
+ * browser and the user copy/pastes the resulting URL back into the CLI.
20
+ *
21
+ * To have `auth login` capture the redirect automatically instead, configure a redirect URI with a
22
+ * concrete loopback port (e.g. `http://127.0.0.1:8976/callback`) and register that exact URI with
23
+ * the OAuth client — `auth login` binds it and reads the code straight out of the browser redirect.
19
24
  */
20
25
  export declare const DEFAULT_CLI_REDIRECT_URI = "http://127.0.0.1:0/callback";
21
26
  /**
@@ -193,6 +198,14 @@ export declare function isCliFirebaseConfigComplete(firebase: Maybe<CliFirebaseC
193
198
  * @returns `true` when emulators are configured and not explicitly disabled.
194
199
  */
195
200
  export declare function cliFirebaseEmulatorsInUse(firebase: Maybe<CliFirebaseConfig>): boolean;
201
+ /**
202
+ * The OAuth client's registered `token_endpoint_auth_method`, as far as the CLI needs to model it.
203
+ *
204
+ * Wider than {@link OidcClientAuthMethod} by exactly one member: `'none'`, the public-client case.
205
+ * That value is meaningless to the protocol layer — a public client sends no credential, so there is
206
+ * no presentation to choose — but it is precisely what the CLI needs in order to stop asking for one.
207
+ */
208
+ export type OidcCliTokenEndpointAuthMethod = OidcClientAuthMethod | 'none';
196
209
  /**
197
210
  * Environment-targeting config for a CLI invocation.
198
211
  *
@@ -231,12 +244,30 @@ export interface CliEnvConfig {
231
244
  readonly clientId?: string;
232
245
  /**
233
246
  * The OAuth client secret registered with the target app.
247
+ *
248
+ * Absent for a public client — see {@link tokenEndpointAuthMethod}.
234
249
  */
235
250
  readonly clientSecret?: string;
236
251
  /**
237
- * The redirect URI registered with the OAuth client. The CLI does not bind a server — it parses
238
- * the URL the user pastes back, so this can be any value the OIDC provider accepts as a
239
- * registered redirect URI (e.g. `http://127.0.0.1:0/callback` or another loopback/placeholder URL).
252
+ * How the client authenticates at the token endpoint, mirroring the OAuth client's registered
253
+ * `token_endpoint_auth_method`.
254
+ *
255
+ * `'none'` marks a PUBLIC client: it holds no secret and proves itself with PKCE instead. Recording
256
+ * it lets `auth setup` skip the client-secret prompt outright rather than asking for a credential
257
+ * that must not exist — the prompt cannot infer this, since "no secret yet" and "never a secret"
258
+ * look identical from an empty config.
259
+ *
260
+ * Omit to leave it unknown, which keeps the existing prompt-and-accept-empty behaviour.
261
+ */
262
+ readonly tokenEndpointAuthMethod?: OidcCliTokenEndpointAuthMethod;
263
+ /**
264
+ * The redirect URI registered with the OAuth client.
265
+ *
266
+ * When this is an `http:` loopback URI with a concrete, non-zero port (e.g.
267
+ * `http://127.0.0.1:8976/callback`), `auth login` binds that port and reads the authorization code
268
+ * straight out of the browser redirect. Any other value — including the
269
+ * {@link DEFAULT_CLI_REDIRECT_URI} `:0` placeholder and out-of-band URNs — falls back to the user
270
+ * pasting the redirect URL back into the CLI.
240
271
  *
241
272
  * Defaults to {@link DEFAULT_CLI_REDIRECT_URI}.
242
273
  */
@@ -312,10 +343,15 @@ export declare function applyEnvVarOverrides(input: EnvVarOverrideInput): Maybe<
312
343
  /**
313
344
  * Returns true when the env has the minimum fields needed to attempt an OAuth login or token refresh.
314
345
  *
346
+ * `clientSecret` is NOT among them. A CLI is a public client in the usual case
347
+ * (`token_endpoint_auth_method: 'none'`), authenticating with PKCE rather than a secret, so requiring
348
+ * one here would report a correctly-configured public env as incomplete — and send the caller back to
349
+ * `auth setup` to supply a credential the provider would then reject.
350
+ *
315
351
  * @param env - The env config to check.
316
- * @returns `true` when `apiBaseUrl`, `oidcIssuer`, `clientId`, `clientSecret`, and `redirectUri` are all present and non-empty.
352
+ * @returns `true` when `apiBaseUrl`, `oidcIssuer`, `clientId`, and `redirectUri` are all present and non-empty.
317
353
  */
318
- export declare function isCliEnvConfigComplete(env: Maybe<CliEnvConfig>): env is Required<Pick<CliEnvConfig, 'apiBaseUrl' | 'oidcIssuer' | 'clientId' | 'clientSecret' | 'redirectUri'>> & CliEnvConfig;
354
+ export declare function isCliEnvConfigComplete(env: Maybe<CliEnvConfig>): env is Required<Pick<CliEnvConfig, 'apiBaseUrl' | 'oidcIssuer' | 'clientId' | 'redirectUri'>> & CliEnvConfig;
319
355
  /**
320
356
  * Inputs to {@link readEnvTokenEntry}.
321
357
  */
@@ -8,7 +8,7 @@ import { type CliPaths } from './paths';
8
8
  * Guarantees that the OIDC client fields are present so callers can pass them through to the
9
9
  * OIDC client/token helpers without a non-null assertion or extra runtime check.
10
10
  */
11
- export type CliEnvConfigComplete = Required<Pick<CliEnvConfig, 'apiBaseUrl' | 'oidcIssuer' | 'clientId' | 'clientSecret' | 'redirectUri'>> & CliEnvConfig;
11
+ export type CliEnvConfigComplete = Required<Pick<CliEnvConfig, 'apiBaseUrl' | 'oidcIssuer' | 'clientId' | 'redirectUri'>> & CliEnvConfig;
12
12
  /**
13
13
  * Builds the conventional `<CLINAME>_ENV` env var name from the CLI binary name.
14
14
  *
@@ -71,8 +71,9 @@ export declare function resolveCliEnv(input: ResolveCliEnvInput): Promise<Resolv
71
71
  export interface ResolveCliEnvOrThrowInput extends ResolveCliEnvInput {
72
72
  /**
73
73
  * When `true`, throws `AUTH_ENV_INCOMPLETE` if the resolved env is missing OIDC fields
74
- * (apiBaseUrl, oidcIssuer, clientId, clientSecret, redirectUri). The returned env is narrowed
75
- * to {@link CliEnvConfigComplete}. Defaults to `false`.
74
+ * (apiBaseUrl, oidcIssuer, clientId, redirectUri). `clientSecret` is not required — see
75
+ * {@link isCliEnvConfigComplete}. The returned env is narrowed to {@link CliEnvConfigComplete}.
76
+ * Defaults to `false`.
76
77
  */
77
78
  readonly requireComplete?: boolean;
78
79
  }
@@ -6,6 +6,15 @@ export interface CliPaths {
6
6
  readonly configFilePath: string;
7
7
  readonly tokenCachePath: string;
8
8
  readonly firestoreSessionCachePath: string;
9
+ /**
10
+ * Directory holding the recorded query/export dataset cache — an index file plus one payload file
11
+ * per recorded build.
12
+ *
13
+ * A directory rather than a single file because a payload here is a whole dataset (a 20k-row
14
+ * export), not the handful of fields the token and session caches hold: one file per build is what
15
+ * keeps listing the cache from having to read all of it.
16
+ */
17
+ readonly dataCacheDir: string;
9
18
  }
10
19
  export interface CliPathsConfig {
11
20
  /**
@@ -26,11 +35,12 @@ export interface CliPathsConfig {
26
35
  * - `<configDir>/config.json` — the persistent CLI config (envs, output settings)
27
36
  * - `<configDir>/.tokens.json` — per-env access/refresh token cache (mode 0600)
28
37
  * - `<configDir>/.firestore-sessions.json` — per-env direct-Firestore session cache (mode 0600)
38
+ * - `<configDir>/cache/` — recorded query/export dataset cache (mode 0600 throughout)
29
39
  *
30
40
  * @param config - The path-building inputs.
31
41
  * @param config.cliName - The CLI's binary name; the default config dir is `~/.<cliName>`.
32
42
  * @param config.configDirOverride - Optional override that replaces the default config directory verbatim (used by tests).
33
- * @returns The {@link CliPaths} pointing at `configDir`, the config file, the token cache file, and the Firestore session cache file.
43
+ * @returns The {@link CliPaths} pointing at `configDir`, the config file, the token cache file, the Firestore session cache file, and the dataset cache directory.
34
44
  * @__NO_SIDE_EFFECTS__
35
45
  */
36
46
  export declare function buildCliPaths(config: CliPathsConfig): CliPaths;
@@ -1,14 +1,38 @@
1
+ import { type Maybe } from '@dereekb/util';
1
2
  import type { CommandModule } from 'yargs';
3
+ import { type CliDataCache } from '../cache/data-cache';
2
4
  import { type CliFirestoreQueryManifest } from '../manifest/types';
3
5
  /**
4
6
  * Default command name for the Firestore query execution command.
5
7
  */
6
8
  export declare const DEFAULT_FIRESTORE_QUERY_COMMAND_NAME = "firestore-query";
9
+ /**
10
+ * Dataset id prefix under which a `firestore-query` run records its result.
11
+ *
12
+ * One dataset per catalog slug, so `cache list` reads as a list of queries rather than one
13
+ * undifferentiated blob.
14
+ */
15
+ export declare const CLI_FIRESTORE_QUERY_DATASET_PREFIX = "firestore-query";
16
+ /**
17
+ * Version of the recorded `firestore-query` payload.
18
+ *
19
+ * Bump when the result envelope's shape changes, or when the row projection changes what it decodes
20
+ * — a recorded build from older code is a wrong-answer bug, not just a slow one.
21
+ */
22
+ export declare const CLI_FIRESTORE_QUERY_DATASET_VERSION = 1;
7
23
  /**
8
24
  * Options accepted by {@link buildFirestoreQueryCommand}.
9
25
  */
10
26
  export interface BuildFirestoreQueryCommandOptions {
11
27
  readonly commandName?: string;
28
+ /**
29
+ * The dataset cache recorded runs are written to and `--cache` reads from.
30
+ *
31
+ * Supplied by `createCli` so the `cache` command group and this command share ONE instance (and
32
+ * so a test can point both at a temp directory). Omitted, it falls back to the CLI's own
33
+ * `<configDir>/cache`.
34
+ */
35
+ readonly dataCache?: Maybe<CliDataCache>;
12
36
  }
13
37
  /**
14
38
  * Builds the top-level `firestore-query <query>` command.
@@ -17,7 +41,7 @@ export interface BuildFirestoreQueryCommandOptions {
17
41
  * as the authenticated user.
18
42
  *
19
43
  * @param manifest - The generated Firestore query manifest.
20
- * @param options - Optional command-name override.
44
+ * @param options - Optional command-name override and the shared dataset cache.
21
45
  * @returns A yargs `CommandModule` for `runCli({ apiCommands })`.
22
46
  *
23
47
  * @__NO_SIDE_EFFECTS__