@dxos/cli-util 0.10.0 → 0.11.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.
@@ -0,0 +1,18 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Layer from "effect/Layer";
3
+ import * as Context from "effect/Context";
4
+ //#region src/services/command-config.ts
5
+ var CommandConfig = class CommandConfig extends Context.Tag("CommandConfig")() {
6
+ static isJson = CommandConfig.pipe(Effect.map((config) => config.json));
7
+ static isVerbose = CommandConfig.pipe(Effect.map((config) => config.verbose));
8
+ static layerTest = Layer.succeed(CommandConfig, {
9
+ json: true,
10
+ verbose: false,
11
+ profile: "default",
12
+ logLevel: "info"
13
+ });
14
+ };
15
+ //#endregion
16
+ export { CommandConfig as t };
17
+
18
+ //# sourceMappingURL=chunk-command-config.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-command-config.mjs","names":[],"sources":["../../src/services/command-config.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Context from 'effect/Context';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\n\nexport class CommandConfig extends Context.Tag('CommandConfig')<\n CommandConfig,\n {\n json: boolean;\n verbose: boolean;\n profile: string;\n logLevel: string;\n }\n>() {\n static isJson: Effect.Effect<boolean, never, CommandConfig> = CommandConfig.pipe(Effect.map((config) => config.json));\n static isVerbose: Effect.Effect<boolean, never, CommandConfig> = CommandConfig.pipe(\n Effect.map((config) => config.verbose),\n );\n\n static layerTest = Layer.succeed(CommandConfig, {\n json: true,\n verbose: false,\n profile: 'default',\n logLevel: 'info',\n });\n}\n"],"mappings":";;;;AAQA,IAAa,gBAAb,MAAa,sBAAsB,QAAQ,IAAI,eAAe,CAAC,CAQ7D,CAAC,CAAC;CACF,OAAO,SAAuD,cAAc,KAAK,OAAO,KAAK,WAAW,OAAO,IAAI,CAAC;CACpH,OAAO,YAA0D,cAAc,KAC7E,OAAO,KAAK,WAAW,OAAO,OAAO,CACvC;CAEA,OAAO,YAAY,MAAM,QAAQ,eAAe;EAC9C,MAAM;EACN,SAAS;EACT,SAAS;EACT,UAAU;CACZ,CAAC;AACH"}
@@ -0,0 +1,601 @@
1
+ import { t as CommandConfig } from "./chunk-command-config.mjs";
2
+ import * as BunHttpServer from "@effect/platform-bun/BunHttpServer";
3
+ import * as HttpRouter from "@effect/platform/HttpRouter";
4
+ import * as HttpServer from "@effect/platform/HttpServer";
5
+ import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
6
+ import * as HttpServerResponse from "@effect/platform/HttpServerResponse";
7
+ import * as Effect from "effect/Effect";
8
+ import * as Exit from "effect/Exit";
9
+ import * as Layer from "effect/Layer";
10
+ import * as Option from "effect/Option";
11
+ import * as Ref from "effect/Ref";
12
+ import * as Scope from "effect/Scope";
13
+ import { getPort } from "get-port-please";
14
+ import { spawn } from "node:child_process";
15
+ import { EntityId, SpaceId } from "@dxos/keys";
16
+ import * as Ansi from "@effect/printer-ansi/Ansi";
17
+ import * as Doc from "@effect/printer/Doc";
18
+ import * as Pipeable from "effect/Pipeable";
19
+ import * as Options from "@effect/cli/Options";
20
+ import { Database, Key } from "@dxos/echo";
21
+ import * as AnsiDoc from "@effect/printer-ansi/AnsiDoc";
22
+ import { ClientService } from "@dxos/client";
23
+ import * as Console from "effect/Console";
24
+ import { AppSpace } from "@dxos/app-toolkit";
25
+ import { BaseError } from "@dxos/errors";
26
+ import { log } from "@dxos/log";
27
+ import { EdgeReplicationSetting } from "@dxos/protocols/proto/dxos/echo/metadata";
28
+ import { isBun } from "@dxos/util";
29
+ import * as Duration from "effect/Duration";
30
+ import { SpaceState } from "@dxos/client/echo";
31
+ import * as Config from "effect/Config";
32
+ //#region \0rolldown/runtime.js
33
+ var __defProp = Object.defineProperty;
34
+ var __exportAll = (all, no_symbols) => {
35
+ let target = {};
36
+ for (var name in all) __defProp(target, name, {
37
+ get: all[name],
38
+ enumerable: true
39
+ });
40
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
41
+ return target;
42
+ };
43
+ //#endregion
44
+ //#region src/util/platform.ts
45
+ /**
46
+ * Copy text to the system clipboard.
47
+ * Supports macOS (pbcopy), Windows (clip), and Linux (xclip/xsel).
48
+ */
49
+ var copyToClipboard = (text) => Effect.tryPromise({
50
+ try: () => {
51
+ return new Promise((resolve, reject) => {
52
+ const platform = process.platform;
53
+ let command;
54
+ let args;
55
+ if (platform === "darwin") {
56
+ command = "pbcopy";
57
+ args = [];
58
+ } else if (platform === "win32") {
59
+ command = "clip";
60
+ args = [];
61
+ } else {
62
+ command = "xclip";
63
+ args = ["-selection", "clipboard"];
64
+ }
65
+ const proc = spawn(command, args);
66
+ proc.stdin?.write(text);
67
+ proc.stdin?.end();
68
+ proc.on("close", (code) => {
69
+ if (code === 0) resolve();
70
+ else if (platform === "linux") {
71
+ const proc2 = spawn("xsel", ["--clipboard", "--input"]);
72
+ proc2.stdin?.write(text);
73
+ proc2.stdin?.end();
74
+ proc2.on("close", (code2) => {
75
+ if (code2 === 0) resolve();
76
+ else reject(/* @__PURE__ */ new Error("Failed to copy to clipboard"));
77
+ });
78
+ proc2.on("error", reject);
79
+ } else reject(/* @__PURE__ */ new Error("Failed to copy to clipboard"));
80
+ });
81
+ proc.on("error", reject);
82
+ });
83
+ },
84
+ catch: (error) => /* @__PURE__ */ new Error(`Failed to copy to clipboard: ${error}`)
85
+ });
86
+ /**
87
+ * Open a URL in the system's default browser.
88
+ * Supports macOS (open), Windows (start), and Linux (xdg-open).
89
+ */
90
+ var openBrowser = (url) => Effect.tryPromise({
91
+ try: () => {
92
+ return new Promise((resolve, reject) => {
93
+ const platform = process.platform;
94
+ let command;
95
+ let args;
96
+ if (platform === "darwin") {
97
+ command = "open";
98
+ args = [url];
99
+ } else if (platform === "win32") {
100
+ command = "start";
101
+ args = [url];
102
+ } else {
103
+ command = "xdg-open";
104
+ args = [url];
105
+ }
106
+ const proc = spawn(command, args);
107
+ proc.on("close", (code) => {
108
+ if (code === 0) resolve();
109
+ else reject(/* @__PURE__ */ new Error("Failed to open browser"));
110
+ });
111
+ proc.on("error", reject);
112
+ });
113
+ },
114
+ catch: (error) => /* @__PURE__ */ new Error(`Failed to open browser: ${error}`)
115
+ });
116
+ //#endregion
117
+ //#region src/oauth/server.ts
118
+ /** Default timeout for a full OAuth browser round-trip. */
119
+ var OAUTH_TIMEOUT_MS = 300 * 1e3;
120
+ /**
121
+ * Relay page that performs a top-level redirect to Edge's `authUrl`. Edge finalizes the flow
122
+ * by redirecting the browser back to this server's callback path (bsky.social nullifies
123
+ * `window.opener`, so a popup + postMessage relay can't be used).
124
+ */
125
+ var getRelayPageHtml = (authUrl) => `
126
+ <!DOCTYPE html>
127
+ <html>
128
+ <head>
129
+ <meta charset="UTF-8">
130
+ <script>
131
+ window.location.href = '${authUrl.replace(/'/g, "\\'")}';
132
+ <\/script>
133
+ </head>
134
+ <body></body>
135
+ </html>
136
+ `;
137
+ /**
138
+ * Starts a local Bun HTTP server that relays the browser to Edge's auth URL and captures Edge's
139
+ * eventual top-level redirect back to `callbackPath`. Generic over the callback shape: all query
140
+ * params are captured and returned; an `error` query param fails the wait.
141
+ *
142
+ * Used by both the integration-connect flow (`/redirect/oauth`) and the identity-recovery login
143
+ * flow (`/redirect/oauth-recovery`).
144
+ */
145
+ var startOAuthCallbackServer = (callbackPath) => Effect.gen(function* () {
146
+ const port = yield* Effect.promise(() => getPort({ random: true }));
147
+ const origin = `http://localhost:${port}`;
148
+ const received = yield* Ref.make(false);
149
+ const outcome = yield* Ref.make(Option.none());
150
+ const parseUrl = (rawUrl) => new URL(rawUrl.startsWith("http") ? rawUrl : `http://localhost${rawUrl}`);
151
+ const serverLayer = HttpRouter.empty.pipe(HttpRouter.get("/oauth-relay", Effect.gen(function* () {
152
+ const request = yield* HttpServerRequest.HttpServerRequest;
153
+ const authUrl = parseUrl(request.url).searchParams.get("authUrl");
154
+ if (!authUrl) return yield* HttpServerResponse.text("Missing authUrl parameter", { status: 400 });
155
+ return yield* HttpServerResponse.text(getRelayPageHtml(authUrl), { headers: { "Content-Type": "text/html" } });
156
+ })), HttpRouter.get(callbackPath, Effect.gen(function* () {
157
+ if (Option.isSome(yield* Ref.get(outcome))) return yield* HttpServerResponse.text("Already received.", { status: 400 });
158
+ const request = yield* HttpServerRequest.HttpServerRequest;
159
+ const params = parseUrl(request.url).searchParams;
160
+ const error = params.get("error");
161
+ if (error) {
162
+ yield* Ref.set(outcome, Option.some({
163
+ success: false,
164
+ reason: error
165
+ }));
166
+ yield* Ref.set(received, true);
167
+ return yield* HttpServerResponse.text(`<html><body><h1>Authentication failed</h1><p>${error}</p></body></html>`, {
168
+ status: 400,
169
+ headers: { "Content-Type": "text/html" }
170
+ });
171
+ }
172
+ const captured = {};
173
+ for (const [key, value] of params.entries()) captured[key] = value;
174
+ yield* Ref.set(outcome, Option.some({
175
+ success: true,
176
+ params: captured
177
+ }));
178
+ yield* Ref.set(received, true);
179
+ return yield* HttpServerResponse.text("<html><body><h1>Authentication successful! You can close this window.</h1></body></html>", { headers: { "Content-Type": "text/html" } });
180
+ }))).pipe(HttpServer.serve()).pipe(Layer.provide(BunHttpServer.layer({ port })));
181
+ const scope = yield* Scope.make();
182
+ yield* Layer.build(serverLayer).pipe(Scope.extend(scope));
183
+ const waitForResult = (timeoutMs = OAUTH_TIMEOUT_MS) => Effect.race(Effect.gen(function* () {
184
+ while (true) {
185
+ if (yield* Ref.get(received)) break;
186
+ yield* Effect.sleep("500 millis");
187
+ }
188
+ const result = yield* Ref.get(outcome);
189
+ return yield* Option.match(result, {
190
+ onNone: () => Effect.fail(/* @__PURE__ */ new Error("OAuth callback received but no result")),
191
+ onSome: (value) => value.success ? Effect.succeed(value.params) : Effect.fail(/* @__PURE__ */ new Error(`OAuth flow failed: ${value.reason}`))
192
+ });
193
+ }), Effect.sleep(`${timeoutMs} millis`).pipe(Effect.flatMap(() => Effect.fail(/* @__PURE__ */ new Error("OAuth flow timed out")))));
194
+ return {
195
+ port,
196
+ origin,
197
+ open: (authUrl) => openBrowser(`${origin}/oauth-relay?authUrl=${encodeURIComponent(authUrl)}`),
198
+ waitForResult,
199
+ stop: () => Scope.close(scope, Exit.void).pipe(Effect.catchAll(() => Effect.void))
200
+ };
201
+ });
202
+ //#endregion
203
+ //#region src/oauth/recovery.ts
204
+ /** Path Edge redirects the browser to after a recovery/register OAuth round-trip. */
205
+ var RECOVERY_CALLBACK_PATH = "/redirect/oauth-recovery";
206
+ /**
207
+ * Drives the gate's OAuth identity-recovery flow from a CLI: starts a local callback server, asks
208
+ * Edge to begin a `recovery`-purpose OAuth flow (advertising the local server as the redirect
209
+ * origin via the `Origin` header), opens the browser, and resolves with the one-time
210
+ * `recoveryProof` Edge carries back in its redirect. The caller redeems the proof via
211
+ * `client.halo.recoverIdentity({ recoveryProof })`.
212
+ *
213
+ * Recovery runs before any identity exists, so no auth header is sent — Edge resolves the user's
214
+ * space / token server-side from the recovery binding, and the `spaceId` / `accessTokenId` in the
215
+ * request are unused (random values satisfy request validation).
216
+ */
217
+ var performRecoveryOAuthFlow = Effect.fn(function* (params) {
218
+ const server = yield* startOAuthCallbackServer(RECOVERY_CALLBACK_PATH);
219
+ return yield* Effect.gen(function* () {
220
+ const initiateUrl = new URL("/oauth/initiate", params.edgeBaseUrl).toString();
221
+ const response = yield* Effect.tryPromise({
222
+ try: () => fetch(initiateUrl, {
223
+ method: "POST",
224
+ headers: {
225
+ "Content-Type": "application/json",
226
+ "Origin": server.origin
227
+ },
228
+ body: JSON.stringify({
229
+ provider: params.provider,
230
+ scopes: params.scopes,
231
+ spaceId: SpaceId.random(),
232
+ accessTokenId: EntityId.random(),
233
+ purpose: "recovery",
234
+ loginHint: params.loginHint
235
+ })
236
+ }),
237
+ catch: (error) => /* @__PURE__ */ new Error(`OAuth initiate request failed: ${error instanceof Error ? error.message : String(error)}`)
238
+ });
239
+ const envelope = yield* Effect.tryPromise({
240
+ try: () => response.json(),
241
+ catch: (error) => /* @__PURE__ */ new Error(`OAuth initiate response parse failed: ${String(error)}`)
242
+ });
243
+ if (!envelope.success || !envelope.data?.authUrl) return yield* Effect.fail(/* @__PURE__ */ new Error(`OAuth initiation failed: ${envelope.error?.message ?? "unknown error"}`));
244
+ yield* server.open(envelope.data.authUrl);
245
+ const recoveryProof = (yield* server.waitForResult(OAUTH_TIMEOUT_MS)).recoveryProof;
246
+ if (!recoveryProof) return yield* Effect.fail(/* @__PURE__ */ new Error("OAuth recovery completed but no recoveryProof was returned."));
247
+ return { recoveryProof };
248
+ }).pipe(Effect.ensuring(server.stop()));
249
+ });
250
+ //#endregion
251
+ //#region src/util/form-builder.ts
252
+ var form_builder_exports = /* @__PURE__ */ __exportAll({
253
+ build: () => build,
254
+ each: () => each,
255
+ make: () => make,
256
+ nest: () => nest,
257
+ nestedOption: () => nestedOption,
258
+ option: () => option,
259
+ set: () => set,
260
+ when: () => when
261
+ });
262
+ var FormBuilderImpl = class {
263
+ entries = [];
264
+ title;
265
+ prefix;
266
+ constructor(options = {}) {
267
+ this.title = options.title;
268
+ this.prefix = options.prefix ?? "- ";
269
+ }
270
+ pipe() {
271
+ return Pipeable.pipeArguments(this, arguments);
272
+ }
273
+ };
274
+ /**
275
+ * Creates a new FormBuilder instance.
276
+ */
277
+ var make = (props) => new FormBuilderImpl(props);
278
+ var calculateDimensions = (entries, prefix) => {
279
+ const maxKeyLen = Math.max(0, ...entries.map((entry) => entry.key.length));
280
+ return {
281
+ maxKeyLen,
282
+ targetWidth: prefix.length + maxKeyLen + 2
283
+ };
284
+ };
285
+ var buildKeyLine = (prefix, key, targetWidth) => {
286
+ return Doc.annotate(Doc.fill(targetWidth)(Doc.text(prefix + key + ": ")), Ansi.blackBright);
287
+ };
288
+ var setImpl = (builder, key, value, color) => {
289
+ if (value !== void 0) {
290
+ let valueDoc;
291
+ if (typeof value === "object" && value !== null) valueDoc = value;
292
+ else valueDoc = Doc.text(String(value));
293
+ if (color) {
294
+ const ansi = typeof color === "function" ? color(value) : color;
295
+ valueDoc = Doc.annotate(valueDoc, ansi);
296
+ }
297
+ builder.entries.push({
298
+ key,
299
+ value: valueDoc,
300
+ isNested: false
301
+ });
302
+ }
303
+ return builder;
304
+ };
305
+ function set(builderOrKey, keyOrValue, valueOrColor, color) {
306
+ if (builderOrKey instanceof FormBuilderImpl) return setImpl(builderOrKey, keyOrValue, valueOrColor, color);
307
+ else {
308
+ const key = builderOrKey;
309
+ const value = keyOrValue;
310
+ const color = valueOrColor;
311
+ return (builder) => setImpl(builder, key, value, color);
312
+ }
313
+ }
314
+ var nestImpl = (parent, key, builder) => {
315
+ let valueDoc = build({
316
+ ...builder,
317
+ title: void 0,
318
+ entries: builder.entries
319
+ });
320
+ valueDoc = Doc.indent(valueDoc, 2);
321
+ parent.entries.push({
322
+ key,
323
+ value: valueDoc,
324
+ isNested: true
325
+ });
326
+ return parent;
327
+ };
328
+ function nest(parentOrKey, keyOrBuilder, builder) {
329
+ if (parentOrKey instanceof FormBuilderImpl) return nestImpl(parentOrKey, keyOrBuilder, builder);
330
+ else {
331
+ const key = parentOrKey;
332
+ const builder = keyOrBuilder;
333
+ return (parent) => nestImpl(parent, key, builder);
334
+ }
335
+ }
336
+ var optionImpl = (builder, key, value, color) => {
337
+ if (Option.isSome(value)) return setImpl(builder, key, value.value, color);
338
+ return builder;
339
+ };
340
+ function option(builderOrKey, keyOrValue, valueOrColor, color) {
341
+ if (builderOrKey instanceof FormBuilderImpl) return optionImpl(builderOrKey, keyOrValue, valueOrColor, color);
342
+ else {
343
+ const key = builderOrKey;
344
+ const value = keyOrValue;
345
+ const color = valueOrColor;
346
+ return (builder) => optionImpl(builder, key, value, color);
347
+ }
348
+ }
349
+ var nestedOptionImpl = (builder, key, value) => {
350
+ if (Option.isSome(value)) return nestImpl(builder, key, value.value);
351
+ return builder;
352
+ };
353
+ function nestedOption(builderOrKey, keyOrValue, value) {
354
+ if (builderOrKey instanceof FormBuilderImpl) return nestedOptionImpl(builderOrKey, keyOrValue, value);
355
+ else {
356
+ const key = builderOrKey;
357
+ const value = keyOrValue;
358
+ return (builder) => nestedOptionImpl(builder, key, value);
359
+ }
360
+ }
361
+ var whenImpl = (builder, condition, ...ops) => {
362
+ if (condition) for (const op of ops) op(builder);
363
+ return builder;
364
+ };
365
+ function when(builderOrCondition, conditionOrOp, ...ops) {
366
+ if (builderOrCondition instanceof FormBuilderImpl) return whenImpl(builderOrCondition, conditionOrOp, ...ops);
367
+ else {
368
+ const condition = builderOrCondition;
369
+ const allOps = [conditionOrOp, ...ops].filter((op) => typeof op === "function");
370
+ return (builder) => whenImpl(builder, condition, ...allOps);
371
+ }
372
+ }
373
+ var eachImpl = (builder, items, fn) => {
374
+ items.forEach((item) => fn(item)(builder));
375
+ return builder;
376
+ };
377
+ function each(builderOrItems, itemsOrFn, fn) {
378
+ if (builderOrItems instanceof FormBuilderImpl) return eachImpl(builderOrItems, itemsOrFn, fn);
379
+ else {
380
+ const items = builderOrItems;
381
+ const fn = itemsOrFn;
382
+ return (builder) => eachImpl(builder, items, fn);
383
+ }
384
+ }
385
+ /**
386
+ * Builds the final document.
387
+ */
388
+ var build = (builder) => {
389
+ const { targetWidth } = calculateDimensions(builder.entries, builder.prefix);
390
+ const entryLines = [];
391
+ builder.entries.forEach(({ key, value, isNested }) => {
392
+ const keyLine = buildKeyLine(builder.prefix, key, targetWidth);
393
+ if (isNested) entryLines.push(Doc.hcat([
394
+ keyLine,
395
+ Doc.hardLine,
396
+ value
397
+ ]));
398
+ else entryLines.push(Doc.hcat([keyLine, value]));
399
+ });
400
+ const entriesDoc = Doc.vsep(entryLines);
401
+ if (builder.title) {
402
+ const titleDoc = Doc.hcat([Doc.annotate(Doc.text(builder.title), Ansi.combine(Ansi.bold, Ansi.cyan))]);
403
+ return Doc.cat(titleDoc, Doc.cat(Doc.line, entriesDoc));
404
+ }
405
+ return entriesDoc;
406
+ };
407
+ //#endregion
408
+ //#region src/util/format.ts
409
+ var BYTE_UNITS = [
410
+ "B",
411
+ "KB",
412
+ "MB",
413
+ "GB",
414
+ "TB"
415
+ ];
416
+ /**
417
+ * Format a byte count using binary units.
418
+ */
419
+ var formatBytes = (bytes) => {
420
+ let value = bytes;
421
+ let unit = 0;
422
+ while (value >= 1024 && unit < BYTE_UNITS.length - 1) {
423
+ value /= 1024;
424
+ unit++;
425
+ }
426
+ return `${unit === 0 ? value : value.toFixed(1)} ${BYTE_UNITS[unit]}`;
427
+ };
428
+ //#endregion
429
+ //#region src/util/options.ts
430
+ var Common = {
431
+ functionId: Options.text("function-id").pipe(Options.withDescription("EDGE Function ID.")),
432
+ spaceId: Options.text("space-id").pipe(Options.withSchema(Key.SpaceId), Options.withDescription("Space ID."))
433
+ };
434
+ //#endregion
435
+ //#region src/util/printer.ts
436
+ /**
437
+ * Pretty print document.
438
+ */
439
+ var print = (doc) => AnsiDoc.render(doc, { style: "pretty" });
440
+ /**
441
+ * Pretty prints a list of documents with ANSI colors.
442
+ */
443
+ var printList = (items) => AnsiDoc.render(Doc.vsep(items.map((item) => Doc.cat(item, Doc.hardLine))), { style: "pretty" });
444
+ //#endregion
445
+ //#region src/util/runtime.ts
446
+ /** @deprecated Migrate to providing types via plugin capabilities. */
447
+ var withTypes = (...types) => Effect.gen(function* () {
448
+ const client = yield* ClientService;
449
+ yield* Effect.promise(() => client.addTypes(types));
450
+ });
451
+ //#endregion
452
+ //#region src/util/space.ts
453
+ var __dxlog_file = "/__w/dxos/dxos/packages/devtools/cli-util/src/util/space.ts";
454
+ var getSpace = (spaceId) => Effect.gen(function* () {
455
+ const client = yield* ClientService;
456
+ return yield* Option.fromNullable(client.spaces.get(spaceId));
457
+ }).pipe(Effect.catchTag("NoSuchElementException", () => Effect.fail(new SpaceNotFoundError(spaceId))));
458
+ var spaceIdWithDefault = (spaceId) => Effect.gen(function* () {
459
+ const client = yield* ClientService;
460
+ return Option.getOrElse(spaceId, () => {
461
+ const personal = AppSpace.getPersonalSpace(client);
462
+ if (!personal) throw new Error("No space ID provided and no personal space found.");
463
+ return personal.id;
464
+ });
465
+ });
466
+ var spaceLayer = (spaceId$, fallbackToPersonalSpace = false) => {
467
+ const getSpace = Effect.fn(function* () {
468
+ const client = yield* ClientService;
469
+ const resolveSpace = () => {
470
+ if (!fallbackToPersonalSpace) return spaceId$.pipe(Option.flatMap((id) => Option.fromNullable(client.spaces.get(id))));
471
+ return spaceId$.pipe(Option.flatMap((id) => Option.fromNullable(client.spaces.get(id))), Option.orElse(() => Option.fromNullable(AppSpace.getPersonalSpace(client))), Option.orElse(() => Option.fromNullable(client.spaces.get()[0])));
472
+ };
473
+ const space = resolveSpace().pipe(Option.getOrUndefined);
474
+ if (space) yield* Effect.promise(() => space.waitUntilReady());
475
+ return space;
476
+ });
477
+ const NO_DB_STUB = { get db() {
478
+ throw new Error("Space not found");
479
+ } };
480
+ return Layer.scoped(Database.Service, Effect.acquireRelease(Effect.gen(function* () {
481
+ const space = yield* getSpace();
482
+ if (!space) return NO_DB_STUB;
483
+ return { db: space.db };
484
+ }), (holder) => holder === NO_DB_STUB ? Effect.void : Effect.promise(() => holder.db.flush())));
485
+ };
486
+ var waitForSync = Effect.fn(function* (space) {
487
+ if (!isBun()) return;
488
+ if (space.internal.data.edgeReplication !== EdgeReplicationSetting.ENABLED) {
489
+ yield* Console.log("Edge replication is disabled, enabling...");
490
+ yield* Effect.promise(() => space.internal.setEdgeReplicationPreference(EdgeReplicationSetting.ENABLED));
491
+ }
492
+ yield* Effect.promise(() => space.internal.syncToEdge({ onProgress: (state) => log.info("syncing", { state: state ?? "no connection to edge" }, {
493
+ "~LogMeta": "~LogMeta",
494
+ F: __dxlog_file,
495
+ L: 114,
496
+ S: this
497
+ }) }));
498
+ yield* Console.log("Sync complete");
499
+ });
500
+ var flushAndSync = Effect.fn(function* (opts) {
501
+ yield* Database.flush(opts);
502
+ yield* waitForSync(yield* getSpace(yield* Database.spaceId));
503
+ });
504
+ var SpaceNotFoundError = class extends BaseError.extend("SpaceNotFoundError", "Space not found") {
505
+ constructor(spaceId, options) {
506
+ super({
507
+ context: { spaceId },
508
+ ...options
509
+ });
510
+ }
511
+ };
512
+ //#endregion
513
+ //#region src/util/space-format.ts
514
+ var DEFAULT_OPTIONS = {
515
+ verbose: false,
516
+ truncateKeys: false,
517
+ waitSeconds: 0
518
+ };
519
+ /**
520
+ * Per-async-read internal timeout. Some `space.internal.*` getters do
521
+ * filesystem / network IO and can themselves hang on a partially-loaded
522
+ * space; cap each one so the command can never be held hostage by SDK
523
+ * internals.
524
+ */
525
+ var READ_TIMEOUT_SECONDS = 2;
526
+ var tryWithFallback = (label, run, fallback) => Effect.tryPromise(run).pipe(Effect.timeoutFail({
527
+ duration: Duration.seconds(READ_TIMEOUT_SECONDS),
528
+ onTimeout: () => /* @__PURE__ */ new Error(`${label} timed out`)
529
+ }), Effect.catchAll(() => Effect.succeed(fallback)));
530
+ var tryWithFallbackSync = (read, fallback) => {
531
+ try {
532
+ return read();
533
+ } catch {
534
+ return fallback;
535
+ }
536
+ };
537
+ var formatSpace = Effect.fn(function* (space, options = {}) {
538
+ const { waitSeconds } = {
539
+ ...DEFAULT_OPTIONS,
540
+ ...options
541
+ };
542
+ if (waitSeconds > 0) yield* Effect.tryPromise(() => space.waitUntilReady()).pipe(Effect.timeoutFail({
543
+ duration: Duration.seconds(waitSeconds),
544
+ onTimeout: () => /* @__PURE__ */ new Error("waitUntilReady timed out")
545
+ }), Effect.catchAll(() => Effect.void));
546
+ const state = tryWithFallbackSync(() => space.state.get(), SpaceState.SPACE_INITIALIZING);
547
+ const ready = state === SpaceState.SPACE_READY;
548
+ const metrics = tryWithFallbackSync(() => space.internal.data.metrics, void 0);
549
+ const startup = metrics?.open && metrics?.ready ? metrics.ready.getTime() - metrics.open.getTime() : void 0;
550
+ const pipeline = tryWithFallbackSync(() => space.internal.data.pipeline, void 0);
551
+ const epoch = pipeline?.currentEpoch?.subject.assertion.number;
552
+ const syncState = aggregateSyncState(yield* tryWithFallback("getAutomergeSyncState", () => space.internal.db.getAutomergeSyncState(), { peers: {} }));
553
+ const name = ready ? tryWithFallbackSync(() => space.properties.name, void 0) : "loading...";
554
+ const members = tryWithFallbackSync(() => space.members.get().length, 0);
555
+ const objects = tryWithFallbackSync(() => space.internal.db.getAllObjectIds().length, 0);
556
+ const key = options.truncateKeys ? tryWithFallbackSync(() => space.key.truncate(), "") : tryWithFallbackSync(() => space.key.toHex(), "");
557
+ return {
558
+ id: space.id,
559
+ state: SpaceState[state],
560
+ name,
561
+ members,
562
+ objects,
563
+ key,
564
+ epoch,
565
+ startup,
566
+ automergeRoot: pipeline?.spaceRootUrl,
567
+ syncState: `${syncState.count} ${getSyncIndicator(syncState.up, syncState.down)} (${syncState.peers} peers)`
568
+ };
569
+ });
570
+ var aggregateSyncState = (syncState) => {
571
+ const peers = Object.keys(syncState.peers ?? {}).length;
572
+ const missingOnLocal = Math.max(...Object.values(syncState.peers ?? {}).map((peer) => peer.missingOnLocal), 0);
573
+ const missingOnRemote = Math.max(...Object.values(syncState.peers ?? {}).map((peer) => peer.missingOnRemote), 0);
574
+ const differentDocuments = Math.max(...Object.values(syncState.peers ?? {}).map((peer) => peer.differentDocuments), 0);
575
+ return {
576
+ count: missingOnLocal + missingOnRemote + differentDocuments,
577
+ peers,
578
+ up: missingOnRemote > 0 || differentDocuments > 0,
579
+ down: missingOnLocal > 0 || differentDocuments > 0
580
+ };
581
+ };
582
+ var getSyncIndicator = (up, down) => {
583
+ if (up) if (down) return "⇅";
584
+ else return "↑";
585
+ else if (down) return "↓";
586
+ else return "✓";
587
+ };
588
+ /**
589
+ * Pretty prints a space with ANSI colors.
590
+ */
591
+ var printSpace = (spaceData) => make({ title: spaceData.name ?? spaceData.id }).pipe(set("id", spaceData.id), set("state", spaceData.state), set("key", spaceData.key), set("members", spaceData.members), set("objects", spaceData.objects), set("epoch", spaceData.epoch ?? "<none>"), set("startup", spaceData.startup ? `${spaceData.startup}ms` : "<none>"), set("syncState", spaceData.syncState), set("automergeRoot", spaceData.automergeRoot ?? "<none>"), build);
592
+ //#endregion
593
+ //#region src/util/timeout.ts
594
+ var withTimeout = Effect.fnUntraced(function* (effect) {
595
+ const duration = (yield* Config.integer("TIMEOUT").pipe(Config.option)).pipe(Option.map(Duration.millis), Option.getOrElse(() => Duration.infinity));
596
+ return yield* effect.pipe(Effect.timeout(duration));
597
+ });
598
+ //#endregion
599
+ export { CommandConfig, Common, form_builder_exports as FormBuilder, OAUTH_TIMEOUT_MS, SpaceNotFoundError, copyToClipboard, flushAndSync, formatBytes, formatSpace, getSpace, openBrowser, performRecoveryOAuthFlow, print, printList, printSpace, spaceIdWithDefault, spaceLayer, startOAuthCallbackServer, waitForSync, withTimeout, withTypes };
600
+
601
+ //# sourceMappingURL=index.mjs.map