alchemy 0.69.0 → 0.70.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 (314) hide show
  1. package/bin/alchemy.js +3810 -3889
  2. package/bin/commands/init.ts +41 -55
  3. package/bin/trpc.ts +4 -9
  4. package/lib/alchemy.d.ts +3 -3
  5. package/lib/alchemy.d.ts.map +1 -1
  6. package/lib/alchemy.js +2 -15
  7. package/lib/alchemy.js.map +1 -1
  8. package/lib/apply.js +24 -9
  9. package/lib/apply.js.map +1 -1
  10. package/lib/aws/utils.d.ts +2 -1
  11. package/lib/aws/utils.d.ts.map +1 -1
  12. package/lib/aws/utils.js.map +1 -1
  13. package/lib/clickhouse/api/client.gen.d.ts +13 -0
  14. package/lib/clickhouse/api/client.gen.d.ts.map +1 -0
  15. package/lib/clickhouse/api/client.gen.js +7 -0
  16. package/lib/clickhouse/api/client.gen.js.map +1 -0
  17. package/lib/clickhouse/api/sdk.gen.d.ts +322 -0
  18. package/lib/clickhouse/api/sdk.gen.d.ts.map +1 -0
  19. package/lib/clickhouse/api/sdk.gen.js +683 -0
  20. package/lib/clickhouse/api/sdk.gen.js.map +1 -0
  21. package/lib/clickhouse/api/types.gen.d.ts +5030 -0
  22. package/lib/clickhouse/api/types.gen.d.ts.map +1 -0
  23. package/lib/clickhouse/api/types.gen.js +2 -0
  24. package/lib/clickhouse/api/types.gen.js.map +1 -0
  25. package/lib/clickhouse/api-key.d.ts +29 -0
  26. package/lib/clickhouse/api-key.d.ts.map +1 -0
  27. package/lib/clickhouse/api-key.js +83 -0
  28. package/lib/clickhouse/api-key.js.map +1 -0
  29. package/lib/clickhouse/api.d.ts +36 -0
  30. package/lib/clickhouse/api.d.ts.map +1 -0
  31. package/lib/clickhouse/api.js +48 -0
  32. package/lib/clickhouse/api.js.map +1 -0
  33. package/lib/clickhouse/index.d.ts +4 -0
  34. package/lib/clickhouse/index.d.ts.map +1 -0
  35. package/lib/clickhouse/index.js +4 -0
  36. package/lib/clickhouse/index.js.map +1 -0
  37. package/lib/clickhouse/organization.d.ts +16 -0
  38. package/lib/clickhouse/organization.d.ts.map +1 -0
  39. package/lib/clickhouse/organization.js +21 -0
  40. package/lib/clickhouse/organization.js.map +1 -0
  41. package/lib/clickhouse/service.d.ts +275 -0
  42. package/lib/clickhouse/service.d.ts.map +1 -0
  43. package/lib/clickhouse/service.js +274 -0
  44. package/lib/clickhouse/service.js.map +1 -0
  45. package/lib/cloudflare/account-api-token.d.ts.map +1 -1
  46. package/lib/cloudflare/account-api-token.js +1 -1
  47. package/lib/cloudflare/account-api-token.js.map +1 -1
  48. package/lib/cloudflare/bucket-object.d.ts +5 -0
  49. package/lib/cloudflare/bucket-object.d.ts.map +1 -1
  50. package/lib/cloudflare/bucket-object.js +1 -1
  51. package/lib/cloudflare/bucket-object.js.map +1 -1
  52. package/lib/cloudflare/bucket.d.ts +6 -3
  53. package/lib/cloudflare/bucket.d.ts.map +1 -1
  54. package/lib/cloudflare/bucket.js +60 -8
  55. package/lib/cloudflare/bucket.js.map +1 -1
  56. package/lib/cloudflare/bun-spa/bun-spa.d.ts +15 -0
  57. package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -0
  58. package/lib/cloudflare/bun-spa/bun-spa.js +95 -0
  59. package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -0
  60. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  61. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  62. package/lib/cloudflare/container.d.ts +11 -1
  63. package/lib/cloudflare/container.d.ts.map +1 -1
  64. package/lib/cloudflare/container.js +1 -1
  65. package/lib/cloudflare/container.js.map +1 -1
  66. package/lib/cloudflare/index.d.ts +1 -0
  67. package/lib/cloudflare/index.d.ts.map +1 -1
  68. package/lib/cloudflare/index.js +1 -0
  69. package/lib/cloudflare/index.js.map +1 -1
  70. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  71. package/lib/cloudflare/miniflare/build-worker-options.js +3 -3
  72. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  73. package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
  74. package/lib/cloudflare/miniflare/miniflare-controller.js +2 -0
  75. package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
  76. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts +1 -1
  77. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
  78. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +33 -14
  79. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
  80. package/lib/cloudflare/permission-groups.d.ts +2 -3
  81. package/lib/cloudflare/permission-groups.d.ts.map +1 -1
  82. package/lib/cloudflare/permission-groups.js +10 -15
  83. package/lib/cloudflare/permission-groups.js.map +1 -1
  84. package/lib/cloudflare/redwood/redwood.d.ts +1 -1
  85. package/lib/cloudflare/redwood/redwood.js +1 -1
  86. package/lib/cloudflare/tanstack-start/plugin.d.ts +2 -15
  87. package/lib/cloudflare/tanstack-start/plugin.d.ts.map +1 -1
  88. package/lib/cloudflare/tanstack-start/plugin.js +5 -25
  89. package/lib/cloudflare/tanstack-start/plugin.js.map +1 -1
  90. package/lib/cloudflare/tanstack-start/tanstack-start.d.ts.map +1 -1
  91. package/lib/cloudflare/tanstack-start/tanstack-start.js +15 -4
  92. package/lib/cloudflare/tanstack-start/tanstack-start.js.map +1 -1
  93. package/lib/cloudflare/util/filter-env-bindings.d.ts +62 -0
  94. package/lib/cloudflare/util/filter-env-bindings.d.ts.map +1 -0
  95. package/lib/cloudflare/util/filter-env-bindings.js +81 -0
  96. package/lib/cloudflare/util/filter-env-bindings.js.map +1 -0
  97. package/lib/cloudflare/website.d.ts +2 -2
  98. package/lib/cloudflare/website.d.ts.map +1 -1
  99. package/lib/cloudflare/website.js +7 -18
  100. package/lib/cloudflare/website.js.map +1 -1
  101. package/lib/cloudflare/worker-metadata.d.ts +5 -1
  102. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  103. package/lib/cloudflare/worker-metadata.js +3 -0
  104. package/lib/cloudflare/worker-metadata.js.map +1 -1
  105. package/lib/cloudflare/worker.d.ts +17 -1
  106. package/lib/cloudflare/worker.d.ts.map +1 -1
  107. package/lib/cloudflare/worker.js +1 -0
  108. package/lib/cloudflare/worker.js.map +1 -1
  109. package/lib/cloudflare/wrangler.json.d.ts +15 -1
  110. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  111. package/lib/cloudflare/wrangler.json.js +12 -4
  112. package/lib/cloudflare/wrangler.json.js.map +1 -1
  113. package/lib/coinbase/client.d.ts +54 -0
  114. package/lib/coinbase/client.d.ts.map +1 -0
  115. package/lib/coinbase/client.js +89 -0
  116. package/lib/coinbase/client.js.map +1 -0
  117. package/lib/coinbase/evm-account.d.ts +118 -0
  118. package/lib/coinbase/evm-account.d.ts.map +1 -0
  119. package/lib/coinbase/evm-account.js +198 -0
  120. package/lib/coinbase/evm-account.js.map +1 -0
  121. package/lib/coinbase/evm-smart-account.d.ts +122 -0
  122. package/lib/coinbase/evm-smart-account.d.ts.map +1 -0
  123. package/lib/coinbase/evm-smart-account.js +213 -0
  124. package/lib/coinbase/evm-smart-account.js.map +1 -0
  125. package/lib/coinbase/faucet.d.ts +11 -0
  126. package/lib/coinbase/faucet.d.ts.map +1 -0
  127. package/lib/coinbase/faucet.js +146 -0
  128. package/lib/coinbase/faucet.js.map +1 -0
  129. package/lib/coinbase/index.d.ts +4 -0
  130. package/lib/coinbase/index.d.ts.map +1 -0
  131. package/lib/coinbase/index.js +4 -0
  132. package/lib/coinbase/index.js.map +1 -0
  133. package/lib/coinbase/types.d.ts +28 -0
  134. package/lib/coinbase/types.d.ts.map +1 -0
  135. package/lib/coinbase/types.js +4 -0
  136. package/lib/{util/telemetry → coinbase}/types.js.map +1 -1
  137. package/lib/coinbase/utils.d.ts +13 -0
  138. package/lib/coinbase/utils.d.ts.map +1 -0
  139. package/lib/coinbase/utils.js +17 -0
  140. package/lib/coinbase/utils.js.map +1 -0
  141. package/lib/docker/api.d.ts +22 -1
  142. package/lib/docker/api.d.ts.map +1 -1
  143. package/lib/docker/api.js +64 -2
  144. package/lib/docker/api.js.map +1 -1
  145. package/lib/docker/container.d.ts +88 -0
  146. package/lib/docker/container.d.ts.map +1 -1
  147. package/lib/docker/container.js +36 -0
  148. package/lib/docker/container.js.map +1 -1
  149. package/lib/neon/api/types.gen.d.ts +7 -2
  150. package/lib/neon/api/types.gen.d.ts.map +1 -1
  151. package/lib/planetscale/api/types.gen.d.ts +20 -4
  152. package/lib/planetscale/api/types.gen.d.ts.map +1 -1
  153. package/lib/planetscale/api.d.ts.map +1 -1
  154. package/lib/planetscale/api.js +1 -0
  155. package/lib/planetscale/api.js.map +1 -1
  156. package/lib/planetscale/password.d.ts.map +1 -1
  157. package/lib/planetscale/password.js +1 -13
  158. package/lib/planetscale/password.js.map +1 -1
  159. package/lib/resource.d.ts.map +1 -1
  160. package/lib/resource.js +7 -3
  161. package/lib/resource.js.map +1 -1
  162. package/lib/scope.d.ts +4 -9
  163. package/lib/scope.d.ts.map +1 -1
  164. package/lib/scope.js +16 -37
  165. package/lib/scope.js.map +1 -1
  166. package/lib/state/instrumented-state-store.d.ts +1 -3
  167. package/lib/state/instrumented-state-store.d.ts.map +1 -1
  168. package/lib/state/instrumented-state-store.js +19 -21
  169. package/lib/state/instrumented-state-store.js.map +1 -1
  170. package/lib/test/bun.d.ts.map +1 -1
  171. package/lib/test/bun.js +1 -2
  172. package/lib/test/bun.js.map +1 -1
  173. package/lib/test/vitest.d.ts.map +1 -1
  174. package/lib/test/vitest.js +1 -2
  175. package/lib/test/vitest.js.map +1 -1
  176. package/lib/util/diff.d.ts +5 -0
  177. package/lib/util/diff.d.ts.map +1 -0
  178. package/lib/util/diff.js +14 -0
  179. package/lib/util/diff.js.map +1 -0
  180. package/lib/util/telemetry.d.ts +52 -0
  181. package/lib/util/telemetry.d.ts.map +1 -0
  182. package/lib/util/telemetry.js +191 -0
  183. package/lib/util/telemetry.js.map +1 -0
  184. package/package.json +19 -1
  185. package/src/alchemy.ts +5 -20
  186. package/src/apply.ts +29 -12
  187. package/src/aws/utils.ts +2 -1
  188. package/src/clickhouse/api/client.gen.ts +28 -0
  189. package/src/clickhouse/api/sdk.gen.ts +1286 -0
  190. package/src/clickhouse/api/types.gen.ts +5898 -0
  191. package/src/clickhouse/api-key.ts +136 -0
  192. package/src/clickhouse/api.ts +84 -0
  193. package/src/clickhouse/index.ts +3 -0
  194. package/src/clickhouse/organization.ts +31 -0
  195. package/src/clickhouse/service.ts +668 -0
  196. package/src/cloudflare/account-api-token.ts +1 -4
  197. package/src/cloudflare/bucket-object.ts +11 -1
  198. package/src/cloudflare/bucket.ts +72 -6
  199. package/src/cloudflare/bun-spa/bun-spa.ts +139 -0
  200. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  201. package/src/cloudflare/container.ts +22 -3
  202. package/src/cloudflare/index.ts +1 -0
  203. package/src/cloudflare/miniflare/build-worker-options.ts +6 -4
  204. package/src/cloudflare/miniflare/miniflare-controller.ts +2 -0
  205. package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +42 -15
  206. package/src/cloudflare/permission-groups.ts +21 -49
  207. package/src/cloudflare/redwood/redwood.ts +1 -1
  208. package/src/cloudflare/tanstack-start/plugin.ts +7 -28
  209. package/src/cloudflare/tanstack-start/tanstack-start.ts +15 -4
  210. package/src/cloudflare/util/filter-env-bindings.ts +96 -0
  211. package/src/cloudflare/website.ts +12 -24
  212. package/src/cloudflare/worker-metadata.ts +8 -1
  213. package/src/cloudflare/worker.ts +19 -1
  214. package/src/cloudflare/wrangler.json.ts +27 -9
  215. package/src/coinbase/client.ts +146 -0
  216. package/src/coinbase/evm-account.ts +317 -0
  217. package/src/coinbase/evm-smart-account.ts +339 -0
  218. package/src/coinbase/faucet.ts +173 -0
  219. package/src/coinbase/index.ts +3 -0
  220. package/src/coinbase/types.ts +33 -0
  221. package/src/coinbase/utils.ts +19 -0
  222. package/src/docker/api.ts +86 -2
  223. package/src/docker/container.ts +97 -0
  224. package/src/neon/api/types.gen.ts +13 -1
  225. package/src/planetscale/api/types.gen.ts +22 -2
  226. package/src/planetscale/api.ts +1 -0
  227. package/src/planetscale/password.ts +1 -14
  228. package/src/resource.ts +11 -4
  229. package/src/scope.ts +22 -45
  230. package/src/state/instrumented-state-store.ts +27 -26
  231. package/src/test/bun.ts +1 -2
  232. package/src/test/vitest.ts +1 -2
  233. package/src/util/diff.ts +14 -0
  234. package/src/util/telemetry.ts +297 -0
  235. package/templates/tanstack-start/.cta.json +13 -0
  236. package/templates/tanstack-start/README.md +267 -49
  237. package/templates/tanstack-start/_gitignore +9 -18
  238. package/templates/tanstack-start/alchemy.run.ts +2 -2
  239. package/templates/tanstack-start/package.json +24 -19
  240. package/templates/tanstack-start/public/favicon.ico +0 -0
  241. package/templates/tanstack-start/public/logo192.png +0 -0
  242. package/templates/tanstack-start/public/logo512.png +0 -0
  243. package/templates/tanstack-start/public/manifest.json +25 -0
  244. package/templates/tanstack-start/public/robots.txt +3 -0
  245. package/templates/tanstack-start/src/components/Header.tsx +21 -0
  246. package/templates/tanstack-start/src/logo.svg +12 -0
  247. package/templates/tanstack-start/src/router.tsx +7 -16
  248. package/templates/tanstack-start/src/routes/__root.tsx +13 -114
  249. package/templates/tanstack-start/src/routes/api.demo-names.ts +15 -0
  250. package/templates/tanstack-start/src/routes/demo.start.api-request.tsx +44 -0
  251. package/templates/tanstack-start/src/routes/demo.start.server-funcs.tsx +97 -0
  252. package/templates/tanstack-start/src/routes/index.tsx +31 -4
  253. package/templates/tanstack-start/src/styles.css +15 -0
  254. package/templates/tanstack-start/tsconfig.json +19 -22
  255. package/templates/tanstack-start/types/env.d.ts +2 -2
  256. package/templates/tanstack-start/vite.config.ts +16 -25
  257. package/workers/tunnel-proxy.js +1 -1
  258. package/lib/util/telemetry/client.d.ts +0 -31
  259. package/lib/util/telemetry/client.d.ts.map +0 -1
  260. package/lib/util/telemetry/client.js +0 -121
  261. package/lib/util/telemetry/client.js.map +0 -1
  262. package/lib/util/telemetry/constants.d.ts +0 -5
  263. package/lib/util/telemetry/constants.d.ts.map +0 -1
  264. package/lib/util/telemetry/constants.js +0 -8
  265. package/lib/util/telemetry/constants.js.map +0 -1
  266. package/lib/util/telemetry/context.d.ts +0 -15
  267. package/lib/util/telemetry/context.d.ts.map +0 -1
  268. package/lib/util/telemetry/context.js +0 -128
  269. package/lib/util/telemetry/context.js.map +0 -1
  270. package/lib/util/telemetry/index.d.ts +0 -3
  271. package/lib/util/telemetry/index.d.ts.map +0 -1
  272. package/lib/util/telemetry/index.js +0 -3
  273. package/lib/util/telemetry/index.js.map +0 -1
  274. package/lib/util/telemetry/types.d.ts +0 -68
  275. package/lib/util/telemetry/types.d.ts.map +0 -1
  276. package/lib/util/telemetry/types.js +0 -1
  277. package/src/util/telemetry/client.ts +0 -156
  278. package/src/util/telemetry/constants.ts +0 -13
  279. package/src/util/telemetry/context.ts +0 -154
  280. package/src/util/telemetry/index.ts +0 -2
  281. package/src/util/telemetry/types.ts +0 -105
  282. package/templates/tanstack-start/_prettierignore +0 -4
  283. package/templates/tanstack-start/public/android-chrome-192x192.png +0 -0
  284. package/templates/tanstack-start/public/android-chrome-512x512.png +0 -0
  285. package/templates/tanstack-start/public/apple-touch-icon.png +0 -0
  286. package/templates/tanstack-start/public/favicon-16x16.png +0 -0
  287. package/templates/tanstack-start/public/favicon-32x32.png +0 -0
  288. package/templates/tanstack-start/public/favicon.png +0 -0
  289. package/templates/tanstack-start/public/site.webmanifest +0 -19
  290. package/templates/tanstack-start/src/components/DefaultCatchBoundary.tsx +0 -53
  291. package/templates/tanstack-start/src/components/NotFound.tsx +0 -25
  292. package/templates/tanstack-start/src/components/PostError.tsx +0 -5
  293. package/templates/tanstack-start/src/components/UserError.tsx +0 -5
  294. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-a.tsx +0 -10
  295. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-b.tsx +0 -10
  296. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout.tsx +0 -34
  297. package/templates/tanstack-start/src/routes/_pathlessLayout.tsx +0 -16
  298. package/templates/tanstack-start/src/routes/api/users.$userId.ts +0 -28
  299. package/templates/tanstack-start/src/routes/api/users.ts +0 -59
  300. package/templates/tanstack-start/src/routes/customScript[.]js.ts +0 -10
  301. package/templates/tanstack-start/src/routes/deferred.tsx +0 -62
  302. package/templates/tanstack-start/src/routes/posts.$postId.tsx +0 -34
  303. package/templates/tanstack-start/src/routes/posts.index.tsx +0 -8
  304. package/templates/tanstack-start/src/routes/posts.tsx +0 -38
  305. package/templates/tanstack-start/src/routes/posts_.$postId.deep.tsx +0 -29
  306. package/templates/tanstack-start/src/routes/redirect.tsx +0 -9
  307. package/templates/tanstack-start/src/routes/users.$userId.tsx +0 -44
  308. package/templates/tanstack-start/src/routes/users.index.tsx +0 -18
  309. package/templates/tanstack-start/src/routes/users.tsx +0 -49
  310. package/templates/tanstack-start/src/styles/app.css +0 -12
  311. package/templates/tanstack-start/src/utils/loggingMiddleware.tsx +0 -41
  312. package/templates/tanstack-start/src/utils/posts.tsx +0 -40
  313. package/templates/tanstack-start/src/utils/seo.ts +0 -33
  314. package/templates/tanstack-start/src/utils/users.tsx +0 -5
