@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.
- package/LICENSE +21 -0
- package/SKILL.md +8 -1
- package/dist/chunk-EWWQPGFI.mjs +662 -0
- package/dist/chunk-FEKROS4B.mjs +66 -0
- package/dist/cli/bin.d.mts +1 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +729 -0
- package/dist/cli/bin.mjs +11 -0
- package/dist/cli/generate-types.d.mts +75 -1
- package/dist/cli/generate-types.d.ts +75 -1
- package/dist/cli/generate-types.js +345 -45
- package/dist/cli/generate-types.mjs +24 -430
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/server/index.d.mts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.mjs +8 -63
- package/dist/{types-D2D9kZNF.d.mts → types-P3VlojJ9.d.mts} +1 -1
- package/dist/{types-D2D9kZNF.d.ts → types-P3VlojJ9.d.ts} +1 -1
- package/package.json +6 -4
package/dist/cli/bin.mjs
ADDED
|
@@ -1 +1,75 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
319
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
});
|