@stardeck-customer-apps/data-store-sdk 0.4.0 → 0.6.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,11 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ main
4
+ } from "../chunk-EWWQPGFI.mjs";
5
+ import "../chunk-FEKROS4B.mjs";
6
+
7
+ // src/cli/bin.ts
8
+ main().catch((error) => {
9
+ console.error(error instanceof Error ? error.message : String(error));
10
+ process.exit(1);
11
+ });
@@ -1 +1,75 @@
1
- #!/usr/bin/env node
1
+ declare function toPascalCase(name: string): string;
2
+
3
+ interface ColumnInfo {
4
+ table_name: string;
5
+ column_name: string;
6
+ data_type: string;
7
+ is_nullable: string;
8
+ column_default: string | null;
9
+ }
10
+ declare const PG_TYPE_MAP: Record<string, string>;
11
+ declare function pgTypeToTs(pgType: string): string;
12
+
13
+ type ParsedCliArgs = {
14
+ connectionString: string | undefined;
15
+ /** `--store`: which manifest entry to use when the app has several connected. */
16
+ store: string | undefined;
17
+ outputPath: string;
18
+ schemaOutputPath: string | undefined;
19
+ emitSchema: boolean;
20
+ modulesDir: string;
21
+ noModules: boolean;
22
+ help: boolean;
23
+ };
24
+ /**
25
+ * Pure argv parser for generate-types flags. Exported for unit tests.
26
+ */
27
+ declare function parseCliArgs(argv: string[], options?: {
28
+ cwd?: string;
29
+ env?: NodeJS.ProcessEnv;
30
+ }): ParsedCliArgs;
31
+ declare function introspectSchema(connectionString: string): Promise<Map<string, ColumnInfo[]>>;
32
+ declare function generateTypeScript(tables: Map<string, ColumnInfo[]>, moduleSlices?: string): string;
33
+ type RunGenerateTypesOptions = {
34
+ connectionString: string;
35
+ outputPath: string;
36
+ schemaOutputPath?: string;
37
+ emitSchema?: boolean;
38
+ modulesDir: string;
39
+ noModules: boolean;
40
+ /** Injectable for tests — defaults to live Postgres introspection. */
41
+ introspect?: (connectionString: string) => Promise<Map<string, ColumnInfo[]>>;
42
+ warn?: (message: string) => void;
43
+ };
44
+ /**
45
+ * Flag → discovery → emitter orchestration. Accepts an injectable introspect
46
+ * so tests can stub the pool without a live database.
47
+ */
48
+ declare function runGenerateTypes(options: RunGenerateTypesOptions): Promise<void>;
49
+ /**
50
+ * The connection string for the store the app is pointed at, from the manifest
51
+ * the app runtime itself resolves stores from. `--store` picks one entry the way
52
+ * `resolveDataStore` does (id, binding key, slug, name); otherwise the app's
53
+ * single database store is the answer, and anything else is an error the
54
+ * operator can act on. Storage stores carry no url and never count.
55
+ */
56
+ declare function connectionStringFromManifest(store: string | undefined): {
57
+ url: string;
58
+ } | {
59
+ error: string;
60
+ };
61
+ /**
62
+ * Load `./.env.local` the way the app's `next dev` reads it. `env:pull` writes
63
+ * the file for `@next/env` — dotenv parse followed by dotenv-expand — and
64
+ * escapes every `$` as `\$` so expand leaves the value alone; Node's parser
65
+ * keeps that backslash, which turns a manifest holding a `$` (a store name, a
66
+ * password) into invalid JSON. Undo the one escape so the manifest is the JSON
67
+ * the app sees. Shell values win over the file, like `node --env-file`.
68
+ *
69
+ * ponytail: no `$VAR` expansion of hand-edited lines; `env:pull` never writes
70
+ * one. Add dotenv-expand if a file ever needs it.
71
+ */
72
+ declare function loadEnvLocal(cwd: string): void;
73
+ declare function main(argv?: string[]): Promise<void>;
74
+
75
+ export { type ColumnInfo, PG_TYPE_MAP, type ParsedCliArgs, type RunGenerateTypesOptions, connectionStringFromManifest, generateTypeScript, introspectSchema, loadEnvLocal, main, parseCliArgs, pgTypeToTs, runGenerateTypes, toPascalCase };
@@ -1 +1,75 @@
1
- #!/usr/bin/env node
1
+ declare function toPascalCase(name: string): string;
2
+
3
+ interface ColumnInfo {
4
+ table_name: string;
5
+ column_name: string;
6
+ data_type: string;
7
+ is_nullable: string;
8
+ column_default: string | null;
9
+ }
10
+ declare const PG_TYPE_MAP: Record<string, string>;
11
+ declare function pgTypeToTs(pgType: string): string;
12
+
13
+ type ParsedCliArgs = {
14
+ connectionString: string | undefined;
15
+ /** `--store`: which manifest entry to use when the app has several connected. */
16
+ store: string | undefined;
17
+ outputPath: string;
18
+ schemaOutputPath: string | undefined;
19
+ emitSchema: boolean;
20
+ modulesDir: string;
21
+ noModules: boolean;
22
+ help: boolean;
23
+ };
24
+ /**
25
+ * Pure argv parser for generate-types flags. Exported for unit tests.
26
+ */
27
+ declare function parseCliArgs(argv: string[], options?: {
28
+ cwd?: string;
29
+ env?: NodeJS.ProcessEnv;
30
+ }): ParsedCliArgs;
31
+ declare function introspectSchema(connectionString: string): Promise<Map<string, ColumnInfo[]>>;
32
+ declare function generateTypeScript(tables: Map<string, ColumnInfo[]>, moduleSlices?: string): string;
33
+ type RunGenerateTypesOptions = {
34
+ connectionString: string;
35
+ outputPath: string;
36
+ schemaOutputPath?: string;
37
+ emitSchema?: boolean;
38
+ modulesDir: string;
39
+ noModules: boolean;
40
+ /** Injectable for tests — defaults to live Postgres introspection. */
41
+ introspect?: (connectionString: string) => Promise<Map<string, ColumnInfo[]>>;
42
+ warn?: (message: string) => void;
43
+ };
44
+ /**
45
+ * Flag → discovery → emitter orchestration. Accepts an injectable introspect
46
+ * so tests can stub the pool without a live database.
47
+ */
48
+ declare function runGenerateTypes(options: RunGenerateTypesOptions): Promise<void>;
49
+ /**
50
+ * The connection string for the store the app is pointed at, from the manifest
51
+ * the app runtime itself resolves stores from. `--store` picks one entry the way
52
+ * `resolveDataStore` does (id, binding key, slug, name); otherwise the app's
53
+ * single database store is the answer, and anything else is an error the
54
+ * operator can act on. Storage stores carry no url and never count.
55
+ */
56
+ declare function connectionStringFromManifest(store: string | undefined): {
57
+ url: string;
58
+ } | {
59
+ error: string;
60
+ };
61
+ /**
62
+ * Load `./.env.local` the way the app's `next dev` reads it. `env:pull` writes
63
+ * the file for `@next/env` — dotenv parse followed by dotenv-expand — and
64
+ * escapes every `$` as `\$` so expand leaves the value alone; Node's parser
65
+ * keeps that backslash, which turns a manifest holding a `$` (a store name, a
66
+ * password) into invalid JSON. Undo the one escape so the manifest is the JSON
67
+ * the app sees. Shell values win over the file, like `node --env-file`.
68
+ *
69
+ * ponytail: no `$VAR` expansion of hand-edited lines; `env:pull` never writes
70
+ * one. Add dotenv-expand if a file ever needs it.
71
+ */
72
+ declare function loadEnvLocal(cwd: string): void;
73
+ declare function main(argv?: string[]): Promise<void>;
74
+
75
+ export { type ColumnInfo, PG_TYPE_MAP, type ParsedCliArgs, type RunGenerateTypesOptions, connectionStringFromManifest, generateTypeScript, introspectSchema, loadEnvLocal, main, parseCliArgs, pgTypeToTs, runGenerateTypes, toPascalCase };
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  "use strict";
3
2
  var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
