@supatype/cli 0.3.1 → 0.3.3
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +168 -158
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +77 -0
- package/dist/api-config-cache.d.ts +20 -0
- package/dist/api-config-cache.d.ts.map +1 -1
- package/dist/api-config-cache.js +27 -0
- package/dist/api-config-cache.js.map +1 -1
- package/dist/augmentation-generator.d.ts.map +1 -1
- package/dist/augmentation-generator.js +126 -26
- package/dist/augmentation-generator.js.map +1 -1
- package/dist/cli-version-embedded.js +1 -1
- package/dist/client-generator.d.ts +3 -0
- package/dist/client-generator.d.ts.map +1 -0
- package/dist/client-generator.js +108 -0
- package/dist/client-generator.js.map +1 -0
- package/dist/commands/functions.d.ts.map +1 -1
- package/dist/commands/functions.js +2 -2
- package/dist/commands/functions.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +3 -3
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +36 -29
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +14 -13
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/seed.d.ts +27 -1
- package/dist/commands/seed.d.ts.map +1 -1
- package/dist/commands/seed.js +206 -33
- package/dist/commands/seed.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +19 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/db-port.d.ts +36 -0
- package/dist/db-port.d.ts.map +1 -0
- package/dist/db-port.js +90 -0
- package/dist/db-port.js.map +1 -0
- package/dist/dev-compose.d.ts.map +1 -1
- package/dist/dev-compose.js +108 -13
- package/dist/dev-compose.js.map +1 -1
- package/dist/engine-client.d.ts +1 -0
- package/dist/engine-client.d.ts.map +1 -1
- package/dist/engine-client.js +47 -2
- package/dist/engine-client.js.map +1 -1
- package/dist/engine-floor.d.ts +27 -4
- package/dist/engine-floor.d.ts.map +1 -1
- package/dist/engine-floor.js +90 -9
- package/dist/engine-floor.js.map +1 -1
- package/dist/functions-context-refresh.d.ts +4 -0
- package/dist/functions-context-refresh.d.ts.map +1 -0
- package/dist/functions-context-refresh.js +26 -0
- package/dist/functions-context-refresh.js.map +1 -0
- package/dist/functions-deno-types.d.ts +9 -0
- package/dist/functions-deno-types.d.ts.map +1 -1
- package/dist/functions-deno-types.js +21 -1
- package/dist/functions-deno-types.js.map +1 -1
- package/dist/gitignore.d.ts +3 -1
- package/dist/gitignore.d.ts.map +1 -1
- package/dist/gitignore.js +35 -6
- package/dist/gitignore.js.map +1 -1
- package/dist/hooks-generator.d.ts.map +1 -1
- package/dist/hooks-generator.js +18 -3
- package/dist/hooks-generator.js.map +1 -1
- package/dist/schema-ast-v2.d.ts.map +1 -1
- package/dist/schema-ast-v2.js +17 -8
- package/dist/schema-ast-v2.js.map +1 -1
- package/dist/seed-connection.d.ts +60 -0
- package/dist/seed-connection.d.ts.map +1 -0
- package/dist/seed-connection.js +90 -0
- package/dist/seed-connection.js.map +1 -0
- package/dist/seed-ir.d.ts +66 -0
- package/dist/seed-ir.d.ts.map +1 -0
- package/dist/seed-ir.js +265 -0
- package/dist/seed-ir.js.map +1 -0
- package/dist/seed-runner.d.ts +44 -0
- package/dist/seed-runner.d.ts.map +1 -0
- package/dist/seed-runner.js +83 -0
- package/dist/seed-runner.js.map +1 -0
- package/dist/seed.d.ts +345 -0
- package/dist/seed.d.ts.map +1 -1
- package/dist/seed.js +138 -0
- package/dist/seed.js.map +1 -1
- package/dist/self-host-compose.d.ts +10 -2
- package/dist/self-host-compose.d.ts.map +1 -1
- package/dist/self-host-compose.js +73 -6
- package/dist/self-host-compose.js.map +1 -1
- package/dist/strict.d.ts +29 -0
- package/dist/strict.d.ts.map +1 -0
- package/dist/strict.js +37 -0
- package/dist/strict.js.map +1 -0
- package/dist/studio-dev-server.d.ts +12 -2
- package/dist/studio-dev-server.d.ts.map +1 -1
- package/dist/studio-dev-server.js +12 -2
- package/dist/studio-dev-server.js.map +1 -1
- package/dist/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor.js +81 -2
- package/dist/type-extractor.js.map +1 -1
- package/dist/type-generation.d.ts +43 -0
- package/dist/type-generation.d.ts.map +1 -1
- package/dist/type-generation.js +93 -2
- package/dist/type-generation.js.map +1 -1
- package/package.json +2 -2
- package/src/api-config-cache.ts +30 -0
- package/src/augmentation-generator.ts +142 -26
- package/src/cli-version-embedded.ts +1 -1
- package/src/client-generator.ts +122 -0
- package/src/commands/functions.ts +5 -2
- package/src/commands/generate.ts +7 -3
- package/src/commands/init.ts +41 -31
- package/src/commands/push.ts +14 -15
- package/src/commands/seed.ts +294 -44
- package/src/commands/update.ts +20 -1
- package/src/db-port.ts +99 -0
- package/src/dev-compose.ts +116 -16
- package/src/engine-client.ts +52 -3
- package/src/engine-floor.ts +100 -9
- package/src/functions-context-refresh.ts +25 -0
- package/src/functions-deno-types.ts +24 -1
- package/src/gitignore.ts +38 -10
- package/src/hooks-generator.ts +21 -6
- package/src/schema-ast-v2.ts +17 -8
- package/src/seed-connection.ts +146 -0
- package/src/seed-ir.ts +311 -0
- package/src/seed-runner.ts +109 -0
- package/src/seed.ts +408 -0
- package/src/self-host-compose.ts +74 -6
- package/src/strict.ts +40 -0
- package/src/studio-dev-server.ts +12 -2
- package/src/type-extractor.ts +109 -2
- package/src/type-generation.ts +111 -2
- package/tests/access-composition.test.ts +123 -0
- package/tests/ast-derived-outputs.test.ts +92 -0
- package/tests/augmentation-generator.test.ts +273 -7
- package/tests/client-generator.test.ts +103 -0
- package/tests/db-port.test.ts +128 -0
- package/tests/engine-floor.test.ts +93 -0
- package/tests/external-database-compose.test.ts +8 -1
- package/tests/fixtures/seed_golden_ir.json +186 -0
- package/tests/functions-context-refresh.test.ts +86 -0
- package/tests/gitignore-secrets.test.ts +90 -0
- package/tests/runtime-contract.test.ts +146 -8
- package/tests/seed-connection.test.ts +158 -0
- package/tests/seed-ir.test.ts +436 -0
- package/tests/strict.test.ts +78 -0
- package/tests/type-extractor.test.ts +34 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/type-generation.js
CHANGED
|
@@ -12,9 +12,47 @@
|
|
|
12
12
|
* asked for and must not start creating files in projects that never configured any.
|
|
13
13
|
*/
|
|
14
14
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
15
|
-
import { dirname, resolve } from "node:path";
|
|
15
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
16
16
|
import { generateClientAugmentation } from "./augmentation-generator.js";
|
|
17
17
|
import { ensureEngine, engineRequest } from "./engine-client.js";
|
|
18
|
+
import { generateProjectClient } from "./client-generator.js";
|
|
19
|
+
/** The client a project imports, beside the generated types. */
|
|
20
|
+
const PROJECT_CLIENT_FILENAME = "client.ts";
|
|
21
|
+
/**
|
|
22
|
+
* Where generated output goes when a project has not said otherwise.
|
|
23
|
+
*
|
|
24
|
+
* Exported because two commands need the same answer: `generate` writes these files and
|
|
25
|
+
* `seed` reads one of them, and when they disagreed the builder was written to a path the
|
|
26
|
+
* seed run then reported as missing.
|
|
27
|
+
*/
|
|
28
|
+
export const DEFAULT_TYPES_PATH = "types/database.ts";
|
|
29
|
+
export const DEFAULT_CLIENT_PATH = "supatype/generated/index.d.ts";
|
|
30
|
+
/** The seed builder, beside the client, because both are generated from the same schema. */
|
|
31
|
+
const SEED_BUILDER_FILENAME = "seed.ts";
|
|
32
|
+
/**
|
|
33
|
+
* Where the generated seed builder lives for this project.
|
|
34
|
+
*
|
|
35
|
+
* One rule, exported, because two things need the answer and a seed run that looked
|
|
36
|
+
* somewhere else would report a missing builder for a file that had just been written.
|
|
37
|
+
*/
|
|
38
|
+
export function seedBuilderPath(req) {
|
|
39
|
+
const directory = req.clientPath ?? req.typesPath;
|
|
40
|
+
if (directory === undefined || directory === "")
|
|
41
|
+
return undefined;
|
|
42
|
+
return join(dirname(directory), SEED_BUILDER_FILENAME);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The same answer for a project that has configured nothing.
|
|
46
|
+
*
|
|
47
|
+
* `generate` writes with the defaults applied, so a reader must resolve them the same way or
|
|
48
|
+
* it looks for a file beside a path the project never set.
|
|
49
|
+
*/
|
|
50
|
+
export function seedBuilderPathWithDefaults(output) {
|
|
51
|
+
return (seedBuilderPath({
|
|
52
|
+
typesPath: output?.types ?? DEFAULT_TYPES_PATH,
|
|
53
|
+
clientPath: output?.client ?? DEFAULT_CLIENT_PATH,
|
|
54
|
+
}) ?? join(dirname(DEFAULT_CLIENT_PATH), SEED_BUILDER_FILENAME));
|
|
55
|
+
}
|
|
18
56
|
/** Writes what was asked for and returns one message per file, for the caller to report. */
|
|
19
57
|
export async function writeGeneratedTypes(req) {
|
|
20
58
|
const written = [];
|
|
@@ -32,13 +70,66 @@ export async function writeGeneratedTypes(req) {
|
|
|
32
70
|
writeFileSync(outPath, code, "utf8");
|
|
33
71
|
written.push(`Types written to ${req.typesPath}`);
|
|
34
72
|
}
|
|
35
|
-
|
|
73
|
+
const seedPath = seedBuilderPath(req);
|
|
74
|
+
if (seedPath !== undefined) {
|
|
75
|
+
await ensureEngine();
|
|
76
|
+
const result = await engineRequest("/generate", {
|
|
77
|
+
ast: req.ast,
|
|
78
|
+
lang: "typescript",
|
|
79
|
+
artifact: "seed",
|
|
80
|
+
});
|
|
81
|
+
const code = result.code ?? result.message;
|
|
82
|
+
if (code === undefined) {
|
|
83
|
+
throw new Error("Engine returned no output for the seed builder.");
|
|
84
|
+
}
|
|
85
|
+
const outPath = resolve(req.cwd, seedPath);
|
|
86
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
87
|
+
writeFileSync(outPath, code, "utf8");
|
|
88
|
+
written.push(`Seed builder written to ${toPosix(seedPath)}`);
|
|
89
|
+
}
|
|
90
|
+
written.push(...writeAstDerivedOutputs(req));
|
|
91
|
+
return written;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The generated files that come from the AST alone, with no engine round trip.
|
|
95
|
+
*
|
|
96
|
+
* Shared because three commands need them and only one was writing them. `push` and `generate`
|
|
97
|
+
* call {@link writeGeneratedTypes}; `dev` has its own type generation, for good reasons it should
|
|
98
|
+
* keep, and so never wrote these at all. The result was a project developed entirely through
|
|
99
|
+
* `supatype dev` that never received `index.d.ts`, so the module augmentation that makes
|
|
100
|
+
* `createClient` typed without a generic never happened, and never received the generated client,
|
|
101
|
+
* so every exact-value column fell back to asking the API.
|
|
102
|
+
*
|
|
103
|
+
* Kept separate from the types branch above rather than folding `dev` into it: that branch slices
|
|
104
|
+
* the engine's output from a marker and treats a failure as fatal, and `dev` must do neither.
|
|
105
|
+
*/
|
|
106
|
+
export function writeAstDerivedOutputs(req) {
|
|
107
|
+
const written = [];
|
|
36
108
|
if (req.clientPath !== undefined && req.clientPath !== "") {
|
|
37
109
|
const outPath = resolve(req.cwd, req.clientPath);
|
|
38
110
|
mkdirSync(dirname(outPath), { recursive: true });
|
|
39
111
|
writeFileSync(outPath, generateClientAugmentation(req.ast), "utf8");
|
|
40
112
|
written.push(`Client augmentation written to ${req.clientPath}`);
|
|
41
113
|
}
|
|
114
|
+
// The client the project imports. One file carrying the augmentation, the exact-value columns
|
|
115
|
+
// and a re-export of `createClient`, so an app writes a single import and tsconfig cannot lose
|
|
116
|
+
// any of it. See `client-generator.ts` for what each part replaced.
|
|
117
|
+
// Beside `output.client` when set, otherwise beside `output.types`. A project that configured
|
|
118
|
+
// only types still gets a usable client: this one file carries the augmentation and the exact
|
|
119
|
+
// columns as well as `createClient`, so without it the project has types and no way to use them
|
|
120
|
+
// that knows anything about its schema.
|
|
121
|
+
const clientDir = req.clientPath ?? req.typesPath;
|
|
122
|
+
if (clientDir !== undefined && clientDir !== "") {
|
|
123
|
+
const relative = join(dirname(clientDir), PROJECT_CLIENT_FILENAME);
|
|
124
|
+
const outPath = resolve(req.cwd, relative);
|
|
125
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
126
|
+
writeFileSync(outPath, generateProjectClient(req.ast), "utf8");
|
|
127
|
+
written.push(`Client written to ${toPosix(relative)}`);
|
|
128
|
+
}
|
|
42
129
|
return written;
|
|
43
130
|
}
|
|
131
|
+
/** Reported with forward slashes, so the message reads the same on every platform. */
|
|
132
|
+
function toPosix(path) {
|
|
133
|
+
return path.split(sep).join("/");
|
|
134
|
+
}
|
|
44
135
|
//# sourceMappingURL=type-generation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type-generation.js","sourceRoot":"","sources":["../src/type-generation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"type-generation.js","sourceRoot":"","sources":["../src/type-generation.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supatype/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Supatype CLI: schema push, introspect, generate",
|
|
6
6
|
"type": "module",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@types/pg": "^8",
|
|
42
42
|
"@types/react": "^18.3.18",
|
|
43
43
|
"vitest": "^4.0.18",
|
|
44
|
-
"@supatype/types": "0.3.
|
|
44
|
+
"@supatype/types": "0.3.3"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "tsc --project tsconfig.json && node --input-type=module -e \"import{cpSync,mkdirSync}from'node:fs';const a='dist/assets';mkdirSync(a,{recursive:true});cpSync('assets/supatype-logo-wordmark.ascii.txt',a+'/supatype-logo-wordmark.ascii.txt')\"",
|
package/src/api-config-cache.ts
CHANGED
|
@@ -100,6 +100,36 @@ export function seedApiConfigCache(cwd: string, ast: unknown): CacheSeedResult |
|
|
|
100
100
|
* refused it where the read rule varies by caller. Defaulting it off would make the declaration
|
|
101
101
|
* mean nothing until someone ticked a box that the schema had already justified.
|
|
102
102
|
*/
|
|
103
|
+
/**
|
|
104
|
+
* What a push or a dev boot should say about cache seeding, as lines to print.
|
|
105
|
+
*
|
|
106
|
+
* Returns the text rather than printing it, because the two callers write to the console
|
|
107
|
+
* differently and the thing worth sharing is the decision about what to say, not the formatting.
|
|
108
|
+
* Extracted from `push` when `dev` grew the same need: `seedApiConfigCache` had exactly one caller
|
|
109
|
+
* for a long time, which is why a stack brought up with `supatype dev` alone had no cache
|
|
110
|
+
* allowlist at all and nothing anywhere said so.
|
|
111
|
+
*
|
|
112
|
+
* Never an error, and never a rewrite of an entry that already exists. See `seedApiConfigCache`.
|
|
113
|
+
*/
|
|
114
|
+
export function cacheSeedingNotes(cwd: string, ast: unknown): string[] {
|
|
115
|
+
const result = seedApiConfigCache(cwd, ast)
|
|
116
|
+
if (result === null) return []
|
|
117
|
+
|
|
118
|
+
const notes: string[] = []
|
|
119
|
+
if (result.seeded.length > 0) {
|
|
120
|
+
notes.push(`Server cache enabled for ${result.seeded.join(", ")} in .supatype/api-config.json`)
|
|
121
|
+
}
|
|
122
|
+
if (result.ttlIsOff) {
|
|
123
|
+
// A note rather than a fix. Zero is an off switch someone may have chosen, and a push that
|
|
124
|
+
// turned caching on project-wide would be overriding a decision rather than filling in a blank.
|
|
125
|
+
notes.push(
|
|
126
|
+
`${result.declared.length} table(s) declare a cache, but cache_max_ttl is 0, so nothing ` +
|
|
127
|
+
`is cached until it is set, under API > REST > Settings or in .supatype/api-config.json.`,
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
return notes
|
|
131
|
+
}
|
|
132
|
+
|
|
103
133
|
function seedEntry(entry: ManifestCacheEntry): TableCacheConfig {
|
|
104
134
|
return { enabled: true, allow_public: entry.public === true }
|
|
105
135
|
}
|
|
@@ -38,21 +38,44 @@ function fieldServerGenerated(meta: Record<string, unknown>): boolean {
|
|
|
38
38
|
|
|
39
39
|
export function generateClientAugmentation(ast: unknown): string {
|
|
40
40
|
const parsed = ast as AstShape
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
// In the order the schema declares them. The AST preserves that order, and it is the order
|
|
42
|
+
// the author reads their own schema in.
|
|
43
|
+
const models = [...(parsed.models ?? [])].map((model) => ({
|
|
44
|
+
model,
|
|
45
|
+
tableName: resolveTableName(model),
|
|
46
|
+
}))
|
|
47
|
+
|
|
48
|
+
const kinds = new Set<string>()
|
|
49
|
+
for (const { model } of models) {
|
|
50
|
+
for (const meta of Object.values(model.fields ?? {})) {
|
|
51
|
+
if (typeof meta["kind"] === "string") kinds.add(meta["kind"])
|
|
52
|
+
}
|
|
53
|
+
}
|
|
44
54
|
|
|
45
55
|
const lines: string[] = []
|
|
46
56
|
lines.push("// Generated by supatype CLI, do not edit manually.")
|
|
47
57
|
lines.push("")
|
|
58
|
+
if (kinds.has("richText")) {
|
|
59
|
+
lines.push("import type { SerializedEditorState } from \"@supatype/types/lexical\"")
|
|
60
|
+
lines.push("")
|
|
61
|
+
lines.push("export type RichText = SerializedEditorState")
|
|
62
|
+
lines.push("")
|
|
63
|
+
}
|
|
64
|
+
if (kinds.has("image") || kinds.has("file")) {
|
|
65
|
+
lines.push("export type StorageReference = {")
|
|
66
|
+
lines.push(" bucket: string")
|
|
67
|
+
lines.push(" path: string")
|
|
68
|
+
lines.push("}")
|
|
69
|
+
lines.push("")
|
|
70
|
+
}
|
|
48
71
|
lines.push("declare module \"@supatype/client\" {")
|
|
49
72
|
lines.push(" interface SupatypeModels {")
|
|
50
73
|
|
|
51
74
|
for (const { model, tableName } of models) {
|
|
52
75
|
lines.push(` ${quoteKey(tableName)}: {`)
|
|
53
|
-
lines.push(` Row: ${generateRowType(model.fields)}`)
|
|
54
|
-
lines.push(` Insert: ${generateInsertType(model.fields)}`)
|
|
55
|
-
lines.push(` Update: ${generateUpdateType(model.fields)}`)
|
|
76
|
+
lines.push(` Row: ${indent(generateRowType(model.fields), 6)}`)
|
|
77
|
+
lines.push(` Insert: ${indent(generateInsertType(model.fields), 6)}`)
|
|
78
|
+
lines.push(` Update: ${indent(generateUpdateType(model.fields), 6)}`)
|
|
56
79
|
lines.push(" }")
|
|
57
80
|
}
|
|
58
81
|
|
|
@@ -65,41 +88,117 @@ export function generateClientAugmentation(ast: unknown): string {
|
|
|
65
88
|
return lines.join("\n")
|
|
66
89
|
}
|
|
67
90
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
.
|
|
91
|
+
/** Place a generated object body inside the block that holds it. */
|
|
92
|
+
function indent(source: string, spaces: number): string {
|
|
93
|
+
const pad = " ".repeat(spaces)
|
|
94
|
+
return source
|
|
95
|
+
.split("\n")
|
|
96
|
+
.map((line, i) => (i === 0 ? line : `${pad}${line}`))
|
|
73
97
|
.join("\n")
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function toSnakeCase(name: string): string {
|
|
101
|
+
return name.replace(/([A-Z])/g, "_$1").replace(/^_/, "").toLowerCase()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The column a declared field actually occupies, which is not always the name it was declared under.
|
|
106
|
+
*
|
|
107
|
+
* Only `belongsTo` puts a column on this table, and that column is the foreign key: `speaker_id`
|
|
108
|
+
* holding text, not `speaker` holding the target row. This used to group `relation` with the JSONB
|
|
109
|
+
* kinds, so the augmentation described a column that does not exist while the engine described the
|
|
110
|
+
* one that does, and the two generated files disagreed about the same field. `hasMany`, `hasOne` and
|
|
111
|
+
* `manyToMany` keep their key on the other table, so there is nothing here to describe.
|
|
112
|
+
*/
|
|
113
|
+
function resolveColumn(
|
|
114
|
+
name: string,
|
|
115
|
+
meta: Record<string, unknown>,
|
|
116
|
+
): { column: string; ts: string } | null {
|
|
117
|
+
if (meta["kind"] !== "relation") return { column: name, ts: toTsType(meta) }
|
|
118
|
+
if (meta["cardinality"] !== "belongsTo") return null
|
|
119
|
+
|
|
120
|
+
const annotations = meta["annotations"]
|
|
121
|
+
const declared =
|
|
122
|
+
typeof annotations === "object" && annotations !== null
|
|
123
|
+
? (annotations as { db?: { foreignKey?: unknown } }).db?.foreignKey
|
|
124
|
+
: undefined
|
|
125
|
+
const column =
|
|
126
|
+
typeof declared === "string" && declared.length > 0 ? declared : `${toSnakeCase(name)}_id`
|
|
127
|
+
|
|
128
|
+
// Nullable unless the relation asked for `NOT NULL`, which is the engine's rule. A fix that made
|
|
129
|
+
// every foreign key non-nullable would be wrong in the other direction just as often.
|
|
130
|
+
return { column, ts: meta["required"] === true ? "string" : "string | null" }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Declared fields as the columns they become, dropping the ones that are not columns here. */
|
|
134
|
+
function columnsOf(
|
|
135
|
+
fields: Record<string, Record<string, unknown>>,
|
|
136
|
+
): Array<{ column: string; ts: string; meta: Record<string, unknown> }> {
|
|
137
|
+
return Object.entries(fields)
|
|
138
|
+
.map(([name, meta]) => {
|
|
139
|
+
const resolved = resolveColumn(name, meta)
|
|
140
|
+
return resolved === null ? null : { ...resolved, meta }
|
|
141
|
+
})
|
|
142
|
+
.filter((entry): entry is { column: string; ts: string; meta: Record<string, unknown> } =>
|
|
143
|
+
entry !== null,
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function generateRowType(fields: Record<string, Record<string, unknown>>): string {
|
|
148
|
+
const columns = columnsOf(fields)
|
|
149
|
+
if (columns.length === 0) return "Record<string, unknown>"
|
|
150
|
+
const body = columns.map(({ column, ts }) => ` ${quoteKey(column)}: ${ts}`).join("\n")
|
|
74
151
|
return `{\n${body}\n}`
|
|
75
152
|
}
|
|
76
153
|
|
|
154
|
+
/**
|
|
155
|
+
* What a column accepts on the way in, where that is wider than what it returns.
|
|
156
|
+
*
|
|
157
|
+
* A rich text column holds a Lexical document, and the engine emits a trigger that normalises a
|
|
158
|
+
* plain string into one on insert and update. So writing prose is a string and reading it is
|
|
159
|
+
* always a document, and this is the only place the three shapes differ by more than
|
|
160
|
+
* optionality. Flattening them back would either demand hand-built Lexical JSON from every
|
|
161
|
+
* caller, or claim a read might return a string, which after the trigger it cannot.
|
|
162
|
+
*/
|
|
163
|
+
function widenForWrites(ts: string, meta: Record<string, unknown>): string {
|
|
164
|
+
// Rebuilt rather than edited. The widening has to land on the value the trigger normalises,
|
|
165
|
+
// and for a localized column that is each locale's value, not the map holding them. Splicing
|
|
166
|
+
// ` | string` into the finished string put it outside the locale wrapper, which said a bare
|
|
167
|
+
// string was a valid write to the column; the trigger would then store it as a single document
|
|
168
|
+
// where a locale map belongs, and the Row type says that cannot happen.
|
|
169
|
+
return meta["kind"] === "richText" ? toTsType(meta, true) : ts
|
|
170
|
+
}
|
|
171
|
+
|
|
77
172
|
function insertColumnOptionalOnInsert(meta: Record<string, unknown>): boolean {
|
|
78
|
-
|
|
173
|
+
// `slug` has neither a declared default nor a `serverGenerated` flag, but the engine emits
|
|
174
|
+
// a trigger that fills it, which is the same thing from an insert's point of view.
|
|
175
|
+
return (
|
|
176
|
+
meta["default"] !== undefined || fieldServerGenerated(meta) || meta["kind"] === "slug"
|
|
177
|
+
)
|
|
79
178
|
}
|
|
80
179
|
|
|
81
180
|
export function generateInsertType(fields: Record<string, Record<string, unknown>>): string {
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
const body =
|
|
85
|
-
.map((
|
|
181
|
+
const columns = columnsOf(fields)
|
|
182
|
+
if (columns.length === 0) return "Record<string, unknown>"
|
|
183
|
+
const body = columns
|
|
184
|
+
.map(({ column, ts, meta }) => {
|
|
86
185
|
const required = meta["required"] === true && !insertColumnOptionalOnInsert(meta)
|
|
87
|
-
return ` ${quoteKey(
|
|
186
|
+
return ` ${quoteKey(column)}${required ? "" : "?"}: ${widenForWrites(ts, meta)}`
|
|
88
187
|
})
|
|
89
188
|
.join("\n")
|
|
90
189
|
return `{\n${body}\n}`
|
|
91
190
|
}
|
|
92
191
|
|
|
93
192
|
export function generateUpdateType(fields: Record<string, Record<string, unknown>>): string {
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
const body =
|
|
97
|
-
.map((
|
|
193
|
+
const columns = columnsOf(fields)
|
|
194
|
+
if (columns.length === 0) return "Record<string, unknown>"
|
|
195
|
+
const body = columns
|
|
196
|
+
.map(({ column, ts, meta }) => ` ${quoteKey(column)}?: ${widenForWrites(ts, meta)}`)
|
|
98
197
|
.join("\n")
|
|
99
198
|
return `{\n${body}\n}`
|
|
100
199
|
}
|
|
101
200
|
|
|
102
|
-
function toTsType(meta: Record<string, unknown
|
|
201
|
+
function toTsType(meta: Record<string, unknown>, prose = false): string {
|
|
103
202
|
const kind = typeof meta["kind"] === "string" ? meta["kind"] : "json"
|
|
104
203
|
const required = meta["required"] === true
|
|
105
204
|
const base = (() => {
|
|
@@ -119,6 +218,11 @@ function toTsType(meta: Record<string, unknown>): string {
|
|
|
119
218
|
case "timestamp":
|
|
120
219
|
case "datetime":
|
|
121
220
|
case "money":
|
|
221
|
+
// NUMERIC, and the exact value is the point. A `number` here is the same defect as parsing
|
|
222
|
+
// the wire with `JSON.parse`: `12345678901234567890.1234` has no float representation, so a
|
|
223
|
+
// column declared to hold it would arrive rounded and typed as though it had not been.
|
|
224
|
+
// `@supatype/types` already says so: `Decimal<P, S>` carries `string`.
|
|
225
|
+
case "decimal":
|
|
122
226
|
case "xml":
|
|
123
227
|
case "interval":
|
|
124
228
|
return "string"
|
|
@@ -126,7 +230,6 @@ function toTsType(meta: Record<string, unknown>): string {
|
|
|
126
230
|
case "smallInt":
|
|
127
231
|
case "serial":
|
|
128
232
|
case "float":
|
|
129
|
-
case "decimal":
|
|
130
233
|
return "number"
|
|
131
234
|
case "bigSerial":
|
|
132
235
|
case "bigInt":
|
|
@@ -139,11 +242,16 @@ function toTsType(meta: Record<string, unknown>): string {
|
|
|
139
242
|
case "vector":
|
|
140
243
|
case "relation":
|
|
141
244
|
case "array":
|
|
245
|
+
return "Record<string, unknown>"
|
|
246
|
+
// What `storage.upload()` actually produces, plus the bucket it went to. Emitting
|
|
247
|
+
// `Record<string, unknown>` here is why two example screens cast on the good path. A `url`
|
|
248
|
+
// is deliberately absent: a stored one goes stale and hard-codes the storage host, so it is
|
|
249
|
+
// derived at read time instead.
|
|
142
250
|
case "image":
|
|
143
251
|
case "file":
|
|
144
|
-
return "
|
|
252
|
+
return "StorageReference"
|
|
145
253
|
case "richText":
|
|
146
|
-
return "
|
|
254
|
+
return "RichText"
|
|
147
255
|
case "enum": {
|
|
148
256
|
const values = Array.isArray(meta["values"]) ? meta["values"] as unknown[] : []
|
|
149
257
|
const quoted = values
|
|
@@ -155,7 +263,15 @@ function toTsType(meta: Record<string, unknown>): string {
|
|
|
155
263
|
return "unknown"
|
|
156
264
|
}
|
|
157
265
|
})()
|
|
158
|
-
|
|
266
|
+
// A localized column is JSONB holding a locale map, `{"en": ..., "fr": ...}`, not the bare
|
|
267
|
+
// value. `page.title` is exactly this and was typed `string`, so calling a string method on it
|
|
268
|
+
// compiled and then failed against real data.
|
|
269
|
+
// Prose, which a trigger normalises into a document on the way in. Applied to the element,
|
|
270
|
+
// before the locale wrapper goes on, so a localized column reads `{ [locale: string]: RichText
|
|
271
|
+
// | string }` rather than claiming the whole map could be a string.
|
|
272
|
+
const written = prose && meta["kind"] === "richText" ? `${base} | string` : base
|
|
273
|
+
const shaped = meta["localized"] === true ? `{ [locale: string]: ${written} }` : written
|
|
274
|
+
return required ? shaped : `${shaped} | null`
|
|
159
275
|
}
|
|
160
276
|
|
|
161
277
|
function quoteKey(key: string): string {
|
|
@@ -9,4 +9,4 @@
|
|
|
9
9
|
// Annotated `string`, not left to inference: once a release stamps a value here, the
|
|
10
10
|
// inferred literal type makes the `!== ""` test in cliPackageVersion() a compile error
|
|
11
11
|
// (TS2367, no overlap), so the build would only fail during a release.
|
|
12
|
-
export const EMBEDDED_CLI_VERSION: string = "0.3.
|
|
12
|
+
export const EMBEDDED_CLI_VERSION: string = "0.3.3"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client a project imports, generated into its own source tree.
|
|
3
|
+
*
|
|
4
|
+
* One file, written by `supatype push`, that an app imports once:
|
|
5
|
+
*
|
|
6
|
+
* import { createClient } from "./supatype/client"
|
|
7
|
+
*
|
|
8
|
+
* It carries three things that used to arrive by three routes, two of which could fail without
|
|
9
|
+
* saying so.
|
|
10
|
+
*
|
|
11
|
+
* The **module augmentation** was a `.d.ts` that TypeScript found only because the project's
|
|
12
|
+
* tsconfig `include` happened to name the generated directory. Thirteen tsconfig files across the
|
|
13
|
+
* examples named it, one project never named it at all, and in that state `AugmentedTables` falls
|
|
14
|
+
* back to `Record<string, TableDef>`: every table name is accepted, every column is `unknown`, and
|
|
15
|
+
* the build passes. Reaching it by import instead means tsconfig cannot lose it.
|
|
16
|
+
*
|
|
17
|
+
* The **field kinds** were read from `field-kinds.json` on disk, falling back to fetching
|
|
18
|
+
* PostgREST's OpenAPI document. That fallback reconstructs the answer from the live database, so it
|
|
19
|
+
* always succeeds, which made a project that had never generated indistinguishable from one that
|
|
20
|
+
* had. Compiling them in removes both the read and the fallback, and makes absence detectable.
|
|
21
|
+
*
|
|
22
|
+
* And **`createClient`** itself, so the one import is the whole setup. A project that has not
|
|
23
|
+
* generated has no file here to import, so the build fails rather than the app running with a
|
|
24
|
+
* client that knows nothing about its schema.
|
|
25
|
+
*/
|
|
26
|
+
import { generateClientAugmentation } from "./augmentation-generator.js"
|
|
27
|
+
|
|
28
|
+
/** The kinds whose values no binary float holds, keyed by the schema kind that produces them. */
|
|
29
|
+
const EXACT_KIND_BY_FIELD_KIND: Readonly<Record<string, "bigint" | "numeric">> = {
|
|
30
|
+
bigInt: "bigint",
|
|
31
|
+
bigSerial: "bigint",
|
|
32
|
+
decimal: "numeric",
|
|
33
|
+
money: "numeric",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const FORMAT_VERSION = 1
|
|
37
|
+
|
|
38
|
+
type AstShape = {
|
|
39
|
+
models?: Array<{
|
|
40
|
+
name?: string
|
|
41
|
+
annotations?: Record<string, unknown>
|
|
42
|
+
fields?: Record<string, Record<string, unknown>>
|
|
43
|
+
}>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The full contents of `supatype/client.ts`. */
|
|
47
|
+
export function generateProjectClient(ast: unknown): string {
|
|
48
|
+
// Normalised first: `push` should not die on a malformed AST while writing a file, and the
|
|
49
|
+
// augmentation generator indexes `.models` without checking.
|
|
50
|
+
const safe = ast !== null && typeof ast === "object" ? ast : { models: [] }
|
|
51
|
+
const augmentation = generateClientAugmentation(safe)
|
|
52
|
+
|
|
53
|
+
return [
|
|
54
|
+
"// Generated by supatype CLI, do not edit manually.",
|
|
55
|
+
"//",
|
|
56
|
+
"// Import the client from here rather than from `@supatype/client`:",
|
|
57
|
+
"//",
|
|
58
|
+
'// import { createClient } from "./supatype/client"',
|
|
59
|
+
"//",
|
|
60
|
+
"// This file carries your schema's types and the columns that need exact handling, so one",
|
|
61
|
+
"// import is the whole setup. Importing `@supatype/client` directly gets a client that knows",
|
|
62
|
+
"// nothing about this project.",
|
|
63
|
+
"",
|
|
64
|
+
'import { registerFieldKinds } from "@supatype/client"',
|
|
65
|
+
"",
|
|
66
|
+
augmentationBody(augmentation),
|
|
67
|
+
"",
|
|
68
|
+
"// Columns holding values a JSON parser would round: bigInt, decimal and money. Registered at",
|
|
69
|
+
"// import time, so reading one returns what the database holds rather than the nearest double.",
|
|
70
|
+
`registerFieldKinds(${JSON.stringify(fieldKindsDocument(safe), null, 2)})`,
|
|
71
|
+
"",
|
|
72
|
+
'export * from "@supatype/client"',
|
|
73
|
+
"",
|
|
74
|
+
].join("\n")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The augmentation, without the pieces that only made sense in a file of its own.
|
|
79
|
+
*
|
|
80
|
+
* Its header said "generated", which this file says once already, and it ended in `export {}` to
|
|
81
|
+
* make itself a module. This file has imports and exports of its own, so it is one regardless.
|
|
82
|
+
*/
|
|
83
|
+
function augmentationBody(augmentation: string): string {
|
|
84
|
+
return augmentation
|
|
85
|
+
.replace(/^\/\/ Generated by supatype CLI[^\n]*\n+/, "")
|
|
86
|
+
.replace(/\n*export \{\}\s*$/, "")
|
|
87
|
+
.trimEnd()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The exact-value columns of every table, as the registry expects them. */
|
|
91
|
+
function fieldKindsDocument(ast: unknown): { version: number; tables: Record<string, unknown> } {
|
|
92
|
+
const tables: Record<string, Record<string, string>> = {}
|
|
93
|
+
|
|
94
|
+
for (const model of readModels(ast)) {
|
|
95
|
+
const exact: Record<string, string> = {}
|
|
96
|
+
for (const [column, meta] of Object.entries(model.fields ?? {})) {
|
|
97
|
+
const kind = typeof meta["kind"] === "string" ? meta["kind"] : ""
|
|
98
|
+
const exactKind = EXACT_KIND_BY_FIELD_KIND[kind]
|
|
99
|
+
if (exactKind !== undefined) exact[column] = exactKind
|
|
100
|
+
}
|
|
101
|
+
if (Object.keys(exact).length > 0) {
|
|
102
|
+
tables[resolveTableName(model)] = exact
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { version: FORMAT_VERSION, tables }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function resolveTableName(model: { name?: string; annotations?: Record<string, unknown> }): string {
|
|
110
|
+
const db = model.annotations?.["db"]
|
|
111
|
+
if (db !== null && typeof db === "object") {
|
|
112
|
+
const named = (db as { tableName?: unknown }).tableName
|
|
113
|
+
if (typeof named === "string" && named !== "") return named
|
|
114
|
+
}
|
|
115
|
+
return model.name ?? ""
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readModels(ast: unknown): NonNullable<AstShape["models"]> {
|
|
119
|
+
if (ast === null || typeof ast !== "object") return []
|
|
120
|
+
const models = (ast as AstShape).models
|
|
121
|
+
return Array.isArray(models) ? models : []
|
|
122
|
+
}
|
|
@@ -25,7 +25,10 @@ import {
|
|
|
25
25
|
import { loadProjectLink } from "../link.js"
|
|
26
26
|
import { resolveTarget } from "../resolve-target.js"
|
|
27
27
|
import { targetFetch } from "../target-client.js"
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
ensureFunctionsDenoTypes,
|
|
30
|
+
sharedFunctionsReadmeSource,
|
|
31
|
+
} from "../functions-deno-types.js"
|
|
29
32
|
import { error, info, plain } from "../ui/messages.js"
|
|
30
33
|
import { nextSteps } from "../ui/next-steps.js"
|
|
31
34
|
|
|
@@ -173,7 +176,7 @@ export default async function handler(req: Request, ctx: FunctionContext): Promi
|
|
|
173
176
|
mkdirSync(sharedDir, { recursive: true })
|
|
174
177
|
writeFileSync(
|
|
175
178
|
join(sharedDir, "README.md"),
|
|
176
|
-
|
|
179
|
+
sharedFunctionsReadmeSource(),
|
|
177
180
|
"utf8",
|
|
178
181
|
)
|
|
179
182
|
}
|
package/src/commands/generate.ts
CHANGED
|
@@ -2,7 +2,11 @@ import type { Command } from "commander"
|
|
|
2
2
|
import { loadConfig, loadSchemaAst } from "../config.js"
|
|
3
3
|
import { hooksPathFromProject, schemaPathFromProject } from "../project-config.js"
|
|
4
4
|
import { writeHooksModule } from "../model-hooks.js"
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_CLIENT_PATH,
|
|
7
|
+
DEFAULT_TYPES_PATH,
|
|
8
|
+
writeGeneratedTypes,
|
|
9
|
+
} from "../type-generation.js"
|
|
6
10
|
import { error, info } from "../ui/messages.js"
|
|
7
11
|
|
|
8
12
|
export function registerGenerate(program: Command): void {
|
|
@@ -14,8 +18,8 @@ export function registerGenerate(program: Command): void {
|
|
|
14
18
|
const cwd = process.cwd()
|
|
15
19
|
const config = loadConfig(cwd)
|
|
16
20
|
const schemaPath = schemaPathFromProject(config, cwd)
|
|
17
|
-
const outputTypesPath = config.output?.types ??
|
|
18
|
-
const outputClientPath = config.output?.client ??
|
|
21
|
+
const outputTypesPath = config.output?.types ?? DEFAULT_TYPES_PATH
|
|
22
|
+
const outputClientPath = config.output?.client ?? DEFAULT_CLIENT_PATH
|
|
19
23
|
|
|
20
24
|
info("Loading schema...")
|
|
21
25
|
const ast = loadSchemaAst(schemaPath, cwd)
|