@alepha/devtools 0.16.0 → 0.19.1

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 (236) hide show
  1. package/README.md +1 -5
  2. package/dist/index.browser.js +224 -0
  3. package/dist/index.browser.js.map +1 -0
  4. package/dist/index.d.ts +349 -321
  5. package/dist/index.js +293 -186
  6. package/dist/index.js.map +1 -1
  7. package/package.json +30 -23
  8. package/src/assets.ts +6 -0
  9. package/src/{api/entities → entities}/logs.ts +2 -2
  10. package/src/index.browser.ts +11 -0
  11. package/src/index.shared.ts +15 -0
  12. package/src/index.ts +11 -37
  13. package/src/{api/providers → providers}/DevToolsMetadataProvider.ts +84 -47
  14. package/src/providers/DevToolsProvider.ts +280 -0
  15. package/src/{api/schemas → schemas}/DevActionMetadata.ts +8 -0
  16. package/src/{api/schemas → schemas}/DevEntityMetadata.ts +3 -0
  17. package/src/{api/schemas → schemas}/DevMetadata.ts +13 -2
  18. package/src/{api/schemas → schemas}/DevPageMetadata.ts +3 -0
  19. package/src/{api/schemas → schemas}/DevTopicMetadata.ts +1 -0
  20. package/src/ui/AppRouter.tsx +55 -59
  21. package/src/ui/components/DevLayout.tsx +104 -84
  22. package/src/ui/components/configuration/ConfigAtoms.page.tsx +5 -0
  23. package/src/ui/components/configuration/ConfigAtoms.tsx +511 -0
  24. package/src/ui/components/configuration/ConfigEnv.page.tsx +5 -0
  25. package/src/ui/components/configuration/ConfigEnv.tsx +230 -0
  26. package/src/ui/components/configuration/DevConfiguration.tsx +36 -0
  27. package/src/ui/components/configuration/index.ts +3 -0
  28. package/src/ui/components/dashboard/DevDashboard.tsx +482 -0
  29. package/src/ui/components/database/DatabaseEditor.page.tsx +23 -0
  30. package/src/ui/components/database/DatabaseEditor.tsx +399 -0
  31. package/src/ui/components/database/DatabaseErd.page.tsx +28 -0
  32. package/src/ui/components/database/DatabaseErd.tsx +107 -0
  33. package/src/ui/components/database/DevDatabase.tsx +36 -0
  34. package/src/ui/components/database/EntityNode.tsx +83 -0
  35. package/src/ui/components/explorer/DevExplorer.tsx +351 -0
  36. package/src/ui/components/explorer/ExplorerTree.tsx +178 -0
  37. package/src/ui/components/explorer/panels/DevPanelAction.tsx +499 -0
  38. package/src/ui/components/explorer/panels/DevPanelCache.tsx +73 -0
  39. package/src/ui/components/explorer/panels/DevPanelPage.tsx +96 -0
  40. package/src/ui/components/explorer/panels/DevPanelQueue.tsx +51 -0
  41. package/src/ui/components/explorer/panels/DevPanelTopic.tsx +56 -0
  42. package/src/ui/components/explorer/panels/index.ts +5 -0
  43. package/src/ui/components/graph/DevDependencyGraph.tsx +35 -60
  44. package/src/ui/components/graph/GraphControls.tsx +10 -11
  45. package/src/ui/components/graph/NodeDetails.tsx +22 -29
  46. package/src/ui/components/graph/ProviderNode.tsx +4 -4
  47. package/src/ui/components/graph/helpers.ts +1 -1
  48. package/src/ui/components/logs/DevLogs.tsx +661 -0
  49. package/src/ui/components/logs/index.ts +1 -0
  50. package/src/ui/components/shared/TreeView.tsx +189 -0
  51. package/src/ui/main.css +17 -0
  52. package/src/ui/main.ts +2 -6
  53. package/LICENSE +0 -21
  54. package/assets/devtools/actions.html +0 -21
  55. package/assets/devtools/actions.html.br +0 -0
  56. package/assets/devtools/actions.html.gz +0 -0
  57. package/assets/devtools/asset.BJSLFcNT.css +0 -1
  58. package/assets/devtools/asset.BJSLFcNT.css.br +0 -0
  59. package/assets/devtools/asset.BJSLFcNT.css.gz +0 -0
  60. package/assets/devtools/asset.BZV40eAE.css +0 -1
  61. package/assets/devtools/asset.BZV40eAE.css.br +0 -0
  62. package/assets/devtools/asset.BZV40eAE.css.gz +0 -0
  63. package/assets/devtools/atoms.html +0 -21
  64. package/assets/devtools/atoms.html.br +0 -0
  65. package/assets/devtools/atoms.html.gz +0 -0
  66. package/assets/devtools/caches.html +0 -21
  67. package/assets/devtools/caches.html.br +0 -0
  68. package/assets/devtools/caches.html.gz +0 -0
  69. package/assets/devtools/chunk.1h5GuATm.js +0 -1
  70. package/assets/devtools/chunk.1h5GuATm.js.br +0 -0
  71. package/assets/devtools/chunk.1h5GuATm.js.gz +0 -0
  72. package/assets/devtools/chunk.3PgxxOdM.js +0 -1
  73. package/assets/devtools/chunk.3PgxxOdM.js.br +0 -0
  74. package/assets/devtools/chunk.3PgxxOdM.js.gz +0 -0
  75. package/assets/devtools/chunk.A_W3H6Aa.js +0 -1
  76. package/assets/devtools/chunk.A_W3H6Aa.js.br +0 -0
  77. package/assets/devtools/chunk.A_W3H6Aa.js.gz +0 -0
  78. package/assets/devtools/chunk.B5tL0VjH.js +0 -1
  79. package/assets/devtools/chunk.B5tL0VjH.js.br +0 -0
  80. package/assets/devtools/chunk.B5tL0VjH.js.gz +0 -0
  81. package/assets/devtools/chunk.B8p_Szro.js +0 -1
  82. package/assets/devtools/chunk.B8p_Szro.js.br +0 -0
  83. package/assets/devtools/chunk.B8p_Szro.js.gz +0 -0
  84. package/assets/devtools/chunk.BANy8c2v.js +0 -1
  85. package/assets/devtools/chunk.BANy8c2v.js.br +0 -0
  86. package/assets/devtools/chunk.BANy8c2v.js.gz +0 -0
  87. package/assets/devtools/chunk.BKph0hv1.js +0 -1
  88. package/assets/devtools/chunk.BKph0hv1.js.br +0 -0
  89. package/assets/devtools/chunk.BKph0hv1.js.gz +0 -0
  90. package/assets/devtools/chunk.BUs1kuwE.js +0 -1
  91. package/assets/devtools/chunk.BUs1kuwE.js.br +0 -0
  92. package/assets/devtools/chunk.BUs1kuwE.js.gz +0 -0
  93. package/assets/devtools/chunk.BVIEr21R.js +0 -1
  94. package/assets/devtools/chunk.BVIEr21R.js.br +0 -0
  95. package/assets/devtools/chunk.BVIEr21R.js.gz +0 -0
  96. package/assets/devtools/chunk.Bb3re2d8.js +0 -1
  97. package/assets/devtools/chunk.Bb3re2d8.js.br +0 -2
  98. package/assets/devtools/chunk.Bb3re2d8.js.gz +0 -0
  99. package/assets/devtools/chunk.BjFrJKj1.js +0 -1
  100. package/assets/devtools/chunk.BjFrJKj1.js.br +0 -2
  101. package/assets/devtools/chunk.BjFrJKj1.js.gz +0 -0
  102. package/assets/devtools/chunk.BkXzz14p.js +0 -1
  103. package/assets/devtools/chunk.BkXzz14p.js.br +0 -0
  104. package/assets/devtools/chunk.BkXzz14p.js.gz +0 -0
  105. package/assets/devtools/chunk.BlqFPyLh.js +0 -1
  106. package/assets/devtools/chunk.BlqFPyLh.js.br +0 -0
  107. package/assets/devtools/chunk.BlqFPyLh.js.gz +0 -0
  108. package/assets/devtools/chunk.BymZ9jU5.js +0 -1
  109. package/assets/devtools/chunk.BymZ9jU5.js.br +0 -0
  110. package/assets/devtools/chunk.BymZ9jU5.js.gz +0 -0
  111. package/assets/devtools/chunk.C0BD3Ujz.js +0 -1
  112. package/assets/devtools/chunk.C0BD3Ujz.js.br +0 -0
  113. package/assets/devtools/chunk.C0BD3Ujz.js.gz +0 -0
  114. package/assets/devtools/chunk.C63rzhbT.js +0 -1
  115. package/assets/devtools/chunk.C63rzhbT.js.br +0 -0
  116. package/assets/devtools/chunk.C63rzhbT.js.gz +0 -0
  117. package/assets/devtools/chunk.CJrYVzjN.js +0 -9
  118. package/assets/devtools/chunk.CJrYVzjN.js.br +0 -0
  119. package/assets/devtools/chunk.CJrYVzjN.js.gz +0 -0
  120. package/assets/devtools/chunk.CPGX3Xpx.js +0 -1
  121. package/assets/devtools/chunk.CPGX3Xpx.js.br +0 -1
  122. package/assets/devtools/chunk.CPGX3Xpx.js.gz +0 -0
  123. package/assets/devtools/chunk.Cf-3skUw.js +0 -1
  124. package/assets/devtools/chunk.Cf-3skUw.js.br +0 -0
  125. package/assets/devtools/chunk.Cf-3skUw.js.gz +0 -0
  126. package/assets/devtools/chunk.D7JLxcoJ.js +0 -7
  127. package/assets/devtools/chunk.D7JLxcoJ.js.br +0 -0
  128. package/assets/devtools/chunk.D7JLxcoJ.js.gz +0 -0
  129. package/assets/devtools/chunk.D7e5mBY4.js +0 -1
  130. package/assets/devtools/chunk.D7e5mBY4.js.br +0 -0
  131. package/assets/devtools/chunk.D7e5mBY4.js.gz +0 -0
  132. package/assets/devtools/chunk.DClU9Z1_.js +0 -1
  133. package/assets/devtools/chunk.DClU9Z1_.js.br +0 -0
  134. package/assets/devtools/chunk.DClU9Z1_.js.gz +0 -0
  135. package/assets/devtools/chunk.DE_M8b3Z.js +0 -1
  136. package/assets/devtools/chunk.DE_M8b3Z.js.br +0 -0
  137. package/assets/devtools/chunk.DE_M8b3Z.js.gz +0 -0
  138. package/assets/devtools/chunk.DWASJDBE.js +0 -1
  139. package/assets/devtools/chunk.DWASJDBE.js.br +0 -0
  140. package/assets/devtools/chunk.DWASJDBE.js.gz +0 -0
  141. package/assets/devtools/chunk.DfzRLqwW.js +0 -1
  142. package/assets/devtools/chunk.DfzRLqwW.js.br +0 -0
  143. package/assets/devtools/chunk.DfzRLqwW.js.gz +0 -0
  144. package/assets/devtools/chunk.Dww1YQtc.js +0 -1
  145. package/assets/devtools/chunk.Dww1YQtc.js.br +0 -0
  146. package/assets/devtools/chunk.Dww1YQtc.js.gz +0 -0
  147. package/assets/devtools/chunk.HFLdduaf.js +0 -1
  148. package/assets/devtools/chunk.HFLdduaf.js.br +0 -0
  149. package/assets/devtools/chunk.HFLdduaf.js.gz +0 -0
  150. package/assets/devtools/chunk.J-htqECs.js +0 -1
  151. package/assets/devtools/chunk.J-htqECs.js.br +0 -2
  152. package/assets/devtools/chunk.J-htqECs.js.gz +0 -0
  153. package/assets/devtools/chunk.JjTGVewZ.js +0 -2
  154. package/assets/devtools/chunk.JjTGVewZ.js.br +0 -0
  155. package/assets/devtools/chunk.JjTGVewZ.js.gz +0 -0
  156. package/assets/devtools/chunk.OV_89czZ.js +0 -1
  157. package/assets/devtools/chunk.OV_89czZ.js.br +0 -0
  158. package/assets/devtools/chunk.OV_89czZ.js.gz +0 -0
  159. package/assets/devtools/chunk.YFkMUqFM.js +0 -1
  160. package/assets/devtools/chunk.YFkMUqFM.js.br +0 -0
  161. package/assets/devtools/chunk.YFkMUqFM.js.gz +0 -0
  162. package/assets/devtools/chunk._KdUFIrt.js +0 -1
  163. package/assets/devtools/chunk._KdUFIrt.js.br +0 -0
  164. package/assets/devtools/chunk._KdUFIrt.js.gz +0 -0
  165. package/assets/devtools/chunk.pjP6xqG8.js +0 -1
  166. package/assets/devtools/chunk.pjP6xqG8.js.br +0 -0
  167. package/assets/devtools/chunk.pjP6xqG8.js.gz +0 -0
  168. package/assets/devtools/chunk.uTFtY0ae.js +0 -2
  169. package/assets/devtools/chunk.uTFtY0ae.js.br +0 -0
  170. package/assets/devtools/chunk.uTFtY0ae.js.gz +0 -0
  171. package/assets/devtools/chunk.uyVen0u2.js +0 -1
  172. package/assets/devtools/chunk.uyVen0u2.js.br +0 -0
  173. package/assets/devtools/chunk.uyVen0u2.js.gz +0 -0
  174. package/assets/devtools/chunk.vHjNjQS8.js +0 -1
  175. package/assets/devtools/chunk.vHjNjQS8.js.br +0 -0
  176. package/assets/devtools/chunk.vHjNjQS8.js.gz +0 -0
  177. package/assets/devtools/db.html +0 -21
  178. package/assets/devtools/db.html.br +0 -0
  179. package/assets/devtools/db.html.gz +0 -0
  180. package/assets/devtools/entry.DhzNl8q_.js +0 -79
  181. package/assets/devtools/entry.DhzNl8q_.js.br +0 -0
  182. package/assets/devtools/entry.DhzNl8q_.js.gz +0 -0
  183. package/assets/devtools/env.html +0 -21
  184. package/assets/devtools/env.html.br +0 -0
  185. package/assets/devtools/env.html.gz +0 -0
  186. package/assets/devtools/graph.html +0 -22
  187. package/assets/devtools/graph.html.br +0 -0
  188. package/assets/devtools/graph.html.gz +0 -0
  189. package/assets/devtools/index.html +0 -21
  190. package/assets/devtools/index.html.br +0 -0
  191. package/assets/devtools/index.html.gz +0 -0
  192. package/assets/devtools/logs.html +0 -21
  193. package/assets/devtools/logs.html.br +0 -0
  194. package/assets/devtools/logs.html.gz +0 -0
  195. package/assets/devtools/queues.html +0 -21
  196. package/assets/devtools/queues.html.br +0 -0
  197. package/assets/devtools/queues.html.gz +0 -0
  198. package/assets/devtools/topics.html +0 -21
  199. package/assets/devtools/topics.html.br +0 -0
  200. package/assets/devtools/topics.html.gz +0 -0
  201. package/src/api/DevToolsProvider.ts +0 -157
  202. package/src/api/providers/DevToolsDatabaseProvider.ts +0 -27
  203. package/src/api/repositories/LogRepository.ts +0 -8
  204. package/src/api/schemas/DevCommandMetadata.ts +0 -9
  205. package/src/ui/components/DevAtomsViewer.tsx +0 -637
  206. package/src/ui/components/DevCacheInspector.tsx +0 -423
  207. package/src/ui/components/DevDashboard.tsx +0 -38
  208. package/src/ui/components/DevEnvExplorer.tsx +0 -462
  209. package/src/ui/components/DevLogViewer.tsx +0 -252
  210. package/src/ui/components/DevQueueMonitor.tsx +0 -51
  211. package/src/ui/components/DevTopicsViewer.tsx +0 -686
  212. package/src/ui/components/actions/ActionGroup.tsx +0 -37
  213. package/src/ui/components/actions/ActionItem.tsx +0 -138
  214. package/src/ui/components/actions/DevActionsExplorer.tsx +0 -132
  215. package/src/ui/components/actions/MethodBadge.tsx +0 -18
  216. package/src/ui/components/actions/SchemaViewer.tsx +0 -21
  217. package/src/ui/components/actions/TryItPanel.tsx +0 -140
  218. package/src/ui/components/actions/constants.ts +0 -7
  219. package/src/ui/components/actions/helpers.ts +0 -18
  220. package/src/ui/components/actions/index.ts +0 -8
  221. package/src/ui/components/db/ColumnBadge.tsx +0 -55
  222. package/src/ui/components/db/DevDbStudio.tsx +0 -485
  223. package/src/ui/components/db/constants.ts +0 -11
  224. package/src/ui/components/db/index.ts +0 -4
  225. package/src/ui/components/db/types.ts +0 -7
  226. package/src/ui/styles.css +0 -1
  227. /package/src/{api/schemas → schemas}/DevAtomMetadata.ts +0 -0
  228. /package/src/{api/schemas → schemas}/DevBucketMetadata.ts +0 -0
  229. /package/src/{api/schemas → schemas}/DevCacheMetadata.ts +0 -0
  230. /package/src/{api/schemas → schemas}/DevEnvMetadata.ts +0 -0
  231. /package/src/{api/schemas → schemas}/DevModuleMetadata.ts +0 -0
  232. /package/src/{api/schemas → schemas}/DevProviderMetadata.ts +0 -0
  233. /package/src/{api/schemas → schemas}/DevQueueMetadata.ts +0 -0
  234. /package/src/{api/schemas → schemas}/DevRealmMetadata.ts +0 -0
  235. /package/src/{api/schemas → schemas}/DevRouteMetadata.ts +0 -0
  236. /package/src/{api/schemas → schemas}/DevSchedulerMetadata.ts +0 -0
