@portel/photon 1.9.0 → 1.11.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 (294) hide show
  1. package/README.md +163 -210
  2. package/dist/async/dedup-map.d.ts +40 -0
  3. package/dist/async/dedup-map.d.ts.map +1 -0
  4. package/dist/async/dedup-map.js +80 -0
  5. package/dist/async/dedup-map.js.map +1 -0
  6. package/dist/async/index.d.ts +11 -0
  7. package/dist/async/index.d.ts.map +1 -0
  8. package/dist/async/index.js +11 -0
  9. package/dist/async/index.js.map +1 -0
  10. package/dist/async/loading-gate.d.ts +27 -0
  11. package/dist/async/loading-gate.d.ts.map +1 -0
  12. package/dist/async/loading-gate.js +48 -0
  13. package/dist/async/loading-gate.js.map +1 -0
  14. package/dist/async/with-timeout.d.ts +6 -0
  15. package/dist/async/with-timeout.d.ts.map +1 -0
  16. package/dist/async/with-timeout.js +17 -0
  17. package/dist/async/with-timeout.js.map +1 -0
  18. package/dist/auto-ui/beam/class-metadata.d.ts +52 -0
  19. package/dist/auto-ui/beam/class-metadata.d.ts.map +1 -0
  20. package/dist/auto-ui/beam/class-metadata.js +133 -0
  21. package/dist/auto-ui/beam/class-metadata.js.map +1 -0
  22. package/dist/auto-ui/beam/config.d.ts +13 -0
  23. package/dist/auto-ui/beam/config.d.ts.map +1 -0
  24. package/dist/auto-ui/beam/config.js +52 -0
  25. package/dist/auto-ui/beam/config.js.map +1 -0
  26. package/dist/auto-ui/beam/external-mcp.d.ts +37 -0
  27. package/dist/auto-ui/beam/external-mcp.d.ts.map +1 -0
  28. package/dist/auto-ui/beam/external-mcp.js +311 -0
  29. package/dist/auto-ui/beam/external-mcp.js.map +1 -0
  30. package/dist/auto-ui/beam/photon-management.d.ts +51 -0
  31. package/dist/auto-ui/beam/photon-management.d.ts.map +1 -0
  32. package/dist/auto-ui/beam/photon-management.js +310 -0
  33. package/dist/auto-ui/beam/photon-management.js.map +1 -0
  34. package/dist/auto-ui/beam/routes/api-browse.d.ts +17 -0
  35. package/dist/auto-ui/beam/routes/api-browse.d.ts.map +1 -0
  36. package/dist/auto-ui/beam/routes/api-browse.js +531 -0
  37. package/dist/auto-ui/beam/routes/api-browse.js.map +1 -0
  38. package/dist/auto-ui/beam/routes/api-config.d.ts +9 -0
  39. package/dist/auto-ui/beam/routes/api-config.d.ts.map +1 -0
  40. package/dist/auto-ui/beam/routes/api-config.js +494 -0
  41. package/dist/auto-ui/beam/routes/api-config.js.map +1 -0
  42. package/dist/auto-ui/beam/routes/api-marketplace.d.ts +8 -0
  43. package/dist/auto-ui/beam/routes/api-marketplace.d.ts.map +1 -0
  44. package/dist/auto-ui/beam/routes/api-marketplace.js +490 -0
  45. package/dist/auto-ui/beam/routes/api-marketplace.js.map +1 -0
  46. package/dist/auto-ui/beam/startup.d.ts +41 -0
  47. package/dist/auto-ui/beam/startup.d.ts.map +1 -0
  48. package/dist/auto-ui/beam/startup.js +98 -0
  49. package/dist/auto-ui/beam/startup.js.map +1 -0
  50. package/dist/auto-ui/beam/subscription.d.ts +35 -0
  51. package/dist/auto-ui/beam/subscription.d.ts.map +1 -0
  52. package/dist/auto-ui/beam/subscription.js +151 -0
  53. package/dist/auto-ui/beam/subscription.js.map +1 -0
  54. package/dist/auto-ui/beam/types.d.ts +103 -0
  55. package/dist/auto-ui/beam/types.d.ts.map +1 -0
  56. package/dist/auto-ui/beam/types.js +8 -0
  57. package/dist/auto-ui/beam/types.js.map +1 -0
  58. package/dist/auto-ui/beam.d.ts +2 -0
  59. package/dist/auto-ui/beam.d.ts.map +1 -1
  60. package/dist/auto-ui/beam.js +729 -2596
  61. package/dist/auto-ui/beam.js.map +1 -1
  62. package/dist/auto-ui/bridge/index.d.ts.map +1 -1
  63. package/dist/auto-ui/bridge/index.js +10 -2
  64. package/dist/auto-ui/bridge/index.js.map +1 -1
  65. package/dist/auto-ui/components/card.d.ts.map +1 -1
  66. package/dist/auto-ui/components/card.js +3 -1
  67. package/dist/auto-ui/components/card.js.map +1 -1
  68. package/dist/auto-ui/components/progress.d.ts.map +1 -1
  69. package/dist/auto-ui/components/progress.js.map +1 -1
  70. package/dist/auto-ui/daemon-tools.d.ts +1 -1
  71. package/dist/auto-ui/daemon-tools.d.ts.map +1 -1
  72. package/dist/auto-ui/daemon-tools.js +4 -3
  73. package/dist/auto-ui/daemon-tools.js.map +1 -1
  74. package/dist/auto-ui/photon-bridge.d.ts +6 -2
  75. package/dist/auto-ui/photon-bridge.d.ts.map +1 -1
  76. package/dist/auto-ui/photon-bridge.js +20 -8
  77. package/dist/auto-ui/photon-bridge.js.map +1 -1
  78. package/dist/auto-ui/platform-compat.d.ts.map +1 -1
  79. package/dist/auto-ui/platform-compat.js +4 -0
  80. package/dist/auto-ui/platform-compat.js.map +1 -1
  81. package/dist/auto-ui/streamable-http-transport.d.ts +4 -2
  82. package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
  83. package/dist/auto-ui/streamable-http-transport.js +120 -30
  84. package/dist/auto-ui/streamable-http-transport.js.map +1 -1
  85. package/dist/auto-ui/types.d.ts +4 -2
  86. package/dist/auto-ui/types.d.ts.map +1 -1
  87. package/dist/auto-ui/types.js.map +1 -1
  88. package/dist/beam.bundle.js +8235 -4002
  89. package/dist/beam.bundle.js.map +4 -4
  90. package/dist/cli/commands/alias.d.ts +14 -0
  91. package/dist/cli/commands/alias.d.ts.map +1 -0
  92. package/dist/cli/commands/alias.js +41 -0
  93. package/dist/cli/commands/alias.js.map +1 -0
  94. package/dist/cli/commands/audit.d.ts +9 -0
  95. package/dist/cli/commands/audit.d.ts.map +1 -0
  96. package/dist/cli/commands/audit.js +377 -0
  97. package/dist/cli/commands/audit.js.map +1 -0
  98. package/dist/cli/commands/beam.d.ts +20 -0
  99. package/dist/cli/commands/beam.d.ts.map +1 -0
  100. package/dist/cli/commands/beam.js +256 -0
  101. package/dist/cli/commands/beam.js.map +1 -0
  102. package/dist/cli/commands/config.d.ts +14 -0
  103. package/dist/cli/commands/config.d.ts.map +1 -0
  104. package/dist/cli/commands/config.js +165 -0
  105. package/dist/cli/commands/config.js.map +1 -0
  106. package/dist/cli/commands/daemon.d.ts +11 -0
  107. package/dist/cli/commands/daemon.d.ts.map +1 -0
  108. package/dist/cli/commands/daemon.js +108 -0
  109. package/dist/cli/commands/daemon.js.map +1 -0
  110. package/dist/cli/commands/doctor.d.ts +14 -0
  111. package/dist/cli/commands/doctor.d.ts.map +1 -0
  112. package/dist/cli/commands/doctor.js +257 -0
  113. package/dist/cli/commands/doctor.js.map +1 -0
  114. package/dist/cli/commands/host.d.ts +11 -0
  115. package/dist/cli/commands/host.d.ts.map +1 -0
  116. package/dist/cli/commands/host.js +96 -0
  117. package/dist/cli/commands/host.js.map +1 -0
  118. package/dist/cli/commands/info.d.ts +1 -1
  119. package/dist/cli/commands/info.d.ts.map +1 -1
  120. package/dist/cli/commands/info.js +16 -15
  121. package/dist/cli/commands/info.js.map +1 -1
  122. package/dist/cli/commands/init.d.ts +20 -0
  123. package/dist/cli/commands/init.d.ts.map +1 -0
  124. package/dist/cli/commands/init.js +774 -0
  125. package/dist/cli/commands/init.js.map +1 -0
  126. package/dist/cli/commands/maker.d.ts +12 -0
  127. package/dist/cli/commands/maker.d.ts.map +1 -0
  128. package/dist/cli/commands/maker.js +605 -0
  129. package/dist/cli/commands/maker.js.map +1 -0
  130. package/dist/cli/commands/mcp.d.ts +27 -0
  131. package/dist/cli/commands/mcp.d.ts.map +1 -0
  132. package/dist/cli/commands/mcp.js +390 -0
  133. package/dist/cli/commands/mcp.js.map +1 -0
  134. package/dist/cli/commands/package-app.d.ts +1 -1
  135. package/dist/cli/commands/package-app.d.ts.map +1 -1
  136. package/dist/cli/commands/package-app.js +5 -4
  137. package/dist/cli/commands/package-app.js.map +1 -1
  138. package/dist/cli/commands/package.d.ts +1 -1
  139. package/dist/cli/commands/package.d.ts.map +1 -1
  140. package/dist/cli/commands/package.js +134 -32
  141. package/dist/cli/commands/package.js.map +1 -1
  142. package/dist/cli/commands/run.d.ts +34 -0
  143. package/dist/cli/commands/run.d.ts.map +1 -0
  144. package/dist/cli/commands/run.js +334 -0
  145. package/dist/cli/commands/run.js.map +1 -0
  146. package/dist/cli/commands/search.d.ts +11 -0
  147. package/dist/cli/commands/search.d.ts.map +1 -0
  148. package/dist/cli/commands/search.js +60 -0
  149. package/dist/cli/commands/search.js.map +1 -0
  150. package/dist/cli/commands/serve.d.ts +11 -0
  151. package/dist/cli/commands/serve.d.ts.map +1 -0
  152. package/dist/cli/commands/serve.js +138 -0
  153. package/dist/cli/commands/serve.js.map +1 -0
  154. package/dist/cli/commands/test.d.ts +14 -0
  155. package/dist/cli/commands/test.d.ts.map +1 -0
  156. package/dist/cli/commands/test.js +51 -0
  157. package/dist/cli/commands/test.js.map +1 -0
  158. package/dist/cli/commands/update.d.ts +11 -0
  159. package/dist/cli/commands/update.d.ts.map +1 -0
  160. package/dist/cli/commands/update.js +72 -0
  161. package/dist/cli/commands/update.js.map +1 -0
  162. package/dist/cli/index.d.ts +14 -0
  163. package/dist/cli/index.d.ts.map +1 -0
  164. package/dist/cli/index.js +139 -0
  165. package/dist/cli/index.js.map +1 -0
  166. package/dist/cli-alias.js +2 -2
  167. package/dist/cli-alias.js.map +1 -1
  168. package/dist/cli.d.ts +3 -16
  169. package/dist/cli.d.ts.map +1 -1
  170. package/dist/cli.js +4 -2725
  171. package/dist/cli.js.map +1 -1
  172. package/dist/context-store.d.ts +13 -12
  173. package/dist/context-store.d.ts.map +1 -1
  174. package/dist/context-store.js +47 -23
  175. package/dist/context-store.js.map +1 -1
  176. package/dist/context.d.ts +35 -0
  177. package/dist/context.d.ts.map +1 -0
  178. package/dist/context.js +38 -0
  179. package/dist/context.js.map +1 -0
  180. package/dist/daemon/client.d.ts +25 -13
  181. package/dist/daemon/client.d.ts.map +1 -1
  182. package/dist/daemon/client.js +183 -135
  183. package/dist/daemon/client.js.map +1 -1
  184. package/dist/daemon/manager.d.ts +58 -26
  185. package/dist/daemon/manager.d.ts.map +1 -1
  186. package/dist/daemon/manager.js +348 -157
  187. package/dist/daemon/manager.js.map +1 -1
  188. package/dist/daemon/protocol.d.ts +9 -3
  189. package/dist/daemon/protocol.d.ts.map +1 -1
  190. package/dist/daemon/protocol.js +2 -0
  191. package/dist/daemon/protocol.js.map +1 -1
  192. package/dist/daemon/server.js +850 -200
  193. package/dist/daemon/server.js.map +1 -1
  194. package/dist/daemon/session-manager.d.ts +16 -2
  195. package/dist/daemon/session-manager.d.ts.map +1 -1
  196. package/dist/daemon/session-manager.js +65 -7
  197. package/dist/daemon/session-manager.js.map +1 -1
  198. package/dist/daemon/state-machine.d.ts +22 -0
  199. package/dist/daemon/state-machine.d.ts.map +1 -0
  200. package/dist/daemon/state-machine.js +48 -0
  201. package/dist/daemon/state-machine.js.map +1 -0
  202. package/dist/deploy/cloudflare.d.ts.map +1 -1
  203. package/dist/deploy/cloudflare.js +5 -5
  204. package/dist/deploy/cloudflare.js.map +1 -1
  205. package/dist/loader.d.ts +82 -7
  206. package/dist/loader.d.ts.map +1 -1
  207. package/dist/loader.js +679 -63
  208. package/dist/loader.js.map +1 -1
  209. package/dist/marketplace-manager.d.ts +84 -12
  210. package/dist/marketplace-manager.d.ts.map +1 -1
  211. package/dist/marketplace-manager.js +470 -26
  212. package/dist/marketplace-manager.js.map +1 -1
  213. package/dist/path-resolver.d.ts +3 -1
  214. package/dist/path-resolver.d.ts.map +1 -1
  215. package/dist/path-resolver.js +4 -3
  216. package/dist/path-resolver.js.map +1 -1
  217. package/dist/photon-cli-runner.d.ts +1 -1
  218. package/dist/photon-cli-runner.d.ts.map +1 -1
  219. package/dist/photon-cli-runner.js +34 -44
  220. package/dist/photon-cli-runner.js.map +1 -1
  221. package/dist/photon-doc-extractor.d.ts +1 -0
  222. package/dist/photon-doc-extractor.d.ts.map +1 -1
  223. package/dist/photon-doc-extractor.js +33 -12
  224. package/dist/photon-doc-extractor.js.map +1 -1
  225. package/dist/photons/maker.photon.d.ts.map +1 -1
  226. package/dist/photons/maker.photon.js +4 -4
  227. package/dist/photons/maker.photon.js.map +1 -1
  228. package/dist/photons/maker.photon.ts +4 -3
  229. package/dist/photons/marketplace.photon.d.ts.map +1 -1
  230. package/dist/photons/marketplace.photon.js +10 -27
  231. package/dist/photons/marketplace.photon.js.map +1 -1
  232. package/dist/photons/marketplace.photon.ts +14 -33
  233. package/dist/photons/tunnel.photon.d.ts.map +1 -1
  234. package/dist/photons/tunnel.photon.js +4 -8
  235. package/dist/photons/tunnel.photon.js.map +1 -1
  236. package/dist/photons/tunnel.photon.ts +4 -7
  237. package/dist/serv/session/kv-store.d.ts +1 -1
  238. package/dist/serv/session/kv-store.d.ts.map +1 -1
  239. package/dist/serv/session/store.d.ts.map +1 -1
  240. package/dist/serv/session/store.js +16 -14
  241. package/dist/serv/session/store.js.map +1 -1
  242. package/dist/serv/vault/token-vault.js +1 -1
  243. package/dist/serv/vault/token-vault.js.map +1 -1
  244. package/dist/server.d.ts +34 -12
  245. package/dist/server.d.ts.map +1 -1
  246. package/dist/server.js +364 -313
  247. package/dist/server.js.map +1 -1
  248. package/dist/shared/audit.d.ts +30 -0
  249. package/dist/shared/audit.d.ts.map +1 -0
  250. package/dist/shared/audit.js +89 -0
  251. package/dist/shared/audit.js.map +1 -0
  252. package/dist/shared/cli-sections.d.ts +0 -4
  253. package/dist/shared/cli-sections.d.ts.map +1 -1
  254. package/dist/shared/cli-sections.js +0 -6
  255. package/dist/shared/cli-sections.js.map +1 -1
  256. package/dist/shared/cli-utils.d.ts +2 -56
  257. package/dist/shared/cli-utils.d.ts.map +1 -1
  258. package/dist/shared/cli-utils.js +1 -87
  259. package/dist/shared/cli-utils.js.map +1 -1
  260. package/dist/shared/error-handler.d.ts +6 -72
  261. package/dist/shared/error-handler.d.ts.map +1 -1
  262. package/dist/shared/error-handler.js +22 -213
  263. package/dist/shared/error-handler.js.map +1 -1
  264. package/dist/shared/security.d.ts +0 -9
  265. package/dist/shared/security.d.ts.map +1 -1
  266. package/dist/shared/security.js +0 -30
  267. package/dist/shared/security.js.map +1 -1
  268. package/dist/shared-utils.d.ts +0 -26
  269. package/dist/shared-utils.d.ts.map +1 -1
  270. package/dist/shared-utils.js +0 -44
  271. package/dist/shared-utils.js.map +1 -1
  272. package/dist/shell-completions.d.ts +1 -1
  273. package/dist/shell-completions.d.ts.map +1 -1
  274. package/dist/shell-completions.js +5 -5
  275. package/dist/shell-completions.js.map +1 -1
  276. package/dist/template-manager.d.ts.map +1 -1
  277. package/dist/template-manager.js +14 -1
  278. package/dist/template-manager.js.map +1 -1
  279. package/dist/test-runner.d.ts +0 -12
  280. package/dist/test-runner.d.ts.map +1 -1
  281. package/dist/test-runner.js +4 -39
  282. package/dist/test-runner.js.map +1 -1
  283. package/dist/testing.d.ts +1 -1
  284. package/dist/testing.d.ts.map +1 -1
  285. package/dist/testing.js +2 -2
  286. package/dist/testing.js.map +1 -1
  287. package/dist/version-checker.d.ts +4 -4
  288. package/dist/version-checker.d.ts.map +1 -1
  289. package/dist/version-checker.js +33 -4
  290. package/dist/version-checker.js.map +1 -1
  291. package/dist/watcher.d.ts.map +1 -1
  292. package/dist/watcher.js +14 -12
  293. package/dist/watcher.js.map +1 -1
  294. package/package.json +24 -17
