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.
- package/LICENSE +21 -0
- package/README.md +512 -0
- package/detox/colors.ts +54 -0
- package/detox/config-loader.ts +135 -0
- package/detox/element-types.ts +39 -0
- package/detox/expectations.ts +537 -0
- package/detox/gestures.ts +480 -0
- package/detox/index.ts +2118 -0
- package/detox/jest-preset.cjs +51 -0
- package/detox/jest-setup-after-env.cjs +18 -0
- package/detox/matchers.ts +30 -0
- package/detox/motion-change.cjs +109 -0
- package/detox/motion-change.d.cts +35 -0
- package/detox/navigation.ts +43 -0
- package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
- package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
- package/detox/screenshots/color-test-rest-norngh.png +0 -0
- package/detox/screenshots/theme-blue-toggle.png +0 -0
- package/detox/screenshots/theme-blue.png +0 -0
- package/detox/screenshots/theme-red-toggle.png +0 -0
- package/detox/screenshots/theme-red.png +0 -0
- package/dist-cli/bin.js +15 -0
- package/dist-cli/chunks/agent-XZ2KTPCU.js +48 -0
- package/dist-cli/chunks/agent-wrapper-JJYYW2WH.js +17 -0
- package/dist-cli/chunks/app-fonts-IXRNQG6B.js +4 -0
- package/dist-cli/chunks/assert-54T5SK5F.js +49 -0
- package/dist-cli/chunks/auth-FI5UDI45.js +12 -0
- package/dist-cli/chunks/beta-JV6UKADW.js +4 -0
- package/dist-cli/chunks/camera-VL73YIKP.js +22 -0
- package/dist-cli/chunks/chunk-2D2UPBBR.js +4 -0
- package/dist-cli/chunks/chunk-2YR5BGA5.js +5 -0
- package/dist-cli/chunks/chunk-3NV2NCNX.js +4 -0
- package/dist-cli/chunks/chunk-46EUUFJ5.js +4 -0
- package/dist-cli/chunks/chunk-4NPPOV2N.js +5 -0
- package/dist-cli/chunks/chunk-5DHC6KHQ.js +7 -0
- package/dist-cli/chunks/chunk-5TEF3ET3.js +74 -0
- package/dist-cli/chunks/chunk-5TPRP5QT.js +4 -0
- package/dist-cli/chunks/chunk-5YJCOWCH.js +4 -0
- package/dist-cli/chunks/chunk-7GN3LVWB.js +4 -0
- package/dist-cli/chunks/chunk-BBULZ7CG.js +568 -0
- package/dist-cli/chunks/chunk-BTWORNNG.js +71 -0
- package/dist-cli/chunks/chunk-D4FFVGI5.js +5 -0
- package/dist-cli/chunks/chunk-DCEMHR2Y.js +4 -0
- package/dist-cli/chunks/chunk-DZS6WPUI.js +4 -0
- package/dist-cli/chunks/chunk-F5ZRSS3C.js +3 -0
- package/dist-cli/chunks/chunk-FSUYIVJ6.js +9 -0
- package/dist-cli/chunks/chunk-G2WW6L2C.js +23 -0
- package/dist-cli/chunks/chunk-GASE6UBA.js +4 -0
- package/dist-cli/chunks/chunk-GGRX24GF.js +142 -0
- package/dist-cli/chunks/chunk-HI5TFJWN.js +5 -0
- package/dist-cli/chunks/chunk-IJ5CAZZC.js +5 -0
- package/dist-cli/chunks/chunk-IJO63TDP.js +4 -0
- package/dist-cli/chunks/chunk-KTHV3RUS.js +26 -0
- package/dist-cli/chunks/chunk-LF2ZVT7O.js +6 -0
- package/dist-cli/chunks/chunk-MJRLLB4R.js +38 -0
- package/dist-cli/chunks/chunk-NFK7T35W.js +4 -0
- package/dist-cli/chunks/chunk-NMF2ZMZQ.js +15 -0
- package/dist-cli/chunks/chunk-OHAZNXLK.js +4 -0
- package/dist-cli/chunks/chunk-OVFJFXUD.js +101 -0
- package/dist-cli/chunks/chunk-OZSSI4WN.js +28 -0
- package/dist-cli/chunks/chunk-QKDWYITG.js +25 -0
- package/dist-cli/chunks/chunk-RSZWCKNT.js +30 -0
- package/dist-cli/chunks/chunk-RTN5C5RL.js +4 -0
- package/dist-cli/chunks/chunk-TIVZIMMW.js +4 -0
- package/dist-cli/chunks/chunk-TZFFR3SD.js +7 -0
- package/dist-cli/chunks/chunk-UC6U3MML.js +5 -0
- package/dist-cli/chunks/chunk-VFCMSYZK.js +6 -0
- package/dist-cli/chunks/chunk-VNQEB4L7.js +5 -0
- package/dist-cli/chunks/chunk-VZXWHRUZ.js +983 -0
- package/dist-cli/chunks/chunk-W6K4EFPH.js +107 -0
- package/dist-cli/chunks/chunk-WEXDAC74.js +4 -0
- package/dist-cli/chunks/chunk-WF3T4SVI.js +14 -0
- package/dist-cli/chunks/chunk-WINYQ44O.js +4 -0
- package/dist-cli/chunks/chunk-WMIIKMGK.js +10 -0
- package/dist-cli/chunks/chunk-WUSWBCWA.js +507 -0
- package/dist-cli/chunks/chunk-WWZIXIRD.js +4 -0
- package/dist-cli/chunks/chunk-XEVZYVIW.js +766 -0
- package/dist-cli/chunks/chunk-YDGQTMQL.js +4 -0
- package/dist-cli/chunks/chunk-YIFT42WN.js +4 -0
- package/dist-cli/chunks/chunk-ZMJD5GEC.js +125 -0
- package/dist-cli/chunks/cleanup-P27PA6JI.js +25 -0
- package/dist-cli/chunks/cli-version-WWLPBDQ7.js +4 -0
- package/dist-cli/chunks/compat-ZD65FED3.js +20 -0
- package/dist-cli/chunks/config-XMJRNM2A.js +13 -0
- package/dist-cli/chunks/control-KMIQT3QP.js +4 -0
- package/dist-cli/chunks/daemon-G2ME7NLB.js +4 -0
- package/dist-cli/chunks/debug-PT4HOP7N.js +200 -0
- package/dist-cli/chunks/desktop-S3FG72AK.js +31 -0
- package/dist-cli/chunks/detox-B3D4IFCN.js +52 -0
- package/dist-cli/chunks/device-XBNDSB2R.js +18 -0
- package/dist-cli/chunks/diagnose-HMQXJE5N.js +43 -0
- package/dist-cli/chunks/disk-cleanup-BLCZ5BSZ.js +4 -0
- package/dist-cli/chunks/drivers-LDECZGP2.js +4 -0
- package/dist-cli/chunks/film-BJGTBYZB.js +74 -0
- package/dist-cli/chunks/flow-UEQNVTU7.js +4 -0
- package/dist-cli/chunks/help-T5FYSVGB.js +4 -0
- package/dist-cli/chunks/hidden-runtime-alias-ANOYADHM.js +4 -0
- package/dist-cli/chunks/home-paths-GT3LFNOR.js +4 -0
- package/dist-cli/chunks/inspect-ZA6XF5LD.js +4 -0
- package/dist-cli/chunks/install-desktop-TIMUDHPL.js +4 -0
- package/dist-cli/chunks/login-FJ737MWG.js +28 -0
- package/dist-cli/chunks/logout-ZCNMMHMY.js +4 -0
- package/dist-cli/chunks/maestro-SZTNKLDF.js +104 -0
- package/dist-cli/chunks/maestro-generate-DCFAIZ4H.js +36 -0
- package/dist-cli/chunks/mode-GRMQCRXR.js +26 -0
- package/dist-cli/chunks/optional-demo-registry-W36EWFFB.js +4 -0
- package/dist-cli/chunks/perf-QYBAAUZG.js +48 -0
- package/dist-cli/chunks/permissions-3QCQ6VF4.js +13 -0
- package/dist-cli/chunks/record-QPPC2S4E.js +72 -0
- package/dist-cli/chunks/report-issue-7NMFP4HK.js +29 -0
- package/dist-cli/chunks/runtime-XOAXMSTU.js +4 -0
- package/dist-cli/chunks/screenshot-command-67AECJFB.js +159 -0
- package/dist-cli/chunks/screenshot-layers-ASWBYPJL.js +20 -0
- package/dist-cli/chunks/screenshots-capture-PXA3HFQK.js +42 -0
- package/dist-cli/chunks/serve-BI2NBAXG.js +44 -0
- package/dist-cli/chunks/setup-7DWPMRSB.js +32 -0
- package/dist-cli/chunks/skills-S3Y22TUA.js +50 -0
- package/dist-cli/chunks/storage-XUIMJWAJ.js +37 -0
- package/dist-cli/chunks/store-JTHEJLAZ.js +4 -0
- package/dist-cli/chunks/telemetry-ZYJGD2DB.js +4 -0
- package/dist-cli/chunks/timeline-TMPLQPSP.js +24 -0
- package/dist-cli/chunks/upgrade-7HDSIM7K.js +7 -0
- package/dist-cli/chunks/upload-GMSZPWM6.js +4 -0
- package/dist-cli/chunks/version-HOCHZ37L.js +6 -0
- package/dist-cli/chunks/web-DG3WBYD3.js +4 -0
- package/dist-cli/chunks/what-happened-XFVUTZR7.js +25 -0
- package/dist-lib/agent-daemon-client.cjs +468 -0
- package/dist-lib/agent-events.cjs +48 -0
- package/dist-lib/agent-identity.cjs +53 -0
- package/dist-lib/agent-sessions.cjs +725 -0
- package/dist-lib/attached-projects.cjs +526 -0
- package/dist-lib/auth/shared-session.cjs +195 -0
- package/dist-lib/backend-origin.cjs +70 -0
- package/dist-lib/beta.cjs +41 -0
- package/dist-lib/beta.mjs +13 -0
- package/dist-lib/bridge-constants.cjs +161 -0
- package/dist-lib/cli-constants.cjs +32 -0
- package/dist-lib/config.cjs +96 -0
- package/dist-lib/detox/index.cjs +2863 -0
- package/dist-lib/detox/jest-preset.cjs +51 -0
- package/dist-lib/dev-bundle-resolution.cjs +223 -0
- package/dist-lib/home-paths.cjs +421 -0
- package/dist-lib/host/bridge-host.cjs +5600 -0
- package/dist-lib/host/fetch-proxy-handler.cjs +373 -0
- package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
- package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
- package/dist-lib/host/websocket-proxy.cjs +214 -0
- package/dist-lib/index.cjs +513 -0
- package/dist-lib/jump-to-source-babel.cjs +93 -0
- package/dist-lib/menu.cjs +620 -0
- package/dist-lib/menu.mjs +581 -0
- package/dist-lib/metro.cjs +297 -0
- package/dist-lib/profiles.cjs +246 -0
- package/dist-lib/public-brand.cjs +39 -0
- package/dist-lib/render-mode.cjs +54 -0
- package/dist-lib/scripts/dev-server-scanner.cjs +1034 -0
- package/dist-lib/sdk.cjs +2333 -0
- package/dist-lib/sdk.mjs +2240 -0
- package/dist-lib/skills.cjs +23922 -0
- package/dist-lib/vite.cjs +358 -0
- package/package.json +189 -18
- package/scripts/dev-server-scanner.ts +941 -0
- package/scripts/optional-demo-registry.ts +113 -0
- package/scripts/postinstall.cjs +3 -0
- package/scripts/preuninstall.cjs +118 -0
- package/skills/contrast/SKILL.md +171 -0
- package/skills/rnx-debug/SKILL.md +425 -0
- package/skills/rnx-perf/SKILL.md +295 -0
- package/skills/rnx-setup/SKILL.md +160 -0
- package/skills/rnx-test/SKILL.md +292 -0
- package/skills/rnx-visual/SKILL.md +122 -0
- package/src/agent-daemon-client.ts +410 -0
- package/src/agent-events.ts +71 -0
- package/src/agent-identity.ts +23 -0
- package/src/agent-prompt.ts +71 -0
- package/src/agent-sessions.ts +573 -0
- package/src/attached-projects.ts +539 -0
- package/src/auth/shared-session.ts +204 -0
- package/src/backend-origin.ts +49 -0
- package/src/beta.ts +19 -0
- package/src/bridge-constants.ts +171 -0
- package/src/browser-cache.ts +16 -0
- package/src/cli-constants.ts +1 -0
- package/src/cli-version.ts +32 -0
- package/src/codex-client.ts +215 -0
- package/src/config.ts +134 -0
- package/src/dev-bundle-resolution.ts +236 -0
- package/src/disk-cleanup.ts +466 -0
- package/src/home-paths.ts +552 -0
- package/src/host/agent-host.ts +666 -0
- package/src/host/bridge-host.ts +2626 -0
- package/src/host/fetch-proxy-handler.ts +397 -0
- package/src/host/fetch-proxy-overrides.ts +51 -0
- package/src/host/open-url.ts +89 -0
- package/src/host/websocket-proxy.ts +210 -0
- package/src/index.ts +12 -705
- package/src/jump-to-source-babel.ts +135 -0
- package/src/menu.ts +734 -0
- package/src/metro-plugin.ts +139 -0
- package/src/native-dev-bundle-url.ts +96 -0
- package/src/native-seam-manifest.ts +341 -0
- package/src/plist.ts +27 -0
- package/src/poll-until.ts +92 -0
- package/src/profiles.ts +179 -0
- package/src/public-brand.ts +9 -0
- package/src/render-mode.ts +27 -0
- package/src/runtime-assets.ts +228 -0
- package/src/runtime-delivery.ts +54 -0
- package/src/screenshots/compose.ts +422 -0
- package/src/screenshots/frame-compose.ts +454 -0
- package/src/screenshots/orchestrate.ts +256 -0
- package/src/screenshots/org-deck.ts +91 -0
- package/src/screenshots/registry.ts +58 -0
- package/src/screenshots/schema.ts +364 -0
- package/src/sdk.ts +7 -0
- package/src/settings-bundle.ts +418 -0
- package/src/skills/builtin/a11y-review.ts +132 -0
- package/src/skills/builtin/compat-check.ts +71 -0
- package/src/skills/builtin/maestro-test.ts +121 -0
- package/src/skills/builtin/perf-profile.ts +90 -0
- package/src/skills/builtin/screenshot-all.ts +52 -0
- package/src/skills/builtin/visual-diff.ts +100 -0
- package/src/skills/registry.ts +107 -0
- package/src/skills/types.ts +41 -0
- package/src/team-machine-prompt.ts +73 -0
- package/src/vite-plugin-one.ts +190 -0
- package/src/vite-plugin.ts +1750 -0
- package/src/worklets-babel.ts +134 -0
package/src/index.ts
CHANGED
|
@@ -1,706 +1,13 @@
|
|
|
1
|
-
//
|
|
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
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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'
|