marcidb-client 0.1.1 → 0.3.0
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.
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/bin.js
CHANGED
|
@@ -8,23 +8,25 @@ import fs from "node:fs";
|
|
|
8
8
|
|
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
11
|
+
// Platform suffix for the prebuilt binaries. `marci-generate` generates TS types,
|
|
12
|
+
// `marci-migrate` — migration files (a separate binary after the migration refactor).
|
|
13
|
+
const PLATFORM = {
|
|
14
|
+
"linux-x64": "linux-x64",
|
|
15
|
+
"darwin-arm64": "darwin-arm64",
|
|
16
|
+
"win32-x64": "win32-x64.exe",
|
|
15
17
|
};
|
|
16
18
|
|
|
17
|
-
function binPath() {
|
|
19
|
+
function binPath(base) {
|
|
18
20
|
const key = `${os.platform()}-${os.arch()}`;
|
|
19
|
-
const
|
|
20
|
-
if (!
|
|
21
|
+
const suffix = PLATFORM[key];
|
|
22
|
+
if (!suffix) {
|
|
21
23
|
console.error(`marcidb: unsupported platform ${key}`);
|
|
22
24
|
process.exit(1);
|
|
23
25
|
}
|
|
24
|
-
return path.join(__dirname, "bin",
|
|
26
|
+
return path.join(__dirname, "bin", `${base}-${suffix}`);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
//
|
|
29
|
+
// Parses positional arguments and flags (--flag value | --flag)
|
|
28
30
|
function parseArgs(args) {
|
|
29
31
|
const positional = [];
|
|
30
32
|
const flags = {};
|
|
@@ -56,7 +58,11 @@ Commands:
|
|
|
56
58
|
--name <name> migration name (default: migration)
|
|
57
59
|
--no-migrations types only, skip the migration file
|
|
58
60
|
|
|
59
|
-
migrate push <db> [options] Push migration files (.
|
|
61
|
+
migrate push <db> [options] Push migration files (.march) to a server; it applies the new ones
|
|
62
|
+
--url <url> server url (default: http://localhost:3000)
|
|
63
|
+
--migrations <dir> migrations directory (default: migrations)
|
|
64
|
+
|
|
65
|
+
migrate check <db> [options] Compare the server's current snapshot with your latest migration
|
|
60
66
|
--url <url> server url (default: http://localhost:3000)
|
|
61
67
|
--migrations <dir> migrations directory (default: migrations)
|
|
62
68
|
|
|
@@ -64,6 +70,7 @@ Examples:
|
|
|
64
70
|
marcidb generate
|
|
65
71
|
marcidb generate schema.marci --name add_users
|
|
66
72
|
marcidb migrate push myapp --url http://localhost:3000
|
|
73
|
+
marcidb migrate check myapp
|
|
67
74
|
`);
|
|
68
75
|
}
|
|
69
76
|
|
|
@@ -71,25 +78,24 @@ function cmdGenerate(args) {
|
|
|
71
78
|
const { positional, flags } = parseArgs(args);
|
|
72
79
|
const schema = positional[0] ?? "schema.marci";
|
|
73
80
|
const output = positional[1] ?? path.join(__dirname, "..", ".marcidb", "client");
|
|
74
|
-
const bin = binPath();
|
|
75
81
|
|
|
76
|
-
// 1) TypeScript
|
|
82
|
+
// 1) TypeScript types (always — without them a migration is pointless)
|
|
77
83
|
if (fs.existsSync(output)) {
|
|
78
84
|
fs.rmSync(output, { recursive: true, force: true });
|
|
79
85
|
}
|
|
80
86
|
console.log(`Generating types from ${schema} into ${output}...`);
|
|
81
|
-
execFileSync(
|
|
87
|
+
execFileSync(binPath("marci-generate"), ["types", schema, output], { stdio: "inherit" });
|
|
82
88
|
fs.writeFileSync(path.join(output, "package.json"), `{
|
|
83
89
|
"name": "marci-client",
|
|
84
90
|
"main": "index.js",
|
|
85
91
|
"types": "index.d.ts"
|
|
86
92
|
}`);
|
|
87
93
|
|
|
88
|
-
// 2)
|
|
94
|
+
// 2) Migration file (full snapshot of the version) — via the separate marci-migrate binary
|
|
89
95
|
if (!flags["no-migrations"]) {
|
|
90
96
|
const dir = flags.migrations ?? "migrations";
|
|
91
97
|
const name = flags.name ?? "migration";
|
|
92
|
-
execFileSync(
|
|
98
|
+
execFileSync(binPath("marci-migrate"), ["generate", schema, dir, name], { stdio: "inherit" });
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
console.log("Done.");
|
|
@@ -98,54 +104,77 @@ function cmdGenerate(args) {
|
|
|
98
104
|
async function cmdMigrate(args) {
|
|
99
105
|
const [sub, ...rest] = args;
|
|
100
106
|
if (sub === "push") return migratePush(rest);
|
|
101
|
-
|
|
107
|
+
if (sub === "check") return migrateCheck(rest);
|
|
108
|
+
console.error(`marcidb migrate: unknown subcommand "${sub ?? ""}" (expected "push" or "check")`);
|
|
102
109
|
help();
|
|
103
110
|
process.exit(1);
|
|
104
111
|
}
|
|
105
112
|
|
|
106
|
-
//
|
|
113
|
+
// The server's current snapshot ("" if the DB doesn't exist yet)
|
|
114
|
+
async function fetchServerSnapshot(url, db) {
|
|
115
|
+
const res = await fetch(`${url}/${db}/$snapshot`);
|
|
116
|
+
if (res.ok) return await res.text();
|
|
117
|
+
if (res.status === 404) return "";
|
|
118
|
+
console.error(`Cannot read server snapshot [${res.status}]: ${await res.text()}`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Smart client: marci-migrate plan (STDIN = server snapshot) → actions of the not-yet-applied migrations
|
|
123
|
+
function planActions(dir, serverSnapshot) {
|
|
124
|
+
try {
|
|
125
|
+
return { out: execFileSync(binPath("marci-migrate"), ["plan", dir], { input: serverSnapshot, encoding: "utf8" }) };
|
|
126
|
+
} catch (e) {
|
|
127
|
+
return { error: (e.stderr || e.message || "").toString().trim() };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Push: the client computes the unapplied tail and sends it to the dumb $migrate (the server just applies it)
|
|
107
132
|
async function migratePush(rest) {
|
|
108
133
|
const { positional, flags } = parseArgs(rest);
|
|
109
134
|
const db = positional[0];
|
|
110
|
-
if (!db) {
|
|
111
|
-
console.error("marcidb migrate push: <db> name required");
|
|
112
|
-
process.exit(1);
|
|
113
|
-
}
|
|
135
|
+
if (!db) { console.error("marcidb migrate push: <db> name required"); process.exit(1); }
|
|
114
136
|
|
|
115
137
|
const url = (flags.url ?? "http://localhost:3000").replace(/\/+$/, "");
|
|
116
138
|
const dir = flags.migrations ?? "migrations";
|
|
117
|
-
|
|
118
139
|
if (!fs.existsSync(dir)) {
|
|
119
140
|
console.error(`marcidb migrate push: migrations directory "${dir}" not found — run "marcidb generate" first`);
|
|
120
141
|
process.exit(1);
|
|
121
142
|
}
|
|
122
143
|
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
.map((f) => ({ id: f.replace(/\.mig$/, ""), ops: fs.readFileSync(path.join(dir, f), "utf8") }));
|
|
128
|
-
|
|
129
|
-
if (migrations.length === 0) {
|
|
130
|
-
console.log("No migration files — nothing to push");
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
144
|
+
const serverSnap = await fetchServerSnapshot(url, db);
|
|
145
|
+
const plan = planActions(dir, serverSnap);
|
|
146
|
+
if (plan.error !== undefined) { console.error(`Migration planning failed: ${plan.error}`); process.exit(1); }
|
|
147
|
+
if (!plan.out.trim()) { console.log(`'${db}' is up to date — nothing to push`); return; }
|
|
133
148
|
|
|
134
149
|
const res = await fetch(`${url}/${db}/$migrate`, {
|
|
135
150
|
method: "POST",
|
|
136
|
-
headers: { "Content-Type": "
|
|
137
|
-
body:
|
|
151
|
+
headers: { "Content-Type": "text/plain" },
|
|
152
|
+
body: plan.out,
|
|
138
153
|
});
|
|
154
|
+
if (!res.ok) { console.error(`Migration failed [${res.status}]: ${await res.text()}`); process.exit(1); }
|
|
155
|
+
console.log(`Applied pending migrations to '${db}'`);
|
|
156
|
+
}
|
|
139
157
|
|
|
140
|
-
|
|
141
|
-
|
|
158
|
+
// Check: whether the server matches the latest local migration (same planner: empty plan = matched)
|
|
159
|
+
async function migrateCheck(rest) {
|
|
160
|
+
const { positional, flags } = parseArgs(rest);
|
|
161
|
+
const db = positional[0];
|
|
162
|
+
if (!db) { console.error("marcidb migrate check: <db> name required"); process.exit(1); }
|
|
163
|
+
|
|
164
|
+
const url = (flags.url ?? "http://localhost:3000").replace(/\/+$/, "");
|
|
165
|
+
const dir = flags.migrations ?? "migrations";
|
|
166
|
+
|
|
167
|
+
const serverSnap = await fetchServerSnapshot(url, db);
|
|
168
|
+
const plan = planActions(dir, serverSnap);
|
|
169
|
+
if (plan.error !== undefined) {
|
|
170
|
+
console.error(`'${db}' has DIVERGED — the server schema isn't a point in your migration history.`);
|
|
142
171
|
process.exit(1);
|
|
143
172
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
console.log(`Applied ${applied.length} migration(s) to '${db}': ${applied.join(", ")}`);
|
|
173
|
+
if (!plan.out.trim()) {
|
|
174
|
+
console.log(`'${db}' matches your latest migration ✓`);
|
|
147
175
|
} else {
|
|
148
|
-
console.
|
|
176
|
+
console.error(`'${db}' is behind — there are pending migrations (run "marcidb migrate push ${db}").`);
|
|
177
|
+
process.exit(1);
|
|
149
178
|
}
|
|
150
179
|
}
|
|
151
180
|
|
package/package.json
CHANGED
package/runtime/index.js
CHANGED
|
@@ -13,7 +13,19 @@ async function request(method, url, body) {
|
|
|
13
13
|
}
|
|
14
14
|
function encodeId(id) {
|
|
15
15
|
if (typeof id === "object" && id !== null) {
|
|
16
|
-
|
|
16
|
+
const parts = [];
|
|
17
|
+
const walk = (obj, prefix) => {
|
|
18
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
19
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
20
|
+
if (typeof v === "object" && v !== null) {
|
|
21
|
+
walk(v, key);
|
|
22
|
+
} else {
|
|
23
|
+
parts.push(`${key}=${encodeURIComponent(String(v))}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
walk(id, "");
|
|
28
|
+
return parts.join("&");
|
|
17
29
|
}
|
|
18
30
|
return String(id);
|
|
19
31
|
}
|