@dxos/cli-util 0.8.4-main.9735255 → 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 +106 -70
- 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 +2 -2
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- 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/testing/test-layer.d.ts.map +1 -1
- package/dist/types/src/util/form-builder.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/runtime.d.ts +1 -1
- package/dist/types/src/util/runtime.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 +7 -7
- package/dist/types/src/util/space.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -15
- package/src/testing/test-console.ts +1 -2
- package/src/testing/test-layer.ts +0 -1
- package/src/util/form-builder.ts +1 -1
- package/src/util/platform.ts +1 -2
- package/src/util/printer.ts +1 -1
- package/src/util/runtime.ts +1 -1
- package/src/util/space-format.ts +86 -12
- package/src/util/space.ts +43 -29
- /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",
|
|
@@ -30,26 +30,28 @@
|
|
|
30
30
|
"src"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@effect/cli": "0.
|
|
33
|
+
"@effect/cli": "0.73.2",
|
|
34
34
|
"@effect/printer": "0.47.0",
|
|
35
35
|
"@effect/printer-ansi": "0.47.0",
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/debug": "0.8.4-main.
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/effect": "0.8.4-main.
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
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"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
47
|
-
"effect": "3.
|
|
48
|
-
"typescript": "^
|
|
49
|
-
"vitest": "
|
|
49
|
+
"effect": "3.20.0",
|
|
50
|
+
"typescript": "^6.0.3",
|
|
51
|
+
"vitest": "4.1.5"
|
|
50
52
|
},
|
|
51
53
|
"peerDependencies": {
|
|
52
|
-
"effect": "3.
|
|
54
|
+
"effect": "3.20.0"
|
|
53
55
|
},
|
|
54
56
|
"publishConfig": {
|
|
55
57
|
"access": "public"
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { inspect } from 'node:util';
|
|
6
|
-
|
|
7
5
|
import * as Console from 'effect/Console';
|
|
8
6
|
import * as Context from 'effect/Context';
|
|
9
7
|
import * as Effect from 'effect/Effect';
|
|
10
8
|
import * as Layer from 'effect/Layer';
|
|
9
|
+
import { inspect } from 'node:util';
|
|
11
10
|
|
|
12
11
|
function logToString(...args: any[]): string {
|
|
13
12
|
return args
|
package/src/util/form-builder.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import * as Doc from '@effect/printer/Doc';
|
|
6
5
|
import * as Ansi from '@effect/printer-ansi/Ansi';
|
|
6
|
+
import * as Doc from '@effect/printer/Doc';
|
|
7
7
|
import * as Option from 'effect/Option';
|
|
8
8
|
import * as Pipeable from 'effect/Pipeable';
|
|
9
9
|
|
package/src/util/platform.ts
CHANGED
package/src/util/printer.ts
CHANGED
package/src/util/runtime.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { ClientService } from '@dxos/client';
|
|
|
8
8
|
import { type Type } from '@dxos/echo';
|
|
9
9
|
|
|
10
10
|
/** @deprecated Migrate to providing types via plugin capabilities. */
|
|
11
|
-
export const withTypes: (...types: Type.
|
|
11
|
+
export const withTypes: (...types: Type.AnyEntity[]) => Effect.Effect<void, never, ClientService> = (...types) =>
|
|
12
12
|
Effect.gen(function* () {
|
|
13
13
|
const client = yield* ClientService;
|
|
14
14
|
yield* Effect.promise(() => client.addTypes(types));
|
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,9 +5,9 @@
|
|
|
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
|
|
|
10
|
+
import { getPersonalSpace } from '@dxos/app-toolkit';
|
|
11
11
|
import { ClientService } from '@dxos/client';
|
|
12
12
|
import { type Space } from '@dxos/client/echo';
|
|
13
13
|
import { Database, type Key } from '@dxos/echo';
|
|
@@ -26,34 +26,42 @@ export const getSpace = (spaceId: Key.SpaceId): Effect.Effect<Space, SpaceNotFou
|
|
|
26
26
|
export const spaceIdWithDefault = (spaceId: Option.Option<Key.SpaceId>) =>
|
|
27
27
|
Effect.gen(function* () {
|
|
28
28
|
const client = yield* ClientService;
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
return Option.getOrElse(spaceId, () => {
|
|
30
|
+
const personal = getPersonalSpace(client);
|
|
31
|
+
if (!personal) {
|
|
32
|
+
throw new Error('No space ID provided and no personal space found.');
|
|
33
|
+
}
|
|
34
|
+
return personal.id;
|
|
35
|
+
});
|
|
31
36
|
});
|
|
32
37
|
|
|
33
38
|
// TODO(wittjosiah): Factor out.
|
|
34
39
|
export const spaceLayer = (
|
|
35
40
|
spaceId$: Option.Option<Key.SpaceId>,
|
|
36
|
-
|
|
41
|
+
fallbackToPersonalSpace = false,
|
|
37
42
|
): Layer.Layer<Database.Service | QueueService, never, ClientService> => {
|
|
38
43
|
const getSpace = Effect.fn(function* () {
|
|
39
44
|
const client = yield* ClientService;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
45
|
+
|
|
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
|
+
};
|
|
63
|
+
|
|
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
|
|
|
@@ -129,8 +143,8 @@ export const waitForSync = Effect.fn(function* (space: Space) {
|
|
|
129
143
|
});
|
|
130
144
|
|
|
131
145
|
export const flushAndSync = Effect.fn(function* (opts?: Database.FlushOptions) {
|
|
132
|
-
yield* Database.
|
|
133
|
-
const spaceId = yield* Database.
|
|
146
|
+
yield* Database.flush(opts);
|
|
147
|
+
const spaceId = yield* Database.spaceId;
|
|
134
148
|
const space = yield* getSpace(spaceId);
|
|
135
149
|
yield* waitForSync(space);
|
|
136
150
|
});
|
|
File without changes
|