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
package/dist/cli.test.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { access, chmod, copyFile, mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
4
|
import { tmpdir } from "node:os";
|
|
4
5
|
import { join } from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
7
|
-
import {
|
|
7
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
8
|
+
import { createMigration, exportSchema, permissionsStatus, pushMigration, pushPermissions, validate, } from "./cli.js";
|
|
8
9
|
const dslPath = fileURLToPath(new URL("./dsl.ts", import.meta.url));
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const distPermissionsDslPath = fileURLToPath(new URL("../dist/permissions/index.js", import.meta.url));
|
|
10
|
+
const indexPath = fileURLToPath(new URL("./index.ts", import.meta.url));
|
|
11
|
+
const distIndexPath = fileURLToPath(new URL("../dist/index.js", import.meta.url));
|
|
12
|
+
const binPath = fileURLToPath(new URL("../bin/jazz-tools.js", import.meta.url));
|
|
13
|
+
const bootstrapVerifierPath = fileURLToPath(new URL("../scripts/verify-packed-runtime-bootstrap.mjs", import.meta.url));
|
|
14
14
|
const tempRoots = [];
|
|
15
15
|
afterEach(async () => {
|
|
16
|
+
vi.unstubAllGlobals();
|
|
16
17
|
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
17
18
|
});
|
|
18
19
|
async function createWorkspace() {
|
|
@@ -20,44 +21,72 @@ async function createWorkspace() {
|
|
|
20
21
|
tempRoots.push(root);
|
|
21
22
|
const schemaDir = join(root, "schema");
|
|
22
23
|
await mkdir(schemaDir, { recursive: true });
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
await chmod(jazzBin, 0o755);
|
|
26
|
-
return { root, schemaDir, jazzBin };
|
|
24
|
+
await writeFile(join(root, "package.json"), '{ "type": "module" }\n');
|
|
25
|
+
return { root, schemaDir };
|
|
27
26
|
}
|
|
28
|
-
async function
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
async function fileExists(path) {
|
|
28
|
+
try {
|
|
29
|
+
await access(path);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function captureConsoleLogs(run) {
|
|
37
|
+
const logs = [];
|
|
38
|
+
const spy = vi
|
|
39
|
+
.spyOn(console, "log")
|
|
40
|
+
.mockImplementation((message, ...rest) => {
|
|
41
|
+
logs.push([message, ...rest].map((value) => String(value ?? "")).join(" "));
|
|
42
|
+
});
|
|
43
|
+
try {
|
|
44
|
+
const result = await run();
|
|
45
|
+
return { result, logs };
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
spy.mockRestore();
|
|
49
|
+
}
|
|
35
50
|
}
|
|
36
|
-
function
|
|
51
|
+
function rootSchemaWithoutInlinePermissions(indexImportPath = indexPath) {
|
|
37
52
|
return `
|
|
38
|
-
import {
|
|
53
|
+
import { schema as s } from ${JSON.stringify(indexImportPath)};
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
const schema = {
|
|
56
|
+
projects: s.table({
|
|
57
|
+
name: s.string(),
|
|
58
|
+
}),
|
|
59
|
+
todos: s.table({
|
|
60
|
+
title: s.string(),
|
|
61
|
+
ownerId: s.string(),
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
43
64
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
ownerId: col.string(),
|
|
47
|
-
});
|
|
65
|
+
type AppSchema = s.Schema<typeof schema>;
|
|
66
|
+
export const app: s.App<AppSchema> = s.defineApp(schema);
|
|
48
67
|
`;
|
|
49
68
|
}
|
|
50
|
-
function
|
|
69
|
+
function rootSchemaWithBooleanTodo(indexImportPath = indexPath) {
|
|
51
70
|
return `
|
|
52
|
-
import {
|
|
71
|
+
import { schema as s } from ${JSON.stringify(indexImportPath)};
|
|
53
72
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
73
|
+
const schema = {
|
|
74
|
+
todos: s.table({
|
|
75
|
+
title: s.string(),
|
|
76
|
+
done: s.boolean(),
|
|
77
|
+
}),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
type AppSchema = s.Schema<typeof schema>;
|
|
81
|
+
export const app: s.App<AppSchema> = s.defineApp(schema);
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
function rootSchemaWithInlinePermissions(dslImportPath = dslPath) {
|
|
85
|
+
return `
|
|
86
|
+
import { table, col } from ${JSON.stringify(dslImportPath)};
|
|
57
87
|
|
|
58
88
|
table("todos", {
|
|
59
89
|
title: col.string(),
|
|
60
|
-
ownerId: col.string(),
|
|
61
90
|
}, {
|
|
62
91
|
permissions: {
|
|
63
92
|
select: { type: "True" },
|
|
@@ -65,16 +94,31 @@ table("todos", {
|
|
|
65
94
|
});
|
|
66
95
|
`;
|
|
67
96
|
}
|
|
68
|
-
function
|
|
97
|
+
function rootPermissionsSchema(appImportPath = "./schema.ts", importPath = indexPath) {
|
|
98
|
+
return `
|
|
99
|
+
import { schema as s } from ${JSON.stringify(importPath)};
|
|
100
|
+
import { app } from ${JSON.stringify(appImportPath)};
|
|
101
|
+
|
|
102
|
+
export default s.definePermissions(app, ({ policy, session }) => [
|
|
103
|
+
policy.todos.allowRead.where({ ownerId: session.user_id }),
|
|
104
|
+
]);
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
function rootBooleanLiteralPermissionsSchema(appImportPath = "./schema.ts", importPath = indexPath) {
|
|
69
108
|
return `
|
|
70
|
-
import {
|
|
109
|
+
import { schema as s } from ${JSON.stringify(importPath)};
|
|
71
110
|
import { app } from ${JSON.stringify(appImportPath)};
|
|
72
111
|
|
|
73
|
-
export default definePermissions(app, ({ policy
|
|
74
|
-
policy.todos.allowRead.where({
|
|
112
|
+
export default s.definePermissions(app, ({ policy }) => [
|
|
113
|
+
policy.todos.allowRead.where({ done: true }),
|
|
75
114
|
]);
|
|
76
115
|
`;
|
|
77
116
|
}
|
|
117
|
+
function permissionsSchemaMissingExport() {
|
|
118
|
+
return `
|
|
119
|
+
export const nope = 42;
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
78
122
|
function permissionsSchemaUnknownTable() {
|
|
79
123
|
return `
|
|
80
124
|
export default {
|
|
@@ -86,18 +130,13 @@ export default {
|
|
|
86
130
|
};
|
|
87
131
|
`;
|
|
88
132
|
}
|
|
89
|
-
function
|
|
133
|
+
function permissionsSchemaNamedExport(appImportPath = "./schema.ts", importPath = indexPath) {
|
|
90
134
|
return `
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
function permissionsSchemaNamedExport() {
|
|
95
|
-
return `
|
|
96
|
-
import { definePermissions } from ${JSON.stringify(permissionsDslPath)};
|
|
97
|
-
import { app } from "./app.js";
|
|
135
|
+
import { schema as s } from ${JSON.stringify(importPath)};
|
|
136
|
+
import { app } from ${JSON.stringify(appImportPath)};
|
|
98
137
|
|
|
99
|
-
export const permissions = definePermissions(app, ({ policy, session }) => [
|
|
100
|
-
policy.todos.allowRead.where({
|
|
138
|
+
export const permissions = s.definePermissions(app, ({ policy, session }) => [
|
|
139
|
+
policy.todos.allowRead.where({ ownerId: session.user_id }),
|
|
101
140
|
]);
|
|
102
141
|
`;
|
|
103
142
|
}
|
|
@@ -108,275 +147,582 @@ export default {
|
|
|
108
147
|
};
|
|
109
148
|
`;
|
|
110
149
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
124
|
-
`;
|
|
150
|
+
function storedRootSchema() {
|
|
151
|
+
return {
|
|
152
|
+
projects: {
|
|
153
|
+
columns: [{ name: "name", column_type: { type: "Text" }, nullable: false }],
|
|
154
|
+
},
|
|
155
|
+
todos: {
|
|
156
|
+
columns: [
|
|
157
|
+
{ name: "title", column_type: { type: "Text" }, nullable: false },
|
|
158
|
+
{ name: "ownerId", column_type: { type: "Text" }, nullable: false },
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
};
|
|
125
162
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
table("canvases", {
|
|
136
|
-
name: col.string(),
|
|
137
|
-
isPublic: col.boolean(),
|
|
138
|
-
});
|
|
139
|
-
`;
|
|
140
|
-
}
|
|
141
|
-
function binMigrationDropIsPublicFromBothTables() {
|
|
142
|
-
return `
|
|
143
|
-
import { migrate, col } from ${JSON.stringify(distDslPath)};
|
|
144
|
-
|
|
145
|
-
migrate("messages", {
|
|
146
|
-
isPublic: col.drop().boolean({ backwardsDefault: false }),
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
migrate("canvases", {
|
|
150
|
-
isPublic: col.drop().boolean({ backwardsDefault: false }),
|
|
151
|
-
});
|
|
152
|
-
`;
|
|
153
|
-
}
|
|
154
|
-
function binPermissionsSchema(appImportPath = "./app.js") {
|
|
155
|
-
return `
|
|
156
|
-
import { definePermissions } from ${JSON.stringify(distPermissionsDslPath)};
|
|
157
|
-
import { app } from ${JSON.stringify(appImportPath)};
|
|
158
|
-
|
|
159
|
-
export default definePermissions(app, ({ policy, session }) => [
|
|
160
|
-
policy.todos.allowRead.where({ owner_id: session.user_id }),
|
|
161
|
-
]);
|
|
162
|
-
`;
|
|
163
|
-
}
|
|
164
|
-
function currentSchemaWithComments() {
|
|
165
|
-
return `
|
|
166
|
-
import { table, col } from ${JSON.stringify(distDslPath)};
|
|
167
|
-
|
|
168
|
-
table("projects", {
|
|
169
|
-
name: col.string(),
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
table("todos", {
|
|
173
|
-
title: col.string(),
|
|
174
|
-
ownerId: col.string(),
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
table("comments", {
|
|
178
|
-
body: col.string(),
|
|
179
|
-
});
|
|
180
|
-
`;
|
|
181
|
-
}
|
|
182
|
-
function schemaWithMessagesAndCanvases() {
|
|
183
|
-
return `
|
|
184
|
-
import { table, col } from ${JSON.stringify(dslPath)};
|
|
185
|
-
|
|
186
|
-
table("messages", {
|
|
187
|
-
content: col.string(),
|
|
188
|
-
isPublic: col.boolean(),
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
table("canvases", {
|
|
192
|
-
name: col.string(),
|
|
193
|
-
isPublic: col.boolean(),
|
|
194
|
-
});
|
|
195
|
-
`;
|
|
196
|
-
}
|
|
197
|
-
function migrationDropIsPublicFromBothTables() {
|
|
198
|
-
return `
|
|
199
|
-
import { migrate, col } from ${JSON.stringify(dslPath)};
|
|
200
|
-
|
|
201
|
-
migrate("messages", {
|
|
202
|
-
isPublic: col.drop().boolean({ backwardsDefault: false }),
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
migrate("canvases", {
|
|
206
|
-
isPublic: col.drop().boolean({ backwardsDefault: false }),
|
|
207
|
-
});
|
|
208
|
-
`;
|
|
209
|
-
}
|
|
210
|
-
describe("cli build basic output", () => {
|
|
211
|
-
it("generates app.ts even when current.sql already exists", async () => {
|
|
212
|
-
const { schemaDir, jazzBin } = await createWorkspace();
|
|
213
|
-
await writeFile(join(schemaDir, "current.ts"), currentSchemaWithoutInlinePermissions());
|
|
214
|
-
await writeFile(join(schemaDir, "current.sql"), "-- stale");
|
|
215
|
-
await build({ schemaDir, jazzBin });
|
|
216
|
-
await readFile(join(schemaDir, "app.ts"), "utf8");
|
|
163
|
+
describe("cli validate", () => {
|
|
164
|
+
it("validates root schema.ts without generating SQL or app artifacts", async () => {
|
|
165
|
+
const { root } = await createWorkspace();
|
|
166
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions());
|
|
167
|
+
await validate({ schemaDir: root });
|
|
168
|
+
expect(await fileExists(join(root, "schema", "current.sql"))).toBe(false);
|
|
169
|
+
expect(await fileExists(join(root, "schema", "app.ts"))).toBe(false);
|
|
170
|
+
expect(await fileExists(join(root, "permissions.test.ts"))).toBe(false);
|
|
217
171
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
expect(
|
|
229
|
-
expect(
|
|
230
|
-
expect(
|
|
172
|
+
it("fails when pointed at the legacy ./schema shim directory", async () => {
|
|
173
|
+
const { root, schemaDir } = await createWorkspace();
|
|
174
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions());
|
|
175
|
+
await expect(validate({ schemaDir })).rejects.toThrow(/schema file not found/i);
|
|
176
|
+
});
|
|
177
|
+
it("loads root permissions.ts that imports ./schema.ts", async () => {
|
|
178
|
+
const { root } = await createWorkspace();
|
|
179
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions());
|
|
180
|
+
await writeFile(join(root, "permissions.ts"), rootPermissionsSchema());
|
|
181
|
+
const { logs } = await captureConsoleLogs(() => validate({ schemaDir: root }));
|
|
182
|
+
expect(await fileExists(join(root, "schema", "current.sql"))).toBe(false);
|
|
183
|
+
expect(await fileExists(join(root, "permissions.test.ts"))).toBe(false);
|
|
184
|
+
expect(logs).toContain(`Loaded structural schema from ${join(root, "schema.ts")}.`);
|
|
185
|
+
expect(logs).toContain(`Loaded current permissions from ${join(root, "permissions.ts")}.`);
|
|
186
|
+
expect(logs).toContain("Permission-only changes do not create schema hashes or require migrations.");
|
|
187
|
+
});
|
|
188
|
+
it("loads src/schema.ts and src/permissions.ts when schemaDir points at the app root", async () => {
|
|
189
|
+
const { root } = await createWorkspace();
|
|
190
|
+
const srcDir = join(root, "src");
|
|
191
|
+
await mkdir(srcDir, { recursive: true });
|
|
192
|
+
await writeFile(join(srcDir, "schema.ts"), rootSchemaWithoutInlinePermissions());
|
|
193
|
+
await writeFile(join(srcDir, "permissions.ts"), rootPermissionsSchema());
|
|
194
|
+
const { logs } = await captureConsoleLogs(() => validate({ schemaDir: root }));
|
|
195
|
+
expect(logs).toContain(`Loaded structural schema from ${join(srcDir, "schema.ts")}.`);
|
|
196
|
+
expect(logs).toContain(`Loaded current permissions from ${join(srcDir, "permissions.ts")}.`);
|
|
197
|
+
});
|
|
198
|
+
it("accepts named permissions exports for transitional ergonomics", async () => {
|
|
199
|
+
const { root } = await createWorkspace();
|
|
200
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions());
|
|
201
|
+
await writeFile(join(root, "permissions.ts"), permissionsSchemaNamedExport());
|
|
202
|
+
await validate({ schemaDir: root });
|
|
203
|
+
});
|
|
204
|
+
it("fails when schema.ts uses inline table permissions", async () => {
|
|
205
|
+
const { root } = await createWorkspace();
|
|
206
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithInlinePermissions());
|
|
207
|
+
await expect(validate({ schemaDir: root })).rejects.toThrow(/inline table permissions are no longer supported/i);
|
|
208
|
+
});
|
|
209
|
+
it("fails when permissions.ts has no default or named permissions export", async () => {
|
|
210
|
+
const { root } = await createWorkspace();
|
|
211
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions());
|
|
212
|
+
await writeFile(join(root, "permissions.ts"), permissionsSchemaMissingExport());
|
|
213
|
+
await expect(validate({ schemaDir: root })).rejects.toThrow(/missing permissions export/i);
|
|
214
|
+
});
|
|
215
|
+
it("fails when permissions.ts references unknown tables", async () => {
|
|
216
|
+
const { root } = await createWorkspace();
|
|
217
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions());
|
|
218
|
+
await writeFile(join(root, "permissions.ts"), permissionsSchemaUnknownTable());
|
|
219
|
+
await expect(validate({ schemaDir: root })).rejects.toThrow(/permissions\.ts defines permissions for unknown table\(s\): ghosts/i);
|
|
220
|
+
});
|
|
221
|
+
it("fails when permissions.ts export shape is invalid", async () => {
|
|
222
|
+
const { root } = await createWorkspace();
|
|
223
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions());
|
|
224
|
+
await writeFile(join(root, "permissions.ts"), permissionsSchemaInvalidShape());
|
|
225
|
+
await expect(validate({ schemaDir: root })).rejects.toThrow(/invalid permissions export/i);
|
|
231
226
|
});
|
|
232
227
|
});
|
|
233
|
-
describe("cli
|
|
234
|
-
it("
|
|
235
|
-
const {
|
|
236
|
-
await writeFile(join(
|
|
237
|
-
await writeFile(join(
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
228
|
+
describe("cli schema export", () => {
|
|
229
|
+
it("prints the compiled schema representation as JSON", async () => {
|
|
230
|
+
const { root } = await createWorkspace();
|
|
231
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions());
|
|
232
|
+
await writeFile(join(root, "permissions.ts"), rootPermissionsSchema());
|
|
233
|
+
const writes = [];
|
|
234
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
235
|
+
const writeSpy = vi.spyOn(process.stdout, "write").mockImplementation(((chunk) => {
|
|
236
|
+
writes.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
|
|
237
|
+
return true;
|
|
238
|
+
}));
|
|
239
|
+
try {
|
|
240
|
+
await exportSchema({ schemaDir: root, format: "json" });
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
writeSpy.mockRestore();
|
|
244
|
+
process.stdout.write = originalWrite;
|
|
245
|
+
}
|
|
246
|
+
const exported = JSON.parse(writes.join(""));
|
|
247
|
+
expect(exported.projects.columns[0].name).toBe("name");
|
|
248
|
+
expect(exported.todos.columns.map((column) => column.name)).toEqual([
|
|
249
|
+
"title",
|
|
250
|
+
"ownerId",
|
|
251
|
+
]);
|
|
252
|
+
expect(exported.todos.policies).toBeUndefined();
|
|
247
253
|
});
|
|
248
|
-
it("
|
|
249
|
-
const {
|
|
250
|
-
|
|
251
|
-
await
|
|
252
|
-
await
|
|
253
|
-
|
|
254
|
-
|
|
254
|
+
it("prints the compiled schema representation from src/schema.ts", async () => {
|
|
255
|
+
const { root } = await createWorkspace();
|
|
256
|
+
const srcDir = join(root, "src");
|
|
257
|
+
await mkdir(srcDir, { recursive: true });
|
|
258
|
+
await writeFile(join(srcDir, "schema.ts"), rootSchemaWithoutInlinePermissions());
|
|
259
|
+
await writeFile(join(srcDir, "permissions.ts"), rootPermissionsSchema());
|
|
260
|
+
const writes = [];
|
|
261
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
262
|
+
const writeSpy = vi.spyOn(process.stdout, "write").mockImplementation(((chunk) => {
|
|
263
|
+
writes.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
|
|
264
|
+
return true;
|
|
265
|
+
}));
|
|
266
|
+
try {
|
|
267
|
+
await exportSchema({ schemaDir: root, format: "json" });
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
writeSpy.mockRestore();
|
|
271
|
+
process.stdout.write = originalWrite;
|
|
272
|
+
}
|
|
273
|
+
const exported = JSON.parse(writes.join(""));
|
|
274
|
+
expect(exported.projects.columns[0].name).toBe("name");
|
|
275
|
+
expect(exported.todos.columns.map((column) => column.name)).toEqual([
|
|
276
|
+
"title",
|
|
277
|
+
"ownerId",
|
|
278
|
+
]);
|
|
255
279
|
});
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
|
|
280
|
+
});
|
|
281
|
+
describe("cli migrations", () => {
|
|
282
|
+
it("generates a typed migration stub from stored schema hashes", async () => {
|
|
283
|
+
const { root } = await createWorkspace();
|
|
284
|
+
const migrationsDir = join(root, "migrations");
|
|
285
|
+
const fromHash = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
|
286
|
+
const toHash = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
|
|
287
|
+
const fromShortHash = fromHash.slice(0, 12);
|
|
288
|
+
const toShortHash = toHash.slice(0, 12);
|
|
289
|
+
const fetchMock = vi.fn(async (input) => {
|
|
290
|
+
if (input.endsWith("/schemas")) {
|
|
291
|
+
return new Response(JSON.stringify({ hashes: [fromHash, toHash] }), { status: 200 });
|
|
292
|
+
}
|
|
293
|
+
if (input.endsWith(`/schema/${fromHash}`)) {
|
|
294
|
+
return new Response(JSON.stringify({
|
|
295
|
+
todos: {
|
|
296
|
+
columns: [{ name: "title", column_type: { type: "Text" }, nullable: false }],
|
|
297
|
+
},
|
|
298
|
+
}), { status: 200 });
|
|
299
|
+
}
|
|
300
|
+
if (input.endsWith(`/schema/${toHash}`)) {
|
|
301
|
+
return new Response(JSON.stringify({
|
|
302
|
+
todos: {
|
|
303
|
+
columns: [
|
|
304
|
+
{ name: "title", column_type: { type: "Text" }, nullable: false },
|
|
305
|
+
{ name: "notes", column_type: { type: "Text" }, nullable: true },
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
}), { status: 200 });
|
|
309
|
+
}
|
|
310
|
+
throw new Error(`Unexpected fetch: ${input}`);
|
|
311
|
+
});
|
|
312
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
313
|
+
const { result: filePath, logs } = await captureConsoleLogs(() => createMigration({
|
|
314
|
+
serverUrl: "http://localhost:1625",
|
|
315
|
+
adminSecret: "admin-secret",
|
|
316
|
+
migrationsDir,
|
|
317
|
+
fromHash: fromShortHash,
|
|
318
|
+
toHash: toShortHash,
|
|
319
|
+
}));
|
|
320
|
+
const generated = await readFile(filePath, "utf8");
|
|
321
|
+
expect(filePath).toContain(`-unnamed-${fromShortHash}-${toShortHash}.ts`);
|
|
322
|
+
expect(generated).toContain("s.defineMigration");
|
|
323
|
+
expect(generated).toContain(`fromHash: "${fromShortHash}"`);
|
|
324
|
+
expect(generated).toContain(`toHash: "${toShortHash}"`);
|
|
325
|
+
expect(generated).toContain("migrate: {");
|
|
326
|
+
expect(generated).toContain('"notes": s.add.string({ default: null }),');
|
|
327
|
+
expect(logs).toContain("Migration stubs are only for structural schema changes.");
|
|
328
|
+
expect(logs).toContain("Permission-only changes do not create schema hashes or require migrations.");
|
|
263
329
|
});
|
|
264
|
-
it("
|
|
265
|
-
const {
|
|
266
|
-
|
|
267
|
-
|
|
330
|
+
it("skips table add/drop steps when inferring a migration stub", async () => {
|
|
331
|
+
const { root } = await createWorkspace();
|
|
332
|
+
const migrationsDir = join(root, "migrations");
|
|
333
|
+
const fromHash = "abababababababababababababababababababababababababababababababab";
|
|
334
|
+
const toHash = "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd";
|
|
335
|
+
const fromShortHash = fromHash.slice(0, 12);
|
|
336
|
+
const toShortHash = toHash.slice(0, 12);
|
|
337
|
+
const fetchMock = vi.fn(async (input) => {
|
|
338
|
+
if (input.endsWith("/schemas")) {
|
|
339
|
+
return new Response(JSON.stringify({ hashes: [fromHash, toHash] }), { status: 200 });
|
|
340
|
+
}
|
|
341
|
+
if (input.endsWith(`/schema/${fromHash}`)) {
|
|
342
|
+
return new Response(JSON.stringify({
|
|
343
|
+
todos: {
|
|
344
|
+
columns: [{ name: "title", column_type: { type: "Text" }, nullable: false }],
|
|
345
|
+
},
|
|
346
|
+
legacy_users: {
|
|
347
|
+
columns: [{ name: "email", column_type: { type: "Text" }, nullable: false }],
|
|
348
|
+
},
|
|
349
|
+
}), { status: 200 });
|
|
350
|
+
}
|
|
351
|
+
if (input.endsWith(`/schema/${toHash}`)) {
|
|
352
|
+
return new Response(JSON.stringify({
|
|
353
|
+
todos: {
|
|
354
|
+
columns: [
|
|
355
|
+
{ name: "title", column_type: { type: "Text" }, nullable: false },
|
|
356
|
+
{ name: "notes", column_type: { type: "Text" }, nullable: true },
|
|
357
|
+
],
|
|
358
|
+
},
|
|
359
|
+
users: {
|
|
360
|
+
columns: [{ name: "name", column_type: { type: "Text" }, nullable: false }],
|
|
361
|
+
},
|
|
362
|
+
}), { status: 200 });
|
|
363
|
+
}
|
|
364
|
+
throw new Error(`Unexpected fetch: ${input}`);
|
|
365
|
+
});
|
|
366
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
367
|
+
const filePath = await createMigration({
|
|
368
|
+
serverUrl: "http://localhost:1625",
|
|
369
|
+
adminSecret: "admin-secret",
|
|
370
|
+
migrationsDir,
|
|
371
|
+
fromHash: fromShortHash,
|
|
372
|
+
toHash: toShortHash,
|
|
373
|
+
});
|
|
374
|
+
const generated = await readFile(filePath, "utf8");
|
|
375
|
+
expect(generated).toContain('"todos": {');
|
|
376
|
+
expect(generated).toContain('"notes": s.add.string({ default: null }),');
|
|
377
|
+
expect(generated).not.toContain("createTable");
|
|
378
|
+
expect(generated).not.toContain("dropTable");
|
|
379
|
+
expect(generated).not.toContain('"legacy_users"');
|
|
380
|
+
expect(generated).not.toContain('"users"');
|
|
268
381
|
});
|
|
269
|
-
it("
|
|
270
|
-
const {
|
|
271
|
-
|
|
272
|
-
await
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
382
|
+
it("pushes a reviewed migration via the admin migrations endpoint", async () => {
|
|
383
|
+
const { root } = await createWorkspace();
|
|
384
|
+
const migrationsDir = join(root, "migrations");
|
|
385
|
+
await mkdir(migrationsDir, { recursive: true });
|
|
386
|
+
const fromHash = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
|
|
387
|
+
const toHash = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";
|
|
388
|
+
const fromShortHash = fromHash.slice(0, 12);
|
|
389
|
+
const toShortHash = toHash.slice(0, 12);
|
|
390
|
+
const migrationPath = join(migrationsDir, `20260318-rename-${fromShortHash}-${toShortHash}.ts`);
|
|
391
|
+
await writeFile(migrationPath, `
|
|
392
|
+
import { schema as s } from ${JSON.stringify(indexPath)};
|
|
393
|
+
|
|
394
|
+
export default s.defineMigration({
|
|
395
|
+
migrate: {
|
|
396
|
+
users: {
|
|
397
|
+
email_address: s.renameFrom("email"),
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
fromHash: ${JSON.stringify(fromShortHash)},
|
|
401
|
+
toHash: ${JSON.stringify(toShortHash)},
|
|
402
|
+
from: {
|
|
403
|
+
users: s.table({
|
|
404
|
+
email: s.string(),
|
|
405
|
+
}),
|
|
406
|
+
},
|
|
407
|
+
to: {
|
|
408
|
+
users: s.table({
|
|
409
|
+
email_address: s.string(),
|
|
410
|
+
}),
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
`);
|
|
414
|
+
const fetchMock = vi.fn(async (_input, init) => {
|
|
415
|
+
if (_input.endsWith("/schemas")) {
|
|
416
|
+
return new Response(JSON.stringify({ hashes: [fromHash, toHash] }), { status: 200 });
|
|
417
|
+
}
|
|
418
|
+
const body = JSON.parse(String(init?.body));
|
|
419
|
+
expect(body.fromHash).toBe(fromHash);
|
|
420
|
+
expect(body.toHash).toBe(toHash);
|
|
421
|
+
expect(body.forward).toEqual([
|
|
422
|
+
{
|
|
423
|
+
table: "users",
|
|
424
|
+
operations: [
|
|
425
|
+
{
|
|
426
|
+
type: "rename",
|
|
427
|
+
column: "email",
|
|
428
|
+
value: "email_address",
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
},
|
|
432
|
+
]);
|
|
433
|
+
return new Response(JSON.stringify({ ok: true }), { status: 201 });
|
|
434
|
+
});
|
|
435
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
436
|
+
await pushMigration({
|
|
437
|
+
serverUrl: "http://localhost:1625",
|
|
438
|
+
adminSecret: "admin-secret",
|
|
439
|
+
migrationsDir,
|
|
440
|
+
fromHash: fromShortHash,
|
|
441
|
+
toHash: toShortHash,
|
|
442
|
+
});
|
|
443
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
277
444
|
});
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
await
|
|
445
|
+
});
|
|
446
|
+
describe("cli permissions", () => {
|
|
447
|
+
it("reports the current permissions head against the matching stored structural schema", async () => {
|
|
448
|
+
const { root } = await createWorkspace();
|
|
449
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions());
|
|
450
|
+
await writeFile(join(root, "permissions.ts"), rootPermissionsSchema());
|
|
451
|
+
const schemaHash = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
452
|
+
const fetchMock = vi.fn(async (input) => {
|
|
453
|
+
if (input.endsWith("/schemas")) {
|
|
454
|
+
return new Response(JSON.stringify({ hashes: [schemaHash] }), { status: 200 });
|
|
455
|
+
}
|
|
456
|
+
if (input.endsWith(`/schema/${schemaHash}`)) {
|
|
457
|
+
return new Response(JSON.stringify(storedRootSchema()), { status: 200 });
|
|
458
|
+
}
|
|
459
|
+
if (input.endsWith("/admin/permissions/head")) {
|
|
460
|
+
return new Response(JSON.stringify({
|
|
461
|
+
head: {
|
|
462
|
+
schemaHash,
|
|
463
|
+
version: 3,
|
|
464
|
+
parentBundleObjectId: "11111111-1111-1111-1111-111111111111",
|
|
465
|
+
bundleObjectId: "22222222-2222-2222-2222-222222222222",
|
|
466
|
+
},
|
|
467
|
+
}), { status: 200 });
|
|
468
|
+
}
|
|
469
|
+
throw new Error(`Unexpected fetch: ${input}`);
|
|
470
|
+
});
|
|
471
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
472
|
+
const { logs } = await captureConsoleLogs(() => permissionsStatus({
|
|
473
|
+
serverUrl: "http://localhost:1625",
|
|
474
|
+
adminSecret: "admin-secret",
|
|
475
|
+
schemaDir: root,
|
|
476
|
+
}));
|
|
477
|
+
expect(logs).toContain(`Loaded structural schema from ${join(root, "schema.ts")}.`);
|
|
478
|
+
expect(logs).toContain(`Loaded current permissions from ${join(root, "permissions.ts")}.`);
|
|
479
|
+
expect(logs).toContain(`Local structural schema matches stored hash ${schemaHash.slice(0, 12)}.`);
|
|
480
|
+
expect(logs).toContain(`Server permissions head is v3 on ${schemaHash.slice(0, 12)}.`);
|
|
481
|
+
expect(logs).toContain("Next push will require parent bundle 22222222-2222-2222-2222-222222222222.");
|
|
283
482
|
});
|
|
284
|
-
it("
|
|
285
|
-
const {
|
|
286
|
-
|
|
287
|
-
await
|
|
288
|
-
await
|
|
483
|
+
it("loads src/permissions.ts when reporting the current permissions head", async () => {
|
|
484
|
+
const { root } = await createWorkspace();
|
|
485
|
+
const srcDir = join(root, "src");
|
|
486
|
+
await mkdir(srcDir, { recursive: true });
|
|
487
|
+
await writeFile(join(srcDir, "schema.ts"), rootSchemaWithoutInlinePermissions());
|
|
488
|
+
await writeFile(join(srcDir, "permissions.ts"), rootPermissionsSchema());
|
|
489
|
+
const schemaHash = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
490
|
+
const fetchMock = vi.fn(async (input) => {
|
|
491
|
+
if (input.endsWith("/schemas")) {
|
|
492
|
+
return new Response(JSON.stringify({ hashes: [schemaHash] }), { status: 200 });
|
|
493
|
+
}
|
|
494
|
+
if (input.endsWith(`/schema/${schemaHash}`)) {
|
|
495
|
+
return new Response(JSON.stringify(storedRootSchema()), { status: 200 });
|
|
496
|
+
}
|
|
497
|
+
if (input.endsWith("/admin/permissions/head")) {
|
|
498
|
+
return new Response(JSON.stringify({ head: null }), { status: 200 });
|
|
499
|
+
}
|
|
500
|
+
throw new Error(`Unexpected fetch: ${input}`);
|
|
501
|
+
});
|
|
502
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
503
|
+
const { logs } = await captureConsoleLogs(() => permissionsStatus({
|
|
504
|
+
serverUrl: "http://localhost:1625",
|
|
505
|
+
adminSecret: "admin-secret",
|
|
506
|
+
schemaDir: root,
|
|
507
|
+
}));
|
|
508
|
+
expect(logs).toContain(`Loaded structural schema from ${join(srcDir, "schema.ts")}.`);
|
|
509
|
+
expect(logs).toContain(`Loaded current permissions from ${join(srcDir, "permissions.ts")}.`);
|
|
289
510
|
});
|
|
290
|
-
it("
|
|
291
|
-
const {
|
|
292
|
-
await writeFile(join(
|
|
293
|
-
await writeFile(join(
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
511
|
+
it("publishes permissions with the current head bundle as the expected parent", async () => {
|
|
512
|
+
const { root } = await createWorkspace();
|
|
513
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions());
|
|
514
|
+
await writeFile(join(root, "permissions.ts"), rootPermissionsSchema());
|
|
515
|
+
const schemaHash = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
|
|
516
|
+
const currentHead = {
|
|
517
|
+
schemaHash,
|
|
518
|
+
version: 2,
|
|
519
|
+
parentBundleObjectId: "11111111-1111-1111-1111-111111111111",
|
|
520
|
+
bundleObjectId: "22222222-2222-2222-2222-222222222222",
|
|
521
|
+
};
|
|
522
|
+
const fetchMock = vi.fn(async (input, init) => {
|
|
523
|
+
if (input.endsWith("/schemas")) {
|
|
524
|
+
return new Response(JSON.stringify({ hashes: [schemaHash] }), { status: 200 });
|
|
525
|
+
}
|
|
526
|
+
if (input.endsWith(`/schema/${schemaHash}`)) {
|
|
527
|
+
return new Response(JSON.stringify(storedRootSchema()), { status: 200 });
|
|
528
|
+
}
|
|
529
|
+
if (input.endsWith("/admin/permissions/head")) {
|
|
530
|
+
return new Response(JSON.stringify({ head: currentHead }), { status: 200 });
|
|
531
|
+
}
|
|
532
|
+
if (input.endsWith("/admin/permissions")) {
|
|
533
|
+
const body = JSON.parse(String(init?.body));
|
|
534
|
+
expect(body.schemaHash).toBe(schemaHash);
|
|
535
|
+
expect(body.expectedParentBundleObjectId).toBe(currentHead.bundleObjectId);
|
|
536
|
+
expect(Object.keys(body.permissions)).toContain("todos");
|
|
537
|
+
return new Response(JSON.stringify({
|
|
538
|
+
head: {
|
|
539
|
+
schemaHash,
|
|
540
|
+
version: 3,
|
|
541
|
+
parentBundleObjectId: currentHead.bundleObjectId,
|
|
542
|
+
bundleObjectId: "33333333-3333-3333-3333-333333333333",
|
|
543
|
+
},
|
|
544
|
+
}), { status: 201 });
|
|
545
|
+
}
|
|
546
|
+
throw new Error(`Unexpected fetch: ${input}`);
|
|
547
|
+
});
|
|
548
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
549
|
+
const { logs } = await captureConsoleLogs(() => pushPermissions({
|
|
550
|
+
serverUrl: "http://localhost:1625",
|
|
551
|
+
adminSecret: "admin-secret",
|
|
552
|
+
schemaDir: root,
|
|
553
|
+
}));
|
|
554
|
+
expect(logs).toContain(`Resolved structural schema hash ${schemaHash.slice(0, 12)}.`);
|
|
555
|
+
expect(logs).toContain(`Publishing from parent v2 on ${schemaHash.slice(0, 12)}.`);
|
|
556
|
+
expect(logs).toContain(`Published permissions head v3 on ${schemaHash.slice(0, 12)}.`);
|
|
557
|
+
expect(logs).toContain("Permission-only changes do not create schema hashes or require migrations.");
|
|
297
558
|
});
|
|
298
|
-
it("
|
|
299
|
-
const {
|
|
300
|
-
await writeFile(join(
|
|
301
|
-
await writeFile(join(
|
|
302
|
-
|
|
559
|
+
it("publishes permission literals using tagged wire values", async () => {
|
|
560
|
+
const { root } = await createWorkspace();
|
|
561
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithBooleanTodo());
|
|
562
|
+
await writeFile(join(root, "permissions.ts"), rootBooleanLiteralPermissionsSchema());
|
|
563
|
+
const schemaHash = "abababababababababababababababababababababababababababababababab";
|
|
564
|
+
const fetchMock = vi.fn(async (input, init) => {
|
|
565
|
+
if (input.endsWith("/schemas")) {
|
|
566
|
+
return new Response(JSON.stringify({ hashes: [schemaHash] }), { status: 200 });
|
|
567
|
+
}
|
|
568
|
+
if (input.endsWith(`/schema/${schemaHash}`)) {
|
|
569
|
+
return new Response(JSON.stringify({
|
|
570
|
+
todos: {
|
|
571
|
+
columns: [
|
|
572
|
+
{ name: "title", column_type: { type: "Text" }, nullable: false },
|
|
573
|
+
{ name: "done", column_type: { type: "Boolean" }, nullable: false },
|
|
574
|
+
],
|
|
575
|
+
},
|
|
576
|
+
}), { status: 200 });
|
|
577
|
+
}
|
|
578
|
+
if (input.endsWith("/admin/permissions/head")) {
|
|
579
|
+
return new Response(JSON.stringify({ head: null }), { status: 200 });
|
|
580
|
+
}
|
|
581
|
+
if (input.endsWith("/admin/permissions")) {
|
|
582
|
+
const body = JSON.parse(String(init?.body));
|
|
583
|
+
expect(body.permissions.todos.select.using).toEqual({
|
|
584
|
+
type: "Cmp",
|
|
585
|
+
column: "done",
|
|
586
|
+
op: "Eq",
|
|
587
|
+
value: {
|
|
588
|
+
type: "Literal",
|
|
589
|
+
value: {
|
|
590
|
+
type: "Boolean",
|
|
591
|
+
value: true,
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
return new Response(JSON.stringify({
|
|
596
|
+
head: {
|
|
597
|
+
schemaHash,
|
|
598
|
+
version: 1,
|
|
599
|
+
parentBundleObjectId: null,
|
|
600
|
+
bundleObjectId: "99999999-9999-9999-9999-999999999999",
|
|
601
|
+
},
|
|
602
|
+
}), { status: 201 });
|
|
603
|
+
}
|
|
604
|
+
throw new Error(`Unexpected fetch: ${input}`);
|
|
605
|
+
});
|
|
606
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
607
|
+
await pushPermissions({
|
|
608
|
+
serverUrl: "http://localhost:1625",
|
|
609
|
+
adminSecret: "admin-secret",
|
|
610
|
+
schemaDir: root,
|
|
611
|
+
});
|
|
612
|
+
expect(fetchMock).toHaveBeenCalled();
|
|
303
613
|
});
|
|
304
614
|
});
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const result = spawnSync(process.execPath, [binPath, "build", "--schema-dir", schemaDir, "--rust-bin", rustBin], {
|
|
310
|
-
stdio: "inherit",
|
|
615
|
+
function runBin(args) {
|
|
616
|
+
return spawnSync(process.execPath, [binPath, ...args], {
|
|
617
|
+
encoding: "utf8",
|
|
618
|
+
env: process.env,
|
|
311
619
|
});
|
|
312
|
-
|
|
620
|
+
}
|
|
621
|
+
function hostNativeBinaryName() {
|
|
622
|
+
switch (`${process.platform}-${process.arch}`) {
|
|
623
|
+
case "darwin-arm64":
|
|
624
|
+
return "jazz-tools-darwin-arm64";
|
|
625
|
+
case "darwin-x64":
|
|
626
|
+
return "jazz-tools-darwin-x64";
|
|
627
|
+
case "linux-arm64":
|
|
628
|
+
return "jazz-tools-linux-arm64";
|
|
629
|
+
case "linux-x64":
|
|
630
|
+
return "jazz-tools-linux-x64";
|
|
631
|
+
default:
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
313
634
|
}
|
|
314
635
|
describe("bin integration", () => {
|
|
315
|
-
it("
|
|
316
|
-
const {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
await
|
|
636
|
+
it("routes validate through the TypeScript CLI for a root schema.ts project", async () => {
|
|
637
|
+
const { root } = await createWorkspace();
|
|
638
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions(distIndexPath));
|
|
639
|
+
const result = runBin(["validate", "--schema-dir", root]);
|
|
640
|
+
expect(result.status).toBe(0);
|
|
641
|
+
expect(await fileExists(join(root, "schema", "current.sql"))).toBe(false);
|
|
642
|
+
expect(await fileExists(join(root, "schema", "app.ts"))).toBe(false);
|
|
643
|
+
});
|
|
644
|
+
it("loads root permissions.ts through the validate command", async () => {
|
|
645
|
+
const { root } = await createWorkspace();
|
|
646
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions(distIndexPath));
|
|
647
|
+
await writeFile(join(root, "permissions.ts"), rootPermissionsSchema("./schema.ts", distIndexPath));
|
|
648
|
+
const result = runBin(["validate", "--schema-dir", root]);
|
|
649
|
+
expect(result.status).toBe(0);
|
|
650
|
+
expect(await fileExists(join(root, "permissions.test.ts"))).toBe(false);
|
|
651
|
+
});
|
|
652
|
+
it("loads src/schema.ts and src/permissions.ts through the validate command", async () => {
|
|
653
|
+
const { root } = await createWorkspace();
|
|
654
|
+
const srcDir = join(root, "src");
|
|
655
|
+
await mkdir(srcDir, { recursive: true });
|
|
656
|
+
await writeFile(join(srcDir, "schema.ts"), rootSchemaWithoutInlinePermissions(distIndexPath));
|
|
657
|
+
await writeFile(join(srcDir, "permissions.ts"), rootPermissionsSchema("./schema.ts", distIndexPath));
|
|
658
|
+
const result = runBin(["validate", "--schema-dir", root]);
|
|
659
|
+
expect(result.status).toBe(0);
|
|
660
|
+
expect(result.stdout).toContain(`Loaded structural schema from ${join(srcDir, "schema.ts")}.`);
|
|
661
|
+
expect(result.stdout).toContain(`Loaded current permissions from ${join(srcDir, "permissions.ts")}.`);
|
|
321
662
|
});
|
|
322
|
-
it("
|
|
323
|
-
const { schemaDir } = await createWorkspace();
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
663
|
+
it("fails when validate is pointed at the legacy ./schema shim directory", async () => {
|
|
664
|
+
const { root, schemaDir } = await createWorkspace();
|
|
665
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions(distIndexPath));
|
|
666
|
+
const result = runBin(["validate", "--schema-dir", schemaDir]);
|
|
667
|
+
expect(result.status).toBe(1);
|
|
668
|
+
expect(result.stderr).toContain("Schema file not found");
|
|
328
669
|
});
|
|
329
|
-
it("
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
await writeFile(join(schemaDir, "current.ts"), binCurrentSchema());
|
|
335
|
-
await writeFile(join(schemaDir, "current.sql"), "-- stale");
|
|
336
|
-
runBinBuild(schemaDir, rustBin);
|
|
337
|
-
const sql = await readFile(join(schemaDir, "current.sql"), "utf8");
|
|
338
|
-
expect(sql).toContain("CREATE TABLE");
|
|
339
|
-
await readFile(join(schemaDir, "app.ts"), "utf8");
|
|
670
|
+
it("fails when no root schema.ts can be found", async () => {
|
|
671
|
+
const { root } = await createWorkspace();
|
|
672
|
+
const result = runBin(["validate", "--schema-dir", root]);
|
|
673
|
+
expect(result.status).toBe(1);
|
|
674
|
+
expect(result.stderr).toContain("Schema file not found");
|
|
340
675
|
});
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
await writeFile(join(schemaDir, "current.ts"), currentSchemaWithComments());
|
|
348
|
-
runBinBuild(schemaDir, rustBin);
|
|
349
|
-
const sql = await readFile(join(schemaDir, "current.sql"), "utf8");
|
|
350
|
-
expect(sql).toContain("CREATE TABLE comments");
|
|
676
|
+
it("rejects the removed build alias with a validate hint", async () => {
|
|
677
|
+
const { root } = await createWorkspace();
|
|
678
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions(distIndexPath));
|
|
679
|
+
const result = runBin(["build", "--schema-dir", root]);
|
|
680
|
+
expect(result.status).toBe(1);
|
|
681
|
+
expect(result.stderr).toContain("renamed to `jazz-tools validate`");
|
|
351
682
|
});
|
|
352
|
-
it("
|
|
353
|
-
const {
|
|
354
|
-
|
|
355
|
-
await writeFile(join(
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
expect(appTs).toContain("comments");
|
|
683
|
+
it("routes schema export through the TypeScript CLI", async () => {
|
|
684
|
+
const { root } = await createWorkspace();
|
|
685
|
+
await writeFile(join(root, "schema.ts"), rootSchemaWithoutInlinePermissions(distIndexPath));
|
|
686
|
+
await writeFile(join(root, "permissions.ts"), rootPermissionsSchema("./schema.ts", distIndexPath));
|
|
687
|
+
const result = runBin(["schema", "export", "--schema-dir", root, "--format", "json"]);
|
|
688
|
+
expect(result.status).toBe(0);
|
|
689
|
+
const exported = JSON.parse(String(result.stdout));
|
|
690
|
+
expect(exported.todos.columns.some((column) => column.name === "ownerId")).toBe(true);
|
|
361
691
|
});
|
|
362
|
-
it("
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
692
|
+
it("verifies packed runtime bootstrap with a native-only help probe", async () => {
|
|
693
|
+
const hostBinaryName = hostNativeBinaryName();
|
|
694
|
+
if (!hostBinaryName) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const { root } = await createWorkspace();
|
|
698
|
+
const packageRoot = join(root, "package");
|
|
699
|
+
const nativeDir = join(packageRoot, "bin", "native");
|
|
700
|
+
const argsPath = join(root, "captured-args.txt");
|
|
701
|
+
const binaryPath = join(nativeDir, hostBinaryName);
|
|
702
|
+
await mkdir(nativeDir, { recursive: true });
|
|
703
|
+
await copyFile(binPath, join(packageRoot, "bin", "jazz-tools.js"));
|
|
704
|
+
await writeFile(binaryPath, `#!/bin/sh
|
|
705
|
+
printf '%s\n' "$@" > ${JSON.stringify(argsPath)}
|
|
706
|
+
exit 0
|
|
707
|
+
`, "utf8");
|
|
708
|
+
await chmod(binaryPath, 0o644);
|
|
709
|
+
const result = spawnSync(process.execPath, [bootstrapVerifierPath, packageRoot], {
|
|
710
|
+
encoding: "utf8",
|
|
711
|
+
env: process.env,
|
|
712
|
+
});
|
|
713
|
+
expect(result.status).toBe(0);
|
|
714
|
+
expect(await readFile(argsPath, "utf8")).toBe("create\n--help\n");
|
|
715
|
+
await expect(access(binaryPath, constants.X_OK)).resolves.toBeUndefined();
|
|
371
716
|
});
|
|
372
|
-
it("
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
expect(
|
|
717
|
+
it("shows the wrapper command surface in --help output", () => {
|
|
718
|
+
const result = runBin(["--help"]);
|
|
719
|
+
expect(result.status).toBe(0);
|
|
720
|
+
expect(result.stdout).toContain("validate");
|
|
721
|
+
expect(result.stdout).toContain("schema export");
|
|
722
|
+
expect(result.stdout).toContain("permissions push");
|
|
723
|
+
expect(result.stdout).toContain("migrations push");
|
|
724
|
+
expect(result.stdout).toContain("server");
|
|
725
|
+
expect(result.stdout).toContain("create");
|
|
380
726
|
});
|
|
381
727
|
});
|
|
382
728
|
//# sourceMappingURL=cli.test.js.map
|