@mak-98/dirvi-app 0.11.0-beta.5 → 0.11.0-beta.7
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/application/create-app-api.d.ts +1 -1
- package/dist/application/create-app-api.d.ts.map +1 -1
- package/dist/application/create-app-api.js +41 -14
- package/dist/application/init-runtime.d.ts +26 -6
- package/dist/application/init-runtime.d.ts.map +1 -1
- package/dist/application/init-runtime.error.d.ts +53 -0
- package/dist/application/init-runtime.error.d.ts.map +1 -0
- package/dist/application/init-runtime.error.js +77 -0
- package/dist/application/init-runtime.js +123 -89
- package/dist/domain/app-input.d.ts +2 -0
- package/dist/domain/app-input.d.ts.map +1 -0
- package/dist/domain/app-input.js +1 -0
- package/dist/domain/ex-command.d.ts +5 -2
- package/dist/domain/ex-command.d.ts.map +1 -1
- package/dist/domain/initialization.d.ts +14 -11
- package/dist/domain/initialization.d.ts.map +1 -1
- package/dist/domain/node-presentation.d.ts +12 -0
- package/dist/domain/node-presentation.d.ts.map +1 -0
- package/dist/domain/node-presentation.js +1 -0
- package/dist/domain/public-init-api.d.ts +12 -7
- package/dist/domain/public-init-api.d.ts.map +1 -1
- package/dist/domain/shell-api.d.ts +1 -1
- package/dist/domain/shell-api.d.ts.map +1 -1
- package/dist/domain/theme.d.ts +2 -14
- package/dist/domain/theme.d.ts.map +1 -1
- package/dist/domain/theme.js +9 -15
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/ui/App.d.ts +3 -5
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +36 -19
- package/dist/ui/AppSetup.d.ts +1 -1
- package/dist/ui/AppSetup.d.ts.map +1 -1
- package/dist/ui/AppSetup.js +30 -24
- package/dist/ui/Shell.js +6 -6
- package/dist/ui/color-resolver.d.ts +1 -1
- package/dist/ui/color-resolver.d.ts.map +1 -1
- package/dist/ui/hooks/useView.d.ts +2 -2
- package/dist/ui/hooks/useView.d.ts.map +1 -1
- package/dist/ui/hooks/useView.js +3 -3
- package/dist/ui/view.d.ts +7 -3
- package/dist/ui/view.d.ts.map +1 -1
- package/dist/ui/view.js +76 -19
- package/package.json +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SerializableKey, AppApi } from 'dirvi-lib';
|
|
2
2
|
import type { AppApiApplicator, AppDefinition, AppDefinitionApplicator } from 'dirvi-apis';
|
|
3
3
|
/**
|
|
4
4
|
* Turns a plugin-owned app definition into a host-owned runtime AppApi.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-app-api.d.ts","sourceRoot":"","sources":["../../src/application/create-app-api.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"create-app-api.d.ts","sourceRoot":"","sources":["../../src/application/create-app-api.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,MAAM,EACP,MAAM,WAAW,CAAC;AAKnB,OAAO,KAAK,EAAE,gBAAgB,EAAkB,aAAa,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE3G;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,uBAAuB,EAAE,uBAAuB,GAC/C,gBAAgB,CAQlB;AAED,wBAAgB,YAAY,CAC1B,EAAE,SAAS,eAAe,EAC1B,KAAK,EACL,aAAa,EAAE,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CAiE5D"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createFoldMethods, createCursorMethods, createUnorderedNavMethods, createOrderedNavMethods, } from 'dirvi-lib';
|
|
2
|
+
import { createTreeMethods, createStateApi, } from 'dirvi-lib';
|
|
2
3
|
/**
|
|
3
4
|
* Turns a plugin-owned app definition into a host-owned runtime AppApi.
|
|
4
5
|
*
|
|
@@ -12,22 +13,48 @@ export function createAppApiApplicator(appDefinitionApplicator) {
|
|
|
12
13
|
});
|
|
13
14
|
}
|
|
14
15
|
export function createAppApi(appDefinition) {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const stateApi = createStateApi(
|
|
20
|
-
const
|
|
16
|
+
const treeMethods = createTreeMethods();
|
|
17
|
+
const foldTreeMethods = createTreeMethods();
|
|
18
|
+
const foldMethods = createFoldMethods(foldTreeMethods);
|
|
19
|
+
const cursorMethods = createCursorMethods();
|
|
20
|
+
const stateApi = createStateApi(treeMethods, foldMethods, cursorMethods);
|
|
21
|
+
const navMethods = appDefinition.semanticOrder ?
|
|
22
|
+
createOrderedNavMethods(treeMethods, foldMethods, cursorMethods) :
|
|
23
|
+
createUnorderedNavMethods(treeMethods, foldMethods, cursorMethods);
|
|
24
|
+
async function loadChildren(path) {
|
|
25
|
+
const children = await appDefinition.loadChildren(path);
|
|
26
|
+
const seenIds = new Set();
|
|
27
|
+
return children.map((child) => {
|
|
28
|
+
if (seenIds.has(child.id)) {
|
|
29
|
+
throw new Error('Application returned duplicate sibling IDs');
|
|
30
|
+
}
|
|
31
|
+
seenIds.add(child.id);
|
|
32
|
+
switch (child.kind) {
|
|
33
|
+
case 'leaf':
|
|
34
|
+
return treeMethods.createLeaf(child.id, child.value);
|
|
35
|
+
case 'unloaded-branch':
|
|
36
|
+
return treeMethods.createUnloadedBranch(child.id, child.value);
|
|
37
|
+
default:
|
|
38
|
+
// Also protects against invalid output from JavaScript plugins.
|
|
39
|
+
throw new Error('Application returned an invalid child kind');
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
21
43
|
return {
|
|
22
44
|
name: appDefinition.displayName,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
45
|
+
loadChildren: (path) => loadChildren(path),
|
|
46
|
+
loadRoot: async () => {
|
|
47
|
+
const rootData = await appDefinition.loadRoot();
|
|
48
|
+
const children = await loadChildren([]);
|
|
49
|
+
const root = treeMethods.createLoadedBranch(rootData.id, rootData.value, children);
|
|
50
|
+
if (root === undefined) {
|
|
51
|
+
throw new Error('Cannot create root: duplicate child IDs');
|
|
52
|
+
}
|
|
53
|
+
return root;
|
|
54
|
+
},
|
|
55
|
+
presentNode: (id, isBranch) => appDefinition.presentNode(id, isBranch),
|
|
26
56
|
subscribeToResync: appDefinition.subscribeToResync,
|
|
27
|
-
treeNodeApi,
|
|
28
|
-
foldsApi,
|
|
29
|
-
cursorApi,
|
|
30
57
|
stateApi,
|
|
31
|
-
|
|
58
|
+
navMethods,
|
|
32
59
|
};
|
|
33
60
|
}
|
|
@@ -1,20 +1,40 @@
|
|
|
1
1
|
import type { Initialization } from '../domain/initialization.js';
|
|
2
2
|
import type { PublicInitApi } from '../domain/public-init-api.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* A session for collecting and resolving initialization-script registrations.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* evaluation completes
|
|
6
|
+
* Expose `api` while evaluating `init.mjs`, then call `finish()` after module
|
|
7
|
+
* evaluation completes. Finishing validates the collected state and returns an
|
|
8
|
+
* immutable-by-contract snapshot of the resolved initialization.
|
|
8
9
|
*/
|
|
9
10
|
export type InitRuntime = {
|
|
11
|
+
/** The temporary API made available to `init.mjs` during evaluation. */
|
|
10
12
|
readonly api: PublicInitApi;
|
|
13
|
+
/**
|
|
14
|
+
* Validates and seals this session, returning its resolved startup state.
|
|
15
|
+
*
|
|
16
|
+
* The session is sealed only after successful validation. Once sealed,
|
|
17
|
+
* further API calls and calls to `finish()` throw
|
|
18
|
+
* `InitializationAlreadyFinishedError`.
|
|
19
|
+
*/
|
|
11
20
|
readonly finish: () => Initialization;
|
|
12
21
|
};
|
|
13
22
|
/**
|
|
14
|
-
* Creates
|
|
23
|
+
* Creates an isolated session for evaluating one `init.mjs` script.
|
|
15
24
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
25
|
+
* Option patches are merged with the current config and the complete result is
|
|
26
|
+
* validated. App and command registrations are checked as they are made.
|
|
27
|
+
*
|
|
28
|
+
* The runtime keeps its working state inside the closure and exposes it only
|
|
29
|
+
* through `api` and `finish`. finish() returns new maps, so later changes to
|
|
30
|
+
* the runtime’s maps cannot alter the returned maps.
|
|
31
|
+
*
|
|
32
|
+
* If no startup app is selected explicitly, the most recently registered app
|
|
33
|
+
* is chosen. At least one app must be registered before the session can finish.
|
|
34
|
+
*
|
|
35
|
+
* @returns A fresh initialization API and its corresponding `finish` function.
|
|
36
|
+
* @throws {InitRuntimeError} If a registration is invalid, validation fails,
|
|
37
|
+
* no app was registered, or the session has already been finished.
|
|
18
38
|
*/
|
|
19
39
|
export declare function createInitRuntime(): InitRuntime;
|
|
20
40
|
//# sourceMappingURL=init-runtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init-runtime.d.ts","sourceRoot":"","sources":["../../src/application/init-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"init-runtime.d.ts","sourceRoot":"","sources":["../../src/application/init-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAWlE;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,wEAAwE;IACxE,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAE5B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,cAAc,CAAC;CACvC,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,IAAI,WAAW,CAuI/C"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Stable categories for errors raised while collecting startup initialization. */
|
|
2
|
+
export type InitRuntimeErrorCode = 'ALREADY_FINISHED' | 'INVALID_VALUE' | 'DUPLICATE_APP' | 'DUPLICATE_EX_COMMAND' | 'INVALID_EX_COMMAND_HANDLER' | 'INVALID_CONFIG' | 'INVALID_APP_PACKAGE_REFERENCE' | 'NO_APPS_REGISTERED' | 'INITIAL_APP_NOT_REGISTERED' | 'INTERNAL_INVARIANT';
|
|
3
|
+
/**
|
|
4
|
+
* Base class for errors raised by the initialization runtime.
|
|
5
|
+
*
|
|
6
|
+
* Consumers can use `code` for reliable error handling without depending on
|
|
7
|
+
* message text. `cause` is set when an underlying validation error is wrapped.
|
|
8
|
+
*/
|
|
9
|
+
export declare class InitRuntimeError extends Error {
|
|
10
|
+
readonly code: InitRuntimeErrorCode;
|
|
11
|
+
constructor(code: InitRuntimeErrorCode, message: string, options?: ErrorOptions);
|
|
12
|
+
}
|
|
13
|
+
/** An initialization API was used after its runtime had been sealed. */
|
|
14
|
+
export declare class InitializationAlreadyFinishedError extends InitRuntimeError {
|
|
15
|
+
constructor();
|
|
16
|
+
}
|
|
17
|
+
/** A registration API received a value of the wrong type or an empty string. */
|
|
18
|
+
export declare class InvalidInitializationValueError extends InitRuntimeError {
|
|
19
|
+
constructor(label: string, expected: string);
|
|
20
|
+
}
|
|
21
|
+
/** An app ID was registered more than once. */
|
|
22
|
+
export declare class DuplicateAppRegistrationError extends InitRuntimeError {
|
|
23
|
+
constructor(id: string);
|
|
24
|
+
}
|
|
25
|
+
/** An Ex command name was registered more than once. */
|
|
26
|
+
export declare class DuplicateExCommandRegistrationError extends InitRuntimeError {
|
|
27
|
+
constructor(name: string);
|
|
28
|
+
}
|
|
29
|
+
/** An Ex command registration supplied a value that is not a handler function. */
|
|
30
|
+
export declare class InvalidExCommandHandlerError extends InitRuntimeError {
|
|
31
|
+
constructor();
|
|
32
|
+
}
|
|
33
|
+
/** The initialization script supplied options that do not resolve to valid config. */
|
|
34
|
+
export declare class InvalidInitializationConfigError extends InitRuntimeError {
|
|
35
|
+
constructor(cause: unknown);
|
|
36
|
+
}
|
|
37
|
+
/** An app registration supplied a package reference that failed validation. */
|
|
38
|
+
export declare class InvalidAppPackageReferenceError extends InitRuntimeError {
|
|
39
|
+
constructor(cause: unknown);
|
|
40
|
+
}
|
|
41
|
+
/** Initialization completed without registering any apps. */
|
|
42
|
+
export declare class NoAppsRegisteredError extends InitRuntimeError {
|
|
43
|
+
constructor();
|
|
44
|
+
}
|
|
45
|
+
/** The explicitly selected startup app was not registered. */
|
|
46
|
+
export declare class InitialAppNotRegisteredError extends InitRuntimeError {
|
|
47
|
+
constructor(id: string);
|
|
48
|
+
}
|
|
49
|
+
/** An internal invariant required by the initialization runtime was violated. */
|
|
50
|
+
export declare class InitRuntimeInvariantError extends InitRuntimeError {
|
|
51
|
+
constructor(message: string);
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=init-runtime.error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-runtime.error.d.ts","sourceRoot":"","sources":["../../src/application/init-runtime.error.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,MAAM,MAAM,oBAAoB,GAC5B,kBAAkB,GAClB,eAAe,GACf,eAAe,GACf,sBAAsB,GACtB,4BAA4B,GAC5B,gBAAgB,GAChB,+BAA+B,GAC/B,oBAAoB,GACpB,4BAA4B,GAC5B,oBAAoB,CAAC;AAEzB;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IAEvC,QAAQ,CAAC,IAAI,EAAE,oBAAoB;gBAA1B,IAAI,EAAE,oBAAoB,EACnC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY;CAKzB;AAED,wEAAwE;AACxE,qBAAa,kCAAmC,SAAQ,gBAAgB;;CAOvE;AAED,gFAAgF;AAChF,qBAAa,+BAAgC,SAAQ,gBAAgB;gBACvD,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAG5C;AAED,+CAA+C;AAC/C,qBAAa,6BAA8B,SAAQ,gBAAgB;gBACrD,EAAE,EAAE,MAAM;CAMvB;AAED,wDAAwD;AACxD,qBAAa,mCAAoC,SAAQ,gBAAgB;gBAC3D,IAAI,EAAE,MAAM;CAMzB;AAED,kFAAkF;AAClF,qBAAa,4BAA6B,SAAQ,gBAAgB;;CAOjE;AAED,sFAAsF;AACtF,qBAAa,gCAAiC,SAAQ,gBAAgB;gBACxD,KAAK,EAAE,OAAO;CAO3B;AAED,+EAA+E;AAC/E,qBAAa,+BAAgC,SAAQ,gBAAgB;gBACvD,KAAK,EAAE,OAAO;CAO3B;AAED,6DAA6D;AAC7D,qBAAa,qBAAsB,SAAQ,gBAAgB;;CAO1D;AAED,8DAA8D;AAC9D,qBAAa,4BAA6B,SAAQ,gBAAgB;gBACpD,EAAE,EAAE,MAAM;CAMvB;AAED,iFAAiF;AACjF,qBAAa,yBAA0B,SAAQ,gBAAgB;gBACjD,OAAO,EAAE,MAAM;CAG5B"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for errors raised by the initialization runtime.
|
|
3
|
+
*
|
|
4
|
+
* Consumers can use `code` for reliable error handling without depending on
|
|
5
|
+
* message text. `cause` is set when an underlying validation error is wrapped.
|
|
6
|
+
*/
|
|
7
|
+
export class InitRuntimeError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
constructor(code, message, options) {
|
|
10
|
+
super(message, options);
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.name = new.target.name;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** An initialization API was used after its runtime had been sealed. */
|
|
16
|
+
export class InitializationAlreadyFinishedError extends InitRuntimeError {
|
|
17
|
+
constructor() {
|
|
18
|
+
super('ALREADY_FINISHED', 'Initialization has finished. Make all registrations while init.mjs is evaluated.');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** A registration API received a value of the wrong type or an empty string. */
|
|
22
|
+
export class InvalidInitializationValueError extends InitRuntimeError {
|
|
23
|
+
constructor(label, expected) {
|
|
24
|
+
super('INVALID_VALUE', `${label} ${expected}.`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** An app ID was registered more than once. */
|
|
28
|
+
export class DuplicateAppRegistrationError extends InitRuntimeError {
|
|
29
|
+
constructor(id) {
|
|
30
|
+
super('DUPLICATE_APP', `An app with ID "${id}" was registered multiple times.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** An Ex command name was registered more than once. */
|
|
34
|
+
export class DuplicateExCommandRegistrationError extends InitRuntimeError {
|
|
35
|
+
constructor(name) {
|
|
36
|
+
super('DUPLICATE_EX_COMMAND', `An Ex command named "${name}" was registered multiple times.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** An Ex command registration supplied a value that is not a handler function. */
|
|
40
|
+
export class InvalidExCommandHandlerError extends InitRuntimeError {
|
|
41
|
+
constructor() {
|
|
42
|
+
super('INVALID_EX_COMMAND_HANDLER', 'Ex command handler must be a function.');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** The initialization script supplied options that do not resolve to valid config. */
|
|
46
|
+
export class InvalidInitializationConfigError extends InitRuntimeError {
|
|
47
|
+
constructor(cause) {
|
|
48
|
+
super('INVALID_CONFIG', `Initialization produced invalid configuration: ${getErrorMessage(cause)}`, { cause });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** An app registration supplied a package reference that failed validation. */
|
|
52
|
+
export class InvalidAppPackageReferenceError extends InitRuntimeError {
|
|
53
|
+
constructor(cause) {
|
|
54
|
+
super('INVALID_APP_PACKAGE_REFERENCE', `Invalid app package reference: ${getErrorMessage(cause)}`, { cause });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Initialization completed without registering any apps. */
|
|
58
|
+
export class NoAppsRegisteredError extends InitRuntimeError {
|
|
59
|
+
constructor() {
|
|
60
|
+
super('NO_APPS_REGISTERED', 'Initialization did not register any apps. Call dirvi.app.register(id, appReference) at least once in init.mjs.');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** The explicitly selected startup app was not registered. */
|
|
64
|
+
export class InitialAppNotRegisteredError extends InitRuntimeError {
|
|
65
|
+
constructor(id) {
|
|
66
|
+
super('INITIAL_APP_NOT_REGISTERED', `Startup app "${id}" has not been registered.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** An internal invariant required by the initialization runtime was violated. */
|
|
70
|
+
export class InitRuntimeInvariantError extends InitRuntimeError {
|
|
71
|
+
constructor(message) {
|
|
72
|
+
super('INTERNAL_INVARIANT', message);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function getErrorMessage(error) {
|
|
76
|
+
return error instanceof Error ? error.message : String(error);
|
|
77
|
+
}
|
|
@@ -1,117 +1,151 @@
|
|
|
1
1
|
import { ConfigSchema, defaultConfig, merge } from '../domain/config.js';
|
|
2
|
+
import { AppPackageReference } from '../domain/app-pack-ref.js';
|
|
3
|
+
import { DuplicateAppRegistrationError, DuplicateExCommandRegistrationError, InitializationAlreadyFinishedError, InitRuntimeInvariantError, InvalidAppPackageReferenceError, InvalidExCommandHandlerError, InvalidInitializationConfigError, InvalidInitializationValueError, } from './init-runtime.error.js';
|
|
2
4
|
/**
|
|
3
|
-
* Creates
|
|
5
|
+
* Creates an isolated session for evaluating one `init.mjs` script.
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
+
* Option patches are merged with the current config and the complete result is
|
|
8
|
+
* validated. App and command registrations are checked as they are made.
|
|
9
|
+
*
|
|
10
|
+
* The runtime keeps its working state inside the closure and exposes it only
|
|
11
|
+
* through `api` and `finish`. finish() returns new maps, so later changes to
|
|
12
|
+
* the runtime’s maps cannot alter the returned maps.
|
|
13
|
+
*
|
|
14
|
+
* If no startup app is selected explicitly, the most recently registered app
|
|
15
|
+
* is chosen. At least one app must be registered before the session can finish.
|
|
16
|
+
*
|
|
17
|
+
* @returns A fresh initialization API and its corresponding `finish` function.
|
|
18
|
+
* @throws {InitRuntimeError} If a registration is invalid, validation fails,
|
|
19
|
+
* no app was registered, or the session has already been finished.
|
|
7
20
|
*/
|
|
8
21
|
export function createInitRuntime() {
|
|
9
22
|
let config = defaultConfig;
|
|
10
23
|
let initialAppId;
|
|
11
|
-
const
|
|
24
|
+
const registeredApps = new Map();
|
|
12
25
|
const exCommands = new Map();
|
|
13
26
|
let finished = false;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
// === Error Helpers =========================================================
|
|
28
|
+
/**
|
|
29
|
+
* Rejects API use after a successful call to `finish()`.
|
|
30
|
+
*
|
|
31
|
+
* Failed calls to `finish()` do not seal the runtime, allowing the caller to
|
|
32
|
+
* correct registrations and try again.
|
|
33
|
+
*/
|
|
34
|
+
const ensureUnfinished = () => {
|
|
35
|
+
if (finished) {
|
|
36
|
+
throw new InitializationAlreadyFinishedError();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
/** Validates values. */
|
|
40
|
+
const validateNonEmptyString = (value, label) => {
|
|
41
|
+
if (typeof value !== 'string') {
|
|
42
|
+
throw new InvalidInitializationValueError(label, 'must be a string');
|
|
43
|
+
}
|
|
44
|
+
if (value.trim().length === 0) {
|
|
45
|
+
throw new InvalidInitializationValueError(label, 'must not be empty');
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
// === Public Initialization API =============================================
|
|
49
|
+
const api = {
|
|
50
|
+
options: {
|
|
51
|
+
set: (patch) => {
|
|
52
|
+
ensureUnfinished();
|
|
53
|
+
try {
|
|
54
|
+
// Validate the merged result so patches cannot break config
|
|
55
|
+
// invariants outside the fields they directly update.
|
|
25
56
|
config = ConfigSchema.parse(merge(config, patch));
|
|
26
|
-
}
|
|
57
|
+
}
|
|
58
|
+
catch (cause) {
|
|
59
|
+
throw new InvalidInitializationConfigError(cause);
|
|
60
|
+
}
|
|
27
61
|
},
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
initialAppId: (appId) => {
|
|
43
|
-
if (finished) {
|
|
44
|
-
throw new Error('Initialization has finished. Select an initial app while ' +
|
|
45
|
-
'init.mjs is being evaluated.');
|
|
46
|
-
}
|
|
47
|
-
if (appId.trim().length === 0) {
|
|
48
|
-
throw new Error('App ID must not be empty.');
|
|
49
|
-
}
|
|
50
|
-
initialAppId = appId;
|
|
51
|
-
},
|
|
62
|
+
},
|
|
63
|
+
app: {
|
|
64
|
+
register: (id, appReference) => {
|
|
65
|
+
ensureUnfinished();
|
|
66
|
+
validateNonEmptyString(id, 'App ID');
|
|
67
|
+
if (registeredApps.has(id)) {
|
|
68
|
+
throw new DuplicateAppRegistrationError(id);
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
registeredApps.set(id, AppPackageReference.parse(appReference));
|
|
72
|
+
}
|
|
73
|
+
catch (cause) {
|
|
74
|
+
throw new InvalidAppPackageReferenceError(cause);
|
|
75
|
+
}
|
|
52
76
|
},
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
'init.mjs is being evaluated.');
|
|
58
|
-
}
|
|
59
|
-
if (name.trim().length === 0) {
|
|
60
|
-
throw new Error('Ex command name must not be empty.');
|
|
61
|
-
}
|
|
62
|
-
if (exCommands.has(name)) {
|
|
63
|
-
throw new Error(`An Ex command named "${name}" was registered multiple times.`);
|
|
64
|
-
}
|
|
65
|
-
exCommands.set(name, {
|
|
66
|
-
name,
|
|
67
|
-
execute,
|
|
68
|
-
});
|
|
69
|
-
},
|
|
77
|
+
initial: (appId) => {
|
|
78
|
+
ensureUnfinished();
|
|
79
|
+
validateNonEmptyString(appId, 'App ID');
|
|
80
|
+
initialAppId = appId;
|
|
70
81
|
},
|
|
71
82
|
},
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
'dirvi.apps.register(id, appReference) at least once in init.mjs.');
|
|
79
|
-
}
|
|
80
|
-
if (initialAppId !== undefined) {
|
|
81
|
-
if (!apps.has(initialAppId)) {
|
|
82
|
-
throw new Error(`Startup app "${initialAppId}" has not been registered.`);
|
|
83
|
+
ex: {
|
|
84
|
+
register: (name, execute) => {
|
|
85
|
+
ensureUnfinished();
|
|
86
|
+
validateNonEmptyString(name, 'Ex command name');
|
|
87
|
+
if (typeof execute !== 'function') {
|
|
88
|
+
throw new InvalidExCommandHandlerError();
|
|
83
89
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
exCommands: [...exCommands.values()],
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
const lastRegisteredAppRef = getLastRegisteredAppId(apps);
|
|
93
|
-
finished = true;
|
|
94
|
-
return {
|
|
95
|
-
config,
|
|
96
|
-
initialAppId: lastRegisteredAppRef,
|
|
97
|
-
registeredApps: new Map(apps),
|
|
98
|
-
exCommands: [...exCommands.values()],
|
|
99
|
-
};
|
|
90
|
+
if (exCommands.has(name)) {
|
|
91
|
+
throw new DuplicateExCommandRegistrationError(name);
|
|
92
|
+
}
|
|
93
|
+
exCommands.set(name, { name, execute });
|
|
94
|
+
},
|
|
100
95
|
},
|
|
101
96
|
};
|
|
97
|
+
/**
|
|
98
|
+
* === FINISH ================================================================
|
|
99
|
+
* Resolves the startup app, seals the session, and returns its snapshot.
|
|
100
|
+
*
|
|
101
|
+
* Validation happens before sealing, so a failed finish can be retried after
|
|
102
|
+
* the script corrects its registrations.
|
|
103
|
+
* ===========================================================================
|
|
104
|
+
*/
|
|
105
|
+
const finish = () => {
|
|
106
|
+
ensureUnfinished();
|
|
107
|
+
if (registeredApps.size === 0) {
|
|
108
|
+
throw new Error('Initialization did not register any apps. Call ' +
|
|
109
|
+
'dirvi.apps.register(id, appReference) at least once in init.mjs.');
|
|
110
|
+
}
|
|
111
|
+
let initial;
|
|
112
|
+
if (initialAppId !== undefined) {
|
|
113
|
+
if (!registeredApps.has(initialAppId)) {
|
|
114
|
+
throw new Error(`Startup app "${initialAppId}" has not been registered.`);
|
|
115
|
+
}
|
|
116
|
+
initial = initialAppId;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
initial = getLastRegisteredAppId(registeredApps);
|
|
120
|
+
}
|
|
121
|
+
finished = true;
|
|
122
|
+
return {
|
|
123
|
+
config,
|
|
124
|
+
app: {
|
|
125
|
+
initial,
|
|
126
|
+
registered: new Map(registeredApps),
|
|
127
|
+
},
|
|
128
|
+
exCommands: new Map(exCommands),
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
api,
|
|
133
|
+
finish,
|
|
134
|
+
};
|
|
102
135
|
}
|
|
103
136
|
/**
|
|
104
|
-
* Returns the
|
|
137
|
+
* Returns the app registered last, using `Map` insertion order.
|
|
138
|
+
*
|
|
139
|
+
* This is only the fallback when `init.mjs` does not explicitly select an
|
|
140
|
+
* initial app. Callers must check that the map is non-empty first.
|
|
105
141
|
*
|
|
106
|
-
*
|
|
107
|
-
* fallback only; init.mjs should set initialAppId explicitly when startup
|
|
108
|
-
* selection is semantically important.
|
|
142
|
+
* @throws {InitRuntimeInvariantError} If called with an empty map.
|
|
109
143
|
*/
|
|
110
144
|
function getLastRegisteredAppId(apps) {
|
|
111
145
|
const appIds = [...apps.keys()];
|
|
112
146
|
const lastAppId = appIds.at(-1);
|
|
113
147
|
if (lastAppId === undefined) {
|
|
114
|
-
throw new
|
|
148
|
+
throw new InitRuntimeInvariantError('Expected at least one registered app when selecting the startup app.');
|
|
115
149
|
}
|
|
116
150
|
return lastAppId;
|
|
117
151
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-input.d.ts","sourceRoot":"","sources":["../../src/domain/app-input.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { ShellApi } from './shell-api.js';
|
|
2
|
+
export type DirviApi = {
|
|
3
|
+
shell: ShellApi;
|
|
4
|
+
};
|
|
2
5
|
/**
|
|
3
6
|
* A command registered by init.mjs and invoked through Ex mode.
|
|
4
7
|
*
|
|
5
|
-
* The command receives the live
|
|
8
|
+
* The command receives the live Api only when it is executed. This keeps
|
|
6
9
|
* initialization separate from the running application lifecycle.
|
|
7
10
|
*/
|
|
8
11
|
export type ExCommand = {
|
|
9
12
|
readonly name: string;
|
|
10
|
-
readonly execute: (
|
|
13
|
+
readonly execute: (api: DirviApi) => void | Promise<void>;
|
|
11
14
|
};
|
|
12
15
|
//# sourceMappingURL=ex-command.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ex-command.d.ts","sourceRoot":"","sources":["../../src/domain/ex-command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ex-command.d.ts","sourceRoot":"","sources":["../../src/domain/ex-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,QAAQ,CAAC;CACjB,CAAA;AAGD;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D,CAAC"}
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import { Config } from './config.js';
|
|
2
2
|
import { ExCommand } from './ex-command.js';
|
|
3
3
|
import { AppPackageReference } from './app-pack-ref.js';
|
|
4
|
-
export type AppInput = string | undefined;
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
5
|
+
* Resolved startup state produced by evaluating `init.mjs`.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* initialAppId is always the ID of one app in apps: either the app explicitly
|
|
13
|
-
* selected by init.mjs or the most recently registered app.
|
|
7
|
+
* The initialization script runs before the terminal UI starts. It uses the
|
|
8
|
+
* temporary `dirvi` configuration facade to set shared options, register app
|
|
9
|
+
* packages, choose the initial app, and register Ex commands.
|
|
14
10
|
*/
|
|
15
11
|
export type Initialization = {
|
|
12
|
+
/** Dirvi-wide configuration resolved from the initialization script. */
|
|
16
13
|
readonly config: Readonly<Config>;
|
|
17
|
-
|
|
18
|
-
readonly
|
|
19
|
-
|
|
14
|
+
/** Registered apps and the app selected for startup. */
|
|
15
|
+
readonly app: {
|
|
16
|
+
/** ID of the app to open initially; must be present in `registered`. */
|
|
17
|
+
initial: string;
|
|
18
|
+
/** App package references keyed by app ID. */
|
|
19
|
+
registered: ReadonlyMap<string, AppPackageReference>;
|
|
20
|
+
};
|
|
21
|
+
/** Ex commands registered by the initialization script, keyed by name. */
|
|
22
|
+
readonly exCommands: ReadonlyMap<string, ExCommand>;
|
|
20
23
|
};
|
|
21
24
|
//# sourceMappingURL=initialization.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initialization.d.ts","sourceRoot":"","sources":["../../src/domain/initialization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD
|
|
1
|
+
{"version":3,"file":"initialization.d.ts","sourceRoot":"","sources":["../../src/domain/initialization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAElC,wDAAwD;IACxD,QAAQ,CAAC,GAAG,EAAE;QACZ,wEAAwE;QACxE,OAAO,EAAE,MAAM,CAAC;QAEhB,8CAA8C;QAC9C,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;KACtD,CAAC;IAEF,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACrD,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A semantic highlight name, not a concrete terminal color.
|
|
3
|
+
*
|
|
4
|
+
* Dot-separated names allow fallback to broader categories:
|
|
5
|
+
* "filesystem.directory" can fall back to "filesystem".
|
|
6
|
+
*/
|
|
7
|
+
export type HighlightToken = string;
|
|
8
|
+
export type NodePresentation = Readonly<{
|
|
9
|
+
content: string;
|
|
10
|
+
highlight: HighlightToken;
|
|
11
|
+
}>;
|
|
12
|
+
//# sourceMappingURL=node-presentation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-presentation.d.ts","sourceRoot":"","sources":["../../src/domain/node-presentation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,cAAc,CAAC;CAC3B,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|