@supatype/cli 0.1.0-alpha.10 → 0.1.0-alpha.12
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 +98 -65
- package/.turbo/turbo-typecheck.log +1 -1
- package/dist/app/framework.js +1 -3
- package/dist/app/framework.js.map +1 -1
- package/dist/app/proxy-dev-app.d.ts +14 -0
- package/dist/app/proxy-dev-app.d.ts.map +1 -1
- package/dist/app/proxy-dev-app.js +109 -6
- package/dist/app/proxy-dev-app.js.map +1 -1
- package/dist/binary-cache.d.ts +1 -1
- package/dist/binary-cache.d.ts.map +1 -1
- package/dist/binary-cache.js +6 -1
- package/dist/binary-cache.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts +3 -0
- package/dist/commands/adopt.d.ts.map +1 -0
- package/dist/commands/adopt.js +58 -0
- package/dist/commands/adopt.js.map +1 -0
- package/dist/commands/cloud.d.ts +4 -9
- package/dist/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +49 -91
- package/dist/commands/cloud.js.map +1 -1
- package/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +25 -47
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +117 -74
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +21 -3
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +37 -37
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +77 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/functions.d.ts.map +1 -1
- package/dist/commands/functions.js +80 -33
- package/dist/commands/functions.js.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +26 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/introspect.d.ts +3 -0
- package/dist/commands/introspect.d.ts.map +1 -0
- package/dist/commands/introspect.js +34 -0
- package/dist/commands/introspect.js.map +1 -0
- package/dist/commands/link-helpers.d.ts +15 -0
- package/dist/commands/link-helpers.d.ts.map +1 -0
- package/dist/commands/link-helpers.js +187 -0
- package/dist/commands/link-helpers.js.map +1 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +116 -14
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +32 -5
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +102 -129
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +93 -29
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +6 -2
- package/dist/commands/update.js.map +1 -1
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/dev-compose.d.ts +23 -0
- package/dist/dev-compose.d.ts.map +1 -1
- package/dist/dev-compose.js +183 -6
- package/dist/dev-compose.js.map +1 -1
- package/dist/diff-output.d.ts +5 -1
- package/dist/diff-output.d.ts.map +1 -1
- package/dist/diff-output.js +69 -0
- package/dist/diff-output.js.map +1 -1
- package/dist/engine-client.d.ts +10 -1
- package/dist/engine-client.d.ts.map +1 -1
- package/dist/engine-client.js +64 -13
- package/dist/engine-client.js.map +1 -1
- package/dist/engine-push-output.d.ts +1 -0
- package/dist/engine-push-output.d.ts.map +1 -1
- package/dist/engine-push-output.js +4 -1
- package/dist/engine-push-output.js.map +1 -1
- package/dist/gitignore.d.ts +8 -0
- package/dist/gitignore.d.ts.map +1 -0
- package/dist/gitignore.js +41 -0
- package/dist/gitignore.js.map +1 -0
- package/dist/link.d.ts +66 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +159 -0
- package/dist/link.js.map +1 -0
- package/dist/process-manager.d.ts +2 -0
- package/dist/process-manager.d.ts.map +1 -1
- package/dist/process-manager.js +2 -0
- package/dist/process-manager.js.map +1 -1
- package/dist/project-config.d.ts +8 -0
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js.map +1 -1
- package/dist/pull-utils.d.ts +50 -14
- package/dist/pull-utils.d.ts.map +1 -1
- package/dist/pull-utils.js +152 -12
- package/dist/pull-utils.js.map +1 -1
- package/dist/resolve-target.d.ts +86 -0
- package/dist/resolve-target.d.ts.map +1 -0
- package/dist/resolve-target.js +291 -0
- package/dist/resolve-target.js.map +1 -0
- package/dist/runtime-routes.d.ts.map +1 -1
- package/dist/runtime-routes.js +7 -0
- package/dist/runtime-routes.js.map +1 -1
- package/dist/schema-ast-v2.d.ts +1 -1
- package/dist/schema-ast-v2.d.ts.map +1 -1
- package/dist/schema-ast-v2.js +2 -2
- package/dist/schema-ast-v2.js.map +1 -1
- package/dist/schema-sources.d.ts +40 -0
- package/dist/schema-sources.d.ts.map +1 -0
- package/dist/schema-sources.js +183 -0
- package/dist/schema-sources.js.map +1 -0
- package/dist/self-host-compose.d.ts +10 -0
- package/dist/self-host-compose.d.ts.map +1 -1
- package/dist/self-host-compose.js +85 -3
- package/dist/self-host-compose.js.map +1 -1
- package/dist/storage-provision.d.ts +4 -0
- package/dist/storage-provision.d.ts.map +1 -1
- package/dist/storage-provision.js +24 -2
- package/dist/storage-provision.js.map +1 -1
- package/dist/target-client.d.ts +10 -0
- package/dist/target-client.d.ts.map +1 -0
- package/dist/target-client.js +22 -0
- package/dist/target-client.js.map +1 -0
- package/dist/type-extractor.d.ts +11 -0
- package/dist/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor.js +95 -8
- package/dist/type-extractor.js.map +1 -1
- package/package.json +1 -1
- package/src/app/framework.ts +1 -3
- package/src/app/proxy-dev-app.ts +113 -6
- package/src/binary-cache.ts +6 -1
- package/src/cli.ts +6 -0
- package/src/commands/adopt.ts +83 -0
- package/src/commands/cloud.ts +66 -108
- package/src/commands/db.ts +28 -52
- package/src/commands/deploy.ts +162 -104
- package/src/commands/dev.ts +24 -10
- package/src/commands/diff.ts +40 -41
- package/src/commands/doctor.ts +102 -0
- package/src/commands/functions.ts +95 -37
- package/src/commands/init.ts +25 -4
- package/src/commands/introspect.ts +47 -0
- package/src/commands/link-helpers.ts +228 -0
- package/src/commands/migrate.ts +163 -15
- package/src/commands/pull.ts +37 -9
- package/src/commands/push.ts +132 -166
- package/src/commands/status.ts +100 -33
- package/src/commands/update.ts +6 -2
- package/src/config.ts +2 -1
- package/src/dev-compose.ts +240 -6
- package/src/diff-output.ts +79 -1
- package/src/engine-client.ts +70 -13
- package/src/engine-push-output.ts +7 -3
- package/src/gitignore.ts +48 -0
- package/src/link.ts +242 -0
- package/src/process-manager.ts +4 -0
- package/src/project-config.ts +8 -0
- package/src/pull-utils.ts +217 -23
- package/src/resolve-target.ts +419 -0
- package/src/runtime-routes.ts +7 -0
- package/src/schema-ast-v2.ts +2 -1
- package/src/schema-sources.ts +248 -0
- package/src/self-host-compose.ts +87 -3
- package/src/storage-provision.ts +33 -1
- package/src/target-client.ts +40 -0
- package/src/type-extractor.ts +124 -11
- package/tests/cli-help.test.ts +27 -2
- package/tests/init.test.ts +1 -1
- package/tests/link.test.ts +148 -0
- package/tests/proxy-dev-app.test.ts +45 -1
- package/tests/pull-utils.test.ts +5 -4
- package/tests/runtime-contract.test.ts +44 -1
- package/tests/schema-sources.test.ts +119 -0
- package/tests/storage-provision.test.ts +100 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/schema-ast-v2.ts
CHANGED
|
@@ -293,13 +293,14 @@ export function emitModel(
|
|
|
293
293
|
options: Record<string, unknown>,
|
|
294
294
|
tableName: string,
|
|
295
295
|
access: Record<string, unknown>,
|
|
296
|
+
indexes: unknown[] = [],
|
|
296
297
|
): ModelAstV2 {
|
|
297
298
|
return {
|
|
298
299
|
name,
|
|
299
300
|
fields,
|
|
300
301
|
options,
|
|
301
302
|
annotations: {
|
|
302
|
-
db: { tableName, indexes
|
|
303
|
+
db: { tableName, indexes },
|
|
303
304
|
platform: { access },
|
|
304
305
|
},
|
|
305
306
|
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
cpSync,
|
|
8
|
+
} from "node:fs"
|
|
9
|
+
import { join, resolve, relative } from "node:path"
|
|
10
|
+
import { gzipSync, gunzipSync } from "node:zlib"
|
|
11
|
+
import { collectSchemaSourcePaths, type SchemaSourceGraph } from "./type-extractor.js"
|
|
12
|
+
import { loadConfig } from "./config.js"
|
|
13
|
+
import { projectRootFromConfig, schemaPathFromProject } from "./project-config.js"
|
|
14
|
+
|
|
15
|
+
const MAX_COMPRESSED_BYTES = 2 * 1024 * 1024
|
|
16
|
+
const WARN_COMPRESSED_BYTES = 512 * 1024
|
|
17
|
+
|
|
18
|
+
export interface SchemaSourcesManifest {
|
|
19
|
+
version: number
|
|
20
|
+
format: "tar+gzip"
|
|
21
|
+
entryPoint: string
|
|
22
|
+
fileCount: number
|
|
23
|
+
uncompressedBytes: number
|
|
24
|
+
compressedBytes: number
|
|
25
|
+
files: Array<{ path: string; sha256: string; bytes: number }>
|
|
26
|
+
pushedBy?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SchemaSourcesPayload {
|
|
30
|
+
manifest: SchemaSourcesManifest
|
|
31
|
+
dataBase64: string
|
|
32
|
+
gz: Buffer
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function padTarField(value: string, len: number): string {
|
|
36
|
+
return value.slice(0, len).padEnd(len, "\0")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function tarChecksum(header: Buffer): number {
|
|
40
|
+
let sum = 0
|
|
41
|
+
for (let i = 0; i < 512; i++) {
|
|
42
|
+
sum += i >= 148 && i < 156 ? 32 : header[i]!
|
|
43
|
+
}
|
|
44
|
+
return sum
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function writeTarEntry(name: string, content: Buffer): Buffer {
|
|
48
|
+
const header = Buffer.alloc(512, 0)
|
|
49
|
+
const size = content.length
|
|
50
|
+
header.write(padTarField(name, 100), 0, 100, "ascii")
|
|
51
|
+
header.write("0000644\0", 100, 8, "ascii")
|
|
52
|
+
header.write("0000000\0", 108, 8, "ascii")
|
|
53
|
+
header.write("0000000\0", 116, 8, "ascii")
|
|
54
|
+
header.write(padTarField(size.toString(8), 11), 124, 12, "ascii")
|
|
55
|
+
header.write("00000000000\0", 136, 12, "ascii")
|
|
56
|
+
header.write(" ", 148, 8, "ascii")
|
|
57
|
+
header.write("ustar\0", 257, 6, "ascii")
|
|
58
|
+
header.write("00", 263, 2, "ascii")
|
|
59
|
+
const chk = tarChecksum(header)
|
|
60
|
+
header.write(chk.toString(8).padStart(6, "0") + "\0 ", 148, 8, "ascii")
|
|
61
|
+
|
|
62
|
+
const pad = (512 - (size % 512)) % 512
|
|
63
|
+
return Buffer.concat([header, content, Buffer.alloc(pad)])
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function packSchemaSources(graph: SchemaSourceGraph): Buffer {
|
|
67
|
+
const chunks: Buffer[] = []
|
|
68
|
+
for (const file of graph.files) {
|
|
69
|
+
const content = readFileSync(file.absolutePath)
|
|
70
|
+
chunks.push(writeTarEntry(file.relativePath.replace(/\\/g, "/"), content))
|
|
71
|
+
}
|
|
72
|
+
chunks.push(Buffer.alloc(1024, 0))
|
|
73
|
+
return Buffer.concat(chunks)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function unpackSchemaSources(tar: Buffer, projectRoot: string): Map<string, Buffer> {
|
|
77
|
+
const root = resolve(projectRoot)
|
|
78
|
+
const out = new Map<string, Buffer>()
|
|
79
|
+
let offset = 0
|
|
80
|
+
while (offset + 512 <= tar.length) {
|
|
81
|
+
const header = tar.subarray(offset, offset + 512)
|
|
82
|
+
offset += 512
|
|
83
|
+
if (header.every((b) => b === 0)) break
|
|
84
|
+
const name = header.subarray(0, 100).toString("utf8").replace(/\0.*$/, "").trim()
|
|
85
|
+
if (!name) break
|
|
86
|
+
const sizeOct = header.subarray(124, 136).toString("utf8").replace(/\0.*$/, "").trim()
|
|
87
|
+
const size = parseInt(sizeOct, 8) || 0
|
|
88
|
+
const content = tar.subarray(offset, offset + size)
|
|
89
|
+
offset += size + ((512 - (size % 512)) % 512)
|
|
90
|
+
const rel = name.replace(/\\/g, "/")
|
|
91
|
+
if (rel.startsWith("..") || rel.includes("/../")) {
|
|
92
|
+
throw new Error(`Invalid tar path: ${name}`)
|
|
93
|
+
}
|
|
94
|
+
const dest = resolve(root, rel)
|
|
95
|
+
if (!dest.startsWith(root)) {
|
|
96
|
+
throw new Error(`Tar path escapes project root: ${name}`)
|
|
97
|
+
}
|
|
98
|
+
out.set(rel, Buffer.from(content))
|
|
99
|
+
}
|
|
100
|
+
return out
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function buildSchemaSourcesPayload(
|
|
104
|
+
cwd: string,
|
|
105
|
+
pushedBy?: string,
|
|
106
|
+
): SchemaSourcesPayload | null {
|
|
107
|
+
const config = loadConfig(cwd)
|
|
108
|
+
const root = projectRootFromConfig(config, cwd)
|
|
109
|
+
const entry = schemaPathFromProject(config, cwd)
|
|
110
|
+
const graph = collectSchemaSourcePaths(entry, root)
|
|
111
|
+
if (graph.files.length === 0) return null
|
|
112
|
+
|
|
113
|
+
const tarBuf = packSchemaSources(graph)
|
|
114
|
+
const gz = gzipSync(tarBuf, { level: 9 })
|
|
115
|
+
|
|
116
|
+
if (gz.length > MAX_COMPRESSED_BYTES) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Schema snapshot too large (${(gz.length / 1024).toFixed(0)} KB compressed, max 2 MB). Split schema modules.`,
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
if (gz.length > WARN_COMPRESSED_BYTES) {
|
|
122
|
+
console.warn(`Warning: large schema snapshot (${(gz.length / 1024).toFixed(0)} KB compressed).`)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const manifest: SchemaSourcesManifest = {
|
|
126
|
+
version: 1,
|
|
127
|
+
format: "tar+gzip",
|
|
128
|
+
entryPoint: graph.entryPoint,
|
|
129
|
+
fileCount: graph.files.length,
|
|
130
|
+
uncompressedBytes: graph.files.reduce((s, f) => s + f.bytes, 0),
|
|
131
|
+
compressedBytes: gz.length,
|
|
132
|
+
files: graph.files.map((f) => ({ path: f.relativePath, sha256: f.sha256, bytes: f.bytes })),
|
|
133
|
+
...(pushedBy ? { pushedBy } : {}),
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
manifest,
|
|
138
|
+
dataBase64: gz.toString("base64"),
|
|
139
|
+
gz,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function cacheSchemaSourcesLocally(cwd: string, migrationName: string, gz: Buffer): void {
|
|
144
|
+
const dir = join(cwd, ".supatype", "schema-snapshots")
|
|
145
|
+
mkdirSync(dir, { recursive: true })
|
|
146
|
+
writeFileSync(join(dir, `${migrationName}.tar.gz`), gz)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function restoreSchemaSourcesFromGz(
|
|
150
|
+
gz: Buffer,
|
|
151
|
+
manifest: SchemaSourcesManifest,
|
|
152
|
+
projectRoot: string,
|
|
153
|
+
opts?: { backupDir?: string },
|
|
154
|
+
): string[] {
|
|
155
|
+
const tar = gunzipSync(gz)
|
|
156
|
+
const files = unpackSchemaSources(tar, projectRoot)
|
|
157
|
+
const root = resolve(projectRoot)
|
|
158
|
+
const restored: string[] = []
|
|
159
|
+
|
|
160
|
+
if (opts?.backupDir) {
|
|
161
|
+
mkdirSync(opts.backupDir, { recursive: true })
|
|
162
|
+
for (const path of manifest.files) {
|
|
163
|
+
const abs = join(root, path.path)
|
|
164
|
+
if (existsSync(abs)) {
|
|
165
|
+
const dest = join(opts.backupDir, path.path)
|
|
166
|
+
mkdirSync(join(dest, ".."), { recursive: true })
|
|
167
|
+
cpSync(abs, dest)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for (const [rel, content] of files) {
|
|
173
|
+
const abs = join(root, rel)
|
|
174
|
+
mkdirSync(join(abs, ".."), { recursive: true })
|
|
175
|
+
writeFileSync(abs, content)
|
|
176
|
+
restored.push(rel)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return restored
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function findOrphanSchemaFiles(
|
|
183
|
+
projectRoot: string,
|
|
184
|
+
entryPoint: string,
|
|
185
|
+
manifestPaths: Set<string>,
|
|
186
|
+
): string[] {
|
|
187
|
+
const entry = resolve(projectRoot, entryPoint)
|
|
188
|
+
const graph = collectSchemaSourcePaths(entry, projectRoot)
|
|
189
|
+
const orphans: string[] = []
|
|
190
|
+
const schemaDir = join(projectRoot, graph.entryPoint.split("/").slice(0, -1).join("/") || ".")
|
|
191
|
+
if (!existsSync(schemaDir)) return orphans
|
|
192
|
+
|
|
193
|
+
const walk = (dir: string): void => {
|
|
194
|
+
for (const ent of readdirSync(dir, { withFileTypes: true })) {
|
|
195
|
+
const abs = join(dir, ent.name)
|
|
196
|
+
if (ent.isDirectory()) {
|
|
197
|
+
walk(abs)
|
|
198
|
+
continue
|
|
199
|
+
}
|
|
200
|
+
if (!/\.(ts|tsx)$/.test(ent.name)) continue
|
|
201
|
+
const rel = relative(projectRoot, abs).replace(/\\/g, "/")
|
|
202
|
+
if (!manifestPaths.has(rel) && !graph.files.some((f) => f.relativePath === rel)) {
|
|
203
|
+
orphans.push(rel)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
walk(schemaDir)
|
|
208
|
+
return orphans
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function resolvePushedBy(): string {
|
|
212
|
+
return (
|
|
213
|
+
process.env["SUPATYPE_PUSHED_BY"] ??
|
|
214
|
+
process.env["USER"] ??
|
|
215
|
+
process.env["USERNAME"] ??
|
|
216
|
+
"local"
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface SchemaSourcePushArtifacts {
|
|
221
|
+
gzPath: string
|
|
222
|
+
manifestPath: string
|
|
223
|
+
/** Paths inside the Docker /project bind mount. */
|
|
224
|
+
dockerGzPath: string
|
|
225
|
+
dockerManifestPath: string
|
|
226
|
+
payload: SchemaSourcesPayload
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Write schema source blob + manifest under `.supatype/` for engine push (CLI + dev watch). */
|
|
230
|
+
export function writeSchemaSourcePushArtifacts(cwd: string): SchemaSourcePushArtifacts | null {
|
|
231
|
+
const payload = buildSchemaSourcesPayload(cwd, resolvePushedBy())
|
|
232
|
+
if (!payload) return null
|
|
233
|
+
|
|
234
|
+
const dir = join(cwd, ".supatype")
|
|
235
|
+
mkdirSync(dir, { recursive: true })
|
|
236
|
+
const gzPath = join(dir, "schema-sources-push.gz")
|
|
237
|
+
const manifestPath = join(dir, "schema-sources-manifest.json")
|
|
238
|
+
writeFileSync(gzPath, payload.gz)
|
|
239
|
+
writeFileSync(manifestPath, JSON.stringify(payload.manifest))
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
gzPath,
|
|
243
|
+
manifestPath,
|
|
244
|
+
dockerGzPath: "/project/.supatype/schema-sources-push.gz",
|
|
245
|
+
dockerManifestPath: "/project/.supatype/schema-sources-manifest.json",
|
|
246
|
+
payload,
|
|
247
|
+
}
|
|
248
|
+
}
|
package/src/self-host-compose.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs"
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
2
2
|
import { dirname, join, relative, resolve } from "node:path"
|
|
3
3
|
import { spawnSync } from "node:child_process"
|
|
4
4
|
import { preferredFunctionsPathFromProject, type SupatypeProjectConfig } from "./project-config.js"
|
|
@@ -12,6 +12,16 @@ export const COMPOSE_PINNED_IMAGE_ENV_KEYS = [
|
|
|
12
12
|
"SUPATYPE_POSTGRES_IMAGE",
|
|
13
13
|
] as const
|
|
14
14
|
|
|
15
|
+
/** Compose image env vars that may be overridden manually in `.env`. */
|
|
16
|
+
export const COMPOSE_IMAGE_ENV_KEYS = [
|
|
17
|
+
...COMPOSE_PINNED_IMAGE_ENV_KEYS,
|
|
18
|
+
"SUPATYPE_CONTROL_PLANE_IMAGE",
|
|
19
|
+
"SUPATYPE_AUTH_IMAGE",
|
|
20
|
+
"SUPATYPE_STUDIO_IMAGE",
|
|
21
|
+
"SUPATYPE_STORAGE_IMAGE",
|
|
22
|
+
"SUPATYPE_FUNCTIONS_WORKER_IMAGE",
|
|
23
|
+
] as const
|
|
24
|
+
|
|
15
25
|
type DockerPinComponent = "engine" | "server" | "postgres"
|
|
16
26
|
|
|
17
27
|
/** Map a config version pin to a Docker Hub image reference. */
|
|
@@ -57,6 +67,52 @@ export function composeDockerImageEnv(config: SupatypeProjectConfig): Record<str
|
|
|
57
67
|
return env
|
|
58
68
|
}
|
|
59
69
|
|
|
70
|
+
/** True when a Docker image tag is a semver/latest ref we expect `docker pull` to resolve. */
|
|
71
|
+
export function isRegistryPullableImageRef(ref: string): boolean {
|
|
72
|
+
const trimmed = ref.trim()
|
|
73
|
+
if (!trimmed) return true
|
|
74
|
+
const tag = trimmed.includes(":") ? trimmed.slice(trimmed.lastIndexOf(":") + 1) : "latest"
|
|
75
|
+
if (tag === "latest") return true
|
|
76
|
+
if (/^v?\d+\.\d+/.test(tag)) return true
|
|
77
|
+
if (/^\d+-latest$/.test(tag)) return true
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function hasLocalVersionPins(config: SupatypeProjectConfig): boolean {
|
|
82
|
+
const versions = config.versions
|
|
83
|
+
if (!versions) return false
|
|
84
|
+
return (
|
|
85
|
+
versions.engine === VERSION_PIN_LOCAL ||
|
|
86
|
+
versions.server === VERSION_PIN_LOCAL ||
|
|
87
|
+
versions.postgres === VERSION_PIN_LOCAL ||
|
|
88
|
+
versions.deno === VERSION_PIN_LOCAL
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readComposeImageEnvValues(cwd: string): string[] {
|
|
93
|
+
const envPath = resolve(cwd, ".env")
|
|
94
|
+
if (!existsSync(envPath)) return []
|
|
95
|
+
const text = readFileSync(envPath, "utf8")
|
|
96
|
+
const values: string[] = []
|
|
97
|
+
for (const key of COMPOSE_IMAGE_ENV_KEYS) {
|
|
98
|
+
const match = text.match(new RegExp(`^${key}=(.+)$`, "m"))
|
|
99
|
+
if (match?.[1]) values.push(match[1].trim())
|
|
100
|
+
}
|
|
101
|
+
return values
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Use `docker compose pull --ignore-pull-failures` only when the project may
|
|
106
|
+
* reference local-only images (config `versions: local` or custom `.env` tags).
|
|
107
|
+
*/
|
|
108
|
+
export function composePullNeedsIgnoreFailures(
|
|
109
|
+
config: SupatypeProjectConfig,
|
|
110
|
+
cwd: string = process.cwd(),
|
|
111
|
+
): boolean {
|
|
112
|
+
if (hasLocalVersionPins(config)) return true
|
|
113
|
+
return readComposeImageEnvValues(cwd).some((ref) => !isRegistryPullableImageRef(ref))
|
|
114
|
+
}
|
|
115
|
+
|
|
60
116
|
/**
|
|
61
117
|
* Schema-engine image for a one-off `docker compose run` when pushing schema.
|
|
62
118
|
* Uses config pin when set; otherwise CDN engine semver (Docker Hub `:latest` can lag).
|
|
@@ -174,6 +230,8 @@ export function renderSelfHostCompose(
|
|
|
174
230
|
const devLocal = options?.devLocal === true
|
|
175
231
|
const studioHostDev = devLocal && hasStudioOverride(config)
|
|
176
232
|
const appEnv = serverAppEnvForCompose(config, devLocal)
|
|
233
|
+
const staticDir = staticDirForCompose(config) ?? "./dist"
|
|
234
|
+
const composeProject = composeProjectName(config.project.name)
|
|
177
235
|
const studioService = studioServiceBlock()
|
|
178
236
|
const studioBlock = studioHostDev
|
|
179
237
|
? ""
|
|
@@ -186,9 +244,11 @@ ${studioService}
|
|
|
186
244
|
- "3002"
|
|
187
245
|
`
|
|
188
246
|
const kongDependsOn = studioHostDev
|
|
189
|
-
? ` - server
|
|
247
|
+
? ` - server
|
|
248
|
+
- control-plane`
|
|
190
249
|
: ` - server
|
|
191
|
-
- studio
|
|
250
|
+
- studio
|
|
251
|
+
- control-plane`
|
|
192
252
|
const publishDbToHost = !devLocal || hasEngineOverride(config)
|
|
193
253
|
const dbPorts = publishDbToHost
|
|
194
254
|
? devLocal
|
|
@@ -280,6 +340,27 @@ ${dbPorts} volumes:
|
|
|
280
340
|
db:
|
|
281
341
|
condition: service_healthy
|
|
282
342
|
|
|
343
|
+
control-plane:
|
|
344
|
+
image: \${SUPATYPE_CONTROL_PLANE_IMAGE:-supatype/control-plane:latest}
|
|
345
|
+
expose:
|
|
346
|
+
- "8080"
|
|
347
|
+
volumes:
|
|
348
|
+
- ${projectMount}:/project
|
|
349
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
350
|
+
environment:
|
|
351
|
+
PORT: "8080"
|
|
352
|
+
SUPATYPE_PROJECT_REF: ${JSON.stringify(config.project.name)}
|
|
353
|
+
SUPATYPE_PROJECT_ROOT: /project
|
|
354
|
+
DATABASE_URL: "postgresql://\${POSTGRES_USER:-supatype_admin}:\${POSTGRES_PASSWORD:-postgres}@db:5432/\${POSTGRES_DB:-supatype}"
|
|
355
|
+
SUPATYPE_FUNCTIONS_ROOT: /project/functions
|
|
356
|
+
SUPATYPE_STATIC_ROOT: /project/${staticDir.replace(/^\.\//, "")}
|
|
357
|
+
SUPATYPE_DEPLOYMENTS_DIR: /project/.supatype/deployments
|
|
358
|
+
COMPOSE_PROJECT_NAME: ${composeProject}
|
|
359
|
+
SUPATYPE_ENGINE_BIN: supatype-engine
|
|
360
|
+
depends_on:
|
|
361
|
+
db:
|
|
362
|
+
condition: service_healthy
|
|
363
|
+
|
|
283
364
|
server:
|
|
284
365
|
image: \${SUPATYPE_SERVER_IMAGE:-\${SUPATYPE_AUTH_IMAGE:-supatype/server:latest}}
|
|
285
366
|
${serverPorts} volumes:
|
|
@@ -299,6 +380,7 @@ ${serverPorts} volumes:
|
|
|
299
380
|
SUPATYPE_SQL_DATABASE_URL: "postgresql://\${POSTGRES_USER:-supatype_admin}:\${POSTGRES_PASSWORD:-postgres}@db:5432/\${POSTGRES_DB:-supatype}"
|
|
300
381
|
SUPATYPE_DENO_FUNCTIONS_DIR: /project/functions
|
|
301
382
|
SUPATYPE_FUNCTIONS_WORKER_URL: http://functions-worker:8001
|
|
383
|
+
SUPATYPE_CONTROL_PLANE_URL: http://control-plane:8080
|
|
302
384
|
${appEnv}
|
|
303
385
|
GOTRUE_API_HOST: 0.0.0.0
|
|
304
386
|
GOTRUE_API_PORT: 9999
|
|
@@ -324,6 +406,8 @@ ${devLocal ? " STUDIO_OPEN_DEV: \"1\"\n" : ""}
|
|
|
324
406
|
condition: service_started
|
|
325
407
|
functions-worker:
|
|
326
408
|
condition: service_started
|
|
409
|
+
control-plane:
|
|
410
|
+
condition: service_started
|
|
327
411
|
|
|
328
412
|
minio:
|
|
329
413
|
image: minio/minio:RELEASE.2024-11-07T00-52-20Z
|
package/src/storage-provision.ts
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
* Buckets already registered return 409 Conflict, which is treated as success.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import type { ExtractedSchemaAstV2 } from "./schema-ast-v2.js"
|
|
10
|
+
|
|
11
|
+
export type { ExtractedStorageBucketAst as SchemaStorageBucketAst } from "./schema-ast-v2.js"
|
|
12
|
+
|
|
9
13
|
export interface BucketSpec {
|
|
10
14
|
id: string
|
|
11
15
|
public: boolean
|
|
@@ -15,6 +19,29 @@ export interface BucketSpec {
|
|
|
15
19
|
s3_bucket_policy?: string | null
|
|
16
20
|
}
|
|
17
21
|
|
|
22
|
+
export function bucketSpecsFromAst(ast: Pick<ExtractedSchemaAstV2, "storageBuckets">): BucketSpec[] {
|
|
23
|
+
return (ast.storageBuckets ?? []).map((b) => ({
|
|
24
|
+
id: b.id,
|
|
25
|
+
public: b.public,
|
|
26
|
+
...(b.accessMode !== undefined && { access_mode: b.accessMode }),
|
|
27
|
+
...(b.allowedMimeTypes != null && { allowed_mime_types: b.allowedMimeTypes }),
|
|
28
|
+
...(b.fileSizeLimit != null && { file_size_limit: b.fileSizeLimit }),
|
|
29
|
+
...(b.s3BucketPolicy != null &&
|
|
30
|
+
b.s3BucketPolicy !== "" && { s3_bucket_policy: b.s3BucketPolicy }),
|
|
31
|
+
}))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function provisionBucketsFromAst(
|
|
35
|
+
ast: Pick<ExtractedSchemaAstV2, "storageBuckets">,
|
|
36
|
+
storageApiUrl: string,
|
|
37
|
+
serviceRoleKey: string,
|
|
38
|
+
): Promise<void> {
|
|
39
|
+
const buckets = bucketSpecsFromAst(ast)
|
|
40
|
+
if (buckets.length === 0) return
|
|
41
|
+
console.log("[supatype] Provisioning storage buckets...")
|
|
42
|
+
await provisionBuckets(storageApiUrl, serviceRoleKey, buckets)
|
|
43
|
+
}
|
|
44
|
+
|
|
18
45
|
/**
|
|
19
46
|
* Ensure all declared buckets exist in the storage server.
|
|
20
47
|
*
|
|
@@ -48,11 +75,16 @@ export async function provisionBuckets(
|
|
|
48
75
|
const res = await fetch(`${base}/bucket`, { method: "POST", headers, body })
|
|
49
76
|
.catch(() => null)
|
|
50
77
|
|
|
51
|
-
if (res === null)
|
|
78
|
+
if (res === null) {
|
|
79
|
+
console.warn(`[storage] Storage API unreachable — skipped bucket "${bucket.id}"`)
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
52
82
|
if (res.status === 409) continue // already exists — fine
|
|
53
83
|
if (!res.ok) {
|
|
54
84
|
const msg = await res.text().catch(() => res.statusText)
|
|
55
85
|
console.warn(`[storage] Failed to provision bucket "${bucket.id}": ${msg}`)
|
|
86
|
+
continue
|
|
56
87
|
}
|
|
88
|
+
console.log(`[supatype] Storage bucket "${bucket.id}" ready.`)
|
|
57
89
|
}
|
|
58
90
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface TargetFetchOptions {
|
|
2
|
+
method: string
|
|
3
|
+
path: string
|
|
4
|
+
body?: unknown
|
|
5
|
+
token: string
|
|
6
|
+
orgId?: string | undefined
|
|
7
|
+
environment?: string | undefined
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function targetFetch<T>(
|
|
11
|
+
baseUrl: string,
|
|
12
|
+
apiPrefix: "/api/v1" | "/platform/v1",
|
|
13
|
+
opts: TargetFetchOptions,
|
|
14
|
+
): Promise<T> {
|
|
15
|
+
const headers: Record<string, string> = {
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
Authorization: `Bearer ${opts.token}`,
|
|
18
|
+
}
|
|
19
|
+
if (opts.orgId) headers["X-Org-Id"] = opts.orgId
|
|
20
|
+
if (opts.environment) headers["X-Supatype-Environment"] = opts.environment
|
|
21
|
+
|
|
22
|
+
const url = `${baseUrl.replace(/\/$/, "")}${apiPrefix}${opts.path}`
|
|
23
|
+
const res = await fetch(url, {
|
|
24
|
+
method: opts.method,
|
|
25
|
+
headers,
|
|
26
|
+
...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const json = (await res.json().catch(() => ({}))) as {
|
|
30
|
+
data?: T
|
|
31
|
+
error?: string
|
|
32
|
+
message?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
throw new Error(json.message ?? json.error ?? `API error: ${res.status} ${url}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (json.data !== undefined ? json.data : json) as T
|
|
40
|
+
}
|
package/src/type-extractor.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs"
|
|
2
|
+
import { createHash } from "node:crypto"
|
|
3
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path"
|
|
3
4
|
import ts from "typescript"
|
|
4
5
|
import {
|
|
5
6
|
applyImportRename,
|
|
@@ -103,7 +104,7 @@ export function extractSchemaAstFromTypes(
|
|
|
103
104
|
)
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
const { tableName, access, options } = parseModelMeta(
|
|
107
|
+
const { tableName, access, options, indexes } = parseModelMeta(
|
|
107
108
|
metaArg,
|
|
108
109
|
sourceFile,
|
|
109
110
|
stmt.name.text,
|
|
@@ -112,7 +113,7 @@ export function extractSchemaAstFromTypes(
|
|
|
112
113
|
)
|
|
113
114
|
|
|
114
115
|
models.push(
|
|
115
|
-
emitModel(stmt.name.text, fields, options, tableName, access),
|
|
116
|
+
emitModel(stmt.name.text, fields, options, tableName, access, indexes),
|
|
116
117
|
)
|
|
117
118
|
}
|
|
118
119
|
}
|
|
@@ -143,9 +144,51 @@ export function extractSchemaAstFromTypes(
|
|
|
143
144
|
})
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
|
|
147
|
+
export interface SchemaSourceFile {
|
|
148
|
+
relativePath: string
|
|
149
|
+
absolutePath: string
|
|
150
|
+
sha256: string
|
|
151
|
+
bytes: number
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface SchemaSourceGraph {
|
|
155
|
+
entryPoint: string
|
|
156
|
+
files: SchemaSourceFile[]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function collectSchemaSourcePaths(entryAbsPath: string, projectRoot: string): SchemaSourceGraph {
|
|
160
|
+
const root = resolve(projectRoot)
|
|
161
|
+
const entryReal = realpathSync(entryAbsPath)
|
|
162
|
+
const entryPoint = relative(root, entryReal).replace(/\\/g, "/")
|
|
163
|
+
if (entryPoint.startsWith("..")) {
|
|
164
|
+
throw new Error(`Schema entry must be under project root: ${entryAbsPath}`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const absolutePaths = walkSchemaSourceAbsPaths(entryReal)
|
|
168
|
+
const files: SchemaSourceFile[] = []
|
|
169
|
+
|
|
170
|
+
for (const abs of absolutePaths) {
|
|
171
|
+
const real = realpathSync(abs)
|
|
172
|
+
const rel = relative(root, real).replace(/\\/g, "/")
|
|
173
|
+
if (rel.startsWith("..")) {
|
|
174
|
+
throw new Error(`Schema source escapes project root: ${abs}`)
|
|
175
|
+
}
|
|
176
|
+
const content = readFileSync(real)
|
|
177
|
+
files.push({
|
|
178
|
+
relativePath: rel,
|
|
179
|
+
absolutePath: real,
|
|
180
|
+
sha256: createHash("sha256").update(content).digest("hex"),
|
|
181
|
+
bytes: content.length,
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
files.sort((a, b) => a.relativePath.localeCompare(b.relativePath))
|
|
186
|
+
return { entryPoint, files }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function walkSchemaSourceAbsPaths(entryPath: string): string[] {
|
|
147
190
|
const visited = new Set<string>()
|
|
148
|
-
const
|
|
191
|
+
const paths: string[] = []
|
|
149
192
|
const queue: string[] = [entryPath]
|
|
150
193
|
|
|
151
194
|
while (queue.length > 0) {
|
|
@@ -153,12 +196,11 @@ function loadSchemaSourceFiles(entryPath: string): ts.SourceFile[] {
|
|
|
153
196
|
if (!currentPath) continue
|
|
154
197
|
if (visited.has(currentPath)) continue
|
|
155
198
|
visited.add(currentPath)
|
|
156
|
-
|
|
157
199
|
if (!existsSync(currentPath)) continue
|
|
200
|
+
paths.push(currentPath)
|
|
201
|
+
|
|
158
202
|
const sourceText = readFileSync(currentPath, "utf8")
|
|
159
203
|
const sourceFile = ts.createSourceFile(currentPath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
|
|
160
|
-
sourceFiles.push(sourceFile)
|
|
161
|
-
|
|
162
204
|
const baseDir = dirname(currentPath)
|
|
163
205
|
for (const stmt of sourceFile.statements) {
|
|
164
206
|
let specifier: string | undefined
|
|
@@ -178,7 +220,14 @@ function loadSchemaSourceFiles(entryPath: string): ts.SourceFile[] {
|
|
|
178
220
|
}
|
|
179
221
|
}
|
|
180
222
|
|
|
181
|
-
return
|
|
223
|
+
return paths
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function loadSchemaSourceFiles(entryPath: string): ts.SourceFile[] {
|
|
227
|
+
return walkSchemaSourceAbsPaths(entryPath).map((abs) => {
|
|
228
|
+
const sourceText = readFileSync(abs, "utf8")
|
|
229
|
+
return ts.createSourceFile(abs, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
|
|
230
|
+
})
|
|
182
231
|
}
|
|
183
232
|
|
|
184
233
|
function resolveTypeModulePath(fromDir: string, specifier: string): string | null {
|
|
@@ -1372,7 +1421,12 @@ function parseModelMeta(
|
|
|
1372
1421
|
modelName: string,
|
|
1373
1422
|
fieldsArg: ts.TypeNode,
|
|
1374
1423
|
fields: Record<string, FieldAstV2>,
|
|
1375
|
-
): {
|
|
1424
|
+
): {
|
|
1425
|
+
tableName: string
|
|
1426
|
+
access: Record<string, unknown>
|
|
1427
|
+
options: Record<string, unknown>
|
|
1428
|
+
indexes: unknown[]
|
|
1429
|
+
} {
|
|
1376
1430
|
const literal = parseMetaLiteral(metaArg, sourceFile)
|
|
1377
1431
|
const singleton = literal.singleton === true
|
|
1378
1432
|
const tableName =
|
|
@@ -1397,7 +1451,66 @@ function parseModelMeta(
|
|
|
1397
1451
|
tableName,
|
|
1398
1452
|
access: parseModelAccess(metaArg, sourceFile),
|
|
1399
1453
|
options,
|
|
1454
|
+
indexes: parseModelIndexes(metaArg, sourceFile, fields),
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function parseModelIndexes(
|
|
1459
|
+
metaArg: ts.TypeNode | undefined,
|
|
1460
|
+
sourceFile: ts.SourceFile,
|
|
1461
|
+
fields: Record<string, FieldAstV2>,
|
|
1462
|
+
): unknown[] {
|
|
1463
|
+
if (!metaArg || !ts.isTypeLiteralNode(metaArg)) return []
|
|
1464
|
+
|
|
1465
|
+
const indexesProp = metaArg.members.find(
|
|
1466
|
+
(member) => ts.isPropertySignature(member) && getPropertyName(member.name) === "indexes",
|
|
1467
|
+
)
|
|
1468
|
+
if (
|
|
1469
|
+
!indexesProp ||
|
|
1470
|
+
!ts.isPropertySignature(indexesProp) ||
|
|
1471
|
+
!indexesProp.type ||
|
|
1472
|
+
!ts.isTupleTypeNode(indexesProp.type)
|
|
1473
|
+
) {
|
|
1474
|
+
return []
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
const indexes: unknown[] = []
|
|
1478
|
+
for (const element of indexesProp.type.elements) {
|
|
1479
|
+
if (!ts.isTypeLiteralNode(element)) continue
|
|
1480
|
+
const indexDef: Record<string, unknown> = { using: "btree" }
|
|
1481
|
+
for (const member of element.members) {
|
|
1482
|
+
if (!ts.isPropertySignature(member) || !member.type) continue
|
|
1483
|
+
const key = getPropertyName(member.name)
|
|
1484
|
+
if (!key) continue
|
|
1485
|
+
if (key === "name" && ts.isLiteralTypeNode(member.type) && ts.isStringLiteral(member.type.literal)) {
|
|
1486
|
+
indexDef.name = member.type.literal.text
|
|
1487
|
+
} else if (key === "unique" && isBooleanLiteralType(member.type, true)) {
|
|
1488
|
+
indexDef.unique = true
|
|
1489
|
+
} else if (key === "fields" && ts.isTupleTypeNode(member.type)) {
|
|
1490
|
+
indexDef.fields = member.type.elements
|
|
1491
|
+
.map((fieldNode) => {
|
|
1492
|
+
if (!ts.isLiteralTypeNode(fieldNode) || !ts.isStringLiteral(fieldNode.literal)) return null
|
|
1493
|
+
return resolveIndexFieldName(fieldNode.literal.text, fields)
|
|
1494
|
+
})
|
|
1495
|
+
.filter((field): field is string => field !== null)
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
if (Array.isArray(indexDef.fields) && indexDef.fields.length > 0) {
|
|
1499
|
+
indexes.push(indexDef)
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
return indexes
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
function resolveIndexFieldName(fieldName: string, fields: Record<string, FieldAstV2>): string | null {
|
|
1506
|
+
if (fields[fieldName] !== undefined) {
|
|
1507
|
+
const field = fields[fieldName]
|
|
1508
|
+
if (field.kind === "relation" && field.annotations?.db?.foreignKey) {
|
|
1509
|
+
return field.annotations.db.foreignKey as string
|
|
1510
|
+
}
|
|
1511
|
+
return fieldName
|
|
1400
1512
|
}
|
|
1513
|
+
return fieldName
|
|
1401
1514
|
}
|
|
1402
1515
|
|
|
1403
1516
|
function parseModelAccess(metaArg: ts.TypeNode | undefined, sourceFile: ts.SourceFile): Record<string, unknown> {
|