@arki/dot 0.3.0 → 0.4.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.
Files changed (113) hide show
  1. package/README.md +86 -86
  2. package/dist/cli/discover.js +2 -2
  3. package/dist/cli/discover.js.map +1 -1
  4. package/dist/cli/error-codes.d.ts +8 -0
  5. package/dist/cli/error-codes.d.ts.map +1 -1
  6. package/dist/cli/error-codes.js +12 -0
  7. package/dist/cli/error-codes.js.map +1 -1
  8. package/dist/cli/index.d.ts +9 -2
  9. package/dist/cli/index.d.ts.map +1 -1
  10. package/dist/cli/index.js +54 -23
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/cli/new.d.ts +1 -1
  13. package/dist/cli/new.js +1 -1
  14. package/dist/cli/render-doctor.d.ts +1 -1
  15. package/dist/cli/render-doctor.js +23 -23
  16. package/dist/cli/render-doctor.js.map +1 -1
  17. package/dist/cli/render-explain.d.ts.map +1 -1
  18. package/dist/cli/render-explain.js +33 -19
  19. package/dist/cli/render-explain.js.map +1 -1
  20. package/dist/cli/render-graph.d.ts +3 -3
  21. package/dist/cli/render-graph.js +12 -12
  22. package/dist/cli/render-graph.js.map +1 -1
  23. package/dist/cli/render-projection.d.ts +29 -0
  24. package/dist/cli/render-projection.d.ts.map +1 -0
  25. package/dist/cli/render-projection.js +202 -0
  26. package/dist/cli/render-projection.js.map +1 -0
  27. package/dist/define-app.d.ts +49 -17
  28. package/dist/define-app.d.ts.map +1 -1
  29. package/dist/define-app.js +16 -9
  30. package/dist/define-app.js.map +1 -1
  31. package/dist/diagnostics.d.ts +19 -15
  32. package/dist/diagnostics.d.ts.map +1 -1
  33. package/dist/diagnostics.js +2 -2
  34. package/dist/index.d.ts +15 -12
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +7 -5
  37. package/dist/index.js.map +1 -1
  38. package/dist/init-pips.d.ts +45 -0
  39. package/dist/init-pips.d.ts.map +1 -0
  40. package/dist/init-pips.js +49 -0
  41. package/dist/init-pips.js.map +1 -0
  42. package/dist/init-plugins.d.ts +45 -0
  43. package/dist/init-plugins.d.ts.map +1 -0
  44. package/dist/init-plugins.js +49 -0
  45. package/dist/init-plugins.js.map +1 -0
  46. package/dist/kernel/app-instance.d.ts +5 -5
  47. package/dist/kernel/app-instance.d.ts.map +1 -1
  48. package/dist/kernel/app-instance.js +375 -244
  49. package/dist/kernel/app-instance.js.map +1 -1
  50. package/dist/kernel/otel.d.ts +19 -19
  51. package/dist/kernel/otel.d.ts.map +1 -1
  52. package/dist/kernel/otel.js +22 -22
  53. package/dist/kernel/otel.js.map +1 -1
  54. package/dist/lifecycle-observer.d.ts +10 -10
  55. package/dist/lifecycle-observer.d.ts.map +1 -1
  56. package/dist/lifecycle-observer.js +1 -1
  57. package/dist/lifecycle.d.ts +18 -14
  58. package/dist/lifecycle.d.ts.map +1 -1
  59. package/dist/lifecycle.js +13 -9
  60. package/dist/lifecycle.js.map +1 -1
  61. package/dist/manifest.d.ts +70 -48
  62. package/dist/manifest.d.ts.map +1 -1
  63. package/dist/manifest.js +74 -5
  64. package/dist/manifest.js.map +1 -1
  65. package/dist/pip-contract.d.ts +29 -21
  66. package/dist/pip-contract.d.ts.map +1 -1
  67. package/dist/pip-contract.js +1 -0
  68. package/dist/pip-contract.js.map +1 -1
  69. package/dist/pip.d.ts +7 -4
  70. package/dist/pip.d.ts.map +1 -1
  71. package/dist/pip.js +2 -0
  72. package/dist/pip.js.map +1 -1
  73. package/dist/plugin-contract.d.ts +335 -143
  74. package/dist/plugin-contract.d.ts.map +1 -1
  75. package/dist/plugin-contract.js +201 -36
  76. package/dist/plugin-contract.js.map +1 -1
  77. package/dist/plugin.d.ts +18 -15
  78. package/dist/plugin.d.ts.map +1 -1
  79. package/dist/plugin.js +11 -11
  80. package/dist/plugin.js.map +1 -1
  81. package/dist/test-harness.d.ts +31 -31
  82. package/dist/test-harness.d.ts.map +1 -1
  83. package/dist/test-harness.js +29 -28
  84. package/dist/test-harness.js.map +1 -1
  85. package/dist/timeline.d.ts +2 -2
  86. package/dist/timeline.js +5 -5
  87. package/dist/timeline.js.map +1 -1
  88. package/package.json +9 -9
  89. package/src/cli/discover.ts +2 -2
  90. package/src/cli/error-codes.ts +12 -0
  91. package/src/cli/index.ts +76 -29
  92. package/src/cli/new.ts +1 -1
  93. package/src/cli/render-doctor.ts +21 -21
  94. package/src/cli/render-explain.ts +37 -19
  95. package/src/cli/render-graph.ts +12 -12
  96. package/src/cli/render-projection.ts +239 -0
  97. package/src/define-app.ts +85 -22
  98. package/src/diagnostics.ts +19 -15
  99. package/src/index.ts +37 -20
  100. package/src/init-plugins.ts +142 -0
  101. package/src/kernel/app-instance.ts +428 -282
  102. package/src/kernel/otel.ts +27 -27
  103. package/src/lifecycle-observer.ts +10 -10
  104. package/src/lifecycle.ts +19 -15
  105. package/src/manifest.ts +128 -50
  106. package/src/{pip-contract.ts → plugin-contract.ts} +73 -62
  107. package/src/{pip.ts → plugin.ts} +37 -18
  108. package/src/test-harness.ts +52 -51
  109. package/src/timeline.ts +6 -6
  110. package/templates/app-minimal/AGENTS.md.tmpl +9 -9
  111. package/templates/app-minimal/README.md.tmpl +1 -1
  112. package/templates/app-minimal/tests/boot.test.ts.tmpl +1 -1
  113. package/src/cli/render-openapi.ts +0 -147