@@ -1,7 +1,7 @@
1
1
  import { $inject, Alepha, type TObject, type TSchema, t } from "alepha";
2
2
  import { $bucket } from "alepha/bucket";
3
3
  import { $cache } from "alepha/cache";
4
- import { $command } from "alepha/command";
4
+ import { DateTimeProvider } from "alepha/datetime";
5
5
  import { $logger } from "alepha/logger";
6
6
  import {
7
7
  PG_CREATED_AT,
@@ -16,15 +16,15 @@ import {
16
16
  RepositoryProvider,
17
17
  } from "alepha/orm";
18
18
  import { $queue } from "alepha/queue";
19
+ import { $page } from "alepha/react/router";
19
20
  import { $scheduler } from "alepha/scheduler";
20
21
  import { $issuer } from "alepha/security";
21
- import { $action } from "alepha/server";
22
+ import { $action, ServerProvider } from "alepha/server";
22
23
  import { $topic } from "alepha/topic";
23
24
  import type { DevActionMetadata } from "../schemas/DevActionMetadata.ts";
24
25
  import type { DevAtomMetadata } from "../schemas/DevAtomMetadata.ts";
25
26
  import type { DevBucketMetadata } from "../schemas/DevBucketMetadata.ts";
26
27
  import type { DevCacheMetadata } from "../schemas/DevCacheMetadata.ts";
27
- import type { DevCommandMetadata } from "../schemas/DevCommandMetadata.ts";
28
28
  import type {
29
29
  DevEntityColumn,
30
30
  DevEntityConstraint,
@@ -33,7 +33,7 @@ import type {
33
33
  DevEntityMetadata,
34
34
  } from "../schemas/DevEntityMetadata.ts";
35
35
  import type { DevEnvMetadata } from "../schemas/DevEnvMetadata.ts";
36
- import type { DevMetadata } from "../schemas/DevMetadata.ts";
36
+ import type { DevMetadata, DevSystem } from "../schemas/DevMetadata.ts";
37
37
  import type { DevModuleMetadata } from "../schemas/DevModuleMetadata.ts";
38
38
  import type { DevPageMetadata } from "../schemas/DevPageMetadata.ts";
39
39
  import type { DevProviderMetadata } from "../schemas/DevProviderMetadata.ts";
@@ -45,7 +45,10 @@ import type { DevTopicMetadata } from "../schemas/DevTopicMetadata.ts";
45
45
 
46
46
  export class DevToolsMetadataProvider {
47
47
  protected readonly alepha = $inject(Alepha);
48
+ protected readonly dateTime = $inject(DateTimeProvider);
48
49
  protected readonly log = $logger();
50
+ protected readonly serverProvider = $inject(ServerProvider);
51
+ protected readonly startedAt = this.dateTime.nowMillis();
49
52
 
50
53
  public getActions(): DevActionMetadata[] {
51
54
  const actionPrimitives = this.alepha.primitives($action);
@@ -64,13 +67,17 @@ export class DevToolsMetadataProvider {
64
67
  description: action.options.description,
65
68
  summary: options.summary,
66
69
  disabled: action.options.disabled,
67
- secure: options.secure,
70
+ secure:
71
+ action.middlewares.some((m) => m?.name === "$secure") || undefined,
68
72
  hide: options.hide,
69
73
  body: schema?.body,
70
74
  params: schema?.params,
71
75
  query: schema?.query,
72
76
  response: schema?.response,
73
77
  bodyContentType: action.getBodyContentType(),
78
+ middlewares: action.middlewares.filter(Boolean).length
79
+ ? action.middlewares.filter(Boolean)
80
+ : undefined,
74
81
  };
75
82
  });
76
83
  }
@@ -101,12 +108,23 @@ export class DevToolsMetadataProvider {
101
108
  public getTopics(): DevTopicMetadata[] {
102
109
  const topicPrimitives = this.alepha.primitives($topic);
103
110
 
104
- return topicPrimitives.map((topic) => ({
105
- name: topic.name,
106
- description: topic.options.description,
107
- schema: topic.options.schema,
108
- provider: this.getProviderName(topic.options.provider),
109
- }));
111
+ // Deduplicate by name, count subscribers
112
+ const topicMap = new Map<string, DevTopicMetadata>();
113
+ for (const topic of topicPrimitives) {
114
+ const existing = topicMap.get(topic.name);
115
+ if (existing) {
116
+ existing.subscribers++;
117
+ } else {
118
+ topicMap.set(topic.name, {
119
+ name: topic.name,
120
+ description: topic.options.description,
121
+ schema: topic.options.schema,
122
+ provider: this.getProviderName(topic.options.provider),
123
+ subscribers: 1,
124
+ });
125
+ }
126
+ }
127
+ return Array.from(topicMap.values());
110
128
  }
111
129
 
112
130
  public getBuckets(): DevBucketMetadata[] {
@@ -152,30 +170,42 @@ export class DevToolsMetadataProvider {
152
170
  }
153
171
 
154
172
  public getPages(): DevPageMetadata[] {
155
- // const pagePrimitives = this.alepha.primitives($page);
156
- //
157
- // return pagePrimitives.map((page) => ({
158
- // name: page.name,
159
- // description: page.options.description,
160
- // path: page.options.path,
161
- // params: page.options.schema?.params,
162
- // query: page.options.schema?.query,
163
- // hasComponent: !!page.options.component,
164
- // hasLazy: !!page.options.lazy,
165
- // hasResolve: !!page.options.resolve,
166
- // hasChildren: !!page.options.children,
167
- // hasParent: !!page.options.parent,
168
- // hasErrorHandler: !!page.options.errorHandler,
169
- // static:
170
- // typeof page.options.static === "boolean"
171
- // ? page.options.static
172
- // : !!page.options.static,
173
- // cache: page.options.cache,
174
- // client: page.options.client,
175
- // animation: page.options.animation,
176
- // }));
173
+ const pagePrimitives = this.alepha.primitives($page);
174
+
175
+ return pagePrimitives.map((page: any) => {
176
+ // Resolve children (can be an array or a function returning an array)
177
+ const children =
178
+ typeof page.options.children === "function"
179
+ ? page.options.children()
180
+ : page.options.children;
181
+ const childrenNames = Array.isArray(children)
182
+ ? children.map((c: any) => c.name).filter(Boolean)
183
+ : undefined;
177
184
 
178
- return [];
185
+ return {
186
+ name: page.name,
187
+ label: page.options.label,
188
+ description: page.options.description,
189
+ path: page.options.path,
190
+ parentName: page.options.parent?.name,
191
+ params: page.options.schema?.params,
192
+ query: page.options.schema?.query,
193
+ hasComponent: !!page.options.component,
194
+ hasLazy: !!page.options.lazy,
195
+ hasResolve: !!page.options.resolve,
196
+ childrenNames: childrenNames?.length ? childrenNames : undefined,
197
+ hasChildren: !!page.options.children,
198
+ hasParent: !!page.options.parent,
199
+ hasErrorHandler: !!page.options.errorHandler,
200
+ static:
201
+ typeof page.options.static === "boolean"
202
+ ? page.options.static
203
+ : !!page.options.static,
204
+ cache: page.options.cache,
205
+ client: page.options.client,
206
+ animation: page.options.animation,
207
+ };
208
+ });
179
209
  }
180
210
 
181
211
  public getProviders(): DevProviderMetadata[] {
@@ -291,6 +321,9 @@ export class DevToolsMetadataProvider {
291
321
  indexes,
292
322
  foreignKeys,
293
323
  constraints,
324
+ schema: entity.schema,
325
+ insertSchema: entity.insertSchema,
326
+ updateSchema: entity.updateSchema,
294
327
  };
295
328
  });
296
329
  } catch {
@@ -347,18 +380,6 @@ export class DevToolsMetadataProvider {
347
380
  return false;
348
381
  }
349
382
 
350
- public getCommands(): DevCommandMetadata[] {
351
- const commandPrimitives = this.alepha.primitives($command);
352
-
353
- return commandPrimitives
354
- .filter((cmd) => !cmd.options.hide)
355
- .map((command) => ({
356
- name: command.name,
357
- description: command.options.description,
358
- hidden: command.options.hide,
359
- }));
360
- }
361
-
362
383
  public getRoutes(): DevRouteMetadata[] {
363
384
  // Routes are the base primitive - actions and pages are routes
364
385
  // Showing them separately would be redundant with actions
@@ -388,8 +409,25 @@ export class DevToolsMetadataProvider {
388
409
  }));
389
410
  }