package/src/scope.ts CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  import { logger } from "./util/logger.ts";
30
30
  import { AsyncMutex } from "./util/mutex.ts";
31
31
  import { ALCHEMY_ROOT } from "./util/root-dir.ts";
32
- import type { ITelemetryClient } from "./util/telemetry/client.ts";
32
+ import { createAndSendEvent } from "./util/telemetry.ts";
33
33
 
34
34
  export class RootScopeStateAttemptError extends Error {
35
35
  constructor() {
@@ -76,10 +76,11 @@ export interface ScopeOptions extends ProviderCredentials {
76
76
  */
77
77
  destroyStrategy?: DestroyStrategy;
78
78
  /**
79
- * The telemetry client to use for the scope.
79
+ * Whether to disable telemetry for the scope.
80
80
  *
81
+ * @default false
81
82
  */
82
- telemetryClient?: ITelemetryClient;
83
+ noTrack?: boolean;
83
84
  /**
84
85
  * The logger to use for the scope.
85
86
  */
@@ -197,7 +198,7 @@ export class Scope {
197
198
  public readonly adopt: boolean;
198
199
  public readonly destroyStrategy: DestroyStrategy;
199
200
  public readonly logger: LoggerApi;
200
- public readonly telemetryClient: ITelemetryClient;
201
+ public readonly noTrack: boolean;
201
202
  public readonly dataMutex: AsyncMutex;
202
203
  public readonly rootDir: string;
203
204
  public readonly dotAlchemy: string;
@@ -235,12 +236,12 @@ export class Scope {
235
236
  tunnel,
236
237
  force,
237
238
  destroyStrategy,
238
- telemetryClient,
239
239
  logger,
240
240
  adopt,
241
241
  dotAlchemy,
242
242
  rootDir,
243
243
  isSelected,
244
+ noTrack,
244
245
  ...providerCredentials
245
246
  } = options;
246
247
 
@@ -273,6 +274,7 @@ export class Scope {
273
274
  throw new Error("Scope name is required when creating a child scope");
274
275
  }
275
276
  this.password = password ?? this.parent?.password;
277
+ this.noTrack = noTrack ?? this.parent?.noTrack ?? false;
276
278
  const resolvedPhase = phase ?? this.parent?.phase;
277
279
  if (resolvedPhase === undefined) {
278
280
  throw new Error("Phase is required");
@@ -307,14 +309,7 @@ export class Scope {
307
309
  stateStore ??
308
310
  this.parent?.stateStore ??
309
311
  ((scope) => new FileSystemStateStore(scope));
310
- this.telemetryClient = telemetryClient ?? this.parent?.telemetryClient!;
311
- this.state = new InstrumentedStateStore(
312
- this.stateStore(this),
313
- this.telemetryClient,
314
- );
315
- if (!telemetryClient && !this.parent?.telemetryClient) {
316
- throw new Error("Telemetry client is required");
317
- }
312
+ this.state = new InstrumentedStateStore(this.stateStore(this));
318
313
  this.dataMutex = new AsyncMutex();
319
314
  }
320
315
 
@@ -413,12 +408,7 @@ export class Scope {
413
408
  }
414
409
 
415
410
  public async init() {
416
- await Promise.all([
417
- this.state.init?.(),
418
- this.telemetryClient.ready.catch((error) => {
419
- this.logger.warn("Telemetry initialization failed:", error);
420
- }),
421
- ]);
411
+ await Promise.all([this.state.init?.()]);
422
412
  }
423
413
 
424
414
  public async deinit() {
@@ -515,26 +505,15 @@ export class Scope {
515
505
  return this.finalize();
516
506
  }
517
507
 
518
- /**
519
- * The telemetry client for the root scope.
520
- * This is used so that app-level hooks are only called once.
521
- */
522
- private get rootTelemetryClient(): ITelemetryClient | null {
523
- if (!this.parent) {
524
- return this.telemetryClient;
525
- }
526
- return null;
527
- }
528
-
529
508
  public async finalize(options?: { force?: boolean; noop?: boolean }) {
530
509
  const shouldForce =
531
510
  options?.force ||
532
511
  this.parent === undefined ||
533
512
  this?.parent?.scopeName === this.root.scopeName;
534
513
  if (this.phase === "read") {
535
- this.rootTelemetryClient?.record({
536
- event: "app.success",
537
- elapsed: performance.now() - this.startedAt,
514
+ createAndSendEvent({
515
+ event: "alchemy.success",
516
+ duration: performance.now() - this.startedAt,
538
517
  });
539
518
  return;
540
519
  }
@@ -583,23 +562,21 @@ export class Scope {
583
562
  force: shouldForce,
584
563
  noop: options?.noop,
585
564
  });
586
- this.rootTelemetryClient?.record({
587
- event: "app.success",
588
- elapsed: performance.now() - this.startedAt,
565
+ createAndSendEvent({
566
+ event: "alchemy.success",
567
+ duration: performance.now() - this.startedAt,
589
568
  });
590
569
  } else if (this.isErrored) {
591
570
  this.logger.warn("Scope is in error, skipping finalize");
592
- this.rootTelemetryClient?.record({
593
- event: "app.error",
594
- error: new Error("Scope failed"),
595
- elapsed: performance.now() - this.startedAt,
596
- });
571
+ createAndSendEvent(
572
+ {
573
+ event: "alchemy.error",
574
+ duration: performance.now() - this.startedAt,
575
+ },
576
+ new Error("Scope failed"),
577
+ );
597
578
  }
598
579
 
599
- await this.rootTelemetryClient?.finalize()?.catch((error) => {
600
- this.logger.warn("Telemetry finalization failed:", error);
601
- });
602
-
603
580
  if (!this.parent && process.env.ALCHEMY_TEST_KILL_ON_FINALIZE) {
604
581
  await this.cleanup();
605
582
  process.exit(0);
@@ -1,6 +1,8 @@
1
1
  import type { State, StateStore } from "../state.ts";
2
- import type { ITelemetryClient } from "../util/telemetry/client.ts";
3
- import type { Telemetry } from "../util/telemetry/types.ts";
2
+ import {
3
+ createAndSendEvent,
4
+ type StateStoreTelemetryData,
5
+ } from "../util/telemetry.ts";
4
6
 
5
7
  //todo(michael): we should also handle serde here
6
8
  export class InstrumentedStateStore<T extends StateStore>
@@ -9,17 +11,15 @@ export class InstrumentedStateStore<T extends StateStore>
9
11
  /** @internal */
10
12
  __phantom?: T;
11
13
  private readonly stateStore: StateStore;
12
- private readonly telemetryClient: ITelemetryClient;
13
14
  private readonly stateStoreClass: string;
14
15
 
15
- constructor(stateStore: StateStore, telemetryClient: ITelemetryClient) {
16
+ constructor(stateStore: StateStore) {
16
17
  this.stateStore = stateStore;
17
- this.telemetryClient = telemetryClient;
18
18
  this.stateStoreClass = stateStore.constructor.name;
19
19
  }
20
20
 
21
21
  private async callWithTelemetry<T>(
22
- event: Telemetry.StateStoreEvent["event"],
22
+ event: StateStoreTelemetryData["event"],
23
23
  fn: () => Promise<T>,
24
24
  ): Promise<T> {
25
25
  const start = performance.now();
@@ -30,17 +30,18 @@ export class InstrumentedStateStore<T extends StateStore>
30
30
  error = err;
31
31
  throw err;
32
32
  } finally {
33
- this.telemetryClient.record({
34
- event,
35
- stateStoreClass: this.stateStoreClass,
36
- elapsed: performance.now() - start,
37
- error:
38
- error instanceof Error
39
- ? error
40
- : error
41
- ? new Error(String(error))
42
- : undefined,
43
- });
33
+ createAndSendEvent(
34
+ {
35
+ event,
36
+ stateStore: this.stateStoreClass,
37
+ duration: performance.now() - start,
38
+ },
39
+ error instanceof Error
40
+ ? error
41
+ : error
42
+ ? new Error(String(error))
43
+ : undefined,
44
+ );
44
45
  }
45
46
  }
46
47
 
@@ -49,7 +50,7 @@ export class InstrumentedStateStore<T extends StateStore>
49
50
  return;
50
51
  }
51
52
  await this.callWithTelemetry(
52
- "stateStore.init",
53
+ "statestore.init",
53
54
  this.stateStore.init.bind(this.stateStore),
54
55
  );
55
56
  }
@@ -58,49 +59,49 @@ export class InstrumentedStateStore<T extends StateStore>
58
59
  return;
59
60
  }
60
61
  await this.callWithTelemetry(
61
- "stateStore.deinit",
62
+ "statestore.deinit",
62
63
  this.stateStore.deinit.bind(this.stateStore),
63
64
  );
64
65
  }
65
66
  async list() {
66
67
  return await this.callWithTelemetry(
67
- "stateStore.list",
68
+ "statestore.list",
68
69
  this.stateStore.list.bind(this.stateStore),
69
70
  );
70
71
  }
71
72
  async count() {
72
73
  return await this.callWithTelemetry(
73
- "stateStore.count",
74
+ "statestore.count",
74
75
  this.stateStore.count.bind(this.stateStore),
75
76
  );
76
77
  }
77
78
  async get(key: string) {
78
79
  return await this.callWithTelemetry(
79
- "stateStore.get",
80
+ "statestore.get",
80
81
  this.stateStore.get.bind(this.stateStore, key),
81
82
  );
82
83
  }
83
84
  async getBatch(ids: string[]) {
84
85
  return await this.callWithTelemetry(
85
- "stateStore.getBatch",
86
+ "statestore.getBatch",
86
87
  this.stateStore.getBatch.bind(this.stateStore, ids),
87
88
  );
88
89
  }
89
90
  async all() {
90
91
  return await this.callWithTelemetry(
91
- "stateStore.all",
92
+ "statestore.all",
92
93
  this.stateStore.all.bind(this.stateStore),
93
94
  );
94
95
  }
95
96
  async set(key: string, value: State) {
96
97
  await this.callWithTelemetry(
97
- "stateStore.set",
98
+ "statestore.set",
98
99
  this.stateStore.set.bind(this.stateStore, key, value),
99
100
  );
100
101
  }
101
102
  async delete(key: string) {
102
103
  await this.callWithTelemetry(
103
- "stateStore.delete",
104
+ "statestore.delete",
104
105
  this.stateStore.delete.bind(this.stateStore, key),
105
106
  );
106
107
  }
package/src/test/bun.ts CHANGED
@@ -4,7 +4,6 @@ import { afterAll, beforeAll, it } from "bun:test";
4
4
  import path from "node:path";
5
5
  import { alchemy } from "../alchemy.ts";
6
6
  import { Scope } from "../scope.ts";
7
- import { NoopTelemetryClient } from "../util/telemetry/index.ts";
8
7
  import type { TestOptions } from "./options.ts";
9
8
 
10
9
  /**
@@ -103,7 +102,7 @@ export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
103
102
  // parent: globalTestScope,
104
103
  stateStore: defaultOptions?.stateStore,
105
104
  phase: "up",
106
- telemetryClient: new NoopTelemetryClient(),
105
+ noTrack: true,
107
106
  local: defaultOptions.local,
108
107
  });
109
108
 
@@ -8,7 +8,6 @@ import {
8
8
  FileSystemStateStore,
9
9
  SQLiteStateStore,
10
10
  } from "../state/index.ts";
11
- import { NoopTelemetryClient } from "../util/telemetry/client.ts";
12
11
  import type { TestOptions } from "./options.ts";
13
12
 
14
13
  /**
@@ -132,7 +131,7 @@ export function test(
132
131
  scopeName: `${defaultOptions.prefix ? `${defaultOptions.prefix}-` : ""}${path.basename(meta.filename)}`,
133
132
  stateStore: defaultOptions?.stateStore,
134
133
  phase: "up",
135
- telemetryClient: new NoopTelemetryClient(),
134
+ noTrack: true,
136
135
  quiet: defaultOptions.quiet,
137
136
  password: process.env.ALCHEMY_PASSWORD,
138
137
  local: defaultOptions.local,
@@ -0,0 +1,14 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+
3
+ /**
4
+ * Returns an array of keys in `b` that are different from `a`.
5
+ */
6
+ export const diff = <T>(a: T, b: NoInfer<T>) => {
7
+ const keys: (keyof T)[] = [];
8
+ for (const key in a) {
9
+ if (!isDeepStrictEqual(a[key], b[key])) {
10
+ keys.push(key);
11
+ }
12
+ }
13
+ return keys;
14
+ };
@@ -0,0 +1,297 @@
1
+ import envPaths from "env-paths";
2
+ import { exec } from "node:child_process";
3
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "pathe";
6
+ import pkg from "../../package.json" with { type: "json" };
7
+ import type { Phase } from "../alchemy.ts";
8
+ import { Scope } from "../scope.ts";
9
+ import { logger } from "./logger.ts";
10
+ import { memoize } from "./memoize.ts";
11
+
12
+ export const CONFIG_PATH = path.join(os.homedir(), ".alchemy", "id");
13
+ export const CONFIG_PATH_LEGACY = path.join(
14
+ envPaths("alchemy", { suffix: "" }).config,
15
+ "id",
16
+ );
17
+
18
+ export const TELEMETRY_DISABLED =
19
+ !!process.env.ALCHEMY_TELEMETRY_DISABLED || !!process.env.DO_NOT_TRACK;
20
+
21
+ export const TELEMETRY_API_URL =
22
+ process.env.ALCHEMY_TELEMETRY_API_URL ?? "https://telemetry.alchemy.run";
23
+ export const SUPPRESS_TELEMETRY_ERRORS =
24
+ !!process.env.ALCHEMY_TELEMETRY_SUPPRESS_ERRORS;
25
+
26
+ async function getOrCreateUserId() {
27
+ async function readUserId(path: string) {
28
+ try {
29
+ return (await readFile(path, "utf-8")).trim();
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ const id = await readUserId(CONFIG_PATH);
36
+ if (id) {
37
+ return id;
38
+ }
39
+
40
+ const legacyId = await readUserId(CONFIG_PATH_LEGACY);
41
+
42
+ try {
43
+ const id = legacyId ?? crypto.randomUUID();
44
+ await mkdir(path.dirname(CONFIG_PATH), { recursive: true });
45
+ await writeFile(CONFIG_PATH, id);
46
+ if (!legacyId) {
47
+ console.warn(
48
+ [
49
+ "Attention: To help improve Alchemy, we collect anonymous usage, performance, and error data.",
50
+ "You can opt out by setting the ALCHEMY_TELEMETRY_DISABLED or DO_NOT_TRACK environment variable to a truthy value.",
51
+ ].join("\n"),
52
+ );
53
+ }
54
+ return id;
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+
60
+ async function getRootCommitHash() {
61
+ return new Promise<string | null>((resolve) => {
62
+ exec("git rev-list --max-parents=0 HEAD", (err, stdout) => {
63
+ if (err) {
64
+ resolve(null);
65
+ return;
66
+ }
67
+ resolve(stdout.trim());
68
+ });
69
+ });
70
+ }
71
+
72
+ async function getGitOriginUrlHash() {
73
+ return new Promise<string | null>((resolve) => {
74
+ exec("git config --get remote.origin.url", (err, stdout) => {
75
+ if (err) {
76
+ resolve(null);
77
+ return;
78
+ }
79
+ resolve(hashString(stdout.trim()));
80
+ });
81
+ });
82
+ }
83
+
84
+ async function getBranchName() {
85
+ return new Promise<string | null>((resolve) => {
86
+ exec("git rev-parse --abbrev-ref HEAD", (err, stdout) => {
87
+ if (err) {
88
+ resolve(null);
89
+ return;
90
+ }
91
+ resolve(stdout.trim());
92
+ });
93
+ });
94
+ }
95
+
96
+ function getRuntime() {
97
+ if (globalThis.Bun) return { name: "bun", version: Bun.version };
98
+ // @ts-expect-error
99
+ if (globalThis.Deno)
100
+ // @ts-expect-error
101
+ return { name: "deno", version: Deno.version?.deno ?? null };
102
+ // @ts-expect-error
103
+ if (globalThis.EdgeRuntime) return { name: "workerd", version: null };
104
+ if (globalThis.process?.versions?.node)
105
+ return { name: "node", version: process.versions.node };
106
+ return { name: null, version: null };
107
+ }
108
+
109
+ const PROVIDERS = [
110
+ { env: "GITHUB_ACTIONS", provider: "GitHub Actions", isCI: true },
111
+ { env: "GITLAB_CI", provider: "GitLab CI", isCI: true },
112
+ { env: "CIRCLECI", provider: "CircleCI", isCI: true },
113
+ { env: "JENKINS_URL", provider: "Jenkins", isCI: true },
114
+ { env: "TRAVIS", provider: "Travis CI", isCI: true },
115
+ { env: "NOW_BUILDER", provider: "Vercel", isCI: true },
116
+ { env: "VERCEL", provider: "Vercel", isCI: false },
117
+ ] as const;
118
+
119
+ function getEnvironment() {
120
+ for (const provider of PROVIDERS) {
121
+ if (process.env[provider.env]) {
122
+ return {
123
+ provider: provider.provider,
124
+ isCI: provider.isCI,
125
+ };
126
+ }
127
+ }
128
+ return {
129
+ provider: null,
130
+ isCI: !!process.env.CI,
131
+ };
132
+ }
133
+
134
+ export const collectData = memoize(async (): Promise<GenericTelemetryData> => {
135
+ const [
136
+ userId,
137
+ rootCommitHash,
138
+ gitOriginUrlHash,
139
+ branchHash,
140
+ runtime,
141
+ environment,
142
+ ] = await Promise.all([
143
+ getOrCreateUserId(),
144
+ getRootCommitHash(),
145
+ getGitOriginUrlHash(),
146
+ getBranchName().then(hashString),
147
+ getRuntime(),
148
+ getEnvironment(),
149
+ ]);
150
+ return {
151
+ userId: userId ?? "",
152
+ sessionId: crypto.randomUUID(),
153
+ platform: os.platform(),
154
+ osVersion: os.release(),
155
+ arch: os.arch(),
156
+ cpus: os.cpus().length,
157
+ memory: Math.round(os.totalmem() / 1024 / 1024),
158
+ rootCommitHash: rootCommitHash ?? "",
159
+ gitOriginUrl: gitOriginUrlHash ?? "",
160
+ gitBranchHash: branchHash ?? "",
161
+ runtime: runtime.name ?? "",
162
+ runtimeVersion: runtime.version ?? "",
163
+ ciProvider: environment.provider ?? "",
164
+ isCI: environment.isCI,
165
+ alchemyVersion: pkg.version,
166
+ };
167
+ });
168
+
169
+ export type GenericTelemetryData = {
170
+ userId: string;
171
+ sessionId: string;
172
+ platform: string;
173
+ osVersion: string;
174
+ arch: string;
175
+ cpus: number;
176
+ memory: number;
177
+ rootCommitHash: string;
178
+ gitOriginUrl: string;
179
+ gitBranchHash: string;
180
+ runtime: string;
181
+ runtimeVersion: string;
182
+ ciProvider: string;
183
+ isCI: boolean;
184
+ alchemyVersion: string;
185
+ };
186
+
187
+ export type ErrorData = {
188
+ errorTag: string;
189
+ errorMessage: string;
190
+ errorStack: string;
191
+ };
192
+
193
+ export type CliTelemetryData = {
194
+ command: string;
195
+ event: "cli.start" | "cli.success" | "cli.error";
196
+ };
197
+
198
+ export type ResourceTelemetryData = {
199
+ phase: Phase;
200
+ event:
201
+ | "resource.start"
202
+ | "resource.success"
203
+ | "resource.error"
204
+ | "resource.skip"
205
+ | "resource.read";
206
+ resource: string;
207
+ status:
208
+ | "creating"
209
+ | "created"
210
+ | "updating"
211
+ | "updated"
212
+ | "deleting"
213
+ | "deleted"
214
+ | "unknown";
215
+ duration: number;
216
+ replaced: boolean;
217
+ };
218
+
219
+ export type StateStoreTelemetryData = {
220
+ event:
221
+ | "statestore.init"
222
+ | "statestore.deinit"
223
+ | "statestore.list"
224
+ | "statestore.count"
225
+ | "statestore.get"
226
+ | "statestore.getBatch"
227
+ | "statestore.all"
228
+ | "statestore.set"
229
+ | "statestore.delete";
230
+ stateStore: string;
231
+ duration: number;
232
+ };
233
+
234
+ export type AlchemyTelemetryData = {
235
+ event: "alchemy.start" | "alchemy.success" | "alchemy.error";
236
+ duration: number;
237
+ };
238
+
239
+ export async function createAndSendEvent(
240
+ data:
241
+ | CliTelemetryData
242
+ | ResourceTelemetryData
243
+ | StateStoreTelemetryData
244
+ | AlchemyTelemetryData,
245
+ error?: Error,
246
+ ) {
247
+ if (Scope.getScope()?.noTrack || TELEMETRY_DISABLED) {
248
+ return;
249
+ }
250
+ try {
251
+ const eventData = {
252
+ ...data,
253
+ ...("duration" in data
254
+ ? { duration: Math.round(data.duration * 1000) }
255
+ : {}),
256
+ ...(await collectData()),
257
+ ...serializeError(error),
258
+ };
259
+ await fetch(TELEMETRY_API_URL, {
260
+ method: "POST",
261
+ headers: {
262
+ "Content-Type": "application/json",
263
+ },
264
+ body: JSON.stringify(eventData),
265
+ });
266
+ } catch (error) {
267
+ if (!SUPPRESS_TELEMETRY_ERRORS) {
268
+ logger.warn("Failed to send telemetry event:", error);
269
+ }
270
+ }
271
+ }
272
+
273
+ async function hashString(input: string | null): Promise<string | null> {
274
+ if (input == null) {
275
+ return null;
276
+ }
277
+ const data = new TextEncoder().encode(input);
278
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
279
+ return Array.from(new Uint8Array(hashBuffer), (byte) =>
280
+ byte.toString(16).padStart(2, "0"),
281
+ ).join("");
282
+ }
283
+
284
+ function serializeError(error: Error | undefined) {
285
+ if (error instanceof Error) {
286
+ return {
287
+ errorTag: error.name ?? "",
288
+ errorMessage: error.message?.replaceAll(os.homedir(), "~") ?? "", // redact home directory
289
+ errorStack: error.stack?.replaceAll(os.homedir(), "~") ?? "",
290
+ };
291
+ }
292
+ return {
293
+ errorTag: "",
294
+ errorMessage: "",
295
+ errorStack: "",
296
+ };
297
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "projectName": "@alchemy.run/tanstack-start-template",
3
+ "mode": "file-router",
4
+ "typescript": true,
5
+ "tailwind": true,
6
+ "packageManager": "bun",
7
+ "git": true,
8
+ "version": 1,
9
+ "framework": "react-cra",
10
+ "chosenAddOns": [
11
+ "start"
12
+ ]
13
+ }