rnxsim 0.0.0 → 0.1.313

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 (228) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +512 -0
  3. package/detox/colors.ts +54 -0
  4. package/detox/config-loader.ts +135 -0
  5. package/detox/element-types.ts +39 -0
  6. package/detox/expectations.ts +537 -0
  7. package/detox/gestures.ts +480 -0
  8. package/detox/index.ts +2118 -0
  9. package/detox/jest-preset.cjs +51 -0
  10. package/detox/jest-setup-after-env.cjs +18 -0
  11. package/detox/matchers.ts +30 -0
  12. package/detox/motion-change.cjs +109 -0
  13. package/detox/motion-change.d.cts +35 -0
  14. package/detox/navigation.ts +43 -0
  15. package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
  16. package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
  17. package/detox/screenshots/color-test-rest-norngh.png +0 -0
  18. package/detox/screenshots/theme-blue-toggle.png +0 -0
  19. package/detox/screenshots/theme-blue.png +0 -0
  20. package/detox/screenshots/theme-red-toggle.png +0 -0
  21. package/detox/screenshots/theme-red.png +0 -0
  22. package/dist-cli/bin.js +15 -0
  23. package/dist-cli/chunks/agent-XZ2KTPCU.js +48 -0
  24. package/dist-cli/chunks/agent-wrapper-JJYYW2WH.js +17 -0
  25. package/dist-cli/chunks/app-fonts-IXRNQG6B.js +4 -0
  26. package/dist-cli/chunks/assert-54T5SK5F.js +49 -0
  27. package/dist-cli/chunks/auth-FI5UDI45.js +12 -0
  28. package/dist-cli/chunks/beta-JV6UKADW.js +4 -0
  29. package/dist-cli/chunks/camera-VL73YIKP.js +22 -0
  30. package/dist-cli/chunks/chunk-2D2UPBBR.js +4 -0
  31. package/dist-cli/chunks/chunk-2YR5BGA5.js +5 -0
  32. package/dist-cli/chunks/chunk-3NV2NCNX.js +4 -0
  33. package/dist-cli/chunks/chunk-46EUUFJ5.js +4 -0
  34. package/dist-cli/chunks/chunk-4NPPOV2N.js +5 -0
  35. package/dist-cli/chunks/chunk-5DHC6KHQ.js +7 -0
  36. package/dist-cli/chunks/chunk-5TEF3ET3.js +74 -0
  37. package/dist-cli/chunks/chunk-5TPRP5QT.js +4 -0
  38. package/dist-cli/chunks/chunk-5YJCOWCH.js +4 -0
  39. package/dist-cli/chunks/chunk-7GN3LVWB.js +4 -0
  40. package/dist-cli/chunks/chunk-BBULZ7CG.js +568 -0
  41. package/dist-cli/chunks/chunk-BTWORNNG.js +71 -0
  42. package/dist-cli/chunks/chunk-D4FFVGI5.js +5 -0
  43. package/dist-cli/chunks/chunk-DCEMHR2Y.js +4 -0
  44. package/dist-cli/chunks/chunk-DZS6WPUI.js +4 -0
  45. package/dist-cli/chunks/chunk-F5ZRSS3C.js +3 -0
  46. package/dist-cli/chunks/chunk-FSUYIVJ6.js +9 -0
  47. package/dist-cli/chunks/chunk-G2WW6L2C.js +23 -0
  48. package/dist-cli/chunks/chunk-GASE6UBA.js +4 -0
  49. package/dist-cli/chunks/chunk-GGRX24GF.js +142 -0
  50. package/dist-cli/chunks/chunk-HI5TFJWN.js +5 -0
  51. package/dist-cli/chunks/chunk-IJ5CAZZC.js +5 -0
  52. package/dist-cli/chunks/chunk-IJO63TDP.js +4 -0
  53. package/dist-cli/chunks/chunk-KTHV3RUS.js +26 -0
  54. package/dist-cli/chunks/chunk-LF2ZVT7O.js +6 -0
  55. package/dist-cli/chunks/chunk-MJRLLB4R.js +38 -0
  56. package/dist-cli/chunks/chunk-NFK7T35W.js +4 -0
  57. package/dist-cli/chunks/chunk-NMF2ZMZQ.js +15 -0
  58. package/dist-cli/chunks/chunk-OHAZNXLK.js +4 -0
  59. package/dist-cli/chunks/chunk-OVFJFXUD.js +101 -0
  60. package/dist-cli/chunks/chunk-OZSSI4WN.js +28 -0
  61. package/dist-cli/chunks/chunk-QKDWYITG.js +25 -0
  62. package/dist-cli/chunks/chunk-RSZWCKNT.js +30 -0
  63. package/dist-cli/chunks/chunk-RTN5C5RL.js +4 -0
  64. package/dist-cli/chunks/chunk-TIVZIMMW.js +4 -0
  65. package/dist-cli/chunks/chunk-TZFFR3SD.js +7 -0
  66. package/dist-cli/chunks/chunk-UC6U3MML.js +5 -0
  67. package/dist-cli/chunks/chunk-VFCMSYZK.js +6 -0
  68. package/dist-cli/chunks/chunk-VNQEB4L7.js +5 -0
  69. package/dist-cli/chunks/chunk-VZXWHRUZ.js +983 -0
  70. package/dist-cli/chunks/chunk-W6K4EFPH.js +107 -0
  71. package/dist-cli/chunks/chunk-WEXDAC74.js +4 -0
  72. package/dist-cli/chunks/chunk-WF3T4SVI.js +14 -0
  73. package/dist-cli/chunks/chunk-WINYQ44O.js +4 -0
  74. package/dist-cli/chunks/chunk-WMIIKMGK.js +10 -0
  75. package/dist-cli/chunks/chunk-WUSWBCWA.js +507 -0
  76. package/dist-cli/chunks/chunk-WWZIXIRD.js +4 -0
  77. package/dist-cli/chunks/chunk-XEVZYVIW.js +766 -0
  78. package/dist-cli/chunks/chunk-YDGQTMQL.js +4 -0
  79. package/dist-cli/chunks/chunk-YIFT42WN.js +4 -0
  80. package/dist-cli/chunks/chunk-ZMJD5GEC.js +125 -0
  81. package/dist-cli/chunks/cleanup-P27PA6JI.js +25 -0
  82. package/dist-cli/chunks/cli-version-WWLPBDQ7.js +4 -0
  83. package/dist-cli/chunks/compat-ZD65FED3.js +20 -0
  84. package/dist-cli/chunks/config-XMJRNM2A.js +13 -0
  85. package/dist-cli/chunks/control-KMIQT3QP.js +4 -0
  86. package/dist-cli/chunks/daemon-G2ME7NLB.js +4 -0
  87. package/dist-cli/chunks/debug-PT4HOP7N.js +200 -0
  88. package/dist-cli/chunks/desktop-S3FG72AK.js +31 -0
  89. package/dist-cli/chunks/detox-B3D4IFCN.js +52 -0
  90. package/dist-cli/chunks/device-XBNDSB2R.js +18 -0
  91. package/dist-cli/chunks/diagnose-HMQXJE5N.js +43 -0
  92. package/dist-cli/chunks/disk-cleanup-BLCZ5BSZ.js +4 -0
  93. package/dist-cli/chunks/drivers-LDECZGP2.js +4 -0
  94. package/dist-cli/chunks/film-BJGTBYZB.js +74 -0
  95. package/dist-cli/chunks/flow-UEQNVTU7.js +4 -0
  96. package/dist-cli/chunks/help-T5FYSVGB.js +4 -0
  97. package/dist-cli/chunks/hidden-runtime-alias-ANOYADHM.js +4 -0
  98. package/dist-cli/chunks/home-paths-GT3LFNOR.js +4 -0
  99. package/dist-cli/chunks/inspect-ZA6XF5LD.js +4 -0
  100. package/dist-cli/chunks/install-desktop-TIMUDHPL.js +4 -0
  101. package/dist-cli/chunks/login-FJ737MWG.js +28 -0
  102. package/dist-cli/chunks/logout-ZCNMMHMY.js +4 -0
  103. package/dist-cli/chunks/maestro-SZTNKLDF.js +104 -0
  104. package/dist-cli/chunks/maestro-generate-DCFAIZ4H.js +36 -0
  105. package/dist-cli/chunks/mode-GRMQCRXR.js +26 -0
  106. package/dist-cli/chunks/optional-demo-registry-W36EWFFB.js +4 -0
  107. package/dist-cli/chunks/perf-QYBAAUZG.js +48 -0
  108. package/dist-cli/chunks/permissions-3QCQ6VF4.js +13 -0
  109. package/dist-cli/chunks/record-QPPC2S4E.js +72 -0
  110. package/dist-cli/chunks/report-issue-7NMFP4HK.js +29 -0
  111. package/dist-cli/chunks/runtime-XOAXMSTU.js +4 -0
  112. package/dist-cli/chunks/screenshot-command-67AECJFB.js +159 -0
  113. package/dist-cli/chunks/screenshot-layers-ASWBYPJL.js +20 -0
  114. package/dist-cli/chunks/screenshots-capture-PXA3HFQK.js +42 -0
  115. package/dist-cli/chunks/serve-BI2NBAXG.js +44 -0
  116. package/dist-cli/chunks/setup-7DWPMRSB.js +32 -0
  117. package/dist-cli/chunks/skills-S3Y22TUA.js +50 -0
  118. package/dist-cli/chunks/storage-XUIMJWAJ.js +37 -0
  119. package/dist-cli/chunks/store-JTHEJLAZ.js +4 -0
  120. package/dist-cli/chunks/telemetry-ZYJGD2DB.js +4 -0
  121. package/dist-cli/chunks/timeline-TMPLQPSP.js +24 -0
  122. package/dist-cli/chunks/upgrade-7HDSIM7K.js +7 -0
  123. package/dist-cli/chunks/upload-GMSZPWM6.js +4 -0
  124. package/dist-cli/chunks/version-HOCHZ37L.js +6 -0
  125. package/dist-cli/chunks/web-DG3WBYD3.js +4 -0
  126. package/dist-cli/chunks/what-happened-XFVUTZR7.js +25 -0
  127. package/dist-lib/agent-daemon-client.cjs +468 -0
  128. package/dist-lib/agent-events.cjs +48 -0
  129. package/dist-lib/agent-identity.cjs +53 -0
  130. package/dist-lib/agent-sessions.cjs +725 -0
  131. package/dist-lib/attached-projects.cjs +526 -0
  132. package/dist-lib/auth/shared-session.cjs +195 -0
  133. package/dist-lib/backend-origin.cjs +70 -0
  134. package/dist-lib/beta.cjs +41 -0
  135. package/dist-lib/beta.mjs +13 -0
  136. package/dist-lib/bridge-constants.cjs +161 -0
  137. package/dist-lib/cli-constants.cjs +32 -0
  138. package/dist-lib/config.cjs +96 -0
  139. package/dist-lib/detox/index.cjs +2863 -0
  140. package/dist-lib/detox/jest-preset.cjs +51 -0
  141. package/dist-lib/dev-bundle-resolution.cjs +223 -0
  142. package/dist-lib/home-paths.cjs +421 -0
  143. package/dist-lib/host/bridge-host.cjs +5600 -0
  144. package/dist-lib/host/fetch-proxy-handler.cjs +373 -0
  145. package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
  146. package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
  147. package/dist-lib/host/websocket-proxy.cjs +214 -0
  148. package/dist-lib/index.cjs +513 -0
  149. package/dist-lib/jump-to-source-babel.cjs +93 -0
  150. package/dist-lib/menu.cjs +620 -0
  151. package/dist-lib/menu.mjs +581 -0
  152. package/dist-lib/metro.cjs +297 -0
  153. package/dist-lib/profiles.cjs +246 -0
  154. package/dist-lib/public-brand.cjs +39 -0
  155. package/dist-lib/render-mode.cjs +54 -0
  156. package/dist-lib/scripts/dev-server-scanner.cjs +1034 -0
  157. package/dist-lib/sdk.cjs +2333 -0
  158. package/dist-lib/sdk.mjs +2240 -0
  159. package/dist-lib/skills.cjs +23922 -0
  160. package/dist-lib/vite.cjs +358 -0
  161. package/package.json +189 -18
  162. package/scripts/dev-server-scanner.ts +941 -0
  163. package/scripts/optional-demo-registry.ts +113 -0
  164. package/scripts/postinstall.cjs +3 -0
  165. package/scripts/preuninstall.cjs +118 -0
  166. package/skills/contrast/SKILL.md +171 -0
  167. package/skills/rnx-debug/SKILL.md +425 -0
  168. package/skills/rnx-perf/SKILL.md +295 -0
  169. package/skills/rnx-setup/SKILL.md +160 -0
  170. package/skills/rnx-test/SKILL.md +292 -0
  171. package/skills/rnx-visual/SKILL.md +122 -0
  172. package/src/agent-daemon-client.ts +410 -0
  173. package/src/agent-events.ts +71 -0
  174. package/src/agent-identity.ts +23 -0
  175. package/src/agent-prompt.ts +71 -0
  176. package/src/agent-sessions.ts +573 -0
  177. package/src/attached-projects.ts +539 -0
  178. package/src/auth/shared-session.ts +204 -0
  179. package/src/backend-origin.ts +49 -0
  180. package/src/beta.ts +19 -0
  181. package/src/bridge-constants.ts +171 -0
  182. package/src/browser-cache.ts +16 -0
  183. package/src/cli-constants.ts +1 -0
  184. package/src/cli-version.ts +32 -0
  185. package/src/codex-client.ts +215 -0
  186. package/src/config.ts +134 -0
  187. package/src/dev-bundle-resolution.ts +236 -0
  188. package/src/disk-cleanup.ts +466 -0
  189. package/src/home-paths.ts +552 -0
  190. package/src/host/agent-host.ts +666 -0
  191. package/src/host/bridge-host.ts +2626 -0
  192. package/src/host/fetch-proxy-handler.ts +397 -0
  193. package/src/host/fetch-proxy-overrides.ts +51 -0
  194. package/src/host/open-url.ts +89 -0
  195. package/src/host/websocket-proxy.ts +210 -0
  196. package/src/index.ts +12 -705
  197. package/src/jump-to-source-babel.ts +135 -0
  198. package/src/menu.ts +734 -0
  199. package/src/metro-plugin.ts +139 -0
  200. package/src/native-dev-bundle-url.ts +96 -0
  201. package/src/native-seam-manifest.ts +341 -0
  202. package/src/plist.ts +27 -0
  203. package/src/poll-until.ts +92 -0
  204. package/src/profiles.ts +179 -0
  205. package/src/public-brand.ts +9 -0
  206. package/src/render-mode.ts +27 -0
  207. package/src/runtime-assets.ts +228 -0
  208. package/src/runtime-delivery.ts +54 -0
  209. package/src/screenshots/compose.ts +422 -0
  210. package/src/screenshots/frame-compose.ts +454 -0
  211. package/src/screenshots/orchestrate.ts +256 -0
  212. package/src/screenshots/org-deck.ts +91 -0
  213. package/src/screenshots/registry.ts +58 -0
  214. package/src/screenshots/schema.ts +364 -0
  215. package/src/sdk.ts +7 -0
  216. package/src/settings-bundle.ts +418 -0
  217. package/src/skills/builtin/a11y-review.ts +132 -0
  218. package/src/skills/builtin/compat-check.ts +71 -0
  219. package/src/skills/builtin/maestro-test.ts +121 -0
  220. package/src/skills/builtin/perf-profile.ts +90 -0
  221. package/src/skills/builtin/screenshot-all.ts +52 -0
  222. package/src/skills/builtin/visual-diff.ts +100 -0
  223. package/src/skills/registry.ts +107 -0
  224. package/src/skills/types.ts +41 -0
  225. package/src/team-machine-prompt.ts +73 -0
  226. package/src/vite-plugin-one.ts +190 -0
  227. package/src/vite-plugin.ts +1750 -0
  228. package/src/worklets-babel.ts +134 -0
