@async/db 0.2.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 (398) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/README.md +431 -0
  3. package/SPEC.md +1429 -0
  4. package/db.config.example.mjs +128 -0
  5. package/dist/cli/args.d.ts +8 -0
  6. package/dist/cli/args.js +16 -0
  7. package/dist/cli/commands/create.d.ts +3 -0
  8. package/dist/cli/commands/create.js +13 -0
  9. package/dist/cli/commands/doctor.d.ts +3 -0
  10. package/dist/cli/commands/doctor.js +31 -0
  11. package/dist/cli/commands/generate.d.ts +6 -0
  12. package/dist/cli/commands/generate.js +24 -0
  13. package/dist/cli/commands/operations.d.ts +12 -0
  14. package/dist/cli/commands/operations.js +61 -0
  15. package/dist/cli/commands/schema.d.ts +11 -0
  16. package/dist/cli/commands/schema.js +1086 -0
  17. package/dist/cli/commands/serve.d.ts +9 -0
  18. package/dist/cli/commands/serve.js +18 -0
  19. package/dist/cli/commands/sync.d.ts +3 -0
  20. package/dist/cli/commands/sync.js +11 -0
  21. package/dist/cli/commands/types.d.ts +7 -0
  22. package/dist/cli/commands/types.js +37 -0
  23. package/dist/cli/commands/viewer.d.ts +6 -0
  24. package/dist/cli/commands/viewer.js +29 -0
  25. package/dist/cli/index.d.ts +2 -0
  26. package/dist/cli/index.js +108 -0
  27. package/dist/cli/output.d.ts +25 -0
  28. package/dist/cli/output.js +149 -0
  29. package/dist/cli/schema-prompt.d.ts +20 -0
  30. package/dist/cli/schema-prompt.js +66 -0
  31. package/dist/cli.d.ts +2 -0
  32. package/dist/cli.js +3 -0
  33. package/dist/client-cache.d.ts +105 -0
  34. package/dist/client-cache.js +916 -0
  35. package/dist/client.d.ts +64 -0
  36. package/dist/client.js +405 -0
  37. package/dist/config-public.d.ts +1 -0
  38. package/dist/config-public.js +1 -0
  39. package/dist/config.d.ts +54 -0
  40. package/dist/config.js +2 -0
  41. package/dist/csv.d.ts +1 -0
  42. package/dist/csv.js +1 -0
  43. package/dist/db.d.ts +3 -0
  44. package/dist/db.js +3 -0
  45. package/dist/doctor.d.ts +1 -0
  46. package/dist/doctor.js +1 -0
  47. package/dist/errors.d.ts +1 -0
  48. package/dist/errors.js +1 -0
  49. package/dist/features/config/defaults.d.ts +98 -0
  50. package/dist/features/config/defaults.js +95 -0
  51. package/dist/features/config/load.d.ts +11 -0
  52. package/dist/features/config/load.js +265 -0
  53. package/dist/features/config/public.d.ts +17 -0
  54. package/dist/features/config/public.js +75 -0
  55. package/dist/features/doctor/duplicate-ids.d.ts +18 -0
  56. package/dist/features/doctor/duplicate-ids.js +79 -0
  57. package/dist/features/doctor/field-consistency.d.ts +17 -0
  58. package/dist/features/doctor/field-consistency.js +48 -0
  59. package/dist/features/doctor/index.d.ts +39 -0
  60. package/dist/features/doctor/index.js +177 -0
  61. package/dist/features/doctor/relations.d.ts +22 -0
  62. package/dist/features/doctor/relations.js +90 -0
  63. package/dist/features/doctor/schema-guidance.d.ts +35 -0
  64. package/dist/features/doctor/schema-guidance.js +184 -0
  65. package/dist/features/generate/registry.d.ts +14 -0
  66. package/dist/features/generate/registry.js +37 -0
  67. package/dist/features/http/registry.d.ts +46 -0
  68. package/dist/features/http/registry.js +86 -0
  69. package/dist/features/operations/index.d.ts +49 -0
  70. package/dist/features/operations/index.js +199 -0
  71. package/dist/features/operations/maps.d.ts +1 -0
  72. package/dist/features/operations/maps.js +10 -0
  73. package/dist/features/operations/readiness.d.ts +30 -0
  74. package/dist/features/operations/readiness.js +228 -0
  75. package/dist/features/operations/runtime.d.ts +57 -0
  76. package/dist/features/operations/runtime.js +288 -0
  77. package/dist/features/runtime/collection.d.ts +51 -0
  78. package/dist/features/runtime/collection.js +198 -0
  79. package/dist/features/runtime/db.d.ts +152 -0
  80. package/dist/features/runtime/db.js +824 -0
  81. package/dist/features/runtime/document.d.ts +43 -0
  82. package/dist/features/runtime/document.js +111 -0
  83. package/dist/features/runtime/fanout.d.ts +24 -0
  84. package/dist/features/runtime/fanout.js +77 -0
  85. package/dist/features/runtime/json-pointer.d.ts +5 -0
  86. package/dist/features/runtime/json-pointer.js +49 -0
  87. package/dist/features/runtime/scope-state.d.ts +44 -0
  88. package/dist/features/runtime/scope-state.js +185 -0
  89. package/dist/features/runtime/state.d.ts +1 -0
  90. package/dist/features/runtime/state.js +1 -0
  91. package/dist/features/schema/api.d.ts +107 -0
  92. package/dist/features/schema/api.js +460 -0
  93. package/dist/features/schema/builders.d.ts +86 -0
  94. package/dist/features/schema/builders.js +110 -0
  95. package/dist/features/schema/fields.d.ts +38 -0
  96. package/dist/features/schema/fields.js +296 -0
  97. package/dist/features/schema/generated.d.ts +29 -0
  98. package/dist/features/schema/generated.js +32 -0
  99. package/dist/features/schema/locator.d.ts +16 -0
  100. package/dist/features/schema/locator.js +135 -0
  101. package/dist/features/schema/manifest.d.ts +91 -0
  102. package/dist/features/schema/manifest.js +384 -0
  103. package/dist/features/schema/metadata.d.ts +30 -0
  104. package/dist/features/schema/metadata.js +75 -0
  105. package/dist/features/schema/project.d.ts +46 -0
  106. package/dist/features/schema/project.js +442 -0
  107. package/dist/features/schema/relations.d.ts +38 -0
  108. package/dist/features/schema/relations.js +109 -0
  109. package/dist/features/schema/resolvers.d.ts +36 -0
  110. package/dist/features/schema/resolvers.js +111 -0
  111. package/dist/features/schema/resource.d.ts +75 -0
  112. package/dist/features/schema/resource.js +253 -0
  113. package/dist/features/schema/source-definitions.d.ts +21 -0
  114. package/dist/features/schema/source-definitions.js +29 -0
  115. package/dist/features/schema/sources.d.ts +83 -0
  116. package/dist/features/schema/sources.js +689 -0
  117. package/dist/features/schema/standard-schema.d.ts +57 -0
  118. package/dist/features/schema/standard-schema.js +232 -0
  119. package/dist/features/schema/validation.d.ts +69 -0
  120. package/dist/features/schema/validation.js +434 -0
  121. package/dist/features/storage/events.d.ts +12 -0
  122. package/dist/features/storage/events.js +30 -0
  123. package/dist/features/storage/json.d.ts +112 -0
  124. package/dist/features/storage/json.js +239 -0
  125. package/dist/features/storage/memory.d.ts +30 -0
  126. package/dist/features/storage/memory.js +44 -0
  127. package/dist/features/storage/resource-json.d.ts +31 -0
  128. package/dist/features/storage/resource-json.js +76 -0
  129. package/dist/features/storage/runtime.d.ts +37 -0
  130. package/dist/features/storage/runtime.js +184 -0
  131. package/dist/features/storage/source-metadata.d.ts +20 -0
  132. package/dist/features/storage/source-metadata.js +25 -0
  133. package/dist/features/storage/source.d.ts +37 -0
  134. package/dist/features/storage/source.js +60 -0
  135. package/dist/features/storage/static.d.ts +29 -0
  136. package/dist/features/storage/static.js +42 -0
  137. package/dist/features/sync/defaults.d.ts +21 -0
  138. package/dist/features/sync/defaults.js +21 -0
  139. package/dist/features/sync/index.d.ts +35 -0
  140. package/dist/features/sync/index.js +85 -0
  141. package/dist/features/sync/mirror-state.d.ts +14 -0
  142. package/dist/features/sync/mirror-state.js +4 -0
  143. package/dist/features/sync/runtime-dirs.d.ts +5 -0
  144. package/dist/features/sync/runtime-dirs.js +9 -0
  145. package/dist/features/sync/source-writes.d.ts +15 -0
  146. package/dist/features/sync/source-writes.js +27 -0
  147. package/dist/features/sync/synthetic-seed.d.ts +26 -0
  148. package/dist/features/sync/synthetic-seed.js +83 -0
  149. package/dist/features/viewer/manifest.d.ts +148 -0
  150. package/dist/features/viewer/manifest.js +165 -0
  151. package/dist/fs-utils.d.ts +1 -0
  152. package/dist/fs-utils.js +1 -0
  153. package/dist/generate/hono/app.d.ts +6 -0
  154. package/dist/generate/hono/app.js +51 -0
  155. package/dist/generate/hono/graphql.d.ts +7 -0
  156. package/dist/generate/hono/graphql.js +53 -0
  157. package/dist/generate/hono/index.d.ts +55 -0
  158. package/dist/generate/hono/index.js +140 -0
  159. package/dist/generate/hono/package.d.ts +6 -0
  160. package/dist/generate/hono/package.js +44 -0
  161. package/dist/generate/hono/readme.d.ts +13 -0
  162. package/dist/generate/hono/readme.js +28 -0
  163. package/dist/generate/hono/repository.d.ts +1 -0
  164. package/dist/generate/hono/repository.js +27 -0
  165. package/dist/generate/hono/rest.d.ts +1 -0
  166. package/dist/generate/hono/rest.js +38 -0
  167. package/dist/generate/hono/schema.d.ts +13 -0
  168. package/dist/generate/hono/schema.js +18 -0
  169. package/dist/generate/hono/sqlite.d.ts +20 -0
  170. package/dist/generate/hono/sqlite.js +266 -0
  171. package/dist/generate/hono/validators.d.ts +1 -0
  172. package/dist/generate/hono/validators.js +141 -0
  173. package/dist/generate/hono.d.ts +1 -0
  174. package/dist/generate/hono.js +1 -0
  175. package/dist/graphql/execute.d.ts +14 -0
  176. package/dist/graphql/execute.js +719 -0
  177. package/dist/graphql/http.d.ts +15 -0
  178. package/dist/graphql/http.js +29 -0
  179. package/dist/graphql/index.d.ts +3 -0
  180. package/dist/graphql/index.js +3 -0
  181. package/dist/graphql/parser.d.ts +54 -0
  182. package/dist/graphql/parser.js +433 -0
  183. package/dist/hono.d.ts +77 -0
  184. package/dist/hono.js +1 -0
  185. package/dist/index.d.ts +1065 -0
  186. package/dist/index.js +14 -0
  187. package/dist/integrations/hono.d.ts +136 -0
  188. package/dist/integrations/hono.js +508 -0
  189. package/dist/integrations/kv.d.ts +69 -0
  190. package/dist/integrations/kv.js +69 -0
  191. package/dist/integrations/postgres.d.ts +52 -0
  192. package/dist/integrations/postgres.js +113 -0
  193. package/dist/integrations/sqlite.d.ts +112 -0
  194. package/dist/integrations/sqlite.js +489 -0
  195. package/dist/integrations/vite.d.ts +45 -0
  196. package/dist/integrations/vite.js +111 -0
  197. package/dist/json.d.ts +48 -0
  198. package/dist/json.js +1 -0
  199. package/dist/jsonc.d.ts +1 -0
  200. package/dist/jsonc.js +1 -0
  201. package/dist/kv.d.ts +24 -0
  202. package/dist/kv.js +1 -0
  203. package/dist/mock.d.ts +1 -0
  204. package/dist/mock.js +1 -0
  205. package/dist/names.d.ts +1 -0
  206. package/dist/names.js +1 -0
  207. package/dist/operations.d.ts +3 -0
  208. package/dist/operations.js +3 -0
  209. package/dist/postgres.d.ts +24 -0
  210. package/dist/postgres.js +1 -0
  211. package/dist/redis.d.ts +14 -0
  212. package/dist/redis.js +1 -0
  213. package/dist/rest/formats.d.ts +80 -0
  214. package/dist/rest/formats.js +318 -0
  215. package/dist/rest/handler.d.ts +111 -0
  216. package/dist/rest/handler.js +833 -0
  217. package/dist/rest/shape.d.ts +33 -0
  218. package/dist/rest/shape.js +218 -0
  219. package/dist/schema-builders.d.ts +1 -0
  220. package/dist/schema-builders.js +1 -0
  221. package/dist/schema-manifest.d.ts +1 -0
  222. package/dist/schema-manifest.js +1 -0
  223. package/dist/schema.d.ts +193 -0
  224. package/dist/schema.js +6 -0
  225. package/dist/server.d.ts +116 -0
  226. package/dist/server.js +601 -0
  227. package/dist/shared/csv.d.ts +8 -0
  228. package/dist/shared/csv.js +149 -0
  229. package/dist/shared/errors.d.ts +40 -0
  230. package/dist/shared/errors.js +55 -0
  231. package/dist/shared/fs-utils.d.ts +4 -0
  232. package/dist/shared/fs-utils.js +30 -0
  233. package/dist/shared/jsonc.d.ts +2 -0
  234. package/dist/shared/jsonc.js +99 -0
  235. package/dist/shared/mock.d.ts +40 -0
  236. package/dist/shared/mock.js +83 -0
  237. package/dist/shared/names.d.ts +28 -0
  238. package/dist/shared/names.js +127 -0
  239. package/dist/shared/operations.d.ts +32 -0
  240. package/dist/shared/operations.js +302 -0
  241. package/dist/sqlite.d.ts +24 -0
  242. package/dist/sqlite.js +1 -0
  243. package/dist/state.d.ts +1 -0
  244. package/dist/state.js +1 -0
  245. package/dist/sync.d.ts +1 -0
  246. package/dist/sync.js +1 -0
  247. package/dist/tracing.d.ts +95 -0
  248. package/dist/tracing.js +260 -0
  249. package/dist/types.d.ts +51 -0
  250. package/dist/types.js +285 -0
  251. package/dist/viewer-manifest.d.ts +1 -0
  252. package/dist/viewer-manifest.js +1 -0
  253. package/dist/vite.d.ts +59 -0
  254. package/dist/vite.js +1 -0
  255. package/dist/web/json-viewer.d.ts +5 -0
  256. package/dist/web/json-viewer.js +176 -0
  257. package/dist/web/viewer.d.ts +12 -0
  258. package/dist/web/viewer.js +1015 -0
  259. package/docs/README.md +42 -0
  260. package/docs/architecture.md +112 -0
  261. package/docs/ci-and-release.md +177 -0
  262. package/docs/cms-storage-patterns.md +108 -0
  263. package/docs/concepts.md +141 -0
  264. package/docs/configuration.md +552 -0
  265. package/docs/fixtures-and-schemas.md +527 -0
  266. package/docs/fork-branch-workflows.md +108 -0
  267. package/docs/generated-files.md +174 -0
  268. package/docs/getting-started.md +165 -0
  269. package/docs/integrations.md +206 -0
  270. package/docs/json-production.md +120 -0
  271. package/docs/package-api.md +418 -0
  272. package/docs/prototype-to-production.md +378 -0
  273. package/docs/server-and-viewer.md +466 -0
  274. package/docs/store-graduation.md +120 -0
  275. package/docs/typescript-schema-sources.md +79 -0
  276. package/examples/advanced/README.md +55 -0
  277. package/examples/advanced/db/projects.schema.jsonc +44 -0
  278. package/examples/advanced/db/settings.jsonc +9 -0
  279. package/examples/advanced/db/users.json +23 -0
  280. package/examples/advanced/db/users.schema.mjs +31 -0
  281. package/examples/advanced/db.config.mjs +18 -0
  282. package/examples/advanced/example.json +5 -0
  283. package/examples/advanced/src/generated/db.types.d.ts +64 -0
  284. package/examples/basic/README.md +95 -0
  285. package/examples/basic/db/operations/get-user.jsonc +8 -0
  286. package/examples/basic/db/settings.json +7 -0
  287. package/examples/basic/db/users.schema.jsonc +36 -0
  288. package/examples/basic/db.config.mjs +68 -0
  289. package/examples/basic/example.json +5 -0
  290. package/examples/basic/src/generated/db.types.d.ts +39 -0
  291. package/examples/cms-json-publish/README.md +21 -0
  292. package/examples/cms-json-publish/db/navigation.json +7 -0
  293. package/examples/cms-json-publish/db/pages.json +18 -0
  294. package/examples/cms-json-publish/example.json +5 -0
  295. package/examples/cms-json-publish/src/cms.mjs +104 -0
  296. package/examples/computed-fields/README.md +93 -0
  297. package/examples/computed-fields/db/orders.schema.mjs +62 -0
  298. package/examples/computed-fields/db/posts.schema.mjs +59 -0
  299. package/examples/computed-fields/db/products.schema.mjs +39 -0
  300. package/examples/computed-fields/db/users.schema.mjs +43 -0
  301. package/examples/computed-fields/db.config.mjs +15 -0
  302. package/examples/computed-fields/example.json +5 -0
  303. package/examples/computed-fields/src/generated/db.types.d.ts +81 -0
  304. package/examples/content-collections/README.md +91 -0
  305. package/examples/content-collections/db/authors.json +12 -0
  306. package/examples/content-collections/db/authors.schema.mjs +20 -0
  307. package/examples/content-collections/db/blog/draft-roadmap.mdx +12 -0
  308. package/examples/content-collections/db/blog/index.schema.mjs +61 -0
  309. package/examples/content-collections/db/blog/launch-notes.mdx +15 -0
  310. package/examples/content-collections/db/docs/index.schema.mjs +32 -0
  311. package/examples/content-collections/db/docs/intro.mdx +11 -0
  312. package/examples/content-collections/db/docs/schema-workflow.mdx +10 -0
  313. package/examples/content-collections/db/site.schema.jsonc +21 -0
  314. package/examples/content-collections/db.config.mjs +26 -0
  315. package/examples/content-collections/example.json +5 -0
  316. package/examples/content-collections/src/content-preview.mjs +66 -0
  317. package/examples/content-collections/src/generated/db.types.d.ts +81 -0
  318. package/examples/csv/README.md +52 -0
  319. package/examples/csv/db/customers.csv +4 -0
  320. package/examples/csv/db.config.mjs +13 -0
  321. package/examples/csv/example.json +5 -0
  322. package/examples/data-first/README.md +54 -0
  323. package/examples/data-first/db/posts.json +16 -0
  324. package/examples/data-first/db/settings.json +8 -0
  325. package/examples/data-first/db/users.json +14 -0
  326. package/examples/data-first/db.config.mjs +13 -0
  327. package/examples/data-first/example.json +5 -0
  328. package/examples/diagnostics/README.md +55 -0
  329. package/examples/diagnostics/db/projects.schema.jsonc +27 -0
  330. package/examples/diagnostics/db/users.json +9 -0
  331. package/examples/diagnostics/db/users.schema.jsonc +23 -0
  332. package/examples/diagnostics/db.config.mjs +16 -0
  333. package/examples/diagnostics/example.json +5 -0
  334. package/examples/free-plan-upgrade/README.md +22 -0
  335. package/examples/free-plan-upgrade/db/appSettings.json +4 -0
  336. package/examples/free-plan-upgrade/db/projects.json +7 -0
  337. package/examples/free-plan-upgrade/example.json +5 -0
  338. package/examples/free-plan-upgrade/src/upgrade-tenant-to-paid.mjs +105 -0
  339. package/examples/hono-auth/README.md +74 -0
  340. package/examples/hono-auth/db/pages.schema.jsonc +44 -0
  341. package/examples/hono-auth/db/users.schema.jsonc +42 -0
  342. package/examples/hono-auth/db.config.mjs +17 -0
  343. package/examples/hono-auth/example.json +5 -0
  344. package/examples/hono-auth/package.json +14 -0
  345. package/examples/hono-auth/src/app.mjs +79 -0
  346. package/examples/hono-auth/src/server.mjs +13 -0
  347. package/examples/production-json/README.md +102 -0
  348. package/examples/production-json/db/appSettings.schema.jsonc +41 -0
  349. package/examples/production-json/db/featureFlags.schema.jsonc +84 -0
  350. package/examples/production-json/db/operations/get-control-plane.jsonc +6 -0
  351. package/examples/production-json/db/operations/get-feature-flag.jsonc +9 -0
  352. package/examples/production-json/db/operations/list-feature-flags.jsonc +8 -0
  353. package/examples/production-json/db/operations/read-public-settings.jsonc +8 -0
  354. package/examples/production-json/db.config.mjs +33 -0
  355. package/examples/production-json/example.json +5 -0
  356. package/examples/production-json/src/client-demo.mjs +28 -0
  357. package/examples/production-json/src/generated/db.types.d.ts +60 -0
  358. package/examples/relations/README.md +56 -0
  359. package/examples/relations/db/posts.schema.jsonc +46 -0
  360. package/examples/relations/db/users.schema.jsonc +34 -0
  361. package/examples/relations/db.config.mjs +13 -0
  362. package/examples/relations/example.json +5 -0
  363. package/examples/rest-client/README.md +54 -0
  364. package/examples/rest-client/db/settings.json +5 -0
  365. package/examples/rest-client/db/users.schema.jsonc +42 -0
  366. package/examples/rest-client/db.config.mjs +13 -0
  367. package/examples/rest-client/example.json +5 -0
  368. package/examples/rest-client/src/client-demo.mjs +24 -0
  369. package/examples/schema-first/README.md +55 -0
  370. package/examples/schema-first/db/auditEvents.schema.jsonc +24 -0
  371. package/examples/schema-first/db/settings.schema.jsonc +29 -0
  372. package/examples/schema-first/db/users.schema.jsonc +36 -0
  373. package/examples/schema-first/db.config.mjs +15 -0
  374. package/examples/schema-first/example.json +5 -0
  375. package/examples/schema-first/src/generated/db.types.d.ts +47 -0
  376. package/examples/schema-manifest/README.md +50 -0
  377. package/examples/schema-manifest/db/projects.schema.jsonc +48 -0
  378. package/examples/schema-manifest/db/users.schema.jsonc +35 -0
  379. package/examples/schema-manifest/db.config.mjs +41 -0
  380. package/examples/schema-manifest/example.json +5 -0
  381. package/examples/schema-manifest/src/generated/db.schema.json +130 -0
  382. package/examples/schema-manifest/src/generated/db.types.d.ts +50 -0
  383. package/examples/schema-ui/README.md +103 -0
  384. package/examples/schema-ui/db/pages.schema.jsonc +53 -0
  385. package/examples/schema-ui/db/users.schema.jsonc +30 -0
  386. package/examples/schema-ui/db.config.mjs +55 -0
  387. package/examples/schema-ui/example.json +5 -0
  388. package/examples/schema-ui/src/cms-ssr.mjs +276 -0
  389. package/examples/schema-ui/src/generated/db.schema.json +133 -0
  390. package/examples/schema-ui/src/generated/db.types.d.ts +46 -0
  391. package/examples/schema-ui/src/render-admin.mjs +175 -0
  392. package/examples/schema-ui/src/schema-ui-ssr-handler.mjs +149 -0
  393. package/examples/schema-ui/src/start-schema-ui-server.mjs +140 -0
  394. package/examples/standard-schema/README.md +55 -0
  395. package/examples/standard-schema/db/settings.schema.mjs +22 -0
  396. package/examples/standard-schema/db/users.schema.mjs +72 -0
  397. package/examples/standard-schema/example.json +5 -0
  398. package/package.json +108 -0
