@bb-labs/convex-cache 0.0.1

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 (86) hide show
  1. package/README.md +342 -0
  2. package/dist/cli/fns/dev.d.ts +11 -0
  3. package/dist/cli/fns/dev.js +59 -0
  4. package/dist/cli/fns/fns/convex-runner.d.ts +26 -0
  5. package/dist/cli/fns/fns/convex-runner.js +71 -0
  6. package/dist/cli/fns/fns/generate-z-schema/fns/build-schema-map.d.ts +8 -0
  7. package/dist/cli/fns/fns/generate-z-schema/fns/build-schema-map.js +39 -0
  8. package/dist/cli/fns/fns/generate-z-schema/fns/load-convex.d.ts +7 -0
  9. package/dist/cli/fns/fns/generate-z-schema/fns/load-convex.js +49 -0
  10. package/dist/cli/fns/fns/generate-z-schema/generate.d.ts +7 -0
  11. package/dist/cli/fns/fns/generate-z-schema/generate.js +16 -0
  12. package/dist/cli/fns/fns/generate-z-schema/utils/build-schema-file.d.ts +13 -0
  13. package/dist/cli/fns/fns/generate-z-schema/utils/build-schema-file.js +42 -0
  14. package/dist/cli/fns/fns/generate-z-schema/utils/extract-schema.d.ts +3 -0
  15. package/dist/cli/fns/fns/generate-z-schema/utils/extract-schema.js +84 -0
  16. package/dist/cli/fns/fns/generate-z-schema/utils/find-fn-ref.d.ts +5 -0
  17. package/dist/cli/fns/fns/generate-z-schema/utils/find-fn-ref.js +27 -0
  18. package/dist/cli/index.d.ts +2 -0
  19. package/dist/cli/index.js +25 -0
  20. package/dist/cli/lib/convex-config.d.ts +21 -0
  21. package/dist/cli/lib/convex-config.js +37 -0
  22. package/dist/cli/lib/dir-watcher.d.ts +49 -0
  23. package/dist/cli/lib/dir-watcher.js +97 -0
  24. package/dist/cli/lib/package-cmds.d.ts +10 -0
  25. package/dist/cli/lib/package-cmds.js +17 -0
  26. package/dist/cli/lib/resolve-bun.d.ts +4 -0
  27. package/dist/cli/lib/resolve-bun.js +24 -0
  28. package/dist/cli/lib/shell-runner.d.ts +61 -0
  29. package/dist/cli/lib/shell-runner.js +133 -0
  30. package/dist/convex-cache/adapters/next/fns/build-preloader.d.ts +5 -0
  31. package/dist/convex-cache/adapters/next/fns/build-preloader.js +6 -0
  32. package/dist/convex-cache/adapters/next/fns/preload-query.d.ts +12 -0
  33. package/dist/convex-cache/adapters/next/fns/preload-query.js +27 -0
  34. package/dist/convex-cache/adapters/next/hooks/hooks.d.ts +8 -0
  35. package/dist/convex-cache/adapters/next/hooks/hooks.js +9 -0
  36. package/dist/convex-cache/adapters/next/index.d.ts +1 -0
  37. package/dist/convex-cache/adapters/next/index.js +1 -0
  38. package/dist/convex-cache/adapters/next/lib/revalidate-query-cache.d.ts +13 -0
  39. package/dist/convex-cache/adapters/next/lib/revalidate-query-cache.js +21 -0
  40. package/dist/convex-cache/adapters/next/server-fns/preload-query.d.ts +16 -0
  41. package/dist/convex-cache/adapters/next/server-fns/preload-query.js +32 -0
  42. package/dist/convex-cache/adapters/next/server-fns/revalidate-cache.d.ts +3 -0
  43. package/dist/convex-cache/adapters/next/server-fns/revalidate-cache.js +6 -0
  44. package/dist/convex-cache/adapters/next/server.d.ts +2 -0
  45. package/dist/convex-cache/adapters/next/server.js +2 -0
  46. package/dist/convex-cache/adapters/next/types/preloaded.d.ts +8 -0
  47. package/dist/convex-cache/adapters/next/types/preloaded.js +1 -0
  48. package/dist/convex-cache/adapters/next/utils/cache-profile.d.ts +1 -0
  49. package/dist/convex-cache/adapters/next/utils/cache-profile.js +1 -0
  50. package/dist/convex-cache/adapters/react/hooks/hooks.d.ts +8 -0
  51. package/dist/convex-cache/adapters/react/hooks/hooks.js +12 -0
  52. package/dist/convex-cache/adapters/react/index.d.ts +2 -0
  53. package/dist/convex-cache/adapters/react/index.js +2 -0
  54. package/dist/convex-cache/adapters/react/provider/provider.d.ts +13 -0
  55. package/dist/convex-cache/adapters/react/provider/provider.js +16 -0
  56. package/dist/convex-cache/core/client-cache/helpers/hooks/use-client-cache.d.ts +10 -0
  57. package/dist/convex-cache/core/client-cache/helpers/hooks/use-client-cache.js +16 -0
  58. package/dist/convex-cache/core/client-cache/helpers/hooks/use-query-key.d.ts +7 -0
  59. package/dist/convex-cache/core/client-cache/helpers/hooks/use-query-key.js +5 -0
  60. package/dist/convex-cache/core/client-cache/queries/paginated-query.d.ts +13 -0
  61. package/dist/convex-cache/core/client-cache/queries/paginated-query.js +37 -0
  62. package/dist/convex-cache/core/client-cache/queries/query.d.ts +10 -0
  63. package/dist/convex-cache/core/client-cache/queries/query.js +23 -0
  64. package/dist/convex-cache/core/helpers/utils/convert-paginated-schema.d.ts +13 -0
  65. package/dist/convex-cache/core/helpers/utils/convert-paginated-schema.js +45 -0
  66. package/dist/convex-cache/core/helpers/utils/fetch-schema-from-map.d.ts +21 -0
  67. package/dist/convex-cache/core/helpers/utils/fetch-schema-from-map.js +16 -0
  68. package/dist/convex-cache/core/helpers/utils/query-key.d.ts +10 -0
  69. package/dist/convex-cache/core/helpers/utils/query-key.js +16 -0
  70. package/dist/convex-cache/core/server-cache/queries/paginated-query.d.ts +15 -0
  71. package/dist/convex-cache/core/server-cache/queries/paginated-query.js +16 -0
  72. package/dist/convex-cache/core/server-cache/queries/query.d.ts +12 -0
  73. package/dist/convex-cache/core/server-cache/queries/query.js +16 -0
  74. package/dist/convex-cache/core/types/index.d.ts +2 -0
  75. package/dist/convex-cache/core/types/index.js +2 -0
  76. package/dist/convex-cache/core/types/types/paginated-query.d.ts +8 -0
  77. package/dist/convex-cache/core/types/types/paginated-query.js +2 -0
  78. package/dist/convex-cache/core/types/types/query.d.ts +5 -0
  79. package/dist/convex-cache/core/types/types/query.js +1 -0
  80. package/dist/convex-cache/index.d.ts +1 -0
  81. package/dist/convex-cache/index.js +1 -0
  82. package/dist/convex-cache/types/schema-map.d.ts +4 -0
  83. package/dist/convex-cache/types/schema-map.js +1 -0
  84. package/dist/validation/index.d.ts +10 -0
  85. package/dist/validation/index.js +83 -0
  86. package/package.json +58 -0
