@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.
Files changed (44) hide show
  1. package/dist/application/create-app-api.d.ts +1 -1
  2. package/dist/application/create-app-api.d.ts.map +1 -1
  3. package/dist/application/create-app-api.js +41 -14
  4. package/dist/application/init-runtime.d.ts +26 -6
  5. package/dist/application/init-runtime.d.ts.map +1 -1
  6. package/dist/application/init-runtime.error.d.ts +53 -0
  7. package/dist/application/init-runtime.error.d.ts.map +1 -0
  8. package/dist/application/init-runtime.error.js +77 -0
  9. package/dist/application/init-runtime.js +123 -89
  10. package/dist/domain/app-input.d.ts +2 -0
  11. package/dist/domain/app-input.d.ts.map +1 -0
  12. package/dist/domain/app-input.js +1 -0
  13. package/dist/domain/ex-command.d.ts +5 -2
  14. package/dist/domain/ex-command.d.ts.map +1 -1
  15. package/dist/domain/initialization.d.ts +14 -11
  16. package/dist/domain/initialization.d.ts.map +1 -1
  17. package/dist/domain/node-presentation.d.ts +12 -0
  18. package/dist/domain/node-presentation.d.ts.map +1 -0
  19. package/dist/domain/node-presentation.js +1 -0
  20. package/dist/domain/public-init-api.d.ts +12 -7
  21. package/dist/domain/public-init-api.d.ts.map +1 -1
  22. package/dist/domain/shell-api.d.ts +1 -1
  23. package/dist/domain/shell-api.d.ts.map +1 -1
  24. package/dist/domain/theme.d.ts +2 -14
  25. package/dist/domain/theme.d.ts.map +1 -1
  26. package/dist/domain/theme.js +9 -15
  27. package/dist/index.d.ts +0 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/ui/App.d.ts +3 -5
  30. package/dist/ui/App.d.ts.map +1 -1
  31. package/dist/ui/App.js +36 -19
  32. package/dist/ui/AppSetup.d.ts +1 -1
  33. package/dist/ui/AppSetup.d.ts.map +1 -1
  34. package/dist/ui/AppSetup.js +30 -24
  35. package/dist/ui/Shell.js +6 -6
  36. package/dist/ui/color-resolver.d.ts +1 -1
  37. package/dist/ui/color-resolver.d.ts.map +1 -1
  38. package/dist/ui/hooks/useView.d.ts +2 -2
  39. package/dist/ui/hooks/useView.d.ts.map +1 -1
  40. package/dist/ui/hooks/useView.js +3 -3
  41. package/dist/ui/view.d.ts +7 -3
  42. package/dist/ui/view.d.ts.map +1 -1
  43. package/dist/ui/view.js +76 -19
  44. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- import { type SerializableKey, type AppApi } from 'dirvi-lib';
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,KAAK,eAAe,EACpB,KAAK,MAAM,EAMZ,MAAM,WAAW,CAAC;AACnB,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,CAyB5D"}
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 { createCursorApi, createFoldsApi, createNavNodeApi, createStateApi, createTreeNodeApi, } from 'dirvi-lib';
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 treeNodeApi = createTreeNodeApi();
16
- const foldTreeNodeApi = createTreeNodeApi();
17
- const foldsApi = createFoldsApi(foldTreeNodeApi);
18
- const cursorApi = createCursorApi();
19
- const stateApi = createStateApi(treeNodeApi, cursorApi);
20
- const navNodeApi = createNavNodeApi(treeNodeApi, foldsApi, cursorApi, appDefinition.createNavNodeApiOptions);
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
- rootId: appDefinition.rootId,
24
- createRoot: () => appDefinition.createRoot(treeNodeApi),
25
- loadBranches: (path) => appDefinition.loadChildren(treeNodeApi, path),
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
- navNodeApi,
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
- * Collects startup configuration registrations while init.mjs is evaluated.
4
+ * A session for collecting and resolving initialization-script registrations.
5
5
  *
6
- * The application exposes `api` to the script, then calls `finish()` once
7
- * evaluation completes to seal registration and obtain the validated Config.
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 a fresh startup configuration session.
23
+ * Creates an isolated session for evaluating one `init.mjs` script.
15
24
  *
16
- * Each runtime owns its own mutable configuration while registration is open.
17
- * The mutable state is not exposed directly to the config script.
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;AAIlE;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,cAAc,CAAC;CACvC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,WAAW,CAiI/C"}
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 a fresh startup configuration session.
5
+ * Creates an isolated session for evaluating one `init.mjs` script.
4
6
  *
5
- * Each runtime owns its own mutable configuration while registration is open.
6
- * The mutable state is not exposed directly to the config script.
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 apps = new Map();
24
+ const registeredApps = new Map();
12
25
  const exCommands = new Map();
13
26
  let finished = false;
14
- return {
15
- api: {
16
- options: {
17
- set: (patch) => {
18
- // init.mjs is a startup script. It must not modify startup options
19
- // after the host has finished evaluating and consuming it.
20
- if (finished) {
21
- throw new Error('Initialization has finished. Configure dirvi while init.mjs is being evaluated.');
22
- }
23
- // Validate the complete result, not merely the patch. This preserves
24
- // domain invariants even when a patch changes nested configuration.
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
- apps: {
29
- register: (id, appReference) => {
30
- if (finished) {
31
- throw new Error('Initialization has finished. Register apps while ' +
32
- 'init.mjs is being evaluated.');
33
- }
34
- if (id.trim().length === 0) {
35
- throw new Error('App ID must not be empty.');
36
- }
37
- if (apps.has(id)) {
38
- throw new Error(`An app with ID "${id}" was registered multiple times.`);
39
- }
40
- apps.set(id, appReference);
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
- ex: {
54
- register: (name, execute) => {
55
- if (finished) {
56
- throw new Error('Initialization has finished. Register Ex commands while ' +
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
- finish: () => {
73
- if (finished) {
74
- throw new Error('Initialization has already finished.');
75
- }
76
- if (apps.size === 0) {
77
- throw new Error('Initialization did not register any apps. Call ' +
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
- finished = true;
85
- return {
86
- config,
87
- initialAppId: initialAppId,
88
- registeredApps: new Map(apps),
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 most recently registered app ID.
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
- * Map iteration follows registration insertion order. This is a convenience
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 Error('Internal error: Check some app is registered before searching ids.');
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,2 @@
1
+ export type AppInput = string | undefined;
2
+ //# sourceMappingURL=app-input.d.ts.map
@@ -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 DirviApi only when it is executed. This keeps
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: (shell: ShellApi) => void | Promise<void>;
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":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D,CAAC"}
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
- * Object set through initialization, i.e. evaluating init.mjs.
5
+ * Resolved startup state produced by evaluating `init.mjs`.
7
6
  *
8
- * Config is shared tree-UI configuration. App selection is distinct from Config
9
- * value. Ex commands are executable startup registrations and are therefore
10
- * stored separately from Config.
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
- readonly initialAppId: string;
18
- readonly registeredApps: ReadonlyMap<string, AppPackageReference>;
19
- readonly exCommands: readonly ExCommand[];
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,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAClE,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;CAC3C,CAAC"}
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 {};