@hugomrdias/foxer 0.0.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 (311) hide show
  1. package/README.md +56 -0
  2. package/dist/src/api/index.d.ts +2 -0
  3. package/dist/src/api/index.d.ts.map +1 -0
  4. package/dist/src/api/index.js +2 -0
  5. package/dist/src/api/index.js.map +1 -0
  6. package/dist/src/api/runner.d.ts +12 -0
  7. package/dist/src/api/runner.d.ts.map +1 -0
  8. package/dist/src/api/runner.js +29 -0
  9. package/dist/src/api/runner.js.map +1 -0
  10. package/dist/src/api/server.d.ts +18 -0
  11. package/dist/src/api/server.d.ts.map +1 -0
  12. package/dist/src/api/server.js +21 -0
  13. package/dist/src/api/server.js.map +1 -0
  14. package/dist/src/api/sql-middleware.d.ts +7 -0
  15. package/dist/src/api/sql-middleware.d.ts.map +1 -0
  16. package/dist/src/api/sql-middleware.js +95 -0
  17. package/dist/src/api/sql-middleware.js.map +1 -0
  18. package/dist/src/api/sql.d.ts +14 -0
  19. package/dist/src/api/sql.d.ts.map +1 -0
  20. package/dist/src/api/sql.js +108 -0
  21. package/dist/src/api/sql.js.map +1 -0
  22. package/dist/src/api/sse.d.ts +3 -0
  23. package/dist/src/api/sse.d.ts.map +1 -0
  24. package/dist/src/api/sse.js +11 -0
  25. package/dist/src/api/sse.js.map +1 -0
  26. package/dist/src/bin/dev.d.ts +3 -0
  27. package/dist/src/bin/dev.d.ts.map +1 -0
  28. package/dist/src/bin/dev.js +76 -0
  29. package/dist/src/bin/dev.js.map +1 -0
  30. package/dist/src/bin/flags.d.ts +32 -0
  31. package/dist/src/bin/flags.d.ts.map +1 -0
  32. package/dist/src/bin/flags.js +55 -0
  33. package/dist/src/bin/flags.js.map +1 -0
  34. package/dist/src/bin/index.d.ts +3 -0
  35. package/dist/src/bin/index.d.ts.map +1 -0
  36. package/dist/src/bin/index.js +22 -0
  37. package/dist/src/bin/index.js.map +1 -0
  38. package/dist/src/bin/utils.d.ts +4 -0
  39. package/dist/src/bin/utils.d.ts.map +1 -0
  40. package/dist/src/bin/utils.js +52 -0
  41. package/dist/src/bin/utils.js.map +1 -0
  42. package/dist/src/client/index.d.ts +18 -0
  43. package/dist/src/client/index.d.ts.map +1 -0
  44. package/dist/src/client/index.js +150 -0
  45. package/dist/src/client/index.js.map +1 -0
  46. package/dist/src/config/config.d.ts +157 -0
  47. package/dist/src/config/config.d.ts.map +1 -0
  48. package/dist/src/config/config.js +65 -0
  49. package/dist/src/config/config.js.map +1 -0
  50. package/dist/src/config/env.d.ts +25 -0
  51. package/dist/src/config/env.d.ts.map +1 -0
  52. package/dist/src/config/env.js +21 -0
  53. package/dist/src/config/env.js.map +1 -0
  54. package/dist/src/contants.d.ts +4 -0
  55. package/dist/src/contants.d.ts.map +1 -0
  56. package/dist/src/contants.js +4 -0
  57. package/dist/src/contants.js.map +1 -0
  58. package/dist/src/db/actions/blocks.d.ts +44 -0
  59. package/dist/src/db/actions/blocks.d.ts.map +1 -0
  60. package/dist/src/db/actions/blocks.js +152 -0
  61. package/dist/src/db/actions/blocks.js.map +1 -0
  62. package/dist/src/db/actions/index.d.ts +2 -0
  63. package/dist/src/db/actions/index.d.ts.map +1 -0
  64. package/dist/src/db/actions/index.js +2 -0
  65. package/dist/src/db/actions/index.js.map +1 -0
  66. package/dist/src/db/actions/transactions.d.ts +11 -0
  67. package/dist/src/db/actions/transactions.d.ts.map +1 -0
  68. package/dist/src/db/actions/transactions.js +22 -0
  69. package/dist/src/db/actions/transactions.js.map +1 -0
  70. package/dist/src/db/client.d.ts +329 -0
  71. package/dist/src/db/client.d.ts.map +1 -0
  72. package/dist/src/db/client.js +108 -0
  73. package/dist/src/db/client.js.map +1 -0
  74. package/dist/src/db/column-types.d.ts +132 -0
  75. package/dist/src/db/column-types.d.ts.map +1 -0
  76. package/dist/src/db/column-types.js +86 -0
  77. package/dist/src/db/column-types.js.map +1 -0
  78. package/dist/src/db/encode.d.ts +10 -0
  79. package/dist/src/db/encode.d.ts.map +1 -0
  80. package/dist/src/db/encode.js +79 -0
  81. package/dist/src/db/encode.js.map +1 -0
  82. package/dist/src/db/migrate.d.ts +31 -0
  83. package/dist/src/db/migrate.d.ts.map +1 -0
  84. package/dist/src/db/migrate.js +147 -0
  85. package/dist/src/db/migrate.js.map +1 -0
  86. package/dist/src/db/schema/blocks.d.ts +369 -0
  87. package/dist/src/db/schema/blocks.d.ts.map +1 -0
  88. package/dist/src/db/schema/blocks.js +24 -0
  89. package/dist/src/db/schema/blocks.js.map +1 -0
  90. package/dist/src/db/schema/index.d.ts +1415 -0
  91. package/dist/src/db/schema/index.d.ts.map +1 -0
  92. package/dist/src/db/schema/index.js +18 -0
  93. package/dist/src/db/schema/index.js.map +1 -0
  94. package/dist/src/db/schema/transactions.d.ts +336 -0
  95. package/dist/src/db/schema/transactions.d.ts.map +1 -0
  96. package/dist/src/db/schema/transactions.js +33 -0
  97. package/dist/src/db/schema/transactions.js.map +1 -0
  98. package/dist/src/db/transaction.d.ts +7 -0
  99. package/dist/src/db/transaction.d.ts.map +1 -0
  100. package/dist/src/db/transaction.js +8 -0
  101. package/dist/src/db/transaction.js.map +1 -0
  102. package/dist/src/hooks/default-hooks.d.ts +2 -0
  103. package/dist/src/hooks/default-hooks.d.ts.map +1 -0
  104. package/dist/src/hooks/default-hooks.js +107 -0
  105. package/dist/src/hooks/default-hooks.js.map +1 -0
  106. package/dist/src/hooks/registry.d.ts +51 -0
  107. package/dist/src/hooks/registry.d.ts.map +1 -0
  108. package/dist/src/hooks/registry.js +32 -0
  109. package/dist/src/hooks/registry.js.map +1 -0
  110. package/dist/src/index.d.ts +10 -0
  111. package/dist/src/index.d.ts.map +1 -0
  112. package/dist/src/index.js +4 -0
  113. package/dist/src/index.js.map +1 -0
  114. package/dist/src/indexer/backfill.d.ts +15 -0
  115. package/dist/src/indexer/backfill.d.ts.map +1 -0
  116. package/dist/src/indexer/backfill.js +95 -0
  117. package/dist/src/indexer/backfill.js.map +1 -0
  118. package/dist/src/indexer/live.d.ts +20 -0
  119. package/dist/src/indexer/live.d.ts.map +1 -0
  120. package/dist/src/indexer/live.js +51 -0
  121. package/dist/src/indexer/live.js.map +1 -0
  122. package/dist/src/indexer/process-block.d.ts +29 -0
  123. package/dist/src/indexer/process-block.d.ts.map +1 -0
  124. package/dist/src/indexer/process-block.js +91 -0
  125. package/dist/src/indexer/process-block.js.map +1 -0
  126. package/dist/src/indexer/queue-block.d.ts +18 -0
  127. package/dist/src/indexer/queue-block.d.ts.map +1 -0
  128. package/dist/src/indexer/queue-block.js +38 -0
  129. package/dist/src/indexer/queue-block.js.map +1 -0
  130. package/dist/src/indexer/reorg.d.ts +24 -0
  131. package/dist/src/indexer/reorg.d.ts.map +1 -0
  132. package/dist/src/indexer/reorg.js +83 -0
  133. package/dist/src/indexer/reorg.js.map +1 -0
  134. package/dist/src/indexer/runner.d.ts +14 -0
  135. package/dist/src/indexer/runner.d.ts.map +1 -0
  136. package/dist/src/indexer/runner.js +22 -0
  137. package/dist/src/indexer/runner.js.map +1 -0
  138. package/dist/src/rpc/client.d.ts +11 -0
  139. package/dist/src/rpc/client.d.ts.map +1 -0
  140. package/dist/src/rpc/client.js +18 -0
  141. package/dist/src/rpc/client.js.map +1 -0
  142. package/dist/src/rpc/get-block.d.ts +16 -0
  143. package/dist/src/rpc/get-block.d.ts.map +1 -0
  144. package/dist/src/rpc/get-block.js +77 -0
  145. package/dist/src/rpc/get-block.js.map +1 -0
  146. package/dist/src/rpc/get-logs.d.ts +11 -0
  147. package/dist/src/rpc/get-logs.d.ts.map +1 -0
  148. package/dist/src/rpc/get-logs.js +23 -0
  149. package/dist/src/rpc/get-logs.js.map +1 -0
  150. package/dist/src/schema.d.ts +3 -0
  151. package/dist/src/schema.d.ts.map +1 -0
  152. package/dist/src/schema.js +9 -0
  153. package/dist/src/schema.js.map +1 -0
  154. package/dist/src/types.d.ts +22 -0
  155. package/dist/src/types.d.ts.map +1 -0
  156. package/dist/src/types.js +1 -0
  157. package/dist/src/types.js.map +1 -0
  158. package/dist/src/utils/bloom.d.ts +6 -0
  159. package/dist/src/utils/bloom.d.ts.map +1 -0
  160. package/dist/src/utils/bloom.js +30 -0
  161. package/dist/src/utils/bloom.js.map +1 -0
  162. package/dist/src/utils/build-conflict-columns.d.ts +4 -0
  163. package/dist/src/utils/build-conflict-columns.d.ts.map +1 -0
  164. package/dist/src/utils/build-conflict-columns.js +14 -0
  165. package/dist/src/utils/build-conflict-columns.js.map +1 -0
  166. package/dist/src/utils/common.d.ts +2 -0
  167. package/dist/src/utils/common.d.ts.map +1 -0
  168. package/dist/src/utils/common.js +4 -0
  169. package/dist/src/utils/common.js.map +1 -0
  170. package/dist/src/utils/cursor.d.ts +5 -0
  171. package/dist/src/utils/cursor.d.ts.map +1 -0
  172. package/dist/src/utils/cursor.js +8 -0
  173. package/dist/src/utils/cursor.js.map +1 -0
  174. package/dist/src/utils/format.d.ts +2 -0
  175. package/dist/src/utils/format.d.ts.map +1 -0
  176. package/dist/src/utils/format.js +16 -0
  177. package/dist/src/utils/format.js.map +1 -0
  178. package/dist/src/utils/hash.d.ts +9 -0
  179. package/dist/src/utils/hash.d.ts.map +1 -0
  180. package/dist/src/utils/hash.js +15 -0
  181. package/dist/src/utils/hash.js.map +1 -0
  182. package/dist/src/utils/json.d.ts +5 -0
  183. package/dist/src/utils/json.d.ts.map +1 -0
  184. package/dist/src/utils/json.js +11 -0
  185. package/dist/src/utils/json.js.map +1 -0
  186. package/dist/src/utils/logger.d.ts +11 -0
  187. package/dist/src/utils/logger.d.ts.map +1 -0
  188. package/dist/src/utils/logger.js +111 -0
  189. package/dist/src/utils/logger.js.map +1 -0
  190. package/dist/src/utils/shutdown.d.ts +9 -0
  191. package/dist/src/utils/shutdown.d.ts.map +1 -0
  192. package/dist/src/utils/shutdown.js +24 -0
  193. package/dist/src/utils/shutdown.js.map +1 -0
  194. package/dist/src/utils/timer.d.ts +6 -0
  195. package/dist/src/utils/timer.d.ts.map +1 -0
  196. package/dist/src/utils/timer.js +9 -0
  197. package/dist/src/utils/timer.js.map +1 -0
  198. package/dist/src/utils/types.d.ts +39 -0
  199. package/dist/src/utils/types.d.ts.map +1 -0
  200. package/dist/src/utils/types.js +1 -0
  201. package/dist/src/utils/types.js.map +1 -0
  202. package/dist/tsconfig.tsbuildinfo +1 -0
  203. package/hello/apps/foc-api/README.md +69 -0
  204. package/hello/apps/foc-api/biome.json +8 -0
  205. package/hello/apps/foc-api/index.html +13 -0
  206. package/hello/apps/foc-api/package.json +39 -0
  207. package/hello/apps/foc-api/public/vite.svg +1 -0
  208. package/hello/apps/foc-api/src/app.css +45 -0
  209. package/hello/apps/foc-api/src/app.tsx +43 -0
  210. package/hello/apps/foc-api/src/assets/Cloudflare_Logo.svg +51 -0
  211. package/hello/apps/foc-api/src/assets/react.svg +1 -0
  212. package/hello/apps/foc-api/src/client.ts +41 -0
  213. package/hello/apps/foc-api/src/components/account.tsx +100 -0
  214. package/hello/apps/foc-api/src/components/wallet-options.tsx +43 -0
  215. package/hello/apps/foc-api/src/index.css +68 -0
  216. package/hello/apps/foc-api/src/main.tsx +38 -0
  217. package/hello/apps/foc-api/src/vite-env.d.ts +1 -0
  218. package/hello/apps/foc-api/tsconfig.app.json +44 -0
  219. package/hello/apps/foc-api/tsconfig.json +17 -0
  220. package/hello/apps/foc-api/tsconfig.node.json +25 -0
  221. package/hello/apps/foc-api/tsconfig.worker.json +8 -0
  222. package/hello/apps/foc-api/vite.config.ts +8 -0
  223. package/hello/apps/foc-api/worker/capabilities.ts +25 -0
  224. package/hello/apps/foc-api/worker/index.ts +64 -0
  225. package/hello/apps/foc-api/worker/router.ts +35 -0
  226. package/hello/apps/foc-api/worker-configuration.d.ts +7357 -0
  227. package/hello/apps/foc-api/wrangler.jsonc +50 -0
  228. package/hello/apps/foc-app/README.md +69 -0
  229. package/hello/apps/foc-app/biome.json +8 -0
  230. package/hello/apps/foc-app/index.html +13 -0
  231. package/hello/apps/foc-app/package.json +39 -0
  232. package/hello/apps/foc-app/public/vite.svg +1 -0
  233. package/hello/apps/foc-app/src/app.css +45 -0
  234. package/hello/apps/foc-app/src/app.tsx +43 -0
  235. package/hello/apps/foc-app/src/assets/Cloudflare_Logo.svg +51 -0
  236. package/hello/apps/foc-app/src/assets/react.svg +1 -0
  237. package/hello/apps/foc-app/src/client.ts +41 -0
  238. package/hello/apps/foc-app/src/components/account.tsx +100 -0
  239. package/hello/apps/foc-app/src/components/wallet-options.tsx +43 -0
  240. package/hello/apps/foc-app/src/index.css +68 -0
  241. package/hello/apps/foc-app/src/main.tsx +38 -0
  242. package/hello/apps/foc-app/src/vite-env.d.ts +1 -0
  243. package/hello/apps/foc-app/tsconfig.app.json +44 -0
  244. package/hello/apps/foc-app/tsconfig.json +17 -0
  245. package/hello/apps/foc-app/tsconfig.node.json +25 -0
  246. package/hello/apps/foc-app/tsconfig.worker.json +8 -0
  247. package/hello/apps/foc-app/vite.config.ts +8 -0
  248. package/hello/apps/foc-app/worker/capabilities.ts +25 -0
  249. package/hello/apps/foc-app/worker/index.ts +64 -0
  250. package/hello/apps/foc-app/worker/router.ts +35 -0
  251. package/hello/apps/foc-app/worker-configuration.d.ts +7357 -0
  252. package/hello/apps/foc-app/wrangler.jsonc +50 -0
  253. package/hello/biome.json +50 -0
  254. package/hello/package.json +22 -0
  255. package/hello/pnpm-workspace.yaml +3 -0
  256. package/hello/tsconfig.json +37 -0
  257. package/package.json +78 -0
  258. package/src/api/index.ts +1 -0
  259. package/src/api/runner.ts +43 -0
  260. package/src/api/server.ts +38 -0
  261. package/src/api/sql-middleware.ts +131 -0
  262. package/src/api/sql.ts +149 -0
  263. package/src/api/sse.ts +12 -0
  264. package/src/bin/create.ts +199 -0
  265. package/src/bin/dev.ts +91 -0
  266. package/src/bin/flags.ts +65 -0
  267. package/src/bin/index.ts +28 -0
  268. package/src/bin/utils.ts +55 -0
  269. package/src/config/config.ts +221 -0
  270. package/src/config/env.ts +28 -0
  271. package/src/contants.ts +3 -0
  272. package/src/db/actions/blocks.ts +209 -0
  273. package/src/db/actions/index.ts +1 -0
  274. package/src/db/actions/transactions.ts +32 -0
  275. package/src/db/client.ts +186 -0
  276. package/src/db/column-types.ts +105 -0
  277. package/src/db/encode.ts +99 -0
  278. package/src/db/migrate.ts +222 -0
  279. package/src/db/schema/blocks.ts +24 -0
  280. package/src/db/schema/index.ts +21 -0
  281. package/src/db/schema/transactions.ts +39 -0
  282. package/src/db/transaction.ts +20 -0
  283. package/src/hooks/registry.ts +107 -0
  284. package/src/index.ts +9 -0
  285. package/src/indexer/backfill.ts +133 -0
  286. package/src/indexer/live.ts +76 -0
  287. package/src/indexer/process-block.ts +142 -0
  288. package/src/indexer/queue-block.ts +74 -0
  289. package/src/indexer/reorg.ts +120 -0
  290. package/src/indexer/runner.ts +35 -0
  291. package/src/rpc/client.ts +27 -0
  292. package/src/rpc/get-block.ts +100 -0
  293. package/src/rpc/get-logs.ts +38 -0
  294. package/src/schema.ts +10 -0
  295. package/src/types.ts +32 -0
  296. package/src/utils/bloom.ts +41 -0
  297. package/src/utils/build-conflict-columns.ts +26 -0
  298. package/src/utils/common.ts +3 -0
  299. package/src/utils/cursor.ts +7 -0
  300. package/src/utils/format.ts +18 -0
  301. package/src/utils/hash.ts +17 -0
  302. package/src/utils/json.ts +11 -0
  303. package/src/utils/logger.ts +149 -0
  304. package/src/utils/shutdown.ts +36 -0
  305. package/src/utils/timer.ts +8 -0
  306. package/src/utils/types.ts +87 -0
  307. package/template/biome.json +50 -0
  308. package/template/package.json +22 -0
  309. package/template/pnpm-workspace.yaml +3 -0
  310. package/template/tsconfig.json +37 -0
  311. package/tsconfig.json +8 -0