package/README.md CHANGED
@@ -3,9 +3,7 @@
3
3
 
4
4
  <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/photon-logo.png" alt="Photon" width="500">
5
5
 
6
- **Simplify the creation of CLI tools, MCP servers, and web applications.**
7
-
8
- A framework, runtime, and ecosystem. Batteries included.
6
+ **Software for humans and AI. Written once.**
9
7
 
10
8
  [![npm version](https://img.shields.io/npm/v/@portel/photon?color=cb3837&label=npm)](https://www.npmjs.com/package/@portel/photon)
11
9
  [![npm downloads](https://img.shields.io/npm/dm/@portel/photon?color=cb3837)](https://www.npmjs.com/package/@portel/photon)
@@ -14,140 +12,146 @@ A framework, runtime, and ecosystem. Batteries included.
14
12
  [![Node](https://img.shields.io/badge/node-%3E%3D18-43853d.svg)](https://nodejs.org)
15
13
  [![MCP](https://img.shields.io/badge/MCP-compatible-7c3aed.svg)](https://modelcontextprotocol.io)
16
14
 
17
- [Quick Start](#quick-start) · [Why Photon](#why-did-we-build-this) · [Beam UI](#beam) · [How It Works](#how-it-works) · [Docs](#documentation)
18
-
19
- [![Watch: Why Photon? (2 min)](https://img.youtube.com/vi/FI0M8s6ZKv4/maxresdefault.jpg)](https://www.youtube.com/watch?v=FI0M8s6ZKv4)
20
-
21
15
  </div>
22
16
 
23
17
  ---
24
18
 
25
- ## What Is This Thing?
26
-
27
- So, here is the situation. You write a single TypeScript file. Just one. And somehow, through some dark magic I don't fully understand either, you get three things at once:
19
+ Your tools have two kinds of consumers now. Humans who open a dashboard and explore. AI agents that call your methods through a protocol. Until now, you've been building for one or the other, or building everything twice.
28
20
 
29
- 1. **An MCP server** (so Claude or Cursor can use your tools).
30
- 2. **A CLI tool** (so you can run it from the terminal like a normal human).
31
- 3. **A web application** (a visual dashboard called "Beam" that makes forms for you).
21
+ Photon is built around a different premise: **write what you mean, and let both consumers figure it out from that**.
32
22
 
33
- It looks like this:
23
+ You write a TypeScript class. Methods are your capabilities. Types describe what's valid. Comments explain the intent. That's it. Photon reads all of it and generates a web UI for human exploration, a CLI for scripting, and an MCP server for AI agents. Same logic. Same validation. Same data. Three interfaces from one file.
34
24
 
35
25
  ```
36
- analytics.photon.ts → MCP Server | CLI Tool | Web UI
26
+ analytics.photon.ts → Web UI (Beam) · CLI · MCP Server for AI
37
27
  ```
38
28
 
39
- You just write the logic. Photon deals with the protocols, schemas, and the boring stuff that usually makes you question your life choices.
29
+ The code stays simple, almost embarrassingly simple, because the complexity isn't in what you write. It's in what Photon derives from it.
40
30
 
41
- ### Who Is This For?
31
+ <div align="center">
42
32
 
43
- * **Developers** who want to give AI access to their database but are too lazy to write a full server.
44
- * **Teams** who want to share tools without emailing zip files.
45
- * **Anyone** who wants a CLI and a web UI without writing the boilerplate.
33
+ <a href="https://www.youtube.com/watch?v=FI0M8s6ZKv4">
34
+ <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/video-preview.png" alt="Watch: Why Photon? (2 min)" width="100%">
35
+ </a>
46
36
 
47
- You don't need to know what "MCP" actually stands for. If you can write a TypeScript class, you are qualified.
37
+ </div>
48
38
 
49
39
  ---
50
40
 
51
- ## Why did we build this?
41
+ ## Quick Start
52
42
 
53
- Three reasons, if you want the short version. ([Read the longer version](./WHY-PHOTON.md))
43
+ ```bash
44
+ npm install -g @portel/photon
45
+ photon maker new my-tool # Create a photon
46
+ photon # Open Beam, the web UI
47
+ ```
54
48
 
55
- **MCP is personal.** The best MCP is the one built for exactly one use case. Yours. Your team's. Your company's. When you stop building for everyone, the code gets absurdly simple. One file. Twelve lines. Not twelve hundred.
49
+ Or without installing:
56
50
 
57
- **Solve once, run forever.** If an LLM figured out your workflow the first time, why ask it to re-derive the same answer from scratch every time? Photon lets you keep the answer. No middleman, no tokens, no latency.
51
+ ```bash
52
+ npx @portel/photon maker new my-tool
53
+ npx @portel/photon
54
+ ```
58
55
 
59
- **Same door, every key.** AI calls it through MCP. You call it through CLI. You open it in Beam. Same methods, same data, same result. And half the time, you don't need AI at all. You just need the data.
56
+ > Requires [Node.js 18+](https://nodejs.org). TypeScript is compiled internally; no `tsconfig.json` needed.
60
57
 
61
58
  ---
62
59
 
63
- ## Quick Start
60
+ ## What You Actually Write
64
61
 
65
- If you are the type who likes to just run commands and see what happens:
62
+ Here is a complete, working photon:
66
63
 
67
- ```bash
68
- npm install -g @portel/photon
69
- photon maker new my-tool # Makes a new photon
70
- photon # Opens the Beam UI
64
+ ```typescript
65
+ export default class Analytics {
66
+ async report(params: { period: string }) {
67
+ return await db.query(`SELECT * FROM events WHERE period = $1`, [params.period]);
68
+ }
69
+ }
71
70
  ```
72
71
 
73
- Or if you don't want to install anything (I get it):
72
+ From this, Photon generates:
73
+ - A web form in Beam with a `period` text input
74
+ - `photon cli analytics report --period 2024-Q4`
75
+ - An MCP tool that Claude or Cursor can invoke
74
76
 
75
- ```bash
76
- npx @portel/photon maker new my-tool
77
- npx @portel/photon
78
- ```
77
+ No decorators. No registration. No server boilerplate. You wrote the logic. Photon derived the rest.
79
78
 
80
- > **Note:** You need [Node.js 18+](https://nodejs.org). Also, TypeScript helps, but Photon handles the compiling, so you don't have to fight with `tsconfig.json`.
79
+ The more you express, the more Photon understands.
81
80
 
82
81
  ---
83
82
 
84
- ## Beam
83
+ ## Everything You Add Becomes Something Useful
84
+
85
+ Photon reads your TypeScript as **intent**. Every construct you'd write anyway carries meaning it can act on.
85
86
 
86
- Beam is the dashboard. It's where you go to poke your tools and see if they work before you let an AI loose on them.
87
+ | What you write | What Photon derives |
88
+ |---|---|
89
+ | Method signatures | Tool definitions: names, inputs, outputs |
90
+ | Type annotations | Input validation rules, UI field types |
91
+ | JSDoc comments | Documentation for AI clients and human users |
92
+ | Constructor parameters | Config UI, environment variable mapping |
93
+ | `@tags` | Validation, formatting, scheduling, webhooks |
87
94
 
88
- Run `photon`. That's it.
95
+ So when you add a `@param city {@pattern ^[a-zA-Z\s]+$}` annotation you were going to write anyway, Beam automatically validates it in the form, the CLI validates it before running, and the MCP schema enforces it for the AI. One annotation. Three consumers.
89
96
 
90
97
  <div align="center">
91
- <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/beam-dashboard.png" alt="Beam Dashboard" width="100%">
98
+ <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/photon-ecosystem.png" alt="Photon: one file, three surfaces" width="100%">
92
99
  </div>
93
100
 
94
101
  ---
95
102
 
96
- ## Connecting to AI
103
+ ## Beam: Human Exploration
104
+
105
+ Beam is the web dashboard. Every photon becomes an interactive form. Run `photon`. That's the whole command.
97
106
 
98
- If you want to use this with Claude or Cursor, you need the config.
107
+ <div align="center">
108
+ <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/beam-dashboard.png" alt="Beam Dashboard" width="100%">
109
+ </div>
110
+
111
+ The UI is **fully auto-generated** from your method signatures: field types, validation, defaults, layouts. You never write frontend code. When you add a `{@choice a,b,c}` tag to a parameter, Beam renders a dropdown. When you mark a string as `{@format email}`, the field validates email format. The UI evolves as your code does.
112
+
113
+ When forms aren't the right interface for what you're building, you can replace Beam's auto-generated view with your own HTML. The custom UI receives tool results via `window.photon.onResult()`, a thin bridge with no framework required.
114
+
115
+ > Custom UIs follow the [MCP Apps Extension (SEP-1865)](https://github.com/nicolo-ribaudo/modelcontextprotocol/blob/nicolo/sep-1865/docs/specification/draft/extensions/apps.mdx) standard and work across compatible hosts. See the [Custom UI Guide](./docs/guides/CUSTOM-UI.md).
116
+
117
+ ---
118
+
119
+ ## AI Agents: Machine Invocation
99
120
 
100
121
  ```bash
101
- photon info weather --mcp
122
+ photon info analytics --mcp
102
123
  ```
103
124
 
104
- It spits out some JSON:
105
-
106
125
  ```json
107
126
  {
108
127
  "mcpServers": {
109
- "weather": {
128
+ "analytics": {
110
129
  "command": "photon",
111
- "args": ["mcp", "weather"]
130
+ "args": ["mcp", "analytics"]
112
131
  }
113
132
  }
114
133
  }
115
134
  ```
116
135
 
117
- Copy that. Paste it into your AI client's config file. Done.
136
+ Paste into your AI client's config. Your photon is now an MCP server. Claude can call your methods. Cursor can call your methods. Any MCP-compatible host can call your methods.
118
137
 
119
- Works with [Claude Desktop](https://claude.ai/download), [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Cursor](https://cursor.com), and any [MCP-compatible client](https://modelcontextprotocol.io).
138
+ The AI sees the same thing a human sees in Beam: the method names, the parameter descriptions from your JSDoc, the validation rules from your types. The JSDoc comment you wrote to document the tool for yourself is what Claude reads to decide when and how to call it.
120
139
 
121
- ---
122
-
123
- ## Marketplace
140
+ The MCP tools themselves work with [Claude Desktop](https://claude.ai/download), [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Cursor](https://cursor.com), and any MCP-compatible client.
124
141
 
125
- We also have a marketplace. 35 photons and counting.
142
+ When your photon has a custom UI, clients that support the [MCP Apps Extension](https://github.com/nicolo-ribaudo/modelcontextprotocol/blob/nicolo/sep-1865/docs/specification/draft/extensions/apps.mdx) render it natively, no separate app needed. The photon below is running inside Claude Desktop, same UI, same data as Beam.
126
143
 
127
144
  <div align="center">
128
- <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/beam-marketplace.png" alt="Marketplace" width="100%">
145
+ <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/claude-desktop.png" alt="Photon running as an MCP App with custom UI inside Claude Desktop" width="100%">
129
146
  </div>
130
147
 
131
- ```bash
132
- photon search postgres
133
- photon add postgres
134
- ```
135
-
136
- Browse the full catalog and documentation in the [official photons repository](https://github.com/portel-dev/photons).
137
-
138
- You can also make a private marketplace for your team, so internal tools stay off the public internet.
139
-
140
148
  ---
141
149
 
142
- ## How It Works
150
+ ## The Progression
143
151
 
144
- A photon is just a TypeScript class. The **public methods become tools**. Photon reads your code, looks at the types, reads your comments, and then generates everything else.
152
+ Here is how a photon grows. Each step adds one thing and gets multiple capabilities from it.
145
153
 
146
- I'll show you.
147
-
148
- ### Step 1: The Bare Minimum
149
-
150
- Here is a class with one method. This is a valid photon.
154
+ ### Bare method: three interfaces from five lines
151
155
 
152
156
  ```typescript
153
157
  export default class Weather {
@@ -157,47 +161,34 @@ export default class Weather {
157
161
  }
158
162
  ```
159
163
 
160
- **What happens:** Beam sees this and makes a form with a text box labeled "city". You click a button, and it runs.
161
-
162
- **What you get:**
163
- * `photon mcp weather` (The server for Claude)
164
- * `photon cli weather forecast --city Paris` (The command line tool)
165
- * `photon` (The web UI)
164
+ A text input in Beam. A `--city` flag in the CLI. An MCP input schema. From five lines.
166
165
 
167
166
  <div align="center">
168
- <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-1.png" alt="Step 1 — Bare method in Beam" width="100%">
167
+ <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-1.png" alt="Step 1" width="100%">
169
168
  </div>
170
169
 
171
- ### Step 2: Adding Descriptions
172
-
173
- If you add JSDoc comments, they show up as descriptions.
170
+ ### Add comments: AI understands your intent
174
171
 
175
172
  ```typescript
176
173
  /**
177
174
  * Weather - Check weather forecasts worldwide
178
- *
179
- * Provides current conditions.
180
175
  */
181
176
  export default class Weather {
182
177
  /**
183
- * Get the weather forecast
178
+ * Get the weather forecast for a city
184
179
  * @param city City name (e.g., "London")
185
180
  */
186
- async forecast(params: { city: string }) {
187
- return `Weather for ${params.city}: Sunny, 72°F`;
188
- }
181
+ async forecast(params: { city: string }) { ... }
189
182
  }
190
183
  ```
191
184
 
192
- **What happens:** Now the UI has helpful text. Also, the AI client reads this to understand what the tool does.
185
+ The class description becomes how AI clients introduce the tool to users. The `@param` description is what the AI reads before deciding what value to pass. Same comments. Human help text and AI contract at once.
193
186
 
194
187
  <div align="center">
195
- <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-2.png" alt="Step 2 — JSDoc descriptions in Beam" width="100%">
188
+ <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-2.png" alt="Step 2" width="100%">
196
189
  </div>
197
190
 
198
- ### Step 3: Configuration (The clever bit)
199
-
200
- If you need an API key, put it in the constructor.
191
+ ### Add a constructor: configuration appears
201
192
 
202
193
  ```typescript
203
194
  export default class Weather {
@@ -207,163 +198,137 @@ export default class Weather {
207
198
  ) {}
208
199
 
209
200
  async forecast(params: { city: string }) {
210
- const res = await fetch(
211
- `https://api.openweathermap.org/data/2.5/weather?q=${params.city}&appid=${this.apiKey}&units=${this.units}`
212
- );
201
+ const res = await fetch(`...?appid=${this.apiKey}&units=${this.units}`);
213
202
  return await res.json();
214
203
  }
215
204
  }
216
205
  ```
217
206
 
218
- **What happens:** Beam creates a settings panel. `apiKey` becomes a password field. It also maps to environment variables like `WEATHER_API_KEY`. It just works.
207
+ `apiKey` becomes a password field in the Beam settings panel and maps to the `WEATHER_API_KEY` environment variable. `units` gets a text input with `'metric'` pre-filled. You declared what you need. Photon built the configuration surface.
219
208
 
220
209
  <div align="center">
221
- <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-3.png" alt="Step 3 — Configuration panel in Beam" width="100%">
210
+ <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-3.png" alt="Step 3" width="100%">
222
211
  </div>
223
212
 
224
- ### Step 4: Validation (Stop bad inputs)
225
-
226
- You can add tags to valid inputs.
213
+ ### Add tags: behavior extends across all surfaces
227
214
 
228
215
  ```typescript
229
216
  /**
230
- * Weather - Check weather forecasts worldwide
231
217
  * @dependencies node-fetch@^3.0.0
232
218
  */
233
219
  export default class Weather {
234
- constructor(
235
- private apiKey: string,
236
- private units: string = 'metric'
237
- ) {}
238
-
239
220
  /**
240
- * Get the weather forecast for a city
241
221
  * @param city City name {@example London} {@pattern ^[a-zA-Z\s]+$}
242
222
  * @param days Number of days {@min 1} {@max 7}
243
223
  * @format table
244
224
  */
245
- async forecast(params: { city: string; days?: number }) {
246
- // fetch and return forecast data...
247
- }
225
+ async forecast(params: { city: string; days?: number }) { ... }
248
226
  }
249
227
  ```
250
228
 
251
- **What happens:**
252
- * The `city` input validates the regex.
253
- * The `days` input becomes a number spinner (1-7).
254
- * The result is formatted as a table.
255
- * `@dependencies` makes Photon install `node-fetch` automatically. You don't even run `npm install`.
229
+ `@dependencies` installs `node-fetch` automatically on first run, no `npm install` needed. The `{@pattern}` validates in the form, the CLI, and the MCP schema simultaneously. `days` becomes a number spinner with bounds. `@format table` renders the result as a table in Beam. One annotation, three surfaces.
256
230
 
257
- #### System CLI Dependencies
231
+ <div align="center">
232
+ <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-4.png" alt="Step 4" width="100%">
233
+ </div>
258
234
 
259
- If your photon wraps a command-line tool (e.g. `ffmpeg`, `git`, `docker`), declare it with `@cli`. Photon checks for the tool at load time and refuses to load if it's missing.
235
+ ### System CLI dependencies
236
+
237
+ If your photon wraps a command-line tool, declare it and Photon enforces it at load time:
260
238
 
261
239
  ```typescript
262
240
  /**
263
- * Video processor
264
241
  * @cli ffmpeg - https://ffmpeg.org/download.html
265
- * @cli imagemagick - https://imagemagick.org/script/download.php
266
242
  */
267
243
  export default class VideoProcessor {
268
244
  async convert({ input, format }: { input: string; format: string }) {
269
- // ffmpeg is guaranteed to exist if this method runs
245
+ // ffmpeg is guaranteed to exist when this runs
270
246
  }
271
247
  }
272
248
  ```
273
249
 
274
- If `ffmpeg` is not installed, the photon won't load and the user sees:
250
+ <div align="center">
251
+ <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-5.png" alt="Step 5" width="100%">
252
+ </div>
275
253
 
276
- ```
277
- VideoProcessor requires the following CLI tools to be installed:
278
- - ffmpeg: Install from https://ffmpeg.org/download.html
279
- ```
254
+ ---
280
255
 
281
- > See the full [Tag Reference](./docs/reference/DOCBLOCK-TAGS.md) for all available tags. There are 30+ covering validation, UI hints, scheduling, webhooks, and more.
256
+ ## What Comes for Free
282
257
 
283
- <div align="center">
284
- <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-4.png" alt="Step 4 — Validation and formatting in Beam" width="100%">
285
- </div>
258
+ Things you don't build because Photon handles them:
259
+
260
+ | | |
261
+ |---|---|
262
+ | **Auto-UI** | Forms, field types, validation, layouts generated from your signatures |
263
+ | **Stateful instances** | Multiple named instances of the same photon, each with isolated state |
264
+ | **Persistent memory** | `this.memory` gives your photon per-instance key-value storage, no database needed |
265
+ | **Scheduled execution** | `@scheduled` runs any method on a cron schedule |
266
+ | **Webhooks** | `@webhook` exposes any method as an HTTP endpoint |
267
+ | **OAuth** | Built-in OAuth 2.0 flows for Google, GitHub, Microsoft |
268
+ | **Distributed locks** | `@locked` serializes access: one caller at a time, across processes |
269
+ | **Cross-photon calls** | `this.call()` invokes another photon's methods |
270
+ | **Real-time events** | `this.emit()` fires named events to the browser UI with zero wiring |
271
+ | **Dependency management** | `@dependencies` auto-installs npm packages on first run |
272
+
273
+ ---
274
+
275
+ ## Coordination: Locks + Events
286
276
 
287
- ### Step 5: Custom UI (When you want to be fancy)
277
+ Two primitives. Together they unlock a class of things that are surprisingly hard to build today.
288
278
 
289
- If the auto-generated form is too boring, you can write your own HTML.
279
+ **Locks** serialize access. When a method is marked `@locked`, only one caller can execute at a time, whether that caller is a human in Beam, a CLI script, or an AI agent. Everyone else waits their turn.
280
+
281
+ **Events** push state changes to any browser UI in real time. `this.emit('name', data)` in your method fires `window.photon.on('name', handler)` in your custom UI. No WebSockets to configure. No polling. The data marshalling and delivery is handled by the system.
282
+
283
+ Together: **turn-based coordination with live state**.
290
284
 
291
285
  ```typescript
292
- /**
293
- * Weather - Check weather forecasts worldwide
294
- * @dependencies node-fetch@^3.0.0
295
- * @ui dashboard ./ui/weather.html
296
- */
297
- export default class Weather {
298
- constructor(private apiKey: string, private units: string = 'metric') {}
286
+ export default class Chess {
287
+ /** Make a move. Locks ensure human and AI alternate turns. */
288
+ /** @locked */
289
+ async move(params: { from: string; to: string }) {
290
+ const result = await this.applyMove(params.from, params.to);
299
291
 
300
- /**
301
- * Get the weather forecast for a city
302
- * @ui dashboard
303
- * @format table
304
- */
305
- async forecast(params: { city: string; days?: number }) {
306
- // returns structured weather data
292
+ // Browser UI updates instantly, no polling needed
293
+ this.emit('board-updated', result.board);
294
+ this.emit('turn-changed', { next: result.nextPlayer });
295
+
296
+ return result;
307
297
  }
308
298
  }
309
299
  ```
310
300
 
311
- ```html
312
- <!-- ui/weather.html -->
313
- <div id="weather-app">
314
- <div id="forecast"></div>
315
- </div>
316
- <script>
317
- window.photon.onResult(data => {
318
- document.getElementById('forecast').innerHTML = renderWeather(data);
319
- });
320
- </script>
301
+ ```javascript
302
+ // In your custom UI (ui/chess.html)
303
+ window.photon.on('board-updated', board => renderBoard(board));
304
+ window.photon.on('turn-changed', ({ next }) => showTurn(next));
321
305
  ```
322
306
 
323
- **What changes in Beam:** Instead of the auto-generated table, results render inside your custom HTML (a weather dashboard with icons, charts, or any visualization you build). The `window.photon` API bridges your UI to the tool system.
307
+ A human moves through Beam. Claude is configured with the MCP server. The lock ensures they truly alternate. Events keep the board live on both sides. That's a fully functional turn-based chess game, human vs AI, in about 50 lines of application logic.
324
308
 
325
- > Custom UIs follow the [MCP Apps Extension (SEP-1865)](https://github.com/nicolo-ribaudo/modelcontextprotocol/blob/nicolo/sep-1865/docs/specification/draft/extensions/apps.mdx) standard and work across compatible hosts. See the [Custom UI Guide](./docs/guides/CUSTOM-UI.md).
309
+ The same pattern applies beyond games: approval workflows where a human reviews before AI continues, collaborative tools where edits from any source appear instantly, simulations where steps must execute in strict sequence, any system where **who acts next matters**.
326
310
 
327
- <div align="center">
328
- <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-5.png" alt="Step 5 — Custom UI result in Beam" width="100%">
329
- </div>
311
+ ---
330
312
 
331
- ### In Summary
313
+ ## Marketplace
332
314
 
333
- | Step | You write | Photon generates |
334
- |------|---------|-----------------|
335
- | **1. Methods** | A function | Tools, CLI commands, Forms |
336
- | **2. JSDoc** | Comments | Descriptions for AI and Humans |
337
- | **3. Constructor** | Arguments | Config UI, Env vars |
338
- | **4. Tags** | `@tags` | Validation, Installers, Formatting |
339
- | **5. Custom UI** | HTML | A custom app |
315
+ 32 photons ready to install: databases, APIs, developer tools, and more.
340
316
 
341
317
  <div align="center">
342
- <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/photon-ecosystem.png" alt="Photon Ecosystem" width="100%">
318
+ <img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/beam-marketplace.png" alt="Marketplace" width="100%">
343
319
  </div>
344
320
 
345
- ---
346
-
347
- ## The Basics
348
-
349
- If you are just skimming, here is what you need to know:
321
+ ```bash
322
+ photon search postgres
323
+ photon add postgres
324
+ ```
350
325
 
351
- | Concept | What it is | Learn more |
352
- |---------|-----------|------------|
353
- | **MCP** | A way for AI to use your tools. It's a standard. | [modelcontextprotocol.io](https://modelcontextprotocol.io/introduction) |
354
- | **Photon file** | A `.photon.ts` file. You define tools as methods in a class. | [Guide](./docs/GUIDE.md) |
355
- | **Beam** | A web dashboard. It shows your tools as forms. | [Beam UI](#beam) |
356
- | **Marketplace** | A way to get other people's photons. | [Marketplace](#marketplace) |
357
- | **Daemon** | A background thing that handles messages and jobs. | [Daemon Pub/Sub](./docs/core/DAEMON-PUBSUB.md) |
358
- | **Tags** | JSDoc comments that tell Photon what to do. | [Tag Reference](./docs/reference/DOCBLOCK-TAGS.md) |
359
- | **Custom UI** | When the auto-generated forms aren't enough. | [Custom UI Guide](./docs/guides/CUSTOM-UI.md) |
326
+ Browse the full catalog in the [official photons repository](https://github.com/portel-dev/photons). You can also host a private marketplace for your team: internal tools that stay off the public internet.
360
327
 
361
328
  ---
362
329
 
363
330
  ## Commands
364
331
 
365
- A few commands you might use:
366
-
367
332
  ```bash
368
333
  # Run
369
334
  photon # Open Beam UI
@@ -376,7 +341,7 @@ photon maker new <name> # Scaffold a new photon
376
341
 
377
342
  # Manage
378
343
  photon info # List all photons
379
- photon info <name> --mcp # Get MCP client config (paste into Claude/Cursor)
344
+ photon info <name> --mcp # Get MCP client config
380
345
  photon maker validate <name> # Check for errors
381
346
 
382
347
  # Marketplace
@@ -391,15 +356,13 @@ photon test # Run tests
391
356
 
392
357
  ---
393
358
 
394
- ## Tag Reference (Quick Overview)
395
-
396
- Tags are JSDoc annotations that control how Photon processes your code. Here are the most commonly used ones:
359
+ ## Tag Reference
397
360
 
398
361
  | Tag | Where | What it does |
399
- |-----|-------|-------------|
362
+ |---|---|---|
400
363
  | `@dependencies` | Class | Auto-install npm packages on first run |
401
- | `@cli` | Class | Declare system CLI tool dependencies, checked at load time |
402
- | `@format` | Method | Control result rendering (table, list, markdown, code, etc.) |
364
+ | `@cli` | Class | Declare system CLI dependencies, checked at load time |
365
+ | `@format` | Method | Result rendering (table, list, markdown, code, etc.) |
403
366
  | `@param ... {@choice a,b,c}` | Param | Dropdown selection in Beam |
404
367
  | `@param ... {@format email}` | Param | Input validation and field type |
405
368
  | `@param ... {@min N} {@max N}` | Param | Numeric range constraints |
@@ -411,7 +374,7 @@ Tags are JSDoc annotations that control how Photon processes your code. Here are
411
374
  | `@mcp` | Class | Inject another MCP server as a dependency |
412
375
  | `@icon` | Class/Method | Set emoji icon |
413
376
 
414
- > This is a subset. See the full [Tag Reference](./docs/reference/DOCBLOCK-TAGS.md) for all 30+ tags with examples.
377
+ > See the full [Tag Reference](./docs/reference/DOCBLOCK-TAGS.md) for all 30+ tags with examples.
415
378
 
416
379
  ---
417
380
 
@@ -420,7 +383,7 @@ Tags are JSDoc annotations that control how Photon processes your code. Here are
420
383
  **Start here:**
421
384
 
422
385
  | Guide | |
423
- |-------|-|
386
+ |---|---|
424
387
  | [Getting Started](./docs/GUIDE.md) | Create your first photon, step by step |
425
388
  | [Tag Reference](./docs/reference/DOCBLOCK-TAGS.md) | Complete JSDoc tag reference with examples |
426
389
  | [Naming Conventions](./docs/guides/NAMING-CONVENTIONS.md) | How to name methods so they read naturally as CLI commands |
@@ -429,9 +392,9 @@ Tags are JSDoc annotations that control how Photon processes your code. Here are
429
392
  **Build more:**
430
393
 
431
394
  | Topic | |
432
- |-------|-|
395
+ |---|---|
433
396
  | [Custom UI](./docs/guides/CUSTOM-UI.md) | Build rich interactive interfaces with `window.photon` |
434
- | [OAuth](./docs/guides/AUTH.md) | Built-in OAuth 2.1 with Google, GitHub, Microsoft |
397
+ | [OAuth](./docs/guides/AUTH.md) | Built-in OAuth 2.0 with Google, GitHub, Microsoft |
435
398
  | [Daemon Pub/Sub](./docs/core/DAEMON-PUBSUB.md) | Real-time cross-process messaging |
436
399
  | [Webhooks](./docs/reference/WEBHOOKS.md) | HTTP endpoints for external services |
437
400
  | [Locks](./docs/reference/LOCKS.md) | Distributed locks for exclusive access |
@@ -441,7 +404,7 @@ Tags are JSDoc annotations that control how Photon processes your code. Here are
441
404
  **Operate:**
442
405
 
443
406
  | Topic | |
444
- |-------|-|
407
+ |---|---|
445
408
  | [Security](./SECURITY.md) | Best practices and audit checklist |
446
409
  | [Marketplace Publishing](./docs/guides/MARKETPLACE-PUBLISHING.md) | Create and share team marketplaces |
447
410
  | [Best Practices](./docs/guides/BEST-PRACTICES.md) | Patterns for production photons |
@@ -453,18 +416,8 @@ Tags are JSDoc annotations that control how Photon processes your code. Here are
453
416
 
454
417
  ## Contributing
455
418
 
456
- If you find a bug, or if my code offends you, feel free to open an issue or a PR. See [CONTRIBUTING.md](./CONTRIBUTING.md).
419
+ Open an issue or a PR. See [CONTRIBUTING.md](./CONTRIBUTING.md).
457
420
 
458
421
  ## License
459
422
 
460
- [MIT](./LICENSE). Do what you want with it.
461
-
462
- ---
463
-
464
- <div align="center">
465
-
466
- *Singular focus. Precise target.*
467
-
468
- Made by [Portel](https://github.com/portel-dev)
469
-
470
- </div>
423
+ [MIT](./LICENSE).
@@ -0,0 +1,40 @@
1
+ /**
2
+ * DedupMap — Map with deduplication for concurrent async creation.
3
+ *
4
+ * When multiple callers request the same key concurrently, only one
5
+ * factory call runs — all callers share the same inflight promise.
6
+ *
7
+ * Usage:
8
+ * const map = new DedupMap<string, Connection>();
9
+ * const conn = await map.getOrCreate('redis', () => connect('redis://...'));
10
+ */
11
+ export declare class DedupMap<K, V> {
12
+ private _values;
13
+ private _inflight;
14
+ /** Number of resolved entries. */
15
+ get size(): number;
16
+ /** Check if a resolved value exists for key. */
17
+ has(key: K): boolean;
18
+ /** Get a resolved value (undefined if not yet created). */
19
+ get(key: K): V | undefined;
20
+ /** Set a value directly (bypasses factory). */
21
+ set(key: K, value: V): void;
22
+ /**
23
+ * Get existing value or create one. Concurrent calls for the same
24
+ * key join the same inflight promise instead of spawning duplicates.
25
+ */
26
+ getOrCreate(key: K, factory: () => Promise<V>): Promise<V>;
27
+ /** Delete a key (cancels inflight if pending). */
28
+ delete(key: K): boolean;
29
+ /** Clear all entries and inflight promises. */
30
+ clear(): void;
31
+ /** Iterate over resolved entries. Returns a snapshot (safe across await). */
32
+ entries(): [K, V][];
33
+ /** Iterate over resolved keys. Returns a snapshot. */
34
+ keys(): K[];
35
+ /** Iterate over resolved values. Returns a snapshot. */
36
+ values(): V[];
37
+ /** Iterate resolved entries via for...of. Returns snapshot array. */
38
+ [Symbol.iterator](): IterableIterator<[K, V]>;
39
+ }
40
+ //# sourceMappingURL=dedup-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedup-map.d.ts","sourceRoot":"","sources":["../../src/async/dedup-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,qBAAa,QAAQ,CAAC,CAAC,EAAE,CAAC;IACxB,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,SAAS,CAA4B;IAE7C,kCAAkC;IAClC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,gDAAgD;IAChD,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAIpB,2DAA2D;IAC3D,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAI1B,+CAA+C;IAC/C,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAK3B;;;OAGG;IACG,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAuBhE,kDAAkD;IAClD,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAKvB,+CAA+C;IAC/C,KAAK,IAAI,IAAI;IAKb,6EAA6E;IAC7E,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;IAInB,sDAAsD;IACtD,IAAI,IAAI,CAAC,EAAE;IAIX,wDAAwD;IACxD,MAAM,IAAI,CAAC,EAAE;IAIb,qEAAqE;IACrE,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAG9C"}