package/src/index.ts CHANGED
@@ -1,706 +1,13 @@
1
- // better-auth adapter for sqlite-shaped stores (Cloudflare D1, bun:sqlite,
2
- // node:sqlite). hand-written against better-auth's CustomAdapter contract so
3
- // we don't pull in kysely or drizzle just to run prepared statements.
1
+ // sootsim public package — CLI + framework integrations.
4
2
  //
5
- // the adapter accepts a minimal `Sqlite` executor interface — anything that
6
- // can `exec(sql, params)` and return rows/meta. drivers for the three target
7
- // stores live alongside: `d1Driver`, `bunSqliteDriver`, `nodeSqliteDriver`.
8
- //
9
- // schema generation (createSchema) emits a CREATE TABLE script from
10
- // better-auth's resolved BetterAuthDBSchema. apply once at deploy time.
11
-
12
- import { createAdapterFactory } from '@better-auth/core/db/adapter'
13
- import type { BetterAuthDBSchema, DBFieldAttribute } from '@better-auth/core/db'
14
- import type {
15
- AdapterFactoryOptions,
16
- CustomAdapter,
17
- CleanedWhere,
18
- } from '@better-auth/core/db/adapter'
19
-
20
- // ── executor abstraction ──────────────────────────────────────────────────
21
- // what the adapter actually needs from the underlying store. all three drivers
22
- // (d1, bun-sqlite, node-sqlite) hide behind this shape so the adapter logic
23
- // stays the same.
24
-
25
- export interface SqliteExecutor {
26
- /** run a query that returns rows */
27
- all<T = Record<string, unknown>>(sql: string, params: readonly unknown[]): Promise<T[]>
28
- /** run a query that returns a single row or null */
29
- first<T = Record<string, unknown>>(
30
- sql: string,
31
- params: readonly unknown[],
32
- ): Promise<T | null>
33
- /** run a mutation (INSERT/UPDATE/DELETE) — returns rows-affected */
34
- run(sql: string, params: readonly unknown[]): Promise<{ changes: number }>
35
- /** run a raw schema statement (no params) */
36
- exec(sql: string): Promise<void>
37
- }
38
-
39
- // minimal D1 binding surface — matches @cloudflare/workers-types without
40
- // importing it (keeps the package zero-dep at type-level).
41
- interface D1DatabaseLike {
42
- prepare(sql: string): {
43
- bind(...params: unknown[]): {
44
- all<T = unknown>(): Promise<{ results: T[]; meta?: { changes?: number } }>
45
- first<T = unknown>(): Promise<T | null>
46
- run(): Promise<{ meta?: { changes?: number } }>
47
- }
48
- }
49
- exec(sql: string): Promise<unknown>
50
- }
51
-
52
- export function d1Driver(db: D1DatabaseLike): SqliteExecutor {
53
- return {
54
- all: async (sql, params) => {
55
- const { results } = await db
56
- .prepare(sql)
57
- .bind(...params)
58
- .all()
59
- return (results ?? []) as never
60
- },
61
- first: async (sql, params) => {
62
- return (await db
63
- .prepare(sql)
64
- .bind(...params)
65
- .first()) as never
66
- },
67
- run: async (sql, params) => {
68
- const { meta } = await db
69
- .prepare(sql)
70
- .bind(...params)
71
- .run()
72
- return { changes: meta?.changes ?? 0 }
73
- },
74
- exec: async (sql) => {
75
- // D1.exec treats *every* newline as a statement boundary, which
76
- // explodes when buildSchemaSql emits multi-line `CREATE TABLE (...)`.
77
- // Split on `;` (the canonical SQL terminator), drop empties, then
78
- // dispatch each statement via prepare().run() so D1 sees one
79
- // statement per call. Each statement may freely contain newlines.
80
- const statements = sql
81
- .split(/;\s*/)
82
- .map((s) => s.trim())
83
- .filter(Boolean)
84
- for (const stmt of statements) {
85
- await db.prepare(stmt).bind().run()
86
- }
87
- },
88
- }
89
- }
90
-
91
- // bun:sqlite / node:sqlite — they share enough surface that one shim covers
92
- // both. you pass in a Database instance and we drive it.
93
- interface NodeBunSqliteDatabase {
94
- prepare(sql: string): {
95
- all(...params: unknown[]): unknown[]
96
- get(...params: unknown[]): unknown
97
- run(...params: unknown[]): { changes?: number }
98
- }
99
- exec(sql: string): unknown
100
- }
101
-
102
- export function nodeBunSqliteDriver(db: NodeBunSqliteDatabase): SqliteExecutor {
103
- return {
104
- all: async (sql, params) => db.prepare(sql).all(...params) as never,
105
- first: async (sql, params) => (db.prepare(sql).get(...params) as never) ?? null,
106
- run: async (sql, params) => {
107
- const res = db.prepare(sql).run(...params)
108
- return { changes: res.changes ?? 0 }
109
- },
110
- exec: async (sql) => {
111
- db.exec(sql)
112
- },
113
- }
114
- }
115
-
116
- // ── SQL builders ──────────────────────────────────────────────────────────
117
- // rendering happens against `CleanedWhere` after better-auth has normalised
118
- // model/field names via getModelName/getFieldName.
119
-
120
- const quote = (id: string) => `"${id.replace(/"/g, '""')}"`
121
-
122
- function renderWhereClause(where: CleanedWhere[]): {
123
- sql: string
124
- params: unknown[]
125
- } {
126
- if (where.length === 0) return { sql: '', params: [] }
127
- const parts: string[] = []
128
- const params: unknown[] = []
129
- for (let i = 0; i < where.length; i++) {
130
- const clause = where[i]
131
- const connector = i === 0 ? '' : ` ${clause.connector} `
132
- const field = quote(clause.field)
133
- let frag: string
134
- switch (clause.operator) {
135
- case 'eq':
136
- if (clause.value === null) frag = `${field} IS NULL`
137
- else {
138
- frag = `${field} = ?`
139
- params.push(serialize(clause.value))
140
- }
141
- break
142
- case 'ne':
143
- if (clause.value === null) frag = `${field} IS NOT NULL`
144
- else {
145
- frag = `${field} != ?`
146
- params.push(serialize(clause.value))
147
- }
148
- break
149
- case 'lt':
150
- frag = `${field} < ?`
151
- params.push(serialize(clause.value))
152
- break
153
- case 'lte':
154
- frag = `${field} <= ?`
155
- params.push(serialize(clause.value))
156
- break
157
- case 'gt':
158
- frag = `${field} > ?`
159
- params.push(serialize(clause.value))
160
- break
161
- case 'gte':
162
- frag = `${field} >= ?`
163
- params.push(serialize(clause.value))
164
- break
165
- case 'in': {
166
- const arr = clause.value as readonly unknown[]
167
- if (!arr.length) {
168
- frag = '0'
169
- break
170
- }
171
- frag = `${field} IN (${arr.map(() => '?').join(',')})`
172
- for (const v of arr) params.push(serialize(v))
173
- break
174
- }
175
- case 'not_in': {
176
- const arr = clause.value as readonly unknown[]
177
- if (!arr.length) {
178
- frag = '1'
179
- break
180
- }
181
- frag = `${field} NOT IN (${arr.map(() => '?').join(',')})`
182
- for (const v of arr) params.push(serialize(v))
183
- break
184
- }
185
- case 'contains':
186
- frag = `${field} LIKE ?`
187
- params.push(`%${String(clause.value)}%`)
188
- break
189
- case 'starts_with':
190
- frag = `${field} LIKE ?`
191
- params.push(`${String(clause.value)}%`)
192
- break
193
- case 'ends_with':
194
- frag = `${field} LIKE ?`
195
- params.push(`%${String(clause.value)}`)
196
- break
197
- default:
198
- throw new Error(`[better-auth-d1] unsupported operator: ${clause.operator}`)
199
- }
200
- parts.push(connector + frag)
201
- }
202
- return { sql: ` WHERE ${parts.join('')}`, params }
203
- }
204
-
205
- // SQLite has no native boolean/date/json — we encode them on the way in and
206
- // decode on the way out. better-auth keeps its own input typing so we don't
207
- // have to guess: anything that looks like a Date / object becomes a string,
208
- // booleans become 0/1.
209
- function serialize(value: unknown): unknown {
210
- if (value === undefined || value === null) return null
211
- if (value instanceof Date) return value.toISOString()
212
- if (typeof value === 'boolean') return value ? 1 : 0
213
- if (Array.isArray(value)) return JSON.stringify(value)
214
- if (typeof value === 'object') return JSON.stringify(value)
215
- return value
216
- }
217
-
218
- function deserializeRow(
219
- row: Record<string, unknown>,
220
- schemaFields: Record<string, DBFieldAttribute>,
221
- ): Record<string, unknown> {
222
- const out: Record<string, unknown> = {}
223
- for (const [k, v] of Object.entries(row)) {
224
- const attr = schemaFields[k]
225
- if (!attr) {
226
- out[k] = v
227
- continue
228
- }
229
- if (v === null || v === undefined) {
230
- out[k] = null
231
- continue
232
- }
233
- if (attr.type === 'boolean') {
234
- out[k] = v === 1 || v === '1' || v === true
235
- } else if (attr.type === 'date') {
236
- out[k] = typeof v === 'string' ? new Date(v) : v
237
- } else if (
238
- attr.type === 'json' ||
239
- attr.type === 'string[]' ||
240
- attr.type === 'number[]'
241
- ) {
242
- out[k] = typeof v === 'string' ? safeJsonParse(v) : v
243
- } else {
244
- // string | number — sqlite returns native types
245
- out[k] = v
246
- }
247
- }
248
- return out
249
- }
250
-
251
- function safeJsonParse(s: string): unknown {
252
- try {
253
- return JSON.parse(s)
254
- } catch {
255
- return s
256
- }
257
- }
258
-
259
- function fieldsOf(schema: BetterAuthDBSchema, model: string) {
260
- return schema[model]?.fields ?? {}
261
- }
262
-
263
- // ── schema generation ─────────────────────────────────────────────────────
264
- // build a CREATE TABLE script from the better-auth schema, so deploy time
265
- // can apply it once against a fresh D1 / sqlite file.
266
-
267
- function sqlType(field: DBFieldAttribute): string {
268
- if (field.type === 'number' || field.type === 'boolean') return 'INTEGER'
269
- // string | date | json | string[] | number[] | string-enum-array → TEXT
270
- return 'TEXT'
271
- }
272
-
273
- export function buildSchemaSql(schema: BetterAuthDBSchema): string {
274
- const stmts: string[] = []
275
- for (const [, model] of Object.entries(schema)) {
276
- const cols: string[] = ['"id" TEXT PRIMARY KEY NOT NULL']
277
- for (const [fieldName, attr] of Object.entries(model.fields)) {
278
- if (fieldName === 'id') continue
279
- const parts = [quote(fieldName), sqlType(attr)]
280
- if (attr.required !== false && attr.defaultValue === undefined) {
281
- // better-auth treats `required:true` as `defaultValue` may be
282
- // provided at runtime; only mark NOT NULL when there's no risk of
283
- // the field being absent on insert.
284
- if (attr.required === true) parts.push('NOT NULL')
285
- }
286
- if (attr.unique) parts.push('UNIQUE')
287
- cols.push(parts.join(' '))
288
- }
289
- stmts.push(
290
- `CREATE TABLE IF NOT EXISTS ${quote(model.modelName)} (\n ${cols.join(',\n ')}\n);`,
291
- )
292
- }
293
- return stmts.join('\n\n') + '\n'
294
- }
295
-
296
- // ── adapter factory ───────────────────────────────────────────────────────
297
-
298
- export interface D1AdapterConfig {
299
- /**
300
- * Pre-built executor. Use one of the bundled drivers (`d1Driver` /
301
- * `nodeBunSqliteDriver`) or implement `SqliteExecutor` yourself.
302
- */
303
- executor: SqliteExecutor
304
- /**
305
- * Use plural table names. better-auth defaults to singular; keep that
306
- * unless your existing schema is plural.
307
- */
308
- usePlural?: boolean
309
- debugLogs?: boolean
310
- /**
311
- * Apply `buildSchemaSql(schema)` on the first DB operation. The generated
312
- * statements are `CREATE TABLE IF NOT EXISTS`, so this is safe to run
313
- * every cold start. Defaults to true — set false if you prefer to run
314
- * migrations explicitly (e.g. via `wrangler d1 execute --file=<sql>` at
315
- * deploy time).
316
- */
317
- autoMigrate?: boolean
318
- }
319
-
320
- export function d1Adapter(config: D1AdapterConfig) {
321
- const exec = config.executor
322
- return createAdapterFactory({
323
- config: {
324
- adapterId: 'contrast-d1',
325
- adapterName: 'Contrast D1 Adapter',
326
- usePlural: config.usePlural ?? false,
327
- debugLogs: config.debugLogs ?? false,
328
- supportsArrays: false,
329
- // Sqlite doesn't have a native bool/date type — better-auth's transform
330
- // layer + our serialize() handle the round-trip.
331
- supportsBooleans: false,
332
- supportsDates: false,
333
- supportsJSON: false,
334
- transaction: false,
335
- } as AdapterFactoryOptions['config'],
336
- adapter: ({ schema, getFieldName, getModelName }) => {
337
- const table = (model: string) => quote(getModelName(model))
338
- const select = (model: string, columns?: string[]) =>
339
- columns?.length
340
- ? columns.map((c) => quote(getFieldName({ model, field: c }))).join(', ')
341
- : '*'
342
-
343
- // lazy auto-migration. better-auth's resolved schema arrives here in
344
- // `schema`; we hand it to buildSchemaSql and apply once per process.
345
- // every method calls ensureSchema() at the top so even a fresh sqlite
346
- // file gets bootstrapped on the first /api/auth/* request.
347
- let schemaApplied = config.autoMigrate === false
348
- const ensureSchema = async () => {
349
- if (schemaApplied) return
350
- await exec.exec(buildSchemaSql(schema))
351
- schemaApplied = true
352
- }
353
-
354
- const adapter: CustomAdapter = {
355
- create: async ({ model, data }) => {
356
- await ensureSchema()
357
- const cols = Object.keys(data).map((k) =>
358
- quote(getFieldName({ model, field: k })),
359
- )
360
- const values = Object.values(data).map(serialize)
361
- const placeholders = cols.map(() => '?').join(',')
362
- await exec.run(
363
- `INSERT INTO ${table(model)} (${cols.join(',')}) VALUES (${placeholders})`,
364
- values,
365
- )
366
- return data as never
367
- },
368
-
369
- findOne: async ({ model, where, select: cols }) => {
370
- await ensureSchema()
371
- const w = renderWhereClause(where)
372
- const row = await exec.first(
373
- `SELECT ${select(model, cols)} FROM ${table(model)}${w.sql} LIMIT 1`,
374
- w.params,
375
- )
376
- if (!row) return null
377
- return deserializeRow(row, fieldsOf(schema, getModelName(model))) as never
378
- },
379
-
380
- findMany: async ({ model, where, limit, offset, sortBy, select: cols }) => {
381
- await ensureSchema()
382
- const w = renderWhereClause(where ?? [])
383
- let sql = `SELECT ${select(model, cols)} FROM ${table(model)}${w.sql}`
384
- if (sortBy) {
385
- const field = quote(getFieldName({ model, field: sortBy.field }))
386
- sql += ` ORDER BY ${field} ${sortBy.direction.toUpperCase()}`
387
- }
388
- if (limit !== undefined) sql += ` LIMIT ${limit}`
389
- // sqlite rejects OFFSET without LIMIT; -1 means unlimited
390
- if (offset !== undefined)
391
- sql += `${limit === undefined ? ' LIMIT -1' : ''} OFFSET ${offset}`
392
- const rows = await exec.all(sql, w.params)
393
- const fields = fieldsOf(schema, getModelName(model))
394
- return rows.map((r) => deserializeRow(r, fields)) as never[]
395
- },
396
-
397
- count: async ({ model, where }) => {
398
- await ensureSchema()
399
- const w = renderWhereClause(where ?? [])
400
- const row = await exec.first<{ c: number }>(
401
- `SELECT COUNT(*) AS c FROM ${table(model)}${w.sql}`,
402
- w.params,
403
- )
404
- return row?.c ?? 0
405
- },
406
-
407
- update: async ({ model, where, update }) => {
408
- await ensureSchema()
409
- const cols = Object.keys(update as Record<string, unknown>)
410
- if (!cols.length) return null
411
- const sets = cols
412
- .map((c) => `${quote(getFieldName({ model, field: c }))} = ?`)
413
- .join(', ')
414
- const setParams = cols.map((c) =>
415
- serialize((update as Record<string, unknown>)[c]),
416
- )
417
- const w = renderWhereClause(where)
418
- await exec.run(`UPDATE ${table(model)} SET ${sets}${w.sql}`, [
419
- ...setParams,
420
- ...w.params,
421
- ])
422
- // re-read to return the updated row
423
- const row = await exec.first(
424
- `SELECT * FROM ${table(model)}${w.sql} LIMIT 1`,
425
- w.params,
426
- )
427
- if (!row) return null
428
- return deserializeRow(row, fieldsOf(schema, getModelName(model))) as never
429
- },
430
-
431
- updateMany: async ({ model, where, update }) => {
432
- await ensureSchema()
433
- const cols = Object.keys(update)
434
- if (!cols.length) return 0
435
- const sets = cols
436
- .map((c) => `${quote(getFieldName({ model, field: c }))} = ?`)
437
- .join(', ')
438
- const setParams = cols.map((c) => serialize(update[c]))
439
- const w = renderWhereClause(where)
440
- const res = await exec.run(`UPDATE ${table(model)} SET ${sets}${w.sql}`, [
441
- ...setParams,
442
- ...w.params,
443
- ])
444
- return res.changes
445
- },
446
-
447
- delete: async ({ model, where }) => {
448
- await ensureSchema()
449
- const w = renderWhereClause(where)
450
- await exec.run(`DELETE FROM ${table(model)}${w.sql}`, w.params)
451
- },
452
-
453
- deleteMany: async ({ model, where }) => {
454
- await ensureSchema()
455
- const w = renderWhereClause(where)
456
- const res = await exec.run(`DELETE FROM ${table(model)}${w.sql}`, w.params)
457
- return res.changes
458
- },
459
-
460
- createSchema: async ({ tables }) => ({
461
- path: 'better-auth.schema.sql',
462
- code: buildSchemaSql(tables),
463
- overwrite: true,
464
- }),
465
- }
466
-
467
- return adapter
468
- },
469
- })
470
- }
471
-
472
- // ── localStorage adapter ──────────────────────────────────────────────────
473
- // preview/browser-only dev path. better-auth still runs server-side in the
474
- // Contrast preview's bundled "server" module, but that module runs *in the
475
- // browser*, so node:sqlite / bun:sqlite aren't available. localStorage gives
476
- // a 1-LOC backing store that persists across reloads inside the iframe.
477
- //
478
- // special-cased: this is a direct CustomAdapter (no SQL emission), so the
479
- // data stored in localStorage is plain JSON rows — easier to inspect via
480
- // devtools and avoids dragging sql.js in.
481
-
482
- export interface LocalStorageLike {
483
- getItem(k: string): string | null
484
- setItem(k: string, v: string): void
485
- }
486
-
487
- export interface LocalStorageAdapterConfig {
488
- /** prefix for localStorage keys. defaults to `better-auth:`. */
489
- prefix?: string
490
- /** plural model names (matches better-auth's `usePlural` option). */
491
- usePlural?: boolean
492
- debugLogs?: boolean
493
- /** custom Storage impl — defaults to `globalThis.localStorage`. */
494
- storage?: LocalStorageLike
495
- }
496
-
497
- function localStorageMethod(value: unknown, key: keyof LocalStorageLike): unknown {
498
- if (typeof value !== 'object' || value === null) return undefined
499
- // boundary assertion for unknown host globals such as node's localStorage.
500
- return (value as Record<keyof LocalStorageLike, unknown>)[key]
501
- }
502
-
503
- export function isLocalStorageLike(value: unknown): value is LocalStorageLike {
504
- return (
505
- typeof localStorageMethod(value, 'getItem') === 'function' &&
506
- typeof localStorageMethod(value, 'setItem') === 'function'
507
- )
508
- }
509
-
510
- function readGlobalLocalStorage(): unknown {
511
- try {
512
- return globalThis.localStorage
513
- } catch {
514
- return null
515
- }
516
- }
517
-
518
- function localStorageStore(
519
- prefix: string,
520
- storage: LocalStorageAdapterConfig['storage'],
521
- ) {
522
- const get = () => {
523
- const candidate = storage ?? readGlobalLocalStorage()
524
- return isLocalStorageLike(candidate) ? candidate : null
525
- }
526
- return {
527
- load: (model: string): Record<string, unknown>[] => {
528
- const s = get()
529
- if (!s) return []
530
- const raw = s.getItem(prefix + model)
531
- return raw ? ((safeJsonParse(raw) as Record<string, unknown>[]) ?? []) : []
532
- },
533
- save: (model: string, rows: Record<string, unknown>[]) => {
534
- const s = get()
535
- if (!s) return
536
- s.setItem(prefix + model, JSON.stringify(rows))
537
- },
538
- }
539
- }
540
-
541
- function matchesWhere(
542
- row: Record<string, unknown>,
543
- where: readonly CleanedWhere[],
544
- ): boolean {
545
- if (!where.length) return true
546
- // better-auth always emits flat AND/OR clauses; the connector applies to the
547
- // step between clause i-1 and i. we evaluate left-to-right with short-circuit.
548
- let result = matchClause(row, where[0])
549
- for (let i = 1; i < where.length; i++) {
550
- const c = where[i]
551
- const v = matchClause(row, c)
552
- result = c.connector === 'OR' ? result || v : result && v
553
- }
554
- return result
555
- }
556
-
557
- function matchClause(row: Record<string, unknown>, c: CleanedWhere): boolean {
558
- const lhs = row[c.field]
559
- const rhs = c.value
560
- switch (c.operator) {
561
- case 'eq':
562
- return lhs === rhs || (lhs == null && rhs == null)
563
- case 'ne':
564
- return lhs !== rhs
565
- case 'lt':
566
- return (lhs as number) < (rhs as number)
567
- case 'lte':
568
- return (lhs as number) <= (rhs as number)
569
- case 'gt':
570
- return (lhs as number) > (rhs as number)
571
- case 'gte':
572
- return (lhs as number) >= (rhs as number)
573
- case 'in':
574
- return (rhs as readonly unknown[]).includes(lhs)
575
- case 'not_in':
576
- return !(rhs as readonly unknown[]).includes(lhs)
577
- case 'contains':
578
- return String(lhs ?? '').includes(String(rhs))
579
- case 'starts_with':
580
- return String(lhs ?? '').startsWith(String(rhs))
581
- case 'ends_with':
582
- return String(lhs ?? '').endsWith(String(rhs))
583
- default:
584
- throw new Error(`[better-auth-d1/localStorage] unsupported operator: ${c.operator}`)
585
- }
586
- }
587
-
588
- function projectRow(
589
- row: Record<string, unknown>,
590
- cols: string[] | undefined,
591
- ): Record<string, unknown> {
592
- if (!cols?.length) return row
593
- const out: Record<string, unknown> = {}
594
- for (const c of cols) out[c] = row[c]
595
- return out
596
- }
597
-
598
- export function localStorageAdapter(config: LocalStorageAdapterConfig = {}) {
599
- const prefix = config.prefix ?? 'better-auth:'
600
- const store = localStorageStore(prefix, config.storage)
601
-
602
- return createAdapterFactory({
603
- config: {
604
- adapterId: 'contrast-localstorage',
605
- adapterName: 'Contrast localStorage Adapter',
606
- usePlural: config.usePlural ?? false,
607
- debugLogs: config.debugLogs ?? false,
608
- supportsArrays: true,
609
- supportsBooleans: true,
610
- supportsDates: true,
611
- supportsJSON: true,
612
- transaction: false,
613
- } as AdapterFactoryOptions['config'],
614
- adapter: ({ getModelName }) => {
615
- const tableName = (model: string) => getModelName(model)
616
-
617
- const adapter: CustomAdapter = {
618
- create: async ({ model, data }) => {
619
- const t = tableName(model)
620
- const rows = store.load(t)
621
- rows.push(data as Record<string, unknown>)
622
- store.save(t, rows)
623
- return data as never
624
- },
625
-
626
- findOne: async ({ model, where, select: cols }) => {
627
- const rows = store.load(tableName(model))
628
- for (const r of rows) {
629
- if (matchesWhere(r, where)) return projectRow(r, cols) as never
630
- }
631
- return null
632
- },
633
-
634
- findMany: async ({ model, where, limit, offset, sortBy, select: cols }) => {
635
- const all = store.load(tableName(model))
636
- let rows = where ? all.filter((r) => matchesWhere(r, where)) : all
637
- if (sortBy) {
638
- const { field, direction } = sortBy
639
- const dir = direction === 'desc' ? -1 : 1
640
- rows = [...rows].sort((a, b) => {
641
- const av = a[field] as never
642
- const bv = b[field] as never
643
- if (av === bv) return 0
644
- return (av < bv ? -1 : 1) * dir
645
- })
646
- }
647
- if (offset) rows = rows.slice(offset)
648
- if (limit !== undefined) rows = rows.slice(0, limit)
649
- return rows.map((r) => projectRow(r, cols)) as never[]
650
- },
651
-
652
- count: async ({ model, where }) => {
653
- const all = store.load(tableName(model))
654
- return where ? all.filter((r) => matchesWhere(r, where)).length : all.length
655
- },
656
-
657
- update: async ({ model, where, update }) => {
658
- const t = tableName(model)
659
- const rows = store.load(t)
660
- let updated: Record<string, unknown> | null = null
661
- for (let i = 0; i < rows.length; i++) {
662
- if (matchesWhere(rows[i], where)) {
663
- rows[i] = { ...rows[i], ...(update as Record<string, unknown>) }
664
- updated = rows[i]
665
- break
666
- }
667
- }
668
- if (updated) store.save(t, rows)
669
- return updated as never
670
- },
671
-
672
- updateMany: async ({ model, where, update }) => {
673
- const t = tableName(model)
674
- const rows = store.load(t)
675
- let n = 0
676
- for (let i = 0; i < rows.length; i++) {
677
- if (matchesWhere(rows[i], where)) {
678
- rows[i] = { ...rows[i], ...(update as Record<string, unknown>) }
679
- n++
680
- }
681
- }
682
- if (n) store.save(t, rows)
683
- return n
684
- },
685
-
686
- delete: async ({ model, where }) => {
687
- const t = tableName(model)
688
- const rows = store.load(t)
689
- const next = rows.filter((r) => !matchesWhere(r, where))
690
- if (next.length !== rows.length) store.save(t, next)
691
- },
692
-
693
- deleteMany: async ({ model, where }) => {
694
- const t = tableName(model)
695
- const rows = store.load(t)
696
- const next = rows.filter((r) => !matchesWhere(r, where))
697
- const removed = rows.length - next.length
698
- if (removed) store.save(t, next)
699
- return removed
700
- },
701
- }
702
-
703
- return adapter
704
- },
705
- })
706
- }
3
+ // the bridge client and plugins live here. the canvas rendering engine lives
4
+ // in the private `sootsim-engine` workspace package.
5
+
6
+ export { sootsimPlugin } from './vite-plugin-one'
7
+ export { default as metroPlugin } from './metro-plugin'
8
+ export { default as jumpToSourceBabelPlugin } from './jump-to-source-babel'
9
+ export {
10
+ DEFAULT_SOOTSIM_BRIDGE_PORT,
11
+ DEFAULT_SOOTSIM_BRIDGE_URL,
12
+ } from './bridge-constants'
13
+ export { DEFAULT_SOOTSIM_SHELL_URL } from './cli-constants'