@@ -0,0 +1,42 @@
1
+ /**
2
+ * TypeScript version: used when convex.json has `"fileType": "ts"`.
3
+ */
4
+ export const buildSchemaFileTs = (schemaEntries) => {
5
+ // Ensure deterministic ordering by sorting by fnName.
6
+ const sorted = [...schemaEntries].sort((a, b) => a.fnName.localeCompare(b.fnName));
7
+ const schemaMapEntries = sorted.map(({ fnName, schemaJson }) => ` "${fnName}": ${JSON.stringify(schemaJson, null, 2)},`);
8
+ return `// Auto-generated file - do not edit manually
9
+ // This file contains Convex ValidatorJSON definitions derived from \`returns\` in vQuery definitions.
10
+ import type { T_SchemaMap } from "convex-cache";
11
+
12
+ export const schemaMap: T_SchemaMap = {
13
+ ${schemaMapEntries.join("\n")}
14
+ };
15
+ `;
16
+ };
17
+ /**
18
+ * JavaScript version: default when not TypeScript.
19
+ */
20
+ export const buildSchemaFileJs = (schemaEntries) => {
21
+ const sorted = [...schemaEntries].sort((a, b) => a.fnName.localeCompare(b.fnName));
22
+ const schemaMapEntries = sorted.map(({ fnName, schemaJson }) => ` "${fnName}": ${JSON.stringify(schemaJson, null, 2)},`);
23
+ return `// Auto-generated file - do not edit manually
24
+ // This file contains Convex ValidatorJSON definitions derived from \`returns\` in vQuery definitions.
25
+
26
+ export const schemaMap = {
27
+ ${schemaMapEntries.join("\n")}
28
+ };
29
+ `;
30
+ };
31
+ /**
32
+ * Declaration file for JS projects: just types.
33
+ */
34
+ export const buildSchemaDtsFile = () => {
35
+ return `// Auto-generated file - do not edit manually
36
+ // Type declarations for the generated schemaMap (ValidatorJSON per function).
37
+
38
+ import type { T_SchemaMap } from "convex-cache";
39
+
40
+ export declare const schemaMap: T_SchemaMap;
41
+ `;
42
+ };
@@ -0,0 +1,3 @@
1
+ import type { AnyApi } from "convex/server";
2
+ import type { SchemaEntry } from "../generate";
3
+ export declare const extractSchema: (file: string, api: AnyApi, convexDir: string) => Promise<SchemaEntry[]>;
@@ -0,0 +1,84 @@
1
+ // utils/extract-schema.ts
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { getFunctionName } from "convex/server";
5
+ import { findFunctionReference } from "./find-fn-ref";
6
+ /**
7
+ * A Convex query we can inspect:
8
+ * - must be isQuery
9
+ * - must be isPublic
10
+ * - must have exportReturns() function
11
+ */
12
+ const isConvexFn = (value) => {
13
+ if (!value || (typeof value !== "function" && typeof value !== "object")) {
14
+ return false;
15
+ }
16
+ const v = value;
17
+ return Boolean(v.isQuery && v.isPublic && typeof v.exportReturns === "function");
18
+ };
19
+ /**
20
+ * Returns true if exportReturns() yields a non-null string.
21
+ * Returns false if null OR if calling exportReturns() throws.
22
+ */
23
+ const fetchReturnsString = (value) => {
24
+ try {
25
+ const result = value.exportReturns?.();
26
+ return typeof result === "string" && result.length > 0 ? result : null;
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ };
32
+ /**
33
+ * Parse returnsJsonString → ValidatorJSON
34
+ * Returns null on parse error.
35
+ */
36
+ const parseReturnsValidatorJson = ({ returnsString, fnName }) => {
37
+ try {
38
+ return JSON.parse(returnsString);
39
+ }
40
+ catch (err) {
41
+ console.warn(`⚠️ Failed to parse return JSON for ${fnName}\nRaw: ${returnsString}`, err);
42
+ return null;
43
+ }
44
+ };
45
+ export const extractSchema = async (file, api, convexDir) => {
46
+ const entries = [];
47
+ // Load module dynamically
48
+ let mod;
49
+ try {
50
+ mod = (await import(pathToFileURL(file).href));
51
+ }
52
+ catch (err) {
53
+ console.warn(`⚠️ Failed to import ${file}:`, err);
54
+ return entries;
55
+ }
56
+ const relativePath = path.relative(convexDir, file);
57
+ const modulePath = relativePath.replace(/\.(ts|js)$/, "");
58
+ for (const [exportName, value] of Object.entries(mod)) {
59
+ // Check if the value is a Convex query function
60
+ if (!isConvexFn(value))
61
+ continue;
62
+ // Fetch the returns value as a string
63
+ const returnsString = fetchReturnsString(value);
64
+ if (!returnsString)
65
+ continue;
66
+ // Find the function reference
67
+ const fnRef = findFunctionReference(api, modulePath, exportName);
68
+ if (!fnRef)
69
+ continue;
70
+ // Get the function name
71
+ const fnName = getFunctionName(fnRef);
72
+ // Parse the returns value as a ValidatorJSON
73
+ const returnsValidatorJson = parseReturnsValidatorJson({ returnsString, fnName });
74
+ if (!returnsValidatorJson)
75
+ continue;
76
+ entries.push({
77
+ fnName,
78
+ schemaJson: {
79
+ returns: returnsValidatorJson,
80
+ },
81
+ });
82
+ }
83
+ return entries;
84
+ };
@@ -0,0 +1,5 @@
1
+ import { AnyApi, FunctionReference } from "convex/server";
2
+ /**
3
+ * Walk the convex API object to find a FunctionReference.
4
+ */
5
+ export declare const findFunctionReference: (api: AnyApi, modulePath: string, exportName: string) => FunctionReference<any, any> | undefined;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Walk the convex API object to find a FunctionReference.
3
+ */
4
+ export const findFunctionReference = (api, modulePath, exportName) => {
5
+ try {
6
+ // Handle both "/" and "\" so this works on Windows too.
7
+ const parts = modulePath.split(/[\\/]/).filter(Boolean);
8
+ if (parts.length === 0)
9
+ return undefined;
10
+ let current = api;
11
+ for (const part of parts) {
12
+ if (!current || typeof current !== "object")
13
+ return undefined;
14
+ current = current[part];
15
+ if (!current)
16
+ return undefined;
17
+ }
18
+ if (exportName === "default") {
19
+ return current.default || current;
20
+ }
21
+ return current[exportName];
22
+ }
23
+ catch (error) {
24
+ console.warn(`Failed to find function reference for ${modulePath}.${exportName}:`, error);
25
+ return undefined;
26
+ }
27
+ };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { dev } from "./fns/dev.js";
4
+ // import pkg from "../package.json" assert { type: "json" }; // optional improvement
5
+ const program = new Command();
6
+ program.name("convex-cache").description("Upload Convex functions & generate Zod schemas on change").version("0.1.0"); // or pkg.version
7
+ program
8
+ .command("dev")
9
+ .description("Runs `convex dev` and generates Zod schemas while watching for changes")
10
+ .option("--schema-only", "Only generate schemas, skip running convex dev", false)
11
+ .action(async (opts) => {
12
+ await dev({ schemaOnly: opts.schemaOnly });
13
+ });
14
+ // Let Commander show help by default if no args are provided
15
+ program.showHelpAfterError("(run with --help for usage information)");
16
+ program.showSuggestionAfterError();
17
+ // Use parseAsync so async commands propagate errors
18
+ program.parseAsync(process.argv).catch((err) => {
19
+ console.error("\n❌ convex-cache failed:");
20
+ if (err?.message)
21
+ console.error(" " + err.message);
22
+ else
23
+ console.error(err);
24
+ process.exit(1);
25
+ });
@@ -0,0 +1,21 @@
1
+ export interface ConvexConfig {
2
+ functions?: string;
3
+ codegen?: {
4
+ fileType?: string;
5
+ };
6
+ }
7
+ /**
8
+ * Reads the convex.json config file from the current working directory.
9
+ * Returns null if the file doesn't exist.
10
+ */
11
+ export declare const readConvexConfig: () => ConvexConfig | null;
12
+ /**
13
+ * Gets the convex directory path from the config file.
14
+ * Defaults to "convex" if not specified in the config.
15
+ */
16
+ export declare const getConvexDir: () => string;
17
+ /**
18
+ * Determines if TypeScript should be used based on the config file.
19
+ * Returns true if fileType is "ts" in the codegen section.
20
+ */
21
+ export declare const shouldUseTs: () => boolean;
@@ -0,0 +1,37 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ /**
4
+ * Reads the convex.json config file from the current working directory.
5
+ * Returns null if the file doesn't exist.
6
+ */
7
+ export const readConvexConfig = () => {
8
+ const configPath = path.join(process.cwd(), "convex.json");
9
+ if (!fs.existsSync(configPath)) {
10
+ return null;
11
+ }
12
+ try {
13
+ const raw = fs.readFileSync(configPath, "utf8");
14
+ return JSON.parse(raw);
15
+ }
16
+ catch (err) {
17
+ console.warn(`⚠️ Failed to parse convex.json:`, err);
18
+ return null;
19
+ }
20
+ };
21
+ /**
22
+ * Gets the convex directory path from the config file.
23
+ * Defaults to "convex" if not specified in the config.
24
+ */
25
+ export const getConvexDir = () => {
26
+ const config = readConvexConfig();
27
+ const functionsPath = config?.functions ?? "convex";
28
+ return path.resolve(functionsPath);
29
+ };
30
+ /**
31
+ * Determines if TypeScript should be used based on the config file.
32
+ * Returns true if fileType is "ts" in the codegen section.
33
+ */
34
+ export const shouldUseTs = () => {
35
+ const config = readConvexConfig();
36
+ return config?.codegen?.fileType === "ts";
37
+ };
@@ -0,0 +1,49 @@
1
+ export interface DirWatcherOptions {
2
+ /** Absolute or relative path to the directory to watch. */
3
+ rootDir: string;
4
+ /** Whether to watch subdirectories as well. Defaults to true. */
5
+ recursive?: boolean;
6
+ /** Debounce time in milliseconds before invoking `onChange`. Defaults to 200ms. */
7
+ debounceMs?: number;
8
+ /** Function to determine if a relative path should be ignored. */
9
+ shouldIgnore?: (relativePath: string) => boolean;
10
+ /**
11
+ * Callback invoked when a (debounced) change is detected.
12
+ * Receives the relative path (from `rootDir`) of the changed file.
13
+ */
14
+ onChange: (relativePath: string) => void | Promise<void>;
15
+ }
16
+ /**
17
+ * A simple debounced directory watcher built on top of `fs.watch`.
18
+ * It watches a root directory (optionally recursively) and calls `onChange`
19
+ * after changes settle for `debounceMs` milliseconds.
20
+ */
21
+ export declare class DirWatcher {
22
+ private readonly rootDir;
23
+ private readonly rootLabel;
24
+ private readonly recursive;
25
+ private readonly debounceMs;
26
+ private readonly shouldIgnore?;
27
+ private readonly onChange;
28
+ private debounceTimer;
29
+ private lastRelPath;
30
+ private watcher;
31
+ private started;
32
+ constructor(options: DirWatcherOptions);
33
+ /**
34
+ * Start watching the directory.
35
+ * Exits the process if the root directory does not exist or watcher fails.
36
+ */
37
+ start: () => void;
38
+ /**
39
+ * Stop watching the directory and clear any pending debounce timer.
40
+ */
41
+ stop: () => void;
42
+ /**
43
+ * Internal handler for fs.watch events.
44
+ * Keeps behavior identical to the original implementation:
45
+ * - debounce on any change
46
+ * - call `onChange` once with the last changed relative path
47
+ */
48
+ private handleFsEvent;
49
+ }
@@ -0,0 +1,97 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ /**
4
+ * A simple debounced directory watcher built on top of `fs.watch`.
5
+ * It watches a root directory (optionally recursively) and calls `onChange`
6
+ * after changes settle for `debounceMs` milliseconds.
7
+ */
8
+ export class DirWatcher {
9
+ constructor(options) {
10
+ this.debounceTimer = null;
11
+ this.lastRelPath = null;
12
+ this.watcher = null;
13
+ this.started = false;
14
+ /**
15
+ * Start watching the directory.
16
+ * Exits the process if the root directory does not exist or watcher fails.
17
+ */
18
+ this.start = () => {
19
+ if (this.started)
20
+ return;
21
+ if (!fs.existsSync(this.rootDir)) {
22
+ console.error(`watch root not found: ${this.rootDir}`);
23
+ process.exit(1);
24
+ }
25
+ try {
26
+ this.watcher = fs.watch(this.rootDir, { recursive: this.recursive }, (eventType, filename) => {
27
+ this.handleFsEvent(eventType, filename);
28
+ });
29
+ this.watcher.on("error", (err) => {
30
+ console.error(`fs.watch error for ${this.rootDir}:`, err);
31
+ this.stop();
32
+ // Keep behavior simple & explicit: fail hard on watcher errors
33
+ process.exit(1);
34
+ });
35
+ this.started = true;
36
+ }
37
+ catch (err) {
38
+ console.error(`Failed to start fs.watch on ${this.rootDir} (recursive=${this.recursive}):`, err);
39
+ process.exit(1);
40
+ }
41
+ };
42
+ /**
43
+ * Stop watching the directory and clear any pending debounce timer.
44
+ */
45
+ this.stop = () => {
46
+ if (this.watcher) {
47
+ this.watcher.close();
48
+ this.watcher = null;
49
+ }
50
+ if (this.debounceTimer) {
51
+ clearTimeout(this.debounceTimer);
52
+ this.debounceTimer = null;
53
+ }
54
+ this.lastRelPath = null;
55
+ this.started = false;
56
+ };
57
+ const { rootDir, recursive = true, debounceMs = 200, shouldIgnore, onChange } = options;
58
+ // Normalize to an absolute path to avoid ambiguity
59
+ this.rootDir = path.resolve(rootDir);
60
+ this.rootLabel = path.basename(this.rootDir);
61
+ this.recursive = recursive;
62
+ this.debounceMs = debounceMs;
63
+ this.shouldIgnore = shouldIgnore;
64
+ this.onChange = onChange;
65
+ }
66
+ /**
67
+ * Internal handler for fs.watch events.
68
+ * Keeps behavior identical to the original implementation:
69
+ * - debounce on any change
70
+ * - call `onChange` once with the last changed relative path
71
+ */
72
+ handleFsEvent(_eventType, filename) {
73
+ if (!filename)
74
+ return;
75
+ const rel = filename.toString();
76
+ if (this.shouldIgnore && this.shouldIgnore(rel))
77
+ return;
78
+ this.lastRelPath = rel;
79
+ console.log(`\n --------- Change detected in ${this.rootLabel}/: ${rel} ---------`);
80
+ if (this.debounceTimer) {
81
+ clearTimeout(this.debounceTimer);
82
+ }
83
+ this.debounceTimer = setTimeout(() => {
84
+ const pathToReport = this.lastRelPath;
85
+ if (!pathToReport)
86
+ return;
87
+ void (async () => {
88
+ try {
89
+ await this.onChange(pathToReport);
90
+ }
91
+ catch (err) {
92
+ console.error("Error in DirWatcher onChange handler:", err);
93
+ }
94
+ })();
95
+ }, this.debounceMs);
96
+ }
97
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Produce a command that runs a file with Bun.
3
+ * Ensures Bun is available before returning.
4
+ */
5
+ export declare const runFileCmd: (filePath: string) => Promise<string>;
6
+ /**
7
+ * Produce a command that runs a CLI via Bunx.
8
+ * Ensures Bun is available before returning.
9
+ */
10
+ export declare const runCmd: (cmd: string) => Promise<string>;
@@ -0,0 +1,17 @@
1
+ import { resolveBunOrExit } from "./resolve-bun.js";
2
+ /**
3
+ * Produce a command that runs a file with Bun.
4
+ * Ensures Bun is available before returning.
5
+ */
6
+ export const runFileCmd = async (filePath) => {
7
+ await resolveBunOrExit();
8
+ return `bun run ${filePath}`;
9
+ };
10
+ /**
11
+ * Produce a command that runs a CLI via Bunx.
12
+ * Ensures Bun is available before returning.
13
+ */
14
+ export const runCmd = async (cmd) => {
15
+ await resolveBunOrExit();
16
+ return `bunx ${cmd}`;
17
+ };
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Resolve a Bun binary or exit the process with a helpful message.
3
+ **/
4
+ export declare const resolveBunOrExit: () => Promise<string>;
@@ -0,0 +1,24 @@
1
+ import { spawn } from "node:child_process";
2
+ /**
3
+ * Resolve a Bun binary or exit the process with a helpful message.
4
+ **/
5
+ export const resolveBunOrExit = async () => {
6
+ const systemBunOk = await canRunBun("bun");
7
+ if (systemBunOk)
8
+ return "bun";
9
+ console.error("⚠️ Bun is not installed. Please install Bun globally and then re-run this command.");
10
+ console.error("Docs: https://bun.sh");
11
+ process.exit(1);
12
+ };
13
+ /**
14
+ * Check whether a given command behaves like Bun.
15
+ */
16
+ const canRunBun = async (cmd) => {
17
+ return await new Promise((resolve) => {
18
+ const child = spawn(cmd, ["--version"], {
19
+ stdio: ["ignore", "ignore", "ignore"],
20
+ });
21
+ child.on("exit", (code) => resolve(code === 0));
22
+ child.on("error", () => resolve(false));
23
+ });
24
+ };
@@ -0,0 +1,61 @@
1
+ import { type SpawnOptions } from "child_process";
2
+ export type TaskKind = string;
3
+ export interface ShellRunOptions {
4
+ /** Human-readable label for logging. */
5
+ label: string;
6
+ /** Logical kind/category for the task, used as a key in `currentProcs`. */
7
+ kind: TaskKind;
8
+ /** Optional message to log when the command succeeds. */
9
+ successMessage?: string;
10
+ /**
11
+ * Whether to run the command through a shell.
12
+ * Defaults to true.
13
+ */
14
+ shell?: boolean;
15
+ /**
16
+ * Extra spawn options (cwd, env, etc.).
17
+ * `shell` and `stdio` are controlled by ShellRunner.
18
+ */
19
+ spawnOptions?: Omit<SpawnOptions, "shell" | "stdio">;
20
+ /**
21
+ * Optional AbortSignal for per-run cancellation.
22
+ */
23
+ abortSignal?: AbortSignal;
24
+ }
25
+ export declare class ShellRunnerError extends Error {
26
+ readonly kind: TaskKind;
27
+ readonly code: number | null;
28
+ readonly signal: NodeJS.Signals | null;
29
+ constructor(message: string, kind: TaskKind, code?: number | null, signal?: NodeJS.Signals | null);
30
+ }
31
+ /**
32
+ * ShellRunner spawns shell commands, tracks them by "kind",
33
+ * and allows cancelling all running processes.
34
+ */
35
+ export declare class ShellRunner {
36
+ private readonly currentProcs;
37
+ private cancelled;
38
+ get isCancelled(): boolean;
39
+ /**
40
+ * Reset the internal "cancelled" flag back to false.
41
+ * Does not restart any cancelled processes; it only affects
42
+ * how subsequent runs behave.
43
+ */
44
+ resetCancelled: () => void;
45
+ /**
46
+ * Run a shell command with the given options.
47
+ *
48
+ * Resolves on successful exit code (0), rejects otherwise,
49
+ * or if the runner is cancelled / AbortSignal aborts / a process of this kind is already running.
50
+ */
51
+ run: (command: string, options: ShellRunOptions) => Promise<void>;
52
+ /**
53
+ * Mark the runner as cancelled and send the given signal
54
+ * (default SIGINT) to all tracked child processes.
55
+ */
56
+ cancelAll: (signal?: NodeJS.Signals) => void;
57
+ /**
58
+ * Cancel a single running task kind, if present.
59
+ */
60
+ cancelKind: (kind: TaskKind, signal?: NodeJS.Signals) => void;
61
+ }
@@ -0,0 +1,133 @@
1
+ import { spawn } from "child_process";
2
+ export class ShellRunnerError extends Error {
3
+ constructor(message, kind, code = null, signal = null) {
4
+ super(message);
5
+ this.kind = kind;
6
+ this.code = code;
7
+ this.signal = signal;
8
+ this.name = "ShellRunnerError";
9
+ }
10
+ }
11
+ /**
12
+ * ShellRunner spawns shell commands, tracks them by "kind",
13
+ * and allows cancelling all running processes.
14
+ */
15
+ export class ShellRunner {
16
+ constructor() {
17
+ this.currentProcs = new Map();
18
+ this.cancelled = false;
19
+ /**
20
+ * Reset the internal "cancelled" flag back to false.
21
+ * Does not restart any cancelled processes; it only affects
22
+ * how subsequent runs behave.
23
+ */
24
+ this.resetCancelled = () => {
25
+ this.cancelled = false;
26
+ };
27
+ /**
28
+ * Run a shell command with the given options.
29
+ *
30
+ * Resolves on successful exit code (0), rejects otherwise,
31
+ * or if the runner is cancelled / AbortSignal aborts / a process of this kind is already running.
32
+ */
33
+ this.run = async (command, options) => {
34
+ const { label, kind, successMessage, shell = true, spawnOptions, abortSignal } = options;
35
+ // Fast-fail if globally cancelled or already aborted.
36
+ if (this.cancelled) {
37
+ throw new ShellRunnerError("ShellRunner is cancelled", kind);
38
+ }
39
+ if (abortSignal?.aborted) {
40
+ throw new ShellRunnerError("Run aborted before start", kind);
41
+ }
42
+ // Enforce single process per kind.
43
+ if (this.currentProcs.has(kind)) {
44
+ throw new ShellRunnerError(`A task of kind "${kind}" is already running`, kind);
45
+ }
46
+ console.log(`\n${label}`);
47
+ return new Promise((resolve, reject) => {
48
+ const child = spawn(command, {
49
+ shell,
50
+ stdio: ["inherit", "inherit", "inherit"],
51
+ ...spawnOptions,
52
+ });
53
+ this.currentProcs.set(kind, child);
54
+ // Support AbortSignal-based cancellation
55
+ const abortHandler = () => {
56
+ if (!child.killed) {
57
+ child.kill("SIGINT");
58
+ }
59
+ };
60
+ if (abortSignal) {
61
+ abortSignal.addEventListener("abort", abortHandler, { once: true });
62
+ }
63
+ let settled = false;
64
+ const settle = (fn) => {
65
+ if (settled)
66
+ return;
67
+ settled = true;
68
+ // Cleanup
69
+ if (this.currentProcs.get(kind) === child) {
70
+ this.currentProcs.delete(kind);
71
+ }
72
+ if (abortSignal) {
73
+ abortSignal.removeEventListener("abort", abortHandler);
74
+ }
75
+ fn();
76
+ };
77
+ child.on("exit", (code, signal) => {
78
+ settle(() => {
79
+ if (this.cancelled || abortSignal?.aborted) {
80
+ return reject(new ShellRunnerError("cancelled", kind, code, signal));
81
+ }
82
+ if (code === 0) {
83
+ if (successMessage) {
84
+ console.log(successMessage);
85
+ }
86
+ return resolve();
87
+ }
88
+ console.error(`❌ ${label} failed (code=${code ?? "unknown"}, signal=${signal ?? "none"})`);
89
+ reject(new ShellRunnerError(`${label} failed with code ${code ?? "unknown"}`, kind, code, signal));
90
+ });
91
+ });
92
+ child.on("error", (err) => {
93
+ settle(() => {
94
+ console.error(`❌ Failed to start ${label}:`, err);
95
+ reject(err);
96
+ });
97
+ });
98
+ });
99
+ };
100
+ /**
101
+ * Mark the runner as cancelled and send the given signal
102
+ * (default SIGINT) to all tracked child processes.
103
+ */
104
+ this.cancelAll = (signal = "SIGINT") => {
105
+ this.cancelled = true;
106
+ for (const child of this.currentProcs.values()) {
107
+ try {
108
+ child.kill(signal);
109
+ }
110
+ catch {
111
+ // ignore
112
+ }
113
+ }
114
+ };
115
+ /**
116
+ * Cancel a single running task kind, if present.
117
+ */
118
+ this.cancelKind = (kind, signal = "SIGINT") => {
119
+ const child = this.currentProcs.get(kind);
120
+ if (!child)
121
+ return;
122
+ try {
123
+ child.kill(signal);
124
+ }
125
+ catch {
126
+ // ignore
127
+ }
128
+ };
129
+ }
130
+ get isCancelled() {
131
+ return this.cancelled;
132
+ }
133
+ }
@@ -0,0 +1,5 @@
1
+ import { Q_Query } from "../../../core/types/types/query";
2
+ import { PQ_Query } from "../../../core/types/types/paginated-query";
3
+ import { PreloadQueryReturn, T_PreloadQueryParams } from "./preload-query";
4
+ import { T_SchemaMap } from "../../../types/schema-map";
5
+ export declare function buildPreloader(schemaMap: T_SchemaMap): <Q extends Q_Query | PQ_Query>(params: Omit<T_PreloadQueryParams<Q>, "schemaMap">) => Promise<PreloadQueryReturn<Q>>;
@@ -0,0 +1,6 @@
1
+ import { preloadQuery } from "./preload-query";
2
+ export function buildPreloader(schemaMap) {
3
+ return function preloadQueryBound(params) {
4
+ return preloadQuery({ ...params, schemaMap });
5
+ };
6
+ }