@@ -0,0 +1,199 @@
1
+ import {
2
+ copyFileSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readdirSync,
6
+ readFileSync,
7
+ statSync,
8
+ writeFileSync,
9
+ } from 'node:fs'
10
+ import { basename, dirname, relative, resolve } from 'node:path'
11
+ import { fileURLToPath } from 'node:url'
12
+ import { downloadTemplate } from '@bluwy/giget-core'
13
+ import * as p from '@clack/prompts'
14
+ import { type Command, command } from 'cleye'
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url))
17
+
18
+ const possibleTemplates = ['app', 'cli'] as const
19
+ type Templates = (typeof possibleTemplates)[number]
20
+
21
+ const Template = (template: Templates) => {
22
+ if (!possibleTemplates.includes(template)) {
23
+ throw new Error(
24
+ `Invalid template: "${template}" (must be one of ${possibleTemplates.join(', ')})`
25
+ )
26
+ }
27
+ return template
28
+ }
29
+
30
+ interface PkgInfo {
31
+ name: string
32
+ version: string
33
+ }
34
+
35
+ function pkgFromUserAgent(userAgent: string | undefined): PkgInfo | undefined {
36
+ if (!userAgent) return undefined
37
+ const pkgSpec = userAgent.split(' ')[0]
38
+ const pkgSpecArr = pkgSpec.split('/')
39
+ return {
40
+ name: pkgSpecArr[0],
41
+ version: pkgSpecArr[1],
42
+ }
43
+ }
44
+
45
+ function formatTargetDir(targetDir: string) {
46
+ return targetDir
47
+ .trim()
48
+ .replace(/[<>:"\\|?*]/g, '')
49
+ .replace(/\/+$/g, '')
50
+ }
51
+
52
+ function isEmpty(path: string) {
53
+ const files = readdirSync(path)
54
+ return files.length === 0 || (files.length === 1 && files[0] === '.git')
55
+ }
56
+
57
+ function isValidPackageName(projectName: string) {
58
+ return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(
59
+ projectName
60
+ )
61
+ }
62
+
63
+ function toValidPackageName(projectName: string) {
64
+ return projectName
65
+ .trim()
66
+ .toLowerCase()
67
+ .replace(/\s+/g, '-')
68
+ .replace(/^[._]/, '')
69
+ .replace(/[^a-z\d\-~]+/g, '-')
70
+ }
71
+
72
+ function copy(src: string, dest: string) {
73
+ const stat = statSync(src)
74
+ if (stat.isDirectory()) {
75
+ copyDir(src, dest)
76
+ } else {
77
+ copyFileSync(src, dest)
78
+ }
79
+ }
80
+
81
+ function copyDir(srcDir: string, destDir: string) {
82
+ mkdirSync(destDir, { recursive: true })
83
+ for (const file of readdirSync(srcDir)) {
84
+ const srcFile = resolve(srcDir, file)
85
+ const destFile = resolve(destDir, file)
86
+ copy(srcFile, destFile)
87
+ }
88
+ }
89
+
90
+ function getInstallCommand(agent: string) {
91
+ if (agent === 'yarn') {
92
+ return [agent]
93
+ }
94
+ return [agent, 'install']
95
+ }
96
+
97
+ function getRunCommand(agent: string, script: string) {
98
+ switch (agent) {
99
+ case 'yarn':
100
+ case 'pnpm':
101
+ case 'bun':
102
+ return [agent, script]
103
+ case 'deno':
104
+ return [agent, 'task', script]
105
+ default:
106
+ return [agent, 'run', script]
107
+ }
108
+ }
109
+
110
+ export const create: Command = command(
111
+ {
112
+ name: 'create',
113
+ parameters: ['<name>'],
114
+ flags: {
115
+ template: {
116
+ type: Template,
117
+ default: 'app',
118
+ description: 'The template to use for the new project (app, cli)',
119
+ },
120
+ },
121
+ help: {
122
+ description: 'Create a new project',
123
+ examples: [`foxer create <name>`],
124
+ },
125
+ },
126
+ async (argv) => {
127
+ const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent)
128
+ const pm = pkgInfo ? pkgInfo.name : 'npm'
129
+ const targetDir = formatTargetDir(argv._.name)
130
+ const cwd = process.cwd()
131
+ const root = resolve(cwd, targetDir)
132
+
133
+ if (existsSync(root) && !isEmpty(root)) {
134
+ p.log.error('Directory already exists and is not empty')
135
+ process.exit(1)
136
+ }
137
+
138
+ let packageName = basename(resolve(targetDir))
139
+ if (!isValidPackageName(packageName)) {
140
+ packageName = toValidPackageName(packageName)
141
+ }
142
+
143
+ let template = argv.flags.template
144
+ if (!possibleTemplates.includes(template as Templates)) {
145
+ template = 'app'
146
+ }
147
+
148
+ mkdirSync(root, { recursive: true })
149
+ p.log.step(`Scaffolding project in ${root}...`)
150
+
151
+ const pkg = JSON.parse(
152
+ readFileSync(resolve(__dirname, `../../template/package.json`), 'utf-8')
153
+ )
154
+
155
+ pkg.name = packageName
156
+
157
+ writeFileSync(
158
+ resolve(root, 'package.json'),
159
+ `${JSON.stringify(pkg, null, 2)}\n`
160
+ )
161
+
162
+ if (pm === 'pnpm') {
163
+ copy(
164
+ resolve(__dirname, `../../template/pnpm-workspace.yaml`),
165
+ resolve(root, 'pnpm-workspace.yaml')
166
+ )
167
+ }
168
+
169
+ copy(
170
+ resolve(__dirname, `../../template/biome.json`),
171
+ resolve(root, 'biome.json')
172
+ )
173
+ copy(
174
+ resolve(__dirname, `../../template/tsconfig.json`),
175
+ resolve(root, 'tsconfig.json')
176
+ )
177
+
178
+ // copy apps/foc-api
179
+ await downloadTemplate('hugomrdias/foxer/examples/api', {
180
+ dir: resolve(root, 'apps/api'),
181
+ })
182
+
183
+ await downloadTemplate('hugomrdias/foxer/examples/app', {
184
+ dir: resolve(root, 'apps/app'),
185
+ })
186
+
187
+ let doneMessage = ''
188
+ const cdProjectName = relative(cwd, root)
189
+ doneMessage += `Done. Now run:\n`
190
+ if (root !== cwd) {
191
+ doneMessage += `\n cd ${
192
+ cdProjectName.includes(' ') ? `"${cdProjectName}"` : cdProjectName
193
+ }`
194
+ }
195
+ doneMessage += `\n ${getInstallCommand(pm).join(' ')}`
196
+ doneMessage += `\n ${getRunCommand(pm, 'dev').join(' ')}`
197
+ p.outro(doneMessage)
198
+ }
199
+ )
package/src/bin/dev.ts ADDED
@@ -0,0 +1,91 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { type Command, command } from 'cleye'
4
+ import { gracefulExit } from 'exit-hook'
5
+ import { bootstrapApiServer } from '../api/runner.ts'
6
+ import { createEnv } from '../config/env.ts'
7
+ import { createDatabase } from '../db/client.ts'
8
+ import { runMigrations } from '../db/migrate.ts'
9
+ import * as InternalSchema from '../db/schema/index.ts'
10
+ import { HookRegistry } from '../hooks/registry.ts'
11
+ import { bootstrapIndexer } from '../indexer/runner.ts'
12
+ import { createLogger } from '../utils/logger.ts'
13
+ import { createExit, registerUnhandled } from '../utils/shutdown.ts'
14
+ import { globalFlags } from './flags.ts'
15
+ import { loadConfig } from './utils.ts'
16
+
17
+ export const dev: Command = command(
18
+ {
19
+ name: 'dev',
20
+ flags: { ...globalFlags },
21
+ help: {
22
+ description: 'Start the development server with hot reloading',
23
+ },
24
+ },
25
+ async (argv) => {
26
+ const logger = createLogger({
27
+ level: argv.flags.logLevel,
28
+ mode: argv.flags.logMode,
29
+ })
30
+
31
+ registerUnhandled({ logger })
32
+
33
+ if (!fs.existsSync(path.join(argv.flags.root, '.env.local'))) {
34
+ logger.warn({
35
+ msg: 'Local environment file (.env.local) not found',
36
+ })
37
+ }
38
+
39
+ try {
40
+ const env = createEnv(logger)
41
+
42
+ const config = await loadConfig(
43
+ logger,
44
+ argv.flags.root,
45
+ argv.flags.config
46
+ )
47
+
48
+ const dbContext = createDatabase({
49
+ env,
50
+ schema: { ...config.schema, ...InternalSchema.schema },
51
+ relations: { ...config.relations, ...InternalSchema.relations },
52
+ })
53
+
54
+ await runMigrations({
55
+ dbContext,
56
+ folder: path.resolve(argv.flags.root, config.drizzleFolder),
57
+ logger,
58
+ })
59
+
60
+ const registry = new HookRegistry()
61
+ config.hooks({ registry })
62
+
63
+ const [api, indexer] = await Promise.all([
64
+ bootstrapApiServer({
65
+ db: dbContext.db,
66
+ config,
67
+ logger,
68
+ port: argv.flags.port,
69
+ }),
70
+ bootstrapIndexer({
71
+ logger,
72
+ db: dbContext.db,
73
+ registry,
74
+ config,
75
+ }),
76
+ ])
77
+
78
+ createExit({
79
+ logger,
80
+ stop: async () => {
81
+ indexer.stop()
82
+ api.stop()
83
+ await dbContext.close()
84
+ },
85
+ })
86
+ } catch (error) {
87
+ logger.error({ error }, 'dev server failed')
88
+ gracefulExit(1)
89
+ }
90
+ }
91
+ )
@@ -0,0 +1,65 @@
1
+ import path from 'node:path'
2
+
3
+ const Root = (dir: string) => {
4
+ return path.resolve(dir)
5
+ }
6
+
7
+ const possibleLogModes = ['pretty', 'json'] as const
8
+
9
+ type LogModes = (typeof possibleLogModes)[number]
10
+
11
+ // Custom type function
12
+ const LogMode = (logMode: LogModes) => {
13
+ if (!possibleLogModes.includes(logMode)) {
14
+ throw new Error(`Invalid log mode: "${logMode}"`)
15
+ }
16
+
17
+ return logMode
18
+ }
19
+
20
+ const LogLevels = [
21
+ 'trace',
22
+ 'debug',
23
+ 'info',
24
+ 'warn',
25
+ 'error',
26
+ 'fatal',
27
+ 'silent',
28
+ ] as const
29
+ type LogLevels = (typeof LogLevels)[number]
30
+
31
+ // Custom type function
32
+ const LogLevel = (logLevel: LogLevels) => {
33
+ if (!LogLevels.includes(logLevel)) {
34
+ throw new Error(`Invalid log level: "${logLevel}"`)
35
+ }
36
+
37
+ return logLevel
38
+ }
39
+ export const globalFlags = {
40
+ config: {
41
+ type: String,
42
+ description: 'The path to the config file',
43
+ },
44
+ root: {
45
+ type: Root,
46
+ description: 'The root directory of the project',
47
+ default: process.cwd(),
48
+ },
49
+
50
+ logLevel: {
51
+ type: LogLevel,
52
+ description: 'The log level to use',
53
+ default: LogLevel((process.env.LOG_LEVEL as LogLevels) ?? 'info'),
54
+ },
55
+ logMode: {
56
+ type: LogMode,
57
+ description: 'The log mode to use',
58
+ default: LogMode(process.env.NODE_ENV !== 'production' ? 'pretty' : 'json'),
59
+ },
60
+ port: {
61
+ type: Number,
62
+ description: 'The port to use for the API server',
63
+ default: process.env.PORT ? Number(process.env.PORT) : 4200,
64
+ },
65
+ } as const
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync } from 'node:fs'
4
+ import { dirname, resolve } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { cli } from 'cleye'
7
+ import { create } from './create.ts'
8
+ import { dev } from './dev.ts'
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url))
11
+ const packageJsonPath = resolve(__dirname, '../../package.json')
12
+ const packageJson = JSON.parse(
13
+ readFileSync(packageJsonPath, { encoding: 'utf8' })
14
+ )
15
+
16
+ const argv = cli({
17
+ name: 'foxer',
18
+ version: packageJson.version,
19
+ commands: [dev, create],
20
+ help: {
21
+ version: packageJson.version,
22
+ },
23
+ })
24
+
25
+ if (!argv.command) {
26
+ argv.showHelp()
27
+ process.exit(1)
28
+ }
@@ -0,0 +1,55 @@
1
+ import path from 'node:path'
2
+ import { pathToFileURL } from 'node:url'
3
+ import { type LilconfigResult, lilconfig } from 'lilconfig'
4
+ import type { InternalConfig } from '../config/config'
5
+ import type { Logger } from '../utils/logger'
6
+
7
+ const CLI_NAME = 'foxer'
8
+
9
+ const loadEsm = async (filepath: string) => {
10
+ const res = await import(pathToFileURL(filepath).href)
11
+ return res.default ?? res
12
+ }
13
+
14
+ const configLoaders = {
15
+ '.js': loadEsm,
16
+ '.mjs': loadEsm,
17
+ '.ts': loadEsm,
18
+ '.mts': loadEsm,
19
+ }
20
+
21
+ export async function loadConfig(
22
+ logger: Logger,
23
+ root: string,
24
+ filePath?: string
25
+ ) {
26
+ let configFile: LilconfigResult | undefined
27
+
28
+ try {
29
+ if (filePath) {
30
+ const configPath = path.resolve(root, filePath)
31
+
32
+ configFile = await lilconfig(configPath, {
33
+ loaders: configLoaders,
34
+ searchPlaces: [],
35
+ }).load(configPath)
36
+ } else {
37
+ configFile = await lilconfig(CLI_NAME, {
38
+ loaders: configLoaders,
39
+ searchPlaces: [`${CLI_NAME}.config.ts`, `${CLI_NAME}.config.mts`],
40
+ }).search()
41
+ }
42
+ } catch (error) {
43
+ logger.error(error, 'config evaluation failed')
44
+ // ignore
45
+ }
46
+
47
+ if (!configFile || configFile.isEmpty) {
48
+ logger.error({
49
+ msg: 'Config file not found',
50
+ })
51
+ process.exit(1)
52
+ }
53
+
54
+ return configFile.config.config as InternalConfig
55
+ }
@@ -0,0 +1,221 @@
1
+ import type { Abi, AbiEvent } from 'abitype'
2
+ import type { AnyRelations, EmptyRelations } from 'drizzle-orm/relations'
3
+ import type { Hono } from 'hono'
4
+ import type { BlankEnv, BlankSchema } from 'hono/types'
5
+ import type { PoolConfig } from 'pg'
6
+ import type { SetRequired, Simplify, UnknownRecord } from 'type-fest'
7
+ import {
8
+ type Address,
9
+ type FallbackTransport,
10
+ getAbiItem,
11
+ type HttpTransport,
12
+ type Narrow,
13
+ type PublicClientConfig,
14
+ type WebSocketTransport,
15
+ } from 'viem'
16
+ import type { Database } from '../db/client.ts'
17
+ import type { HookRegistry } from '../hooks/registry.ts'
18
+ import { createRpcClients, type RpcClients } from '../rpc/client.ts'
19
+ import type { UnknownObject } from '../types.ts'
20
+ import type { Logger } from '../utils/logger.ts'
21
+ import type {
22
+ ContractConfig,
23
+ ContractsConfig,
24
+ GetContract,
25
+ } from '../utils/types.ts'
26
+
27
+ export type ClientConfig = Simplify<
28
+ SetRequired<PublicClientConfig, 'chain'> & {
29
+ realtimeTransport?: HttpTransport | WebSocketTransport | FallbackTransport
30
+ }
31
+ >
32
+
33
+ export type HonoConfig<
34
+ TSchema extends UnknownRecord = UnknownRecord,
35
+ TRelations extends AnyRelations = EmptyRelations,
36
+ > = (options: {
37
+ logger: Logger
38
+ db: Database<TSchema, TRelations>
39
+ }) => Hono<BlankEnv, BlankSchema, '/'>
40
+
41
+ export type HooksConfig<
42
+ contracts extends ContractsConfig<UnknownObject>,
43
+ TSchema extends UnknownRecord,
44
+ TRelations extends AnyRelations,
45
+ > = (context: {
46
+ registry: HookRegistry<
47
+ ContractsConfig<Narrow<contracts>>,
48
+ TSchema,
49
+ TRelations
50
+ >
51
+ }) => void
52
+
53
+ export type DatabaseConfig =
54
+ | {
55
+ driver: 'postgres'
56
+ url?: string
57
+ options?: PoolConfig
58
+ }
59
+ | {
60
+ driver: 'pglite'
61
+ directory: string
62
+ }
63
+
64
+ export type Config<
65
+ contracts extends ContractsConfig<UnknownObject>,
66
+ TSchema extends UnknownRecord,
67
+ TRelations extends AnyRelations = EmptyRelations,
68
+ > = {
69
+ /**
70
+ * The batch size for the backfill. How many blocks to process at a time.
71
+ * @default 100
72
+ */
73
+ batchSize?: number
74
+ /**
75
+ * The finality of the chain.
76
+ * @default 30
77
+ */
78
+ finality?: number
79
+ /**
80
+ * The folder where the drizzle schema is stored.
81
+ * @default './drizzle'
82
+ */
83
+ drizzleFolder?: string
84
+ /**
85
+ * The database configuration.
86
+ */
87
+ database?: DatabaseConfig
88
+ /**
89
+ * The contracts to index.
90
+ */
91
+ contracts: ContractsConfig<Narrow<contracts>>
92
+ /**
93
+ * The RPC clients configuration.
94
+ */
95
+ client: ClientConfig
96
+ /**
97
+ * The Hono configuration.
98
+ */
99
+ hono: HonoConfig<TSchema, TRelations>
100
+ /**
101
+ * The drizzle schema.
102
+ */
103
+ schema: TSchema
104
+ /**
105
+ * The drizzle relations.
106
+ */
107
+ relations?: TRelations
108
+ /**
109
+ * The hooks configuration.
110
+ */
111
+ hooks: HooksConfig<contracts, TSchema, TRelations>
112
+ }
113
+
114
+ export type InternalConfig<
115
+ TSchema extends UnknownRecord = UnknownRecord,
116
+ TRelations extends AnyRelations = EmptyRelations,
117
+ > = {
118
+ batchSize: bigint
119
+ finality: bigint
120
+ drizzleFolder: string
121
+ contracts: { [contractName: string]: GetContract }
122
+ database?: DatabaseConfig
123
+ client: ClientConfig
124
+ hono: HonoConfig<TSchema, TRelations>
125
+ schema: TSchema
126
+ relations: TRelations
127
+ hooks: (context: { registry: HookRegistry }) => void
128
+ startBlockNumber: bigint
129
+ contractsForLive: ContractConfig<Abi, readonly string[]>[]
130
+ clients: RpcClients
131
+ }
132
+
133
+ export function createConfig<
134
+ contracts extends ContractsConfig<UnknownObject>,
135
+ TSchema extends UnknownRecord = UnknownRecord,
136
+ TRelations extends AnyRelations = EmptyRelations,
137
+ >(config: Config<contracts, TSchema, TRelations>) {
138
+ let startBlockNumber: bigint = 0n
139
+ const contractsForLive: ContractConfig<Abi, readonly string[]>[] = []
140
+
141
+ for (const contract of Object.values(
142
+ config.contracts as { [contractName: string]: GetContract }
143
+ )) {
144
+ if (contract.endBlock == null) {
145
+ contractsForLive.push(contract)
146
+ }
147
+
148
+ if (startBlockNumber === 0n && contract.startBlock != null) {
149
+ startBlockNumber = contract.startBlock
150
+ continue
151
+ }
152
+
153
+ if (contract.startBlock != null && contract.startBlock < startBlockNumber) {
154
+ startBlockNumber = contract.startBlock
155
+ }
156
+ }
157
+
158
+ const clients = createRpcClients(config.client)
159
+
160
+ return {
161
+ ...config,
162
+ batchSize: BigInt(config.batchSize ?? 100),
163
+ finality: BigInt(config.finality ?? 30),
164
+ drizzleFolder: config.drizzleFolder ?? './drizzle',
165
+ relations: config.relations ? config.relations : ({} as TRelations),
166
+ startBlockNumber,
167
+ contractsForLive,
168
+ clients,
169
+ }
170
+ }
171
+
172
+ export type FilteredContracts = {
173
+ eventAbis: AbiEvent[]
174
+ addresses: Address[]
175
+ eventNames: Set<string>
176
+ contractNameByAddress: Record<Address, string>
177
+ }
178
+
179
+ export function filterContracts(
180
+ config: InternalConfig,
181
+ fromBlock: bigint,
182
+ toBlock: bigint
183
+ ) {
184
+ const eventAbis: AbiEvent[] = []
185
+ const addresses: Address[] = []
186
+ const eventNames: Set<string> = new Set()
187
+ const contractNameByAddress: Record<Address, string> = {}
188
+ for (const [contractName, contract] of Object.entries(config.contracts)) {
189
+ const address = contract.address.toLowerCase() as `0x${string}`
190
+ const startBlock = contract.startBlock ?? 0n
191
+
192
+ if (startBlock > toBlock) {
193
+ continue
194
+ }
195
+ if (contract.endBlock != null && contract.endBlock < fromBlock) {
196
+ continue
197
+ }
198
+
199
+ addresses.push(address)
200
+ contractNameByAddress[address] = contractName
201
+
202
+ for (const event of contract.events) {
203
+ eventNames.add(event)
204
+ const eventAbi = getAbiItem({
205
+ abi: contract.abi,
206
+ name: event,
207
+ })
208
+ if (!eventAbi) {
209
+ throw new Error(`Event ${event} not found in contract ${contractName}`)
210
+ }
211
+ eventAbis.push(eventAbi as AbiEvent)
212
+ }
213
+ }
214
+
215
+ return {
216
+ eventAbis: eventAbis,
217
+ addresses: addresses,
218
+ contractNameByAddress: contractNameByAddress,
219
+ eventNames: eventNames,
220
+ }
221
+ }
@@ -0,0 +1,28 @@
1
+ import dotenv from 'dotenv'
2
+ import { z } from 'zod'
3
+ import type { Logger } from '../utils/logger'
4
+
5
+ dotenv.config({
6
+ path: '.env.local',
7
+ })
8
+
9
+ export type Env = z.infer<typeof envSchema>
10
+ const envSchema = z.object({
11
+ DATABASE_URL: z.url().optional(),
12
+ LOG_LEVEL: z
13
+ .enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])
14
+ .default('info'),
15
+ LOG_MODE: z.enum(['pretty', 'json']).default('pretty'),
16
+ })
17
+
18
+ export function createEnv(logger: Logger) {
19
+ const parsed = envSchema.safeParse(process.env)
20
+
21
+ if (!parsed.success) {
22
+ throw new Error(
23
+ `Failed to parse environment variables: \n ${z.flattenError(parsed.error)}`
24
+ )
25
+ }
26
+ logger.debug({ env: parsed.data }, 'env parsed')
27
+ return parsed.data
28
+ }
@@ -0,0 +1,3 @@
1
+ export const PUBLICATION_NAME = 'foxer_publication'
2
+ export const FOXER_TABLES = ['blocks', 'transactions']
3
+ export const MAX_QUERY_PARAMS = 16000