@supatype/cli 0.1.0-alpha.6 → 0.1.0-alpha.8
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 +208 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/dist/app/proxy-dev-app.d.ts +13 -0
- package/dist/app/proxy-dev-app.d.ts.map +1 -0
- package/dist/app/proxy-dev-app.js +53 -0
- package/dist/app/proxy-dev-app.js.map +1 -0
- package/dist/app-config.d.ts +7 -0
- package/dist/app-config.d.ts.map +1 -0
- package/dist/app-config.js +113 -0
- package/dist/app-config.js.map +1 -0
- package/dist/augmentation-generator.d.ts +2 -0
- package/dist/augmentation-generator.d.ts.map +1 -0
- package/dist/augmentation-generator.js +111 -0
- package/dist/augmentation-generator.js.map +1 -0
- package/dist/binary-cache.d.ts +94 -0
- package/dist/binary-cache.d.ts.map +1 -0
- package/dist/binary-cache.js +669 -0
- package/dist/binary-cache.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +13 -7
- package/dist/cli.js.map +1 -1
- package/dist/commands/admin.d.ts.map +1 -1
- package/dist/commands/admin.js +4 -3
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/app.d.ts.map +1 -1
- package/dist/commands/app.js +56 -209
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/cache.d.ts +6 -0
- package/dist/commands/cache.d.ts.map +1 -0
- package/dist/commands/cache.js +105 -0
- package/dist/commands/cache.js.map +1 -0
- package/dist/commands/cloud.d.ts +20 -0
- package/dist/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +50 -52
- package/dist/commands/cloud.js.map +1 -1
- package/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +47 -54
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.d.ts +2 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +79 -52
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +11 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +759 -385
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +30 -15
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/engine.d.ts +1 -3
- package/dist/commands/engine.d.ts.map +1 -1
- package/dist/commands/engine.js +13 -85
- package/dist/commands/engine.js.map +1 -1
- package/dist/commands/functions.d.ts.map +1 -1
- package/dist/commands/functions.js +92 -105
- package/dist/commands/functions.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +22 -12
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +137 -410
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/migrate-from-v1.d.ts +5 -0
- package/dist/commands/migrate-from-v1.d.ts.map +1 -0
- package/dist/commands/migrate-from-v1.js +125 -0
- package/dist/commands/migrate-from-v1.js.map +1 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +27 -23
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/pg.d.ts +8 -0
- package/dist/commands/pg.d.ts.map +1 -0
- package/dist/commands/pg.js +102 -0
- package/dist/commands/pg.js.map +1 -0
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +5 -66
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +128 -38
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/seed.d.ts +2 -0
- package/dist/commands/seed.d.ts.map +1 -1
- package/dist/commands/seed.js +44 -11
- package/dist/commands/seed.js.map +1 -1
- package/dist/commands/self-host.d.ts +7 -1
- package/dist/commands/self-host.d.ts.map +1 -1
- package/dist/commands/self-host.js +272 -758
- package/dist/commands/self-host.js.map +1 -1
- package/dist/commands/self-update.d.ts +9 -0
- package/dist/commands/self-update.d.ts.map +1 -0
- package/dist/commands/self-update.js +33 -0
- package/dist/commands/self-update.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +4 -3
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/types.d.ts +3 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +62 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +93 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/components.d.ts +5 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +3 -0
- package/dist/components.js.map +1 -0
- package/dist/config.d.ts +10 -51
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +101 -33
- package/dist/config.js.map +1 -1
- package/dist/dev-compose.d.ts +17 -0
- package/dist/dev-compose.d.ts.map +1 -0
- package/dist/dev-compose.js +374 -0
- package/dist/dev-compose.js.map +1 -0
- package/dist/diff-output.d.ts +4 -0
- package/dist/diff-output.d.ts.map +1 -0
- package/dist/diff-output.js +12 -0
- package/dist/diff-output.js.map +1 -0
- package/dist/docker-postgres.d.ts +57 -0
- package/dist/docker-postgres.d.ts.map +1 -0
- package/dist/docker-postgres.js +208 -0
- package/dist/docker-postgres.js.map +1 -0
- package/dist/engine-client.d.ts +69 -0
- package/dist/engine-client.d.ts.map +1 -0
- package/dist/engine-client.js +157 -0
- package/dist/engine-client.js.map +1 -0
- package/dist/ensure-binary.d.ts +7 -0
- package/dist/ensure-binary.d.ts.map +1 -0
- package/dist/ensure-binary.js +17 -0
- package/dist/ensure-binary.js.map +1 -0
- package/dist/functions-router-gen.d.ts +14 -0
- package/dist/functions-router-gen.d.ts.map +1 -0
- package/dist/functions-router-gen.js +199 -0
- package/dist/functions-router-gen.js.map +1 -0
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/kong-config.d.ts +25 -0
- package/dist/kong-config.d.ts.map +1 -0
- package/dist/kong-config.js +71 -0
- package/dist/kong-config.js.map +1 -0
- package/dist/local-gateway.d.ts +7 -0
- package/dist/local-gateway.d.ts.map +1 -0
- package/dist/local-gateway.js +9 -0
- package/dist/local-gateway.js.map +1 -0
- package/dist/local-storage.d.ts +8 -0
- package/dist/local-storage.d.ts.map +1 -0
- package/dist/local-storage.js +14 -0
- package/dist/local-storage.js.map +1 -0
- package/dist/pgbouncer-userlist.d.ts +5 -0
- package/dist/pgbouncer-userlist.d.ts.map +1 -0
- package/dist/pgbouncer-userlist.js +14 -0
- package/dist/pgbouncer-userlist.js.map +1 -0
- package/dist/postgres-ctl.d.ts +44 -0
- package/dist/postgres-ctl.d.ts.map +1 -0
- package/dist/postgres-ctl.js +137 -0
- package/dist/postgres-ctl.js.map +1 -0
- package/dist/process-manager.d.ts +43 -0
- package/dist/process-manager.d.ts.map +1 -0
- package/dist/process-manager.js +135 -0
- package/dist/process-manager.js.map +1 -0
- package/dist/project-config.d.ts +235 -0
- package/dist/project-config.d.ts.map +1 -0
- package/dist/project-config.js +160 -0
- package/dist/project-config.js.map +1 -0
- package/dist/pull-utils.d.ts +15 -0
- package/dist/pull-utils.d.ts.map +1 -1
- package/dist/pull-utils.js +12 -0
- package/dist/pull-utils.js.map +1 -1
- package/dist/release-pins.d.ts +7 -0
- package/dist/release-pins.d.ts.map +1 -0
- package/dist/release-pins.js +27 -0
- package/dist/release-pins.js.map +1 -0
- package/dist/release-public-key.d.ts +8 -0
- package/dist/release-public-key.d.ts.map +1 -0
- package/dist/release-public-key.js +13 -0
- package/dist/release-public-key.js.map +1 -0
- package/dist/runtime-routes.d.ts +34 -0
- package/dist/runtime-routes.d.ts.map +1 -0
- package/dist/runtime-routes.js +252 -0
- package/dist/runtime-routes.js.map +1 -0
- package/dist/schema-ast-v2.d.ts +127 -0
- package/dist/schema-ast-v2.d.ts.map +1 -0
- package/dist/schema-ast-v2.js +226 -0
- package/dist/schema-ast-v2.js.map +1 -0
- package/dist/scripts/postinstall.d.ts +5 -6
- package/dist/scripts/postinstall.d.ts.map +1 -1
- package/dist/scripts/postinstall.js +36 -20
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/self-host-compose.d.ts +22 -0
- package/dist/self-host-compose.d.ts.map +1 -0
- package/dist/self-host-compose.js +347 -0
- package/dist/self-host-compose.js.map +1 -0
- package/dist/storage-provision.d.ts +24 -0
- package/dist/storage-provision.d.ts.map +1 -0
- package/dist/storage-provision.js +44 -0
- package/dist/storage-provision.js.map +1 -0
- package/dist/studio-admin-roles.d.ts +7 -0
- package/dist/studio-admin-roles.d.ts.map +1 -0
- package/dist/studio-admin-roles.js +14 -0
- package/dist/studio-admin-roles.js.map +1 -0
- package/dist/studio-dev-server.d.ts +22 -0
- package/dist/studio-dev-server.d.ts.map +1 -0
- package/dist/studio-dev-server.js +28 -0
- package/dist/studio-dev-server.js.map +1 -0
- package/dist/systemd.d.ts +26 -0
- package/dist/systemd.d.ts.map +1 -0
- package/dist/systemd.js +102 -0
- package/dist/systemd.js.map +1 -0
- package/dist/tsx-runner.d.ts.map +1 -1
- package/dist/tsx-runner.js +9 -2
- package/dist/tsx-runner.js.map +1 -1
- package/dist/type-extractor.d.ts +4 -0
- package/dist/type-extractor.d.ts.map +1 -0
- package/dist/type-extractor.js +1213 -0
- package/dist/type-extractor.js.map +1 -0
- package/dist/type-resolver.d.ts +33 -0
- package/dist/type-resolver.d.ts.map +1 -0
- package/dist/type-resolver.js +338 -0
- package/dist/type-resolver.js.map +1 -0
- package/package.json +4 -3
- package/releases/deno/VERSION +1 -0
- package/scripts/mirror-deno-release.sh +76 -0
- package/src/TYPE-RESOLUTION.md +294 -0
- package/src/app/proxy-dev-app.ts +67 -0
- package/src/app-config.ts +128 -0
- package/src/augmentation-generator.ts +126 -0
- package/src/binary-cache.ts +822 -0
- package/src/cli.ts +13 -8
- package/src/commands/admin.ts +4 -3
- package/src/commands/app.ts +67 -231
- package/src/commands/cache.ts +117 -0
- package/src/commands/cloud.ts +63 -64
- package/src/commands/db.ts +54 -63
- package/src/commands/deploy.ts +96 -62
- package/src/commands/dev.ts +933 -405
- package/src/commands/diff.ts +31 -29
- package/src/commands/engine.ts +13 -116
- package/src/commands/functions.ts +97 -115
- package/src/commands/generate.ts +23 -10
- package/src/commands/init.ts +149 -414
- package/src/commands/migrate-from-v1.ts +131 -0
- package/src/commands/migrate.ts +27 -23
- package/src/commands/pg.ts +133 -0
- package/src/commands/pull.ts +6 -85
- package/src/commands/push.ts +161 -56
- package/src/commands/seed.ts +54 -12
- package/src/commands/self-host.ts +312 -880
- package/src/commands/self-update.ts +45 -0
- package/src/commands/status.ts +4 -3
- package/src/commands/types.ts +76 -0
- package/src/commands/update.ts +109 -0
- package/src/components.ts +6 -0
- package/src/config.ts +127 -94
- package/src/dev-compose.ts +455 -0
- package/src/diff-output.ts +12 -0
- package/src/docker-postgres.ts +295 -0
- package/src/engine-client.ts +236 -0
- package/src/ensure-binary.ts +28 -0
- package/src/functions-router-gen.ts +224 -0
- package/src/index.ts +4 -12
- package/src/kong-config.ts +93 -0
- package/src/local-gateway.ts +9 -0
- package/src/local-storage.ts +14 -0
- package/src/pgbouncer-userlist.ts +15 -0
- package/src/postgres-ctl.ts +171 -0
- package/src/process-manager.ts +168 -0
- package/src/project-config.ts +386 -0
- package/src/pull-utils.ts +24 -0
- package/src/release-pins.ts +31 -0
- package/src/release-public-key.ts +12 -0
- package/src/runtime-routes.ts +291 -0
- package/src/schema-ast-v2.ts +324 -0
- package/src/scripts/postinstall.ts +36 -25
- package/src/self-host-compose.ts +389 -0
- package/src/storage-provision.ts +58 -0
- package/src/studio-admin-roles.ts +16 -0
- package/src/studio-dev-server.ts +53 -0
- package/src/systemd.ts +137 -0
- package/src/tsx-runner.ts +11 -1
- package/src/type-extractor.ts +1479 -0
- package/src/type-resolver.ts +457 -0
- package/tests/app-command.test.ts +54 -0
- package/tests/augmentation-generator.test.ts +59 -0
- package/tests/binary-cache-cloud-overrides.test.ts +123 -0
- package/tests/cached-artifact-format.test.ts +84 -0
- package/tests/cli-help.test.ts +40 -14
- package/tests/config.test.ts +171 -37
- package/tests/docker-postgres.test.ts +39 -0
- package/tests/engine-distribution.test.ts +3 -3
- package/tests/ensure-binary.test.ts +59 -0
- package/tests/init.test.ts +28 -86
- package/tests/migrate-from-v1.test.ts +29 -0
- package/tests/normalize-admin-config.test.ts +48 -0
- package/tests/pg-spawn-env.test.ts +18 -0
- package/tests/postgres-archive-tag.test.ts +9 -0
- package/tests/proxy-dev-app.test.ts +33 -0
- package/tests/pull-utils.test.ts +36 -1
- package/tests/release-pins.test.ts +28 -0
- package/tests/runtime-contract.test.ts +351 -0
- package/tests/seed-discover.test.ts +31 -0
- package/tests/studio-admin-roles.test.ts +27 -0
- package/tests/tsconfig.json +9 -0
- package/tests/type-extractor.test.ts +985 -0
- package/tests/type-resolver.test.ts +59 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +12 -0
- package/dist/engine/cache.d.ts +0 -37
- package/dist/engine/cache.d.ts.map +0 -1
- package/dist/engine/cache.js +0 -121
- package/dist/engine/cache.js.map +0 -1
- package/dist/engine/download.d.ts +0 -19
- package/dist/engine/download.d.ts.map +0 -1
- package/dist/engine/download.js +0 -108
- package/dist/engine/download.js.map +0 -1
- package/dist/engine/platform.d.ts +0 -24
- package/dist/engine/platform.d.ts.map +0 -1
- package/dist/engine/platform.js +0 -50
- package/dist/engine/platform.js.map +0 -1
- package/dist/engine/resolve.d.ts +0 -37
- package/dist/engine/resolve.d.ts.map +0 -1
- package/dist/engine/resolve.js +0 -133
- package/dist/engine/resolve.js.map +0 -1
- package/dist/engine/update-notify.d.ts +0 -11
- package/dist/engine/update-notify.d.ts.map +0 -1
- package/dist/engine/update-notify.js +0 -43
- package/dist/engine/update-notify.js.map +0 -1
- package/dist/engine/verify.d.ts +0 -50
- package/dist/engine/verify.d.ts.map +0 -1
- package/dist/engine/verify.js +0 -161
- package/dist/engine/verify.js.map +0 -1
- package/dist/engine-version.d.ts +0 -35
- package/dist/engine-version.d.ts.map +0 -1
- package/dist/engine-version.js +0 -35
- package/dist/engine-version.js.map +0 -1
- package/dist/engine.d.ts +0 -34
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js +0 -76
- package/dist/engine.js.map +0 -1
- package/src/engine/cache.ts +0 -135
- package/src/engine/download.ts +0 -143
- package/src/engine/platform.ts +0 -66
- package/src/engine/resolve.ts +0 -197
- package/src/engine/update-notify.ts +0 -50
- package/src/engine/verify.ts +0 -206
- package/src/engine-version.ts +0 -39
- package/src/engine.ts +0 -99
|
@@ -0,0 +1,985 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs"
|
|
2
|
+
import { tmpdir } from "node:os"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest"
|
|
5
|
+
import type { ModelAstV2 } from "../src/schema-ast-v2.js"
|
|
6
|
+
import { extractSchemaAstFromTypes } from "../src/type-extractor.js"
|
|
7
|
+
|
|
8
|
+
const dirs: string[] = []
|
|
9
|
+
|
|
10
|
+
function tableName(model: ModelAstV2 | undefined): string | undefined {
|
|
11
|
+
return model?.annotations.db.tableName
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function modelAccess(model: ModelAstV2 | undefined): Record<string, unknown> {
|
|
15
|
+
return model?.annotations.platform.access ?? {}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true })
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe("extractSchemaAstFromTypes", () => {
|
|
23
|
+
it("extracts exported Model aliases into engine-compatible AST", () => {
|
|
24
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-"))
|
|
25
|
+
dirs.push(dir)
|
|
26
|
+
const schemaPath = join(dir, "schema.ts")
|
|
27
|
+
writeFileSync(
|
|
28
|
+
schemaPath,
|
|
29
|
+
`
|
|
30
|
+
import type { Model, UUID, Slug, Unique, RichText, Optional, Public, Owner, RelatedTo } from "@supatype/types"
|
|
31
|
+
|
|
32
|
+
export type Post = Model<{
|
|
33
|
+
id: UUID
|
|
34
|
+
slug: Unique<Slug>
|
|
35
|
+
title: string
|
|
36
|
+
body: RichText
|
|
37
|
+
publishedAt: Optional<Date>
|
|
38
|
+
}, {
|
|
39
|
+
access: { read: Public; update: Owner<"author_id">; delete: Owner<"author_id"> }
|
|
40
|
+
}>
|
|
41
|
+
|
|
42
|
+
export type Comment = Model<{
|
|
43
|
+
id: UUID
|
|
44
|
+
post: RelatedTo<Post>
|
|
45
|
+
}, {
|
|
46
|
+
access: { read: Public }
|
|
47
|
+
}>
|
|
48
|
+
`,
|
|
49
|
+
"utf8",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
53
|
+
expect(ast).not.toBeNull()
|
|
54
|
+
expect(ast?.astVersion).toBe(2)
|
|
55
|
+
expect(ast?.models).toHaveLength(2)
|
|
56
|
+
const post = ast?.models.find((m) => m.name === "Post")
|
|
57
|
+
const comment = ast?.models.find((m) => m.name === "Comment")
|
|
58
|
+
expect(tableName(post)).toBe("post")
|
|
59
|
+
expect(comment?.fields["post"]).toMatchObject({
|
|
60
|
+
kind: "relation",
|
|
61
|
+
cardinality: "belongsTo",
|
|
62
|
+
target: "Post",
|
|
63
|
+
annotations: { db: { foreignKey: "post_id" } },
|
|
64
|
+
})
|
|
65
|
+
expect(post?.fields["id"]).toMatchObject({
|
|
66
|
+
kind: "uuid",
|
|
67
|
+
annotations: { db: { pgType: "UUID", unique: true } },
|
|
68
|
+
primaryKey: true,
|
|
69
|
+
required: true,
|
|
70
|
+
default: { kind: "genRandomUuid" },
|
|
71
|
+
})
|
|
72
|
+
expect(post?.fields["slug"]).toMatchObject({
|
|
73
|
+
kind: "slug",
|
|
74
|
+
from: "title",
|
|
75
|
+
annotations: { db: { unique: true } },
|
|
76
|
+
})
|
|
77
|
+
expect(modelAccess(post)["read"]).toEqual({ type: "public" })
|
|
78
|
+
expect(modelAccess(post)["update"]).toEqual({ type: "owner", field: "author_id" })
|
|
79
|
+
expect(modelAccess(post)["delete"]).toEqual({ type: "owner", field: "author_id" })
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it("emits DEFAULT now for created_at / updated_at timestamp columns", () => {
|
|
83
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-audit-ts-"))
|
|
84
|
+
dirs.push(dir)
|
|
85
|
+
const schemaPath = join(dir, "schema.ts")
|
|
86
|
+
writeFileSync(
|
|
87
|
+
schemaPath,
|
|
88
|
+
`
|
|
89
|
+
import type { Model, UUID, Timestamp } from "@supatype/types"
|
|
90
|
+
|
|
91
|
+
export type Entry = Model<{ id: UUID; created_at: Timestamp; updated_at: Timestamp }>
|
|
92
|
+
`,
|
|
93
|
+
"utf8",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
97
|
+
const entry = ast?.models.find((m) => m.name === "Entry")
|
|
98
|
+
expect(entry?.fields["created_at"]).toMatchObject({
|
|
99
|
+
kind: "datetime",
|
|
100
|
+
annotations: { db: { serverGenerated: true, pgType: "TIMESTAMP WITH TIME ZONE" } },
|
|
101
|
+
default: { kind: "now" },
|
|
102
|
+
})
|
|
103
|
+
expect(entry?.fields["updated_at"]).toMatchObject({
|
|
104
|
+
kind: "datetime",
|
|
105
|
+
annotations: { db: { serverGenerated: true, pgType: "TIMESTAMP WITH TIME ZONE" } },
|
|
106
|
+
default: { kind: "now" },
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it("extracts Owner<Model, Key> using the key argument", () => {
|
|
111
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-owner-model-"))
|
|
112
|
+
dirs.push(dir)
|
|
113
|
+
const schemaPath = join(dir, "schema.ts")
|
|
114
|
+
writeFileSync(
|
|
115
|
+
schemaPath,
|
|
116
|
+
`
|
|
117
|
+
import type { Model, UUID, Public, Owner } from "@supatype/types"
|
|
118
|
+
|
|
119
|
+
export type User = Model<{
|
|
120
|
+
id: UUID
|
|
121
|
+
}, {
|
|
122
|
+
access: { read: Public; update: Owner<User, "id">; delete: Owner<User, "id"> }
|
|
123
|
+
}>
|
|
124
|
+
`,
|
|
125
|
+
"utf8",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
129
|
+
const user = ast?.models.find((m) => m.name === "User")
|
|
130
|
+
expect(modelAccess(user)["update"]).toEqual({ type: "owner", field: "id" })
|
|
131
|
+
expect(modelAccess(user)["delete"]).toEqual({ type: "owner", field: "id" })
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it("maps SupatypeAuthUser relations and OwnerFrom relation keys", () => {
|
|
135
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-auth-owner-"))
|
|
136
|
+
dirs.push(dir)
|
|
137
|
+
const schemaPath = join(dir, "schema.ts")
|
|
138
|
+
writeFileSync(
|
|
139
|
+
schemaPath,
|
|
140
|
+
`
|
|
141
|
+
import type { Model, UUID, RelatedTo, SupatypeAuthUser, OwnerFrom, LoggedIn } from "@supatype/types"
|
|
142
|
+
|
|
143
|
+
export type Post = Model<{
|
|
144
|
+
id: UUID
|
|
145
|
+
authUser: RelatedTo<SupatypeAuthUser>
|
|
146
|
+
}, {
|
|
147
|
+
access: { create: LoggedIn; update: OwnerFrom<"authUser">; delete: OwnerFrom<"authUser"> }
|
|
148
|
+
}>
|
|
149
|
+
`,
|
|
150
|
+
"utf8",
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
154
|
+
const post = ast?.models.find((m) => m.name === "Post")
|
|
155
|
+
expect(post?.fields["authUser"]).toMatchObject({
|
|
156
|
+
kind: "relation",
|
|
157
|
+
cardinality: "belongsTo",
|
|
158
|
+
target: "supatype:user",
|
|
159
|
+
annotations: { db: { foreignKey: "auth_user_id" } },
|
|
160
|
+
})
|
|
161
|
+
expect(modelAccess(post)["update"]).toEqual({ type: "owner", field: "authUser" })
|
|
162
|
+
expect(modelAccess(post)["delete"]).toEqual({ type: "owner", field: "authUser" })
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it("unwraps Default<> so boolean fields stay boolean in the AST", () => {
|
|
166
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-"))
|
|
167
|
+
dirs.push(dir)
|
|
168
|
+
const schemaPath = join(dir, "schema.ts")
|
|
169
|
+
writeFileSync(
|
|
170
|
+
schemaPath,
|
|
171
|
+
`
|
|
172
|
+
import type { Model, UUID, Default } from "@supatype/types"
|
|
173
|
+
|
|
174
|
+
export type Flags = Model<{
|
|
175
|
+
id: UUID
|
|
176
|
+
isActive: Default<boolean, true>
|
|
177
|
+
}>
|
|
178
|
+
`,
|
|
179
|
+
"utf8",
|
|
180
|
+
)
|
|
181
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
182
|
+
expect(ast?.models[0]?.fields["isActive"]).toMatchObject({
|
|
183
|
+
kind: "boolean",
|
|
184
|
+
annotations: { db: { pgType: "BOOLEAN" } },
|
|
185
|
+
default: { kind: "value", value: true },
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it("extracts Default<> literal values for scalars and RichText plain-string defaults", () => {
|
|
190
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-defaults-"))
|
|
191
|
+
dirs.push(dir)
|
|
192
|
+
const schemaPath = join(dir, "schema.ts")
|
|
193
|
+
writeFileSync(
|
|
194
|
+
schemaPath,
|
|
195
|
+
`
|
|
196
|
+
import type { Model, UUID, Default, Int, RichText } from "@supatype/types"
|
|
197
|
+
|
|
198
|
+
export type Product = Model<{
|
|
199
|
+
id: UUID
|
|
200
|
+
stock: Default<Int, 0>
|
|
201
|
+
blurb: Default<RichText, "Welcome to our shop.">
|
|
202
|
+
}>
|
|
203
|
+
`,
|
|
204
|
+
"utf8",
|
|
205
|
+
)
|
|
206
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
207
|
+
expect(ast?.models[0]?.fields["stock"]).toMatchObject({
|
|
208
|
+
kind: "integer",
|
|
209
|
+
default: { kind: "value", value: 0 },
|
|
210
|
+
})
|
|
211
|
+
expect(ast?.models[0]?.fields["blurb"]).toMatchObject({
|
|
212
|
+
kind: "richText",
|
|
213
|
+
annotations: { db: { pgType: "JSONB" }, platform: { editor: "rich" } },
|
|
214
|
+
default: { kind: "value", value: "Welcome to our shop." },
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it("extracts RichText<\"…\"> inline default (equivalent to Default<RichText, \"…\">)", () => {
|
|
219
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-richtext-inline-"))
|
|
220
|
+
dirs.push(dir)
|
|
221
|
+
const schemaPath = join(dir, "schema.ts")
|
|
222
|
+
writeFileSync(
|
|
223
|
+
schemaPath,
|
|
224
|
+
`
|
|
225
|
+
import type { Model, UUID, RichText } from "@supatype/types"
|
|
226
|
+
|
|
227
|
+
export type Page = Model<{
|
|
228
|
+
id: UUID
|
|
229
|
+
intro: RichText<"Welcome to Elmside.">
|
|
230
|
+
}>
|
|
231
|
+
`,
|
|
232
|
+
"utf8",
|
|
233
|
+
)
|
|
234
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
235
|
+
expect(ast?.models[0]?.fields["intro"]).toMatchObject({
|
|
236
|
+
kind: "richText",
|
|
237
|
+
annotations: { db: { pgType: "JSONB" }, platform: { editor: "rich" } },
|
|
238
|
+
default: { kind: "value", value: "Welcome to Elmside." },
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it("errors when RichText inline default and Default<> are both set", () => {
|
|
243
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-richtext-double-default-"))
|
|
244
|
+
dirs.push(dir)
|
|
245
|
+
const schemaPath = join(dir, "schema.ts")
|
|
246
|
+
writeFileSync(
|
|
247
|
+
schemaPath,
|
|
248
|
+
`
|
|
249
|
+
import type { Model, UUID, Default, RichText } from "@supatype/types"
|
|
250
|
+
|
|
251
|
+
export type Page = Model<{
|
|
252
|
+
id: UUID
|
|
253
|
+
intro: Default<RichText<"a">, "b">
|
|
254
|
+
}>
|
|
255
|
+
`,
|
|
256
|
+
"utf8",
|
|
257
|
+
)
|
|
258
|
+
expect(() => extractSchemaAstFromTypes(schemaPath, dir)).toThrow(
|
|
259
|
+
/either Default<…> or an inline type default/,
|
|
260
|
+
)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it("extracts Bucket<> config into storageBuckets and field accessMode", () => {
|
|
264
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-bucket-"))
|
|
265
|
+
dirs.push(dir)
|
|
266
|
+
const schemaPath = join(dir, "schema.ts")
|
|
267
|
+
writeFileSync(
|
|
268
|
+
schemaPath,
|
|
269
|
+
`
|
|
270
|
+
import type { Model, UUID, Public, LoggedIn, ImageAsset, Bucket } from "@supatype/types"
|
|
271
|
+
|
|
272
|
+
export type covers = Bucket<
|
|
273
|
+
"covers",
|
|
274
|
+
{
|
|
275
|
+
accessMode: "public"
|
|
276
|
+
maxSize: "2MB"
|
|
277
|
+
accept: readonly ["image/jpeg", "image/png"]
|
|
278
|
+
access: { read: Public; create: LoggedIn; delete: LoggedIn }
|
|
279
|
+
}
|
|
280
|
+
>
|
|
281
|
+
|
|
282
|
+
export type Post = Model<{
|
|
283
|
+
id: UUID
|
|
284
|
+
hero: ImageAsset<covers>
|
|
285
|
+
}>
|
|
286
|
+
`,
|
|
287
|
+
"utf8",
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
291
|
+
expect(ast?.storageBuckets).toBeDefined()
|
|
292
|
+
const b = ast?.storageBuckets?.find((x) => x.id === "covers")
|
|
293
|
+
expect(b?.public).toBe(true)
|
|
294
|
+
expect(b?.accessMode).toBe("public")
|
|
295
|
+
expect(b?.fileSizeLimit).toBe(2 * 1024 * 1024)
|
|
296
|
+
expect(b?.allowedMimeTypes).toEqual(["image/jpeg", "image/png"])
|
|
297
|
+
expect(b?.access?.read).toEqual({ type: "public" })
|
|
298
|
+
|
|
299
|
+
const hero = ast?.models[0]?.fields["hero"]
|
|
300
|
+
expect(hero).toMatchObject({ kind: "image", bucket: "covers", accessMode: "public" })
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it("extracts accessMode custom and s3BucketPolicy string", () => {
|
|
304
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-bucket-custom-"))
|
|
305
|
+
dirs.push(dir)
|
|
306
|
+
const schemaPath = join(dir, "schema.ts")
|
|
307
|
+
writeFileSync(
|
|
308
|
+
schemaPath,
|
|
309
|
+
`
|
|
310
|
+
import type { Model, UUID, ImageAsset, Bucket } from "@supatype/types"
|
|
311
|
+
|
|
312
|
+
export type assets = Bucket<"legacy", {
|
|
313
|
+
accessMode: "custom"
|
|
314
|
+
s3BucketPolicy: '{"Version":"2012-10-17"}'
|
|
315
|
+
}>
|
|
316
|
+
|
|
317
|
+
export type X = Model<{
|
|
318
|
+
id: UUID
|
|
319
|
+
f: ImageAsset<assets>
|
|
320
|
+
}>
|
|
321
|
+
`,
|
|
322
|
+
"utf8",
|
|
323
|
+
)
|
|
324
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
325
|
+
const b = ast?.storageBuckets?.find((x) => x.id === "legacy")
|
|
326
|
+
expect(b?.accessMode).toBe("custom")
|
|
327
|
+
expect(b?.public).toBe(false)
|
|
328
|
+
expect(b?.s3BucketPolicy).toBe('{"Version":"2012-10-17"}')
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
it("extracts slug source field from Slug<\"name\">", () => {
|
|
332
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-"))
|
|
333
|
+
dirs.push(dir)
|
|
334
|
+
const schemaPath = join(dir, "schema.ts")
|
|
335
|
+
writeFileSync(
|
|
336
|
+
schemaPath,
|
|
337
|
+
`
|
|
338
|
+
import type { Model, UUID, Slug, Unique } from "@supatype/types"
|
|
339
|
+
|
|
340
|
+
export type Article = Model<{
|
|
341
|
+
id: UUID
|
|
342
|
+
name: string
|
|
343
|
+
slug: Unique<Slug<"name">>
|
|
344
|
+
}>
|
|
345
|
+
`,
|
|
346
|
+
"utf8",
|
|
347
|
+
)
|
|
348
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
349
|
+
expect(ast?.models[0]?.fields["slug"]).toMatchObject({ kind: "slug", from: "name" })
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
it("normalizes RelatedTo foreign keys for fields ending in Id/ID", () => {
|
|
353
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-"))
|
|
354
|
+
dirs.push(dir)
|
|
355
|
+
const schemaPath = join(dir, "schema.ts")
|
|
356
|
+
writeFileSync(
|
|
357
|
+
schemaPath,
|
|
358
|
+
`
|
|
359
|
+
import type { Model, UUID, RelatedTo } from "@supatype/types"
|
|
360
|
+
|
|
361
|
+
export type Author = Model<{ id: UUID }>
|
|
362
|
+
|
|
363
|
+
export type Comment = Model<{
|
|
364
|
+
id: UUID
|
|
365
|
+
author: RelatedTo<Author>
|
|
366
|
+
userId: RelatedTo<Author>
|
|
367
|
+
customerID: RelatedTo<Author>
|
|
368
|
+
}>
|
|
369
|
+
`,
|
|
370
|
+
"utf8",
|
|
371
|
+
)
|
|
372
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
373
|
+
const comment = ast?.models.find((m) => m.name === "Comment")
|
|
374
|
+
expect(comment?.fields["author"]).toMatchObject({
|
|
375
|
+
annotations: { db: { foreignKey: "author_id" } },
|
|
376
|
+
})
|
|
377
|
+
expect(comment?.fields["userId"]).toMatchObject({
|
|
378
|
+
annotations: { db: { foreignKey: "user_id" } },
|
|
379
|
+
})
|
|
380
|
+
expect(comment?.fields["customerID"]).toMatchObject({
|
|
381
|
+
annotations: { db: { foreignKey: "customer_id" } },
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
it("extracts EditorReadOnly wrapper as readOnly field metadata", () => {
|
|
386
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-readonly-"))
|
|
387
|
+
dirs.push(dir)
|
|
388
|
+
const schemaPath = join(dir, "schema.ts")
|
|
389
|
+
writeFileSync(
|
|
390
|
+
schemaPath,
|
|
391
|
+
`
|
|
392
|
+
import type { Model, UUID, EditorReadOnly, RelatedTo } from "@supatype/types"
|
|
393
|
+
|
|
394
|
+
export type User = Model<{ id: UUID }>
|
|
395
|
+
|
|
396
|
+
export type Doc = Model<{
|
|
397
|
+
id: UUID
|
|
398
|
+
title: EditorReadOnly<string>
|
|
399
|
+
owner: EditorReadOnly<RelatedTo<User>>
|
|
400
|
+
}>
|
|
401
|
+
`,
|
|
402
|
+
"utf8",
|
|
403
|
+
)
|
|
404
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
405
|
+
const doc = ast?.models.find((m) => m.name === "Doc")
|
|
406
|
+
expect(doc?.fields["title"]).toMatchObject({
|
|
407
|
+
kind: "text",
|
|
408
|
+
annotations: { platform: { readOnly: true } },
|
|
409
|
+
})
|
|
410
|
+
expect(doc?.fields["owner"]).toMatchObject({
|
|
411
|
+
kind: "relation",
|
|
412
|
+
annotations: { platform: { readOnly: true } },
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
it("extracts Computed wrapper as readOnly + serverGenerated metadata", () => {
|
|
417
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-computed-"))
|
|
418
|
+
dirs.push(dir)
|
|
419
|
+
const schemaPath = join(dir, "schema.ts")
|
|
420
|
+
writeFileSync(
|
|
421
|
+
schemaPath,
|
|
422
|
+
`
|
|
423
|
+
import type { Model, UUID, Computed, Optional } from "@supatype/types"
|
|
424
|
+
|
|
425
|
+
export type Doc = Model<{
|
|
426
|
+
id: UUID
|
|
427
|
+
summary: Computed<Optional<string>>
|
|
428
|
+
}>
|
|
429
|
+
`,
|
|
430
|
+
"utf8",
|
|
431
|
+
)
|
|
432
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
433
|
+
const doc = ast?.models.find((m) => m.name === "Doc")
|
|
434
|
+
expect(doc?.fields["summary"]).toMatchObject({
|
|
435
|
+
kind: "text",
|
|
436
|
+
required: false,
|
|
437
|
+
annotations: { db: { serverGenerated: true }, platform: { readOnly: true } },
|
|
438
|
+
})
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
it("extracts ComputedFrom sources on text (single + tuple)", () => {
|
|
442
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-computed-from-"))
|
|
443
|
+
dirs.push(dir)
|
|
444
|
+
const schemaPath = join(dir, "schema.ts")
|
|
445
|
+
writeFileSync(
|
|
446
|
+
schemaPath,
|
|
447
|
+
`
|
|
448
|
+
import type { Model, UUID, ComputedFrom, Optional } from "@supatype/types"
|
|
449
|
+
|
|
450
|
+
export type Article = Model<{
|
|
451
|
+
id: UUID
|
|
452
|
+
title: string
|
|
453
|
+
subtitle: string
|
|
454
|
+
excerpt: Optional<ComputedFrom<string, "title">>
|
|
455
|
+
teaser: Optional<ComputedFrom<string, readonly ["title", "subtitle"]>>
|
|
456
|
+
}>
|
|
457
|
+
`,
|
|
458
|
+
"utf8",
|
|
459
|
+
)
|
|
460
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
461
|
+
const article = ast?.models.find((m) => m.name === "Article")
|
|
462
|
+
expect(article?.fields["excerpt"]).toMatchObject({
|
|
463
|
+
kind: "text",
|
|
464
|
+
required: false,
|
|
465
|
+
sources: ["title"],
|
|
466
|
+
})
|
|
467
|
+
expect(article?.fields["teaser"]).toMatchObject({
|
|
468
|
+
kind: "text",
|
|
469
|
+
required: false,
|
|
470
|
+
sources: ["title", "subtitle"],
|
|
471
|
+
})
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
it("extracts ComputedFrom template string and inferred sources", () => {
|
|
475
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-computed-tpl-"))
|
|
476
|
+
dirs.push(dir)
|
|
477
|
+
const schemaPath = join(dir, "schema.ts")
|
|
478
|
+
writeFileSync(
|
|
479
|
+
schemaPath,
|
|
480
|
+
`
|
|
481
|
+
import type { Model, UUID, ComputedFrom, Optional } from "@supatype/types"
|
|
482
|
+
|
|
483
|
+
export type Note = Model<{
|
|
484
|
+
id: UUID
|
|
485
|
+
title: string
|
|
486
|
+
author: string
|
|
487
|
+
published_at: string
|
|
488
|
+
description: string
|
|
489
|
+
summary: Optional<ComputedFrom<string, "Author: {author} | Date: {published_at}\\n{truncate(description, 100)}">>
|
|
490
|
+
}>
|
|
491
|
+
`,
|
|
492
|
+
"utf8",
|
|
493
|
+
)
|
|
494
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
495
|
+
const note = ast?.models.find((m) => m.name === "Note")
|
|
496
|
+
const summary = note?.fields["summary"] as { sources?: string[]; template?: string } | undefined
|
|
497
|
+
expect(summary).toMatchObject({
|
|
498
|
+
kind: "text",
|
|
499
|
+
required: false,
|
|
500
|
+
template: "Author: {author} | Date: {published_at}\n{truncate(description, 100)}",
|
|
501
|
+
})
|
|
502
|
+
expect(new Set(summary?.sources ?? [])).toEqual(new Set(["author", "published_at", "description"]))
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
it("extracts singleton: true with default _global_ table name", () => {
|
|
506
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-singleton-"))
|
|
507
|
+
dirs.push(dir)
|
|
508
|
+
const schemaPath = join(dir, "schema.ts")
|
|
509
|
+
writeFileSync(
|
|
510
|
+
schemaPath,
|
|
511
|
+
`
|
|
512
|
+
import type { Model, UUID, Public, Role, Timestamp } from "@supatype/types"
|
|
513
|
+
|
|
514
|
+
export type SiteSettings = Model<{
|
|
515
|
+
id: UUID
|
|
516
|
+
site_name: string
|
|
517
|
+
created_at: Timestamp
|
|
518
|
+
updated_at: Timestamp
|
|
519
|
+
}, {
|
|
520
|
+
singleton: true
|
|
521
|
+
access: { read: Public; update: Role<"supatype_admin"> }
|
|
522
|
+
}>
|
|
523
|
+
`,
|
|
524
|
+
"utf8",
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
528
|
+
const settings = ast?.models.find((m) => m.name === "SiteSettings")
|
|
529
|
+
expect(tableName(settings)).toBe("_global_site_settings")
|
|
530
|
+
expect(settings?.options).toMatchObject({ singleton: true, timestamps: true })
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
it("respects tableName override on singleton models", () => {
|
|
534
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-singleton-table-"))
|
|
535
|
+
dirs.push(dir)
|
|
536
|
+
const schemaPath = join(dir, "schema.ts")
|
|
537
|
+
writeFileSync(
|
|
538
|
+
schemaPath,
|
|
539
|
+
`
|
|
540
|
+
import type { Model, UUID, Public } from "@supatype/types"
|
|
541
|
+
|
|
542
|
+
export type Config = Model<{
|
|
543
|
+
id: UUID
|
|
544
|
+
}, {
|
|
545
|
+
singleton: true
|
|
546
|
+
tableName: "config"
|
|
547
|
+
access: { read: Public }
|
|
548
|
+
}>
|
|
549
|
+
`,
|
|
550
|
+
"utf8",
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
554
|
+
const config = ast?.models.find((m) => m.name === "Config")
|
|
555
|
+
expect(tableName(config)).toBe("config")
|
|
556
|
+
expect(config?.options.singleton).toBe(true)
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
it("infers timestamps from WithTimestamps wrapper", () => {
|
|
560
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-timestamps-"))
|
|
561
|
+
dirs.push(dir)
|
|
562
|
+
const schemaPath = join(dir, "schema.ts")
|
|
563
|
+
writeFileSync(
|
|
564
|
+
schemaPath,
|
|
565
|
+
`
|
|
566
|
+
import type { Model, UUID, WithTimestamps, Public } from "@supatype/types"
|
|
567
|
+
|
|
568
|
+
export type Post = Model<WithTimestamps<{
|
|
569
|
+
id: UUID
|
|
570
|
+
title: string
|
|
571
|
+
}>, {
|
|
572
|
+
access: { read: Public }
|
|
573
|
+
}>
|
|
574
|
+
`,
|
|
575
|
+
"utf8",
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
579
|
+
const post = ast?.models.find((m) => m.name === "Post")
|
|
580
|
+
expect(post?.options.timestamps).toBe(true)
|
|
581
|
+
expect(post?.options.singleton).toBeUndefined()
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
it("extracts LocaleConfig into schema AST locales", () => {
|
|
585
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-locale-config-"))
|
|
586
|
+
dirs.push(dir)
|
|
587
|
+
const schemaPath = join(dir, "schema.ts")
|
|
588
|
+
writeFileSync(
|
|
589
|
+
schemaPath,
|
|
590
|
+
`
|
|
591
|
+
import type { LocaleConfig, Model, UUID } from "@supatype/types"
|
|
592
|
+
|
|
593
|
+
export type localeConfig = LocaleConfig<["en", "de"], "en">
|
|
594
|
+
|
|
595
|
+
export type Page = Model<{ id: UUID; title: string }>
|
|
596
|
+
`,
|
|
597
|
+
"utf8",
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
601
|
+
expect(ast?.locales).toEqual(["en", "de"])
|
|
602
|
+
expect(ast?.defaultLocale).toBe("en")
|
|
603
|
+
})
|
|
604
|
+
|
|
605
|
+
it("marks Localized fields as JSONB with localized:true", () => {
|
|
606
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-localized-field-"))
|
|
607
|
+
dirs.push(dir)
|
|
608
|
+
const schemaPath = join(dir, "schema.ts")
|
|
609
|
+
writeFileSync(
|
|
610
|
+
schemaPath,
|
|
611
|
+
`
|
|
612
|
+
import type { Localized, Model, Optional, RichText, UUID } from "@supatype/types"
|
|
613
|
+
|
|
614
|
+
export type Page = Model<{
|
|
615
|
+
id: UUID
|
|
616
|
+
title: Localized<string>
|
|
617
|
+
body: Localized<RichText>
|
|
618
|
+
subtitle: Optional<Localized<string>>
|
|
619
|
+
}>
|
|
620
|
+
`,
|
|
621
|
+
"utf8",
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
625
|
+
const page = ast?.models.find((m) => m.name === "Page")
|
|
626
|
+
expect(page?.fields["title"]).toMatchObject({
|
|
627
|
+
kind: "text",
|
|
628
|
+
annotations: { db: { pgType: "JSONB" } },
|
|
629
|
+
localized: true,
|
|
630
|
+
required: true,
|
|
631
|
+
})
|
|
632
|
+
expect(page?.fields["body"]).toMatchObject({
|
|
633
|
+
kind: "richText",
|
|
634
|
+
annotations: { db: { pgType: "JSONB" }, platform: { editor: "rich" } },
|
|
635
|
+
localized: true,
|
|
636
|
+
})
|
|
637
|
+
expect(page?.fields["subtitle"]).toMatchObject({
|
|
638
|
+
kind: "text",
|
|
639
|
+
annotations: { db: { pgType: "JSONB" } },
|
|
640
|
+
localized: true,
|
|
641
|
+
required: false,
|
|
642
|
+
})
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
it("extracts LocalizedModel with auto-localized copy fields", () => {
|
|
646
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-localized-model-"))
|
|
647
|
+
dirs.push(dir)
|
|
648
|
+
const schemaPath = join(dir, "schema.ts")
|
|
649
|
+
writeFileSync(
|
|
650
|
+
schemaPath,
|
|
651
|
+
`
|
|
652
|
+
import type {
|
|
653
|
+
LocalizedModel,
|
|
654
|
+
UUID,
|
|
655
|
+
ImageAsset,
|
|
656
|
+
NotLocalized,
|
|
657
|
+
Blocks,
|
|
658
|
+
Block,
|
|
659
|
+
Bucket,
|
|
660
|
+
} from "@supatype/types"
|
|
661
|
+
|
|
662
|
+
export type marketing = Bucket<"marketing", { accessMode: "public" }>
|
|
663
|
+
export type RuleBlock = Block<"rule", { text: string }>
|
|
664
|
+
|
|
665
|
+
export type Homepage = LocalizedModel<{
|
|
666
|
+
id: UUID
|
|
667
|
+
hero_title: string
|
|
668
|
+
map_url: NotLocalized<string>
|
|
669
|
+
og_image: ImageAsset<marketing, { localized: true }>
|
|
670
|
+
hero_slides: Blocks<RuleBlock>
|
|
671
|
+
}>
|
|
672
|
+
`,
|
|
673
|
+
"utf8",
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
677
|
+
const homepage = ast?.models.find((m) => m.name === "Homepage")
|
|
678
|
+
expect(homepage?.fields["hero_title"]).toMatchObject({
|
|
679
|
+
kind: "text",
|
|
680
|
+
localized: true,
|
|
681
|
+
annotations: { db: { pgType: "JSONB" } },
|
|
682
|
+
})
|
|
683
|
+
expect(homepage?.fields["map_url"]?.localized).toBeUndefined()
|
|
684
|
+
expect(homepage?.fields["og_image"]).toMatchObject({
|
|
685
|
+
kind: "image",
|
|
686
|
+
localized: true,
|
|
687
|
+
})
|
|
688
|
+
const slides = homepage?.fields["hero_slides"] as { blocks?: { fields: Record<string, unknown> }[] }
|
|
689
|
+
expect(slides?.blocks?.[0]?.fields["text"]).toMatchObject({
|
|
690
|
+
kind: "text",
|
|
691
|
+
localized: true,
|
|
692
|
+
pgType: "JSONB",
|
|
693
|
+
})
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
it("marks Localized<Blocks<...>> as localized column", () => {
|
|
697
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-types-localized-blocks-col-"))
|
|
698
|
+
dirs.push(dir)
|
|
699
|
+
const schemaPath = join(dir, "schema.ts")
|
|
700
|
+
writeFileSync(
|
|
701
|
+
schemaPath,
|
|
702
|
+
`
|
|
703
|
+
import type { Localized, Model, UUID, Blocks, Block } from "@supatype/types"
|
|
704
|
+
|
|
705
|
+
export type Slide = Block<"slide", { image_path: string }>
|
|
706
|
+
|
|
707
|
+
export type Page = Model<{
|
|
708
|
+
id: UUID
|
|
709
|
+
slides: Localized<Blocks<Slide>>
|
|
710
|
+
}>
|
|
711
|
+
`,
|
|
712
|
+
"utf8",
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
716
|
+
const page = ast?.models.find((m) => m.name === "Page")
|
|
717
|
+
expect(page?.fields["slides"]).toMatchObject({
|
|
718
|
+
kind: "blocks",
|
|
719
|
+
localized: true,
|
|
720
|
+
annotations: { db: { pgType: "JSONB" } },
|
|
721
|
+
})
|
|
722
|
+
})
|
|
723
|
+
|
|
724
|
+
it("resolves type alias Nullable<T> = Optional<T>", () => {
|
|
725
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-alias-nullable-"))
|
|
726
|
+
dirs.push(dir)
|
|
727
|
+
const schemaPath = join(dir, "schema.ts")
|
|
728
|
+
writeFileSync(
|
|
729
|
+
schemaPath,
|
|
730
|
+
`
|
|
731
|
+
import type { Model, UUID, Email, Optional } from "@supatype/types"
|
|
732
|
+
|
|
733
|
+
type Nullable<T> = Optional<T>
|
|
734
|
+
|
|
735
|
+
export type User = Model<{
|
|
736
|
+
id: UUID
|
|
737
|
+
email: Nullable<Email>
|
|
738
|
+
}>
|
|
739
|
+
`,
|
|
740
|
+
"utf8",
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
744
|
+
const user = ast?.models.find((m) => m.name === "User")
|
|
745
|
+
expect(user?.fields["email"]).toMatchObject({
|
|
746
|
+
kind: "email",
|
|
747
|
+
required: false,
|
|
748
|
+
})
|
|
749
|
+
})
|
|
750
|
+
|
|
751
|
+
it("resolves multi-hop type aliases", () => {
|
|
752
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-alias-multihop-"))
|
|
753
|
+
dirs.push(dir)
|
|
754
|
+
const schemaPath = join(dir, "schema.ts")
|
|
755
|
+
writeFileSync(
|
|
756
|
+
schemaPath,
|
|
757
|
+
`
|
|
758
|
+
import type { Model, UUID, Email, Optional } from "@supatype/types"
|
|
759
|
+
|
|
760
|
+
type Nullable<T> = Optional<T>
|
|
761
|
+
type A = Nullable<Email>
|
|
762
|
+
type B = A
|
|
763
|
+
|
|
764
|
+
export type User = Model<{ id: UUID; email: B }>
|
|
765
|
+
`,
|
|
766
|
+
"utf8",
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
770
|
+
const user = ast?.models.find((m) => m.name === "User")
|
|
771
|
+
expect(user?.fields["email"]).toMatchObject({
|
|
772
|
+
kind: "email",
|
|
773
|
+
required: false,
|
|
774
|
+
})
|
|
775
|
+
})
|
|
776
|
+
|
|
777
|
+
it("resolves enum string-union type aliases", () => {
|
|
778
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-alias-enum-"))
|
|
779
|
+
dirs.push(dir)
|
|
780
|
+
const schemaPath = join(dir, "schema.ts")
|
|
781
|
+
writeFileSync(
|
|
782
|
+
schemaPath,
|
|
783
|
+
`
|
|
784
|
+
import type { Model, UUID } from "@supatype/types"
|
|
785
|
+
|
|
786
|
+
type Status = "draft" | "published" | "archived"
|
|
787
|
+
|
|
788
|
+
export type Post = Model<{ id: UUID; status: Status }>
|
|
789
|
+
`,
|
|
790
|
+
"utf8",
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
794
|
+
expect(ast?.models[0]?.fields["status"]).toMatchObject({
|
|
795
|
+
kind: "enum",
|
|
796
|
+
values: ["draft", "published", "archived"],
|
|
797
|
+
})
|
|
798
|
+
})
|
|
799
|
+
|
|
800
|
+
it("resolves import renames of @supatype/types primitives", () => {
|
|
801
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-import-rename-"))
|
|
802
|
+
dirs.push(dir)
|
|
803
|
+
const schemaPath = join(dir, "schema.ts")
|
|
804
|
+
writeFileSync(
|
|
805
|
+
schemaPath,
|
|
806
|
+
`
|
|
807
|
+
import type { Model, UUID, Email, Optional as Maybe } from "@supatype/types"
|
|
808
|
+
|
|
809
|
+
export type User = Model<{ id: UUID; email: Maybe<Email> }>
|
|
810
|
+
`,
|
|
811
|
+
"utf8",
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
815
|
+
const user = ast?.models.find((m) => m.name === "User")
|
|
816
|
+
expect(user?.fields["email"]).toMatchObject({
|
|
817
|
+
kind: "email",
|
|
818
|
+
required: false,
|
|
819
|
+
})
|
|
820
|
+
})
|
|
821
|
+
|
|
822
|
+
it("resolves cross-file type aliases via local import", () => {
|
|
823
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-cross-file-alias-"))
|
|
824
|
+
dirs.push(dir)
|
|
825
|
+
writeFileSync(
|
|
826
|
+
join(dir, "field-types.ts"),
|
|
827
|
+
`
|
|
828
|
+
import type { Optional } from "@supatype/types"
|
|
829
|
+
|
|
830
|
+
export type Nullable<T> = Optional<T>
|
|
831
|
+
`,
|
|
832
|
+
"utf8",
|
|
833
|
+
)
|
|
834
|
+
const schemaPath = join(dir, "schema.ts")
|
|
835
|
+
writeFileSync(
|
|
836
|
+
schemaPath,
|
|
837
|
+
`
|
|
838
|
+
import type { Model, UUID, Email } from "@supatype/types"
|
|
839
|
+
import type { Nullable } from "./field-types"
|
|
840
|
+
|
|
841
|
+
export type User = Model<{ id: UUID; email: Nullable<Email> }>
|
|
842
|
+
`,
|
|
843
|
+
"utf8",
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
847
|
+
const user = ast?.models.find((m) => m.name === "User")
|
|
848
|
+
expect(user?.fields["email"]).toMatchObject({
|
|
849
|
+
kind: "email",
|
|
850
|
+
required: false,
|
|
851
|
+
})
|
|
852
|
+
})
|
|
853
|
+
|
|
854
|
+
it("resolves import rename of a local type alias", () => {
|
|
855
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-rename-local-alias-"))
|
|
856
|
+
dirs.push(dir)
|
|
857
|
+
writeFileSync(
|
|
858
|
+
join(dir, "field-types.ts"),
|
|
859
|
+
`
|
|
860
|
+
import type { Optional } from "@supatype/types"
|
|
861
|
+
|
|
862
|
+
export type Nullable<T> = Optional<T>
|
|
863
|
+
`,
|
|
864
|
+
"utf8",
|
|
865
|
+
)
|
|
866
|
+
const schemaPath = join(dir, "schema.ts")
|
|
867
|
+
writeFileSync(
|
|
868
|
+
schemaPath,
|
|
869
|
+
`
|
|
870
|
+
import type { Model, UUID, Email } from "@supatype/types"
|
|
871
|
+
import type { Nullable as MaybeNull } from "./field-types"
|
|
872
|
+
|
|
873
|
+
export type User = Model<{ id: UUID; email: MaybeNull<Email> }>
|
|
874
|
+
`,
|
|
875
|
+
"utf8",
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
879
|
+
const user = ast?.models.find((m) => m.name === "User")
|
|
880
|
+
expect(user?.fields["email"]).toMatchObject({
|
|
881
|
+
kind: "email",
|
|
882
|
+
required: false,
|
|
883
|
+
})
|
|
884
|
+
})
|
|
885
|
+
|
|
886
|
+
it("resolves conditional type aliases via type checker", () => {
|
|
887
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-conditional-alias-"))
|
|
888
|
+
dirs.push(dir)
|
|
889
|
+
const schemaPath = join(dir, "schema.ts")
|
|
890
|
+
writeFileSync(
|
|
891
|
+
schemaPath,
|
|
892
|
+
`
|
|
893
|
+
import type { Model, UUID, Email, Optional } from "@supatype/types"
|
|
894
|
+
|
|
895
|
+
type NullableStr<T> = T extends string ? Optional<T> : T
|
|
896
|
+
|
|
897
|
+
export type User = Model<{ id: UUID; email: NullableStr<Email> }>
|
|
898
|
+
`,
|
|
899
|
+
"utf8",
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
903
|
+
const user = ast?.models.find((m) => m.name === "User")
|
|
904
|
+
expect(user?.fields["email"]).toMatchObject({
|
|
905
|
+
kind: "email",
|
|
906
|
+
required: false,
|
|
907
|
+
})
|
|
908
|
+
})
|
|
909
|
+
|
|
910
|
+
it("resolves mapped type aliases as Model fields argument", () => {
|
|
911
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-mapped-fields-"))
|
|
912
|
+
dirs.push(dir)
|
|
913
|
+
const schemaPath = join(dir, "schema.ts")
|
|
914
|
+
writeFileSync(
|
|
915
|
+
schemaPath,
|
|
916
|
+
`
|
|
917
|
+
import type { Model, UUID, Email, Optional } from "@supatype/types"
|
|
918
|
+
|
|
919
|
+
type AllOptional<T> = { [K in keyof T]: Optional<T[K]> }
|
|
920
|
+
|
|
921
|
+
export type User = Model<AllOptional<{ email: Email; name: string }>>
|
|
922
|
+
`,
|
|
923
|
+
"utf8",
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
const ast = extractSchemaAstFromTypes(schemaPath, dir)
|
|
927
|
+
const user = ast?.models.find((m) => m.name === "User")
|
|
928
|
+
expect(user?.fields["email"]).toMatchObject({ kind: "email", required: false })
|
|
929
|
+
expect(user?.fields["name"]).toMatchObject({ kind: "text", required: false })
|
|
930
|
+
})
|
|
931
|
+
|
|
932
|
+
it("throws on unknown Supatype types instead of silently mapping to TEXT", () => {
|
|
933
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-unknown-type-"))
|
|
934
|
+
dirs.push(dir)
|
|
935
|
+
const schemaPath = join(dir, "schema.ts")
|
|
936
|
+
writeFileSync(
|
|
937
|
+
schemaPath,
|
|
938
|
+
`
|
|
939
|
+
import type { Model, UUID } from "@supatype/types"
|
|
940
|
+
|
|
941
|
+
export type User = Model<{ id: UUID; email: SomeType }>
|
|
942
|
+
`,
|
|
943
|
+
"utf8",
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
expect(() => extractSchemaAstFromTypes(schemaPath, dir)).toThrow(/Unknown Supatype type "SomeType"/)
|
|
947
|
+
})
|
|
948
|
+
|
|
949
|
+
it("throws on circular type alias chains", () => {
|
|
950
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-circular-alias-"))
|
|
951
|
+
dirs.push(dir)
|
|
952
|
+
const schemaPath = join(dir, "schema.ts")
|
|
953
|
+
writeFileSync(
|
|
954
|
+
schemaPath,
|
|
955
|
+
`
|
|
956
|
+
import type { Model, UUID } from "@supatype/types"
|
|
957
|
+
|
|
958
|
+
type A = B
|
|
959
|
+
type B = A
|
|
960
|
+
|
|
961
|
+
export type User = Model<{ id: UUID; email: A }>
|
|
962
|
+
`,
|
|
963
|
+
"utf8",
|
|
964
|
+
)
|
|
965
|
+
|
|
966
|
+
expect(() => extractSchemaAstFromTypes(schemaPath, dir)).toThrow(/circular alias chain/)
|
|
967
|
+
})
|
|
968
|
+
|
|
969
|
+
it("throws on TypeScript utility types used as field types", () => {
|
|
970
|
+
const dir = mkdtempSync(join(tmpdir(), "supatype-utility-type-"))
|
|
971
|
+
dirs.push(dir)
|
|
972
|
+
const schemaPath = join(dir, "schema.ts")
|
|
973
|
+
writeFileSync(
|
|
974
|
+
schemaPath,
|
|
975
|
+
`
|
|
976
|
+
import type { Model, UUID } from "@supatype/types"
|
|
977
|
+
|
|
978
|
+
export type User = Model<{ id: UUID; email: NonNullable<string> }>
|
|
979
|
+
`,
|
|
980
|
+
"utf8",
|
|
981
|
+
)
|
|
982
|
+
|
|
983
|
+
expect(() => extractSchemaAstFromTypes(schemaPath, dir)).toThrow(/Unknown Supatype type "NonNullable"/)
|
|
984
|
+
})
|
|
985
|
+
})
|