@stardeck-customer-apps/data-store-sdk 0.5.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,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  declare function toPascalCase(name: string): string;
3
2
 
4
3
  interface ColumnInfo {
@@ -13,6 +12,8 @@ declare function pgTypeToTs(pgType: string): string;
13
12
 
14
13
  type ParsedCliArgs = {
15
14
  connectionString: string | undefined;
15
+ /** `--store`: which manifest entry to use when the app has several connected. */
16
+ store: string | undefined;
16
17
  outputPath: string;
17
18
  schemaOutputPath: string | undefined;
18
19
  emitSchema: boolean;
@@ -45,6 +46,30 @@ type RunGenerateTypesOptions = {
45
46
  * so tests can stub the pool without a live database.
46
47
  */
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;
48
73
  declare function main(argv?: string[]): Promise<void>;
49
74
 
50
- export { type ColumnInfo, PG_TYPE_MAP, type ParsedCliArgs, type RunGenerateTypesOptions, generateTypeScript, introspectSchema, main, parseCliArgs, pgTypeToTs, runGenerateTypes, toPascalCase };
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
  declare function toPascalCase(name: string): string;
3
2
 
4
3
  interface ColumnInfo {
@@ -13,6 +12,8 @@ declare function pgTypeToTs(pgType: string): string;
13
12
 
14
13
  type ParsedCliArgs = {
15
14
  connectionString: string | undefined;
15
+ /** `--store`: which manifest entry to use when the app has several connected. */
16
+ store: string | undefined;
16
17
  outputPath: string;
17
18
  schemaOutputPath: string | undefined;
18
19
  emitSchema: boolean;
@@ -45,6 +46,30 @@ type RunGenerateTypesOptions = {
45
46
  * so tests can stub the pool without a live database.
46
47
  */
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;
48
73
  declare function main(argv?: string[]): Promise<void>;
49
74
 
50
- export { type ColumnInfo, PG_TYPE_MAP, type ParsedCliArgs, type RunGenerateTypesOptions, generateTypeScript, introspectSchema, main, parseCliArgs, pgTypeToTs, runGenerateTypes, toPascalCase };
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;
@@ -32,8 +31,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
32
31
  var generate_types_exports = {};
33
32
  __export(generate_types_exports, {
34
33
  PG_TYPE_MAP: () => PG_TYPE_MAP,
34
+ connectionStringFromManifest: () => connectionStringFromManifest,
35
35
  generateTypeScript: () => generateTypeScript,
36
36
  introspectSchema: () => introspectSchema,
37
+ loadEnvLocal: () => loadEnvLocal,
37
38
  main: () => main,
38
39
  parseCliArgs: () => parseCliArgs,
39
40
  pgTypeToTs: () => pgTypeToTs,
@@ -43,9 +44,63 @@ __export(generate_types_exports, {
43
44
  module.exports = __toCommonJS(generate_types_exports);
44
45
  var import_fs2 = require("fs");
45
46
  var import_path2 = require("path");
46
- var import_url = require("url");
47
+ var import_util = require("util");
47
48
  var import_serverless = require("@neondatabase/serverless");
48
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
+
49
104
  // src/cli/module-type-slices.ts
50
105
  var import_fs = require("fs");
51
106
  var import_path = require("path");
@@ -408,7 +463,6 @@ function generateSchemaSql(snapshot) {
408
463
  }
409
464
 
410
465
  // src/cli/generate-types.ts
411
- var import_meta = {};
412
466
  var PG_TYPE_MAP = {
413
467
  text: "string",
414
468
  varchar: "string",
@@ -445,19 +499,23 @@ function parseCliArgs(argv, options = {}) {
445
499
  const cwd = options.cwd ?? process.cwd();
446
500
  const env = options.env ?? process.env;
447
501
  let connectionString = env.DATA_STORE_URL;
502
+ let store;
448
503
  let outputPath = "./src/generated/data-store-types.ts";
449
504
  let schemaOutputPath;
450
- let emitSchema = true;
505
+ let emitSchema = !(0, import_fs2.existsSync)((0, import_path2.resolve)(cwd, "datastore/ledger-order.json"));
451
506
  let modulesDir = (0, import_path2.resolve)(cwd, "./src/modules");
452
507
  let noModules = false;
453
508
  let help = false;
454
509
  for (let i = 0; i < argv.length; i++) {
455
510
  if (argv[i] === "--connection-string" && argv[i + 1]) {
456
511
  connectionString = argv[++i];
512
+ } else if (argv[i] === "--store" && argv[i + 1]) {
513
+ store = argv[++i];
457
514
  } else if (argv[i] === "--output" && argv[i + 1]) {
458
515
  outputPath = argv[++i];
459
516
  } else if (argv[i] === "--schema-output" && argv[i + 1]) {
460
517
  schemaOutputPath = argv[++i];
518
+ emitSchema = true;
461
519
  } else if (argv[i] === "--no-schema") {
462
520
  emitSchema = false;
463
521
  } else if (argv[i] === "--modules-dir" && argv[i + 1]) {
@@ -470,6 +528,7 @@ function parseCliArgs(argv, options = {}) {
470
528
  }
471
529
  return {
472
530
  connectionString,
531
+ store,
473
532
  outputPath,
474
533
  schemaOutputPath,
475
534
  emitSchema,
@@ -602,29 +661,77 @@ async function runGenerateTypes(options) {
602
661
  }
603
662
  }
604
663
  }
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
+ }
605
705
  async function main(argv = process.argv.slice(2)) {
706
+ loadEnvLocal(process.cwd());
606
707
  const parsed = parseCliArgs(argv);
607
708
  if (parsed.help) {
608
709
  console.log(`Usage: stardeck-data-store generate-types [options]
609
710
 
610
711
  Options:
611
- --connection-string <url> Postgres connection string (default: DATA_STORE_URL env var)
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
612
715
  --output <path> Output file path (default: ./src/generated/data-store-types.ts)
613
716
  --schema-output <path> DDL snapshot path (default: data-store-schema.sql next to --output)
614
- --no-schema Skip the DDL snapshot used by the test harness
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)
615
719
  --modules-dir <path> Directory containing module folders (default: ./src/modules)
616
720
  --no-modules Skip per-module DB slice generation
617
721
  --help Show this help message`);
618
722
  process.exit(0);
619
723
  }
620
- if (!parsed.connectionString) {
621
- console.error(
622
- "Error: No connection string provided. Set DATA_STORE_URL or use --connection-string."
623
- );
624
- process.exit(1);
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;
625
732
  }
626
733
  await runGenerateTypes({
627
- connectionString: parsed.connectionString,
734
+ connectionString,
628
735
  outputPath: parsed.outputPath,
629
736
  schemaOutputPath: parsed.schemaOutputPath,
630
737
  emitSchema: parsed.emitSchema,
@@ -632,26 +739,13 @@ Options:
632
739
  noModules: parsed.noModules
633
740
  });
634
741
  }
635
- function isDirectCliRun() {
636
- const entry = process.argv[1];
637
- if (!entry) return false;
638
- try {
639
- return import_meta.url === (0, import_url.pathToFileURL)((0, import_path2.resolve)(entry)).href;
640
- } catch {
641
- return false;
642
- }
643
- }
644
- if (isDirectCliRun()) {
645
- main().catch((error) => {
646
- console.error("Failed to generate types:", error);
647
- process.exit(1);
648
- });
649
- }
650
742
  // Annotate the CommonJS export names for ESM import in node:
651
743
  0 && (module.exports = {
652
744
  PG_TYPE_MAP,
745
+ connectionStringFromManifest,
653
746
  generateTypeScript,
654
747
  introspectSchema,
748
+ loadEnvLocal,
655
749
  main,
656
750
  parseCliArgs,
657
751
  pgTypeToTs,