@madewithremy/admin 0.1.46 → 0.1.47
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/CHANGELOG.md +12 -0
- package/dist/index.d.ts +95 -5
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -1
- package/dist/prod.js +143 -12
- package/package.json +1 -1
package/dist/prod.js
CHANGED
|
@@ -2138,6 +2138,29 @@ function query2(ctx, sql) {
|
|
|
2138
2138
|
{ queries: [{ sql }] }
|
|
2139
2139
|
);
|
|
2140
2140
|
}
|
|
2141
|
+
function history(ctx, params = {}) {
|
|
2142
|
+
return call(
|
|
2143
|
+
ctx,
|
|
2144
|
+
"GET",
|
|
2145
|
+
`/_internal/v2/apps/${ctx.appId}/db/history${qs({ limit: params.limit, cursor: params.cursor })}`
|
|
2146
|
+
);
|
|
2147
|
+
}
|
|
2148
|
+
function historyQuery(ctx, releaseId, sql) {
|
|
2149
|
+
return call(
|
|
2150
|
+
ctx,
|
|
2151
|
+
"POST",
|
|
2152
|
+
`/_internal/v2/apps/${ctx.appId}/db/history/query`,
|
|
2153
|
+
{ releaseId, queries: [{ sql }] }
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
function historyAttach(ctx, releaseId, sql) {
|
|
2157
|
+
return call(
|
|
2158
|
+
ctx,
|
|
2159
|
+
"POST",
|
|
2160
|
+
`/_internal/v2/apps/${ctx.appId}/db/history/attach`,
|
|
2161
|
+
{ releaseId, queries: [{ sql }] }
|
|
2162
|
+
);
|
|
2163
|
+
}
|
|
2141
2164
|
|
|
2142
2165
|
// src/commands/db.ts
|
|
2143
2166
|
var dbSpecs = {
|
|
@@ -2150,11 +2173,46 @@ var dbSpecs = {
|
|
|
2150
2173
|
},
|
|
2151
2174
|
"db tables": {
|
|
2152
2175
|
usage: "Usage: remy-admin db tables"
|
|
2176
|
+
},
|
|
2177
|
+
"db history": {
|
|
2178
|
+
usage: "Usage: remy-admin db history [--limit 20] [--cursor <nextCursor>]",
|
|
2179
|
+
flags: {
|
|
2180
|
+
limit: { type: "number", min: 1, max: 100 },
|
|
2181
|
+
cursor: { type: "string" }
|
|
2182
|
+
}
|
|
2183
|
+
},
|
|
2184
|
+
// raw, as `db query` is: the SQL is taken verbatim.
|
|
2185
|
+
"db history query": {
|
|
2186
|
+
usage: "Usage: remy-admin db history query <releaseId> <sql>",
|
|
2187
|
+
raw: true,
|
|
2188
|
+
positionals: [
|
|
2189
|
+
{ name: "releaseId", required: true },
|
|
2190
|
+
{ name: "sql", required: true }
|
|
2191
|
+
]
|
|
2192
|
+
},
|
|
2193
|
+
"db history attach": {
|
|
2194
|
+
usage: "Usage: remy-admin db history attach <releaseId> <sql>",
|
|
2195
|
+
raw: true,
|
|
2196
|
+
positionals: [
|
|
2197
|
+
{ name: "releaseId", required: true },
|
|
2198
|
+
{ name: "sql", required: true }
|
|
2199
|
+
]
|
|
2153
2200
|
}
|
|
2154
2201
|
};
|
|
2155
2202
|
async function dbQuery(ctx, a) {
|
|
2156
2203
|
out(await query2(ctx, a.req("sql")));
|
|
2157
2204
|
}
|
|
2205
|
+
async function dbHistory(ctx, a) {
|
|
2206
|
+
out(
|
|
2207
|
+
await history(ctx, { limit: a.num("limit"), cursor: a.str("cursor") })
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
2210
|
+
async function dbHistoryQuery(ctx, a) {
|
|
2211
|
+
out(await historyQuery(ctx, a.req("releaseId"), a.req("sql")));
|
|
2212
|
+
}
|
|
2213
|
+
async function dbHistoryAttach(ctx, a) {
|
|
2214
|
+
out(await historyAttach(ctx, a.req("releaseId"), a.req("sql")));
|
|
2215
|
+
}
|
|
2158
2216
|
async function dbTables(ctx) {
|
|
2159
2217
|
out(
|
|
2160
2218
|
await query2(
|
|
@@ -2165,28 +2223,91 @@ async function dbTables(ctx) {
|
|
|
2165
2223
|
}
|
|
2166
2224
|
var dbHandlers = {
|
|
2167
2225
|
"db query": dbQuery,
|
|
2168
|
-
"db tables": dbTables
|
|
2226
|
+
"db tables": dbTables,
|
|
2227
|
+
"db history": dbHistory,
|
|
2228
|
+
"db history query": dbHistoryQuery,
|
|
2229
|
+
"db history attach": dbHistoryAttach
|
|
2169
2230
|
};
|
|
2170
|
-
var dbHelp = `remy-admin db \u2014 Query the production database.
|
|
2231
|
+
var dbHelp = `remy-admin db \u2014 Query the production database, and the copies of it past releases kept.
|
|
2171
2232
|
|
|
2172
2233
|
Subcommands:
|
|
2173
|
-
query
|
|
2174
|
-
tables
|
|
2234
|
+
query Execute a SQL query against the live release's database
|
|
2235
|
+
tables List all tables in the database
|
|
2236
|
+
history List past releases' databases, newest first, with when each was live
|
|
2237
|
+
history query Read a past release's database (read-only)
|
|
2238
|
+
history attach Run SQL against live with a past release's database attached as "history"
|
|
2175
2239
|
|
|
2176
2240
|
Usage:
|
|
2177
2241
|
remy-admin db <sql>
|
|
2178
2242
|
remy-admin db query <sql>
|
|
2179
2243
|
remy-admin db tables
|
|
2244
|
+
remy-admin db history [--limit 20] [--cursor <nextCursor>]
|
|
2245
|
+
remy-admin db history query <releaseId> <sql>
|
|
2246
|
+
remy-admin db history attach <releaseId> <sql>
|
|
2247
|
+
|
|
2248
|
+
Past releases:
|
|
2249
|
+
Every deploy copies the live database into the new release and keeps the
|
|
2250
|
+
release it replaces, database and all, exactly as it stood at that moment.
|
|
2251
|
+
So each entry 'db history' lists holds live as of its liveUntil: anything
|
|
2252
|
+
written after that is only in later copies and in live, and nothing that
|
|
2253
|
+
happened between two deploys is kept anywhere. The release live now is
|
|
2254
|
+
listed first (current: true); its database is live, so use 'db query'.
|
|
2255
|
+
|
|
2256
|
+
'db history query' reads one of those copies, and nothing can change it: a
|
|
2257
|
+
write is refused. Its schema is that release's, which may differ from
|
|
2258
|
+
live's: SELECT name FROM sqlite_master lists its tables, and
|
|
2259
|
+
PRAGMA table_info(<table>) a table's columns.
|
|
2260
|
+
|
|
2261
|
+
'db history attach' runs against live with the copy attached as the schema
|
|
2262
|
+
"history": history.<table> is the past table, and an unqualified name is
|
|
2263
|
+
live's. Writes to live are real, exactly as with 'db query', and a
|
|
2264
|
+
statement runs as one transaction. The attached copy is private to the
|
|
2265
|
+
call, so writing into it changes nothing anywhere.
|
|
2266
|
+
PRAGMA history.table_info(<table>) shows a past table's columns.
|
|
2267
|
+
|
|
2268
|
+
Recovering data is a merge, not a rollback. Live has moved on since the
|
|
2269
|
+
copy was made (rows added, edits people meant to make), so decide what
|
|
2270
|
+
comes back: which rows, which columns, and what happens to anything
|
|
2271
|
+
written since.
|
|
2272
|
+
- Find the copy that has what's missing: the windows say where to look,
|
|
2273
|
+
and a count or a few rows confirm it.
|
|
2274
|
+
- Compare it with live through attach (a join on id, a count of what
|
|
2275
|
+
differs) before changing anything.
|
|
2276
|
+
- Write the repair to restore exactly that. Columns emptied in live,
|
|
2277
|
+
filled back by id, leaving rows created since alone:
|
|
2278
|
+
UPDATE orders SET note = h.note, total = h.total
|
|
2279
|
+
FROM history.orders h
|
|
2280
|
+
WHERE h.id = orders.id AND orders.note IS NULL
|
|
2281
|
+
Rows deleted from live, put back:
|
|
2282
|
+
INSERT INTO orders (id, note, total)
|
|
2283
|
+
SELECT id, note, total FROM history.orders
|
|
2284
|
+
WHERE id NOT IN (SELECT id FROM orders)
|
|
2285
|
+
- Tell the user what will change and get their go-ahead before writing
|
|
2286
|
+
live, as with any change to production.
|
|
2287
|
+
If no copy covers the moment that matters, say so plainly rather than
|
|
2288
|
+
restoring from the nearest one.
|
|
2289
|
+
|
|
2290
|
+
Notes:
|
|
2291
|
+
- SQL is taken verbatim: 'db query' and the two history commands that take
|
|
2292
|
+
SQL don't parse flags, so a statement may safely begin with a '--'
|
|
2293
|
+
comment. Always quote the SQL.
|
|
2294
|
+
- The history commands use the database named as live's default.
|
|
2295
|
+
- The first history command on a copy fetches it, so it takes longer for a
|
|
2296
|
+
large database; the ones after it are quick.
|
|
2297
|
+
- Errors: release_not_found (no release of this app that was ever live has
|
|
2298
|
+
that id), release_is_live (it's the live release: use 'db query'),
|
|
2299
|
+
no_database (that release has no database of live's name), sql_error
|
|
2300
|
+
(the database refused the statement, a write to a past copy included;
|
|
2301
|
+
nothing in it was applied).
|
|
2180
2302
|
|
|
2181
2303
|
Examples:
|
|
2182
2304
|
remy-admin db tables
|
|
2183
2305
|
remy-admin db "SELECT * FROM users LIMIT 10"
|
|
2184
2306
|
remy-admin db "INSERT INTO categories (name) VALUES ('Electronics')"
|
|
2185
2307
|
remy-admin db query "SELECT * FROM users LIMIT 10"
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
-
|
|
2189
|
-
a statement may safely begin with a '--' comment. Always quote the SQL.`;
|
|
2308
|
+
remy-admin db history --limit 5
|
|
2309
|
+
remy-admin db history query <releaseId> "SELECT count(*) AS n FROM orders"
|
|
2310
|
+
remy-admin db history attach <releaseId> "SELECT count(*) AS emptied FROM orders o JOIN history.orders h ON h.id = o.id WHERE o.note IS NULL AND h.note IS NOT NULL"`;
|
|
2190
2311
|
|
|
2191
2312
|
// src/ops/secrets.ts
|
|
2192
2313
|
function list6(ctx) {
|
|
@@ -3544,8 +3665,9 @@ Notes:
|
|
|
3544
3665
|
of the store that holds objects; in a store holding both, a key is found
|
|
3545
3666
|
wherever it is, and ls asks which.
|
|
3546
3667
|
- Handing someone a large or sensitive file? Use a PRIVATE store + \`sign\`:
|
|
3547
|
-
the link is unguessable, expires (--ttl seconds, max 7 days
|
|
3548
|
-
|
|
3668
|
+
the link is unguessable, expires (--ttl seconds, max 7 days, or 3 hours
|
|
3669
|
+
where it's a storage presign; expiresAt says which), and the object stays
|
|
3670
|
+
deletable with \`rm\`. Never use the account media CDN
|
|
3549
3671
|
(\`mindstudio upload\`) for sensitive material \u2014 those URLs are public and
|
|
3550
3672
|
permanent.
|
|
3551
3673
|
- put: the key defaults to a content hash (sha256) of the bytes, so
|
|
@@ -9819,7 +9941,7 @@ Commands:
|
|
|
9819
9941
|
diagnostics View the post-deploy Lighthouse audit (scores, issues, raw report)
|
|
9820
9942
|
domains Subdomain, custom domains, and buying a domain
|
|
9821
9943
|
users Manage app users and roles
|
|
9822
|
-
db Query the production database
|
|
9944
|
+
db Query the production database, and past releases' copies of it
|
|
9823
9945
|
secrets Manage app secrets (env vars)
|
|
9824
9946
|
methods List and invoke methods
|
|
9825
9947
|
data Sync databases between dev and live (lift-from-dev / lift-from-live)
|
|
@@ -9943,6 +10065,15 @@ ${guide}`;
|
|
|
9943
10065
|
}
|
|
9944
10066
|
return void 0;
|
|
9945
10067
|
}
|
|
10068
|
+
function dbWordsEnd(argv) {
|
|
10069
|
+
if (argv[1] === "query") {
|
|
10070
|
+
return 3;
|
|
10071
|
+
}
|
|
10072
|
+
if (argv[1] === "history") {
|
|
10073
|
+
return argv[2] === "query" || argv[2] === "attach" ? 4 : 3;
|
|
10074
|
+
}
|
|
10075
|
+
return 2;
|
|
10076
|
+
}
|
|
9946
10077
|
function resolveHelp(argv) {
|
|
9947
10078
|
const group = argv[0];
|
|
9948
10079
|
if (!group || group === "--help" || group === "-h") {
|
|
@@ -9955,7 +10086,7 @@ function resolveHelp(argv) {
|
|
|
9955
10086
|
if (argv.length === 1) {
|
|
9956
10087
|
return groupHelp;
|
|
9957
10088
|
}
|
|
9958
|
-
const scan = group === "db" ? argv.slice(1, argv
|
|
10089
|
+
const scan = group === "db" ? argv.slice(1, dbWordsEnd(argv)) : argv.slice(1);
|
|
9959
10090
|
if (!scan.some((t) => t === "--help" || t === "-h")) {
|
|
9960
10091
|
return void 0;
|
|
9961
10092
|
}
|