@dereekb/dbx-cli 14.0.1 → 14.2.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.
@@ -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';
@@ -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__
@@ -1,6 +1,7 @@
1
1
  export * from './action';
2
2
  export * from './api';
3
3
  export * from './auth';
4
+ export * from './cache';
4
5
  export * from './config';
5
6
  export * from './context';
6
7
  export * from './doctor';
@@ -1,5 +1,6 @@
1
1
  import { type Argv, type CommandModule } from 'yargs';
2
2
  import { type ActionCommandSpec } from '../action/action.command.factory';
3
+ import { type CreateCacheCommandInput } from '../cache/cache.command.factory';
3
4
  import { type CliEnvDefault } from '../config/env';
4
5
  import { type CliContext } from '../context/cli.context';
5
6
  import { type DoctorCheck } from '../doctor/doctor.command.factory';
@@ -127,6 +128,15 @@ export interface CreateCliInput extends CliLifecycleHooks {
127
128
  * Disable the built-in `firestore-get` command even when {@link firestore} is provided.
128
129
  */
129
130
  readonly disableFirestoreGet?: boolean;
131
+ /**
132
+ * Enables the recorded query/export dataset cache: the auth-free `cache` command group, and the
133
+ * `--cache` / `--refresh` global flags that `firestore-query` and app actions honour.
134
+ *
135
+ * Pass `true` for the defaults (`<configDir>/cache`, the CLI's `version` as the recorded build
136
+ * stamp), or an object to override the cache instance (tests point it at a temp dir), the build
137
+ * stamp, or the command name.
138
+ */
139
+ readonly dataCache?: boolean | Omit<CreateCacheCommandInput, 'cliName'>;
130
140
  /**
131
141
  * Test-only override that bypasses the auth middleware entirely and attaches the supplied
132
142
  * {@link CliContext} on every command invocation.
@@ -199,6 +209,7 @@ export interface CreateCliInput extends CliLifecycleHooks {
199
209
  * if {@link CreateCliInput.modelManifest} is provided.
200
210
  * @param input.firestore - The app-supplied direct-Firestore binding; enables `firestore-get` / `firestore-query`.
201
211
  * @param input.firestoreQueryManifest - The generated Firestore query catalog; enables `firestore-queries`.
212
+ * @param input.dataCache - Enables the recorded dataset cache: the `cache` command group plus the `--cache` / `--refresh` global flags.
202
213
  * @param input.manifestGeneratorVersion - The `@dereekb/dbx-cli` version that emitted the app's generated
203
214
  * manifests, for the built-in `cli-build-not-stale` doctor check.
204
215
  * @param input.setup - App hook run once before the command's handler; a throw aborts the command.
package/test/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli/test",
3
- "version": "14.0.1",
3
+ "version": "14.2.0",
4
+ "sideEffects": false,
4
5
  "type": "module",
5
6
  "peerDependencies": {
6
- "@dereekb/date": "14.0.1",
7
- "@dereekb/dbx-cli": "14.0.1",
8
- "@dereekb/firebase": "14.0.1",
9
- "@dereekb/firebase-server/test": "14.0.1",
10
- "@dereekb/model": "14.0.1",
11
- "@dereekb/nestjs": "14.0.1",
12
- "@dereekb/rxjs": "14.0.1",
13
- "@dereekb/util": "14.0.1",
7
+ "@dereekb/date": "14.2.0",
8
+ "@dereekb/dbx-cli": "14.2.0",
9
+ "@dereekb/firebase": "14.2.0",
10
+ "@dereekb/firebase-server/test": "14.2.0",
11
+ "@dereekb/model": "14.2.0",
12
+ "@dereekb/nestjs": "14.2.0",
13
+ "@dereekb/rxjs": "14.2.0",
14
+ "@dereekb/util": "14.2.0",
14
15
  "@nestjs/common": "^12.0.1",
15
16
  "arktype": "^2.2.0",
16
17
  "vitest": "4.1.11",
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli/validate",
3
- "version": "14.0.1",
3
+ "version": "14.2.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
7
- "@dereekb/dbx-cli": "14.0.1",
8
- "@dereekb/util": "14.0.1",
7
+ "@dereekb/dbx-cli": "14.2.0",
8
+ "@dereekb/util": "14.2.0",
9
9
  "ts-morph": "^28.0.0"
10
10
  }
11
11
  }