@@ -1,37 +1,37 @@
1
1
  /**
2
- * Test harness for unit-testing DOT pips.
2
+ * Test harness for unit-testing DOT plugins.
3
3
  *
4
- * Provides `testApp` — a convenience wrapper that lets pip authors verify
4
+ * Provides `testApp` — a convenience wrapper that lets plugin authors verify
5
5
  * lifecycle behaviour, registration, and service publishing without dragging
6
6
  * in concrete framework dependencies.
7
7
  *
8
- * NOTE: `testApp` takes an erased pip array, so the compile-time wiring
8
+ * NOTE: `testApp` takes an erased plugin array, so the compile-time wiring
9
9
  * guard does not apply here — the kernel's runtime validation (unsatisfied
10
10
  * needs, collisions) still does. Use `defineApp(...).use(...)` chains in
11
11
  * tests that should exercise the type-level guard.
12
12
  *
13
13
  * @example
14
- * import { testApp, pip } from '@arki/dot';
14
+ * import { testApp, plugin } from '@arki/dot';
15
15
  *
16
- * const myPip = pip({
16
+ * const myPlugin = plugin({
17
17
  * name: 'counter',
18
18
  * boot: () => ({ counter: { value: 0 } }),
19
19
  * });
20
20
  *
21
21
  * it('publishes a counter service', async () => {
22
- * const app = await bootTestApp<{ counter: { value: number } }>([myPip]);
22
+ * const app = await bootTestApp<{ counter: { value: number } }>([myPlugin]);
23
23
  * expect(app.services.counter.value).toBe(0);
24
24
  * await app.dispose();
25
25
  * });
26
26
  */
27
27
  import { defineApp } from './define-app.js';
28
28
  /**
29
- * Build a DOT app builder pre-populated with the given pips, ready to
29
+ * Build a DOT app builder pre-populated with the given plugins, ready to
30
30
  * `.configure()`, `.boot()` or `.start()` from a test.
31
31
  */
