eraspace 0.2.0 → 0.4.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.
- package/README.md +7 -1
- package/bin.js +562 -208
- package/package.json +1 -1
- package/sdk/src/client/client.ts +14 -8
- package/sdk/src/client/config.ts +17 -1
- package/sdk/src/client/connection.ts +67 -12
- package/sdk/src/client/files.ts +36 -9
- package/sdk/src/client/index.ts +1 -0
- package/sdk/src/client/router.ts +1 -1
- package/sdk/src/client/storage.ts +31 -0
- package/sdk/src/client/sub-key.ts +19 -0
- package/sdk/src/client/subscription.ts +12 -1
- package/sdk/src/client/transport.ts +82 -15
- package/sdk/src/server/index.ts +41 -2
- package/templates/chat/client/index.tsx +12 -4
- package/templates/poll/client/index.tsx +1 -1
- package/templates/todo/client/index.tsx +1 -1
package/bin.js
CHANGED
|
@@ -241,6 +241,34 @@ __export(Effect_exports, {
|
|
|
241
241
|
zipWith: () => zipWith3
|
|
242
242
|
});
|
|
243
243
|
|
|
244
|
+
// node_modules/.pnpm/effect@4.0.5/node_modules/effect/dist/Context.js
|
|
245
|
+
var Context_exports = {};
|
|
246
|
+
__export(Context_exports, {
|
|
247
|
+
Reference: () => Reference,
|
|
248
|
+
Service: () => Service,
|
|
249
|
+
ServiceTypeId: () => ServiceTypeId,
|
|
250
|
+
add: () => add,
|
|
251
|
+
addOrOmit: () => addOrOmit,
|
|
252
|
+
addUnsafe: () => addUnsafe,
|
|
253
|
+
empty: () => empty,
|
|
254
|
+
get: () => get,
|
|
255
|
+
getOption: () => getOption,
|
|
256
|
+
getOrElse: () => getOrElse2,
|
|
257
|
+
getOrUndefined: () => getOrUndefined2,
|
|
258
|
+
getOrUndefinedUnsafe: () => getOrUndefinedUnsafe,
|
|
259
|
+
getUnsafe: () => getUnsafe,
|
|
260
|
+
hasSameCache: () => hasSameCache,
|
|
261
|
+
isContext: () => isContext,
|
|
262
|
+
isKey: () => isKey,
|
|
263
|
+
isReference: () => isReference,
|
|
264
|
+
make: () => make5,
|
|
265
|
+
makeUnsafe: () => makeUnsafe,
|
|
266
|
+
merge: () => merge,
|
|
267
|
+
mergeAll: () => mergeAll,
|
|
268
|
+
omit: () => omit,
|
|
269
|
+
pick: () => pick
|
|
270
|
+
});
|
|
271
|
+
|
|
244
272
|
// node_modules/.pnpm/effect@4.0.5/node_modules/effect/dist/Pipeable.js
|
|
245
273
|
var pipeArguments = (self, args2) => {
|
|
246
274
|
switch (args2.length) {
|
|
@@ -2273,6 +2301,7 @@ var Proto = {
|
|
|
2273
2301
|
};
|
|
2274
2302
|
var hasSameCache = (self, that) => self.cacheRoot === that.cacheRoot;
|
|
2275
2303
|
var isContext = (u) => hasProperty(u, TypeId3);
|
|
2304
|
+
var isKey = (u) => hasProperty(u, ServiceTypeId);
|
|
2276
2305
|
var isReference = (u) => !!u[ReferenceTypeId];
|
|
2277
2306
|
var empty = () => emptyContext2;
|
|
2278
2307
|
var emptyContext2 = /* @__PURE__ */ makeUnsafe(/* @__PURE__ */ new Map());
|
|
@@ -2293,6 +2322,12 @@ var addUnsafe = (self, key, service4) => {
|
|
|
2293
2322
|
parent: impl.overlay
|
|
2294
2323
|
}, impl.depth + 1);
|
|
2295
2324
|
};
|
|
2325
|
+
var addOrOmit = /* @__PURE__ */ dual(3, (self, key, service4) => service4._tag === "None" ? omit(key)(self) : add(self, key, service4.value));
|
|
2326
|
+
var getOrElse2 = /* @__PURE__ */ dual(3, (self, key, orElse6) => {
|
|
2327
|
+
const value4 = lookup(self, key.key);
|
|
2328
|
+
if (value4 !== notFound) return value4;
|
|
2329
|
+
return isReference(key) ? getDefaultValue(key) : orElse6();
|
|
2330
|
+
});
|
|
2296
2331
|
var getOrUndefined2 = /* @__PURE__ */ dual(2, (self, key) => getOrUndefinedUnsafe(self, key.key));
|
|
2297
2332
|
var getOrUndefinedUnsafe = (self, key) => {
|
|
2298
2333
|
const value4 = lookup(self, key);
|
|
@@ -2342,6 +2377,26 @@ var mergeAll = (...ctxs) => {
|
|
|
2342
2377
|
}
|
|
2343
2378
|
return makeUnsafe(map18);
|
|
2344
2379
|
};
|
|
2380
|
+
var pick = (...services) => (self) => {
|
|
2381
|
+
if (services.length <= 1) {
|
|
2382
|
+
const map18 = /* @__PURE__ */ new Map();
|
|
2383
|
+
if (services.length === 1) {
|
|
2384
|
+
const key = services[0].key;
|
|
2385
|
+
const value4 = lookup(self, key);
|
|
2386
|
+
if (value4 !== notFound) map18.set(key, value4);
|
|
2387
|
+
}
|
|
2388
|
+
return makeUnsafe(map18);
|
|
2389
|
+
}
|
|
2390
|
+
const keep = new Set(services.map((key) => key.key));
|
|
2391
|
+
return withFlat(self, (map18) => map18.forEach((_, key) => {
|
|
2392
|
+
if (!keep.has(key)) map18.delete(key);
|
|
2393
|
+
}));
|
|
2394
|
+
};
|
|
2395
|
+
var omit = (...keys4) => (self) => withFlat(self, (map18) => {
|
|
2396
|
+
for (let i = 0; i < keys4.length; i++) {
|
|
2397
|
+
map18.delete(keys4[i].key);
|
|
2398
|
+
}
|
|
2399
|
+
});
|
|
2345
2400
|
var Reference = Service;
|
|
2346
2401
|
|
|
2347
2402
|
// node_modules/.pnpm/effect@4.0.5/node_modules/effect/dist/Duration.js
|
|
@@ -2988,7 +3043,7 @@ var merge2 = /* @__PURE__ */ match3({
|
|
|
2988
3043
|
onFailure: identity,
|
|
2989
3044
|
onSuccess: identity
|
|
2990
3045
|
});
|
|
2991
|
-
var
|
|
3046
|
+
var getOrElse3 = /* @__PURE__ */ dual(2, (self, onFailure) => isFailure2(self) ? onFailure(self.failure) : self.success);
|
|
2992
3047
|
var getOrThrowWith2 = /* @__PURE__ */ dual(2, (self, onFailure) => {
|
|
2993
3048
|
if (isSuccess2(self)) {
|
|
2994
3049
|
return self.success;
|
|
@@ -16361,7 +16416,7 @@ function transformOptionalEffect(f) {
|
|
|
16361
16416
|
transform: f
|
|
16362
16417
|
});
|
|
16363
16418
|
}
|
|
16364
|
-
function
|
|
16419
|
+
function omit2() {
|
|
16365
16420
|
return transformOptional(() => none2());
|
|
16366
16421
|
}
|
|
16367
16422
|
function withDefault(defaultValue) {
|
|
@@ -18565,9 +18620,9 @@ function isPattern(regExp, annotations) {
|
|
|
18565
18620
|
});
|
|
18566
18621
|
}
|
|
18567
18622
|
var bodyOwners = /* @__PURE__ */ new WeakMap();
|
|
18568
|
-
function copy2(ast,
|
|
18569
|
-
const out = Object.assign(Object.create(Object.getPrototypeOf(ast)), ast,
|
|
18570
|
-
if (Reflect.ownKeys(
|
|
18623
|
+
function copy2(ast, changes3) {
|
|
18624
|
+
const out = Object.assign(Object.create(Object.getPrototypeOf(ast)), ast, changes3);
|
|
18625
|
+
if (Reflect.ownKeys(changes3).every((key) => key === "context" || key === "encoding")) {
|
|
18571
18626
|
bodyOwners.set(out, getContextOwner(ast));
|
|
18572
18627
|
}
|
|
18573
18628
|
return out;
|
|
@@ -24947,10 +25002,10 @@ function toFormatter(ast, options3) {
|
|
|
24947
25002
|
}
|
|
24948
25003
|
|
|
24949
25004
|
// node_modules/.pnpm/effect@4.0.5/node_modules/effect/dist/Struct.js
|
|
24950
|
-
var
|
|
25005
|
+
var pick2 = /* @__PURE__ */ dual(2, (self, keys4) => {
|
|
24951
25006
|
return buildStruct(self, (k, v) => hasPropertyKey(keys4, k) ? [k, v] : void 0);
|
|
24952
25007
|
});
|
|
24953
|
-
var
|
|
25008
|
+
var omit3 = /* @__PURE__ */ dual(2, (self, keys4) => {
|
|
24954
25009
|
return buildStruct(self, (k, v) => !hasPropertyKey(keys4, k) ? [k, v] : void 0);
|
|
24955
25010
|
});
|
|
24956
25011
|
var assign = /* @__PURE__ */ dual(2, (self, that) => {
|
|
@@ -25070,10 +25125,10 @@ var OptionalImpl = class {
|
|
|
25070
25125
|
this.replaceResult = replaceResult;
|
|
25071
25126
|
}
|
|
25072
25127
|
replace(a, s) {
|
|
25073
|
-
return
|
|
25128
|
+
return getOrElse3(this.replaceResult(a, s), () => s);
|
|
25074
25129
|
}
|
|
25075
25130
|
modify(f) {
|
|
25076
|
-
return (s) =>
|
|
25131
|
+
return (s) => getOrElse3(flatMap2(this.getResult(s), (a) => this.replaceResult(f(a), s)), () => s);
|
|
25077
25132
|
}
|
|
25078
25133
|
compose(that) {
|
|
25079
25134
|
return make36(compose3(this.node, that.node));
|
|
@@ -25121,14 +25176,14 @@ var OptionalImpl = class {
|
|
|
25121
25176
|
})));
|
|
25122
25177
|
}
|
|
25123
25178
|
pick(keys4) {
|
|
25124
|
-
return this.compose(makeLens(
|
|
25125
|
-
...
|
|
25179
|
+
return this.compose(makeLens(pick2(keys4), (p, a) => ({
|
|
25180
|
+
...omit3(a, keys4),
|
|
25126
25181
|
...p
|
|
25127
25182
|
})));
|
|
25128
25183
|
}
|
|
25129
25184
|
omit(keys4) {
|
|
25130
|
-
return this.compose(makeLens(
|
|
25131
|
-
...
|
|
25185
|
+
return this.compose(makeLens(omit3(keys4), (o, a) => ({
|
|
25186
|
+
...pick2(a, keys4),
|
|
25132
25187
|
...o
|
|
25133
25188
|
})));
|
|
25134
25189
|
}
|
|
@@ -25174,7 +25229,7 @@ var OptionalImpl = class {
|
|
|
25174
25229
|
);
|
|
25175
25230
|
}
|
|
25176
25231
|
modifyAll(f) {
|
|
25177
|
-
return (s) =>
|
|
25232
|
+
return (s) => getOrElse3(flatMap2(this.getResult(s), (as5) => this.replaceResult(as5.map(f), s)), () => s);
|
|
25178
25233
|
}
|
|
25179
25234
|
};
|
|
25180
25235
|
var IsoImpl = class extends OptionalImpl {
|
|
@@ -25213,7 +25268,7 @@ var PrismImpl = class extends OptionalImpl {
|
|
|
25213
25268
|
return this.set(a);
|
|
25214
25269
|
}
|
|
25215
25270
|
modify(f) {
|
|
25216
|
-
return (s) =>
|
|
25271
|
+
return (s) => getOrElse3(map2(this.getResult(s), (a) => this.set(f(a))), () => s);
|
|
25217
25272
|
}
|
|
25218
25273
|
};
|
|
25219
25274
|
function make36(node) {
|
|
@@ -26001,7 +26056,7 @@ function toIssueEffect(self) {
|
|
|
26001
26056
|
return catchCause3(self, (cause) => failCauseSync2(() => map6(cause, (error2) => error2.issue)));
|
|
26002
26057
|
}
|
|
26003
26058
|
function withDecodingDefaultKey(defaultValue, options3) {
|
|
26004
|
-
const encode5 = options3?.encodingStrategy === "omit" ?
|
|
26059
|
+
const encode5 = options3?.encodingStrategy === "omit" ? omit2() : passthrough2();
|
|
26005
26060
|
return (self) => {
|
|
26006
26061
|
return optionalKey2(toEncoded2(self)).pipe(decodeTo2(self, {
|
|
26007
26062
|
decode: withDefault(toIssueEffect(defaultValue)),
|
|
@@ -26015,7 +26070,7 @@ function withDecodingDefaultTypeKey(defaultValue, options3) {
|
|
|
26015
26070
|
};
|
|
26016
26071
|
}
|
|
26017
26072
|
function withDecodingDefault(defaultValue, options3) {
|
|
26018
|
-
const encode5 = options3?.encodingStrategy === "omit" ?
|
|
26073
|
+
const encode5 = options3?.encodingStrategy === "omit" ? omit2() : passthrough2();
|
|
26019
26074
|
return (self) => {
|
|
26020
26075
|
return optional2(toEncoded2(self)).pipe(decodeTo2(self, {
|
|
26021
26076
|
decode: withDefault(toIssueEffect(defaultValue)),
|
|
@@ -32824,15 +32879,15 @@ var TomlParser = class {
|
|
|
32824
32879
|
}
|
|
32825
32880
|
parseDate(token) {
|
|
32826
32881
|
const date = "\\d{4}-\\d{2}-\\d{2}";
|
|
32827
|
-
const
|
|
32828
|
-
if (new RegExp(`^${date}[Tt]${
|
|
32882
|
+
const time4 = "\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?";
|
|
32883
|
+
if (new RegExp(`^${date}[Tt]${time4}(?:[Zz]|[+-]\\d{2}:\\d{2})$`).test(token)) {
|
|
32829
32884
|
const value4 = new Date(token.replace("t", "T").replace("z", "Z"));
|
|
32830
32885
|
if (Number.isNaN(value4.getTime())) {
|
|
32831
32886
|
this.fail(`Invalid date-time '${token}'`);
|
|
32832
32887
|
}
|
|
32833
32888
|
return value4;
|
|
32834
32889
|
}
|
|
32835
|
-
if (new RegExp(`^${date}[Tt]${
|
|
32890
|
+
if (new RegExp(`^${date}[Tt]${time4}$`).test(token) || new RegExp(`^${date}$`).test(token) || new RegExp(`^${time4}$`).test(token)) {
|
|
32836
32891
|
return token.replace("t", "T");
|
|
32837
32892
|
}
|
|
32838
32893
|
return void 0;
|
|
@@ -38939,7 +38994,7 @@ var runWith2 = (command2, config2) => {
|
|
|
38939
38994
|
// cli/package.json
|
|
38940
38995
|
var package_default = {
|
|
38941
38996
|
name: "eraspace-cli",
|
|
38942
|
-
version: "0.
|
|
38997
|
+
version: "0.4.0",
|
|
38943
38998
|
private: true,
|
|
38944
38999
|
license: "MIT",
|
|
38945
39000
|
type: "module",
|
|
@@ -39259,12 +39314,54 @@ function findSpecifier(source, spec) {
|
|
|
39259
39314
|
}
|
|
39260
39315
|
function formatLog(l) {
|
|
39261
39316
|
const d = l.ts === void 0 ? /* @__PURE__ */ new Date() : new Date(typeof l.ts === "number" ? l.ts : Date.parse(l.ts));
|
|
39262
|
-
const
|
|
39317
|
+
const time4 = isNaN(d.getTime()) ? String(l.ts) : d.toISOString().slice(11, 23);
|
|
39263
39318
|
const level = (l.level ?? "info").toLowerCase();
|
|
39264
39319
|
const color = level === "error" ? c.red : level === "warn" ? c.yellow : level === "debug" ? c.gray : c.blue;
|
|
39265
39320
|
const fn3 = l.fn ? c.magenta(l.fn) + " " : "";
|
|
39266
39321
|
const data = l.data === void 0 || l.data === null ? "" : " " + c.gray(JSON.stringify(l.data));
|
|
39267
|
-
return `${c.gray(
|
|
39322
|
+
return `${c.gray(time4)} ${color(level.padEnd(5))} ${fn3}${l.msg ?? ""}${data}`;
|
|
39323
|
+
}
|
|
39324
|
+
var SKIPPED = {
|
|
39325
|
+
fetch: "uses fetch",
|
|
39326
|
+
identity: "reads the caller's identity",
|
|
39327
|
+
running: "was running",
|
|
39328
|
+
recovering: "cache recovering",
|
|
39329
|
+
"new index": "reads an index this deploy adds",
|
|
39330
|
+
committed: "a commit changed it during the check",
|
|
39331
|
+
budget: "out of time",
|
|
39332
|
+
limit: "too many entries",
|
|
39333
|
+
interrupted: "interrupted"
|
|
39334
|
+
};
|
|
39335
|
+
var call = (fn3, args2) => {
|
|
39336
|
+
const text2 = Array.isArray(args2) ? args2.map((a) => JSON.stringify(a)).join(", ") : JSON.stringify(args2);
|
|
39337
|
+
return `${fn3}(${text2 && text2.length > 80 ? `${text2.slice(0, 77)}...` : text2 ?? ""})`;
|
|
39338
|
+
};
|
|
39339
|
+
function formatShadow(report) {
|
|
39340
|
+
if (report.note) return c.dim(`Live queries: none checked (${report.note})`);
|
|
39341
|
+
const total = report.total ?? 0;
|
|
39342
|
+
if (total === 0) return c.dim("Live queries: none cached, nothing to check");
|
|
39343
|
+
const errors = report.errors ?? 0;
|
|
39344
|
+
const changed = report.changed ?? 0;
|
|
39345
|
+
const parts2 = [
|
|
39346
|
+
`${report.unchanged ?? 0} unchanged`,
|
|
39347
|
+
changed ? c.yellow(`${changed} change value`) : "0 change value",
|
|
39348
|
+
errors ? c.red(`${errors} now throw`) : "0 now throw",
|
|
39349
|
+
...report.fixed ? [c.green(`${report.fixed} fixed`)] : [],
|
|
39350
|
+
...report.skipped ? [c.dim(`${report.skipped} skipped`)] : []
|
|
39351
|
+
];
|
|
39352
|
+
const at = report.commitSeq ? ` at commit ${report.commitSeq}` : "";
|
|
39353
|
+
const lines2 = [`Live queries: ${report.checked ?? 0} of ${total} checked against the new code${at}: ${parts2.join(", ")}`];
|
|
39354
|
+
for (const e of report.entries ?? []) {
|
|
39355
|
+
const what = call(e.fn, e.args);
|
|
39356
|
+
if (e.outcome === "error") lines2.push(` ${c.red("throws ")} ${what}: ${e.error ? `${e.error.name}: ${e.error.message}` : ""}`);
|
|
39357
|
+
else if (e.outcome === "changed") lines2.push(` ${c.yellow("changes")} ${what}${e.error ? `: ${e.error.name}: ${e.error.message}` : ""}`);
|
|
39358
|
+
else if (e.outcome === "fixed") lines2.push(` ${c.green("fixed ")} ${what}`);
|
|
39359
|
+
else lines2.push(c.dim(` skipped ${what}: ${SKIPPED[e.reason ?? ""] ?? e.reason ?? ""}`));
|
|
39360
|
+
}
|
|
39361
|
+
const listed = report.entries?.length ?? 0;
|
|
39362
|
+
const notable = errors + changed + (report.fixed ?? 0) + (report.skipped ?? 0);
|
|
39363
|
+
if (notable > listed) lines2.push(c.dim(` ... and ${notable - listed} more`));
|
|
39364
|
+
return lines2.join("\n");
|
|
39268
39365
|
}
|
|
39269
39366
|
|
|
39270
39367
|
// cli/src/api.ts
|
|
@@ -39280,10 +39377,40 @@ var App = Schema_exports.Struct({
|
|
|
39280
39377
|
expiresAt: Schema_exports.optional(Timestamp),
|
|
39281
39378
|
publicInspection: Schema_exports.optional(Schema_exports.Boolean)
|
|
39282
39379
|
});
|
|
39380
|
+
var Count = Schema_exports.optional(Schema_exports.Number);
|
|
39381
|
+
var ShadowReport = Schema_exports.Struct({
|
|
39382
|
+
total: Count,
|
|
39383
|
+
checked: Count,
|
|
39384
|
+
unchanged: Count,
|
|
39385
|
+
changed: Count,
|
|
39386
|
+
errors: Count,
|
|
39387
|
+
fixed: Count,
|
|
39388
|
+
skipped: Count,
|
|
39389
|
+
commitSeq: Count,
|
|
39390
|
+
note: Schema_exports.optional(Schema_exports.String),
|
|
39391
|
+
entries: Schema_exports.optional(
|
|
39392
|
+
Schema_exports.Array(
|
|
39393
|
+
Schema_exports.Struct({
|
|
39394
|
+
fn: Schema_exports.String,
|
|
39395
|
+
args: Schema_exports.optional(Schema_exports.Unknown),
|
|
39396
|
+
outcome: Schema_exports.String,
|
|
39397
|
+
reason: Schema_exports.optional(Schema_exports.String),
|
|
39398
|
+
error: Schema_exports.optional(Schema_exports.Struct({ name: Schema_exports.String, message: Schema_exports.String }))
|
|
39399
|
+
})
|
|
39400
|
+
)
|
|
39401
|
+
)
|
|
39402
|
+
});
|
|
39283
39403
|
var Deployed = Schema_exports.Struct({
|
|
39284
39404
|
...App.fields,
|
|
39285
39405
|
version: Schema_exports.optional(Schema_exports.Number),
|
|
39286
|
-
claimToken: Schema_exports.optional(Schema_exports.String)
|
|
39406
|
+
claimToken: Schema_exports.optional(Schema_exports.String),
|
|
39407
|
+
shadow: Schema_exports.optional(ShadowReport),
|
|
39408
|
+
/** For a rollback: the kept version whose files were deployed again. */
|
|
39409
|
+
rolledBackTo: Schema_exports.optional(Schema_exports.Number)
|
|
39410
|
+
});
|
|
39411
|
+
var DeployHistory = Schema_exports.Struct({
|
|
39412
|
+
version: Schema_exports.optional(Schema_exports.Number),
|
|
39413
|
+
deploys: Schema_exports.optional(Schema_exports.Array(Schema_exports.Struct({ version: Schema_exports.Number, createdAt: Schema_exports.optional(Schema_exports.Number) })))
|
|
39287
39414
|
});
|
|
39288
39415
|
var AppList = Schema_exports.Struct({ apps: Schema_exports.Array(App) });
|
|
39289
39416
|
var StateChange = Schema_exports.Struct({ state: Schema_exports.String });
|
|
@@ -39300,7 +39427,7 @@ var Token2 = Schema_exports.Struct({
|
|
|
39300
39427
|
lastUsedAt: Schema_exports.optional(Timestamp)
|
|
39301
39428
|
});
|
|
39302
39429
|
var TokenList = Schema_exports.Struct({ tokens: Schema_exports.Array(Token2) });
|
|
39303
|
-
var CreatedToken = Schema_exports.Struct({ token: Schema_exports.String });
|
|
39430
|
+
var CreatedToken = Schema_exports.Struct({ token: Schema_exports.String, id: Schema_exports.String });
|
|
39304
39431
|
var DeviceStart = Schema_exports.Struct({
|
|
39305
39432
|
deviceCode: Schema_exports.String,
|
|
39306
39433
|
userCode: Schema_exports.String,
|
|
@@ -39448,15 +39575,20 @@ var login = Effect_exports.fnUntraced(function* (config2, browser) {
|
|
|
39448
39575
|
}
|
|
39449
39576
|
return yield* fail12("The sign-in code expired. Run eraspace auth login again.");
|
|
39450
39577
|
});
|
|
39578
|
+
var LogoutResult = Schema_exports.Struct({ ok: Schema_exports.Literal(true) });
|
|
39451
39579
|
var logout = Effect_exports.fnUntraced(function* (config2) {
|
|
39452
39580
|
const session = yield* sessionFor(config2.origin);
|
|
39453
39581
|
if (!session) {
|
|
39454
39582
|
yield* Console_exports.log(`Not signed in to ${config2.origin}.`);
|
|
39455
39583
|
return;
|
|
39456
39584
|
}
|
|
39457
|
-
yield*
|
|
39585
|
+
const response = yield* api(config2, "POST", "/_api/auth/logout", { bearer: Redacted_exports.make(session.token), timeout: "10 seconds" });
|
|
39586
|
+
if (response.status !== 401) {
|
|
39587
|
+
if (response.status >= 400 || response.json.ok === false) return yield* Effect_exports.fail(yield* failure(response));
|
|
39588
|
+
yield* decode5(LogoutResult, response.json, "sign-out");
|
|
39589
|
+
}
|
|
39458
39590
|
yield* removeSession(config2.origin);
|
|
39459
|
-
yield* Console_exports.log(`Signed out of ${config2.origin}.`);
|
|
39591
|
+
yield* Console_exports.log(response.status === 401 ? `Removed invalid session for ${config2.origin}.` : `Signed out of ${config2.origin}.`);
|
|
39460
39592
|
});
|
|
39461
39593
|
var okWhoami = ok(Whoami);
|
|
39462
39594
|
var whoami = Effect_exports.fnUntraced(function* (config2) {
|
|
@@ -39491,6 +39623,7 @@ function scanImports(file2, source) {
|
|
|
39491
39623
|
}
|
|
39492
39624
|
return { imports: imports.sort((a, b) => (a.at ?? 0) - (b.at ?? 0)) };
|
|
39493
39625
|
}
|
|
39626
|
+
var MAX_MODULE_BYTES = 256 * 1024;
|
|
39494
39627
|
var EXTENSIONS = [".ts", ".mts", ".js", ".mjs", ".tsx"];
|
|
39495
39628
|
function importCandidates(base) {
|
|
39496
39629
|
const stem = /\.m?js$/.test(base) ? base.replace(/\.m?js$/, "") : void 0;
|
|
@@ -39526,6 +39659,10 @@ var collectServerFiles = Effect_exports.fnUntraced(function* (root) {
|
|
|
39526
39659
|
const rel = toPosix(relative2(root, abs2));
|
|
39527
39660
|
const source = yield* fs.readFileString(abs2);
|
|
39528
39661
|
files[rel] = source;
|
|
39662
|
+
const bytes2 = Buffer.byteLength(source, "utf8");
|
|
39663
|
+
if (bytes2 > MAX_MODULE_BYTES) {
|
|
39664
|
+
errors.push({ file: rel, line: 0, column: 0, message: `module is ${bytes2} bytes, over the server's ${MAX_MODULE_BYTES} byte (256 KiB) limit; split it into smaller modules` });
|
|
39665
|
+
}
|
|
39529
39666
|
const { imports, error: error2 } = scanImports(abs2, source);
|
|
39530
39667
|
if (error2) {
|
|
39531
39668
|
errors.push({ file: rel, ...lineCol(source, error2.at), message: error2.message });
|
|
@@ -39804,7 +39941,6 @@ var withoutEnv = Effect_exports.fnUntraced(function* (payload) {
|
|
|
39804
39941
|
}
|
|
39805
39942
|
return { ...payload, env: {} };
|
|
39806
39943
|
});
|
|
39807
|
-
var okDeployed = ok(Deployed);
|
|
39808
39944
|
var requestedName = (config2, opts) => opts.name !== void 0 ? { name: opts.name } : { name: config2.name ?? toAppName(basename(config2.root)), uniqueName: true };
|
|
39809
39945
|
var createApp = Effect_exports.fnUntraced(function* (config2, payload, extra, opts, signedIn) {
|
|
39810
39946
|
const naming = requestedName(config2, opts);
|
|
@@ -39831,29 +39967,37 @@ var createApp = Effect_exports.fnUntraced(function* (config2, payload, extra, op
|
|
|
39831
39967
|
return { result: result4, name: naming.name };
|
|
39832
39968
|
});
|
|
39833
39969
|
var deploy = Effect_exports.fnUntraced(function* (config2, opts = {}) {
|
|
39970
|
+
const t = yield* target(config2);
|
|
39971
|
+
if (t.ref && opts.name !== void 0)
|
|
39972
|
+
return yield* fail12(
|
|
39973
|
+
t.claim ? `--name only names a new app, and this checkout already deploys to ${t.ref}. Name it when you claim it: eraspace claim --name ${opts.name}` : `--name only names a new app, and this checkout already deploys to ${t.ref}. Add the name with eraspace names add ${opts.name}`
|
|
39974
|
+
);
|
|
39834
39975
|
const build3 = yield* buildCapsule(config2.root);
|
|
39835
39976
|
if (!build3.ok || !build3.payload)
|
|
39836
39977
|
return yield* fail12(yield* formatErrors(build3.errors, config2.root));
|
|
39837
39978
|
const deleteTables = yield* confirmedDeletes(opts.deleteTables);
|
|
39838
39979
|
const extra = {
|
|
39839
39980
|
...opts.publicInspect ? { publicInspection: true } : {},
|
|
39840
|
-
...Object.keys(deleteTables).length ? { deleteTables } : {}
|
|
39981
|
+
...Object.keys(deleteTables).length ? { deleteTables } : {},
|
|
39982
|
+
...opts.shadow ? { shadow: true } : {},
|
|
39983
|
+
...opts.failOnQueryErrors ? { failOnQueryErrors: true } : {}
|
|
39841
39984
|
};
|
|
39842
39985
|
const signedIn = (yield* bearerFor(config2.origin)) !== void 0;
|
|
39843
|
-
const t = yield* target(config2);
|
|
39844
39986
|
let result4;
|
|
39845
39987
|
if (t.ref) {
|
|
39846
39988
|
const payload = t.claim ? yield* withoutEnv(build3.payload) : build3.payload;
|
|
39847
|
-
|
|
39848
|
-
|
|
39849
|
-
|
|
39850
|
-
|
|
39851
|
-
|
|
39852
|
-
|
|
39853
|
-
|
|
39854
|
-
|
|
39855
|
-
|
|
39856
|
-
|
|
39989
|
+
const path = `/_api/apps/${enc(t.ref)}/deploy`;
|
|
39990
|
+
const response = yield* api(config2, "POST", path, {
|
|
39991
|
+
body: { ...payload, ...extra },
|
|
39992
|
+
claim: t.claim,
|
|
39993
|
+
timeout: DEPLOY_TIMEOUT
|
|
39994
|
+
});
|
|
39995
|
+
if (response.status >= 400 || response.json.ok === false) {
|
|
39996
|
+
const refused = Schema_exports.decodeUnknownOption(ShadowReport)(response.json.shadow);
|
|
39997
|
+
if (Option_exports.isSome(refused)) yield* Console_exports.log(formatShadow(refused.value));
|
|
39998
|
+
return yield* Effect_exports.fail(yield* failure(response, config2.root));
|
|
39999
|
+
}
|
|
40000
|
+
result4 = yield* decode5(Deployed, response.json, `POST ${path}`);
|
|
39857
40001
|
if (t.claim && result4.claimed)
|
|
39858
40002
|
yield* removeLocalDeploy(config2.root, config2.origin);
|
|
39859
40003
|
} else if (signedIn) {
|
|
@@ -39878,6 +40022,7 @@ var deploy = Effect_exports.fnUntraced(function* (config2, opts = {}) {
|
|
|
39878
40022
|
expiresAt: Number.isFinite(expiresAt) ? expiresAt : null
|
|
39879
40023
|
});
|
|
39880
40024
|
}
|
|
40025
|
+
if (result4.shadow) yield* Console_exports.log(formatShadow(result4.shadow));
|
|
39881
40026
|
const label = result4.names?.[0] ?? result4.id;
|
|
39882
40027
|
yield* Console_exports.log(
|
|
39883
40028
|
`${label}${result4.version === void 0 ? "" : ` v${result4.version}`}: ${result4.url ?? config2.url}`
|
|
@@ -39900,6 +40045,40 @@ var deploy = Effect_exports.fnUntraced(function* (config2, opts = {}) {
|
|
|
39900
40045
|
}
|
|
39901
40046
|
return result4;
|
|
39902
40047
|
});
|
|
40048
|
+
var time2 = (ms) => ms === void 0 ? "" : new Date(ms).toISOString();
|
|
40049
|
+
var deploys = Effect_exports.fnUntraced(function* (config2, explicit) {
|
|
40050
|
+
const t = yield* requireTarget(config2, explicit);
|
|
40051
|
+
const history = yield* ok(DeployHistory)(config2, "GET", `/_api/apps/${enc(t.ref)}`, { claim: t.claim });
|
|
40052
|
+
const kept = history.deploys ?? [];
|
|
40053
|
+
if (!kept.length) return yield* Console_exports.log(`${t.ref} has no deploys.`);
|
|
40054
|
+
for (const d of kept) {
|
|
40055
|
+
const serving = d.version === history.version ? ` ${c.green("serving")}` : "";
|
|
40056
|
+
yield* Console_exports.log(`v${d.version} ${c.dim(time2(d.createdAt))}${serving}`);
|
|
40057
|
+
}
|
|
40058
|
+
if (kept.length >= 50) yield* Console_exports.log(c.dim(`Showing the latest ${kept.length}; any older kept versions still roll back by number: eraspace rollback <version>`));
|
|
40059
|
+
});
|
|
40060
|
+
var rollback = Effect_exports.fnUntraced(function* (config2, version, opts = {}) {
|
|
40061
|
+
const t = yield* requireTarget(config2);
|
|
40062
|
+
const deleteTables = yield* confirmedDeletes(opts.deleteTables);
|
|
40063
|
+
const body = {
|
|
40064
|
+
...version === void 0 ? {} : { version },
|
|
40065
|
+
...Object.keys(deleteTables).length ? { deleteTables } : {},
|
|
40066
|
+
...opts.shadow ? { shadow: true } : {},
|
|
40067
|
+
...opts.failOnQueryErrors ? { failOnQueryErrors: true } : {}
|
|
40068
|
+
};
|
|
40069
|
+
const path = `/_api/apps/${enc(t.ref)}/rollback`;
|
|
40070
|
+
const response = yield* api(config2, "POST", path, { body, claim: t.claim, timeout: DEPLOY_TIMEOUT });
|
|
40071
|
+
if (response.status >= 400 || response.json.ok === false) {
|
|
40072
|
+
const refused = Schema_exports.decodeUnknownOption(ShadowReport)(response.json.shadow);
|
|
40073
|
+
if (Option_exports.isSome(refused)) yield* Console_exports.log(formatShadow(refused.value));
|
|
40074
|
+
return yield* Effect_exports.fail(yield* failure(response, config2.root));
|
|
40075
|
+
}
|
|
40076
|
+
const result4 = yield* decode5(Deployed, response.json, `POST ${path}`);
|
|
40077
|
+
if (result4.shadow) yield* Console_exports.log(formatShadow(result4.shadow));
|
|
40078
|
+
const label = result4.names?.[0] ?? result4.id;
|
|
40079
|
+
yield* Console_exports.log(`${label} v${String(result4.version)}: rolled back to v${String(result4.rolledBackTo)}: ${result4.url ?? config2.url}`);
|
|
40080
|
+
return result4;
|
|
40081
|
+
});
|
|
39903
40082
|
var claimRequest = (config2, path, body, claim2) => api(config2, "POST", path, { body, claim: claim2 });
|
|
39904
40083
|
var claim = Effect_exports.fnUntraced(function* (config2, name) {
|
|
39905
40084
|
const local = yield* localDeploy(config2.root, config2.origin);
|
|
@@ -39947,10 +40126,14 @@ var IGNORED = /(^|[/\\])(node_modules|\.git|\.eraspace)([/\\]|$)/;
|
|
|
39947
40126
|
var isWatched = (file2) => !IGNORED.test(file2) && WATCHED.test(file2);
|
|
39948
40127
|
var dev = Effect_exports.fnUntraced(function* (config2, opts) {
|
|
39949
40128
|
const fs = yield* FileSystem_exports.FileSystem;
|
|
40129
|
+
let current = opts;
|
|
39950
40130
|
const redeploy = Effect_exports.suspend(
|
|
39951
40131
|
() => loadConfig({ dir: config2.root, app: config2.app, url: config2.url })
|
|
39952
40132
|
).pipe(
|
|
39953
|
-
Effect_exports.flatMap((
|
|
40133
|
+
Effect_exports.flatMap((loaded) => deploy(loaded, current)),
|
|
40134
|
+
Effect_exports.tap(() => Effect_exports.sync(() => {
|
|
40135
|
+
current = { ...current, name: void 0 };
|
|
40136
|
+
})),
|
|
39954
40137
|
Effect_exports.catch((error2) => Console_exports.error(messageOf(error2)))
|
|
39955
40138
|
);
|
|
39956
40139
|
yield* redeploy;
|
|
@@ -39964,7 +40147,10 @@ var dev = Effect_exports.fnUntraced(function* (config2, opts) {
|
|
|
39964
40147
|
});
|
|
39965
40148
|
|
|
39966
40149
|
// cli/src/logs.ts
|
|
39967
|
-
var
|
|
40150
|
+
var LogTimeouts = Context_exports.Reference(
|
|
40151
|
+
"eraspace/cli/LogTimeouts",
|
|
40152
|
+
{ defaultValue: () => ({ idle: Duration_exports.seconds(75), stalled: Duration_exports.seconds(30), errorBody: Duration_exports.seconds(10) }) }
|
|
40153
|
+
);
|
|
39968
40154
|
var Printed = class {
|
|
39969
40155
|
lines = [];
|
|
39970
40156
|
limit;
|
|
@@ -40005,12 +40191,14 @@ var readOnce = Effect_exports.fnUntraced(function* (config2, path, claim2, follo
|
|
|
40005
40191
|
const response = yield* request3(config2, "GET", path, { claim: claim2 }).pipe(
|
|
40006
40192
|
Effect_exports.timeoutOrElse({ duration: "30 seconds", orElse: () => Effect_exports.fail(new Transient({ message: `The platform at ${config2.origin} did not answer within 30s.` })) })
|
|
40007
40193
|
);
|
|
40194
|
+
const timeouts = yield* LogTimeouts;
|
|
40195
|
+
const errorText = response.text.pipe(Effect_exports.timeout(timeouts.errorBody), Effect_exports.orElseSucceed(() => ""));
|
|
40008
40196
|
if (isGatewayError(response.status)) {
|
|
40009
|
-
yield*
|
|
40197
|
+
yield* errorText;
|
|
40010
40198
|
return yield* new Transient({ message: `The platform at ${config2.origin} is unavailable (HTTP ${response.status}).` });
|
|
40011
40199
|
}
|
|
40012
40200
|
if (response.status >= 400) {
|
|
40013
|
-
const text2 = yield*
|
|
40201
|
+
const text2 = yield* errorText;
|
|
40014
40202
|
let json2 = { ok: false, error: text2.trim() || `HTTP ${response.status}` };
|
|
40015
40203
|
try {
|
|
40016
40204
|
json2 = JSON.parse(text2);
|
|
@@ -40018,12 +40206,15 @@ var readOnce = Effect_exports.fnUntraced(function* (config2, path, claim2, follo
|
|
|
40018
40206
|
}
|
|
40019
40207
|
return yield* Effect_exports.fail(yield* failure({ status: response.status, json: json2 }, config2.root));
|
|
40020
40208
|
}
|
|
40021
|
-
|
|
40209
|
+
const lines2 = response.stream.pipe(
|
|
40210
|
+
Stream_exports.mapError((error2) => new Transient({ message: `The log stream broke: ${describe(error2.cause ?? error2)}` })),
|
|
40211
|
+
Stream_exports.timeoutOrElse({
|
|
40212
|
+
duration: follow ? timeouts.idle : timeouts.stalled,
|
|
40213
|
+
orElse: () => Stream_exports.fail(new Transient({ message: follow ? "The log stream went quiet." : `The platform at ${config2.origin} stopped sending logs.` }))
|
|
40214
|
+
}),
|
|
40022
40215
|
Stream_exports.decodeText(),
|
|
40023
|
-
Stream_exports.splitLines
|
|
40024
|
-
Stream_exports.mapError((error2) => new Transient({ message: `The log stream broke: ${describe(error2.cause ?? error2)}` }))
|
|
40216
|
+
Stream_exports.splitLines
|
|
40025
40217
|
);
|
|
40026
|
-
if (follow) lines2 = lines2.pipe(Stream_exports.timeoutOrElse({ duration: IDLE, orElse: () => Stream_exports.fail(new Transient({ message: "The log stream went quiet." })) }));
|
|
40027
40218
|
const fresh2 = printed.connection();
|
|
40028
40219
|
yield* lines2.pipe(
|
|
40029
40220
|
Stream_exports.filter((line) => line.trim() !== ""),
|
|
@@ -40050,7 +40241,7 @@ var logs = Effect_exports.fnUntraced(function* (config2, explicit, follow, limit
|
|
|
40050
40241
|
var okApp = ok(App);
|
|
40051
40242
|
var okJson = ok(JsonObject2);
|
|
40052
40243
|
var okState = ok(StateChange);
|
|
40053
|
-
var
|
|
40244
|
+
var time3 = (value4) => {
|
|
40054
40245
|
if (value4 === null || value4 === void 0) return void 0;
|
|
40055
40246
|
const date = new Date(typeof value4 === "number" ? value4 : Date.parse(value4));
|
|
40056
40247
|
return Number.isNaN(date.getTime()) ? String(value4) : date.toISOString();
|
|
@@ -40090,12 +40281,74 @@ var lifecycle = Effect_exports.fnUntraced(function* (config2, action, explicit,
|
|
|
40090
40281
|
const result4 = yield* okState(config2, "POST", `/_api/apps/${enc(t.ref)}/${action}`, { claim: t.claim });
|
|
40091
40282
|
yield* Console_exports.log(`${t.ref}: ${result4.state}`);
|
|
40092
40283
|
});
|
|
40284
|
+
var explain = Effect_exports.fnUntraced(function* (config2, fn3, inputs, json2) {
|
|
40285
|
+
const args2 = yield* parseCallArgs(inputs);
|
|
40286
|
+
const t = yield* requireTarget(config2);
|
|
40287
|
+
const report = yield* okJson(config2, "POST", `/_api/apps/${enc(t.ref)}/explain`, { body: { fn: fn3, args: args2 }, claim: t.claim });
|
|
40288
|
+
if (json2) return yield* Console_exports.log(JSON.stringify(report, null, 2));
|
|
40289
|
+
const scans = report.scans ?? [];
|
|
40290
|
+
yield* Console_exports.log(`${fn3}: read ${String(report.rowsRead)} row${report.rowsRead === 1 ? "" : "s"} in ${scans.length} scan${scans.length === 1 ? "" : "s"} and returned ${String(report.returned)}`);
|
|
40291
|
+
for (const s of scans) yield* Console_exports.log(` ${s.table} via ${s.index} (${s.mode}): read ${s.rowsRead}, kept ${s.kept} of ${s.rows}`);
|
|
40292
|
+
const suggestions = report.suggestions ?? [];
|
|
40293
|
+
if (!suggestions.length) yield* Console_exports.log(c.dim("No index suggestions."));
|
|
40294
|
+
for (const s of suggestions) yield* Console_exports.log(`${c.yellow("suggest")} ${s.message}`);
|
|
40295
|
+
});
|
|
40296
|
+
var profile = Effect_exports.fnUntraced(function* (config2, explicit, seconds2, out) {
|
|
40297
|
+
const t = yield* requireTarget(config2, explicit);
|
|
40298
|
+
yield* Console_exports.error(c.dim(`Profiling ${t.ref} for ${seconds2}s...`));
|
|
40299
|
+
const result4 = yield* okJson(config2, "POST", `/_api/apps/${enc(t.ref)}/profile`, { body: { seconds: seconds2 }, claim: t.claim, timeout: Duration_exports.seconds(seconds2 + 30) });
|
|
40300
|
+
const file2 = out ?? `${t.ref}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.cpuprofile`;
|
|
40301
|
+
const fs = yield* FileSystem_exports.FileSystem;
|
|
40302
|
+
yield* fs.writeFileString(file2, JSON.stringify(result4));
|
|
40303
|
+
const nodes = new Map((result4.nodes ?? []).map((n) => [n.id, n]));
|
|
40304
|
+
const counts = /* @__PURE__ */ new Map();
|
|
40305
|
+
let busy = 0;
|
|
40306
|
+
for (const id2 of result4.samples ?? []) {
|
|
40307
|
+
const f = nodes.get(id2)?.callFrame;
|
|
40308
|
+
if (!f || f.functionName === "(idle)" || f.functionName === "(root)") continue;
|
|
40309
|
+
busy++;
|
|
40310
|
+
const at = f.url ? ` ${f.url}:${f.lineNumber + 1}` : "";
|
|
40311
|
+
const name = `${f.functionName || "(anonymous)"}${at}`;
|
|
40312
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
40313
|
+
}
|
|
40314
|
+
const samples = (result4.samples ?? []).length;
|
|
40315
|
+
yield* Console_exports.log(`${t.ref}: busy in ${busy} of ${samples} samples. Open ${file2} in Chrome DevTools (Performance > Load profile).`);
|
|
40316
|
+
for (const [name, n] of [...counts].sort((a, b) => b[1] - a[1]).slice(0, 10)) {
|
|
40317
|
+
yield* Console_exports.log(` ${(100 * n / Math.max(busy, 1)).toFixed(1).padStart(5)}% ${name}`);
|
|
40318
|
+
}
|
|
40319
|
+
});
|
|
40320
|
+
var changes2 = Effect_exports.fnUntraced(function* (config2, since, limit) {
|
|
40321
|
+
const t = yield* requireTarget(config2);
|
|
40322
|
+
const response = yield* request3(config2, "GET", `/_api/apps/${enc(t.ref)}/changes?since=${since}&limit=${limit}`, { claim: t.claim });
|
|
40323
|
+
const text2 = yield* response.text.pipe(Effect_exports.orElseSucceed(() => ""));
|
|
40324
|
+
if (response.status >= 400) {
|
|
40325
|
+
let json2 = { ok: false, error: text2.trim() || `HTTP ${response.status}` };
|
|
40326
|
+
try {
|
|
40327
|
+
json2 = JSON.parse(text2);
|
|
40328
|
+
} catch {
|
|
40329
|
+
}
|
|
40330
|
+
return yield* Effect_exports.fail(yield* failure({ status: response.status, json: json2 }));
|
|
40331
|
+
}
|
|
40332
|
+
const lines2 = text2.split("\n").filter(Boolean);
|
|
40333
|
+
for (const line of lines2) yield* Console_exports.log(line);
|
|
40334
|
+
const head5 = Number(response.headers["x-eraspace-commit-seq"] ?? 0);
|
|
40335
|
+
const last = lines2.length ? JSON.parse(lines2.at(-1)).seq : since;
|
|
40336
|
+
const more = response.headers["x-eraspace-more"] === "1";
|
|
40337
|
+
yield* Console_exports.error(c.dim(more ? `Latest commit ${head5}; continue with --since ${last}` : `Up to date at commit ${head5}`));
|
|
40338
|
+
});
|
|
40339
|
+
var undo = Effect_exports.fnUntraced(function* (config2, to, yes) {
|
|
40340
|
+
const t = yield* requireTarget(config2);
|
|
40341
|
+
if (!yes) return yield* fail12(`eraspace undo ${to} puts every row of ${t.ref} changed after commit ${to} back as it was. Re-run with --yes.`);
|
|
40342
|
+
const result4 = yield* okJson(config2, "POST", `/_api/apps/${enc(t.ref)}/undo`, { body: { toSeq: to }, claim: t.claim });
|
|
40343
|
+
const undone = Number(result4.undone ?? 0);
|
|
40344
|
+
yield* Console_exports.log(undone ? `${t.ref}: restored ${undone} row${undone === 1 ? "" : "s"} to commit ${to} as commit ${String(result4.commitSeq)}` : `${t.ref}: nothing changed after commit ${to}`);
|
|
40345
|
+
});
|
|
40093
40346
|
var listTokens = Effect_exports.fnUntraced(function* (config2) {
|
|
40094
40347
|
const { tokens } = yield* ok(TokenList)(config2, "GET", "/_api/tokens");
|
|
40095
40348
|
if (!tokens.length) yield* Console_exports.log("No tokens.");
|
|
40096
40349
|
for (const t of tokens) {
|
|
40097
40350
|
const scope3 = t.scope === "personal" ? "personal (all your apps)" : `${t.app ?? "?"} (${t.access ?? "?"})`;
|
|
40098
|
-
const used =
|
|
40351
|
+
const used = time3(t.lastUsedAt);
|
|
40099
40352
|
yield* Console_exports.log(`${t.id} ${t.name} ${scope3} ${c.dim(used ? `last used ${used}` : "never used")}`);
|
|
40100
40353
|
}
|
|
40101
40354
|
});
|
|
@@ -40106,9 +40359,10 @@ var revokeToken = Effect_exports.fnUntraced(function* (config2, id2) {
|
|
|
40106
40359
|
var createToken = Effect_exports.fnUntraced(function* (config2, name, opts) {
|
|
40107
40360
|
if (opts.personal && opts.read) return yield* fail12("--read only applies to app tokens; personal tokens have full access to your apps.");
|
|
40108
40361
|
const spec = opts.personal ? { name, personal: true } : { name, app: (yield* requireTarget(config2)).ref, access: opts.read ? "read" : "deploy" };
|
|
40109
|
-
const { token } = yield* ok(CreatedToken)(config2, "POST", "/_api/tokens", { body: spec });
|
|
40362
|
+
const { token, id: id2 } = yield* ok(CreatedToken)(config2, "POST", "/_api/tokens", { body: spec });
|
|
40110
40363
|
yield* Console_exports.log(token);
|
|
40111
40364
|
yield* Console_exports.error(c.dim(`Shown once. Use it as ERASPACE_TOKEN with ERASPACE_TOKEN_API=${config2.origin}.`));
|
|
40365
|
+
yield* Console_exports.error(c.dim(`Token id ${id2}; revoke it with eraspace token revoke ${id2}.`));
|
|
40112
40366
|
});
|
|
40113
40367
|
function parseCallArgs(inputs) {
|
|
40114
40368
|
const args2 = [];
|
|
@@ -40122,7 +40376,7 @@ Pass each argument as JSON, for example eraspace call send '"general"' '{"text":
|
|
|
40122
40376
|
}
|
|
40123
40377
|
return Effect_exports.succeed(args2);
|
|
40124
40378
|
}
|
|
40125
|
-
var
|
|
40379
|
+
var call2 = Effect_exports.fnUntraced(function* (config2, fn3, inputs) {
|
|
40126
40380
|
const args2 = yield* parseCallArgs(inputs);
|
|
40127
40381
|
const t = yield* requireTarget(config2);
|
|
40128
40382
|
const { status, text: text2 } = yield* Effect_exports.gen(function* () {
|
|
@@ -40145,132 +40399,74 @@ import { basename as basename2, join as join7, relative as relative3, resolve as
|
|
|
40145
40399
|
var TEMPLATES = ["chat", "todo", "poll"];
|
|
40146
40400
|
var agents = (cli) => `# eraspace capsule: agent instructions
|
|
40147
40401
|
|
|
40148
|
-
This directory is
|
|
40149
|
-
|
|
40150
|
-
|
|
40151
|
-
|
|
40152
|
-
## Commands
|
|
40153
|
-
|
|
40154
|
-
Run these from this directory. There is nothing to install: the CLI bundles the SDK, Preact and TypeScript.
|
|
40155
|
-
|
|
40156
|
-
- \`${cli} typecheck\`: tsc over \`server/\`, \`client/\` and \`shared/\`. Deploy does not typecheck.
|
|
40157
|
-
- \`${cli} deploy\`: build the client, compile the server and print the app URL.
|
|
40158
|
-
- \`${cli} call <fn> '<json arg>'...\`: call a query, mutation or action, e.g. \`call add '"milk"'\`.
|
|
40159
|
-
- \`${cli} logs\`: handler logs, \`ctx.log\` and \`console.log\` output.
|
|
40160
|
-
- \`${cli} inspect\`: deployed functions, schema and metadata.
|
|
40161
|
-
- \`${cli} dev\`: redeploy on every save (long-running; keep it open).
|
|
40162
|
-
|
|
40163
|
-
The platform URL comes from \`ERASPACE_URL\`, then \`eraspace.json\`, then \`${DEFAULT_URL}\`.
|
|
40402
|
+
This directory is an eraspace app ("capsule"): a TypeScript server in V8 and a Preact client over a database with
|
|
40403
|
+
live queries, hosted and driven by \`${cli}\`, which supplies TypeScript, Preact and \`eraspace/*\`, so there is
|
|
40404
|
+
nothing to install.
|
|
40164
40405
|
|
|
40165
40406
|
## Workflow
|
|
40166
40407
|
|
|
40167
|
-
1. Read \`server/index.ts\` and \`client/index.tsx
|
|
40168
|
-
2.
|
|
40169
|
-
3. Run \`${cli}
|
|
40170
|
-
4.
|
|
40171
|
-
|
|
40172
|
-
|
|
40173
|
-
|
|
40174
|
-
|
|
40175
|
-
|
|
40176
|
-
|
|
40177
|
-
|
|
40178
|
-
|
|
40179
|
-
|
|
40180
|
-
|
|
40181
|
-
|
|
40182
|
-
|
|
40183
|
-
|
|
40184
|
-
|
|
40185
|
-
|
|
40186
|
-
|
|
40187
|
-
|
|
40188
|
-
|
|
40189
|
-
|
|
40190
|
-
|
|
40191
|
-
|
|
40192
|
-
|
|
40193
|
-
|
|
40194
|
-
|
|
40195
|
-
|
|
40196
|
-
|
|
40197
|
-
|
|
40198
|
-
|
|
40199
|
-
|
|
40200
|
-
|
|
40201
|
-
|
|
40202
|
-
|
|
40203
|
-
|
|
40204
|
-
|
|
40205
|
-
|
|
40206
|
-
|
|
40207
|
-
|
|
40208
|
-
|
|
40209
|
-
|
|
40210
|
-
|
|
40211
|
-
|
|
40212
|
-
|
|
40213
|
-
|
|
40214
|
-
|
|
40215
|
-
|
|
40216
|
-
|
|
40217
|
-
|
|
40218
|
-
|
|
40219
|
-
|
|
40220
|
-
|
|
40221
|
-
|
|
40222
|
-
|
|
40223
|
-
|
|
40224
|
-
|
|
40225
|
-
- Handler arguments are positional and not validated. Check them in the handler.
|
|
40226
|
-
- \`ctx.auth.userId\` is a per-connection guest id that changes on reload. It is not a login: never use it for
|
|
40227
|
-
ownership or access control. Every app is public.
|
|
40228
|
-
- \`ctx.log.info(msg, data?)\` (also debug/warn/error) writes to \`eraspace logs\`.
|
|
40229
|
-
- Endpoints receive \`req\` with \`method, path, url, headers.get(), query.get(), json(), text()\` and return
|
|
40230
|
-
\`json(value)\`, \`text(string)\`, \`empty()\`, \`redirect(url)\` or a plain value (sent as JSON).
|
|
40231
|
-
- Removing a populated table needs its row count confirmed: \`${cli} deploy --delete-table <table>=<rows>\` (the
|
|
40232
|
-
refused deploy prints the exact flag). Deletion runs in background batches.
|
|
40233
|
-
- Changing a field's type or making it required fails the deploy while stored rows do not match; migrate them first.
|
|
40234
|
-
|
|
40235
|
-
## Client API (\`eraspace/client\`)
|
|
40236
|
-
|
|
40237
|
-
\`\`\`tsx
|
|
40238
|
-
import { createClient } from "eraspace/client";
|
|
40239
|
-
import { useState } from "preact/hooks";
|
|
40240
|
-
import type app from "../server/index";
|
|
40241
|
-
|
|
40242
|
-
const client = createClient<typeof app>({ reloadOnDeploy: true });
|
|
40243
|
-
|
|
40244
|
-
export default function App() {
|
|
40245
|
-
const items = client.useQuery("items", "some-list"); // undefined while loading, then live
|
|
40246
|
-
const add = client.useMutation("add");
|
|
40247
|
-
const [title, setTitle] = useState("");
|
|
40248
|
-
if (items === undefined) return <p>Loading\u2026</p>;
|
|
40249
|
-
return <main class="p-6">
|
|
40250
|
-
<form onSubmit={async (e) => { e.preventDefault(); await add("some-list", title); setTitle(""); }}>
|
|
40251
|
-
<input value={title} onInput={(e) => setTitle(e.currentTarget.value)} />
|
|
40252
|
-
</form>
|
|
40253
|
-
<ul>{items.map((item) => <li key={item.id}>{item.title}</li>)}</ul>
|
|
40254
|
-
</main>;
|
|
40255
|
-
}
|
|
40256
|
-
\`\`\`
|
|
40257
|
-
|
|
40258
|
-
- Query, mutation and action names, arguments and results are typed from the server. Import the server with
|
|
40259
|
-
\`import type\` only.
|
|
40260
|
-
- Optimistic updates: \`client.useMutation("vote", { optimistic(store, id) { store.update("results", [],
|
|
40261
|
-
(rows) => rows.map((r) => (r.id === id ? { ...r, votes: r.votes + 1 } : r))) } })\`. The second argument of
|
|
40262
|
-
\`store.update\` is the query's argument list.
|
|
40263
|
-
- \`useAction(name)\`, \`usePaginatedQuery(name, args, { initialNumItems })\`, \`client.query/mutation/action(...)\`.
|
|
40264
|
-
- Realtime rooms, not stored: \`const room = client.useRoom(name, meta)\` gives \`room.presence\`,
|
|
40265
|
-
\`room.broadcast(event, payload)\` and \`room.on(event, cb)\` (call \`on\` inside \`useEffect\` and return its
|
|
40266
|
-
cleanup). \`client.usePresence(name, meta)\` returns just the members.
|
|
40267
|
-
- Files: see "File uploads" below.
|
|
40268
|
-
- Pages: \`Router\`, \`Route\`, \`Link\`, \`useLocation\`, \`useNavigate\`, \`useParams\` from \`eraspace/client\`.
|
|
40269
|
-
- Client imports are limited to \`eraspace/client\`, \`preact\`, \`preact/hooks\`, \`@preact/signals\` and relative files.
|
|
40408
|
+
1. Read \`CODING_STANDARDS.md\`, \`server/index.ts\` and \`client/index.tsx\`.
|
|
40409
|
+
2. Write the requested app or change.
|
|
40410
|
+
3. Run \`${cli} typecheck\` until it prints "No type errors."
|
|
40411
|
+
4. Run \`${cli} deploy\` until it prints the app URL. Pass \`--delete-table\` only for rows the user agreed to lose.
|
|
40412
|
+
5. Run \`${cli} call <fn> '<json arg>'...\` on every query, mutation and action, and on an invalid input for each
|
|
40413
|
+
mutation that takes arguments, until each returns what the code intends and each invalid input is refused with
|
|
40414
|
+
its message; then \`${cli} logs\` shows no unexpected error.
|
|
40415
|
+
6. Once steps 3 to 5 pass after your last edit, report the URL. For an unclaimed app, also report the expiry deploy
|
|
40416
|
+
printed and that \`${cli} auth login\` then \`${cli} claim\` keeps the app.
|
|
40417
|
+
`;
|
|
40418
|
+
var CODING_STANDARDS = `# eraspace capsule: coding standards
|
|
40419
|
+
|
|
40420
|
+
Before using one of these features, read its section in \`FEATURES.md\`: file uploads, rooms and presence, routing,
|
|
40421
|
+
optimistic updates, pagination, scheduled mutations, HTTP endpoints, reload on deploy.
|
|
40422
|
+
|
|
40423
|
+
## Security
|
|
40424
|
+
|
|
40425
|
+
- Every caller is an anonymous guest and every app is public. \`ctx.auth.userId\` is a guest id that changes with
|
|
40426
|
+
each connection, so it never grants ownership or access.
|
|
40427
|
+
- Keep secrets in \`.env.eraspace.server\` as \`KEY=value\` lines and read them in server code as \`ctx.env.KEY\`:
|
|
40428
|
+
\`client/\` and \`shared/\` ship to the browser.
|
|
40429
|
+
|
|
40430
|
+
## Server (\`eraspace/server\`)
|
|
40431
|
+
|
|
40432
|
+
- Fields: \`string() number() boolean() id("table") userId() any() array(field) object({...})\`, each with
|
|
40433
|
+
\`.optional()\` or \`.default(value)\`. Rows also have \`id\` and \`createdAt\` (epoch ms).
|
|
40434
|
+
- \`ctx.db.<table>\` has \`insert\`, \`get\`, \`update(id, partial)\` (\`null\` removes a field), \`replace\` and
|
|
40435
|
+
\`delete\`. Reads chain \`withIndex\`, \`order("asc" | "desc")\`, then \`collect()\`, \`take(n)\`, \`first()\`,
|
|
40436
|
+
\`unique()\`, \`count()\` or \`paginate(...)\`.
|
|
40437
|
+
- Declare an index with \`table({...}).index("by_list", ["listId"])\` and read it with
|
|
40438
|
+
\`ctx.db.items.withIndex("by_list", (q) => q.eq("listId", id))\`, then \`.gt(...)\` or \`.lt(...)\` on the next
|
|
40439
|
+
field. \`by_creation\` indexes every table by \`createdAt\`.
|
|
40440
|
+
- Validate every argument in the handler: arguments are positional, come from anyone and are not checked against
|
|
40441
|
+
their TypeScript types.
|
|
40442
|
+
- A query is a live query: its cached result is reused until rows it read change, then rerun and pushed to
|
|
40443
|
+
subscribers. Make it depend only on its arguments, its caller and the rows it reads: pass a time it needs as an
|
|
40444
|
+
argument, rounded so it stays the same across renders, and draw random values in mutations.
|
|
40445
|
+
- Mutations run one at a time per app, so a read-then-write in one mutation never races another.
|
|
40446
|
+
- Actions call outbound \`fetch\`, \`ctx.runQuery\`, \`ctx.runMutation\` and \`ctx.runAction\`: fetch in an action
|
|
40447
|
+
and save the result with \`ctx.runMutation\`.
|
|
40448
|
+
- Rows and results are JSON: keep times as epoch-ms numbers, as \`createdAt\` is.
|
|
40449
|
+
- Keep state in the database: module variables reset whenever the app unloads.
|
|
40450
|
+
- Handlers have CPU, wall-clock and heap limits: keep loops and reads bounded.
|
|
40451
|
+
- A \`.migrate\` upgrade is a pure function of the stored row.
|
|
40452
|
+
|
|
40453
|
+
## Client (\`eraspace/client\`)
|
|
40454
|
+
|
|
40455
|
+
- \`client.useQuery(name, ...args)\` subscribes to a live query: \`undefined\` while loading, then every new result,
|
|
40456
|
+
so mutations need no refetch.
|
|
40457
|
+
- \`client.useAction(name)\` returns a function, as \`useMutation\` does. Outside components, call \`client.query\`,
|
|
40458
|
+
\`client.mutation\` or \`client.action(name, ...args)\`.
|
|
40459
|
+
- Style with Tailwind utility classes, each written as a complete literal, and \`client/styles.css\`.
|
|
40460
|
+
|
|
40461
|
+
## Shared code
|
|
40462
|
+
|
|
40463
|
+
- Keep \`shared/\` to types, constants and pure functions, which both the browser and the server can run.
|
|
40464
|
+
`;
|
|
40465
|
+
var FEATURES = `# eraspace capsule: features
|
|
40270
40466
|
|
|
40271
40467
|
## File uploads
|
|
40272
40468
|
|
|
40273
|
-
Files
|
|
40469
|
+
Files live in app storage, not the database: a row saves the \`FileRef\` an upload returns.
|
|
40274
40470
|
|
|
40275
40471
|
\`\`\`tsx
|
|
40276
40472
|
// server/index.ts: declare the file in the schema and copy its fields in the mutation (extra fields are refused).
|
|
@@ -40290,34 +40486,93 @@ const addPhoto = client.useMutation("addPhoto");
|
|
|
40290
40486
|
- \`<FileUpload>\` is a drop zone and file picker with progress and error text. Props: \`accept\`, \`multiple\`,
|
|
40291
40487
|
\`maxBytes\`, \`disabled\`, \`class\` (replaces its default style; \`data-dragging\` is set during a drag) and
|
|
40292
40488
|
children for the label. \`onUpload(file)\` runs once per stored file; if it throws, the file is deleted again.
|
|
40293
|
-
- \`<FilePreview file={fileRef
|
|
40294
|
-
|
|
40295
|
-
|
|
40296
|
-
- Custom UI: \`const { upload, uploading, progress, error } = client.useUpload({ accept, maxBytes, onUpload })\`,
|
|
40297
|
-
then \`upload(input.files)
|
|
40298
|
-
\`.info(key)\` and \`.delete(key)\`.
|
|
40489
|
+
- \`<FilePreview file={fileRef}>\` shows images, video and audio inline, plain text in a frame, and other types as a
|
|
40490
|
+
download card. Pass it the saved \`FileRef\`: a bare key costs a metadata request per preview. \`caption={false}\`
|
|
40491
|
+
hides the name/size line; \`class\` replaces its style.
|
|
40492
|
+
- Custom UI: \`const { upload, uploading, progress, error, file } = client.useUpload({ accept, maxBytes, onUpload })\`,
|
|
40493
|
+
then \`upload(input.files)\`; \`file\` is the Blob uploading now. Lower level: \`client.storage.upload(blob, { onProgress })\`,
|
|
40494
|
+
\`.url(key)\`, \`.localUrl(key)\`, \`.info(key)\` and \`.delete(key)\`.
|
|
40495
|
+
- Up to 5 MiB per file and 100 MiB per app.
|
|
40299
40496
|
- \`accept\` and \`maxBytes\` are checked in the browser only; the server stores any type up to its limit.
|
|
40300
|
-
-
|
|
40497
|
+
- Anyone with a file's key can read that file, so upload only what may be public. The \`deleteToken\` that
|
|
40498
|
+
\`client.storage.upload\` returns lets its holder delete the file: keep it out of rows.
|
|
40499
|
+
|
|
40500
|
+
## Rooms and presence
|
|
40501
|
+
|
|
40502
|
+
Rooms are realtime and never stored. \`const room = client.useRoom(name, meta)\` gives:
|
|
40503
|
+
|
|
40504
|
+
- \`room.presence\`, the members with their \`meta\`, and \`room.self\`, this connection's id.
|
|
40505
|
+
- \`room.broadcast(event, payload)\` and \`room.on(event, (payload, from) => ...)\`. Call \`on\` inside \`useEffect\`
|
|
40506
|
+
and return its cleanup.
|
|
40507
|
+
- \`room.state\`, shared by the members and set with \`room.setState(key, value)\`. It vanishes when the room
|
|
40508
|
+
empties: save what must last with a mutation.
|
|
40301
40509
|
|
|
40302
|
-
|
|
40510
|
+
\`client.usePresence(name, meta)\` returns just the members.
|
|
40303
40511
|
|
|
40304
|
-
|
|
40305
|
-
classes, closures, async/await, generators, getters, private fields, Proxy, Reflect and BigInt use V8 semantics.
|
|
40306
|
-
Values crossing the database or RPC boundary must be JSON-compatible.
|
|
40512
|
+
## Routing
|
|
40307
40513
|
|
|
40308
|
-
|
|
40309
|
-
|
|
40310
|
-
|
|
40311
|
-
|
|
40312
|
-
|
|
40313
|
-
|
|
40314
|
-
|
|
40315
|
-
|
|
40514
|
+
\`\`\`tsx
|
|
40515
|
+
import { Router, Route, Link, useParams, useNavigate } from "eraspace/client";
|
|
40516
|
+
|
|
40517
|
+
<Router>
|
|
40518
|
+
<Route path="/" component={Home} />
|
|
40519
|
+
<Route path="/items/:id" component={Item} /> {/* Item reads useParams().id */}
|
|
40520
|
+
<Route default component={NotFound} />
|
|
40521
|
+
</Router>
|
|
40522
|
+
\`\`\`
|
|
40523
|
+
|
|
40524
|
+
Paths are app-relative: the app is served under \`/<app>/\`, and every path below it loads the app. Link with
|
|
40525
|
+
\`<Link to="/items/1">\` and navigate with \`useNavigate()("/items/1")\`, which add that base. \`useLocation()\`
|
|
40526
|
+
gives the current \`path\` and \`url\`.
|
|
40527
|
+
|
|
40528
|
+
## Optimistic updates
|
|
40316
40529
|
|
|
40317
|
-
|
|
40530
|
+
\`client.useMutation("vote", { optimistic(store, id) { store.update("results", [], (rows) => rows.map((r) =>
|
|
40531
|
+
(r.id === id ? { ...r, votes: r.votes + 1 } : r))) } })\`. The second argument of \`store.update\` is the query's
|
|
40532
|
+
argument list.
|
|
40318
40533
|
|
|
40319
|
-
|
|
40320
|
-
|
|
40534
|
+
## Pagination
|
|
40535
|
+
|
|
40536
|
+
\`\`\`ts
|
|
40537
|
+
// server: the query takes PaginationOptions last and returns paginate(); cap numItems, which the client chooses.
|
|
40538
|
+
feed: query(async (ctx, listId: string, page: PaginationOptions) =>
|
|
40539
|
+
ctx.db.items.withIndex("by_list", (q) => q.eq("listId", listId)).order("desc")
|
|
40540
|
+
.paginate({ cursor: page.cursor, numItems: Math.min(page.numItems, 50) })),
|
|
40541
|
+
|
|
40542
|
+
// client
|
|
40543
|
+
const { results, status, loadMore } = client.usePaginatedQuery("feed", ["some-list"], { initialNumItems: 20 });
|
|
40544
|
+
\`\`\`
|
|
40545
|
+
|
|
40546
|
+
\`PaginationOptions\` comes from \`eraspace/server\`. \`status\` is \`"LoadingFirstPage"\`, \`"CanLoadMore"\`,
|
|
40547
|
+
\`"LoadingMore"\` or \`"Exhausted"\`; \`loadMore(n?)\` fetches the next page.
|
|
40548
|
+
|
|
40549
|
+
## Scheduled mutations
|
|
40550
|
+
|
|
40551
|
+
Mutations and writable endpoints run a mutation later with \`ctx.scheduler.runAfter(ms, "mutationName", ...args)\`,
|
|
40552
|
+
which returns a job id. The job commits with the mutation that schedules it, and its run commits at most once. For
|
|
40553
|
+
recurring work, the scheduled mutation schedules its next run.
|
|
40554
|
+
|
|
40555
|
+
## HTTP endpoints
|
|
40556
|
+
|
|
40557
|
+
\`\`\`ts
|
|
40558
|
+
import { endpoint, json } from "eraspace/server";
|
|
40559
|
+
|
|
40560
|
+
endpoints: {
|
|
40561
|
+
health: endpoint({ method: "GET", path: "/health", readOnly: true }, async (ctx, req) => json({ ok: true })),
|
|
40562
|
+
},
|
|
40563
|
+
\`\`\`
|
|
40564
|
+
|
|
40565
|
+
- An endpoint is served at \`/<app>/<path>\`. \`readOnly: true\` gives it a read-only db; otherwise it writes like a
|
|
40566
|
+
mutation.
|
|
40567
|
+
- \`req\` has \`method, path, url, headers.get(), query.get(), json(), text(), bytes(), formData()\`. Return
|
|
40568
|
+
\`json(value)\`, \`text(string)\`, \`empty()\`, \`redirect(url)\` (each takes \`{ status, headers }\` last) or a
|
|
40569
|
+
plain value (sent as JSON).
|
|
40570
|
+
- Endpoints are public: authenticate a webhook by checking its signature with \`crypto.subtle\`.
|
|
40571
|
+
|
|
40572
|
+
## Reload on deploy
|
|
40573
|
+
|
|
40574
|
+
Open tabs reload by themselves after each deploy. Pass \`createClient({ reloadOnDeploy: false })\` to keep the page
|
|
40575
|
+
and react to \`client.onDeployed(version => ...)\` instead.
|
|
40321
40576
|
`;
|
|
40322
40577
|
var GITIGNORE = [".eraspace/", ".env.eraspace.server"];
|
|
40323
40578
|
var keepsOnInit = (name) => name.startsWith(".") || /^(README|LICENSE)(\.|$)/i.test(name);
|
|
@@ -40347,6 +40602,8 @@ var createCapsule = Effect_exports.fnUntraced(function* (directory, template, mo
|
|
|
40347
40602
|
const files = {
|
|
40348
40603
|
"eraspace.json": JSON.stringify({ name: app }, null, 2) + "\n",
|
|
40349
40604
|
"AGENTS.md": agents(command2),
|
|
40605
|
+
"CODING_STANDARDS.md": CODING_STANDARDS,
|
|
40606
|
+
"FEATURES.md": FEATURES,
|
|
40350
40607
|
"CLAUDE.md": "@AGENTS.md\n"
|
|
40351
40608
|
};
|
|
40352
40609
|
const gitignore = join7(destination, ".gitignore");
|
|
@@ -40446,7 +40703,7 @@ var config = Effect_exports.gen(function* () {
|
|
|
40446
40703
|
return yield* loadConfig({ dir: value3(flags.dir), app: value3(flags.app), url: value3(flags.url) });
|
|
40447
40704
|
});
|
|
40448
40705
|
var deployFlags = {
|
|
40449
|
-
name: optionalString("name", "Exact name for a new app, failing if it is taken (default: the eraspace.json or directory name, or a free variant of it)"),
|
|
40706
|
+
name: optionalString("name", "Exact name for a new app, failing if it is taken (default: the eraspace.json or directory name, or a free variant of it). A redeploy refuses it; use eraspace names add"),
|
|
40450
40707
|
publicInspect: Flag_exports.Boolean("public-inspect").pipe(Flag_exports.withDefault(false), Flag_exports.withDescription("Let anyone read this app's inspection data and logs"))
|
|
40451
40708
|
};
|
|
40452
40709
|
var deployOptions = (input) => ({ name: value3(input.name), publicInspect: input.publicInspect });
|
|
@@ -40456,6 +40713,16 @@ var deleteTableFlag = {
|
|
|
40456
40713
|
Flag_exports.optional
|
|
40457
40714
|
)
|
|
40458
40715
|
};
|
|
40716
|
+
var shadowFlags = {
|
|
40717
|
+
shadow: Flag_exports.Boolean("shadow").pipe(
|
|
40718
|
+
Flag_exports.withDefault(false),
|
|
40719
|
+
Flag_exports.withDescription("Before installing, rerun the app's live queries on the new code and report which results change or start throwing")
|
|
40720
|
+
),
|
|
40721
|
+
failOnQueryErrors: Flag_exports.Boolean("fail-on-query-errors").pipe(
|
|
40722
|
+
Flag_exports.withDefault(false),
|
|
40723
|
+
Flag_exports.withDescription("Like --shadow, but refuse the deploy if a live query that returns a value now would throw")
|
|
40724
|
+
)
|
|
40725
|
+
};
|
|
40459
40726
|
var browserFlag = {
|
|
40460
40727
|
browser: Flag_exports.Boolean("browser").pipe(Flag_exports.withDefault(true), Flag_exports.withDescription("Open the sign-in page in a browser; --no-browser only prints the URL"))
|
|
40461
40728
|
};
|
|
@@ -40479,11 +40746,43 @@ var typecheckCommand = Command_exports.make("typecheck", {}, () => Effect_export
|
|
|
40479
40746
|
);
|
|
40480
40747
|
var deployCommand = Command_exports.make(
|
|
40481
40748
|
"deploy",
|
|
40482
|
-
{ ...deployFlags, ...deleteTableFlag },
|
|
40483
|
-
(input) => Effect_exports.flatMap(
|
|
40749
|
+
{ ...deployFlags, ...deleteTableFlag, ...shadowFlags },
|
|
40750
|
+
(input) => Effect_exports.flatMap(
|
|
40751
|
+
config,
|
|
40752
|
+
(c2) => deploy(c2, { ...deployOptions(input), deleteTables: value3(input.deleteTable), shadow: input.shadow, failOnQueryErrors: input.failOnQueryErrors })
|
|
40753
|
+
)
|
|
40484
40754
|
).pipe(
|
|
40485
40755
|
Command_exports.withDescription("Build and deploy. Without a login this creates a temporary unclaimed app."),
|
|
40486
|
-
Command_exports.withExamples([
|
|
40756
|
+
Command_exports.withExamples([
|
|
40757
|
+
{ command: "eraspace deploy --delete-table drafts=12", description: "Deploy a schema without drafts, deleting its 12 rows" },
|
|
40758
|
+
{ command: "eraspace deploy --fail-on-query-errors", description: "Deploy only if no live query starts throwing on the new code" }
|
|
40759
|
+
])
|
|
40760
|
+
);
|
|
40761
|
+
var deploysCommand = Command_exports.make(
|
|
40762
|
+
"deploys",
|
|
40763
|
+
{ app: optionalArg("app", "App name or id (default: this checkout's app)") },
|
|
40764
|
+
({ app }) => Effect_exports.flatMap(config, (c2) => deploys(c2, value3(app)))
|
|
40765
|
+
).pipe(Command_exports.withDescription("List the latest 50 kept versions you can roll back to, newest first"));
|
|
40766
|
+
var rollbackCommand = Command_exports.make(
|
|
40767
|
+
"rollback",
|
|
40768
|
+
{
|
|
40769
|
+
version: Argument_exports.Int("version").pipe(
|
|
40770
|
+
Argument_exports.withDescription("Version to roll back to (default: the newest kept version below the one serving)"),
|
|
40771
|
+
Argument_exports.optional
|
|
40772
|
+
),
|
|
40773
|
+
...deleteTableFlag,
|
|
40774
|
+
...shadowFlags
|
|
40775
|
+
},
|
|
40776
|
+
(input) => Effect_exports.flatMap(
|
|
40777
|
+
config,
|
|
40778
|
+
(c2) => rollback(c2, value3(input.version), { deleteTables: value3(input.deleteTable), shadow: input.shadow, failOnQueryErrors: input.failOnQueryErrors })
|
|
40779
|
+
)
|
|
40780
|
+
).pipe(
|
|
40781
|
+
Command_exports.withDescription("Deploy an earlier version's server code, client files and server env again, as a new version"),
|
|
40782
|
+
Command_exports.withExamples([
|
|
40783
|
+
{ command: "eraspace rollback", description: "Go back to the version before the one serving" },
|
|
40784
|
+
{ command: "eraspace rollback 3", description: "Serve version 3's code again" }
|
|
40785
|
+
])
|
|
40487
40786
|
);
|
|
40488
40787
|
var devCommand = Command_exports.make("dev", deployFlags, (input) => Effect_exports.flatMap(config, (c2) => dev(c2, deployOptions(input)))).pipe(
|
|
40489
40788
|
Command_exports.withDescription("Deploy, then redeploy whenever server/, client/, shared/ or .env.eraspace.server change")
|
|
@@ -40491,14 +40790,38 @@ var devCommand = Command_exports.make("dev", deployFlags, (input) => Effect_expo
|
|
|
40491
40790
|
var callCommand = Command_exports.make(
|
|
40492
40791
|
"call",
|
|
40493
40792
|
{
|
|
40494
|
-
fn: Argument_exports.String("fn").pipe(Argument_exports.withDescription("Query or
|
|
40793
|
+
fn: Argument_exports.String("fn").pipe(Argument_exports.withDescription("Query, mutation or action name")),
|
|
40495
40794
|
args: Argument_exports.String("json").pipe(Argument_exports.withDescription("Arguments, each one a JSON value"), Argument_exports.variadic())
|
|
40496
40795
|
},
|
|
40497
|
-
({ fn: fn3, args: args2 }) => Effect_exports.flatMap(config, (c2) =>
|
|
40796
|
+
({ fn: fn3, args: args2 }) => Effect_exports.flatMap(config, (c2) => call2(c2, fn3, args2))
|
|
40498
40797
|
).pipe(
|
|
40499
40798
|
Command_exports.withDescription("Call a handler with positional JSON arguments"),
|
|
40500
40799
|
Command_exports.withExamples([{ command: `eraspace call messages '"general"'`, description: "Strings are JSON too, so quote them" }])
|
|
40501
40800
|
);
|
|
40801
|
+
var profileCommand = Command_exports.make(
|
|
40802
|
+
"profile",
|
|
40803
|
+
{
|
|
40804
|
+
app: optionalArg("app", "App name or id (default: this checkout's app)"),
|
|
40805
|
+
seconds: Flag_exports.Int("seconds").pipe(Flag_exports.filter((n) => n >= 1 && n <= 60, (n) => `--seconds must be between 1 and 60, got ${n}`), Flag_exports.withDefault(10), Flag_exports.withDescription("How long to sample")),
|
|
40806
|
+
out: optionalString("out", "Where to write the .cpuprofile (default: <app>-<time>.cpuprofile)")
|
|
40807
|
+
},
|
|
40808
|
+
({ app, seconds: seconds2, out }) => Effect_exports.flatMap(config, (c2) => profile(c2, value3(app), seconds2, value3(out)))
|
|
40809
|
+
).pipe(
|
|
40810
|
+
Command_exports.withDescription("Sample the app's server CPU time and write a .cpuprofile that opens in Chrome DevTools against the TypeScript sources"),
|
|
40811
|
+
Command_exports.withExamples([{ command: "eraspace profile my-app --seconds 10", description: "Profile while you exercise the app" }])
|
|
40812
|
+
);
|
|
40813
|
+
var explainCommand = Command_exports.make(
|
|
40814
|
+
"explain",
|
|
40815
|
+
{
|
|
40816
|
+
fn: Argument_exports.String("fn").pipe(Argument_exports.withDescription("Query name")),
|
|
40817
|
+
args: Argument_exports.String("json").pipe(Argument_exports.withDescription("Arguments, each one a JSON value"), Argument_exports.variadic()),
|
|
40818
|
+
json: Flag_exports.Boolean("json").pipe(Flag_exports.withDefault(false), Flag_exports.withDescription("Print the full report as JSON"))
|
|
40819
|
+
},
|
|
40820
|
+
({ fn: fn3, args: args2, json: json2 }) => Effect_exports.flatMap(config, (c2) => explain(c2, fn3, args2, json2))
|
|
40821
|
+
).pipe(
|
|
40822
|
+
Command_exports.withDescription("Run a query once and suggest indexes for rows it filters in JavaScript"),
|
|
40823
|
+
Command_exports.withExamples([{ command: `eraspace explain todosByStatus '"done"'`, description: "Strings are JSON too, so quote them" }])
|
|
40824
|
+
);
|
|
40502
40825
|
var logsCommand = Command_exports.make(
|
|
40503
40826
|
"logs",
|
|
40504
40827
|
{
|
|
@@ -40543,15 +40866,40 @@ var inspectionCommand = Command_exports.make(
|
|
|
40543
40866
|
},
|
|
40544
40867
|
({ visibility, app }) => Effect_exports.flatMap(config, (c2) => inspection(c2, visibility, value3(app)))
|
|
40545
40868
|
).pipe(Command_exports.withDescription("Open or close inspection and logs to everyone"));
|
|
40546
|
-
var
|
|
40547
|
-
var lifecycleCommand = (action, description) => Command_exports.make(
|
|
40548
|
-
|
|
40869
|
+
var lifecycleApp = optionalArg("app", "App name or id (default: this checkout's app)");
|
|
40870
|
+
var lifecycleCommand = (action, description) => Command_exports.make(action, { app: lifecycleApp }, ({ app }) => Effect_exports.flatMap(config, (c2) => lifecycle(c2, action, value3(app), false))).pipe(
|
|
40871
|
+
Command_exports.withDescription(description)
|
|
40872
|
+
);
|
|
40873
|
+
var changesCommand = Command_exports.make(
|
|
40874
|
+
"changes",
|
|
40549
40875
|
{
|
|
40550
|
-
|
|
40551
|
-
|
|
40876
|
+
since: Flag_exports.Int("since").pipe(Flag_exports.filter((n) => n >= 0, (n) => `--since must be a commit number, got ${n}`), Flag_exports.withDefault(0), Flag_exports.withDescription("Print changes committed after this commit")),
|
|
40877
|
+
limit: Flag_exports.Int("limit").pipe(Flag_exports.filter((n) => n >= 1 && n <= 1e4, (n) => `--limit must be between 1 and 10000, got ${n}`), Flag_exports.withDefault(1e3), Flag_exports.withDescription("Rows to print at most (whole commits)"))
|
|
40878
|
+
},
|
|
40879
|
+
({ since, limit }) => Effect_exports.flatMap(config, (c2) => changes2(c2, since, limit))
|
|
40880
|
+
).pipe(
|
|
40881
|
+
Command_exports.withDescription("Print recent committed row changes as NDJSON (the change log keeps 24 hours by default)"),
|
|
40882
|
+
Command_exports.withExamples([{ command: "eraspace changes --since 120 | jq .", description: "Changes committed after commit 120" }])
|
|
40883
|
+
);
|
|
40884
|
+
var undoCommand = Command_exports.make(
|
|
40885
|
+
"undo",
|
|
40886
|
+
{
|
|
40887
|
+
to: Argument_exports.Int("commit").pipe(Argument_exports.withDescription("Commit number to restore rows to")),
|
|
40888
|
+
yes: Flag_exports.Boolean("yes").pipe(Flag_exports.withAlias("y"), Flag_exports.withDefault(false), Flag_exports.withDescription("Confirm the undo"))
|
|
40889
|
+
},
|
|
40890
|
+
({ to, yes }) => Effect_exports.flatMap(config, (c2) => undo(c2, to, yes))
|
|
40891
|
+
).pipe(
|
|
40892
|
+
Command_exports.withDescription("Put every row changed after a commit back as it was; needs --yes. The undo is a commit too, so it can be undone"),
|
|
40893
|
+
Command_exports.withExamples([{ command: "eraspace undo 120 --yes", description: "Revert everything committed after commit 120" }])
|
|
40894
|
+
);
|
|
40895
|
+
var terminateCommand = Command_exports.make(
|
|
40896
|
+
"terminate",
|
|
40897
|
+
{
|
|
40898
|
+
app: lifecycleApp,
|
|
40899
|
+
yes: Flag_exports.Boolean("yes").pipe(Flag_exports.withAlias("y"), Flag_exports.withDefault(false), Flag_exports.withDescription("Confirm that the app stops permanently"))
|
|
40552
40900
|
},
|
|
40553
|
-
({ app, yes }) => Effect_exports.flatMap(config, (c2) => lifecycle(c2,
|
|
40554
|
-
).pipe(Command_exports.withDescription(
|
|
40901
|
+
({ app, yes }) => Effect_exports.flatMap(config, (c2) => lifecycle(c2, "terminate", value3(app), yes))
|
|
40902
|
+
).pipe(Command_exports.withDescription("Stop permanently (data kept, no more deploys); needs --yes"));
|
|
40555
40903
|
var tokenCommand = Command_exports.make("token").pipe(
|
|
40556
40904
|
Command_exports.withDescription("Create, list and revoke API tokens"),
|
|
40557
40905
|
Command_exports.withSubcommands([
|
|
@@ -40583,8 +40931,12 @@ var command = eraspace.pipe(
|
|
|
40583
40931
|
initCommand,
|
|
40584
40932
|
typecheckCommand,
|
|
40585
40933
|
deployCommand,
|
|
40934
|
+
deploysCommand,
|
|
40935
|
+
rollbackCommand,
|
|
40586
40936
|
devCommand,
|
|
40587
40937
|
callCommand,
|
|
40938
|
+
explainCommand,
|
|
40939
|
+
profileCommand,
|
|
40588
40940
|
logsCommand,
|
|
40589
40941
|
inspectCommand,
|
|
40590
40942
|
authCommand,
|
|
@@ -40594,7 +40946,9 @@ var command = eraspace.pipe(
|
|
|
40594
40946
|
inspectionCommand,
|
|
40595
40947
|
lifecycleCommand("archive", "Stop serving and keep the data"),
|
|
40596
40948
|
lifecycleCommand("restore", "Serve an archived app again"),
|
|
40597
|
-
|
|
40949
|
+
terminateCommand,
|
|
40950
|
+
changesCommand,
|
|
40951
|
+
undoCommand,
|
|
40598
40952
|
tokenCommand,
|
|
40599
40953
|
...hidden
|
|
40600
40954
|
])
|