@@ -0,0 +1,177 @@
1
+ import { loadProjectSchema } from '../../schema.js';
2
+ import { resourceConfigValue } from '../../names.js';
3
+ import { duplicateIdFindings, mixedIdTypeFindings } from './duplicate-ids.js';
4
+ import { inconsistentFieldTypeFindings } from './field-consistency.js';
5
+ import { operationStrictModeFindings } from '../operations/readiness.js';
6
+ import { relationSuggestionFindings } from './relations.js';
7
+ import { schemaGuidanceFindings } from './schema-guidance.js';
8
+ const BUILTIN_STORES = ['json', 'memory', 'sourceFile', 'static'];
9
+ const LARGE_JSON_STORE_RECORD_COUNT = 1000;
10
+ export async function runDbDoctor(config) {
11
+ const project = await loadProjectSchema(config);
12
+ const inferredProject = await loadProjectSchema({
13
+ ...config,
14
+ schema: {
15
+ ...config.schema,
16
+ source: 'data',
17
+ },
18
+ });
19
+ const findings = [
20
+ ...project.diagnostics.map((diagnostic) => diagnosticToFinding(diagnostic, 'schema')),
21
+ ...doctorResourceFindings(project.resources, config),
22
+ ...schemaGuidanceFindings(project, inferredProject),
23
+ ...await operationStrictModeFindings(config),
24
+ ];
25
+ return {
26
+ summary: summarizeFindings(findings),
27
+ findings,
28
+ };
29
+ }
30
+ function diagnosticToFinding(diagnostic, source) {
31
+ return {
32
+ ...diagnostic,
33
+ source: diagnostic.source ?? source,
34
+ severity: diagnostic.severity ?? 'warn',
35
+ hint: diagnostic.hint ?? '',
36
+ details: diagnostic.details ?? {},
37
+ };
38
+ }
39
+ function doctorResourceFindings(resources, config) {
40
+ const collections = resources.filter((resource) => (resource.kind === 'collection'
41
+ && Array.isArray(resource.seed)
42
+ && resource.seed.every((record) => isRecord(record))));
43
+ return [
44
+ ...storeConfigFindings(resources, config),
45
+ ...jsonProductionFindings(resources, config),
46
+ ...collections.flatMap((resource) => [
47
+ ...duplicateIdFindings(resource),
48
+ ...mixedIdTypeFindings(resource),
49
+ ...inconsistentFieldTypeFindings(resource),
50
+ ]),
51
+ ...relationSuggestionFindings(collections),
52
+ ];
53
+ }
54
+ function jsonProductionFindings(resources, config) {
55
+ if (config.doctor?.production !== true) {
56
+ return [];
57
+ }
58
+ return resources.flatMap((resource) => {
59
+ const findings = [];
60
+ const resourceConfig = resourceConfigValue(config.resources, resource.name);
61
+ const storeName = resourceConfig?.store ?? config.stores?.default ?? 'json';
62
+ const driver = resolveStoreDriver(storeName, config);
63
+ if (driver !== 'json') {
64
+ return findings;
65
+ }
66
+ findings.push({
67
+ code: 'DOCTOR_JSON_PRODUCTION_REVIEW',
68
+ severity: 'info',
69
+ source: 'doctor',
70
+ resource: resource.name,
71
+ message: `Resource "${resource.name}" uses the JSON store in production mode.`,
72
+ hint: 'Keep JSON-backed production resources small, low-write, single-writer, and backed up; move high-write, multi-writer, transactional, or compliance-heavy data to SQLite, Postgres, or another app-owned store.',
73
+ details: {
74
+ resource: resource.name,
75
+ store: driver,
76
+ kind: resource.kind,
77
+ production: true,
78
+ },
79
+ });
80
+ if (!resource.schemaPath) {
81
+ findings.push({
82
+ code: 'DOCTOR_JSON_PRODUCTION_SCHEMA_RECOMMENDED',
83
+ severity: 'warn',
84
+ source: 'doctor',
85
+ resource: resource.name,
86
+ message: `Production JSON resource "${resource.name}" does not have an explicit schema file.`,
87
+ hint: `Add db/${resource.name}.schema.jsonc or a root db.schema.js entry so production writes validate against a reviewed contract.`,
88
+ details: {
89
+ resource: resource.name,
90
+ store: driver,
91
+ kind: resource.kind,
92
+ production: true,
93
+ },
94
+ });
95
+ }
96
+ return findings;
97
+ });
98
+ }
99
+ function storeConfigFindings(resources, config) {
100
+ return resources.flatMap((resource) => {
101
+ const findings = [];
102
+ const resourceConfig = resourceConfigValue(config.resources, resource.name);
103
+ const storeName = resourceConfig?.store ?? config.stores?.default ?? 'json';
104
+ const availableStores = configuredStoreNames(config);
105
+ if (!availableStores.includes(storeName) && config.stores?.[storeName] === undefined) {
106
+ findings.push({
107
+ code: 'DOCTOR_STORE_NOT_FOUND',
108
+ severity: 'error',
109
+ source: 'doctor',
110
+ resource: resource.name,
111
+ message: `Resource "${resource.name}" is configured with missing store "${storeName}".`,
112
+ hint: `Configure stores.${storeName}, choose stores.default, or use one of: ${availableStores.join(', ')}.`,
113
+ details: {
114
+ resource: resource.name,
115
+ store: storeName,
116
+ availableStores,
117
+ },
118
+ });
119
+ return findings;
120
+ }
121
+ const driver = resolveStoreDriver(storeName, config);
122
+ if (resource.kind === 'collection'
123
+ && driver === 'json'
124
+ && Array.isArray(resource.seed)
125
+ && resource.seed.length > LARGE_JSON_STORE_RECORD_COUNT
126
+ && !hasIndexMetadata(resourceConfig)) {
127
+ findings.push({
128
+ code: 'DOCTOR_LARGE_JSON_STORE_WITHOUT_INDEXES',
129
+ severity: 'warn',
130
+ source: 'doctor',
131
+ resource: resource.name,
132
+ message: `Resource "${resource.name}" has ${resource.seed.length} records in the JSON store without index metadata.`,
133
+ hint: `Add resources.${resource.name}.indexes for dashboard or range-query fields, or bind the resource to a store better suited for large collections.`,
134
+ details: {
135
+ resource: resource.name,
136
+ store: driver,
137
+ recordCount: resource.seed.length,
138
+ },
139
+ });
140
+ }
141
+ return findings;
142
+ });
143
+ }
144
+ function configuredStoreNames(config) {
145
+ return [
146
+ ...new Set([
147
+ ...BUILTIN_STORES,
148
+ ...Object.keys(config.stores ?? {}).filter((name) => name !== 'default'),
149
+ ]),
150
+ ];
151
+ }
152
+ function resolveStoreDriver(storeName, config) {
153
+ const configured = config.stores?.[storeName] ?? storeName;
154
+ if (typeof configured === 'string') {
155
+ return configured;
156
+ }
157
+ if (configured && typeof configured === 'object' && 'driver' in configured) {
158
+ return configured.driver;
159
+ }
160
+ return storeName;
161
+ }
162
+ function hasIndexMetadata(resourceConfig) {
163
+ return Array.isArray(resourceConfig?.indexes) && resourceConfig.indexes.length > 0;
164
+ }
165
+ function summarizeFindings(findings) {
166
+ return findings.reduce((summary, finding) => {
167
+ summary[finding.severity] = (summary[finding.severity] ?? 0) + 1;
168
+ return summary;
169
+ }, {
170
+ error: 0,
171
+ warn: 0,
172
+ info: 0,
173
+ });
174
+ }
175
+ function isRecord(value) {
176
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
177
+ }
@@ -0,0 +1,22 @@
1
+ type DoctorRelation = {
2
+ sourceField: string;
3
+ };
4
+ type DoctorResource = {
5
+ name: string;
6
+ idField: string;
7
+ fields?: Record<string, unknown>;
8
+ relations?: DoctorRelation[];
9
+ seed: Array<Record<string, unknown>>;
10
+ };
11
+ type DoctorFinding = {
12
+ code: string;
13
+ severity: 'warn' | 'info';
14
+ source: 'doctor';
15
+ resource: string;
16
+ field: string;
17
+ message: string;
18
+ hint: string;
19
+ details: Record<string, unknown>;
20
+ };
21
+ export declare function relationSuggestionFindings(collections: DoctorResource[]): DoctorFinding[];
22
+ export {};
@@ -0,0 +1,90 @@
1
+ export function relationSuggestionFindings(collections) {
2
+ const findings = [];
3
+ for (const source of collections) {
4
+ const explicitRelationFields = new Set((source.relations ?? []).map((relation) => relation.sourceField));
5
+ for (const fieldName of Object.keys(source.fields ?? {})) {
6
+ if (fieldName === source.idField || explicitRelationFields.has(fieldName) || !fieldName.endsWith('Id')) {
7
+ continue;
8
+ }
9
+ const relationName = fieldName.slice(0, -2);
10
+ const target = collections.find((candidate) => candidate.name !== source.name && relationNameMatchesResource(relationName, candidate.name));
11
+ if (!target) {
12
+ continue;
13
+ }
14
+ const sourceValues = source.seed
15
+ .map((record) => record?.[fieldName])
16
+ .filter((value) => !isEmpty(value));
17
+ if (sourceValues.length === 0) {
18
+ continue;
19
+ }
20
+ const targetValues = new Set(target.seed
21
+ .map((record) => record?.[target.idField])
22
+ .filter((value) => !isEmpty(value))
23
+ .map((value) => String(value)));
24
+ const matchingValues = sourceValues
25
+ .filter((value) => targetValues.has(String(value)));
26
+ const missingValues = [...new Set(sourceValues
27
+ .filter((value) => !targetValues.has(String(value)))
28
+ .map((value) => String(value)))];
29
+ const matchingCount = matchingValues.length;
30
+ if (matchingCount === 0) {
31
+ continue;
32
+ }
33
+ const suggestedRelation = {
34
+ name: relationName,
35
+ to: target.name,
36
+ toField: target.idField,
37
+ cardinality: 'one',
38
+ };
39
+ if (missingValues.length > 0) {
40
+ findings.push({
41
+ code: 'DOCTOR_RELATION_MISSING_TARGET_VALUES',
42
+ severity: 'warn',
43
+ source: 'doctor',
44
+ resource: source.name,
45
+ field: fieldName,
46
+ message: `${source.name}.${fieldName} looks related to ${target.name}.${target.idField}, but ${missingValues.length} value(s) are missing from ${target.name}.`,
47
+ hint: `Add matching ${target.name} records, fix ${source.name}.${fieldName}, or ignore this if the field is not a relation.`,
48
+ details: {
49
+ suggestedRelation,
50
+ missingValues,
51
+ matchingCount,
52
+ },
53
+ });
54
+ continue;
55
+ }
56
+ findings.push({
57
+ code: 'DOCTOR_RELATION_SUGGESTION',
58
+ severity: 'info',
59
+ source: 'doctor',
60
+ resource: source.name,
61
+ field: fieldName,
62
+ message: `Possible relation detected: ${source.name}.${fieldName} -> ${target.name}.${target.idField}.`,
63
+ hint: `Add relation metadata to ${source.name}.schema.json to enable ?expand=${relationName}.`,
64
+ details: {
65
+ suggestedRelation,
66
+ matchingCount,
67
+ },
68
+ });
69
+ }
70
+ }
71
+ return findings;
72
+ }
73
+ function relationNameMatchesResource(relationName, resourceName) {
74
+ const normalizedRelation = relationName.toLowerCase();
75
+ return resourceNameVariants(resourceName).has(normalizedRelation);
76
+ }
77
+ function resourceNameVariants(resourceName) {
78
+ const normalized = resourceName.toLowerCase();
79
+ const variants = new Set([normalized]);
80
+ if (normalized.endsWith('ies') && normalized.length > 3) {
81
+ variants.add(`${normalized.slice(0, -3)}y`);
82
+ }
83
+ if (normalized.endsWith('s') && normalized.length > 1) {
84
+ variants.add(normalized.slice(0, -1));
85
+ }
86
+ return variants;
87
+ }
88
+ function isEmpty(value) {
89
+ return value === undefined || value === null || value === '';
90
+ }
@@ -0,0 +1,35 @@
1
+ type SchemaField = {
2
+ type?: string;
3
+ required?: boolean;
4
+ nullable?: boolean;
5
+ values?: unknown[];
6
+ items?: SchemaField;
7
+ fields?: Record<string, SchemaField>;
8
+ [key: string]: unknown;
9
+ };
10
+ type SchemaResource = {
11
+ name: string;
12
+ kind: string;
13
+ idField?: string;
14
+ schemaPath?: string | null;
15
+ dataPath?: string | null;
16
+ typeSource?: string;
17
+ description?: string;
18
+ fields?: Record<string, SchemaField>;
19
+ seed?: unknown;
20
+ };
21
+ type SchemaProject = {
22
+ resources: SchemaResource[];
23
+ };
24
+ type DoctorFinding = {
25
+ code: string;
26
+ severity: 'info';
27
+ source: 'doctor';
28
+ resource: string;
29
+ field?: string;
30
+ message: string;
31
+ hint: string;
32
+ details: Record<string, unknown>;
33
+ };
34
+ export declare function schemaGuidanceFindings(project: SchemaProject, inferredProject: SchemaProject): DoctorFinding[];
35
+ export {};
@@ -0,0 +1,184 @@
1
+ export function schemaGuidanceFindings(project, inferredProject) {
2
+ return [
3
+ ...schemaRecommendedFindings(project.resources),
4
+ ...schemaMatchesInferenceFindings(project.resources, inferredProject.resources),
5
+ ];
6
+ }
7
+ function schemaRecommendedFindings(resources) {
8
+ const findings = [];
9
+ const seen = new Set();
10
+ for (const resource of resources) {
11
+ if (resource.schemaPath || resource.typeSource !== 'data') {
12
+ continue;
13
+ }
14
+ for (const fieldPath of ambiguousPolymorphicArrayPaths(resource.seed, resource.kind)) {
15
+ const key = `${resource.name}:${fieldPath}`;
16
+ if (seen.has(key)) {
17
+ continue;
18
+ }
19
+ seen.add(key);
20
+ findings.push({
21
+ code: 'DOCTOR_SCHEMA_RECOMMENDED',
22
+ severity: 'info',
23
+ source: 'doctor',
24
+ resource: resource.name,
25
+ field: fieldPath,
26
+ message: `${resource.name}.${fieldPath} has polymorphic array data that db cannot infer confidently.`,
27
+ hint: `Run async-db schema infer ${resource.name} --out db/${resource.name}.schema.jsonc to lock the current inferred shape into an explicit schema.`,
28
+ details: {
29
+ resource: resource.name,
30
+ field: fieldPath,
31
+ },
32
+ });
33
+ }
34
+ }
35
+ return findings;
36
+ }
37
+ function schemaMatchesInferenceFindings(resources, inferredResources) {
38
+ const inferredByName = new Map(inferredResources.map((resource) => [resource.name, resource]));
39
+ const findings = [];
40
+ for (const resource of resources) {
41
+ if (!resource.schemaPath || !resource.dataPath || hasNonInferableContractValue(resource)) {
42
+ continue;
43
+ }
44
+ const inferred = inferredByName.get(resource.name);
45
+ if (!inferred) {
46
+ continue;
47
+ }
48
+ if (JSON.stringify(comparableResource(resource)) !== JSON.stringify(comparableResource(inferred))) {
49
+ continue;
50
+ }
51
+ findings.push({
52
+ code: 'DOCTOR_SCHEMA_MATCHES_INFERENCE',
53
+ severity: 'info',
54
+ source: 'doctor',
55
+ resource: resource.name,
56
+ message: `${resource.name} schema matches inferred data and may be removable.`,
57
+ hint: 'Keep the schema if you want it as an explicit contract; otherwise the data fixture can infer the same local shape.',
58
+ details: {
59
+ resource: resource.name,
60
+ },
61
+ });
62
+ }
63
+ return findings;
64
+ }
65
+ function ambiguousPolymorphicArrayPaths(seed, kind) {
66
+ const roots = kind === 'collection' && Array.isArray(seed)
67
+ ? seed
68
+ : [seed];
69
+ const paths = [];
70
+ for (const root of roots) {
71
+ collectAmbiguousArrayPaths(root, '', paths);
72
+ }
73
+ return paths;
74
+ }
75
+ function collectAmbiguousArrayPaths(value, path, paths) {
76
+ if (Array.isArray(value)) {
77
+ if (isAmbiguousPolymorphicArray(value) && path) {
78
+ paths.push(path);
79
+ return;
80
+ }
81
+ for (const item of value) {
82
+ collectAmbiguousArrayPaths(item, path, paths);
83
+ }
84
+ return;
85
+ }
86
+ if (!isPlainRecord(value)) {
87
+ return;
88
+ }
89
+ for (const [fieldName, childValue] of Object.entries(value)) {
90
+ collectAmbiguousArrayPaths(childValue, path ? `${path}.${fieldName}` : fieldName, paths);
91
+ }
92
+ }
93
+ function isAmbiguousPolymorphicArray(value) {
94
+ const records = value.filter((item) => item !== null && item !== undefined && isPlainRecord(item));
95
+ if (records.length < 2 || records.length !== value.filter((item) => item !== null && item !== undefined).length) {
96
+ return false;
97
+ }
98
+ const signatures = new Set(records.map((record) => Object.keys(record).sort().join('|')));
99
+ return signatures.size > 1 && !hasStableDiscriminator(records);
100
+ }
101
+ function hasStableDiscriminator(records) {
102
+ return ['type', 'kind', 'blockType'].some((fieldName) => {
103
+ const values = records.map((record) => record[fieldName]);
104
+ return values.every((value) => typeof value === 'string' && value !== '')
105
+ && new Set(values).size >= 2;
106
+ });
107
+ }
108
+ function hasNonInferableContractValue(resource) {
109
+ if (resource.description) {
110
+ return true;
111
+ }
112
+ return Object.values(resource.fields ?? {}).some((field) => fieldHasNonInferableContractValue(field));
113
+ }
114
+ function fieldHasNonInferableContractValue(field) {
115
+ for (const property of [
116
+ 'description',
117
+ 'default',
118
+ 'unique',
119
+ 'relation',
120
+ 'min',
121
+ 'max',
122
+ 'minLength',
123
+ 'maxLength',
124
+ 'pattern',
125
+ 'additionalProperties',
126
+ 'variants',
127
+ ]) {
128
+ if (property in field) {
129
+ return true;
130
+ }
131
+ }
132
+ if (field.type === 'array') {
133
+ return fieldHasNonInferableContractValue(field.items ?? { type: 'unknown' });
134
+ }
135
+ if (field.type === 'object') {
136
+ return Object.values(field.fields ?? {}).some((childField) => fieldHasNonInferableContractValue(childField));
137
+ }
138
+ return false;
139
+ }
140
+ function comparableResource(resource) {
141
+ const comparable = {
142
+ kind: resource.kind,
143
+ fields: comparableFieldMap(resource.fields ?? {}),
144
+ };
145
+ if (resource.kind === 'collection') {
146
+ comparable.idField = resource.idField;
147
+ }
148
+ return sortObject(comparable);
149
+ }
150
+ function comparableFieldMap(fields) {
151
+ return Object.fromEntries(Object.entries(fields)
152
+ .sort(([left], [right]) => left.localeCompare(right))
153
+ .map(([fieldName, field]) => [fieldName, comparableField(field)]));
154
+ }
155
+ function comparableField(field) {
156
+ const comparable = {
157
+ type: field.type ?? 'unknown',
158
+ required: Boolean(field.required),
159
+ };
160
+ if ('nullable' in field) {
161
+ comparable.nullable = Boolean(field.nullable);
162
+ }
163
+ if (field.type === 'enum') {
164
+ comparable.values = [...(field.values ?? [])];
165
+ }
166
+ if (field.type === 'array') {
167
+ comparable.items = comparableField(field.items ?? { type: 'unknown' });
168
+ }
169
+ if (field.type === 'object') {
170
+ comparable.fields = comparableFieldMap(field.fields ?? {});
171
+ }
172
+ return sortObject(comparable);
173
+ }
174
+ function sortObject(value) {
175
+ if (!isPlainRecord(value)) {
176
+ return value;
177
+ }
178
+ return Object.fromEntries(Object.entries(value)
179
+ .sort(([left], [right]) => left.localeCompare(right))
180
+ .map(([key, childValue]) => [key, sortObject(childValue)]));
181
+ }
182
+ function isPlainRecord(value) {
183
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
184
+ }
@@ -0,0 +1,14 @@
1
+ type GeneratorConfig = Record<string, unknown>;
2
+ type Generator = {
3
+ name: string;
4
+ usage: string;
5
+ run: (config: GeneratorConfig, args: string[]) => Promise<unknown>;
6
+ };
7
+ type GeneratorRegistry = {
8
+ names: () => string[];
9
+ get: (name: string) => Generator | undefined;
10
+ usage: () => string;
11
+ };
12
+ export declare function defaultGeneratorRegistry(): GeneratorRegistry;
13
+ export declare function createGeneratorRegistry(generators: Generator[]): GeneratorRegistry;
14
+ export {};
@@ -0,0 +1,37 @@
1
+ import { generateHonoStarter } from '../../generate/hono.js';
2
+ import { valueAfter } from '../../cli/args.js';
3
+ export function defaultGeneratorRegistry() {
4
+ return createGeneratorRegistry([
5
+ honoGenerator(),
6
+ ]);
7
+ }
8
+ export function createGeneratorRegistry(generators) {
9
+ const byName = new Map(generators.map((generator) => [generator.name, generator]));
10
+ return {
11
+ names() {
12
+ return [...byName.keys()];
13
+ },
14
+ get(name) {
15
+ return byName.get(name);
16
+ },
17
+ usage() {
18
+ return generators.map((generator) => generator.usage).join('\n');
19
+ },
20
+ };
21
+ }
22
+ function honoGenerator() {
23
+ return {
24
+ name: 'hono',
25
+ usage: 'async-db generate hono [--out <dir>] [--api <rest|graphql|rest,graphql|none>] [--db sqlite] [--app <standalone|module>] [--seed fixtures] [--allow-warnings]',
26
+ async run(config, args) {
27
+ return generateHonoStarter(config, {
28
+ outDir: valueAfter(args, '--out'),
29
+ api: valueAfter(args, '--api'),
30
+ db: valueAfter(args, '--db'),
31
+ app: valueAfter(args, '--app'),
32
+ seed: valueAfter(args, '--seed'),
33
+ allowWarnings: args.includes('--allow-warnings') ? true : undefined,
34
+ });
35
+ },
36
+ };
37
+ }
@@ -0,0 +1,46 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ type HttpFeaturePhase = 'preMock' | 'postMock' | string;
3
+ type RuntimeEventSource = {
4
+ subscribe: (subscriber: (event: unknown) => void) => () => void;
5
+ };
6
+ type HttpFeatureDb = {
7
+ config: {
8
+ graphql?: {
9
+ enabled?: boolean;
10
+ };
11
+ server?: {
12
+ maxBodyBytes?: number;
13
+ };
14
+ };
15
+ resources: Map<string, unknown>;
16
+ events: RuntimeEventSource;
17
+ [key: string]: unknown;
18
+ };
19
+ type HttpRoutes = {
20
+ logPath: string;
21
+ graphqlPath: string;
22
+ [key: string]: unknown;
23
+ };
24
+ type HttpFeatureContext = {
25
+ db: HttpFeatureDb;
26
+ request: IncomingMessage;
27
+ response: ServerResponse;
28
+ url: URL;
29
+ routes: HttpRoutes;
30
+ };
31
+ type HttpFeature = {
32
+ name: string;
33
+ phase?: HttpFeaturePhase;
34
+ match: (context: HttpFeatureContext) => boolean;
35
+ handle: (context: HttpFeatureContext) => void | Promise<void>;
36
+ };
37
+ type HttpFeatureOptions = {
38
+ phase?: HttpFeaturePhase;
39
+ };
40
+ type HttpFeatureRegistry = {
41
+ matches: (context: HttpFeatureContext, options?: HttpFeatureOptions) => boolean;
42
+ handle: (context: HttpFeatureContext, options?: HttpFeatureOptions) => Promise<boolean>;
43
+ };
44
+ export declare function defaultHttpFeatureRegistry(): HttpFeatureRegistry;
45
+ export declare function createHttpFeatureRegistry(features: HttpFeature[]): HttpFeatureRegistry;
46
+ export {};