390
411
 
412
+ public getSystem(): DevSystem {
413
+ const isBun = typeof globalThis.Bun !== "undefined";
414
+ const port = Number(this.alepha.env.SERVER_PORT) || 3000;
415
+ return {
416
+ alephaVersion: String(this.alepha.env.npm_package_version ?? "unknown"),
417
+ nodeVersion: isBun
418
+ ? (globalThis.Bun?.version ?? "unknown")
419
+ : process.version,
420
+ runtime: isBun ? "bun" : "node",
421
+ mode: this.alepha.isProduction() ? "production" : "development",
422
+ port,
423
+ uptime: (this.dateTime.nowMillis() - this.startedAt) / 1000,
424
+ memoryUsage: process.memoryUsage?.()?.rss ?? 0,
425
+ };
426
+ }
427
+
391
428
  public getMetadata(): DevMetadata {
392
429
  return {
430
+ system: this.getSystem(),
393
431
  actions: this.getActions(),
394
432
  queues: this.getQueues(),
395
433
  schedulers: this.getSchedulers(),
@@ -401,7 +439,6 @@ export class DevToolsMetadataProvider {
401
439
  providers: this.getProviders(),
402
440
  modules: this.getModules(),
403
441
  entities: this.getEntities(),
404
- commands: this.getCommands(),
405
442
  routes: this.getRoutes(),
406
443
  envs: this.getEnvs(),
407
444
  atoms: this.getAtoms(),
@@ -0,0 +1,280 @@
1
+ import { $hook, $inject, Alepha, t } from "alepha";
2
+ import { $logger, MemoryDestinationProvider } from "alepha/logger";
3
+ import { RepositoryProvider } from "alepha/orm";
4
+ import { $route, ServerProvider } from "alepha/server";
5
+ import { $serve } from "alepha/server/static";
6
+ import { devtoolsAssets } from "../assets.ts";
7
+ import { devMetadataSchema } from "../schemas/DevMetadata.ts";
8
+ import { DevToolsMetadataProvider } from "./DevToolsMetadataProvider.ts";
9
+
10
+ export class DevToolsProvider {
11
+ protected readonly log = $logger();
12
+ protected readonly alepha = $inject(Alepha);
13
+ protected readonly serverProvider = $inject(ServerProvider);
14
+ protected readonly devCollectorProvider = $inject(DevToolsMetadataProvider);
15
+ protected readonly memoryDestination = $inject(MemoryDestinationProvider);
16
+
17
+ protected readonly onStart = $hook({
18
+ on: "start",
19
+ handler: () => {
20
+ this.log.info("Devtools OK", {
21
+ url: `${this.serverProvider.hostname}/__devtools/`,
22
+ });
23
+ },
24
+ });
25
+
26
+ /**
27
+ * Capture all logs into memory so the devtools UI can display them.
28
+ * In dev mode, LogDestinationProvider is bound to ConsoleDestinationProvider,
29
+ * so MemoryDestinationProvider would otherwise never receive writes.
30
+ */
31
+ protected readonly onLog = $hook({
32
+ on: "log",
33
+ handler: ({ entry }) => {
34
+ this.memoryDestination.write("", entry);
35
+ },
36
+ });
37
+
38
+ protected readonly uiRoute = $serve({
39
+ path: "/__devtools",
40
+ root: devtoolsAssets.ui,
41
+ historyApiFallback: true,
42
+ silent: true,
43
+ });
44
+
45
+ protected readonly metadataRoute = $route({
46
+ method: "GET",
47
+ path: "/__devtools/api/metadata",
48
+ silent: true,
49
+ schema: {
50
+ response: devMetadataSchema,
51
+ },
52
+ handler: () => {
53
+ return this.devCollectorProvider.getMetadata();
54
+ },
55
+ });
56
+
57
+ protected readonly updateAtomRoute = $route({
58
+ method: "POST",
59
+ path: "/__devtools/api/atoms",
60
+ silent: true,
61
+ schema: {
62
+ body: t.object({
63
+ name: t.text(),
64
+ value: t.any(),
65
+ }),
66
+ response: t.object({
67
+ success: t.boolean(),
68
+ }),
69
+ },
70
+ handler: ({ body }) => {
71
+ const atoms = this.alepha.store.getAtoms(false);
72
+ const atomEntry = atoms.find((a) => a.atom.key === body.name);
73
+
74
+ if (atomEntry) {
75
+ this.alepha.store.set(atomEntry.atom, body.value);
76
+ return { success: true };
77
+ }
78
+
79
+ return { success: false };
80
+ },
81
+ });
82
+
83
+ // -------------------------------------------------------------------------------------------------------------------
84
+ // Logs endpoint
85
+ // -------------------------------------------------------------------------------------------------------------------
86
+
87
+ protected readonly logsRoute = $route({
88
+ method: "GET",
89
+ path: "/__devtools/api/logs",
90
+ silent: true,
91
+ schema: {
92
+ query: t.object({
93
+ level: t.optional(t.text()),
94
+ type: t.optional(t.text()),
95
+ module: t.optional(t.text()),
96
+ search: t.optional(t.text()),
97
+ since: t.optional(t.text()),
98
+ limit: t.optional(t.text()),
99
+ offset: t.optional(t.text()),
100
+ }),
101
+ response: t.object({
102
+ logs: t.array(t.any()),
103
+ total: t.integer(),
104
+ }),
105
+ },
106
+ handler: ({ query }) => {
107
+ const levelOrder = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR"];
108
+ let entries = this.memoryDestination.logs;
109
+
110
+ if (query.level) {
111
+ const minIndex = levelOrder.indexOf(query.level.toUpperCase());
112
+ if (minIndex >= 0) {
113
+ entries = entries.filter(
114
+ (e) => levelOrder.indexOf(e.level) >= minIndex,
115
+ );
116
+ }
117
+ }
118
+
119
+ if (query.type) {
120
+ const types = query.type.split(",").map((t) => t.trim());
121
+ entries = entries.filter((e) => {
122
+ if (!e.data || typeof e.data !== "object") return false;
123
+ const d = e.data as Record<string, unknown>;
124
+ for (const t of types) {
125
+ if (t === "http" || t === "http:request") {
126
+ if (d.status && d.method && d.path && d.duration) return true;
127
+ } else if (t === "db" || t === "db:query") {
128
+ if (d.type === "db:query") return true;
129
+ } else if (d.type === t) {
130
+ return true;
131
+ }
132
+ }
133
+ return false;
134
+ });
135
+ }
136
+
137
+ if (query.module) {
138
+ entries = entries.filter((e) => e.module === query.module);
139
+ }
140
+
141
+ if (query.search) {
142
+ const terms = query.search.toLowerCase().split(/\s+/);
143
+ entries = entries.filter((e) => {
144
+ const text = `${e.message} ${e.module} ${e.service}`.toLowerCase();
145
+ return terms.every((term) => text.includes(term));
146
+ });
147
+ }
148
+
149
+ if (query.since) {
150
+ const since = Number(query.since);
151
+ if (!Number.isNaN(since)) {
152
+ entries = entries.filter((e) => e.timestamp >= since);
153
+ }
154
+ }
155
+
156
+ const total = entries.length;
157
+
158
+ // Reverse so newest first
159
+ entries = entries.reverse();
160
+
161
+ const offset = query.offset ? Number(query.offset) : 0;
162
+ const limit = query.limit ? Number(query.limit) : 100;
163
+ entries = entries.slice(offset, offset + limit);
164
+
165
+ return { logs: entries, total };
166
+ },
167
+ });
168
+
169
+ // -------------------------------------------------------------------------------------------------------------------
170
+ // DB CRUD endpoints
171
+ // -------------------------------------------------------------------------------------------------------------------
172
+
173
+ protected readonly dbListRoute = $route({
174
+ method: "GET",
175
+ path: "/__devtools/api/db/:entity/records",
176
+ silent: true,
177
+ schema: {
178
+ params: t.object({ entity: t.text() }),
179
+ query: t.object({
180
+ page: t.optional(t.text()),
181
+ size: t.optional(t.text()),
182
+ sort: t.optional(t.text()),
183
+ }),
184
+ response: t.record(t.text(), t.any()),
185
+ },
186
+ handler: async ({ params, query }) => {
187
+ const repo = this.getRepository(params.entity);
188
+ if (!repo) {
189
+ return { content: [], page: { totalElements: 0 } };
190
+ }
191
+
192
+ return repo.paginate(
193
+ {
194
+ page: query.page ? Number(query.page) : 0,
195
+ size: query.size ? Number(query.size) : 50,
196
+ sort: query.sort || undefined,
197
+ },
198
+ {},
199
+ { count: true },
200
+ ) as any;
201
+ },
202
+ });
203
+
204
+ protected readonly dbCreateRoute = $route({
205
+ method: "POST",
206
+ path: "/__devtools/api/db/:entity/records",
207
+ silent: true,
208
+ schema: {
209
+ params: t.object({ entity: t.text() }),
210
+ body: t.record(t.text(), t.any()),
211
+ response: t.record(t.text(), t.any()),
212
+ },
213
+ handler: async ({ params, body }) => {
214
+ const repo = this.getRepository(params.entity);
215
+ if (!repo) {
216
+ return { error: "Entity not found" };
217
+ }
218
+ return repo.create(body) as any;
219
+ },
220
+ });
221
+
222
+ protected readonly dbUpdateRoute = $route({
223
+ method: "PUT",
224
+ path: "/__devtools/api/db/:entity/records/:id",
225
+ silent: true,
226
+ schema: {
227
+ params: t.object({ entity: t.text(), id: t.text() }),
228
+ body: t.record(t.text(), t.any()),
229
+ response: t.record(t.text(), t.any()),
230
+ },
231
+ handler: async ({ params, body }) => {
232
+ const repo = this.getRepository(params.entity);
233
+ if (!repo) {
234
+ return { error: "Entity not found" };
235
+ }
236
+
237
+ const idValue = this.parseId(repo, params.id);
238
+ return repo.updateById(idValue, body) as any;
239
+ },
240
+ });
241
+
242
+ protected readonly dbDeleteRoute = $route({
243
+ method: "DELETE",
244
+ path: "/__devtools/api/db/:entity/records/:id",
245
+ silent: true,
246
+ schema: {
247
+ params: t.object({ entity: t.text(), id: t.text() }),
248
+ response: t.record(t.text(), t.any()),
249
+ },
250
+ handler: async ({ params }) => {
251
+ const repo = this.getRepository(params.entity);
252
+ if (!repo) {
253
+ return { error: "Entity not found" };
254
+ }
255
+
256
+ const idValue = this.parseId(repo, params.id);
257
+ return repo.deleteById(idValue) as any;
258
+ },
259
+ });
260
+
261
+ // -------------------------------------------------------------------------------------------------------------------
262
+
263
+ protected getRepository(entityName: string) {
264
+ try {
265
+ const repositoryProvider = this.alepha.inject(RepositoryProvider);
266
+ const repos = repositoryProvider.getRepositories();
267
+ return repos.find((r) => r.entity.name === entityName);
268
+ } catch {
269
+ return undefined;
270
+ }
271
+ }
272
+
273
+ protected parseId(repo: any, rawId: string): string | number {
274
+ const idType = repo.id.type;
275
+ if (idType?.type === "integer" || idType?.type === "number") {
276
+ return Number(rawId);
277
+ }
278
+ return rawId;
279
+ }
280
+ }
@@ -17,6 +17,14 @@ export const devActionMetadataSchema = t.object({
17
17
  query: t.optional(t.any()),
18
18
  response: t.optional(t.any()),
19
19
  bodyContentType: t.optional(t.text()),
20
+ middlewares: t.optional(
21
+ t.array(
22
+ t.object({
23
+ name: t.text(),
24
+ options: t.optional(t.any()),
25
+ }),
26
+ ),
27
+ ),
20
28
  });
21
29
 
22
30
  export type DevActionMetadata = Static<typeof devActionMetadataSchema>;
@@ -48,6 +48,9 @@ export const devEntityMetadataSchema = t.object({
48
48
  indexes: t.array(devEntityIndexSchema),
49
49
  foreignKeys: t.array(devEntityForeignKeySchema),
50
50
  constraints: t.array(devEntityConstraintSchema),
51
+ schema: t.optional(t.any()),
52
+ insertSchema: t.optional(t.any()),
53
+ updateSchema: t.optional(t.any()),
51
54
  });
52
55
 
53
56
  export type DevEntityColumn = Static<typeof devEntityColumnSchema>;
@@ -3,7 +3,6 @@ import { devActionMetadataSchema } from "./DevActionMetadata.ts";
3
3
  import { devAtomMetadataSchema } from "./DevAtomMetadata.ts";
4
4
  import { devBucketMetadataSchema } from "./DevBucketMetadata.ts";
5
5
  import { devCacheMetadataSchema } from "./DevCacheMetadata.ts";
6
- import { devCommandMetadataSchema } from "./DevCommandMetadata.ts";
7
6
  import { devEntityMetadataSchema } from "./DevEntityMetadata.ts";
8
7
  import { devEnvMetadataSchema } from "./DevEnvMetadata.ts";
9
8
  import { devModuleMetadataSchema } from "./DevModuleMetadata.ts";
@@ -15,7 +14,20 @@ import { devRouteMetadataSchema } from "./DevRouteMetadata.ts";
15
14
  import { devSchedulerMetadataSchema } from "./DevSchedulerMetadata.ts";
16
15
  import { devTopicMetadataSchema } from "./DevTopicMetadata.ts";
17
16
 
17
+ export const devSystemSchema = t.object({
18
+ alephaVersion: t.text(),
19
+ nodeVersion: t.text(),
20
+ runtime: t.enum(["node", "bun"]),
21
+ mode: t.enum(["development", "production"]),
22
+ port: t.integer(),
23
+ uptime: t.number(),
24
+ memoryUsage: t.number(),
25
+ });
26
+
27
+ export type DevSystem = Static<typeof devSystemSchema>;
28
+
18
29
  export const devMetadataSchema = t.object({
30
+ system: devSystemSchema,
19
31
  actions: t.array(devActionMetadataSchema),
20
32
  queues: t.array(devQueueMetadataSchema),
21
33
  schedulers: t.array(devSchedulerMetadataSchema),
@@ -27,7 +39,6 @@ export const devMetadataSchema = t.object({
27
39
  providers: t.array(devProviderMetadataSchema),
28
40
  modules: t.array(devModuleMetadataSchema),
29
41
  entities: t.array(devEntityMetadataSchema),
30
- commands: t.array(devCommandMetadataSchema),
31
42
  routes: t.array(devRouteMetadataSchema),
32
43
  envs: t.array(devEnvMetadataSchema),
33
44
  atoms: t.array(devAtomMetadataSchema),
@@ -2,13 +2,16 @@ import { type Static, t } from "alepha";
2
2
 
3
3
  export const devPageMetadataSchema = t.object({
4
4
  name: t.text(),
5
+ label: t.optional(t.text()),
5
6
  description: t.optional(t.text()),
6
7
  path: t.optional(t.text()),
8
+ parentName: t.optional(t.text()),
7
9
  params: t.optional(t.any()),
8
10
  query: t.optional(t.any()),
9
11
  hasComponent: t.boolean(),
10
12
  hasLazy: t.boolean(),
11
13
  hasResolve: t.boolean(),
14
+ childrenNames: t.optional(t.array(t.text())),
12
15
  hasChildren: t.boolean(),
13
16
  hasParent: t.boolean(),
14
17
  hasErrorHandler: t.boolean(),
@@ -5,6 +5,7 @@ export const devTopicMetadataSchema = t.object({
5
5
  description: t.optional(t.text()),
6
6
  schema: t.optional(t.any()),
7
7
  provider: t.text(),
8
+ subscribers: t.integer(),
8
9
  });
9
10
 
10
11
  export type DevTopicMetadata = Static<typeof devTopicMetadataSchema>;