32
- export function testApp(pips = [], options = {}) {
32
+ export function testApp(plugins = [], options = {}) {
33
33
  let builder = defineApp(options.name ?? 'test-app', { config: options.config });
34
- for (const p of pips) {
34
+ for (const p of plugins) {
35
35
  builder = builder.use(p);
36
36
  }
37
37
  return builder;
@@ -40,22 +40,22 @@ export function testApp(pips = [], options = {}) {
40
40
  * Convenience: build, boot, return the running app. Caller is responsible for
41
41
  * calling `app.dispose()` when finished.
42
42
  */
43
- export async function bootTestApp(pips = [], options = {}) {
44
- return testApp(pips, options).boot();
43
+ export async function bootTestApp(plugins = [], options = {}) {
44
+ return testApp(plugins, options).boot();
45
45
  }
46
46
  /**
47
- * Unit-test a single pip with **typed overrides** — the compile-checked
47
+ * Unit-test a single plugin with **typed overrides** — the compile-checked
48
48
  * counterpart to {@link testApp}'s erased arrays.
49
49
  *
50
- * Each `.provide()` satisfies one of the pip's needs directly (no real
51
- * provider pip, no dependency chain) and removes it from the builder's
50
+ * Each `.provide()` satisfies one of the plugin's needs directly (no real
51
+ * provider plugin, no dependency chain) and removes it from the builder's
52
52
  * remaining-needs type. `boot()` only compiles once every need is covered,
53
53
  * and a fake of the wrong shape fails at the `.provide()` call site:
54
54
  *
55
55
  * ```ts
56
- * import { testPip } from '@arki/dot/test-harness';
56
+ * import { testPlugin } from '@arki/dot/test-harness';
57
57
  *
58
- * const app = await testPip(catalog)
58
+ * const app = await testPlugin(catalog)
59
59
  * .provide(Db, fakeDb) // token need
60
60
  * .provide('cache', fakeKv) // anonymous need — wire key is the alias
61
61
  * .boot();
@@ -67,41 +67,42 @@ export async function bootTestApp(pips = [], options = {}) {
67
67
  * `service.lazy<T>()` needs accept either a plain `T` fake (the kernel
68
68
  * lifts it) or a `Lazy<T>` handle (`lazyOf(value)` is handy here).
69
69
  * Lifecycle semantics are the real kernel's — the fakes are published by a
70
- * synthetic first pip, so reverse-order teardown and lazy auto-dispose
70
+ * synthetic first plugin, so reverse-order teardown and lazy auto-dispose
71
71
  * behave exactly as in production.
72
72
  */
73
- export function testPip(pip, options = {}) {
73
+ export function testPlugin(plugin, options = {}) {
74
74
  // Erasure seam — same boundary as `makeBuilder` in define-app.ts: the
75
75
  // runtime impl is untyped, the type parameters do all the guarding.
76
- return makeTestPipBuilder({ pip: pip, fakes: {}, options });
76
+ return makeTestPluginBuilder({ plugin: plugin, fakes: {}, options });
77
77
  }
78
78
  /**
79
- * Erased implementation behind {@link testPip}. Mirrors `makeBuilder` in
79
+ * Erased implementation behind {@link testPlugin}. Mirrors `makeBuilder` in
80
80
  * `define-app.ts`: the runtime is untyped, the single cast at the return
81
81
  * is the seam, and the type parameters do all the guarding.
82
82
  */
83
- function makeTestPipBuilder(state) {
83
+ function makeTestPluginBuilder(state) {
84
84
  const bootApp = async () => {
85
- const pips = [];
85
+ const plugins = [];
86
86
  if (Object.keys(state.fakes).length > 0) {
87
87
  const fakes = { ...state.fakes };
88
- pips.push({
89
- name: `test:fakes(${state.pip.name})`,
88
+ plugins.push({
89
+ name: `test:fakes(${state.plugin.name})`,
90
90
  needs: {},
91
+ actions: [],
91
92
  renames: {},
92
93
  hooks: { boot: () => fakes },
93
94
  });
94
95
  }
95
- pips.push(state.pip);
96
- return testApp(pips, {
97
- name: state.options.name ?? `test:${state.pip.name}`,
96
+ plugins.push(state.plugin);
97
+ return testApp(plugins, {
98
+ name: state.options.name ?? `test:${state.plugin.name}`,
98
99
  ...(state.options.config === undefined ? {} : { config: state.options.config }),
99
100
  }).boot();
100
101
  };
101
102
  const impl = {
102
103
  provide(tokenOrKey, value) {
103
104
  const key = typeof tokenOrKey === 'string' ? tokenOrKey : tokenOrKey.key;
104
- return makeTestPipBuilder({ ...state, fakes: { ...state.fakes, [key]: value } });
105
+ return makeTestPluginBuilder({ ...state, fakes: { ...state.fakes, [key]: value } });
105
106
  },
106
107
  async boot(..._guard) {
107
108
  return bootApp();
@@ -1 +1 @@
1
- {"version":3,"file":"test-harness.js","sourceRoot":"","sources":["../src/test-harness.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"test-harness.js","sourceRoot":"","sources":["../src/test-harness.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * ASCII waterfall renderer for DOT lifecycle diagnostics.
3
3
  *
4
- * Given a `DotDiagnosticsSnapshot`, builds a compact per-phase, per-pip
4
+ * Given a `DotDiagnosticsSnapshot`, builds a compact per-phase, per-plugin
5
5
  * duration chart suitable for printing in a terminal or embedding in a
6
6
  * `dot doctor` text report. No colour, no Unicode beyond `█` so the
7
7
  * output stays clean in log aggregators and CI.
@@ -29,7 +29,7 @@ export type RenderTimelineOptions = {
29
29
  /** Maximum bar width in characters (default `50`). */
30
30
  readonly barWidth?: number;
31
31
  /**
32
- * When `true`, also emit any pip's diagnostic `issues[]` underneath
32
+ * When `true`, also emit any plugin's diagnostic `issues[]` underneath
33
33
  * the bar so failure reasons are visible in the timeline view.
34
34
  * Default `true`.
35
35
  */
package/dist/timeline.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * ASCII waterfall renderer for DOT lifecycle diagnostics.
3
3
  *
4
- * Given a `DotDiagnosticsSnapshot`, builds a compact per-phase, per-pip
4
+ * Given a `DotDiagnosticsSnapshot`, builds a compact per-phase, per-plugin
5
5
  * duration chart suitable for printing in a terminal or embedding in a
6
6
  * `dot doctor` text report. No colour, no Unicode beyond `█` so the
7
7
  * output stays clean in log aggregators and CI.
@@ -60,8 +60,8 @@ export function renderTimeline(snapshot, opts = {}) {
60
60
  const title = `Timeline: ${snapshot.app.name} (state=${snapshot.app.state})`;
61
61
  lines.push(title);
62
62
  lines.push('─'.repeat(title.length));
63
- // Layout: 2-space indent, 10-char pip column, 8-char duration column, then bar.
64
- const PIP_COL = 12;
63
+ // Layout: 2-space indent, 10-char plugin column, 8-char duration column, then bar.
64
+ const PLUGIN_COL = 12;
65
65
  const DUR_COL = 8;
66
66
  const indent = ' ';
67
67
  let anyRendered = false;
@@ -77,9 +77,9 @@ export function renderTimeline(snapshot, opts = {}) {
77
77
  const durationMs = entry.durationMs ?? 0;
78
78
  const bar = makeBar(durationMs, maxDuration, barWidth);
79
79
  const failedMark = entry.state === 'failed' ? ' ✗' : '';
80
- const pipCol = entry.pip.padEnd(PIP_COL);
80
+ const pluginCol = entry.plugin.padEnd(PLUGIN_COL);
81
81
  const durCol = formatDuration(durationMs).padStart(DUR_COL);
82
- lines.push(`${indent}${pipCol}${durCol} ${bar}${failedMark}`);
82
+ lines.push(`${indent}${pluginCol}${durCol} ${bar}${failedMark}`);
83
83
  if (showIssues && entry.issues.length > 0) {
84
84
  for (const issue of entry.issues) {
85
85
  lines.push(`${indent} └─ ${issue.code}: ${issue.message}`);
@@ -1 +1 @@
1
- {"version":3,"file":"timeline.js","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAKH,MAAM,WAAW,GAAgC,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;AAEnG,MAAM,QAAQ,GAAG,GAAG,CAAC;AACrB,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAa9B;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgC,EAAE,OAA8B,EAAE;IAC/F,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,iBAAiB,CAAC;IACpD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC;IAE3C,sEAAsE;IACtE,sEAAsE;IACtE,qEAAqE;IACrE,yDAAyD;IACzD,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2C,CAAC;IACnE,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;YAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACxC,CAAC;IAED,0EAA0E;IAC1E,oEAAoE;IACpE,uDAAuD;IACvD,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QACvC,MAAM,CAAC,GAAG,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC,GAAG,WAAW;YAAE,WAAW,GAAG,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,aAAa,QAAQ,CAAC,GAAG,CAAC,IAAI,WAAW,QAAQ,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC;IAC7E,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAErC,gFAAgF;IAChF,MAAM,OAAO,GAAG,EAAE,CAAC;IACnB,MAAM,OAAO,GAAG,CAAC,CAAC;IAClB,MAAM,MAAM,GAAG,IAAI,CAAC;IAEpB,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAC/C,IAAI,WAAW;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChC,WAAW,GAAG,IAAI,CAAC;QACnB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;YACzC,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;YACvD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACxD,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACzC,MAAM,MAAM,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAC5D,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,KAAK,GAAG,GAAG,UAAU,EAAE,CAAC,CAAC;YAC/D,IAAI,UAAU,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1C,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;oBACjC,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,QAAQ,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC9D,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;IACvE,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACvC,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,SAAS,OAAO,CAAC,QAAgB,EAAE,GAAW,EAAE,KAAa;IAC3D,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACxB,IAAI,QAAQ,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IAClF,OAAO,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc,CAAC,EAAU;IAChC,IAAI,EAAE,IAAI,IAAI;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC;IAC7C,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9B,CAAC"}
1
+ {"version":3,"file":"timeline.js","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arki/dot",
3
- "version": "0.3.0",
4
- "description": "TypeScript-first application composition framework for the ARKI package family — lifecycle-aware pips, deterministic boot order, manifest/diagnostics envelopes, and an agent-native CLI. A DOT app is the sum of its pips.",
3
+ "version": "0.4.0",
4
+ "description": "TypeScript-first application composition framework for the ARKI package family — lifecycle-aware plugins, deterministic boot order, manifest/diagnostics envelopes, and an agent-native CLI. A DOT app is the sum of its plugins.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -15,8 +15,8 @@
15
15
  "arki",
16
16
  "typescript",
17
17
  "framework",
18
- "pip",
19
- "pips",
18
+ "plugin",
19
+ "plugins",
20
20
  "lifecycle",
21
21
  "diagnostics",
22
22
  "manifest",
@@ -31,10 +31,10 @@
31
31
  "import": "./src/index.ts",
32
32
  "default": "./dist/index.js"
33
33
  },
34
- "./pip": {
35
- "types": "./dist/pip.d.ts",
36
- "import": "./dist/pip.js",
37
- "default": "./dist/pip.js"
34
+ "./plugin": {
35
+ "types": "./dist/plugin.d.ts",
36
+ "import": "./dist/plugin.js",
37
+ "default": "./dist/plugin.js"
38
38
  },
39
39
  "./package.json": "./package.json",
40
40
  "./cli": {
@@ -63,7 +63,7 @@
63
63
  ],
64
64
  "dependencies": {
65
65
  "@arki/assert": "0.0.2",
66
- "@arki/contracts": "0.0.2",
66
+ "@arki/contracts": "0.0.3",
67
67
  "@arki/log": "0.0.2",
68
68
  "@opentelemetry/api": "^1.9.0",
69
69
  "zod": "4.3.5"
@@ -186,7 +186,7 @@ export async function loadAppFromFile(filePath: string): Promise<DiscoveredApp>
186
186
  code: DotCliErrorCode.AppInvalidExport,
187
187
  message: `Default export from ${filePath} is not a DotApp or DotAppBuilder.`,
188
188
  remediation:
189
- 'Export a DotApp or DotAppBuilder as the default. Example: `export default defineApp("my-app").use(pip);`',
189
+ 'Export a DotApp or DotAppBuilder as the default. Example: `export default defineApp("my-app").use(plugin);`',
190
190
  metadata: { filePath },
191
191
  });
192
192
  }
@@ -204,7 +204,7 @@ export async function discoverApp(options: DiscoveryOptions = {}): Promise<Disco
204
204
  code: DotCliErrorCode.AppNotFound,
205
205
  message: `No DOT app file found in ${cwd}.`,
206
206
  remediation:
207
- 'Create `dot.config.ts` exporting your app (e.g. `export default defineApp("my-app").use(pip)`), or pass `--app <path>` to point at an existing file.',
207
+ 'Create `dot.config.ts` exporting your app (e.g. `export default defineApp("my-app").use(plugin)`), or pass `--app <path>` to point at an existing file.',
208
208
  metadata: { cwd, searched: DEFAULT_APP_PATHS },
209
209
  });
210
210
  }
@@ -21,6 +21,14 @@ export const DotCliErrorCode = {
21
21
  AppLifecycleFailed: 'DOT_CLI_E006',
22
22
  /** `dot doctor --observability` ran but no OTel SDK is registered. */
23
23
  ObservabilityNoSdk: 'DOT_CLI_E007',
24
+ /** Requested projection format is not registered by the app. */
25
+ ProjectionNotFound: 'DOT_CLI_E008',
26
+ /** Projection module import failed or did not export `project`. */
27
+ ProjectionImportFailed: 'DOT_CLI_E009',
28
+ /** Projection execution failed or returned non-JSON output. */
29
+ ProjectionExecutionFailed: 'DOT_CLI_E010',
30
+ /** Multiple modules claim the same projection format. */
31
+ ProjectionConflict: 'DOT_CLI_E011',
24
32
  } as const;
25
33
 
26
34
  export type DotCliErrorCodeValue = (typeof DotCliErrorCode)[keyof typeof DotCliErrorCode];
@@ -39,6 +47,10 @@ export const DotCliDocsAnchor: Record<DotCliErrorCodeValue, string> = {
39
47
  [DotCliErrorCode.InvalidArgs]: 'invalid-args',
40
48
  [DotCliErrorCode.AppLifecycleFailed]: 'app-lifecycle-failed',
41
49
  [DotCliErrorCode.ObservabilityNoSdk]: 'observability-no-sdk',
50
+ [DotCliErrorCode.ProjectionNotFound]: 'projection-not-found',
51
+ [DotCliErrorCode.ProjectionImportFailed]: 'projection-import-failed',
52
+ [DotCliErrorCode.ProjectionExecutionFailed]: 'projection-execution-failed',
53
+ [DotCliErrorCode.ProjectionConflict]: 'projection-conflict',
42
54
  };
43
55
 
44
56
  export function dotCliDocsUrl(code: DotCliErrorCodeValue): string {
package/src/cli/index.ts CHANGED
@@ -24,7 +24,7 @@ import { createDebugLogger } from '@arki/log/debug';
24
24
 
25
25
  import type { DotApp, DotAppBuilder, DotAppConfigured } from '../define-app.js';
26
26
  import type { DiagnosticIssue } from '../diagnostics.js';
27
- import type { DiscoveredApp } from './discover.js';
27
+ import type { DiscoveredApp, DiscoveryResult } from './discover.js';
28
28
  import type { DotCliEnvelope, DotCliEnvelopeStatus } from './render-explain.js';
29
29
  import { DotLifecycleError } from '../lifecycle.js';
30
30
  import { discoverApp, guards } from './discover.js';
@@ -34,7 +34,7 @@ import { probeObservability } from './observability-probe.js';
34
34
  import { renderDoctor } from './render-doctor.js';
35
35
  import { renderExplain } from './render-explain.js';
36
36
  import { renderGraph } from './render-graph.js';
37
- import { renderOpenApi } from './render-openapi.js';
37
+ import { renderProjection } from './render-projection.js';
38
38
 
39
39
  const debugCli = createDebugLogger('arki:dot:cli');
40
40
 
@@ -59,14 +59,16 @@ Common options:
59
59
  --app <path> Path to the app entry file (default: discovers
60
60
  ./dot.config.ts, ./src/app.ts, or ./app.ts)
61
61
  --cwd <dir> Working directory (default: current)
62
- --graph Emit the pip graph as Mermaid flowchart source
62
+ --graph Emit the plugin graph as Mermaid flowchart source
63
63
  instead of the standard output. explain shows
64
64
  declaration (= boot) order; doctor shows the
65
65
  wiring observed during boot. Composes with --json.
66
- --openapi (explain only) Emit an OpenAPI 3.1 document built
67
- from the manifest's registered routes and their
68
- JSON Schemas — no boot required. Composes with
69
- --json.
66
+ --as <format> (explain only) Render a registered projection format.
67
+ Examples: openapi, asyncapi, help-text. Composes
68
+ with --json.
69
+ --module <specifier> (explain --as only) Override the registered
70
+ projection module for one invocation.
71
+ --openapi Deprecated alias for --as openapi.
70
72
 
71
73
  \`doctor\` options:
72
74
  --observability Also probe whether an OpenTelemetry SDK is
@@ -101,7 +103,11 @@ export type CliArgs = {
101
103
  observability?: boolean;
102
104
  /** `--graph` (honored by `explain` and `doctor`). */
103
105
  graph?: boolean;
104
- /** `--openapi` (honored by `explain` only). */
106
+ /** `--as` projection format (honored by `explain` only). */
107
+ as?: string;
108
+ /** `--module` projection override (honored by `explain --as` only). */
109
+ module?: string;
110
+ /** Deprecated `--openapi` alias for `--as openapi`. */
105
111
  openapi?: boolean;
106
112
  };
107
113
 
@@ -167,6 +173,8 @@ export function parseArgs(argv: readonly string[]): CliArgs {
167
173
  force: { type: 'boolean', default: false },
168
174
  observability: { type: 'boolean', default: false },
169
175
  graph: { type: 'boolean', default: false },
176
+ as: { type: 'string' },
177
+ module: { type: 'string' },
170
178
  openapi: { type: 'boolean', default: false },
171
179
  },
172
180
  });
@@ -191,6 +199,8 @@ export function parseArgs(argv: readonly string[]): CliArgs {
191
199
  force?: boolean;
192
200
  observability?: boolean;
193
201
  graph?: boolean;
202
+ as?: string;
203
+ module?: string;
194
204
  openapi?: boolean;
195
205
  };
196
206
 
@@ -199,18 +209,27 @@ export function parseArgs(argv: readonly string[]): CliArgs {
199
209
 
200
210
  if (!command) command = 'help';
201
211
 
202
- if (values.openapi === true && values.graph === true) {
212
+ const projectionFormat = values.as ?? (values.openapi === true ? 'openapi' : undefined);
213
+
214
+ if (projectionFormat !== undefined && values.graph === true) {
203
215
  throw new DotCliError({
204
216
  code: DotCliErrorCode.InvalidArgs,
205
- message: '--openapi and --graph are mutually exclusive.',
217
+ message: '--as/--openapi and --graph are mutually exclusive.',
206
218
  remediation: 'Pick one output format per invocation.',
207
219
  });
208
220
  }
209
- if (values.openapi === true && command === 'doctor') {
221
+ if (projectionFormat !== undefined && command === 'doctor') {
222
+ throw new DotCliError({
223
+ code: DotCliErrorCode.InvalidArgs,
224
+ message: '--as is not supported by doctor.',
225
+ remediation: 'Use `dot explain --as <format>` — projections render from the static manifest, no boot needed.',
226
+ });
227
+ }
228
+ if (values.module !== undefined && projectionFormat === undefined) {
210
229
  throw new DotCliError({
211
230
  code: DotCliErrorCode.InvalidArgs,
212
- message: '--openapi is not supported by doctor.',
213
- remediation: 'Use `dot explain --openapi` the document renders from the static manifest, no boot needed.',
231
+ message: '--module requires --as <format>.',
232
+ remediation: 'Pass a projection format with --as, or remove --module.',
214
233
  });
215
234
  }
216
235
 
@@ -239,14 +258,15 @@ export function parseArgs(argv: readonly string[]): CliArgs {
239
258
  force: values.force ?? false,
240
259
  observability: values.observability ?? false,
241
260
  graph: values.graph ?? false,
261
+ as: projectionFormat,
262
+ module: values.module,
242
263
  openapi: values.openapi ?? false,
243
264
  };
244
265
  }
245
266
 
246
267
  /** Discovery wrapper — returns `null` for help/version commands. */
247
- async function loadApp(args: CliArgs): Promise<DiscoveredApp> {
248
- const { app } = await discoverApp({ appPath: args.appPath, cwd: args.cwd });
249
- return app;
268
+ async function loadApp(args: CliArgs): Promise<DiscoveryResult> {
269
+ return discoverApp({ appPath: args.appPath, cwd: args.cwd });
250
270
  }
251
271
 
252
272
  /**
@@ -255,7 +275,16 @@ async function loadApp(args: CliArgs): Promise<DiscoveredApp> {
255
275
  */
256
276
  export async function runExplain(
257
277
  discovered: DiscoveredApp,
258
- opts: { json: boolean; graph?: boolean; openapi?: boolean; out?: (line: string) => void; now?: () => Date },
278
+ opts: {
279
+ json: boolean;
280
+ graph?: boolean;
281
+ as?: string;
282
+ module?: string;
283
+ appFilePath?: string;
284
+ openapi?: boolean;
285
+ out?: (line: string) => void;
286
+ now?: () => Date;
287
+ },
259
288
  ): Promise<DotCliEnvelope<unknown>> {
260
289
  let configured: DotApp<Record<string, unknown>> | DotAppConfigured<Record<string, unknown>>;
261
290
  try {
@@ -271,9 +300,15 @@ export async function runExplain(
271
300
  throw wrapLifecycleError(err, 'configure');
272
301
  }
273
302
 
274
- if (opts.openapi === true) {
275
- return renderOpenApi(
276
- { manifest: configured.manifest, command: 'explain' },
303
+ if (opts.as !== undefined) {
304
+ return renderProjection(
305
+ {
306
+ manifest: configured.manifest,
307
+ command: 'explain',
308
+ format: opts.as,
309
+ appFilePath: opts.appFilePath ?? process.cwd(),
310
+ ...(opts.module === undefined ? {} : { module: opts.module }),
311
+ },
277
312
  { json: opts.json, out: opts.out, now: opts.now },
278
313
  );
279
314
  }
@@ -296,7 +331,7 @@ type DoctorRunOptions = {
296
331
  * present. Default `false`.
297
332
  */
298
333
  observability?: boolean;
299
- /** When `true`, emit the pip graph (Mermaid) instead of diagnostics. */
334
+ /** When `true`, emit the plugin graph (Mermaid) instead of diagnostics. */
300
335
  graph?: boolean;
301
336
  };
302
337
 
@@ -328,7 +363,7 @@ export async function runDoctor(
328
363
  }
329
364
 
330
365
  // Builder or configured seam: drive lifecycle ourselves. Boot failures fall
331
- // back to the configured seam's diagnostics so per-pip issues stay
366
+ // back to the configured seam's diagnostics so per-plugin issues stay
332
367
  // visible.
333
368
  let configured: DotAppConfigured<Record<string, unknown>>;
334
369
  try {
@@ -345,7 +380,7 @@ export async function runDoctor(
345
380
  bootedApp = await configured.boot();
346
381
  } catch (err) {
347
382
  // Boot failed — surface diagnostics from the configured seam below.
348
- // We deliberately swallow the throw so the user sees the per-pip
383
+ // We deliberately swallow the throw so the user sees the per-plugin
349
384
  // issues instead of an opaque wrapper error.
350
385
  bootThrew = true;
351
386
  debugCli('boot threw, falling back to configured diagnostics: %O', err);
@@ -354,7 +389,7 @@ export async function runDoctor(
354
389
  try {
355
390
  if (opts.graph === true) {
356
391
  // Post-boot manifest carries the observed wiring edges; after a boot
357
- // failure it carries the edges recorded up to the failing pip.
392
+ // failure it carries the edges recorded up to the failing plugin.
358
393
  const manifest = bootedApp ? bootedApp.manifest : configured.manifest;
359
394
  const graphEnvelope = renderGraph(
360
395
  { manifest, command: 'doctor' },
@@ -410,8 +445,8 @@ function wrapLifecycleError(err: unknown, phase: 'configure' | 'boot'): DotCliEr
410
445
  message: `App ${phase} failed: ${message}`,
411
446
  remediation:
412
447
  phase === 'configure'
413
- ? 'Check the pips registered in your app for a synchronous `configure` hook that throws or returns a Promise. Run `dot doctor` for per-pip diagnostics.'
414
- : 'Run `dot doctor` to see per-pip diagnostics. The boot hook for one of your pips failed.',
448
+ ? 'Check the plugins registered in your app for a synchronous `configure` hook that throws or returns a Promise. Run `dot doctor` for per-plugin diagnostics.'
449
+ : 'Run `dot doctor` to see per-plugin diagnostics. The boot hook for one of your plugins failed.',
415
450
  metadata: { phase, underlyingCode: code },
416
451
  cause: err,
417
452
  });
@@ -519,9 +554,18 @@ export async function main(options: MainOptions): Promise<number> {
519
554
  let envelope: DotCliEnvelope<unknown>;
520
555
 
521
556
  if (args.command === 'explain') {
522
- envelope = await runExplain(discovered, { ...opts, openapi: args.openapi });
557
+ if (args.openapi === true) {
558
+ stderr('dot: --openapi is deprecated; use --as openapi.\n');
559
+ }
560
+ envelope = await runExplain(discovered.app, {
561
+ ...opts,
562
+ as: args.as,
563
+ module: args.module,
564
+ appFilePath: discovered.filePath,
565
+ openapi: args.openapi,
566
+ });
523
567
  } else {
524
- envelope = await runDoctor(discovered, { ...opts, observability: args.observability });
568
+ envelope = await runDoctor(discovered.app, { ...opts, observability: args.observability });
525
569
  }
526
570
 
527
571
  return envelopeExitCode(envelope.status);
@@ -587,11 +631,14 @@ const isMainModule = await (async () => {
587
631
  /* ignore */
588
632
  }
589
633
  // Node fallback: compare argv[1]'s resolved file URL with import.meta.url.
634
+ // argv[1] is the bin symlink (node_modules/.bin/dot) while Node resolves
635
+ // import.meta.url to the real file — realpath argv[1] so they can match.
590
636
  try {
591
637
  const entry = process.argv[1];
592
638
  if (typeof entry !== 'string') return false;
593
639
  const { pathToFileURL } = await import('node:url');
594
- return pathToFileURL(entry).href === import.meta.url;
640
+ const { realpath } = await import('node:fs/promises');
641
+ return pathToFileURL(await realpath(entry)).href === import.meta.url;
595
642
  } catch {
596
643
  return false;
597
644
  }
package/src/cli/new.ts CHANGED
@@ -17,7 +17,7 @@
17
17
  * stripped during scaffolding so `package.json.tmpl` becomes
18
18
  * `package.json` on disk.
19
19
  *
20
- * Commands deferred to v1.1 (`dot add <pip>`, `dot dev`,
20
+ * Commands deferred to v1.1 (`dot add <plugin>`, `dot dev`,
21
21
  * `dot migrate`) are deliberately NOT surfaced here.
22
22
  */
23
23
 
@@ -2,7 +2,7 @@
2
2
  * Renderers for `dot doctor`.
3
3
  *
4
4
  * Reads a booted (or fully-failed) app's `diagnostics` snapshot and emits
5
- * either a JSON envelope or a human-readable per-pip status report.
5
+ * either a JSON envelope or a human-readable per-plugin status report.
6
6
  *
7
7
  * Envelope `status` reflects the worst severity present:
8
8
  * - `failure` if any issue has severity `error`
@@ -27,8 +27,8 @@ function nowIso(opts: RenderOptions): string {
27
27
  }
28
28
 
29
29
  /**
30
- * Walk every issue carried anywhere in the snapshot (top-level + per-pip +
31
- * per-route + per-service + per-lifecycle entry) and pick the worst severity.
30
+ * Walk every issue carried anywhere in the snapshot (top-level + per-plugin +
31
+ * per-action + per-service + per-lifecycle entry) and pick the worst severity.
32
32
  */
33
33
  function worstSeverity(snap: DotDiagnosticsSnapshot): DotCliEnvelopeStatus {
34
34
  let hasWarning = false;
@@ -46,8 +46,8 @@ function worstSeverity(snap: DotDiagnosticsSnapshot): DotCliEnvelopeStatus {
46
46
 
47
47
  let hasError = false;
48
48
  collect(snap.issues);
49
- for (const p of snap.pips) collect(p.issues);
50
- for (const r of snap.routes) collect(r.issues);
49
+ for (const p of snap.plugins) collect(p.issues);
50
+ for (const action of snap.actions) collect(action.issues);
51
51
  for (const s of snap.services) collect(s.issues);
52
52
  for (const l of snap.lifecycle) collect(l.issues);
53
53
 
@@ -72,8 +72,8 @@ export function buildDoctorEnvelope(
72
72
  for (const issue of issues) errors.push(issue);
73
73
  };
74
74
  collect(source.diagnostics.issues);
75
- for (const p of source.diagnostics.pips) collect(p.issues);
76
- for (const r of source.diagnostics.routes) collect(r.issues);
75
+ for (const p of source.diagnostics.plugins) collect(p.issues);
76
+ for (const action of source.diagnostics.actions) collect(action.issues);
77
77
  for (const s of source.diagnostics.services) collect(s.issues);
78
78
  for (const l of source.diagnostics.lifecycle) collect(l.issues);
79
79
 
@@ -120,13 +120,13 @@ function renderTextDoctor(snap: DotDiagnosticsSnapshot, status: DotCliEnvelopeSt
120
120
  lines.push(`generatedAt: ${snap.generatedAt}`);
121
121
  lines.push('');
122
122
 
123
- // Per-pip status
124
- lines.push(`Pips (${snap.pips.length})`);
125
- if (snap.pips.length === 0) {
123
+ // Per-plugin status
124
+ lines.push(`Plugins (${snap.plugins.length})`);
125
+ if (snap.plugins.length === 0) {
126
126
  lines.push(' (none)');
127
127
  } else {
128
- for (const p of snap.pips) {
129
- lines.push(` ${statusIcon(p.status)} ${p.pip}`);
128
+ for (const p of snap.plugins) {
129
+ lines.push(` ${statusIcon(p.status)} ${p.plugin}`);
130
130
  if (p.issues.length > 0) {
131
131
  lines.push(...renderIssueLines(p.issues, ' '));
132
132
  }
@@ -138,7 +138,7 @@ function renderTextDoctor(snap: DotDiagnosticsSnapshot, status: DotCliEnvelopeSt
138
138
  if (snap.services.length > 0) {
139
139
  lines.push(`Services (${snap.services.length})`);
140
140
  for (const s of snap.services) {
141
- lines.push(` ${statusIcon(s.status)} ${s.service} (pip: ${s.pip})`);
141
+ lines.push(` ${statusIcon(s.status)} ${s.service} (plugin: ${s.plugin})`);
142
142
  if (s.issues.length > 0) {
143
143
  lines.push(...renderIssueLines(s.issues, ' '));
144
144
  }
@@ -146,13 +146,13 @@ function renderTextDoctor(snap: DotDiagnosticsSnapshot, status: DotCliEnvelopeSt
146
146
  lines.push('');
147
147
  }
148
148
 
149
- // Routes
150
- if (snap.routes.length > 0) {
151
- lines.push(`Routes (${snap.routes.length})`);
152
- for (const r of snap.routes) {
153
- lines.push(` ${statusIcon(r.status)} ${r.id} (pip: ${r.pip})`);
154
- if (r.issues.length > 0) {
155
- lines.push(...renderIssueLines(r.issues, ' '));
149
+ // Actions
150
+ if (snap.actions.length > 0) {
151
+ lines.push(`Actions (${snap.actions.length})`);
152
+ for (const action of snap.actions) {
153
+ lines.push(` ${statusIcon(action.status)} ${action.binding}:${action.id} (plugin: ${action.plugin})`);
154
+ if (action.issues.length > 0) {
155
+ lines.push(...renderIssueLines(action.issues, ' '));
156
156
  }
157
157
  }
158
158
  lines.push('');
@@ -163,7 +163,7 @@ function renderTextDoctor(snap: DotDiagnosticsSnapshot, status: DotCliEnvelopeSt
163
163
  if (lifeWithIssues.length > 0) {
164
164
  lines.push(`Lifecycle issues (${lifeWithIssues.length})`);
165
165
  for (const l of lifeWithIssues) {
166
- lines.push(` ${l.pip}:${l.hook} (order=${l.order}, state=${l.state})`);
166
+ lines.push(` ${l.plugin}:${l.hook} (order=${l.order}, state=${l.state})`);
167
167
  lines.push(...renderIssueLines(l.issues, ' '));
168
168
  }
169
169
  lines.push('');