marcidb-client 0.1.0 → 0.2.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,14 +58,19 @@ 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
|
|
61
|
+
migrate push <db> [options] Push migration files (.march) to a server; it applies the new ones
|
|
60
62
|
--url <url> server url (default: http://localhost:3000)
|
|
61
|
-
--
|
|
63
|
+
--migrations <dir> migrations directory (default: migrations)
|
|
64
|
+
|
|
65
|
+
migrate check <db> [options] Compare the server's current snapshot with your latest migration
|
|
66
|
+
--url <url> server url (default: http://localhost:3000)
|
|
67
|
+
--migrations <dir> migrations directory (default: migrations)
|
|
62
68
|
|
|
63
69
|
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.");
|
|
@@ -97,34 +103,79 @@ function cmdGenerate(args) {
|
|
|
97
103
|
|
|
98
104
|
async function cmdMigrate(args) {
|
|
99
105
|
const [sub, ...rest] = args;
|
|
100
|
-
if (sub
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
if (sub === "push") return migratePush(rest);
|
|
107
|
+
if (sub === "check") return migrateCheck(rest);
|
|
108
|
+
console.error(`marcidb migrate: unknown subcommand "${sub ?? ""}" (expected "push" or "check")`);
|
|
109
|
+
help();
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
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() };
|
|
104
128
|
}
|
|
129
|
+
}
|
|
105
130
|
|
|
131
|
+
// Push: the client computes the unapplied tail and sends it to the dumb $migrate (the server just applies it)
|
|
132
|
+
async function migratePush(rest) {
|
|
106
133
|
const { positional, flags } = parseArgs(rest);
|
|
107
134
|
const db = positional[0];
|
|
108
|
-
if (!db) {
|
|
109
|
-
|
|
135
|
+
if (!db) { console.error("marcidb migrate push: <db> name required"); process.exit(1); }
|
|
136
|
+
|
|
137
|
+
const url = (flags.url ?? "http://localhost:3000").replace(/\/+$/, "");
|
|
138
|
+
const dir = flags.migrations ?? "migrations";
|
|
139
|
+
if (!fs.existsSync(dir)) {
|
|
140
|
+
console.error(`marcidb migrate push: migrations directory "${dir}" not found — run "marcidb generate" first`);
|
|
110
141
|
process.exit(1);
|
|
111
142
|
}
|
|
112
143
|
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
|
|
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; }
|
|
116
148
|
|
|
117
149
|
const res = await fetch(`${url}/${db}/$migrate`, {
|
|
118
150
|
method: "POST",
|
|
119
151
|
headers: { "Content-Type": "text/plain" },
|
|
120
|
-
body:
|
|
152
|
+
body: plan.out,
|
|
121
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
|
+
}
|
|
157
|
+
|
|
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";
|
|
122
166
|
|
|
123
|
-
|
|
124
|
-
|
|
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.`);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
if (!plan.out.trim()) {
|
|
174
|
+
console.log(`'${db}' matches your latest migration ✓`);
|
|
175
|
+
} else {
|
|
176
|
+
console.error(`'${db}' is behind — there are pending migrations (run "marcidb migrate push ${db}").`);
|
|
125
177
|
process.exit(1);
|
|
126
178
|
}
|
|
127
|
-
console.log(`Migrated '${db}' on ${url}`);
|
|
128
179
|
}
|
|
129
180
|
|
|
130
181
|
const COMMANDS = {
|
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
|
}
|