jazz-tools 2.0.0-alpha.21 → 2.0.0-alpha.24
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/bin/docs-index.db +0 -0
- package/bin/docs-index.txt +1624 -542
- package/bin/jazz-tools.js +47 -41
- package/bin/native/jazz-tools-darwin-arm64 +0 -0
- package/bin/native/jazz-tools-darwin-x64 +0 -0
- package/bin/native/jazz-tools-linux-arm64 +0 -0
- package/bin/native/jazz-tools-linux-x64 +0 -0
- package/dist/backend/create-jazz-context.d.ts +31 -6
- package/dist/backend/create-jazz-context.d.ts.map +1 -1
- package/dist/backend/create-jazz-context.js +35 -5
- package/dist/backend/create-jazz-context.js.map +1 -1
- package/dist/backend/create-jazz-context.test.js +61 -6
- package/dist/backend/create-jazz-context.test.js.map +1 -1
- package/dist/cli.d.ts +29 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +648 -246
- package/dist/cli.js.map +1 -1
- package/dist/cli.test.js +640 -294
- package/dist/cli.test.js.map +1 -1
- package/dist/codegen/schema-reader.d.ts.map +1 -1
- package/dist/codegen/schema-reader.js +6 -1
- package/dist/codegen/schema-reader.js.map +1 -1
- package/dist/dev-tools/dev-tools.d.ts.map +1 -1
- package/dist/dev-tools/dev-tools.js +61 -13
- package/dist/dev-tools/dev-tools.js.map +1 -1
- package/dist/dev-tools/dev-tools.test.js +166 -0
- package/dist/dev-tools/dev-tools.test.js.map +1 -1
- package/dist/dev-tools/extension-panel.d.ts.map +1 -1
- package/dist/dev-tools/extension-panel.js +30 -7
- package/dist/dev-tools/extension-panel.js.map +1 -1
- package/dist/dev-tools/protocol.d.ts +49 -1
- package/dist/dev-tools/protocol.d.ts.map +1 -1
- package/dist/dev-tools/protocol.js +3 -0
- package/dist/dev-tools/protocol.js.map +1 -1
- package/dist/drivers/index.d.ts +1 -1
- package/dist/drivers/index.d.ts.map +1 -1
- package/dist/drivers/schema-wire.d.ts.map +1 -1
- package/dist/drivers/schema-wire.js +12 -1
- package/dist/drivers/schema-wire.js.map +1 -1
- package/dist/drivers/schema-wire.test.d.ts +2 -0
- package/dist/drivers/schema-wire.test.d.ts.map +1 -0
- package/dist/drivers/schema-wire.test.js +31 -0
- package/dist/drivers/schema-wire.test.js.map +1 -0
- package/dist/drivers/types.d.ts +2 -0
- package/dist/drivers/types.d.ts.map +1 -1
- package/dist/dsl.d.ts +139 -95
- package/dist/dsl.d.ts.map +1 -1
- package/dist/dsl.js +64 -8
- package/dist/dsl.js.map +1 -1
- package/dist/dsl.test.js +78 -8
- package/dist/dsl.test.js.map +1 -1
- package/dist/index.d.ts +32 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -3
- package/dist/index.js.map +1 -1
- package/dist/magic-columns.d.ts +3 -1
- package/dist/magic-columns.d.ts.map +1 -1
- package/dist/magic-columns.js +20 -4
- package/dist/magic-columns.js.map +1 -1
- package/dist/mcp/build-index.test.js +1 -1
- package/dist/migrations.d.ts +126 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +112 -0
- package/dist/migrations.js.map +1 -0
- package/dist/permissions/index.test.js +35 -0
- package/dist/permissions/index.test.js.map +1 -1
- package/dist/react-native/create-jazz-client.test.js +62 -42
- package/dist/react-native/create-jazz-client.test.js.map +1 -1
- package/dist/react-native/jazz-rn-runtime-adapter.d.ts +18 -3
- package/dist/react-native/jazz-rn-runtime-adapter.d.ts.map +1 -1
- package/dist/react-native/jazz-rn-runtime-adapter.js +110 -6
- package/dist/react-native/jazz-rn-runtime-adapter.js.map +1 -1
- package/dist/react-native/jazz-rn-runtime-adapter.test.js +149 -4
- package/dist/react-native/jazz-rn-runtime-adapter.test.js.map +1 -1
- package/dist/reconcile-array.d.ts +29 -0
- package/dist/reconcile-array.d.ts.map +1 -0
- package/dist/reconcile-array.js +110 -0
- package/dist/reconcile-array.js.map +1 -0
- package/dist/reconcile-array.test.d.ts +2 -0
- package/dist/reconcile-array.test.d.ts.map +1 -0
- package/dist/reconcile-array.test.js +118 -0
- package/dist/reconcile-array.test.js.map +1 -0
- package/dist/runtime/client.d.ts +24 -20
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.for-request.test.js +8 -8
- package/dist/runtime/client.for-request.test.js.map +1 -1
- package/dist/runtime/client.js +58 -25
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/client.mutations.test.js +72 -1
- package/dist/runtime/client.mutations.test.js.map +1 -1
- package/dist/runtime/cloud-server.integration.test.js +145 -88
- package/dist/runtime/cloud-server.integration.test.js.map +1 -1
- package/dist/runtime/db.d.ts +3 -7
- package/dist/runtime/db.d.ts.map +1 -1
- package/dist/runtime/db.js +16 -14
- package/dist/runtime/db.js.map +1 -1
- package/dist/runtime/db.schema-order.test.js +8 -8
- package/dist/runtime/db.schema-order.test.js.map +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/napi.integration.test.js +113 -136
- package/dist/runtime/napi.integration.test.js.map +1 -1
- package/dist/runtime/query-adapter.d.ts.map +1 -1
- package/dist/runtime/query-adapter.js +22 -2
- package/dist/runtime/query-adapter.js.map +1 -1
- package/dist/runtime/query-adapter.test.js +81 -5
- package/dist/runtime/query-adapter.test.js.map +1 -1
- package/dist/runtime/row-transformer.js +2 -2
- package/dist/runtime/row-transformer.js.map +1 -1
- package/dist/runtime/row-transformer.test.js +9 -9
- package/dist/runtime/row-transformer.test.js.map +1 -1
- package/dist/runtime/schema-fetch.d.ts +103 -1
- package/dist/runtime/schema-fetch.d.ts.map +1 -1
- package/dist/runtime/schema-fetch.js +106 -0
- package/dist/runtime/schema-fetch.js.map +1 -1
- package/dist/runtime/sync-transport.d.ts +3 -1
- package/dist/runtime/sync-transport.d.ts.map +1 -1
- package/dist/runtime/sync-transport.js +34 -3
- package/dist/runtime/sync-transport.js.map +1 -1
- package/dist/runtime/sync-transport.test.js +53 -1
- package/dist/runtime/sync-transport.test.js.map +1 -1
- package/dist/runtime/value-converter.d.ts +9 -6
- package/dist/runtime/value-converter.d.ts.map +1 -1
- package/dist/runtime/value-converter.js +22 -9
- package/dist/runtime/value-converter.js.map +1 -1
- package/dist/runtime/value-converter.test.js +32 -26
- package/dist/runtime/value-converter.test.js.map +1 -1
- package/dist/schema-loader.d.ts +14 -0
- package/dist/schema-loader.d.ts.map +1 -0
- package/dist/schema-loader.js +219 -0
- package/dist/schema-loader.js.map +1 -0
- package/dist/schema-permissions.d.ts +8 -0
- package/dist/schema-permissions.d.ts.map +1 -0
- package/dist/schema-permissions.js +266 -0
- package/dist/schema-permissions.js.map +1 -0
- package/dist/schema-permissions.test.d.ts +2 -0
- package/dist/schema-permissions.test.d.ts.map +1 -0
- package/dist/schema-permissions.test.js +43 -0
- package/dist/schema-permissions.test.js.map +1 -0
- package/dist/schema.d.ts +11 -9
- package/dist/schema.d.ts.map +1 -1
- package/dist/svelte/context.svelte.test.js +50 -0
- package/dist/svelte/rune-patterns.svelte.test.js +301 -0
- package/dist/svelte/test-helpers.svelte.js +14 -0
- package/dist/svelte/use-all.svelte.d.ts.map +1 -1
- package/dist/svelte/use-all.svelte.js +7 -1
- package/dist/testing/fixtures/basic/schema.d.ts +11 -0
- package/dist/testing/fixtures/basic/schema.d.ts.map +1 -0
- package/dist/testing/fixtures/basic/schema.js +10 -0
- package/dist/testing/fixtures/basic/schema.js.map +1 -0
- package/dist/testing/index.d.ts +2 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +2 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/index.test.js +109 -9
- package/dist/testing/index.test.js.map +1 -1
- package/dist/testing/local-jazz-server.d.ts +2 -0
- package/dist/testing/local-jazz-server.d.ts.map +1 -1
- package/dist/testing/local-jazz-server.js +21 -51
- package/dist/testing/local-jazz-server.js.map +1 -1
- package/dist/testing/policy-test-app.d.ts.map +1 -1
- package/dist/testing/policy-test-app.js +71 -3
- package/dist/testing/policy-test-app.js.map +1 -1
- package/dist/typed-app.d.ts +364 -0
- package/dist/typed-app.d.ts.map +1 -0
- package/dist/{testing/fixtures/basic/app.js → typed-app.js} +118 -30
- package/dist/typed-app.js.map +1 -0
- package/dist/vue/use-all.d.ts +2 -2
- package/dist/vue/use-all.d.ts.map +1 -1
- package/dist/vue/use-all.js +9 -3
- package/dist/vue/use-all.js.map +1 -1
- package/dist/vue/use-all.test.js +137 -0
- package/dist/vue/use-all.test.js.map +1 -1
- package/package.json +17 -14
- package/bin/native/jazz-tools-windows-x64.exe +0 -0
- package/dist/codegen/codegen.test.d.ts +0 -2
- package/dist/codegen/codegen.test.d.ts.map +0 -1
- package/dist/codegen/codegen.test.js +0 -1134
- package/dist/codegen/codegen.test.js.map +0 -1
- package/dist/codegen/index.d.ts +0 -18
- package/dist/codegen/index.d.ts.map +0 -1
- package/dist/codegen/index.js +0 -22
- package/dist/codegen/index.js.map +0 -1
- package/dist/codegen/query-builder-generator.d.ts +0 -26
- package/dist/codegen/query-builder-generator.d.ts.map +0 -1
- package/dist/codegen/query-builder-generator.js +0 -377
- package/dist/codegen/query-builder-generator.js.map +0 -1
- package/dist/codegen/type-generator.d.ts +0 -30
- package/dist/codegen/type-generator.d.ts.map +0 -1
- package/dist/codegen/type-generator.js +0 -368
- package/dist/codegen/type-generator.js.map +0 -1
- package/dist/runtime/napi.fjall.db.all.integration.test.d.ts +0 -2
- package/dist/runtime/napi.fjall.db.all.integration.test.d.ts.map +0 -1
- package/dist/runtime/napi.fjall.db.all.integration.test.js +0 -76
- package/dist/runtime/napi.fjall.db.all.integration.test.js.map +0 -1
- package/dist/runtime/napi.fjall.db.subscribeAll.integration.test.d.ts +0 -2
- package/dist/runtime/napi.fjall.db.subscribeAll.integration.test.d.ts.map +0 -1
- package/dist/runtime/napi.fjall.db.subscribeAll.integration.test.js +0 -47
- package/dist/runtime/napi.fjall.db.subscribeAll.integration.test.js.map +0 -1
- package/dist/runtime/napi.fjall.test-helpers.d.ts +0 -34
- package/dist/runtime/napi.fjall.test-helpers.d.ts.map +0 -1
- package/dist/runtime/napi.fjall.test-helpers.js +0 -172
- package/dist/runtime/napi.fjall.test-helpers.js.map +0 -1
- package/dist/sql-gen.d.ts +0 -5
- package/dist/sql-gen.d.ts.map +0 -1
- package/dist/sql-gen.js +0 -234
- package/dist/sql-gen.js.map +0 -1
- package/dist/sql-gen.test.d.ts +0 -2
- package/dist/sql-gen.test.d.ts.map +0 -1
- package/dist/sql-gen.test.js +0 -481
- package/dist/sql-gen.test.js.map +0 -1
- package/dist/svelte/context.test.d.ts +0 -2
- package/dist/svelte/context.test.d.ts.map +0 -1
- package/dist/svelte/context.test.js +0 -55
- package/dist/svelte/use-all.test.d.ts +0 -2
- package/dist/svelte/use-all.test.d.ts.map +0 -1
- package/dist/svelte/use-all.test.js +0 -147
- package/dist/testing/fixtures/basic/app.d.ts +0 -59
- package/dist/testing/fixtures/basic/app.d.ts.map +0 -1
- package/dist/testing/fixtures/basic/app.js.map +0 -1
- package/dist/testing/fixtures/basic/current.d.ts +0 -2
- package/dist/testing/fixtures/basic/current.d.ts.map +0 -1
- package/dist/testing/fixtures/basic/current.js +0 -6
- package/dist/testing/fixtures/basic/current.js.map +0 -1
|
@@ -1,1134 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import { table, col, resetCollectedState, getCollectedSchema } from "../dsl.js";
|
|
3
|
-
import { schemaToWasm } from "./schema-reader.js";
|
|
4
|
-
import { generateTypes } from "./type-generator.js";
|
|
5
|
-
import { generateClient, analyzeRelations } from "./index.js";
|
|
6
|
-
import { z } from "zod/v4";
|
|
7
|
-
describe("schemaToWasm", () => {
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
resetCollectedState();
|
|
10
|
-
});
|
|
11
|
-
it("converts TEXT to Text", () => {
|
|
12
|
-
table("items", { name: col.string() });
|
|
13
|
-
const schema = getCollectedSchema();
|
|
14
|
-
const wasm = schemaToWasm(schema);
|
|
15
|
-
expect(wasm.items.columns[0]).toEqual({
|
|
16
|
-
name: "name",
|
|
17
|
-
column_type: { type: "Text" },
|
|
18
|
-
nullable: false,
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
it("converts BOOLEAN to Boolean", () => {
|
|
22
|
-
table("items", { active: col.boolean() });
|
|
23
|
-
const schema = getCollectedSchema();
|
|
24
|
-
const wasm = schemaToWasm(schema);
|
|
25
|
-
expect(wasm.items.columns[0]).toEqual({
|
|
26
|
-
name: "active",
|
|
27
|
-
column_type: { type: "Boolean" },
|
|
28
|
-
nullable: false,
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
it("converts INTEGER to Integer", () => {
|
|
32
|
-
table("items", { count: col.int() });
|
|
33
|
-
const schema = getCollectedSchema();
|
|
34
|
-
const wasm = schemaToWasm(schema);
|
|
35
|
-
expect(wasm.items.columns[0]).toEqual({
|
|
36
|
-
name: "count",
|
|
37
|
-
column_type: { type: "Integer" },
|
|
38
|
-
nullable: false,
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
it("converts TIMESTAMP to Timestamp", () => {
|
|
42
|
-
table("items", { created_at: col.timestamp() });
|
|
43
|
-
const schema = getCollectedSchema();
|
|
44
|
-
const wasm = schemaToWasm(schema);
|
|
45
|
-
expect(wasm.items.columns[0]).toEqual({
|
|
46
|
-
name: "created_at",
|
|
47
|
-
column_type: { type: "Timestamp" },
|
|
48
|
-
nullable: false,
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
it("converts REAL to Double", () => {
|
|
52
|
-
table("items", { price: col.float() });
|
|
53
|
-
const schema = getCollectedSchema();
|
|
54
|
-
const wasm = schemaToWasm(schema);
|
|
55
|
-
expect(wasm.items.columns[0]).toEqual({
|
|
56
|
-
name: "price",
|
|
57
|
-
column_type: { type: "Double" },
|
|
58
|
-
nullable: false,
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
it("converts ref to Uuid with references", () => {
|
|
62
|
-
table("items", { ownerId: col.ref("users") });
|
|
63
|
-
const schema = getCollectedSchema();
|
|
64
|
-
const wasm = schemaToWasm(schema);
|
|
65
|
-
expect(wasm.items.columns[0]).toEqual({
|
|
66
|
-
name: "ownerId",
|
|
67
|
-
column_type: { type: "Uuid" },
|
|
68
|
-
nullable: false,
|
|
69
|
-
references: "users",
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
it("handles nullable columns", () => {
|
|
73
|
-
table("items", { description: col.string().optional() });
|
|
74
|
-
const schema = getCollectedSchema();
|
|
75
|
-
const wasm = schemaToWasm(schema);
|
|
76
|
-
expect(wasm.items.columns[0]).toEqual({
|
|
77
|
-
name: "description",
|
|
78
|
-
column_type: { type: "Text" },
|
|
79
|
-
nullable: true,
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
it("handles nullable refs", () => {
|
|
83
|
-
table("todos", { parentId: col.ref("todos").optional() });
|
|
84
|
-
const schema = getCollectedSchema();
|
|
85
|
-
const wasm = schemaToWasm(schema);
|
|
86
|
-
expect(wasm.todos.columns[0]).toEqual({
|
|
87
|
-
name: "parentId",
|
|
88
|
-
column_type: { type: "Uuid" },
|
|
89
|
-
nullable: true,
|
|
90
|
-
references: "todos",
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
it("converts TEXT[] to Array<Text>", () => {
|
|
94
|
-
table("items", { tags: col.array(col.string()) });
|
|
95
|
-
const schema = getCollectedSchema();
|
|
96
|
-
const wasm = schemaToWasm(schema);
|
|
97
|
-
expect(wasm.items.columns[0]).toEqual({
|
|
98
|
-
name: "tags",
|
|
99
|
-
column_type: { type: "Array", element: { type: "Text" } },
|
|
100
|
-
nullable: false,
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
it("converts nested arrays (INTEGER[][])", () => {
|
|
104
|
-
table("items", { matrix: col.array(col.array(col.int())) });
|
|
105
|
-
const schema = getCollectedSchema();
|
|
106
|
-
const wasm = schemaToWasm(schema);
|
|
107
|
-
expect(wasm.items.columns[0]).toEqual({
|
|
108
|
-
name: "matrix",
|
|
109
|
-
column_type: {
|
|
110
|
-
type: "Array",
|
|
111
|
-
element: { type: "Array", element: { type: "Integer" } },
|
|
112
|
-
},
|
|
113
|
-
nullable: false,
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
it("preserves references for UUID[] from array(ref)", () => {
|
|
117
|
-
table("items", { owner_ids: col.array(col.ref("users")) });
|
|
118
|
-
const schema = getCollectedSchema();
|
|
119
|
-
const wasm = schemaToWasm(schema);
|
|
120
|
-
expect(wasm.items.columns[0]).toEqual({
|
|
121
|
-
name: "owner_ids",
|
|
122
|
-
column_type: { type: "Array", element: { type: "Uuid" } },
|
|
123
|
-
nullable: false,
|
|
124
|
-
references: "users",
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
it("converts enum to Enum with normalized variants", () => {
|
|
128
|
-
table("tasks", { status: col.enum("in_progress", "todo", "done") });
|
|
129
|
-
const schema = getCollectedSchema();
|
|
130
|
-
const wasm = schemaToWasm(schema);
|
|
131
|
-
expect(wasm.tasks.columns[0]).toEqual({
|
|
132
|
-
name: "status",
|
|
133
|
-
column_type: { type: "Enum", variants: ["done", "in_progress", "todo"] },
|
|
134
|
-
nullable: false,
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
it("converts JSON to Json without schema metadata", () => {
|
|
138
|
-
table("documents", { payload: col.json() });
|
|
139
|
-
const schema = getCollectedSchema();
|
|
140
|
-
const wasm = schemaToWasm(schema);
|
|
141
|
-
expect(wasm.documents.columns[0]).toEqual({
|
|
142
|
-
name: "payload",
|
|
143
|
-
column_type: { type: "Json", schema: undefined },
|
|
144
|
-
nullable: false,
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
it("converts JSON with plain schema object metadata", () => {
|
|
148
|
-
table("documents", {
|
|
149
|
-
payload: col.json({
|
|
150
|
-
type: "object",
|
|
151
|
-
properties: {
|
|
152
|
-
name: { type: "string" },
|
|
153
|
-
},
|
|
154
|
-
required: ["name"],
|
|
155
|
-
}),
|
|
156
|
-
});
|
|
157
|
-
const schema = getCollectedSchema();
|
|
158
|
-
const wasm = schemaToWasm(schema);
|
|
159
|
-
expect(wasm.documents.columns[0]).toEqual({
|
|
160
|
-
name: "payload",
|
|
161
|
-
column_type: {
|
|
162
|
-
type: "Json",
|
|
163
|
-
schema: {
|
|
164
|
-
type: "object",
|
|
165
|
-
properties: {
|
|
166
|
-
name: { type: "string" },
|
|
167
|
-
},
|
|
168
|
-
required: ["name"],
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
nullable: false,
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
it("accepts zod v4 standard json-schema providers for JSON columns", () => {
|
|
175
|
-
const payloadSchema = z.object({
|
|
176
|
-
name: z.string(),
|
|
177
|
-
age: z.number().int().optional(),
|
|
178
|
-
});
|
|
179
|
-
table("documents", { payload: col.json(payloadSchema) });
|
|
180
|
-
const schema = getCollectedSchema();
|
|
181
|
-
const wasm = schemaToWasm(schema);
|
|
182
|
-
const column = wasm.documents.columns[0];
|
|
183
|
-
expect(column?.column_type.type).toBe("Json");
|
|
184
|
-
if (column?.column_type.type !== "Json") {
|
|
185
|
-
throw new Error("expected Json column type");
|
|
186
|
-
}
|
|
187
|
-
expect(column.column_type.schema).toBeTruthy();
|
|
188
|
-
const jsonSchema = column.column_type.schema;
|
|
189
|
-
expect(jsonSchema.type).toBe("object");
|
|
190
|
-
expect(jsonSchema.properties).toBeTruthy();
|
|
191
|
-
expect(jsonSchema.properties.name).toBeTruthy();
|
|
192
|
-
});
|
|
193
|
-
it("converts multiple tables", () => {
|
|
194
|
-
table("users", { name: col.string() });
|
|
195
|
-
table("todos", { title: col.string(), userId: col.ref("users") });
|
|
196
|
-
const schema = getCollectedSchema();
|
|
197
|
-
const wasm = schemaToWasm(schema);
|
|
198
|
-
expect(Object.keys(wasm)).toEqual(["users", "todos"]);
|
|
199
|
-
expect(wasm.users.columns).toHaveLength(1);
|
|
200
|
-
expect(wasm.todos.columns).toHaveLength(2);
|
|
201
|
-
});
|
|
202
|
-
it("carries table permissions into wasm schema", () => {
|
|
203
|
-
table("todos", { ownerId: col.string(), title: col.string() });
|
|
204
|
-
const schema = getCollectedSchema();
|
|
205
|
-
const ownerMatchesSession = {
|
|
206
|
-
type: "Cmp",
|
|
207
|
-
column: "owner_id",
|
|
208
|
-
op: "Eq",
|
|
209
|
-
value: { type: "SessionRef", path: ["user_id"] },
|
|
210
|
-
};
|
|
211
|
-
schema.tables[0].policies = {
|
|
212
|
-
select: { using: ownerMatchesSession },
|
|
213
|
-
insert: { with_check: ownerMatchesSession },
|
|
214
|
-
update: { using: ownerMatchesSession, with_check: ownerMatchesSession },
|
|
215
|
-
delete: { using: ownerMatchesSession },
|
|
216
|
-
};
|
|
217
|
-
const wasm = schemaToWasm(schema);
|
|
218
|
-
expect(wasm.todos.policies).toEqual({
|
|
219
|
-
select: {
|
|
220
|
-
using: {
|
|
221
|
-
type: "Cmp",
|
|
222
|
-
column: "owner_id",
|
|
223
|
-
op: "Eq",
|
|
224
|
-
value: { type: "SessionRef", path: ["user_id"] },
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
insert: {
|
|
228
|
-
with_check: {
|
|
229
|
-
type: "Cmp",
|
|
230
|
-
column: "owner_id",
|
|
231
|
-
op: "Eq",
|
|
232
|
-
value: { type: "SessionRef", path: ["user_id"] },
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
update: {
|
|
236
|
-
using: {
|
|
237
|
-
type: "Cmp",
|
|
238
|
-
column: "owner_id",
|
|
239
|
-
op: "Eq",
|
|
240
|
-
value: { type: "SessionRef", path: ["user_id"] },
|
|
241
|
-
},
|
|
242
|
-
with_check: {
|
|
243
|
-
type: "Cmp",
|
|
244
|
-
column: "owner_id",
|
|
245
|
-
op: "Eq",
|
|
246
|
-
value: { type: "SessionRef", path: ["user_id"] },
|
|
247
|
-
},
|
|
248
|
-
},
|
|
249
|
-
delete: {
|
|
250
|
-
using: {
|
|
251
|
-
type: "Cmp",
|
|
252
|
-
column: "owner_id",
|
|
253
|
-
op: "Eq",
|
|
254
|
-
value: { type: "SessionRef", path: ["user_id"] },
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
it("carries InheritsReferencing policies into wasm schema", () => {
|
|
260
|
-
table("files", { ownerId: col.string() });
|
|
261
|
-
const schema = getCollectedSchema();
|
|
262
|
-
schema.tables[0].policies = {
|
|
263
|
-
select: {
|
|
264
|
-
using: {
|
|
265
|
-
type: "InheritsReferencing",
|
|
266
|
-
operation: "Select",
|
|
267
|
-
source_table: "todos",
|
|
268
|
-
via_column: "image",
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
|
-
};
|
|
272
|
-
const wasm = schemaToWasm(schema);
|
|
273
|
-
expect(wasm.files.policies).toEqual({
|
|
274
|
-
select: {
|
|
275
|
-
using: {
|
|
276
|
-
type: "InheritsReferencing",
|
|
277
|
-
operation: "Select",
|
|
278
|
-
source_table: "todos",
|
|
279
|
-
via_column: "image",
|
|
280
|
-
},
|
|
281
|
-
},
|
|
282
|
-
insert: {},
|
|
283
|
-
update: {},
|
|
284
|
-
delete: {},
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
it("carries session-left policies into wasm schema", () => {
|
|
288
|
-
table("todos", { owner_id: col.string() });
|
|
289
|
-
const schema = getCollectedSchema();
|
|
290
|
-
schema.tables[0].policies = {
|
|
291
|
-
select: {
|
|
292
|
-
using: {
|
|
293
|
-
type: "And",
|
|
294
|
-
exprs: [
|
|
295
|
-
{
|
|
296
|
-
type: "SessionCmp",
|
|
297
|
-
path: ["claims", "role"],
|
|
298
|
-
op: "Eq",
|
|
299
|
-
value: { type: "Literal", value: "manager" },
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
type: "SessionContains",
|
|
303
|
-
path: ["claims", "teamIds"],
|
|
304
|
-
value: { type: "Literal", value: "team_a" },
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
type: "SessionInList",
|
|
308
|
-
path: ["claims", "plan"],
|
|
309
|
-
values: [
|
|
310
|
-
{ type: "Literal", value: "pro" },
|
|
311
|
-
{ type: "Literal", value: "enterprise" },
|
|
312
|
-
],
|
|
313
|
-
},
|
|
314
|
-
],
|
|
315
|
-
},
|
|
316
|
-
},
|
|
317
|
-
};
|
|
318
|
-
const wasm = schemaToWasm(schema);
|
|
319
|
-
expect(wasm.todos.policies?.select?.using).toEqual({
|
|
320
|
-
type: "And",
|
|
321
|
-
exprs: [
|
|
322
|
-
{
|
|
323
|
-
type: "SessionCmp",
|
|
324
|
-
path: ["claims", "role"],
|
|
325
|
-
op: "Eq",
|
|
326
|
-
value: { type: "Text", value: "manager" },
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
type: "SessionContains",
|
|
330
|
-
path: ["claims", "teamIds"],
|
|
331
|
-
value: { type: "Text", value: "team_a" },
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
type: "SessionInList",
|
|
335
|
-
path: ["claims", "plan"],
|
|
336
|
-
values: [
|
|
337
|
-
{ type: "Text", value: "pro" },
|
|
338
|
-
{ type: "Text", value: "enterprise" },
|
|
339
|
-
],
|
|
340
|
-
},
|
|
341
|
-
],
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
describe("generateTypes", () => {
|
|
346
|
-
beforeEach(() => {
|
|
347
|
-
resetCollectedState();
|
|
348
|
-
});
|
|
349
|
-
it("generates base interface with id field", () => {
|
|
350
|
-
table("todos", { title: col.string() });
|
|
351
|
-
const schema = getCollectedSchema();
|
|
352
|
-
const wasm = schemaToWasm(schema);
|
|
353
|
-
const output = generateTypes(wasm);
|
|
354
|
-
expect(output).toContain("export interface Todo {");
|
|
355
|
-
expect(output).toContain(" id: string;");
|
|
356
|
-
expect(output).toContain(" title: string;");
|
|
357
|
-
});
|
|
358
|
-
it("generates init interface without id field", () => {
|
|
359
|
-
table("todos", { title: col.string() });
|
|
360
|
-
const schema = getCollectedSchema();
|
|
361
|
-
const wasm = schemaToWasm(schema);
|
|
362
|
-
const output = generateTypes(wasm);
|
|
363
|
-
expect(output).toContain("export interface TodoInit {");
|
|
364
|
-
// TodoInit should have title but NOT id
|
|
365
|
-
const initMatch = output.match(/export interface TodoInit \{([^}]+)\}/);
|
|
366
|
-
expect(initMatch).toBeTruthy();
|
|
367
|
-
expect(initMatch[1]).toContain("title: string;");
|
|
368
|
-
expect(initMatch[1]).not.toContain("id:");
|
|
369
|
-
});
|
|
370
|
-
it("handles nullable columns with ?", () => {
|
|
371
|
-
table("todos", {
|
|
372
|
-
title: col.string(),
|
|
373
|
-
description: col.string().optional(),
|
|
374
|
-
});
|
|
375
|
-
const schema = getCollectedSchema();
|
|
376
|
-
const wasm = schemaToWasm(schema);
|
|
377
|
-
const output = generateTypes(wasm);
|
|
378
|
-
expect(output).toContain(" title: string;");
|
|
379
|
-
expect(output).toContain(" description?: string;");
|
|
380
|
-
});
|
|
381
|
-
it("converts snake_case to PascalCase", () => {
|
|
382
|
-
table("user_profiles", { display_name: col.string() });
|
|
383
|
-
const schema = getCollectedSchema();
|
|
384
|
-
const wasm = schemaToWasm(schema);
|
|
385
|
-
const output = generateTypes(wasm);
|
|
386
|
-
expect(output).toContain("export interface UserProfile {");
|
|
387
|
-
expect(output).toContain("export interface UserProfileInit {");
|
|
388
|
-
});
|
|
389
|
-
it("singularises plural table names", () => {
|
|
390
|
-
table("categories", { name: col.string() });
|
|
391
|
-
const schema = getCollectedSchema();
|
|
392
|
-
const wasm = schemaToWasm(schema);
|
|
393
|
-
const output = generateTypes(wasm);
|
|
394
|
-
expect(output).toContain("export interface Category {");
|
|
395
|
-
expect(output).toContain("export interface CategoryInit {");
|
|
396
|
-
});
|
|
397
|
-
it.each([
|
|
398
|
-
["canvases", "Canvas"],
|
|
399
|
-
["statuses", "Status"],
|
|
400
|
-
["buses", "Bus"],
|
|
401
|
-
["processes", "Process"],
|
|
402
|
-
["heroes", "Hero"],
|
|
403
|
-
["vertices", "Vertex"],
|
|
404
|
-
["people", "Person"],
|
|
405
|
-
["matrices", "Matrix"],
|
|
406
|
-
["addresses", "Address"],
|
|
407
|
-
])("singularises %s to %s", (tableName, expected) => {
|
|
408
|
-
table(tableName, { name: col.string() });
|
|
409
|
-
const schema = getCollectedSchema();
|
|
410
|
-
const wasm = schemaToWasm(schema);
|
|
411
|
-
const output = generateTypes(wasm);
|
|
412
|
-
expect(output).toContain(`export interface ${expected} {`);
|
|
413
|
-
expect(output).toContain(`export interface ${expected}Init {`);
|
|
414
|
-
});
|
|
415
|
-
it("maps boolean columns to boolean type", () => {
|
|
416
|
-
table("todos", { done: col.boolean() });
|
|
417
|
-
const schema = getCollectedSchema();
|
|
418
|
-
const wasm = schemaToWasm(schema);
|
|
419
|
-
const output = generateTypes(wasm);
|
|
420
|
-
expect(output).toContain(" done: boolean;");
|
|
421
|
-
});
|
|
422
|
-
it("maps int columns to number type", () => {
|
|
423
|
-
table("items", { count: col.int() });
|
|
424
|
-
const schema = getCollectedSchema();
|
|
425
|
-
const wasm = schemaToWasm(schema);
|
|
426
|
-
const output = generateTypes(wasm);
|
|
427
|
-
expect(output).toContain(" count: number;");
|
|
428
|
-
});
|
|
429
|
-
it("maps timestamp columns to Date type", () => {
|
|
430
|
-
table("items", { created_at: col.timestamp() });
|
|
431
|
-
const schema = getCollectedSchema();
|
|
432
|
-
const wasm = schemaToWasm(schema);
|
|
433
|
-
const output = generateTypes(wasm);
|
|
434
|
-
expect(output).toContain(" created_at: Date;");
|
|
435
|
-
});
|
|
436
|
-
it("maps ref columns to string type", () => {
|
|
437
|
-
table("users", { name: col.string() });
|
|
438
|
-
table("todos", { ownerId: col.ref("users") });
|
|
439
|
-
const schema = getCollectedSchema();
|
|
440
|
-
const wasm = schemaToWasm(schema);
|
|
441
|
-
const output = generateTypes(wasm);
|
|
442
|
-
expect(output).toContain(" ownerId: string;");
|
|
443
|
-
});
|
|
444
|
-
it("maps array columns recursively", () => {
|
|
445
|
-
table("items", {
|
|
446
|
-
tags: col.array(col.string()),
|
|
447
|
-
matrix: col.array(col.array(col.int())),
|
|
448
|
-
});
|
|
449
|
-
const schema = getCollectedSchema();
|
|
450
|
-
const wasm = schemaToWasm(schema);
|
|
451
|
-
const output = generateTypes(wasm);
|
|
452
|
-
expect(output).toContain(" tags: string[];");
|
|
453
|
-
expect(output).toContain(" matrix: number[][];");
|
|
454
|
-
});
|
|
455
|
-
it("maps enum columns to string literal unions", () => {
|
|
456
|
-
table("tasks", { status: col.enum("in_progress", "todo", "done") });
|
|
457
|
-
const schema = getCollectedSchema();
|
|
458
|
-
const wasm = schemaToWasm(schema);
|
|
459
|
-
const output = generateTypes(wasm);
|
|
460
|
-
expect(output).toContain(' status: "done" | "in_progress" | "todo";');
|
|
461
|
-
});
|
|
462
|
-
it("maps JSON columns to JsonValue type", () => {
|
|
463
|
-
table("documents", { payload: col.json() });
|
|
464
|
-
const schema = getCollectedSchema();
|
|
465
|
-
const wasm = schemaToWasm(schema);
|
|
466
|
-
const output = generateTypes(wasm);
|
|
467
|
-
expect(output).toContain("export type JsonValue =");
|
|
468
|
-
expect(output).toContain(" payload: JsonValue;");
|
|
469
|
-
});
|
|
470
|
-
it("narrows JSON columns using schema-derived type aliases", () => {
|
|
471
|
-
table("documents", {
|
|
472
|
-
payload: col.json(z.object({
|
|
473
|
-
name: z.string(),
|
|
474
|
-
done: z.boolean().optional(),
|
|
475
|
-
})),
|
|
476
|
-
});
|
|
477
|
-
const schema = getCollectedSchema();
|
|
478
|
-
const wasm = schemaToWasm(schema);
|
|
479
|
-
const output = generateTypes(wasm);
|
|
480
|
-
expect(output).toContain('import type { WasmSchema, QueryBuilder, JsonSchemaToTs } from "jazz-tools";');
|
|
481
|
-
expect(output).toContain("const __jsonSchema1 =");
|
|
482
|
-
expect(output).toContain("type __JsonType1 = JsonSchemaToTs<typeof __jsonSchema1>;");
|
|
483
|
-
expect(output).toContain(" payload: __JsonType1;");
|
|
484
|
-
expect(output).toContain("payload?: __JsonType1 | { eq?: __JsonType1; ne?: __JsonType1;");
|
|
485
|
-
});
|
|
486
|
-
it("exports wasmSchema constant", () => {
|
|
487
|
-
table("todos", { title: col.string() });
|
|
488
|
-
const schema = getCollectedSchema();
|
|
489
|
-
const wasm = schemaToWasm(schema);
|
|
490
|
-
const output = generateTypes(wasm);
|
|
491
|
-
expect(output).toContain("export const wasmSchema: WasmSchema = {");
|
|
492
|
-
expect(output).toContain('"todos"');
|
|
493
|
-
expect(output).toContain('"columns"');
|
|
494
|
-
});
|
|
495
|
-
it("imports WasmSchema and QueryBuilder from jazz-tools", () => {
|
|
496
|
-
table("todos", { title: col.string() });
|
|
497
|
-
const schema = getCollectedSchema();
|
|
498
|
-
const wasm = schemaToWasm(schema);
|
|
499
|
-
const output = generateTypes(wasm);
|
|
500
|
-
expect(output).toContain('import type { WasmSchema, QueryBuilder } from "jazz-tools";');
|
|
501
|
-
});
|
|
502
|
-
it("includes auto-generated header comment", () => {
|
|
503
|
-
table("todos", { title: col.string() });
|
|
504
|
-
const schema = getCollectedSchema();
|
|
505
|
-
const wasm = schemaToWasm(schema);
|
|
506
|
-
const output = generateTypes(wasm);
|
|
507
|
-
expect(output).toContain("// AUTO-GENERATED FILE - DO NOT EDIT");
|
|
508
|
-
});
|
|
509
|
-
});
|
|
510
|
-
describe("generateClient", () => {
|
|
511
|
-
beforeEach(() => {
|
|
512
|
-
resetCollectedState();
|
|
513
|
-
});
|
|
514
|
-
it("produces complete output for todos example", () => {
|
|
515
|
-
table("todos", {
|
|
516
|
-
title: col.string(),
|
|
517
|
-
done: col.boolean(),
|
|
518
|
-
parentId: col.ref("todos").optional(),
|
|
519
|
-
});
|
|
520
|
-
const schema = getCollectedSchema();
|
|
521
|
-
const output = generateClient(schema);
|
|
522
|
-
// Header
|
|
523
|
-
expect(output).toContain("// AUTO-GENERATED FILE - DO NOT EDIT");
|
|
524
|
-
expect(output).toContain('import type { WasmSchema, QueryBuilder } from "jazz-tools";');
|
|
525
|
-
// Base interface
|
|
526
|
-
expect(output).toContain("export interface Todo {");
|
|
527
|
-
expect(output).toContain(" id: string;");
|
|
528
|
-
expect(output).toContain(" title: string;");
|
|
529
|
-
expect(output).toContain(" done: boolean;");
|
|
530
|
-
expect(output).toContain(" parentId?: string;");
|
|
531
|
-
// Init interface
|
|
532
|
-
expect(output).toContain("export interface TodoInit {");
|
|
533
|
-
const initMatch = output.match(/export interface TodoInit \{([^}]+)\}/);
|
|
534
|
-
expect(initMatch).toBeTruthy();
|
|
535
|
-
expect(initMatch[1]).not.toContain("id:");
|
|
536
|
-
// wasmSchema export
|
|
537
|
-
expect(output).toContain("export const wasmSchema: WasmSchema =");
|
|
538
|
-
expect(output).toContain('"type": "Text"');
|
|
539
|
-
expect(output).toContain('"type": "Boolean"');
|
|
540
|
-
expect(output).toContain('"type": "Uuid"');
|
|
541
|
-
expect(output).toContain('"references": "todos"');
|
|
542
|
-
});
|
|
543
|
-
});
|
|
544
|
-
describe("analyzeRelations", () => {
|
|
545
|
-
it("derives forward relations from references", () => {
|
|
546
|
-
const schema = {
|
|
547
|
-
todos: {
|
|
548
|
-
columns: [
|
|
549
|
-
{
|
|
550
|
-
name: "owner_id",
|
|
551
|
-
column_type: { type: "Uuid" },
|
|
552
|
-
nullable: false,
|
|
553
|
-
references: "users",
|
|
554
|
-
},
|
|
555
|
-
],
|
|
556
|
-
},
|
|
557
|
-
users: { columns: [] },
|
|
558
|
-
};
|
|
559
|
-
const relations = analyzeRelations(schema);
|
|
560
|
-
const todoRels = relations.get("todos");
|
|
561
|
-
expect(todoRels).toContainEqual(expect.objectContaining({
|
|
562
|
-
name: "owner",
|
|
563
|
-
type: "forward",
|
|
564
|
-
toTable: "users",
|
|
565
|
-
isArray: false,
|
|
566
|
-
nullable: false,
|
|
567
|
-
}));
|
|
568
|
-
});
|
|
569
|
-
it("derives reverse relations", () => {
|
|
570
|
-
const schema = {
|
|
571
|
-
todos: {
|
|
572
|
-
columns: [
|
|
573
|
-
{
|
|
574
|
-
name: "owner_id",
|
|
575
|
-
column_type: { type: "Uuid" },
|
|
576
|
-
nullable: false,
|
|
577
|
-
references: "users",
|
|
578
|
-
},
|
|
579
|
-
],
|
|
580
|
-
},
|
|
581
|
-
users: { columns: [] },
|
|
582
|
-
};
|
|
583
|
-
const relations = analyzeRelations(schema);
|
|
584
|
-
const userRels = relations.get("users");
|
|
585
|
-
expect(userRels).toContainEqual(expect.objectContaining({
|
|
586
|
-
name: "todosViaOwner",
|
|
587
|
-
type: "reverse",
|
|
588
|
-
toTable: "todos",
|
|
589
|
-
isArray: true,
|
|
590
|
-
}));
|
|
591
|
-
});
|
|
592
|
-
it("marks forward UUID[] references as array relations", () => {
|
|
593
|
-
const schema = {
|
|
594
|
-
files: {
|
|
595
|
-
columns: [
|
|
596
|
-
{
|
|
597
|
-
name: "parts",
|
|
598
|
-
column_type: { type: "Array", element: { type: "Uuid" } },
|
|
599
|
-
nullable: false,
|
|
600
|
-
references: "file_parts",
|
|
601
|
-
},
|
|
602
|
-
],
|
|
603
|
-
},
|
|
604
|
-
file_parts: { columns: [] },
|
|
605
|
-
};
|
|
606
|
-
const relations = analyzeRelations(schema);
|
|
607
|
-
const fileRels = relations.get("files");
|
|
608
|
-
expect(fileRels).toContainEqual(expect.objectContaining({
|
|
609
|
-
name: "parts",
|
|
610
|
-
type: "forward",
|
|
611
|
-
toTable: "file_parts",
|
|
612
|
-
isArray: true,
|
|
613
|
-
}));
|
|
614
|
-
});
|
|
615
|
-
it("keeps singular array reference columns pluralized", () => {
|
|
616
|
-
const schema = {
|
|
617
|
-
files: {
|
|
618
|
-
columns: [
|
|
619
|
-
{
|
|
620
|
-
name: "partIds",
|
|
621
|
-
column_type: { type: "Array", element: { type: "Uuid" } },
|
|
622
|
-
nullable: false,
|
|
623
|
-
references: "file_parts",
|
|
624
|
-
},
|
|
625
|
-
],
|
|
626
|
-
},
|
|
627
|
-
file_parts: { columns: [] },
|
|
628
|
-
};
|
|
629
|
-
const relations = analyzeRelations(schema);
|
|
630
|
-
expect(relations.get("files")).toContainEqual(expect.objectContaining({
|
|
631
|
-
name: "parts",
|
|
632
|
-
type: "forward",
|
|
633
|
-
toTable: "file_parts",
|
|
634
|
-
fromColumn: "partIds",
|
|
635
|
-
isArray: true,
|
|
636
|
-
}));
|
|
637
|
-
expect(relations.get("file_parts")).toContainEqual(expect.objectContaining({
|
|
638
|
-
name: "filesViaParts",
|
|
639
|
-
type: "reverse",
|
|
640
|
-
toTable: "files",
|
|
641
|
-
toColumn: "partIds",
|
|
642
|
-
isArray: true,
|
|
643
|
-
}));
|
|
644
|
-
});
|
|
645
|
-
it("keeps already-plural array reference columns pluralized", () => {
|
|
646
|
-
const schema = {
|
|
647
|
-
groups: {
|
|
648
|
-
columns: [
|
|
649
|
-
{
|
|
650
|
-
name: "assigneesIds",
|
|
651
|
-
column_type: { type: "Array", element: { type: "Uuid" } },
|
|
652
|
-
nullable: false,
|
|
653
|
-
references: "users",
|
|
654
|
-
},
|
|
655
|
-
],
|
|
656
|
-
},
|
|
657
|
-
users: { columns: [] },
|
|
658
|
-
};
|
|
659
|
-
const relations = analyzeRelations(schema);
|
|
660
|
-
expect(relations.get("groups")).toContainEqual(expect.objectContaining({
|
|
661
|
-
name: "assignees",
|
|
662
|
-
type: "forward",
|
|
663
|
-
toTable: "users",
|
|
664
|
-
fromColumn: "assigneesIds",
|
|
665
|
-
isArray: true,
|
|
666
|
-
}));
|
|
667
|
-
expect(relations.get("users")).toContainEqual(expect.objectContaining({
|
|
668
|
-
name: "groupsViaAssignees",
|
|
669
|
-
type: "reverse",
|
|
670
|
-
toTable: "groups",
|
|
671
|
-
toColumn: "assigneesIds",
|
|
672
|
-
isArray: true,
|
|
673
|
-
}));
|
|
674
|
-
});
|
|
675
|
-
it("handles self-referential relations", () => {
|
|
676
|
-
const schema = {
|
|
677
|
-
todos: {
|
|
678
|
-
columns: [
|
|
679
|
-
{
|
|
680
|
-
name: "parent_id",
|
|
681
|
-
column_type: { type: "Uuid" },
|
|
682
|
-
nullable: true,
|
|
683
|
-
references: "todos",
|
|
684
|
-
},
|
|
685
|
-
],
|
|
686
|
-
},
|
|
687
|
-
};
|
|
688
|
-
const relations = analyzeRelations(schema);
|
|
689
|
-
const todoRels = relations.get("todos");
|
|
690
|
-
// Forward: parent
|
|
691
|
-
expect(todoRels).toContainEqual(expect.objectContaining({ name: "parent", type: "forward", nullable: true }));
|
|
692
|
-
// Reverse: todosViaParent
|
|
693
|
-
expect(todoRels).toContainEqual(expect.objectContaining({ name: "todosViaParent", type: "reverse", isArray: true }));
|
|
694
|
-
});
|
|
695
|
-
it("handles multiple relations on same table", () => {
|
|
696
|
-
const schema = {
|
|
697
|
-
todos: {
|
|
698
|
-
columns: [
|
|
699
|
-
{
|
|
700
|
-
name: "owner_id",
|
|
701
|
-
column_type: { type: "Uuid" },
|
|
702
|
-
nullable: false,
|
|
703
|
-
references: "users",
|
|
704
|
-
},
|
|
705
|
-
{
|
|
706
|
-
name: "assignee_id",
|
|
707
|
-
column_type: { type: "Uuid" },
|
|
708
|
-
nullable: true,
|
|
709
|
-
references: "users",
|
|
710
|
-
},
|
|
711
|
-
],
|
|
712
|
-
},
|
|
713
|
-
users: { columns: [] },
|
|
714
|
-
};
|
|
715
|
-
const relations = analyzeRelations(schema);
|
|
716
|
-
const todoRels = relations.get("todos");
|
|
717
|
-
const userRels = relations.get("users");
|
|
718
|
-
// Forward relations on todos
|
|
719
|
-
expect(todoRels).toContainEqual(expect.objectContaining({ name: "owner" }));
|
|
720
|
-
expect(todoRels).toContainEqual(expect.objectContaining({ name: "assignee" }));
|
|
721
|
-
// Reverse relations on users
|
|
722
|
-
expect(userRels).toContainEqual(expect.objectContaining({ name: "todosViaOwner" }));
|
|
723
|
-
expect(userRels).toContainEqual(expect.objectContaining({ name: "todosViaAssignee" }));
|
|
724
|
-
});
|
|
725
|
-
it("throws error when referencing unknown table", () => {
|
|
726
|
-
const schema = {
|
|
727
|
-
todos: {
|
|
728
|
-
columns: [
|
|
729
|
-
{
|
|
730
|
-
name: "owner_id",
|
|
731
|
-
column_type: { type: "Uuid" },
|
|
732
|
-
nullable: false,
|
|
733
|
-
references: "users",
|
|
734
|
-
},
|
|
735
|
-
],
|
|
736
|
-
},
|
|
737
|
-
// Note: "users" table is NOT defined
|
|
738
|
-
};
|
|
739
|
-
expect(() => analyzeRelations(schema)).toThrow('Table "todos" references unknown table "users" via column "owner_id"');
|
|
740
|
-
});
|
|
741
|
-
it("throws for non-UUID references", () => {
|
|
742
|
-
const schema = {
|
|
743
|
-
files: {
|
|
744
|
-
columns: [
|
|
745
|
-
{
|
|
746
|
-
name: "parts",
|
|
747
|
-
column_type: { type: "Array", element: { type: "Text" } },
|
|
748
|
-
nullable: false,
|
|
749
|
-
references: "file_parts",
|
|
750
|
-
},
|
|
751
|
-
],
|
|
752
|
-
},
|
|
753
|
-
file_parts: { columns: [] },
|
|
754
|
-
};
|
|
755
|
-
expect(() => analyzeRelations(schema)).toThrow('Column "files.parts" uses references but is not UUID or UUID[]');
|
|
756
|
-
});
|
|
757
|
-
});
|
|
758
|
-
describe("generateTypes with relations", () => {
|
|
759
|
-
beforeEach(() => {
|
|
760
|
-
resetCollectedState();
|
|
761
|
-
});
|
|
762
|
-
it("generates Include types", () => {
|
|
763
|
-
table("todos", { ownerId: col.ref("users") });
|
|
764
|
-
table("users", { name: col.string() });
|
|
765
|
-
const schema = getCollectedSchema();
|
|
766
|
-
const wasm = schemaToWasm(schema);
|
|
767
|
-
const output = generateTypes(wasm);
|
|
768
|
-
expect(output).toContain('type AnyUserQueryBuilder<T = any> = { readonly _table: "users" } & QueryBuilder<T>;');
|
|
769
|
-
expect(output).toContain("export interface TodoInclude {");
|
|
770
|
-
// Include types now include QueryBuilder union and only allow `true` for flags
|
|
771
|
-
expect(output).toContain("owner?: true | UserInclude | AnyUserQueryBuilder<any>;");
|
|
772
|
-
});
|
|
773
|
-
it("generates Relations types", () => {
|
|
774
|
-
table("todos", { ownerId: col.ref("users") });
|
|
775
|
-
table("users", { name: col.string() });
|
|
776
|
-
const schema = getCollectedSchema();
|
|
777
|
-
const wasm = schemaToWasm(schema);
|
|
778
|
-
const output = generateTypes(wasm);
|
|
779
|
-
expect(output).toContain("export interface TodoRelations {");
|
|
780
|
-
expect(output).toContain("owner: User | undefined;");
|
|
781
|
-
});
|
|
782
|
-
it("generates reverse relations as arrays", () => {
|
|
783
|
-
table("todos", { ownerId: col.ref("users") });
|
|
784
|
-
table("users", { name: col.string() });
|
|
785
|
-
const schema = getCollectedSchema();
|
|
786
|
-
const wasm = schemaToWasm(schema);
|
|
787
|
-
const output = generateTypes(wasm);
|
|
788
|
-
expect(output).toContain("export interface UserRelations {");
|
|
789
|
-
expect(output).toContain("todosViaOwner: Todo[];");
|
|
790
|
-
});
|
|
791
|
-
it("generates WithIncludes types", () => {
|
|
792
|
-
table("todos", { ownerId: col.ref("users") });
|
|
793
|
-
table("users", { name: col.string() });
|
|
794
|
-
const schema = getCollectedSchema();
|
|
795
|
-
const wasm = schemaToWasm(schema);
|
|
796
|
-
const output = generateTypes(wasm);
|
|
797
|
-
expect(output).toContain("export type TodoIncludedRelations<I extends TodoInclude = {}, R extends boolean = false> = {");
|
|
798
|
-
expect(output).toContain("export type TodoWithIncludes<I extends TodoInclude = {}, R extends boolean = false> = Todo & TodoIncludedRelations<I, R>;");
|
|
799
|
-
expect(output).toContain("export type UserWithIncludes<I extends UserInclude = {}, R extends boolean = false> = User & UserIncludedRelations<I, R>;");
|
|
800
|
-
expect(output).toContain("[K in keyof I]-?:");
|
|
801
|
-
expect(output).toContain('K extends "owner"');
|
|
802
|
-
expect(output).toContain('NonNullable<I["owner"]> extends infer RelationInclude');
|
|
803
|
-
expect(output).toContain("? RelationInclude extends true");
|
|
804
|
-
expect(output).toContain("? R extends true ? User : User | undefined");
|
|
805
|
-
expect(output).toContain(": RelationInclude extends AnyUserQueryBuilder<infer QueryRow>");
|
|
806
|
-
expect(output).toContain("? R extends true ? QueryRow : QueryRow | undefined");
|
|
807
|
-
expect(output).toContain(": RelationInclude extends UserInclude");
|
|
808
|
-
expect(output).toContain("? R extends true ? UserWithIncludes<RelationInclude, false> : UserWithIncludes<RelationInclude, false> | undefined");
|
|
809
|
-
expect(output).toContain('K extends "todosViaOwner"');
|
|
810
|
-
expect(output).toContain("? Todo[]");
|
|
811
|
-
expect(output).toContain(": RelationInclude extends AnyTodoQueryBuilder<infer QueryRow>");
|
|
812
|
-
expect(output).toContain("? QueryRow[]");
|
|
813
|
-
expect(output).toContain(": RelationInclude extends TodoInclude");
|
|
814
|
-
expect(output).toContain("? TodoWithIncludes<RelationInclude, false>[]");
|
|
815
|
-
expect(output).not.toContain("WithIncludesFor<");
|
|
816
|
-
expect(output).not.toContain("WithIncludesArray<");
|
|
817
|
-
});
|
|
818
|
-
it("preserves undefined for nullable forward includes", () => {
|
|
819
|
-
table("users", { name: col.string() });
|
|
820
|
-
table("todos", { ownerId: col.ref("users").optional() });
|
|
821
|
-
const schema = getCollectedSchema();
|
|
822
|
-
const wasm = schemaToWasm(schema);
|
|
823
|
-
const output = generateTypes(wasm);
|
|
824
|
-
expect(output).toContain("? User | undefined");
|
|
825
|
-
expect(output).toContain("? QueryRow | undefined");
|
|
826
|
-
expect(output).toContain("? UserWithIncludes<RelationInclude, false> | undefined");
|
|
827
|
-
});
|
|
828
|
-
it("preserves undefined for nullable forward array includes", () => {
|
|
829
|
-
table("users", { name: col.string() });
|
|
830
|
-
table("groups", { member_ids: col.array(col.ref("users")).optional() });
|
|
831
|
-
const schema = getCollectedSchema();
|
|
832
|
-
const wasm = schemaToWasm(schema);
|
|
833
|
-
const output = generateTypes(wasm);
|
|
834
|
-
expect(output).toContain("? User[] | undefined");
|
|
835
|
-
expect(output).toContain("? QueryRow[] | undefined");
|
|
836
|
-
expect(output).toContain("? UserWithIncludes<RelationInclude, false>[] | undefined");
|
|
837
|
-
});
|
|
838
|
-
it("uses pluralized relation names for array ref columns ending in Ids", () => {
|
|
839
|
-
table("file_parts", { data: col.bytes() });
|
|
840
|
-
table("files", {
|
|
841
|
-
partIds: col.array(col.ref("file_parts")),
|
|
842
|
-
});
|
|
843
|
-
const schema = getCollectedSchema();
|
|
844
|
-
const wasm = schemaToWasm(schema);
|
|
845
|
-
const output = generateTypes(wasm);
|
|
846
|
-
expect(output).toContain("export interface FileInclude {");
|
|
847
|
-
expect(output).toContain("parts?: true | FilePartInclude | AnyFilePartQueryBuilder<any>;");
|
|
848
|
-
expect(output).toContain("export interface FilePartInclude {");
|
|
849
|
-
expect(output).toContain("filesViaParts?: true | FileInclude | AnyFileQueryBuilder<any>;");
|
|
850
|
-
expect(output).not.toContain("part?: true | FilePartInclude | AnyFilePartQueryBuilder<any>;");
|
|
851
|
-
expect(output).not.toContain("filesViaPart?: true | FileInclude | AnyFileQueryBuilder<any>;");
|
|
852
|
-
});
|
|
853
|
-
it("generates selection helper types", () => {
|
|
854
|
-
table("users", { name: col.string() });
|
|
855
|
-
table("todos", { ownerId: col.ref("users"), title: col.string() });
|
|
856
|
-
const schema = getCollectedSchema();
|
|
857
|
-
const wasm = schemaToWasm(schema);
|
|
858
|
-
const output = generateTypes(wasm);
|
|
859
|
-
expect(output).toContain('export type TodoSelectableColumn = keyof Todo | PermissionIntrospectionColumn | "*"');
|
|
860
|
-
expect(output).toContain("export type TodoOrderableColumn = keyof Todo | PermissionIntrospectionColumn");
|
|
861
|
-
expect(output).toContain("export interface PermissionIntrospectionColumns {");
|
|
862
|
-
expect(output).toContain(" $canRead: boolean | null;");
|
|
863
|
-
expect(output).toContain(" $canEdit: boolean | null;");
|
|
864
|
-
expect(output).toContain(" $canDelete: boolean | null;");
|
|
865
|
-
expect(output).toContain("export type TodoSelected<S extends TodoSelectableColumn = keyof Todo>");
|
|
866
|
-
expect(output).toContain('("*" extends S ? Todo : Pick<Todo, Extract<S | "id", keyof Todo>>) & Pick<PermissionIntrospectionColumns, Extract<S, PermissionIntrospectionColumn>>');
|
|
867
|
-
expect(output).toContain("export type TodoSelectedWithIncludes<I extends TodoInclude = {}, S extends TodoSelectableColumn = keyof Todo, R extends boolean = false>");
|
|
868
|
-
expect(output).toContain("TodoSelected<S> & TodoIncludedRelations<I, R>");
|
|
869
|
-
});
|
|
870
|
-
it("avoids collapsing nested array includes to never when selectors are optional", () => {
|
|
871
|
-
table("teams", { legacyId: col.string() });
|
|
872
|
-
table("resources", { kind: col.enum("branding") });
|
|
873
|
-
table("resource_access_edges", {
|
|
874
|
-
resourceId: col.ref("resources"),
|
|
875
|
-
teamId: col.ref("teams"),
|
|
876
|
-
grant_role: col.enum("viewer", "editor", "manager"),
|
|
877
|
-
});
|
|
878
|
-
table("brandings", {
|
|
879
|
-
resourceId: col.ref("resources"),
|
|
880
|
-
name: col.string(),
|
|
881
|
-
});
|
|
882
|
-
const schema = getCollectedSchema();
|
|
883
|
-
const wasm = schemaToWasm(schema);
|
|
884
|
-
const output = generateTypes(wasm);
|
|
885
|
-
expect(output).toContain('K extends "resource_access_edgesViaResource"');
|
|
886
|
-
expect(output).toContain("? ResourceAccessEdgeWithIncludes<RelationInclude, false>[]");
|
|
887
|
-
expect(output).not.toContain('resource_access_edgesViaResource?: I["resource_access_edgesViaResource"] extends true');
|
|
888
|
-
});
|
|
889
|
-
it("generates Include types for self-referential tables", () => {
|
|
890
|
-
table("todos", {
|
|
891
|
-
title: col.string(),
|
|
892
|
-
parentId: col.ref("todos").optional(),
|
|
893
|
-
});
|
|
894
|
-
const schema = getCollectedSchema();
|
|
895
|
-
const wasm = schemaToWasm(schema);
|
|
896
|
-
const output = generateTypes(wasm);
|
|
897
|
-
expect(output).toContain("export interface TodoInclude {");
|
|
898
|
-
// Include types now include QueryBuilder union and only allow `true` for flags
|
|
899
|
-
expect(output).toContain("parent?: true | TodoInclude | AnyTodoQueryBuilder<any>;");
|
|
900
|
-
expect(output).toContain("todosViaParent?: true | TodoInclude | AnyTodoQueryBuilder<any>;");
|
|
901
|
-
});
|
|
902
|
-
it("does not generate relation types for tables without relations", () => {
|
|
903
|
-
table("items", { name: col.string() });
|
|
904
|
-
const schema = getCollectedSchema();
|
|
905
|
-
const wasm = schemaToWasm(schema);
|
|
906
|
-
const output = generateTypes(wasm);
|
|
907
|
-
// Should still have base and init types
|
|
908
|
-
expect(output).toContain("export interface Item {");
|
|
909
|
-
expect(output).toContain("export interface ItemInit {");
|
|
910
|
-
// Should NOT have Include/Relations/WithIncludes since no relations
|
|
911
|
-
expect(output).not.toContain("export interface ItemInclude {");
|
|
912
|
-
expect(output).not.toContain("export interface ItemRelations {");
|
|
913
|
-
expect(output).not.toContain("export type ItemWithIncludes");
|
|
914
|
-
});
|
|
915
|
-
});
|
|
916
|
-
describe("generateWhereInputTypes", () => {
|
|
917
|
-
beforeEach(() => {
|
|
918
|
-
resetCollectedState();
|
|
919
|
-
});
|
|
920
|
-
it("generates WhereInput types for basic columns", () => {
|
|
921
|
-
table("todos", { title: col.string(), done: col.boolean(), priority: col.int() });
|
|
922
|
-
const schema = getCollectedSchema();
|
|
923
|
-
const wasm = schemaToWasm(schema);
|
|
924
|
-
const output = generateTypes(wasm);
|
|
925
|
-
expect(output).toContain("export interface TodoWhereInput {");
|
|
926
|
-
expect(output).toContain("title?: string | { eq?: string; ne?: string; contains?: string };");
|
|
927
|
-
expect(output).toContain("done?: boolean;");
|
|
928
|
-
expect(output).toContain("priority?: number | { eq?: number; ne?: number; gt?: number; gte?: number; lt?: number; lte?: number };");
|
|
929
|
-
});
|
|
930
|
-
it("generates Date-oriented WhereInput for timestamp columns", () => {
|
|
931
|
-
table("todos", { created_at: col.timestamp() });
|
|
932
|
-
const schema = getCollectedSchema();
|
|
933
|
-
const wasm = schemaToWasm(schema);
|
|
934
|
-
const output = generateTypes(wasm);
|
|
935
|
-
expect(output).toContain("created_at?: Date | number | { eq?: Date | number; gt?: Date | number; gte?: Date | number; lt?: Date | number; lte?: Date | number };");
|
|
936
|
-
});
|
|
937
|
-
it("generates id filter with in operator", () => {
|
|
938
|
-
table("todos", { title: col.string() });
|
|
939
|
-
const schema = getCollectedSchema();
|
|
940
|
-
const wasm = schemaToWasm(schema);
|
|
941
|
-
const output = generateTypes(wasm);
|
|
942
|
-
expect(output).toContain("id?: string | { eq?: string; ne?: string; in?: string[] };");
|
|
943
|
-
});
|
|
944
|
-
it("generates FK filter with isNull for nullable refs", () => {
|
|
945
|
-
table("users", { name: col.string() });
|
|
946
|
-
table("todos", { ownerId: col.ref("users").optional() });
|
|
947
|
-
const schema = getCollectedSchema();
|
|
948
|
-
const wasm = schemaToWasm(schema);
|
|
949
|
-
const output = generateTypes(wasm);
|
|
950
|
-
expect(output).toContain("ownerId?: string | { eq?: string; ne?: string; isNull?: boolean };");
|
|
951
|
-
});
|
|
952
|
-
it("generates FK filter without isNull for required refs", () => {
|
|
953
|
-
table("users", { name: col.string() });
|
|
954
|
-
table("todos", { ownerId: col.ref("users") });
|
|
955
|
-
const schema = getCollectedSchema();
|
|
956
|
-
const wasm = schemaToWasm(schema);
|
|
957
|
-
const output = generateTypes(wasm);
|
|
958
|
-
expect(output).toContain("ownerId?: string | { eq?: string; ne?: string };");
|
|
959
|
-
});
|
|
960
|
-
it("generates array filters with eq and contains", () => {
|
|
961
|
-
table("todos", { tags: col.array(col.string()) });
|
|
962
|
-
const schema = getCollectedSchema();
|
|
963
|
-
const wasm = schemaToWasm(schema);
|
|
964
|
-
const output = generateTypes(wasm);
|
|
965
|
-
expect(output).toContain("tags?: string[] | { eq?: string[]; contains?: string };");
|
|
966
|
-
});
|
|
967
|
-
it("generates enum filters with eq/ne/in", () => {
|
|
968
|
-
table("tasks", { status: col.enum("in_progress", "todo", "done") });
|
|
969
|
-
const schema = getCollectedSchema();
|
|
970
|
-
const wasm = schemaToWasm(schema);
|
|
971
|
-
const output = generateTypes(wasm);
|
|
972
|
-
expect(output).toContain('status?: "done" | "in_progress" | "todo" | { eq?: "done" | "in_progress" | "todo"; ne?: "done" | "in_progress" | "todo"; in?: ("done" | "in_progress" | "todo")[] };');
|
|
973
|
-
});
|
|
974
|
-
});
|
|
975
|
-
describe("generateQueryBuilderClasses", () => {
|
|
976
|
-
beforeEach(() => {
|
|
977
|
-
resetCollectedState();
|
|
978
|
-
});
|
|
979
|
-
it("generates QueryBuilder classes", () => {
|
|
980
|
-
table("todos", { title: col.string() });
|
|
981
|
-
const schema = getCollectedSchema();
|
|
982
|
-
const wasm = schemaToWasm(schema);
|
|
983
|
-
const output = generateTypes(wasm);
|
|
984
|
-
expect(output).toContain("export class TodoQueryBuilder<I extends Record<string, never> = {}, S extends TodoSelectableColumn = keyof Todo, R extends boolean = false> implements QueryBuilder<TodoSelected<S>> {");
|
|
985
|
-
expect(output).toContain("readonly _rowType!: TodoSelected<S>;");
|
|
986
|
-
expect(output).toContain("readonly _initType!: TodoInit;");
|
|
987
|
-
expect(output).toContain("where(conditions: TodoWhereInput)");
|
|
988
|
-
expect(output).toContain("select<NewS extends TodoSelectableColumn>(...columns: [NewS, ...NewS[]]): TodoQueryBuilder<I, NewS, R>");
|
|
989
|
-
expect(output).toContain("orderBy(column: TodoOrderableColumn");
|
|
990
|
-
expect(output).toContain("limit(n: number)");
|
|
991
|
-
expect(output).toContain("offset(n: number)");
|
|
992
|
-
expect(output).toContain("gather(options: {");
|
|
993
|
-
expect(output).toContain("_build(): string");
|
|
994
|
-
});
|
|
995
|
-
it("generates QueryBuilder with Include constraint for tables with relations", () => {
|
|
996
|
-
table("users", { name: col.string() });
|
|
997
|
-
table("todos", { ownerId: col.ref("users") });
|
|
998
|
-
const schema = getCollectedSchema();
|
|
999
|
-
const wasm = schemaToWasm(schema);
|
|
1000
|
-
const output = generateTypes(wasm);
|
|
1001
|
-
expect(output).toContain("export class TodoQueryBuilder<I extends TodoInclude = {}, S extends TodoSelectableColumn = keyof Todo, R extends boolean = false> implements QueryBuilder<TodoSelectedWithIncludes<I, S, R>> {");
|
|
1002
|
-
expect(output).toContain("readonly _rowType!: TodoSelectedWithIncludes<I, S, R>;");
|
|
1003
|
-
});
|
|
1004
|
-
it("generates include method for tables with relations", () => {
|
|
1005
|
-
table("users", { name: col.string() });
|
|
1006
|
-
table("todos", { ownerId: col.ref("users") });
|
|
1007
|
-
const schema = getCollectedSchema();
|
|
1008
|
-
const wasm = schemaToWasm(schema);
|
|
1009
|
-
const output = generateTypes(wasm);
|
|
1010
|
-
expect(output).toContain("include<NewI extends TodoInclude>(relations: NewI)");
|
|
1011
|
-
expect(output).toContain("const clone = this._clone<I & NewI, S, R>();");
|
|
1012
|
-
expect(output).toContain("requireIncludes(): TodoQueryBuilder<I, S, true> {");
|
|
1013
|
-
expect(output).toContain("const clone = this._clone<I, S, true>();");
|
|
1014
|
-
expect(output).toContain("clone._requireIncludes = true;");
|
|
1015
|
-
expect(output).not.toContain("as unknown as TodoQueryBuilder<I & NewI>");
|
|
1016
|
-
});
|
|
1017
|
-
it("generates hopTo method for tables with relations", () => {
|
|
1018
|
-
table("users", { name: col.string() });
|
|
1019
|
-
table("todos", { ownerId: col.ref("users") });
|
|
1020
|
-
const schema = getCollectedSchema();
|
|
1021
|
-
const wasm = schemaToWasm(schema);
|
|
1022
|
-
const output = generateTypes(wasm);
|
|
1023
|
-
expect(output).toContain('hopTo(relation: "owner")');
|
|
1024
|
-
});
|
|
1025
|
-
it("does not generate include method for tables without relations", () => {
|
|
1026
|
-
table("items", { name: col.string() });
|
|
1027
|
-
const schema = getCollectedSchema();
|
|
1028
|
-
const wasm = schemaToWasm(schema);
|
|
1029
|
-
const output = generateTypes(wasm);
|
|
1030
|
-
// ItemQueryBuilder should exist
|
|
1031
|
-
expect(output).toContain("export class ItemQueryBuilder");
|
|
1032
|
-
// But should not have include method - look for the specific signature
|
|
1033
|
-
const itemQueryBuilderMatch = output.match(/export class ItemQueryBuilder[\s\S]*?^}/m);
|
|
1034
|
-
expect(itemQueryBuilderMatch).toBeTruthy();
|
|
1035
|
-
expect(itemQueryBuilderMatch[0]).not.toContain("include<NewI extends");
|
|
1036
|
-
});
|
|
1037
|
-
it("updates Include types with QueryBuilder union", () => {
|
|
1038
|
-
table("users", { name: col.string() });
|
|
1039
|
-
table("todos", { ownerId: col.ref("users") });
|
|
1040
|
-
const schema = getCollectedSchema();
|
|
1041
|
-
const wasm = schemaToWasm(schema);
|
|
1042
|
-
const output = generateTypes(wasm);
|
|
1043
|
-
expect(output).toContain("owner?: true | UserInclude | AnyUserQueryBuilder<any>;");
|
|
1044
|
-
});
|
|
1045
|
-
it("QueryBuilder._build() returns valid JSON structure", () => {
|
|
1046
|
-
table("todos", { title: col.string(), done: col.boolean() });
|
|
1047
|
-
const schema = getCollectedSchema();
|
|
1048
|
-
const wasm = schemaToWasm(schema);
|
|
1049
|
-
const output = generateTypes(wasm);
|
|
1050
|
-
// Verify _build method structure exists
|
|
1051
|
-
expect(output).toContain("_build(): string {");
|
|
1052
|
-
expect(output).toContain("return JSON.stringify({");
|
|
1053
|
-
expect(output).toContain("table: this._table,");
|
|
1054
|
-
expect(output).toContain("conditions: this._conditions,");
|
|
1055
|
-
expect(output).toContain("includes: this._includes,");
|
|
1056
|
-
expect(output).toContain("select: this._selectColumns,");
|
|
1057
|
-
expect(output).toContain("orderBy: this._orderBys,");
|
|
1058
|
-
expect(output).toContain("limit: this._limitVal,");
|
|
1059
|
-
expect(output).toContain("offset: this._offsetVal,");
|
|
1060
|
-
expect(output).toContain("hops: this._hops,");
|
|
1061
|
-
expect(output).toContain("gather: this._gatherVal,");
|
|
1062
|
-
expect(output).toContain("toJSON(): unknown {");
|
|
1063
|
-
expect(output).toContain("return JSON.parse(this._build());");
|
|
1064
|
-
});
|
|
1065
|
-
it("generates private _clone method for immutability", () => {
|
|
1066
|
-
table("todos", { title: col.string() });
|
|
1067
|
-
const schema = getCollectedSchema();
|
|
1068
|
-
const wasm = schemaToWasm(schema);
|
|
1069
|
-
const output = generateTypes(wasm);
|
|
1070
|
-
expect(output).toContain("private _clone<CloneI extends Record<string, never> = I, CloneS extends TodoSelectableColumn = S, CloneR extends boolean = R>(): TodoQueryBuilder<CloneI, CloneS, CloneR> {");
|
|
1071
|
-
expect(output).toContain("const clone = new TodoQueryBuilder<CloneI, CloneS, CloneR>();");
|
|
1072
|
-
expect(output).toContain("clone._conditions = [...this._conditions];");
|
|
1073
|
-
expect(output).toContain("clone._selectColumns = this._selectColumns ? [...this._selectColumns] : undefined;");
|
|
1074
|
-
expect(output).toContain("clone._hops = [...this._hops];");
|
|
1075
|
-
expect(output).toContain("clone._gatherVal = this._gatherVal");
|
|
1076
|
-
});
|
|
1077
|
-
it("generates gather helper that compiles start + step", () => {
|
|
1078
|
-
table("todos", { title: col.string(), parentId: col.ref("todos").optional() });
|
|
1079
|
-
const schema = getCollectedSchema();
|
|
1080
|
-
const wasm = schemaToWasm(schema);
|
|
1081
|
-
const output = generateTypes(wasm);
|
|
1082
|
-
expect(output).toContain("gather(options: {");
|
|
1083
|
-
expect(output).toContain("step: (ctx: { current: string }) => QueryBuilder<unknown>;");
|
|
1084
|
-
expect(output).toContain("const stepOutput = options.step({ current: currentToken });");
|
|
1085
|
-
expect(output).toContain("if (stepHops.length !== 1) {");
|
|
1086
|
-
expect(output).toContain("if (currentCondition === undefined) {");
|
|
1087
|
-
expect(output).toContain("const withStart = this.where(options.start);");
|
|
1088
|
-
expect(output).toContain("clone._gatherVal = {");
|
|
1089
|
-
});
|
|
1090
|
-
});
|
|
1091
|
-
describe("generateAppExport", () => {
|
|
1092
|
-
beforeEach(() => {
|
|
1093
|
-
resetCollectedState();
|
|
1094
|
-
});
|
|
1095
|
-
it("generates app export with table proxies", () => {
|
|
1096
|
-
table("todos", { title: col.string() });
|
|
1097
|
-
table("users", { name: col.string() });
|
|
1098
|
-
const schema = getCollectedSchema();
|
|
1099
|
-
const wasm = schemaToWasm(schema);
|
|
1100
|
-
const output = generateTypes(wasm);
|
|
1101
|
-
expect(output).toContain("export const app: GeneratedApp = {");
|
|
1102
|
-
expect(output).toContain("todos: new TodoQueryBuilder(),");
|
|
1103
|
-
expect(output).toContain("users: new UserQueryBuilder(),");
|
|
1104
|
-
expect(output).toContain("wasmSchema,");
|
|
1105
|
-
});
|
|
1106
|
-
it("app export includes wasmSchema reference", () => {
|
|
1107
|
-
table("items", { name: col.string() });
|
|
1108
|
-
const schema = getCollectedSchema();
|
|
1109
|
-
const wasm = schemaToWasm(schema);
|
|
1110
|
-
const output = generateTypes(wasm);
|
|
1111
|
-
// Verify wasmSchema is defined before app and included in app
|
|
1112
|
-
const wasmSchemaIndex = output.indexOf("export const wasmSchema");
|
|
1113
|
-
const appIndex = output.indexOf("export const app: GeneratedApp = {");
|
|
1114
|
-
expect(wasmSchemaIndex).toBeLessThan(appIndex);
|
|
1115
|
-
expect(output).toContain("wasmSchema,");
|
|
1116
|
-
});
|
|
1117
|
-
});
|
|
1118
|
-
describe("QueryBuilder self-referential relations", () => {
|
|
1119
|
-
beforeEach(() => {
|
|
1120
|
-
resetCollectedState();
|
|
1121
|
-
});
|
|
1122
|
-
it("generates Include with QueryBuilder for self-referential tables", () => {
|
|
1123
|
-
table("todos", {
|
|
1124
|
-
title: col.string(),
|
|
1125
|
-
parentId: col.ref("todos").optional(),
|
|
1126
|
-
});
|
|
1127
|
-
const schema = getCollectedSchema();
|
|
1128
|
-
const wasm = schemaToWasm(schema);
|
|
1129
|
-
const output = generateTypes(wasm);
|
|
1130
|
-
expect(output).toContain("parent?: true | TodoInclude | AnyTodoQueryBuilder<any>;");
|
|
1131
|
-
expect(output).toContain("todosViaParent?: true | TodoInclude | AnyTodoQueryBuilder<any>;");
|
|
1132
|
-
});
|
|
1133
|
-
});
|
|
1134
|
-
//# sourceMappingURL=codegen.test.js.map
|