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.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// CLI for jazz-tools schema tooling
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { join, basename, dirname, resolve } from "path";
|
|
3
|
+
import { access, mkdir, readFile, readdir, writeFile } from "fs/promises";
|
|
4
|
+
import { basename, join, resolve } from "path";
|
|
6
5
|
import { pathToFileURL } from "url";
|
|
7
|
-
import { register as registerCjs } from "tsx/cjs/api";
|
|
8
6
|
import { register as registerEsm } from "tsx/esm/api";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
7
|
+
import { schemaDefinitionToAst } from "./migrations.js";
|
|
8
|
+
import { loadCompiledSchema } from "./schema-loader.js";
|
|
9
|
+
import { encodePublishedMigrationValue, fetchPermissionsHead, fetchSchemaHashes, fetchStoredWasmSchema, publishStoredPermissions, publishStoredMigration, } from "./runtime/schema-fetch.js";
|
|
10
|
+
import { toValue } from "./runtime/value-converter.js";
|
|
11
|
+
const PERMISSIONS_LIFECYCLE_NOTE = "Permission-only changes do not create schema hashes or require migrations.";
|
|
12
12
|
function parseArgs() {
|
|
13
13
|
const args = process.argv.slice(2);
|
|
14
14
|
const command = args[0] || "";
|
|
15
|
-
let
|
|
16
|
-
let
|
|
15
|
+
let schemaDir = process.cwd();
|
|
16
|
+
let jazzBin;
|
|
17
17
|
for (let i = 1; i < args.length; i++) {
|
|
18
18
|
const arg = args[i];
|
|
19
19
|
const nextArg = args[i + 1];
|
|
@@ -28,276 +28,612 @@ function parseArgs() {
|
|
|
28
28
|
}
|
|
29
29
|
return { command, options: { jazzBin, schemaDir } };
|
|
30
30
|
}
|
|
31
|
-
// Allow loading `.ts` schema files when invoked via `node dist/cli.js`.
|
|
32
31
|
registerEsm();
|
|
33
|
-
// Counter for cache-busting module loads.
|
|
34
32
|
let importCounter = 0;
|
|
35
|
-
function
|
|
36
|
-
const loader = registerCjs({ namespace: `jazz-tools-cli-permissions-${++importCounter}` });
|
|
33
|
+
async function pathExists(path) {
|
|
37
34
|
try {
|
|
38
|
-
|
|
35
|
+
await access(path);
|
|
36
|
+
return true;
|
|
39
37
|
}
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
42
40
|
}
|
|
43
41
|
}
|
|
44
|
-
async function
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
42
|
+
export async function validate(options) {
|
|
43
|
+
const compiled = await loadCompiledSchema(options.schemaDir);
|
|
44
|
+
const tableCount = compiled.schema.tables.length;
|
|
45
|
+
console.log(`Loaded structural schema from ${compiled.schemaFile}.`);
|
|
46
|
+
if (compiled.permissionsFile) {
|
|
47
|
+
console.log(`Loaded current permissions from ${compiled.permissionsFile}.`);
|
|
48
|
+
console.log(PERMISSIONS_LIFECYCLE_NOTE);
|
|
49
|
+
console.log("Use `jazz-tools permissions status` or `jazz-tools permissions push` for auth publication.");
|
|
50
|
+
}
|
|
51
|
+
console.log(`Validated ${tableCount} table${tableCount === 1 ? "" : "s"} in schema.ts.`);
|
|
48
52
|
}
|
|
49
|
-
async function
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
export async function exportSchema(options) {
|
|
54
|
+
if (options.format !== "json") {
|
|
55
|
+
throw new Error(`Unsupported schema export format: ${options.format}`);
|
|
56
|
+
}
|
|
57
|
+
const compiled = await loadCompiledSchema(options.schemaDir);
|
|
58
|
+
process.stdout.write(`${JSON.stringify(compiled.wasmSchema, null, 2)}\n`);
|
|
52
59
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const appTsPath = join(schemaDir, "app.ts");
|
|
68
|
-
await writeFile(appTsPath, output);
|
|
69
|
-
console.log(`Generated: app.ts`);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Check if a filename is a migration TypeScript stub.
|
|
73
|
-
*
|
|
74
|
-
* Valid format: `migration_v1_v2_455a1f10a158_357c464c4c43.ts`
|
|
75
|
-
*/
|
|
76
|
-
function isMigrationTsStub(filename) {
|
|
77
|
-
const pattern = /^migration_v\d+_v\d+_[0-9a-f]{12}_[0-9a-f]{12}\.ts$/;
|
|
78
|
-
return pattern.test(filename);
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Generate migration SQL filename with direction before hashes.
|
|
82
|
-
*
|
|
83
|
-
* Input: migration_v1_v2_455a1f10a158_357c464c4c43.ts
|
|
84
|
-
* Output: migration_v1_v2_fwd_455a1f10a158_357c464c4c43.sql
|
|
85
|
-
*/
|
|
86
|
-
function migrationSqlFilename(tsFile, direction) {
|
|
87
|
-
const dir = tsFile.substring(0, tsFile.lastIndexOf("/") + 1);
|
|
88
|
-
const name = basename(tsFile, ".ts");
|
|
89
|
-
const match = name.match(/^(migration_v\d+_v\d+)_([0-9a-f]{12})_([0-9a-f]{12})$/);
|
|
90
|
-
if (!match) {
|
|
91
|
-
return tsFile.replace(/\.ts$/, `_${direction}.sql`);
|
|
60
|
+
const SHORT_SCHEMA_HASH_LENGTH = 12;
|
|
61
|
+
function getFlagValue(args, flag) {
|
|
62
|
+
for (let i = 0; i < args.length; i++) {
|
|
63
|
+
const arg = args[i];
|
|
64
|
+
if (!arg) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (arg === flag) {
|
|
68
|
+
return args[i + 1];
|
|
69
|
+
}
|
|
70
|
+
const prefix = `${flag}=`;
|
|
71
|
+
if (arg.startsWith(prefix)) {
|
|
72
|
+
return arg.slice(prefix.length);
|
|
73
|
+
}
|
|
92
74
|
}
|
|
93
|
-
|
|
94
|
-
return `${dir}${prefix}_${direction}_${hash1}_${hash2}.sql`;
|
|
75
|
+
return undefined;
|
|
95
76
|
}
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
77
|
+
function resolveMigrationOptions(args) {
|
|
78
|
+
const serverUrl = getFlagValue(args, "--server-url") ?? process.env.JAZZ_SERVER_URL;
|
|
79
|
+
const adminSecret = getFlagValue(args, "--admin-secret") ?? process.env.JAZZ_ADMIN_SECRET;
|
|
80
|
+
const migrationsDir = resolve(process.cwd(), getFlagValue(args, "--migrations-dir") ?? join(process.cwd(), "migrations"));
|
|
81
|
+
if (!serverUrl) {
|
|
82
|
+
throw new Error("Missing server URL. Pass --server-url <url> or set JAZZ_SERVER_URL.");
|
|
83
|
+
}
|
|
84
|
+
if (!adminSecret) {
|
|
85
|
+
throw new Error("Missing admin secret. Pass --admin-secret <secret> or set JAZZ_ADMIN_SECRET.");
|
|
101
86
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (
|
|
87
|
+
return {
|
|
88
|
+
serverUrl,
|
|
89
|
+
adminSecret,
|
|
90
|
+
migrationsDir,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function resolvePermissionsOptions(args) {
|
|
94
|
+
const serverUrl = getFlagValue(args, "--server-url") ?? process.env.JAZZ_SERVER_URL;
|
|
95
|
+
const adminSecret = getFlagValue(args, "--admin-secret") ?? process.env.JAZZ_ADMIN_SECRET;
|
|
96
|
+
const schemaDir = resolve(process.cwd(), getFlagValue(args, "--schema-dir") ?? process.cwd());
|
|
97
|
+
if (!serverUrl) {
|
|
98
|
+
throw new Error("Missing server URL. Pass --server-url <url> or set JAZZ_SERVER_URL.");
|
|
99
|
+
}
|
|
100
|
+
if (!adminSecret) {
|
|
101
|
+
throw new Error("Missing admin secret. Pass --admin-secret <secret> or set JAZZ_ADMIN_SECRET.");
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
serverUrl,
|
|
105
|
+
adminSecret,
|
|
106
|
+
schemaDir,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function normalizeSchemaHashInput(hash, label) {
|
|
110
|
+
const normalized = hash.trim().toLowerCase();
|
|
111
|
+
if (!/^[0-9a-f]{12,64}$/.test(normalized)) {
|
|
112
|
+
throw new Error(`${label} must be a 12-64 character lowercase hex schema hash.`);
|
|
113
|
+
}
|
|
114
|
+
return normalized;
|
|
115
|
+
}
|
|
116
|
+
function shortSchemaHash(hash) {
|
|
117
|
+
return normalizeSchemaHashInput(hash, "schema hash").slice(0, SHORT_SCHEMA_HASH_LENGTH);
|
|
118
|
+
}
|
|
119
|
+
function hashMatchesFullSchema(hash, fullHash) {
|
|
120
|
+
return fullHash.startsWith(normalizeSchemaHashInput(hash, "schema hash"));
|
|
121
|
+
}
|
|
122
|
+
function resolveKnownSchemaHash(hash, label, knownHashes) {
|
|
123
|
+
const normalized = normalizeSchemaHashInput(hash, label);
|
|
124
|
+
if (normalized.length === 64) {
|
|
125
|
+
if (!knownHashes.includes(normalized)) {
|
|
126
|
+
throw new Error(`No stored schema found for ${label} ${normalized}.`);
|
|
127
|
+
}
|
|
128
|
+
return normalized;
|
|
129
|
+
}
|
|
130
|
+
const matches = knownHashes.filter((candidate) => candidate.startsWith(normalized));
|
|
131
|
+
if (matches.length === 0) {
|
|
132
|
+
throw new Error(`No stored schema found for ${label} prefix ${normalized}.`);
|
|
133
|
+
}
|
|
134
|
+
if (matches.length > 1) {
|
|
135
|
+
throw new Error(`${label} prefix ${normalized} is ambiguous: ${matches
|
|
136
|
+
.map((candidate) => shortSchemaHash(candidate))
|
|
137
|
+
.join(", ")}`);
|
|
138
|
+
}
|
|
139
|
+
return matches[0];
|
|
140
|
+
}
|
|
141
|
+
function columnTypeSignature(columnType) {
|
|
142
|
+
return JSON.stringify(columnType);
|
|
143
|
+
}
|
|
144
|
+
function columnsEqual(left, right) {
|
|
145
|
+
return (left.name === right.name &&
|
|
146
|
+
left.nullable === right.nullable &&
|
|
147
|
+
left.references === right.references &&
|
|
148
|
+
columnTypeSignature(left.column_type) === columnTypeSignature(right.column_type));
|
|
149
|
+
}
|
|
150
|
+
function tableSchemasEqual(left, right) {
|
|
151
|
+
if (!left || !right) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
if (left.columns.length !== right.columns.length) {
|
|
113
155
|
return false;
|
|
114
156
|
}
|
|
115
|
-
|
|
116
|
-
return Object.keys(opPolicy).every((key) => key === "using" || key === "with_check");
|
|
157
|
+
return left.columns.every((column, index) => columnsEqual(column, right.columns[index]));
|
|
117
158
|
}
|
|
118
|
-
function
|
|
119
|
-
|
|
159
|
+
function wasmSchemasEqual(left, right) {
|
|
160
|
+
const leftTableNames = Object.keys(left).sort();
|
|
161
|
+
const rightTableNames = Object.keys(right).sort();
|
|
162
|
+
if (leftTableNames.length !== rightTableNames.length) {
|
|
120
163
|
return false;
|
|
121
164
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return Object.entries(tablePolicy).every(([key, value]) => {
|
|
125
|
-
if (!validOperationKeys.includes(key)) {
|
|
165
|
+
return leftTableNames.every((tableName, index) => {
|
|
166
|
+
if (tableName !== rightTableNames[index]) {
|
|
126
167
|
return false;
|
|
127
168
|
}
|
|
128
|
-
return
|
|
169
|
+
return tableSchemasEqual(left[tableName], right[tableName]);
|
|
129
170
|
});
|
|
130
171
|
}
|
|
131
|
-
function
|
|
132
|
-
|
|
133
|
-
|
|
172
|
+
function changedTableNames(fromSchema, toSchema) {
|
|
173
|
+
const names = new Set([...Object.keys(fromSchema), ...Object.keys(toSchema)]);
|
|
174
|
+
return [...names].filter((tableName) => !tableSchemasEqual(fromSchema[tableName], toSchema[tableName]));
|
|
175
|
+
}
|
|
176
|
+
function ensurePermissionsProject(compiled) {
|
|
177
|
+
if (!compiled.permissions || !compiled.permissionsFile) {
|
|
178
|
+
throw new Error("No permissions.ts found for this app. Create permissions.ts before using permissions commands.");
|
|
134
179
|
}
|
|
135
|
-
return
|
|
180
|
+
return compiled;
|
|
136
181
|
}
|
|
137
|
-
async function
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
182
|
+
async function resolveStoredStructuralSchemaHash(serverUrl, adminSecret, wasmSchema) {
|
|
183
|
+
const { hashes } = await fetchSchemaHashes(serverUrl, { adminSecret });
|
|
184
|
+
const storedSchemas = await Promise.all(hashes.map(async (hash) => ({
|
|
185
|
+
hash,
|
|
186
|
+
schema: (await fetchStoredWasmSchema(serverUrl, { adminSecret, schemaHash: hash })).schema,
|
|
187
|
+
})));
|
|
188
|
+
const match = storedSchemas.find(({ schema }) => wasmSchemasEqual(schema, wasmSchema));
|
|
189
|
+
if (!match) {
|
|
190
|
+
throw new Error("No stored structural schema matches the local schema.ts. Publish the structural schema before pushing permissions.");
|
|
143
191
|
}
|
|
144
|
-
|
|
145
|
-
|
|
192
|
+
return match.hash;
|
|
193
|
+
}
|
|
194
|
+
function pickWitnessSchema(schema, tableNames) {
|
|
195
|
+
return Object.fromEntries(tableNames
|
|
196
|
+
.filter((tableName) => schema[tableName])
|
|
197
|
+
.map((tableName) => [tableName, schema[tableName]]));
|
|
198
|
+
}
|
|
199
|
+
function indentBlock(text, indent) {
|
|
200
|
+
const prefix = " ".repeat(indent);
|
|
201
|
+
return text
|
|
202
|
+
.split("\n")
|
|
203
|
+
.map((line) => (line.length === 0 ? line : `${prefix}${line}`))
|
|
204
|
+
.join("\n");
|
|
205
|
+
}
|
|
206
|
+
function baseBuilderExpression(columnType, references) {
|
|
207
|
+
switch (columnType.type) {
|
|
208
|
+
case "Text":
|
|
209
|
+
return "s.string()";
|
|
210
|
+
case "Boolean":
|
|
211
|
+
return "s.boolean()";
|
|
212
|
+
case "Integer":
|
|
213
|
+
return "s.int()";
|
|
214
|
+
case "Double":
|
|
215
|
+
return "s.float()";
|
|
216
|
+
case "Timestamp":
|
|
217
|
+
return "s.timestamp()";
|
|
218
|
+
case "Bytea":
|
|
219
|
+
return "s.bytes()";
|
|
220
|
+
case "Json":
|
|
221
|
+
return columnType.schema ? `s.json(${JSON.stringify(columnType.schema)})` : "s.json()";
|
|
222
|
+
case "Enum":
|
|
223
|
+
return `s.enum(${columnType.variants.map((variant) => JSON.stringify(variant)).join(", ")})`;
|
|
224
|
+
case "Uuid":
|
|
225
|
+
if (!references) {
|
|
226
|
+
throw new Error("Migration stub generation does not yet support bare UUID columns.");
|
|
227
|
+
}
|
|
228
|
+
return `s.ref(${JSON.stringify(references)})`;
|
|
229
|
+
case "Array":
|
|
230
|
+
return `s.array(${baseBuilderExpression(columnType.element, references)})`;
|
|
231
|
+
case "BigInt":
|
|
232
|
+
throw new Error("Migration stub generation does not yet support BIGINT columns.");
|
|
233
|
+
case "Row":
|
|
234
|
+
throw new Error("Migration stub generation does not yet support row-valued columns.");
|
|
146
235
|
}
|
|
147
|
-
return candidate;
|
|
148
236
|
}
|
|
149
|
-
function
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
237
|
+
function builderExpressionForColumn(column) {
|
|
238
|
+
const base = baseBuilderExpression(column.column_type, column.references);
|
|
239
|
+
return column.nullable ? `${base}.optional()` : base;
|
|
240
|
+
}
|
|
241
|
+
function sqlTypeToWasmColumnType(sqlType) {
|
|
242
|
+
if (typeof sqlType === "string") {
|
|
243
|
+
switch (sqlType) {
|
|
244
|
+
case "TEXT":
|
|
245
|
+
return { type: "Text" };
|
|
246
|
+
case "BOOLEAN":
|
|
247
|
+
return { type: "Boolean" };
|
|
248
|
+
case "INTEGER":
|
|
249
|
+
return { type: "Integer" };
|
|
250
|
+
case "REAL":
|
|
251
|
+
return { type: "Double" };
|
|
252
|
+
case "TIMESTAMP":
|
|
253
|
+
return { type: "Timestamp" };
|
|
254
|
+
case "UUID":
|
|
255
|
+
return { type: "Uuid" };
|
|
256
|
+
case "BYTEA":
|
|
257
|
+
return { type: "Bytea" };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (sqlType.kind === "ENUM") {
|
|
261
|
+
return {
|
|
262
|
+
type: "Enum",
|
|
263
|
+
variants: [...sqlType.variants],
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
if (sqlType.kind === "JSON") {
|
|
267
|
+
return {
|
|
268
|
+
type: "Json",
|
|
269
|
+
schema: sqlType.schema,
|
|
270
|
+
};
|
|
154
271
|
}
|
|
155
272
|
return {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
273
|
+
type: "Array",
|
|
274
|
+
element: sqlTypeToWasmColumnType(sqlType.element),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function serializeForwardLenses(forward) {
|
|
278
|
+
return forward.map((tableLens) => ({
|
|
279
|
+
table: tableLens.table,
|
|
280
|
+
operations: tableLens.operations.map((op) => {
|
|
281
|
+
if (op.type === "rename") {
|
|
282
|
+
return op;
|
|
160
283
|
}
|
|
284
|
+
const columnType = sqlTypeToWasmColumnType(op.sqlType);
|
|
285
|
+
const value = encodePublishedMigrationValue(toValue(op.value, columnType));
|
|
161
286
|
return {
|
|
162
|
-
|
|
163
|
-
|
|
287
|
+
type: op.type,
|
|
288
|
+
column: op.column,
|
|
289
|
+
columnType,
|
|
290
|
+
value,
|
|
164
291
|
};
|
|
165
292
|
}),
|
|
166
|
-
};
|
|
293
|
+
}));
|
|
167
294
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
295
|
+
function renderSchemaWitness(schema) {
|
|
296
|
+
const tableEntries = Object.entries(schema)
|
|
297
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
298
|
+
.map(([tableName, tableSchema]) => {
|
|
299
|
+
const columnLines = tableSchema.columns.map((column) => `${JSON.stringify(column.name)}: ${builderExpressionForColumn(column)},`);
|
|
300
|
+
return `${JSON.stringify(tableName)}: s.table({\n${indentBlock(columnLines.join("\n"), 2)}\n})`;
|
|
301
|
+
});
|
|
302
|
+
if (tableEntries.length === 0) {
|
|
303
|
+
return "{}";
|
|
175
304
|
}
|
|
176
|
-
|
|
177
|
-
|
|
305
|
+
return `{\n${indentBlock(tableEntries.join(",\n"), 2)}\n}`;
|
|
306
|
+
}
|
|
307
|
+
function renderArrayElementExpression(columnType, references) {
|
|
308
|
+
return baseBuilderExpression(columnType, references);
|
|
309
|
+
}
|
|
310
|
+
function renderAddOperationExpression(column, defaultExpression) {
|
|
311
|
+
switch (column.column_type.type) {
|
|
312
|
+
case "Text":
|
|
313
|
+
return `s.add.string({ default: ${defaultExpression} })`;
|
|
314
|
+
case "Boolean":
|
|
315
|
+
return `s.add.boolean({ default: ${defaultExpression} })`;
|
|
316
|
+
case "Integer":
|
|
317
|
+
return `s.add.int({ default: ${defaultExpression} })`;
|
|
318
|
+
case "Double":
|
|
319
|
+
return `s.add.float({ default: ${defaultExpression} })`;
|
|
320
|
+
case "Timestamp":
|
|
321
|
+
return `s.add.timestamp({ default: ${defaultExpression} })`;
|
|
322
|
+
case "Bytea":
|
|
323
|
+
return `s.add.bytes({ default: ${defaultExpression} })`;
|
|
324
|
+
case "Json":
|
|
325
|
+
return column.column_type.schema
|
|
326
|
+
? `s.add.json({ default: ${defaultExpression}, schema: ${JSON.stringify(column.column_type.schema)} })`
|
|
327
|
+
: `s.add.json({ default: ${defaultExpression} })`;
|
|
328
|
+
case "Enum":
|
|
329
|
+
return `s.add.enum(${column.column_type.variants
|
|
330
|
+
.map((variant) => JSON.stringify(variant))
|
|
331
|
+
.join(", ")}, { default: ${defaultExpression} })`;
|
|
332
|
+
case "Uuid":
|
|
333
|
+
if (column.references) {
|
|
334
|
+
return `s.add.ref(${JSON.stringify(column.references)}, { default: ${defaultExpression} })`;
|
|
335
|
+
}
|
|
336
|
+
return `s.add.ref("TODO_TABLE", { default: ${defaultExpression} })`;
|
|
337
|
+
case "Array":
|
|
338
|
+
return `s.add.array({ of: ${renderArrayElementExpression(column.column_type.element, column.references)}, default: ${defaultExpression} })`;
|
|
339
|
+
case "BigInt":
|
|
340
|
+
throw new Error("Migration stub generation does not yet support BIGINT columns.");
|
|
341
|
+
case "Row":
|
|
342
|
+
throw new Error("Migration stub generation does not yet support row-valued columns.");
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function renderDropOperationExpression(column, defaultExpression) {
|
|
346
|
+
switch (column.column_type.type) {
|
|
347
|
+
case "Text":
|
|
348
|
+
return `s.drop.string({ backwardsDefault: ${defaultExpression} })`;
|
|
349
|
+
case "Boolean":
|
|
350
|
+
return `s.drop.boolean({ backwardsDefault: ${defaultExpression} })`;
|
|
351
|
+
case "Integer":
|
|
352
|
+
return `s.drop.int({ backwardsDefault: ${defaultExpression} })`;
|
|
353
|
+
case "Double":
|
|
354
|
+
return `s.drop.float({ backwardsDefault: ${defaultExpression} })`;
|
|
355
|
+
case "Timestamp":
|
|
356
|
+
return `s.drop.timestamp({ backwardsDefault: ${defaultExpression} })`;
|
|
357
|
+
case "Bytea":
|
|
358
|
+
return `s.drop.bytes({ backwardsDefault: ${defaultExpression} })`;
|
|
359
|
+
case "Json":
|
|
360
|
+
return column.column_type.schema
|
|
361
|
+
? `s.drop.json({ backwardsDefault: ${defaultExpression}, schema: ${JSON.stringify(column.column_type.schema)} })`
|
|
362
|
+
: `s.drop.json({ backwardsDefault: ${defaultExpression} })`;
|
|
363
|
+
case "Enum":
|
|
364
|
+
return `s.drop.enum(${column.column_type.variants
|
|
365
|
+
.map((variant) => JSON.stringify(variant))
|
|
366
|
+
.join(", ")}, { backwardsDefault: ${defaultExpression} })`;
|
|
367
|
+
case "Uuid":
|
|
368
|
+
if (column.references) {
|
|
369
|
+
return `s.drop.ref(${JSON.stringify(column.references)}, { backwardsDefault: ${defaultExpression} })`;
|
|
370
|
+
}
|
|
371
|
+
return `s.drop.ref("TODO_TABLE", { backwardsDefault: ${defaultExpression} })`;
|
|
372
|
+
case "Array":
|
|
373
|
+
return `s.drop.array({ of: ${renderArrayElementExpression(column.column_type.element, column.references)}, backwardsDefault: ${defaultExpression} })`;
|
|
374
|
+
case "BigInt":
|
|
375
|
+
throw new Error("Migration stub generation does not yet support BIGINT columns.");
|
|
376
|
+
case "Row":
|
|
377
|
+
throw new Error("Migration stub generation does not yet support row-valued columns.");
|
|
178
378
|
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
379
|
+
}
|
|
380
|
+
function inferTableSuggestions(tableName, fromTable, toTable) {
|
|
381
|
+
const fromColumns = new Map(fromTable.columns.map((column) => [column.name, column]));
|
|
382
|
+
const toColumns = new Map(toTable.columns.map((column) => [column.name, column]));
|
|
383
|
+
const comments = [];
|
|
384
|
+
const properties = [];
|
|
385
|
+
const removedColumns = [...fromColumns.keys()].filter((name) => !toColumns.has(name));
|
|
386
|
+
const addedColumns = [...toColumns.keys()].filter((name) => !fromColumns.has(name));
|
|
387
|
+
if (removedColumns.length === 1 && addedColumns.length === 1) {
|
|
388
|
+
const removed = fromColumns.get(removedColumns[0]);
|
|
389
|
+
const added = toColumns.get(addedColumns[0]);
|
|
390
|
+
if (removed.nullable === added.nullable &&
|
|
391
|
+
removed.references === added.references &&
|
|
392
|
+
columnTypeSignature(removed.column_type) === columnTypeSignature(added.column_type)) {
|
|
393
|
+
comments.push(`Possible rename detected: ${JSON.stringify(removed.name)} -> ${JSON.stringify(added.name)}.`);
|
|
188
394
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
395
|
+
}
|
|
396
|
+
for (const columnName of addedColumns) {
|
|
397
|
+
const column = toColumns.get(columnName);
|
|
398
|
+
if (column.nullable) {
|
|
399
|
+
properties.push(`${JSON.stringify(columnName)}: ${renderAddOperationExpression(column, "null")},`);
|
|
192
400
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return null;
|
|
401
|
+
else {
|
|
402
|
+
comments.push(`Added required column ${JSON.stringify(columnName)} needs an explicit default.`);
|
|
196
403
|
}
|
|
197
|
-
currentDir = parentDir;
|
|
198
404
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
405
|
+
for (const columnName of removedColumns) {
|
|
406
|
+
const column = fromColumns.get(columnName);
|
|
407
|
+
if (column.nullable) {
|
|
408
|
+
properties.push(`${JSON.stringify(columnName)}: ${renderDropOperationExpression(column, "null")},`);
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
comments.push(`Removed required column ${JSON.stringify(columnName)} needs an explicit backwardsDefault.`);
|
|
412
|
+
}
|
|
204
413
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
414
|
+
return {
|
|
415
|
+
tableName,
|
|
416
|
+
comments,
|
|
417
|
+
properties,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
function renderMigrationBody(fromSchema, toSchema) {
|
|
421
|
+
const changedTables = changedTableNames(fromSchema, toSchema);
|
|
422
|
+
const migratableTables = changedTables.filter((tableName) => fromSchema[tableName] !== undefined && toSchema[tableName] !== undefined);
|
|
423
|
+
const witnessFrom = pickWitnessSchema(fromSchema, migratableTables);
|
|
424
|
+
const witnessTo = pickWitnessSchema(toSchema, migratableTables);
|
|
425
|
+
const lines = [];
|
|
426
|
+
for (const tableName of migratableTables) {
|
|
427
|
+
const fromTable = fromSchema[tableName];
|
|
428
|
+
const toTable = toSchema[tableName];
|
|
429
|
+
const suggestion = inferTableSuggestions(tableName, fromTable, toTable);
|
|
430
|
+
lines.push(`${JSON.stringify(tableName)}: {`);
|
|
431
|
+
for (const comment of suggestion.comments) {
|
|
432
|
+
lines.push(` // TODO: ${comment}`);
|
|
433
|
+
}
|
|
434
|
+
for (const property of suggestion.properties) {
|
|
435
|
+
lines.push(` ${property}`);
|
|
436
|
+
}
|
|
437
|
+
if (suggestion.comments.length === 0 && suggestion.properties.length === 0) {
|
|
438
|
+
lines.push(" // TODO: No safe migration steps were inferred automatically.");
|
|
439
|
+
}
|
|
440
|
+
lines.push("},");
|
|
441
|
+
lines.push("");
|
|
442
|
+
}
|
|
443
|
+
if (lines.length === 0) {
|
|
444
|
+
lines.push(changedTables.length === 0
|
|
445
|
+
? "// TODO: No schema differences were detected."
|
|
446
|
+
: "// TODO: No column-level migration steps were required for the detected schema changes.");
|
|
208
447
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
448
|
+
return {
|
|
449
|
+
body: lines.join("\n").trimEnd(),
|
|
450
|
+
witnessFrom,
|
|
451
|
+
witnessTo,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
async function packageVersion() {
|
|
455
|
+
const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
456
|
+
return packageJson.version ?? "unknown";
|
|
457
|
+
}
|
|
458
|
+
function createDateStamp(now = new Date()) {
|
|
459
|
+
const year = now.getFullYear();
|
|
460
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
461
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
462
|
+
return `${year}${month}${day}`;
|
|
463
|
+
}
|
|
464
|
+
function migrationFilename(migrationsDir, fromHash, toHash) {
|
|
465
|
+
return join(migrationsDir, `${createDateStamp()}-unnamed-${shortSchemaHash(fromHash)}-${shortSchemaHash(toHash)}.ts`);
|
|
466
|
+
}
|
|
467
|
+
function renderMigrationStub(input) {
|
|
468
|
+
const rendered = renderMigrationBody(input.fromSchema, input.toSchema);
|
|
469
|
+
return `import { schema as s } from "jazz-tools";
|
|
219
470
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
471
|
+
export default s.defineMigration({
|
|
472
|
+
migrate: {
|
|
473
|
+
${indentBlock(rendered.body, 4)}
|
|
474
|
+
},
|
|
475
|
+
fromHash: ${JSON.stringify(shortSchemaHash(input.fromHash))},
|
|
476
|
+
toHash: ${JSON.stringify(shortSchemaHash(input.toHash))},
|
|
477
|
+
from: ${renderSchemaWitness(rendered.witnessFrom)},
|
|
478
|
+
to: ${renderSchemaWitness(rendered.witnessTo)},
|
|
224
479
|
});
|
|
225
480
|
`;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
481
|
+
}
|
|
482
|
+
function isDefinedMigration(value) {
|
|
483
|
+
if (typeof value !== "object" || value === null) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
const candidate = value;
|
|
487
|
+
return (typeof candidate.fromHash === "string" &&
|
|
488
|
+
typeof candidate.toHash === "string" &&
|
|
489
|
+
typeof candidate.from === "object" &&
|
|
490
|
+
candidate.from !== null &&
|
|
491
|
+
typeof candidate.to === "object" &&
|
|
492
|
+
candidate.to !== null &&
|
|
493
|
+
Array.isArray(candidate.forward));
|
|
494
|
+
}
|
|
495
|
+
async function loadDefinedMigration(filePath) {
|
|
496
|
+
const url = pathToFileURL(filePath).href + `?v=${++importCounter}`;
|
|
497
|
+
const loaded = (await import(url));
|
|
498
|
+
const migration = loaded.default ?? loaded.migration;
|
|
499
|
+
if (!isDefinedMigration(migration)) {
|
|
500
|
+
throw new Error(`Invalid migration export in ${basename(filePath)}. Export default defineMigration(...).`);
|
|
501
|
+
}
|
|
502
|
+
return migration;
|
|
503
|
+
}
|
|
504
|
+
async function findMigrationFile(migrationsDir, fromHash, toHash) {
|
|
505
|
+
const fromShortHash = shortSchemaHash(fromHash);
|
|
506
|
+
const toShortHash = shortSchemaHash(toHash);
|
|
507
|
+
const files = await readdir(migrationsDir);
|
|
508
|
+
const matches = files
|
|
509
|
+
.filter((file) => file.endsWith(".ts"))
|
|
510
|
+
.filter((file) => file.includes(`-${fromShortHash}-${toShortHash}.ts`) ||
|
|
511
|
+
file.includes(`-${fromHash}-${toHash}.ts`));
|
|
512
|
+
if (matches.length === 0) {
|
|
513
|
+
throw new Error(`No migration file found in ${migrationsDir} for ${fromHash} -> ${toHash}.`);
|
|
514
|
+
}
|
|
515
|
+
if (matches.length > 1) {
|
|
516
|
+
throw new Error(`Multiple migration files found for ${fromHash} -> ${toHash}: ${matches.join(", ")}`);
|
|
517
|
+
}
|
|
518
|
+
return join(migrationsDir, matches[0]);
|
|
519
|
+
}
|
|
520
|
+
export async function createMigration(options) {
|
|
521
|
+
const { hashes } = await fetchSchemaHashes(options.serverUrl, {
|
|
522
|
+
adminSecret: options.adminSecret,
|
|
241
523
|
});
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
524
|
+
const fromHash = resolveKnownSchemaHash(options.fromHash, "fromHash", hashes);
|
|
525
|
+
const toHash = resolveKnownSchemaHash(options.toHash, "toHash", hashes);
|
|
526
|
+
await mkdir(options.migrationsDir, { recursive: true });
|
|
527
|
+
const [{ schema: fromSchema }, { schema: toSchema }] = await Promise.all([
|
|
528
|
+
fetchStoredWasmSchema(options.serverUrl, {
|
|
529
|
+
adminSecret: options.adminSecret,
|
|
530
|
+
schemaHash: fromHash,
|
|
531
|
+
}),
|
|
532
|
+
fetchStoredWasmSchema(options.serverUrl, {
|
|
533
|
+
adminSecret: options.adminSecret,
|
|
534
|
+
schemaHash: toHash,
|
|
535
|
+
}),
|
|
536
|
+
]);
|
|
537
|
+
const filePath = migrationFilename(options.migrationsDir, fromHash, toHash);
|
|
538
|
+
if (await pathExists(filePath)) {
|
|
539
|
+
throw new Error(`Migration stub already exists: ${filePath}`);
|
|
540
|
+
}
|
|
541
|
+
const stub = renderMigrationStub({ fromHash, toHash, fromSchema, toSchema });
|
|
542
|
+
await writeFile(filePath, stub);
|
|
543
|
+
const version = await packageVersion();
|
|
544
|
+
console.log(`Generated: ${filePath}`);
|
|
545
|
+
console.log("");
|
|
546
|
+
console.log("Migration stubs are only for structural schema changes.");
|
|
547
|
+
console.log(PERMISSIONS_LIFECYCLE_NOTE);
|
|
548
|
+
console.log("");
|
|
549
|
+
console.log("Next steps:");
|
|
550
|
+
console.log("1. Fill in migrate.");
|
|
551
|
+
console.log("2. Rename the file by replacing 'unnamed'.");
|
|
552
|
+
console.log(`3. Run npx jazz-tools@${version} migrations push ${shortSchemaHash(fromHash)} ${shortSchemaHash(toHash)}`);
|
|
553
|
+
return filePath;
|
|
554
|
+
}
|
|
555
|
+
export async function pushMigration(options) {
|
|
556
|
+
const { hashes } = await fetchSchemaHashes(options.serverUrl, {
|
|
557
|
+
adminSecret: options.adminSecret,
|
|
558
|
+
});
|
|
559
|
+
const fromHash = resolveKnownSchemaHash(options.fromHash, "fromHash", hashes);
|
|
560
|
+
const toHash = resolveKnownSchemaHash(options.toHash, "toHash", hashes);
|
|
561
|
+
const filePath = await findMigrationFile(options.migrationsDir, fromHash, toHash);
|
|
562
|
+
const migration = await loadDefinedMigration(filePath);
|
|
563
|
+
if (!hashMatchesFullSchema(migration.fromHash, fromHash) ||
|
|
564
|
+
!hashMatchesFullSchema(migration.toHash, toHash)) {
|
|
565
|
+
throw new Error(`Migration ${basename(filePath)} exports ${migration.fromHash} -> ${migration.toHash}, expected ${shortSchemaHash(fromHash)} -> ${shortSchemaHash(toHash)}.`);
|
|
566
|
+
}
|
|
567
|
+
schemaDefinitionToAst(migration.from);
|
|
568
|
+
schemaDefinitionToAst(migration.to);
|
|
569
|
+
if (migration.forward.length === 0) {
|
|
570
|
+
throw new Error(`Migration ${basename(filePath)} has no steps. Fill in migrate before push.`);
|
|
571
|
+
}
|
|
572
|
+
const forward = serializeForwardLenses(migration.forward);
|
|
573
|
+
await publishStoredMigration(options.serverUrl, {
|
|
574
|
+
adminSecret: options.adminSecret,
|
|
575
|
+
fromHash,
|
|
576
|
+
toHash,
|
|
577
|
+
forward,
|
|
578
|
+
});
|
|
579
|
+
console.log(`Pushed migration ${shortSchemaHash(fromHash)} -> ${shortSchemaHash(toHash)} from ${basename(filePath)}.`);
|
|
580
|
+
}
|
|
581
|
+
function describePermissionsHead(head) {
|
|
582
|
+
return `v${head.version} on ${shortSchemaHash(head.schemaHash)}`;
|
|
583
|
+
}
|
|
584
|
+
export async function permissionsStatus(options) {
|
|
585
|
+
const compiled = ensurePermissionsProject(await loadCompiledSchema(options.schemaDir));
|
|
586
|
+
const localSchemaHash = await resolveStoredStructuralSchemaHash(options.serverUrl, options.adminSecret, compiled.wasmSchema);
|
|
587
|
+
const { head } = await fetchPermissionsHead(options.serverUrl, {
|
|
588
|
+
adminSecret: options.adminSecret,
|
|
589
|
+
});
|
|
590
|
+
console.log(`Loaded structural schema from ${compiled.schemaFile}.`);
|
|
591
|
+
console.log(`Loaded current permissions from ${compiled.permissionsFile}.`);
|
|
592
|
+
console.log(`Local structural schema matches stored hash ${shortSchemaHash(localSchemaHash)}.`);
|
|
593
|
+
console.log(PERMISSIONS_LIFECYCLE_NOTE);
|
|
594
|
+
if (!head) {
|
|
595
|
+
console.log("Server has no published permissions head yet.");
|
|
596
|
+
console.log("Next push will publish version 1.");
|
|
246
597
|
return;
|
|
247
598
|
}
|
|
248
|
-
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
if (monorepoJazzPath) {
|
|
252
|
-
console.log(`jazz-tools binary not found at '${jazzBin}'. Using monorepo binary at '${monorepoJazzPath}'`);
|
|
253
|
-
return runJazzBuild(monorepoJazzPath, schemaDir, false);
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
console.warn(`jazz-tools binary not found at '${jazzBin}'. Use --jazz-bin to specify the path.\n` +
|
|
257
|
-
`Versioned schemas will not be generated.`);
|
|
258
|
-
}
|
|
599
|
+
console.log(`Server permissions head is ${describePermissionsHead(head)}.`);
|
|
600
|
+
if (head.schemaHash === localSchemaHash) {
|
|
601
|
+
console.log("Current server permissions already target this structural schema.");
|
|
259
602
|
}
|
|
260
|
-
|
|
603
|
+
else {
|
|
604
|
+
console.log(`Current server permissions target ${shortSchemaHash(head.schemaHash)}; pushing will retarget the head to ${shortSchemaHash(localSchemaHash)}.`);
|
|
605
|
+
}
|
|
606
|
+
console.log(`Next push will require parent bundle ${head.bundleObjectId}.`);
|
|
261
607
|
}
|
|
262
|
-
export async function
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
608
|
+
export async function pushPermissions(options) {
|
|
609
|
+
const compiled = ensurePermissionsProject(await loadCompiledSchema(options.schemaDir));
|
|
610
|
+
const localSchemaHash = await resolveStoredStructuralSchemaHash(options.serverUrl, options.adminSecret, compiled.wasmSchema);
|
|
611
|
+
const { head: currentHead } = await fetchPermissionsHead(options.serverUrl, {
|
|
612
|
+
adminSecret: options.adminSecret,
|
|
613
|
+
});
|
|
614
|
+
const { head: publishedHead } = await publishStoredPermissions(options.serverUrl, {
|
|
615
|
+
adminSecret: options.adminSecret,
|
|
616
|
+
schemaHash: localSchemaHash,
|
|
617
|
+
permissions: compiled.permissions,
|
|
618
|
+
expectedParentBundleObjectId: currentHead?.bundleObjectId ?? null,
|
|
619
|
+
});
|
|
620
|
+
console.log(`Loaded structural schema from ${compiled.schemaFile}.`);
|
|
621
|
+
console.log(`Loaded current permissions from ${compiled.permissionsFile}.`);
|
|
622
|
+
console.log(`Resolved structural schema hash ${shortSchemaHash(localSchemaHash)}.`);
|
|
623
|
+
if (currentHead) {
|
|
624
|
+
console.log(`Publishing from parent ${describePermissionsHead(currentHead)}.`);
|
|
267
625
|
}
|
|
268
|
-
|
|
269
|
-
console.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
process.exit(1);
|
|
280
|
-
}
|
|
281
|
-
let schema = await loadSchema(schemaFile);
|
|
282
|
-
const tablesWithInlinePolicies = schema.tables
|
|
283
|
-
.filter((table) => table.policies)
|
|
284
|
-
.map((t) => t.name);
|
|
285
|
-
if (tablesWithInlinePolicies.length > 0) {
|
|
286
|
-
throw new Error("Inline table permissions in current.ts are no longer supported. " +
|
|
287
|
-
"Move policies to schema/permissions.ts. " +
|
|
288
|
-
`Tables: ${tablesWithInlinePolicies.join(", ")}.`);
|
|
289
|
-
}
|
|
290
|
-
// Generate app.ts before loading permissions.ts so permissions can import it for typing.
|
|
291
|
-
await generateAppTs(schemaDir, schema);
|
|
292
|
-
const permissionsFile = join(schemaDir, "permissions.ts");
|
|
293
|
-
if (await pathExists(permissionsFile)) {
|
|
294
|
-
const permissions = await loadPermissionsModule(permissionsFile);
|
|
295
|
-
schema = mergePermissionsIntoSchema(schema, permissions);
|
|
296
|
-
}
|
|
297
|
-
await generateSqlForSchemaFile(schemaFile, schema);
|
|
298
|
-
await generateAppTs(schemaDir, schema);
|
|
299
|
-
await ensurePermissionsTestStub(schemaDir);
|
|
300
|
-
await runJazzBuild(jazzBin, schemaDir);
|
|
626
|
+
else {
|
|
627
|
+
console.log("Publishing first permissions head for this app.");
|
|
628
|
+
}
|
|
629
|
+
const nextHead = publishedHead ?? {
|
|
630
|
+
schemaHash: localSchemaHash,
|
|
631
|
+
version: currentHead ? currentHead.version + 1 : 1,
|
|
632
|
+
parentBundleObjectId: currentHead?.bundleObjectId ?? null,
|
|
633
|
+
bundleObjectId: currentHead?.bundleObjectId ?? "",
|
|
634
|
+
};
|
|
635
|
+
console.log(`Published permissions head ${describePermissionsHead(nextHead)}.`);
|
|
636
|
+
console.log(PERMISSIONS_LIFECYCLE_NOTE);
|
|
301
637
|
}
|
|
302
638
|
function isMainModule() {
|
|
303
639
|
const entry = process.argv[1];
|
|
@@ -307,22 +643,88 @@ function isMainModule() {
|
|
|
307
643
|
return pathToFileURL(entry).href === import.meta.url;
|
|
308
644
|
}
|
|
309
645
|
if (isMainModule()) {
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
console.
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
646
|
+
const command = process.argv[2] ?? "";
|
|
647
|
+
if (command === "validate") {
|
|
648
|
+
const { options } = parseArgs();
|
|
649
|
+
validate(options).catch((err) => {
|
|
650
|
+
console.error(err.message);
|
|
651
|
+
process.exit(1);
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
else if (command === "schema") {
|
|
655
|
+
const subcommand = process.argv[3] ?? "";
|
|
656
|
+
if (subcommand !== "export") {
|
|
657
|
+
console.error("Usage: node dist/cli.js schema export [--schema-dir <path>] [--format json]");
|
|
658
|
+
process.exit(1);
|
|
659
|
+
}
|
|
660
|
+
const args = process.argv.slice(4);
|
|
661
|
+
const schemaDir = getFlagValue(args, "--schema-dir") ?? process.cwd();
|
|
662
|
+
const formatValue = getFlagValue(args, "--format") ?? "json";
|
|
663
|
+
if (formatValue !== "json") {
|
|
664
|
+
console.error(`Unsupported schema export format: ${formatValue}`);
|
|
665
|
+
process.exit(1);
|
|
666
|
+
}
|
|
667
|
+
exportSchema({ schemaDir, format: "json" }).catch((err) => {
|
|
668
|
+
console.error(err.message);
|
|
669
|
+
process.exit(1);
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
else if (command === "migrations") {
|
|
673
|
+
const subcommand = process.argv[3] ?? "";
|
|
674
|
+
const fromHash = process.argv[4];
|
|
675
|
+
const toHash = process.argv[5];
|
|
676
|
+
const sharedArgs = process.argv.slice(6);
|
|
677
|
+
if (!fromHash || !toHash) {
|
|
678
|
+
console.error("Usage: node dist/cli.js migrations <create|push> <fromHash> <toHash> [options]");
|
|
679
|
+
process.exit(1);
|
|
680
|
+
}
|
|
681
|
+
const options = resolveMigrationOptions(sharedArgs);
|
|
682
|
+
const task = subcommand === "create"
|
|
683
|
+
? createMigration({ ...options, fromHash, toHash })
|
|
684
|
+
: subcommand === "push"
|
|
685
|
+
? pushMigration({ ...options, fromHash, toHash })
|
|
686
|
+
: Promise.reject(new Error("Usage: node dist/cli.js migrations <create|push> <fromHash> <toHash> [options]"));
|
|
687
|
+
task.catch((err) => {
|
|
688
|
+
console.error(err.message);
|
|
689
|
+
process.exit(1);
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
else if (command === "permissions") {
|
|
693
|
+
const subcommand = process.argv[3] ?? "";
|
|
694
|
+
const options = resolvePermissionsOptions(process.argv.slice(4));
|
|
695
|
+
const task = subcommand === "status"
|
|
696
|
+
? permissionsStatus(options)
|
|
697
|
+
: subcommand === "push"
|
|
698
|
+
? pushPermissions(options)
|
|
699
|
+
: Promise.reject(new Error("Usage: node dist/cli.js permissions <status|push> [options]"));
|
|
700
|
+
task.catch((err) => {
|
|
701
|
+
console.error(err.message);
|
|
702
|
+
process.exit(1);
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
console.log("Usage: node <path-to-jazz-tools>/dist/cli.js <command> [options]");
|
|
707
|
+
console.log("\nCommands:");
|
|
708
|
+
console.log(" validate Validate root schema.ts and optional permissions.ts");
|
|
709
|
+
console.log(" schema export Print the compiled structural schema as JSON");
|
|
710
|
+
console.log(" permissions status Show the current server permissions head for this app");
|
|
711
|
+
console.log(" permissions push Publish the current permissions.ts with head-parent checks");
|
|
712
|
+
console.log(" migrations create Generate a typed structural migration stub from two known schema hashes");
|
|
713
|
+
console.log(" migrations push Push a reviewed migration edge to the server");
|
|
714
|
+
console.log("\nValidation options:");
|
|
715
|
+
console.log(" --schema-dir <path> Path to app root containing schema.ts (default: .)");
|
|
716
|
+
console.log("\nSchema export options:");
|
|
717
|
+
console.log(" --schema-dir <path> Path to app root containing schema.ts (default: .)");
|
|
718
|
+
console.log(" --format json Output the compiled schema as JSON");
|
|
719
|
+
console.log("\nPermissions options:");
|
|
720
|
+
console.log(" --schema-dir <path> Path to app root containing schema.ts (default: .)");
|
|
721
|
+
console.log(" --server-url <url> Jazz server URL (or set JAZZ_SERVER_URL)");
|
|
722
|
+
console.log(" --admin-secret <sec> Admin secret (or set JAZZ_ADMIN_SECRET)");
|
|
723
|
+
console.log("\nMigration options:");
|
|
724
|
+
console.log(" --server-url <url> Jazz server URL (or set JAZZ_SERVER_URL)");
|
|
725
|
+
console.log(" --admin-secret <sec> Admin secret (or set JAZZ_ADMIN_SECRET)");
|
|
726
|
+
console.log(" --migrations-dir <p> Path to migrations directory (default: ./migrations)");
|
|
727
|
+
process.exit(command ? 1 : 0);
|
|
326
728
|
}
|
|
327
729
|
}
|
|
328
730
|
//# sourceMappingURL=cli.js.map
|