@@ -6,6 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
6
  var __getProtoOf = Object.getPrototypeOf;
8
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
9
12
  var __copyProps = (to, from, except, desc) => {
10
13
  if (from && typeof from === "object" || typeof from === "function") {
11
14
  for (let key of __getOwnPropNames(from))
@@ -22,11 +25,188 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
25
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
26
  mod
24
27
  ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
29
 
26
30
  // src/cli/generate-types.ts
27
- var import_fs = require("fs");
31
+ var generate_types_exports = {};
32
+ __export(generate_types_exports, {
33
+ PG_TYPE_MAP: () => PG_TYPE_MAP,
34
+ connectionStringFromManifest: () => connectionStringFromManifest,
35
+ generateTypeScript: () => generateTypeScript,
36
+ introspectSchema: () => introspectSchema,
37
+ loadEnvLocal: () => loadEnvLocal,
38
+ main: () => main,
39
+ parseCliArgs: () => parseCliArgs,
40
+ pgTypeToTs: () => pgTypeToTs,
41
+ runGenerateTypes: () => runGenerateTypes,
42
+ toPascalCase: () => toPascalCase
43
+ });
44
+ module.exports = __toCommonJS(generate_types_exports);
45
+ var import_fs2 = require("fs");
46
+ var import_path2 = require("path");
47
+ var import_util = require("util");
28
48
  var import_serverless = require("@neondatabase/serverless");
29
49
 
50
+ // src/server/manifest.ts
51
+ var STARDECK_DATA_STORES_ENV = "STARDECK_DATA_STORES";
52
+ function readEnv(key) {
53
+ if (typeof process !== "undefined" && process.env?.[key]) {
54
+ return process.env[key];
55
+ }
56
+ return void 0;
57
+ }
58
+ function dataStoreSlug(name) {
59
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
60
+ return slug || "store";
61
+ }
62
+ function readDataStoreManifest() {
63
+ const raw = readEnv(STARDECK_DATA_STORES_ENV);
64
+ if (!raw) return [];
65
+ try {
66
+ const parsed = JSON.parse(raw);
67
+ if (!Array.isArray(parsed)) return [];
68
+ return parsed.filter((e) => {
69
+ if (typeof e !== "object" || e === null) return false;
70
+ const entry = e;
71
+ if (typeof entry.id !== "string" || typeof entry.name !== "string" || typeof entry.slug !== "string") {
72
+ return false;
73
+ }
74
+ return entry.storeType === "storage" || typeof entry.url === "string";
75
+ });
76
+ } catch {
77
+ return [];
78
+ }
79
+ }
80
+ function resolveDataStore(ref) {
81
+ return resolveManifestEntry(readDataStoreManifest(), ref);
82
+ }
83
+ function resolveManifestEntry(entries, ref) {
84
+ if (ref.storeId) {
85
+ const byId = entries.find((e) => e.id === ref.storeId);
86
+ if (byId) return byId;
87
+ }
88
+ if (ref.storeName) {
89
+ const target = ref.storeName;
90
+ const byBindingKey = entries.find((e) => e.bindingKey === target);
91
+ if (byBindingKey) return byBindingKey;
92
+ const exact = entries.find((e) => e.slug === target || e.id === target || e.name === target);
93
+ if (exact) return exact;
94
+ const norm = dataStoreSlug(target);
95
+ const byBindingKeyNorm = entries.find((e) => e.bindingKey === norm);
96
+ if (byBindingKeyNorm) return byBindingKeyNorm;
97
+ return entries.find(
98
+ (e) => e.slug === norm || typeof e.name === "string" && dataStoreSlug(e.name) === norm
99
+ );
100
+ }
101
+ return void 0;
102
+ }
103
+
104
+ // src/cli/module-type-slices.ts
105
+ var import_fs = require("fs");
106
+ var import_path = require("path");
107
+ function toPascalCase(name) {
108
+ return name.split(/[_-]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
109
+ }
110
+ function parseModuleManifestSlice(content, sourceLabel, warn) {
111
+ let parsed;
112
+ try {
113
+ parsed = JSON.parse(content);
114
+ } catch {
115
+ warn(`Skipping malformed module manifest at ${sourceLabel}`);
116
+ return null;
117
+ }
118
+ if (typeof parsed !== "object" || parsed === null) {
119
+ warn(`Skipping malformed module manifest at ${sourceLabel}`);
120
+ return null;
121
+ }
122
+ const record = parsed;
123
+ const name = record.name;
124
+ const tables = record.tables;
125
+ if (typeof name !== "string" || name.length === 0) {
126
+ warn(`Skipping module manifest at ${sourceLabel}: missing or invalid "name"`);
127
+ return null;
128
+ }
129
+ if (!Array.isArray(tables) || tables.length === 0) {
130
+ return null;
131
+ }
132
+ const tableNames = [];
133
+ for (const entry of tables) {
134
+ if (typeof entry !== "string" || entry.length === 0) {
135
+ warn(`Skipping module manifest at ${sourceLabel}: invalid "tables" entry`);
136
+ return null;
137
+ }
138
+ tableNames.push(entry);
139
+ }
140
+ return { name, tables: tableNames };
141
+ }
142
+ function discoverModuleManifests(modulesDir, warn) {
143
+ if (!(0, import_fs.existsSync)(modulesDir)) {
144
+ return [];
145
+ }
146
+ let entries;
147
+ try {
148
+ entries = (0, import_fs.readdirSync)(modulesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
149
+ } catch {
150
+ warn(`Could not read modules directory: ${modulesDir}`);
151
+ return [];
152
+ }
153
+ const manifests = [];
154
+ for (const dirName of entries) {
155
+ const manifestPath = (0, import_path.join)(modulesDir, dirName, "module.json");
156
+ if (!(0, import_fs.existsSync)(manifestPath)) {
157
+ continue;
158
+ }
159
+ let content;
160
+ try {
161
+ content = (0, import_fs.readFileSync)(manifestPath, "utf-8");
162
+ } catch {
163
+ warn(`Skipping unreadable module manifest at ${manifestPath}`);
164
+ continue;
165
+ }
166
+ const slice = parseModuleManifestSlice(content, manifestPath, warn);
167
+ if (slice) {
168
+ manifests.push(slice);
169
+ }
170
+ }
171
+ return manifests.sort((a, b) => a.name.localeCompare(b.name));
172
+ }
173
+ function shouldLoadModuleManifests(noModules) {
174
+ return !noModules;
175
+ }
176
+ function emitModuleDbSlices(schemaTableNames, manifests, warn) {
177
+ const lines = [];
178
+ const moduleMapEntries = [];
179
+ for (const manifest of manifests) {
180
+ const presentTables = manifest.tables.filter((t) => schemaTableNames.has(t));
181
+ const absentTables = manifest.tables.filter((t) => !schemaTableNames.has(t));
182
+ if (absentTables.length > 0) {
183
+ warn(`Module "${manifest.name}": table(s) not in database: ${absentTables.join(", ")}`);
184
+ }
185
+ if (presentTables.length === 0) {
186
+ continue;
187
+ }
188
+ const interfaceName = `${toPascalCase(manifest.name)}ModuleDB`;
189
+ lines.push(`export interface ${interfaceName} {`);
190
+ for (const tableName of presentTables) {
191
+ const tableTypeName = `${toPascalCase(tableName)}Table`;
192
+ lines.push(` ${tableName}: ${tableTypeName};`);
193
+ }
194
+ lines.push("}");
195
+ lines.push("");
196
+ moduleMapEntries.push({ name: manifest.name, interfaceName });
197
+ }
198
+ if (moduleMapEntries.length === 0) {
199
+ return "";
200
+ }
201
+ lines.push("export interface ModuleDBs {");
202
+ for (const { name, interfaceName } of moduleMapEntries) {
203
+ lines.push(` "${name}": ${interfaceName};`);
204
+ }
205
+ lines.push("}");
206
+ lines.push("");
207
+ return lines.join("\n");
208
+ }
209
+
30
210
  // src/cli/schema-ddl.ts
31
211
  function quoteIdent(name) {
32
212
  return `"${name.replace(/"/g, '""')}"`;
@@ -315,8 +495,47 @@ var PG_TYPE_MAP = {
315
495
  function pgTypeToTs(pgType) {
316
496
  return PG_TYPE_MAP[pgType] ?? "unknown";
317
497
  }
318
- function toPascalCase(name) {
319
- return name.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
498
+ function parseCliArgs(argv, options = {}) {
499
+ const cwd = options.cwd ?? process.cwd();
500
+ const env = options.env ?? process.env;
501
+ let connectionString = env.DATA_STORE_URL;
502
+ let store;
503
+ let outputPath = "./src/generated/data-store-types.ts";
504
+ let schemaOutputPath;
505
+ let emitSchema = !(0, import_fs2.existsSync)((0, import_path2.resolve)(cwd, "datastore/ledger-order.json"));
506
+ let modulesDir = (0, import_path2.resolve)(cwd, "./src/modules");
507
+ let noModules = false;
508
+ let help = false;
509
+ for (let i = 0; i < argv.length; i++) {
510
+ if (argv[i] === "--connection-string" && argv[i + 1]) {
511
+ connectionString = argv[++i];
512
+ } else if (argv[i] === "--store" && argv[i + 1]) {
513
+ store = argv[++i];
514
+ } else if (argv[i] === "--output" && argv[i + 1]) {
515
+ outputPath = argv[++i];
516
+ } else if (argv[i] === "--schema-output" && argv[i + 1]) {
517
+ schemaOutputPath = argv[++i];
518
+ emitSchema = true;
519
+ } else if (argv[i] === "--no-schema") {
520
+ emitSchema = false;
521
+ } else if (argv[i] === "--modules-dir" && argv[i + 1]) {
522
+ modulesDir = (0, import_path2.resolve)(cwd, argv[++i]);
523
+ } else if (argv[i] === "--no-modules") {
524
+ noModules = true;
525
+ } else if (argv[i] === "--help") {
526
+ help = true;
527
+ }
528
+ }
529
+ return {
530
+ connectionString,
531
+ store,
532
+ outputPath,
533
+ schemaOutputPath,
534
+ emitSchema,
535
+ modulesDir,
536
+ noModules,
537
+ help
538
+ };
320
539
  }
321
540
  async function introspectSchema(connectionString) {
322
541
  const pool = new import_serverless.Pool({ connectionString });
@@ -348,7 +567,7 @@ async function introspectSchema(connectionString) {
348
567
  await pool.end();
349
568
  }
350
569
  }
351
- function generateTypeScript(tables) {
570
+ function generateTypeScript(tables, moduleSlices) {
352
571
  const lines = [
353
572
  "// AUTO-GENERATED by @stardeck-customer-apps/data-store-sdk \u2014 DO NOT EDIT.",
354
573
  "// This file is overwritten in full on every run of:",
@@ -356,6 +575,8 @@ function generateTypeScript(tables) {
356
575
  "// Anything you add here (hand-written or derived types) WILL BE LOST on the next",
357
576
  "// regeneration. Put those in a separate file that imports from this one, e.g.",
358
577
  "// src/lib/<domain>-types.ts importing the table types or DB from this file.",
578
+ "// When module manifests are discoverable, per-module DB slices and ModuleDBs are",
579
+ "// appended below the flat DB interface.",
359
580
  "",
360
581
  'import type { Generated } from "kysely";',
361
582
  ""
@@ -386,69 +607,148 @@ function generateTypeScript(tables) {
386
607
  }
387
608
  lines.push("}");
388
609
  lines.push("");
389
- return lines.join("\n");
390
- }
391
- async function main() {
392
- const args = process.argv.slice(2);
393
- let connectionString = process.env.DATA_STORE_URL;
394
- let outputPath = "./src/generated/data-store-types.ts";
395
- let schemaOutputPath;
396
- let emitSchema = true;
397
- for (let i = 0; i < args.length; i++) {
398
- if (args[i] === "--connection-string" && args[i + 1]) {
399
- connectionString = args[++i];
400
- } else if (args[i] === "--output" && args[i + 1]) {
401
- outputPath = args[++i];
402
- } else if (args[i] === "--schema-output" && args[i + 1]) {
403
- schemaOutputPath = args[++i];
404
- } else if (args[i] === "--no-schema") {
405
- emitSchema = false;
406
- } else if (args[i] === "--help") {
407
- console.log(`Usage: stardeck-data-store generate-types [options]
408
-
409
- Options:
410
- --connection-string <url> Postgres connection string (default: DATA_STORE_URL env var)
411
- --output <path> Output file path (default: ./src/generated/data-store-types.ts)
412
- --schema-output <path> DDL snapshot path (default: data-store-schema.sql next to --output)
413
- --no-schema Skip the DDL snapshot used by the test harness
414
- --help Show this help message`);
415
- process.exit(0);
610
+ if (moduleSlices) {
611
+ const trimmed = moduleSlices.trimEnd();
612
+ if (trimmed.length > 0) {
613
+ lines.push(trimmed);
614
+ lines.push("");
416
615
  }
417
616
  }
418
- if (!connectionString) {
419
- console.error(
420
- "Error: No connection string provided. Set DATA_STORE_URL or use --connection-string."
421
- );
422
- process.exit(1);
423
- }
617
+ return lines.join("\n");
618
+ }
619
+ async function runGenerateTypes(options) {
620
+ const {
621
+ connectionString,
622
+ outputPath,
623
+ schemaOutputPath,
624
+ emitSchema = true,
625
+ modulesDir,
626
+ noModules,
627
+ introspect = introspectSchema,
628
+ warn = (message) => console.warn(message)
629
+ } = options;
424
630
  console.log("Introspecting database schema...");
425
- const tables = await introspectSchema(connectionString);
631
+ const tables = await introspect(connectionString);
426
632
  if (tables.size === 0) {
427
633
  console.log("No tables found in database.");
428
634
  return;
429
635
  }
430
636
  console.log(`Found ${tables.size} table(s): ${Array.from(tables.keys()).join(", ")}`);
431
- const typeScript = generateTypeScript(tables);
637
+ let moduleSlices = "";
638
+ if (shouldLoadModuleManifests(noModules)) {
639
+ const manifests = discoverModuleManifests(modulesDir, warn);
640
+ if (manifests.length > 0) {
641
+ moduleSlices = emitModuleDbSlices(new Set(tables.keys()), manifests, warn);
642
+ }
643
+ }
644
+ const typeScript = generateTypeScript(tables, moduleSlices || void 0);
432
645
  const dir = outputPath.substring(0, outputPath.lastIndexOf("/"));
433
646
  if (dir) {
434
647
  const { mkdirSync } = await import("fs");
435
648
  mkdirSync(dir, { recursive: true });
436
649
  }
437
- (0, import_fs.writeFileSync)(outputPath, typeScript, "utf-8");
650
+ (0, import_fs2.writeFileSync)(outputPath, typeScript, "utf-8");
438
651
  console.log(`Types written to ${outputPath}`);
439
652
  if (emitSchema) {
440
653
  const schemaPath = schemaOutputPath ?? `${dir ? `${dir}/` : ""}data-store-schema.sql`;
441
654
  const pool = new import_serverless.Pool({ connectionString });
442
655
  try {
443
656
  const snapshot = await introspectSchemaSnapshot(pool);
444
- (0, import_fs.writeFileSync)(schemaPath, generateSchemaSql(snapshot), "utf-8");
657
+ (0, import_fs2.writeFileSync)(schemaPath, generateSchemaSql(snapshot), "utf-8");
445
658
  console.log(`Schema snapshot written to ${schemaPath} (used by the test harness)`);
446
659
  } finally {
447
660
  await pool.end();
448
661
  }
449
662
  }
450
663
  }
451
- main().catch((error) => {
452
- console.error("Failed to generate types:", error);
453
- process.exit(1);
664
+ function connectionStringFromManifest(store) {
665
+ const databases = readDataStoreManifest().filter((entry) => entry.storeType !== "storage");
666
+ if (store) {
667
+ const match = resolveDataStore({ storeName: store });
668
+ if (!match?.url) {
669
+ return {
670
+ error: `No connected database store matches --store ${store}. Connected: ${listStores(databases)}.`
671
+ };
672
+ }
673
+ return { url: match.url };
674
+ }
675
+ if (databases.length === 1 && databases[0]?.url) return { url: databases[0].url };
676
+ if (databases.length === 0) {
677
+ return {
678
+ error: "No connected database store found. Run `npm run env:pull` in apps/web to write .env.local, or pass --connection-string."
679
+ };
680
+ }
681
+ return {
682
+ error: `${databases.length} database stores are connected; pass --store <bindingKey>. Connected: ${listStores(databases)}.`
683
+ };
684
+ }
685
+ function listStores(entries) {
686
+ return entries.length === 0 ? "none" : entries.map((entry) => entry.bindingKey ?? entry.slug).join(", ");
687
+ }
688
+ function loadEnvLocal(cwd) {
689
+ const path = (0, import_path2.resolve)(cwd, ".env.local");
690
+ let content;
691
+ try {
692
+ content = (0, import_fs2.readFileSync)(path, "utf8");
693
+ } catch (error) {
694
+ if (error.code !== "ENOENT") {
695
+ console.warn(`Warning: could not read ${path}: ${error.message}`);
696
+ }
697
+ return;
698
+ }
699
+ for (const [key, value] of Object.entries((0, import_util.parseEnv)(content))) {
700
+ if (value !== void 0 && process.env[key] === void 0) {
701
+ process.env[key] = value.replace(/\\\$/g, "$");
702
+ }
703
+ }
704
+ }
705
+ async function main(argv = process.argv.slice(2)) {
706
+ loadEnvLocal(process.cwd());
707
+ const parsed = parseCliArgs(argv);
708
+ if (parsed.help) {
709
+ console.log(`Usage: stardeck-data-store generate-types [options]
710
+
711
+ Options:
712
+ --connection-string <url> Postgres connection string (default: DATA_STORE_URL, else the
713
+ app's connected store from STARDECK_DATA_STORES / .env.local)
714
+ --store <bindingKey> Which connected store to read when the app has several
715
+ --output <path> Output file path (default: ./src/generated/data-store-types.ts)
716
+ --schema-output <path> DDL snapshot path (default: data-store-schema.sql next to --output)
717
+ --no-schema Skip the DDL snapshot used by the test harness (implied when
718
+ datastore/ledger-order.json exists: a Blueprint composes its own)
719
+ --modules-dir <path> Directory containing module folders (default: ./src/modules)
720
+ --no-modules Skip per-module DB slice generation
721
+ --help Show this help message`);
722
+ process.exit(0);
723
+ }
724
+ let connectionString = parsed.connectionString;
725
+ if (!connectionString) {
726
+ const resolved = connectionStringFromManifest(parsed.store);
727
+ if ("error" in resolved) {
728
+ console.error(`Error: ${resolved.error}`);
729
+ process.exit(1);
730
+ }
731
+ connectionString = resolved.url;
732
+ }
733
+ await runGenerateTypes({
734
+ connectionString,
735
+ outputPath: parsed.outputPath,
736
+ schemaOutputPath: parsed.schemaOutputPath,
737
+ emitSchema: parsed.emitSchema,
738
+ modulesDir: parsed.modulesDir,
739
+ noModules: parsed.noModules
740
+ });
741
+ }
742
+ // Annotate the CommonJS export names for ESM import in node:
743
+ 0 && (module.exports = {
744
+ PG_TYPE_MAP,
745
+ connectionStringFromManifest,
746
+ generateTypeScript,
747
+ introspectSchema,
748
+ loadEnvLocal,
749
+ main,
750
+ parseCliArgs,
751
+ pgTypeToTs,
752
+ runGenerateTypes,
753
+ toPascalCase
454
754
  });