@dxos/cli-util 0.8.4-main.9be5663bfe → 0.8.4-main.abd8ff62ef
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/dist/lib/node-esm/{chunk-6TKUDRM6.mjs → chunk-N5LOOWPE.mjs} +1 -1
- package/dist/lib/node-esm/index.mjs +92 -59
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +1 -1
- package/dist/lib/node-esm/testing/index.mjs.map +2 -2
- package/dist/types/src/testing/test-console.d.ts.map +1 -1
- package/dist/types/src/testing/test-layer.d.ts +1 -1
- package/dist/types/src/util/form-builder.d.ts.map +1 -1
- package/dist/types/src/util/options.d.ts.map +1 -1
- package/dist/types/src/util/platform.d.ts.map +1 -1
- package/dist/types/src/util/printer.d.ts.map +1 -1
- package/dist/types/src/util/space-format.d.ts +13 -2
- package/dist/types/src/util/space-format.d.ts.map +1 -1
- package/dist/types/src/util/space.d.ts +6 -6
- package/dist/types/src/util/space.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -13
- package/src/util/space-format.ts +86 -12
- package/src/util/space.ts +30 -16
- /package/dist/lib/node-esm/{chunk-6TKUDRM6.mjs.map → chunk-N5LOOWPE.mjs.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/cli-util",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.abd8ff62ef",
|
|
4
4
|
"description": "Shared CLI utilities for DXOS CLI commands and plugins",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -33,21 +33,22 @@
|
|
|
33
33
|
"@effect/cli": "0.73.2",
|
|
34
34
|
"@effect/printer": "0.47.0",
|
|
35
35
|
"@effect/printer-ansi": "0.47.0",
|
|
36
|
-
"@dxos/app-toolkit": "0.8.4-main.
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/
|
|
39
|
-
"@dxos/echo": "0.8.4-main.
|
|
40
|
-
"@dxos/errors": "0.8.4-main.
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/protocols": "0.8.4-main.
|
|
36
|
+
"@dxos/app-toolkit": "0.8.4-main.abd8ff62ef",
|
|
37
|
+
"@dxos/client": "0.8.4-main.abd8ff62ef",
|
|
38
|
+
"@dxos/debug": "0.8.4-main.abd8ff62ef",
|
|
39
|
+
"@dxos/echo": "0.8.4-main.abd8ff62ef",
|
|
40
|
+
"@dxos/errors": "0.8.4-main.abd8ff62ef",
|
|
41
|
+
"@dxos/effect": "0.8.4-main.abd8ff62ef",
|
|
42
|
+
"@dxos/compute": "0.8.4-main.abd8ff62ef",
|
|
43
|
+
"@dxos/functions": "0.8.4-main.abd8ff62ef",
|
|
44
|
+
"@dxos/log": "0.8.4-main.abd8ff62ef",
|
|
45
|
+
"@dxos/protocols": "0.8.4-main.abd8ff62ef",
|
|
46
|
+
"@dxos/util": "0.8.4-main.abd8ff62ef"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"effect": "3.20.0",
|
|
49
|
-
"typescript": "^6.0.
|
|
50
|
-
"vitest": "
|
|
50
|
+
"typescript": "^6.0.3",
|
|
51
|
+
"vitest": "4.1.5"
|
|
51
52
|
},
|
|
52
53
|
"peerDependencies": {
|
|
53
54
|
"effect": "3.20.0"
|
package/src/util/space-format.ts
CHANGED
|
@@ -2,40 +2,114 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import * as Duration from 'effect/Duration';
|
|
5
6
|
import * as Effect from 'effect/Effect';
|
|
6
7
|
|
|
7
8
|
import { type Space, SpaceState, type SpaceSyncState } from '@dxos/client/echo';
|
|
8
9
|
|
|
9
10
|
import * as FormBuilder from './form-builder';
|
|
10
11
|
|
|
12
|
+
export type FormatSpaceOptions = {
|
|
13
|
+
verbose?: boolean;
|
|
14
|
+
truncateKeys?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* If set, wait up to this many seconds for the space to reach
|
|
17
|
+
* `SPACE_READY` before reading its fields. If unset, read whatever state
|
|
18
|
+
* is available right now — much safer for `space list` etc., where a
|
|
19
|
+
* single stuck space would otherwise hang the entire command.
|
|
20
|
+
*/
|
|
21
|
+
waitSeconds?: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const DEFAULT_OPTIONS: Required<FormatSpaceOptions> = {
|
|
25
|
+
verbose: false,
|
|
26
|
+
truncateKeys: false,
|
|
27
|
+
waitSeconds: 0,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Per-async-read internal timeout. Some `space.internal.*` getters do
|
|
32
|
+
* filesystem / network IO and can themselves hang on a partially-loaded
|
|
33
|
+
* space; cap each one so the command can never be held hostage by SDK
|
|
34
|
+
* internals.
|
|
35
|
+
*/
|
|
36
|
+
const READ_TIMEOUT_SECONDS = 2;
|
|
37
|
+
|
|
38
|
+
const tryWithFallback = <T>(label: string, run: () => Promise<T>, fallback: T) =>
|
|
39
|
+
Effect.tryPromise(run).pipe(
|
|
40
|
+
Effect.timeoutFail({
|
|
41
|
+
duration: Duration.seconds(READ_TIMEOUT_SECONDS),
|
|
42
|
+
onTimeout: () => new Error(`${label} timed out`),
|
|
43
|
+
}),
|
|
44
|
+
Effect.catchAll(() => Effect.succeed(fallback)),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const tryWithFallbackSync = <T>(read: () => T, fallback: T): T => {
|
|
48
|
+
try {
|
|
49
|
+
return read();
|
|
50
|
+
} catch {
|
|
51
|
+
return fallback;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
11
55
|
// TODO(wittjosiah): Use @effect/printer.
|
|
12
|
-
export const formatSpace = Effect.fn(function* (space: Space, options = {
|
|
13
|
-
|
|
56
|
+
export const formatSpace = Effect.fn(function* (space: Space, options: FormatSpaceOptions = {}) {
|
|
57
|
+
const { waitSeconds } = { ...DEFAULT_OPTIONS, ...options };
|
|
58
|
+
|
|
59
|
+
// Opt-in wait. Defaults to NO wait so a single stuck space can't hang
|
|
60
|
+
// an enumeration command (e.g. `dx space list`).
|
|
61
|
+
if (waitSeconds > 0) {
|
|
62
|
+
yield* Effect.tryPromise(() => space.waitUntilReady()).pipe(
|
|
63
|
+
Effect.timeoutFail({
|
|
64
|
+
duration: Duration.seconds(waitSeconds),
|
|
65
|
+
onTimeout: () => new Error('waitUntilReady timed out'),
|
|
66
|
+
}),
|
|
67
|
+
Effect.catchAll(() => Effect.void),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const state = tryWithFallbackSync(() => space.state.get(), SpaceState.SPACE_INITIALIZING);
|
|
72
|
+
const ready = state === SpaceState.SPACE_READY;
|
|
14
73
|
|
|
15
74
|
// TODO(burdon): Factor out.
|
|
16
75
|
// TODO(burdon): Agent needs to restart before `ready` is available.
|
|
17
|
-
const
|
|
18
|
-
|
|
76
|
+
const metrics = tryWithFallbackSync(
|
|
77
|
+
() => space.internal.data.metrics,
|
|
78
|
+
undefined as { open?: Date; ready?: Date } | undefined,
|
|
79
|
+
);
|
|
80
|
+
const startup = metrics?.open && metrics?.ready ? metrics.ready.getTime() - metrics.open.getTime() : undefined;
|
|
19
81
|
|
|
20
82
|
// TODO(burdon): Get feeds from client-services if verbose (factor out from devtools/diagnostics).
|
|
21
83
|
// const host = client.services.services.DevtoolsHost!;
|
|
22
|
-
const pipeline = space.internal.data.pipeline;
|
|
84
|
+
const pipeline = tryWithFallbackSync(() => space.internal.data.pipeline, undefined);
|
|
23
85
|
const epoch = pipeline?.currentEpoch?.subject.assertion.number;
|
|
24
86
|
|
|
25
|
-
|
|
87
|
+
// The sync-state read does IO; cap it so a stuck space can't hang the
|
|
88
|
+
// command. Falls back to a "no peers" placeholder.
|
|
89
|
+
const syncStateRaw = yield* tryWithFallback('getSyncState', () => space.internal.db.coreDatabase.getSyncState(), {
|
|
90
|
+
peers: {},
|
|
91
|
+
} as SpaceSyncState);
|
|
92
|
+
const syncState = aggregateSyncState(syncStateRaw);
|
|
93
|
+
|
|
94
|
+
const name = ready ? tryWithFallbackSync(() => space.properties.name, undefined) : 'loading...';
|
|
95
|
+
const members = tryWithFallbackSync(() => space.members.get().length, 0);
|
|
96
|
+
const objects = tryWithFallbackSync(() => space.internal.db.coreDatabase.getAllObjectIds().length, 0);
|
|
97
|
+
const key = options.truncateKeys
|
|
98
|
+
? tryWithFallbackSync(() => space.key.truncate(), '')
|
|
99
|
+
: tryWithFallbackSync(() => space.key.toHex(), '');
|
|
26
100
|
|
|
27
101
|
return {
|
|
28
102
|
id: space.id,
|
|
29
|
-
state: SpaceState[
|
|
30
|
-
name
|
|
103
|
+
state: SpaceState[state],
|
|
104
|
+
name,
|
|
31
105
|
|
|
32
|
-
members
|
|
33
|
-
objects
|
|
106
|
+
members,
|
|
107
|
+
objects,
|
|
34
108
|
|
|
35
|
-
key
|
|
109
|
+
key,
|
|
36
110
|
epoch,
|
|
37
111
|
startup,
|
|
38
|
-
automergeRoot:
|
|
112
|
+
automergeRoot: pipeline?.spaceRootUrl,
|
|
39
113
|
// appliedEpoch,
|
|
40
114
|
syncState: `${syncState.count} ${getSyncIndicator(syncState.up, syncState.down)} (${syncState.peers} peers)`,
|
|
41
115
|
};
|
package/src/util/space.ts
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import * as Console from 'effect/Console';
|
|
6
6
|
import * as Effect from 'effect/Effect';
|
|
7
7
|
import * as Layer from 'effect/Layer';
|
|
8
|
-
import * as Match from 'effect/Match';
|
|
9
8
|
import * as Option from 'effect/Option';
|
|
10
9
|
|
|
11
10
|
import { getPersonalSpace } from '@dxos/app-toolkit';
|
|
@@ -44,16 +43,25 @@ export const spaceLayer = (
|
|
|
44
43
|
const getSpace = Effect.fn(function* () {
|
|
45
44
|
const client = yield* ClientService;
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
// Resolution order when fallbackToPersonalSpace is true:
|
|
47
|
+
// 1. the explicit spaceId arg (if provided);
|
|
48
|
+
// 2. the space tagged `org.dxos.space.personal`;
|
|
49
|
+
// 3. the first available space.
|
|
50
|
+
// This keeps profiles created outside composer-app (which is what creates
|
|
51
|
+
// the personal-space tag on identity creation) usable — the alternative
|
|
52
|
+
// is a "Space not found" throw deep inside CredentialsService.
|
|
53
|
+
const resolveSpace = () => {
|
|
54
|
+
if (!fallbackToPersonalSpace) {
|
|
55
|
+
return spaceId$.pipe(Option.flatMap((id) => Option.fromNullable(client.spaces.get(id))));
|
|
56
|
+
}
|
|
57
|
+
return spaceId$.pipe(
|
|
58
|
+
Option.flatMap((id) => Option.fromNullable(client.spaces.get(id))),
|
|
59
|
+
Option.orElse(() => Option.fromNullable(getPersonalSpace(client))),
|
|
60
|
+
Option.orElse(() => Option.fromNullable(client.spaces.get()[0])),
|
|
61
|
+
);
|
|
62
|
+
};
|
|
52
63
|
|
|
53
|
-
const space =
|
|
54
|
-
Option.flatMap((id) => Option.fromNullable(client.spaces.get(id))),
|
|
55
|
-
Option.getOrUndefined,
|
|
56
|
-
);
|
|
64
|
+
const space = resolveSpace().pipe(Option.getOrUndefined);
|
|
57
65
|
|
|
58
66
|
if (space) {
|
|
59
67
|
yield* Effect.promise(() => space.waitUntilReady());
|
|
@@ -61,21 +69,27 @@ export const spaceLayer = (
|
|
|
61
69
|
return space;
|
|
62
70
|
});
|
|
63
71
|
|
|
72
|
+
// When no space can be resolved we install a stub whose `db` getter throws
|
|
73
|
+
// on access — preserves the existing semantics for commands that *do* need
|
|
74
|
+
// a db — but the release callback must NOT touch `db` or it will throw
|
|
75
|
+
// during teardown (e.g. after a command emits a friendly error and
|
|
76
|
+
// returns early). A shared sentinel object short-circuits the release.
|
|
77
|
+
const NO_DB_STUB = {
|
|
78
|
+
get db(): Database.Database {
|
|
79
|
+
throw new Error('Space not found');
|
|
80
|
+
},
|
|
81
|
+
};
|
|
64
82
|
const db = Layer.scoped(
|
|
65
83
|
Database.Service,
|
|
66
84
|
Effect.acquireRelease(
|
|
67
85
|
Effect.gen(function* () {
|
|
68
86
|
const space = yield* getSpace();
|
|
69
87
|
if (!space) {
|
|
70
|
-
return
|
|
71
|
-
get db(): Database.Database {
|
|
72
|
-
throw new Error('Space not found');
|
|
73
|
-
},
|
|
74
|
-
};
|
|
88
|
+
return NO_DB_STUB;
|
|
75
89
|
}
|
|
76
90
|
return { db: space.db };
|
|
77
91
|
}),
|
|
78
|
-
(
|
|
92
|
+
(holder) => (holder === NO_DB_STUB ? Effect.void : Effect.promise(() => holder.db.flush())),
|
|
79
93
|
),
|
|
80
94
|
);
|
|
81
95
|
|
|
File without changes
|