over-zero 0.0.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 (259) hide show
  1. package/dist/cjs/build/readPermissions.cjs +51 -0
  2. package/dist/cjs/build/readPermissions.js +48 -0
  3. package/dist/cjs/build/readPermissions.js.map +6 -0
  4. package/dist/cjs/build/readPermissions.native.js +56 -0
  5. package/dist/cjs/build/readPermissions.native.js.map +6 -0
  6. package/dist/cjs/build/schema.cjs +28 -0
  7. package/dist/cjs/build/schema.js +22 -0
  8. package/dist/cjs/build/schema.js.map +6 -0
  9. package/dist/cjs/build/schema.native.js +28 -0
  10. package/dist/cjs/build/schema.native.js.map +6 -0
  11. package/dist/cjs/createClient.cjs +89 -0
  12. package/dist/cjs/createClient.js +76 -0
  13. package/dist/cjs/createClient.js.map +6 -0
  14. package/dist/cjs/createClient.native.js +81 -0
  15. package/dist/cjs/createClient.native.js.map +6 -0
  16. package/dist/cjs/createMutations.cjs +50 -0
  17. package/dist/cjs/createMutations.js +43 -0
  18. package/dist/cjs/createMutations.js.map +6 -0
  19. package/dist/cjs/createMutations.native.js +50 -0
  20. package/dist/cjs/createMutations.native.js.map +6 -0
  21. package/dist/cjs/createPermissions.cjs +128 -0
  22. package/dist/cjs/createPermissions.js +121 -0
  23. package/dist/cjs/createPermissions.js.map +6 -0
  24. package/dist/cjs/createPermissions.native.js +135 -0
  25. package/dist/cjs/createPermissions.native.js.map +6 -0
  26. package/dist/cjs/createServer.cjs +92 -0
  27. package/dist/cjs/createServer.js +71 -0
  28. package/dist/cjs/createServer.js.map +6 -0
  29. package/dist/cjs/createServer.native.js +75 -0
  30. package/dist/cjs/createServer.native.js.map +6 -0
  31. package/dist/cjs/helpers/batchQuery.cjs +49 -0
  32. package/dist/cjs/helpers/batchQuery.js +38 -0
  33. package/dist/cjs/helpers/batchQuery.js.map +6 -0
  34. package/dist/cjs/helpers/batchQuery.native.js +42 -0
  35. package/dist/cjs/helpers/batchQuery.native.js.map +6 -0
  36. package/dist/cjs/helpers/clearZeroDatabase.cjs +57 -0
  37. package/dist/cjs/helpers/clearZeroDatabase.js +57 -0
  38. package/dist/cjs/helpers/clearZeroDatabase.js.map +6 -0
  39. package/dist/cjs/helpers/clearZeroDatabase.native.js +71 -0
  40. package/dist/cjs/helpers/clearZeroDatabase.native.js.map +6 -0
  41. package/dist/cjs/helpers/context.cjs +40 -0
  42. package/dist/cjs/helpers/context.js +36 -0
  43. package/dist/cjs/helpers/context.js.map +6 -0
  44. package/dist/cjs/helpers/context.native.js +42 -0
  45. package/dist/cjs/helpers/context.native.js.map +6 -0
  46. package/dist/cjs/helpers/createMutators.cjs +87 -0
  47. package/dist/cjs/helpers/createMutators.js +81 -0
  48. package/dist/cjs/helpers/createMutators.js.map +6 -0
  49. package/dist/cjs/helpers/createMutators.native.js +116 -0
  50. package/dist/cjs/helpers/createMutators.native.js.map +6 -0
  51. package/dist/cjs/helpers/ensureLoggedIn.cjs +33 -0
  52. package/dist/cjs/helpers/ensureLoggedIn.js +25 -0
  53. package/dist/cjs/helpers/ensureLoggedIn.js.map +6 -0
  54. package/dist/cjs/helpers/ensureLoggedIn.native.js +29 -0
  55. package/dist/cjs/helpers/ensureLoggedIn.native.js.map +6 -0
  56. package/dist/cjs/helpers/getAuthData.cjs +36 -0
  57. package/dist/cjs/helpers/getAuthData.js +29 -0
  58. package/dist/cjs/helpers/getAuthData.js.map +6 -0
  59. package/dist/cjs/helpers/getAuthData.native.js +33 -0
  60. package/dist/cjs/helpers/getAuthData.native.js.map +6 -0
  61. package/dist/cjs/helpers/prettyFormatZeroQuery.cjs +107 -0
  62. package/dist/cjs/helpers/prettyFormatZeroQuery.js +92 -0
  63. package/dist/cjs/helpers/prettyFormatZeroQuery.js.map +6 -0
  64. package/dist/cjs/helpers/prettyFormatZeroQuery.native.js +99 -0
  65. package/dist/cjs/helpers/prettyFormatZeroQuery.native.js.map +6 -0
  66. package/dist/cjs/helpers/setupZeroClientGlobalEffects.cjs +40 -0
  67. package/dist/cjs/helpers/setupZeroClientGlobalEffects.js +36 -0
  68. package/dist/cjs/helpers/setupZeroClientGlobalEffects.js.map +6 -0
  69. package/dist/cjs/helpers/setupZeroClientGlobalEffects.native.js +36 -0
  70. package/dist/cjs/helpers/setupZeroClientGlobalEffects.native.js.map +6 -0
  71. package/dist/cjs/helpers/useAuthData.cjs +32 -0
  72. package/dist/cjs/helpers/useAuthData.js +25 -0
  73. package/dist/cjs/helpers/useAuthData.js.map +6 -0
  74. package/dist/cjs/helpers/useAuthData.native.js +33 -0
  75. package/dist/cjs/helpers/useAuthData.native.js.map +6 -0
  76. package/dist/cjs/helpers/useZDB.cjs +70 -0
  77. package/dist/cjs/helpers/useZDB.js +51 -0
  78. package/dist/cjs/helpers/useZDB.js.map +6 -0
  79. package/dist/cjs/helpers/useZDB.native.js +68 -0
  80. package/dist/cjs/helpers/useZDB.native.js.map +6 -0
  81. package/dist/cjs/helpers/zeroEmitter.cjs +27 -0
  82. package/dist/cjs/helpers/zeroEmitter.js +22 -0
  83. package/dist/cjs/helpers/zeroEmitter.js.map +6 -0
  84. package/dist/cjs/helpers/zeroEmitter.native.js +26 -0
  85. package/dist/cjs/helpers/zeroEmitter.native.js.map +6 -0
  86. package/dist/cjs/index.cjs +23 -0
  87. package/dist/cjs/index.js +20 -0
  88. package/dist/cjs/index.js.map +6 -0
  89. package/dist/cjs/index.native.js +30 -0
  90. package/dist/cjs/index.native.js.map +6 -0
  91. package/dist/cjs/types.cjs +16 -0
  92. package/dist/cjs/types.js +14 -0
  93. package/dist/cjs/types.js.map +6 -0
  94. package/dist/cjs/types.native.js +15 -0
  95. package/dist/cjs/types.native.js.map +6 -0
  96. package/dist/esm/build/readPermissions.js +36 -0
  97. package/dist/esm/build/readPermissions.js.map +6 -0
  98. package/dist/esm/build/readPermissions.mjs +28 -0
  99. package/dist/esm/build/readPermissions.mjs.map +1 -0
  100. package/dist/esm/build/readPermissions.native.js +34 -0
  101. package/dist/esm/build/readPermissions.native.js.map +1 -0
  102. package/dist/esm/build/schema.js +7 -0
  103. package/dist/esm/build/schema.js.map +6 -0
  104. package/dist/esm/build/schema.mjs +4 -0
  105. package/dist/esm/build/schema.mjs.map +1 -0
  106. package/dist/esm/build/schema.native.js +4 -0
  107. package/dist/esm/build/schema.native.js.map +1 -0
  108. package/dist/esm/createClient.js +68 -0
  109. package/dist/esm/createClient.js.map +6 -0
  110. package/dist/esm/createClient.mjs +66 -0
  111. package/dist/esm/createClient.mjs.map +1 -0
  112. package/dist/esm/createClient.native.js +74 -0
  113. package/dist/esm/createClient.native.js.map +1 -0
  114. package/dist/esm/createMutations.js +27 -0
  115. package/dist/esm/createMutations.js.map +6 -0
  116. package/dist/esm/createMutations.mjs +27 -0
  117. package/dist/esm/createMutations.mjs.map +1 -0
  118. package/dist/esm/createMutations.native.js +29 -0
  119. package/dist/esm/createMutations.native.js.map +1 -0
  120. package/dist/esm/createPermissions.js +106 -0
  121. package/dist/esm/createPermissions.js.map +6 -0
  122. package/dist/esm/createPermissions.mjs +105 -0
  123. package/dist/esm/createPermissions.mjs.map +1 -0
  124. package/dist/esm/createPermissions.native.js +129 -0
  125. package/dist/esm/createPermissions.native.js.map +1 -0
  126. package/dist/esm/createServer.js +54 -0
  127. package/dist/esm/createServer.js.map +6 -0
  128. package/dist/esm/createServer.mjs +58 -0
  129. package/dist/esm/createServer.mjs.map +1 -0
  130. package/dist/esm/createServer.native.js +61 -0
  131. package/dist/esm/createServer.native.js.map +1 -0
  132. package/dist/esm/helpers/batchQuery.js +22 -0
  133. package/dist/esm/helpers/batchQuery.js.map +6 -0
  134. package/dist/esm/helpers/batchQuery.mjs +26 -0
  135. package/dist/esm/helpers/batchQuery.mjs.map +1 -0
  136. package/dist/esm/helpers/batchQuery.native.js +23 -0
  137. package/dist/esm/helpers/batchQuery.native.js.map +1 -0
  138. package/dist/esm/helpers/clearZeroDatabase.js +42 -0
  139. package/dist/esm/helpers/clearZeroDatabase.js.map +6 -0
  140. package/dist/esm/helpers/clearZeroDatabase.mjs +34 -0
  141. package/dist/esm/helpers/clearZeroDatabase.mjs.map +1 -0
  142. package/dist/esm/helpers/clearZeroDatabase.native.js +50 -0
  143. package/dist/esm/helpers/clearZeroDatabase.native.js.map +1 -0
  144. package/dist/esm/helpers/context.js +20 -0
  145. package/dist/esm/helpers/context.js.map +6 -0
  146. package/dist/esm/helpers/context.mjs +15 -0
  147. package/dist/esm/helpers/context.mjs.map +1 -0
  148. package/dist/esm/helpers/context.native.js +15 -0
  149. package/dist/esm/helpers/context.native.js.map +1 -0
  150. package/dist/esm/helpers/createMutators.js +69 -0
  151. package/dist/esm/helpers/createMutators.js.map +6 -0
  152. package/dist/esm/helpers/createMutators.mjs +64 -0
  153. package/dist/esm/helpers/createMutators.mjs.map +1 -0
  154. package/dist/esm/helpers/createMutators.native.js +101 -0
  155. package/dist/esm/helpers/createMutators.native.js.map +1 -0
  156. package/dist/esm/helpers/ensureLoggedIn.js +10 -0
  157. package/dist/esm/helpers/ensureLoggedIn.js.map +6 -0
  158. package/dist/esm/helpers/ensureLoggedIn.mjs +10 -0
  159. package/dist/esm/helpers/ensureLoggedIn.mjs.map +1 -0
  160. package/dist/esm/helpers/ensureLoggedIn.native.js +10 -0
  161. package/dist/esm/helpers/ensureLoggedIn.native.js.map +1 -0
  162. package/dist/esm/helpers/getAuthData.js +13 -0
  163. package/dist/esm/helpers/getAuthData.js.map +6 -0
  164. package/dist/esm/helpers/getAuthData.mjs +13 -0
  165. package/dist/esm/helpers/getAuthData.mjs.map +1 -0
  166. package/dist/esm/helpers/getAuthData.native.js +13 -0
  167. package/dist/esm/helpers/getAuthData.native.js.map +1 -0
  168. package/dist/esm/helpers/prettyFormatZeroQuery.js +76 -0
  169. package/dist/esm/helpers/prettyFormatZeroQuery.js.map +6 -0
  170. package/dist/esm/helpers/prettyFormatZeroQuery.mjs +84 -0
  171. package/dist/esm/helpers/prettyFormatZeroQuery.mjs.map +1 -0
  172. package/dist/esm/helpers/prettyFormatZeroQuery.native.js +93 -0
  173. package/dist/esm/helpers/prettyFormatZeroQuery.native.js.map +1 -0
  174. package/dist/esm/helpers/setupZeroClientGlobalEffects.js +40 -0
  175. package/dist/esm/helpers/setupZeroClientGlobalEffects.js.map +6 -0
  176. package/dist/esm/helpers/setupZeroClientGlobalEffects.mjs +41 -0
  177. package/dist/esm/helpers/setupZeroClientGlobalEffects.mjs.map +1 -0
  178. package/dist/esm/helpers/setupZeroClientGlobalEffects.native.js +41 -0
  179. package/dist/esm/helpers/setupZeroClientGlobalEffects.native.js.map +1 -0
  180. package/dist/esm/helpers/useAuthData.js +11 -0
  181. package/dist/esm/helpers/useAuthData.js.map +6 -0
  182. package/dist/esm/helpers/useAuthData.mjs +9 -0
  183. package/dist/esm/helpers/useAuthData.mjs.map +1 -0
  184. package/dist/esm/helpers/useAuthData.native.js +13 -0
  185. package/dist/esm/helpers/useAuthData.native.js.map +1 -0
  186. package/dist/esm/helpers/useZDB.js +38 -0
  187. package/dist/esm/helpers/useZDB.js.map +6 -0
  188. package/dist/esm/helpers/useZDB.mjs +47 -0
  189. package/dist/esm/helpers/useZDB.mjs.map +1 -0
  190. package/dist/esm/helpers/useZDB.native.js +55 -0
  191. package/dist/esm/helpers/useZDB.native.js.map +1 -0
  192. package/dist/esm/helpers/zeroEmitter.js +6 -0
  193. package/dist/esm/helpers/zeroEmitter.js.map +6 -0
  194. package/dist/esm/helpers/zeroEmitter.mjs +4 -0
  195. package/dist/esm/helpers/zeroEmitter.mjs.map +1 -0
  196. package/dist/esm/helpers/zeroEmitter.native.js +4 -0
  197. package/dist/esm/helpers/zeroEmitter.native.js.map +1 -0
  198. package/dist/esm/index.js +7 -0
  199. package/dist/esm/index.js.map +6 -0
  200. package/dist/esm/index.mjs +7 -0
  201. package/dist/esm/index.mjs.map +1 -0
  202. package/dist/esm/index.native.js +7 -0
  203. package/dist/esm/index.native.js.map +1 -0
  204. package/dist/esm/types.js +1 -0
  205. package/dist/esm/types.js.map +6 -0
  206. package/dist/esm/types.mjs +2 -0
  207. package/dist/esm/types.mjs.map +1 -0
  208. package/dist/esm/types.native.js +2 -0
  209. package/dist/esm/types.native.js.map +1 -0
  210. package/package.json +51 -0
  211. package/readme.md +16 -0
  212. package/src/createPermissions.ts +281 -0
  213. package/src/createZeroClient.tsx +191 -0
  214. package/src/createZeroServer.ts +153 -0
  215. package/src/helpers/batchQuery.ts +45 -0
  216. package/src/helpers/clearZeroDatabase.ts +68 -0
  217. package/src/helpers/context.ts +28 -0
  218. package/src/helpers/createMutators.ts +139 -0
  219. package/src/helpers/ensureLoggedIn.ts +8 -0
  220. package/src/helpers/getAuthData.tsx +12 -0
  221. package/src/helpers/prettyFormatZeroQuery.ts +167 -0
  222. package/src/helpers/useAuthData.ts +13 -0
  223. package/src/helpers/useZeroDebug.ts +104 -0
  224. package/src/helpers/zeroEmitter.ts +5 -0
  225. package/src/index.ts +15 -0
  226. package/src/mutations.ts +121 -0
  227. package/src/types.ts +49 -0
  228. package/types/createMutations.d.ts +20 -0
  229. package/types/createMutations.d.ts.map +1 -0
  230. package/types/createPermissions.d.ts +37 -0
  231. package/types/createPermissions.d.ts.map +1 -0
  232. package/types/createZeroClient.d.ts +45 -0
  233. package/types/createZeroClient.d.ts.map +1 -0
  234. package/types/createZeroServer.d.ts +61 -0
  235. package/types/createZeroServer.d.ts.map +1 -0
  236. package/types/helpers/batchQuery.d.ts +7 -0
  237. package/types/helpers/batchQuery.d.ts.map +1 -0
  238. package/types/helpers/clearZeroDatabase.d.ts +2 -0
  239. package/types/helpers/clearZeroDatabase.d.ts.map +1 -0
  240. package/types/helpers/context.d.ts +5 -0
  241. package/types/helpers/context.d.ts.map +1 -0
  242. package/types/helpers/createMutators.d.ts +16 -0
  243. package/types/helpers/createMutators.d.ts.map +1 -0
  244. package/types/helpers/ensureLoggedIn.d.ts +2 -0
  245. package/types/helpers/ensureLoggedIn.d.ts.map +1 -0
  246. package/types/helpers/getAuthData.d.ts +1 -0
  247. package/types/helpers/getAuthData.d.ts.map +1 -0
  248. package/types/helpers/prettyFormatZeroQuery.d.ts +3 -0
  249. package/types/helpers/prettyFormatZeroQuery.d.ts.map +1 -0
  250. package/types/helpers/useAuthData.d.ts +1 -0
  251. package/types/helpers/useAuthData.d.ts.map +1 -0
  252. package/types/helpers/useZeroDebug.d.ts +3 -0
  253. package/types/helpers/useZeroDebug.d.ts.map +1 -0
  254. package/types/helpers/zeroEmitter.d.ts +2 -0
  255. package/types/helpers/zeroEmitter.d.ts.map +1 -0
  256. package/types/index.d.ts +9 -0
  257. package/types/index.d.ts.map +1 -0
  258. package/types/types.d.ts +21 -0
  259. package/types/types.d.ts.map +1 -0
@@ -0,0 +1,68 @@
1
+ import { showToast } from '~/interface/toast/Toast'
2
+ import { zero } from '../zero'
3
+
4
+ export const clearZeroDatabase = async () => {
5
+ try {
6
+ // Get all IndexedDB databases
7
+ const databases = await indexedDB.databases()
8
+
9
+ // Find Zero/Replicache databases
10
+ const zeroAndReplicacheDatabases = databases.filter((db) => {
11
+ if (!db.name) return false
12
+ const name = db.name.toLowerCase()
13
+ return (
14
+ name.includes('zero') ||
15
+ name.includes('replicache') ||
16
+ name.includes('roc') || // rocicorp prefix
17
+ name.startsWith('rep:') // replicache prefix
18
+ )
19
+ })
20
+
21
+ if (zeroAndReplicacheDatabases.length > 0) {
22
+ // Delete all Zero/Replicache databases
23
+ await Promise.all(
24
+ zeroAndReplicacheDatabases.map((db) => {
25
+ return new Promise<void>((resolve, reject) => {
26
+ const deleteReq = indexedDB.deleteDatabase(db.name!)
27
+ deleteReq.onsuccess = () => resolve()
28
+ deleteReq.onerror = () => reject(deleteReq.error)
29
+ deleteReq.onblocked = () => reject(new Error('database deletion blocked'))
30
+ })
31
+ })
32
+ )
33
+
34
+ const dbNames = zeroAndReplicacheDatabases.map((db) => db.name).join(', ')
35
+ showToast(
36
+ `Cleared ${zeroAndReplicacheDatabases.length} Zero/Replicache databases: ${dbNames}`
37
+ )
38
+ } else {
39
+ // Fallback: clear all IndexedDB databases
40
+ await Promise.all(
41
+ databases.map((db) => {
42
+ if (db.name) {
43
+ return new Promise<void>((resolve, reject) => {
44
+ const deleteReq = indexedDB.deleteDatabase(db.name!)
45
+ deleteReq.onsuccess = () => resolve()
46
+ deleteReq.onerror = () => reject(deleteReq.error)
47
+ deleteReq.onblocked = () => reject(new Error('database deletion blocked'))
48
+ })
49
+ }
50
+ })
51
+ )
52
+ showToast('Cleared all IndexedDB databases')
53
+ }
54
+
55
+ // Close the zero connection before reloading
56
+ if (zero && typeof (zero as any).close === 'function') {
57
+ await (zero as any).close()
58
+ }
59
+
60
+ // Reload the page to reinitialize Zero
61
+ setTimeout(() => {
62
+ window.location.reload()
63
+ }, 1000)
64
+ } catch (error) {
65
+ console.error('Error clearing Zero database:', error)
66
+ showToast('Error clearing Zero database')
67
+ }
68
+ }
@@ -0,0 +1,28 @@
1
+ import { createAsyncContext } from '@vxrn/helpers'
2
+ import type { MutatorContext } from '../types'
3
+
4
+ // TODO likely should be called "DataContext" or "ZeroContext" as its useful for permissions outside mutators
5
+
6
+ const asyncContext = createAsyncContext<MutatorContext>()
7
+
8
+ // TODO probably rename mutatorContext()
9
+
10
+ export function context(): MutatorContext {
11
+ const currentContext = asyncContext.get()
12
+ if (!currentContext) {
13
+ throw new Error('context must be called within a mutator')
14
+ }
15
+
16
+ return currentContext
17
+ }
18
+
19
+ export function isInZeroMutation() {
20
+ return !!asyncContext.get()
21
+ }
22
+
23
+ export function runWithContext<T>(
24
+ context: MutatorContext,
25
+ fn: () => T | Promise<T>
26
+ ): Promise<T> {
27
+ return asyncContext.run(context, fn)
28
+ }
@@ -0,0 +1,139 @@
1
+ import { isClient, isServer, mapObject, time } from '@vxrn/helpers'
2
+ import type { AuthData } from 'start/types'
3
+ import type { GenericTransaction, MutatorContext } from '../types'
4
+ import { runWithContext } from './context'
5
+
6
+ type MutatorProps = {
7
+ environment: 'server' | 'client'
8
+ authData: AuthData | null
9
+ can: (where: string, action: () => Promise<any>, obj: any) => void
10
+ models: any
11
+ asyncTasks?: Array<() => Promise<void>>
12
+ createServerActions?: (authData: AuthData | null) => Record<string, any>
13
+ }
14
+
15
+ type TransformMutators<T> = {
16
+ [K in keyof T]: T[K] extends (ctx: MutatorContext, ...args: infer Args) => infer Return
17
+ ? (tx: GenericTransaction, ...args: Args) => Return
18
+ : never
19
+ }
20
+
21
+ export function createMutators<
22
+ MutatorsIn extends Record<string, any>,
23
+ Mutators extends {
24
+ [K in keyof MutatorsIn]: TransformMutators<MutatorsIn[K]>
25
+ },
26
+ >({
27
+ environment,
28
+ authData,
29
+ createServerActions,
30
+ asyncTasks = [],
31
+ can,
32
+ models,
33
+ }: MutatorProps) {
34
+ const serverActions = createServerActions?.(authData)
35
+
36
+ const modelMutators = mapObject(models, (val) => val.mutate) as {
37
+ [K in keyof typeof models]: (typeof models)[K]['mutate']
38
+ }
39
+
40
+ function withContext<Args extends any[]>(fn: (...args: Args) => Promise<void>) {
41
+ return async (tx: GenericTransaction, ...args: Args): Promise<void> => {
42
+ const mutationContext = {
43
+ tx,
44
+ authData,
45
+ environment,
46
+ can,
47
+ serverActions,
48
+ asyncTasks,
49
+ }
50
+
51
+ return await runWithContext(mutationContext, () => {
52
+ // @ts-expect-error type shenanigan
53
+ // map to our mutations() helper
54
+ return fn(mutationContext, ...args)
55
+ })
56
+ }
57
+ }
58
+
59
+ function withDevelopmentLogging<Args extends any[]>(
60
+ name: string,
61
+ fn: (...args: Args) => Promise<void>
62
+ ) {
63
+ if (process.env.NODE_ENV !== 'development' && !process.env.IS_TESTING) {
64
+ return fn
65
+ }
66
+
67
+ return async (...args: Args): Promise<void> => {
68
+ const startTime = performance.now()
69
+
70
+ try {
71
+ if (isServer) {
72
+ console.info(`[mutator] ${name} start`)
73
+ }
74
+ const result = await fn(...args)
75
+ const duration = (performance.now() - startTime).toFixed(2)
76
+ if (isClient) {
77
+ console.groupCollapsed(`[mutator] ${name} completed in ${duration}ms`)
78
+ console.info('→', args[1])
79
+ console.info('←', result)
80
+ console.trace()
81
+ console.groupEnd()
82
+ } else {
83
+ // TODO in prod just track
84
+ console.info(`[mutator] ${name} completed in ${duration}ms`)
85
+ }
86
+ return result
87
+ } catch (error) {
88
+ const duration = (performance.now() - startTime).toFixed(2)
89
+ console.groupCollapsed(`[mutator] ${name} failed after ${duration}ms`)
90
+ console.error('error:', error)
91
+ console.info('arguments:', JSON.stringify(args[1], null, 2))
92
+ console.info('stack trace:', new Error().stack)
93
+ console.groupEnd()
94
+ throw error
95
+ }
96
+ }
97
+ }
98
+
99
+ function withTimeoutGuard<Args extends any[]>(
100
+ name: string,
101
+ fn: (...args: Args) => Promise<void>,
102
+ // don't want this too high - zero runs mutations in order and waits for the last to finish it seems
103
+ // so if one mutation gets stuck it will just sit there
104
+ timeoutMs: number = time.ms.minutes(1)
105
+ ) {
106
+ return async (...args: Args): Promise<void> => {
107
+ const timeoutPromise = new Promise<never>((_, reject) => {
108
+ setTimeout(() => {
109
+ reject(new Error(`[mutator] ${name} timeout after ${timeoutMs}ms`))
110
+ }, timeoutMs)
111
+ })
112
+
113
+ return Promise.race([fn(...args), timeoutPromise])
114
+ }
115
+ }
116
+
117
+ function decorateMutators<T extends Record<string, Record<string, any>>>(
118
+ modules: T
119
+ ): Mutators {
120
+ const result: any = {}
121
+
122
+ for (const [moduleName, moduleExports] of Object.entries(modules)) {
123
+ result[moduleName] = {}
124
+ for (const [name, exportValue] of Object.entries(moduleExports)) {
125
+ if (typeof exportValue === 'function') {
126
+ const fullName = `${moduleName}.${name}`
127
+ result[moduleName][name] = withDevelopmentLogging(
128
+ fullName,
129
+ withTimeoutGuard(fullName, withContext(exportValue))
130
+ )
131
+ }
132
+ }
133
+ }
134
+
135
+ return result
136
+ }
137
+
138
+ return decorateMutators(modelMutators)
139
+ }
@@ -0,0 +1,8 @@
1
+ import { ensure } from '@vxrn/helpers'
2
+ import { context } from './context'
3
+
4
+ export const ensureLoggedIn = () => {
5
+ const { authData } = context()
6
+ ensure(authData, 'logged in')
7
+ return authData
8
+ }
@@ -0,0 +1,12 @@
1
+ // import { getAuth } from '~/auth/client/authClient'
2
+
3
+ // export const getAuthData = () => {
4
+ // const { user } = getAuth()
5
+ // return user
6
+ // ? {
7
+ // id: user.id || 'anon',
8
+ // email: user.email,
9
+ // role: undefined,
10
+ // }
11
+ // : null
12
+ // }
@@ -0,0 +1,167 @@
1
+ import type { Query } from '@rocicorp/zero'
2
+ import { ellipsis } from '@vxrn/helpers'
3
+
4
+ export const prettyFormatZeroQuery = (
5
+ query: Query<any, any, any>,
6
+ mode: 'full' | 'minimal' = 'full'
7
+ ): string => {
8
+ // @ts-expect-error accessing hidden property
9
+ const astObject = query['_completeAst']?.()
10
+
11
+ if (!astObject) return ''
12
+
13
+ if (mode === 'minimal') {
14
+ return prettyFormatMinimal(astObject)
15
+ }
16
+ return prettyFormatFull(astObject)
17
+ }
18
+
19
+ const prettyFormatFull = (astObject: any, indent = 0): string => {
20
+ if (!astObject || !astObject.table) return ''
21
+
22
+ const spaces = ' '.repeat(indent)
23
+ let query = astObject.table
24
+ let hasChainedMethods = false
25
+
26
+ // Add where conditions
27
+ if (astObject.where) {
28
+ const whereClause = formatWhere(astObject.where)
29
+ if (hasChainedMethods) {
30
+ query += `\n${spaces} ${whereClause}`
31
+ } else {
32
+ query += whereClause
33
+ hasChainedMethods = true
34
+ }
35
+ }
36
+
37
+ // Add limit
38
+ if (astObject.limit) {
39
+ const limitClause = `.limit(${astObject.limit})`
40
+ if (hasChainedMethods) {
41
+ query += `\n${spaces} ${limitClause}`
42
+ } else {
43
+ query += limitClause
44
+ hasChainedMethods = true
45
+ }
46
+ }
47
+
48
+ // Add orderBy
49
+ if (astObject.orderBy && astObject.orderBy.length > 0) {
50
+ const orderClauses = astObject.orderBy
51
+ .map(([field, direction]: [string, string]) => `${field}, ${direction}`)
52
+ .join(', ')
53
+ const orderByClause = `.orderBy(${orderClauses})`
54
+ if (hasChainedMethods) {
55
+ query += `\n${spaces} ${orderByClause}`
56
+ } else {
57
+ query += orderByClause
58
+ hasChainedMethods = true
59
+ }
60
+ }
61
+
62
+ // Add related queries
63
+ if (astObject.related && astObject.related.length > 0) {
64
+ astObject.related.forEach((rel: any) => {
65
+ if (rel.subquery) {
66
+ const alias = rel.subquery.alias || rel.subquery.table
67
+ const subQuery = prettyFormatFull(rel.subquery, indent + 1)
68
+ query += `\n${spaces} .related(${alias}, q => q.${subQuery}`
69
+ }
70
+ })
71
+
72
+ // Add closing parentheses
73
+ const closingParens = ')'.repeat(astObject.related.length)
74
+ query += `\n${spaces}${closingParens}`
75
+ }
76
+
77
+ return query
78
+ }
79
+
80
+ const prettyFormatMinimal = (astObject: any): string => {
81
+ if (!astObject || !astObject.table) return ''
82
+
83
+ let query = astObject.table
84
+
85
+ // Add where conditions only
86
+ if (astObject.where) {
87
+ query += formatWhere(astObject.where).replace('.where(', '(')
88
+ }
89
+
90
+ // Add sub-queries info if present
91
+ if (astObject.related && astObject.related.length > 0) {
92
+ const subQueries = collectSubQueryTables(astObject.related)
93
+ const count = subQueries.length
94
+ const tableNames = subQueries.join(', ')
95
+ query += ` (+${count}: ${ellipsis(tableNames, 30)})`
96
+ }
97
+
98
+ return query
99
+ }
100
+
101
+ const collectSubQueryTables = (related: any[]): string[] => {
102
+ const tables: string[] = []
103
+
104
+ related.forEach((rel: any) => {
105
+ if (rel.subquery) {
106
+ const tableName = rel.subquery.alias || rel.subquery.table
107
+ tables.push(tableName)
108
+
109
+ // Recursively collect nested sub-queries
110
+ if (rel.subquery.related && rel.subquery.related.length > 0) {
111
+ tables.push(...collectSubQueryTables(rel.subquery.related))
112
+ }
113
+ }
114
+ })
115
+
116
+ return tables
117
+ }
118
+
119
+ const formatWhere = (where: any): string => {
120
+ if (!where) return ''
121
+
122
+ if (where.type === 'simple') {
123
+ const column = where.left?.name || where.left
124
+ const value = where.right?.value !== undefined ? where.right.value : where.right
125
+ const op = where.op || '='
126
+
127
+ // Special case: if column is "id" and op is "=" and value is a single item, show just the value
128
+ if (
129
+ column === 'id' &&
130
+ op === '=' &&
131
+ (typeof value === 'string' || typeof value === 'number')
132
+ ) {
133
+ return `(${value})`
134
+ }
135
+
136
+ if (op === '=') {
137
+ return `.where(${column}, ${value})`
138
+ }
139
+ return `.where(${column}, ${op}, ${value})`
140
+ }
141
+
142
+ if (where.type === 'and' && where.conditions) {
143
+ let result = ''
144
+ where.conditions.forEach((condition: any, index: number) => {
145
+ if (index === 0) {
146
+ result += formatWhere(condition)
147
+ } else {
148
+ result += `.and(${formatWhere(condition).slice(1)})` // Remove the leading dot
149
+ }
150
+ })
151
+ return result
152
+ }
153
+
154
+ if (where.type === 'or' && where.conditions) {
155
+ let result = ''
156
+ where.conditions.forEach((condition: any, index: number) => {
157
+ if (index === 0) {
158
+ result += formatWhere(condition)
159
+ } else {
160
+ result += `.or(${formatWhere(condition).slice(1)})` // Remove the leading dot
161
+ }
162
+ })
163
+ return result
164
+ }
165
+
166
+ return ''
167
+ }
@@ -0,0 +1,13 @@
1
+ // import { useMemo } from 'react'
2
+ // import { useAuth } from '~/auth/client/authClient'
3
+ // import { getAuthData } from './getAuthData'
4
+
5
+ // export const useAuthData = () => {
6
+ // const auth = useAuth()
7
+ // const userId = auth?.user?.id || 'anon'
8
+
9
+ // // biome-ignore lint/correctness/useExhaustiveDependencies: its based on id
10
+ // return useMemo(() => {
11
+ // return getAuthData()
12
+ // }, [userId])
13
+ // }
@@ -0,0 +1,104 @@
1
+ import type { Query } from '@rocicorp/zero'
2
+ import { getCurrentComponentStack } from '@vxrn/helpers'
3
+ import { useEffect, useId } from 'react'
4
+ import { prettyFormatZeroQuery } from './prettyFormatZeroQuery'
5
+
6
+ const activeQueries = new Map<string, number>()
7
+
8
+ // AST change tracking
9
+ interface AstHistory {
10
+ asts: string[]
11
+ changeCount: number
12
+ }
13
+
14
+ const astHistoryByComponent = new Map<string, AstHistory>()
15
+
16
+ // control what is logged here:
17
+ const filterLogs = (table: string): boolean => false
18
+
19
+ const COLLAPSED = true
20
+ const AST_CHANGE_THRESHOLD = 4
21
+ const MAX_AST_HISTORY = 10
22
+
23
+ // short name because otherwise it often forces multiple lines in chrome devtools
24
+ // due to showing the filename next to log lines
25
+ export const useZeroDebug = (query: Query<any, any, any>, options: any, results: any) => {
26
+ // if (DEBUG_LEVEL < 2) {
27
+ // return
28
+ // }
29
+
30
+ const astObject = query['_completeAst']?.()
31
+ const table = astObject.table
32
+ const ast = JSON.stringify(astObject, null, 2)
33
+ const queryDisabled = !options || options?.enabled === false
34
+ const enabled = !queryDisabled && filterLogs(table)
35
+ const stack = new Error().stack
36
+ const isPermissionQuery = stack?.includes(`usePermission.ts`)
37
+ const id = useId()
38
+
39
+ // log here not in effect so we can breakpoint and find the query
40
+ const num = activeQueries.get(ast) || 0
41
+ const shouldLog = enabled && num === 0
42
+ if (enabled) {
43
+ activeQueries.set(ast, num + 1)
44
+ if (shouldLog) {
45
+ if (COLLAPSED) {
46
+ console.groupCollapsed(
47
+ `${isPermissionQuery ? `👮‍♂️` : `✨`}${prettyFormatZeroQuery(query, 'minimal')}`
48
+ )
49
+ console.info(id, prettyFormatZeroQuery(query, 'full'))
50
+ console.info('cached result', results)
51
+ console.trace()
52
+ console.groupEnd()
53
+ } else {
54
+ console.info(`✨`, prettyFormatZeroQuery(query, 'full'))
55
+ }
56
+ }
57
+ }
58
+
59
+ // track AST changes per component
60
+ useEffect(() => {
61
+ if (!enabled) return
62
+ const history = astHistoryByComponent.get(id) || { asts: [], changeCount: 0 }
63
+ const currentAst = ast
64
+ const lastAst = history.asts[history.asts.length - 1]
65
+
66
+ if (currentAst !== lastAst) {
67
+ history.asts.push(currentAst)
68
+ if (history.asts.length > MAX_AST_HISTORY) {
69
+ history.asts.shift()
70
+ }
71
+ history.changeCount++
72
+ astHistoryByComponent.set(id, history)
73
+
74
+ if (history.changeCount > AST_CHANGE_THRESHOLD) {
75
+ console.warn(
76
+ `⚠️ AST changed ${history.changeCount} times for component.
77
+ - id: ${id}
78
+ - stack: ${getCurrentComponentStack('short')}
79
+ - table: ${table}`,
80
+ {
81
+ componentId: id,
82
+ table,
83
+ changeCount: history.changeCount,
84
+ recentAsts: history.asts,
85
+ }
86
+ )
87
+ }
88
+ }
89
+ }, [id, ast, table, enabled])
90
+
91
+ useEffect(() => {
92
+ if (!enabled) return
93
+ return () => {
94
+ activeQueries.set(ast, activeQueries.get(ast)! - 1)
95
+ }
96
+ }, [ast, enabled])
97
+
98
+ // cleanup AST history on unmount
99
+ useEffect(() => {
100
+ return () => {
101
+ astHistoryByComponent.delete(id)
102
+ }
103
+ }, [id])
104
+ }
@@ -0,0 +1,5 @@
1
+ import { createEmitter } from '@vxrn/helpers'
2
+
3
+ export const zeroEmitter = createEmitter<ZeroEvent | undefined>('zero', undefined)
4
+
5
+ type ZeroEvent = { type: 'error'; message: string }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ export * from './createPermissions'
2
+ export * from './helpers/batchQuery'
3
+ export * from './helpers/context'
4
+ export * from './helpers/createMutators'
5
+ export * from './helpers/ensureLoggedIn'
6
+
7
+ export * from './mutations'
8
+ export * from './createZeroClient'
9
+ export * from './createZeroServer'
10
+
11
+ export type * from './types'
12
+
13
+ // export * from '~/data/schema'
14
+ // export * from './types'
15
+ // export * from '../zero'
@@ -0,0 +1,121 @@
1
+ import type { TableBuilderWithColumns } from '@rocicorp/zero'
2
+ import type { MutatorContext, TableInsertRow, TableUpdateRow, Where } from './types'
3
+
4
+ // two ways to use it:
5
+ // - mutations({}) which doesn't add the "allowed" helper or add CRUD
6
+ // - mutation('tableName', permissions) adds CRUD with permissions, adds allowed
7
+
8
+ type MutationBuilder<Obj = any> = (ctx: MutatorContext, obj?: Obj) => Promise<void>
9
+ type MutationBuilders = Record<string, MutationBuilder>
10
+
11
+ // start of adding custom can.write(message) style
12
+
13
+ // type PermissionedMutationBuilder<Permissions extends PermissionsWhere, Obj = any> = (
14
+ // ctx: MutatorContext & {
15
+ // can: any
16
+ // },
17
+ // obj?: Obj
18
+ // ) => Promise<void>
19
+ // type PermissionedMutationBuilders<Permissions extends PermissionsWhere> = Record<
20
+ // string,
21
+ // PermissionedMutationBuilder<Permissions>
22
+ // >
23
+
24
+ type GenericTable = TableBuilderWithColumns<any>
25
+
26
+ type CRUDMutations<Table extends GenericTable> = {
27
+ insert: MutationBuilder<TableInsertRow<Table>>
28
+ upsert: MutationBuilder<TableInsertRow<Table>>
29
+ update: MutationBuilder<TableUpdateRow<Table>>
30
+ delete: MutationBuilder<TableUpdateRow<Table>>
31
+ }
32
+
33
+ type CRUDNames = 'insert' | 'upsert' | 'update' | 'delete'
34
+
35
+ type MutationsWithCRUD<Table extends GenericTable, Mutations extends MutationBuilders> = {
36
+ [Key in CRUDNames | keyof Mutations]: Key extends keyof Mutations
37
+ ? Mutations[Key]
38
+ : Key extends keyof CRUDMutations<any>
39
+ ? CRUDMutations<Table>[Key]
40
+ : never
41
+ }
42
+
43
+ export function mutations<Table extends GenericTable, Permissions extends Where>(
44
+ table: Table,
45
+ permissions: Permissions
46
+ ): MutationsWithCRUD<Table, {}>
47
+
48
+ export function mutations<
49
+ Table extends GenericTable,
50
+ Permissions extends Where,
51
+ Mutations extends MutationBuilders,
52
+ >(
53
+ table: Table,
54
+ permissions: Permissions,
55
+ mutations: Mutations
56
+ ): MutationsWithCRUD<Table, Mutations>
57
+
58
+ export function mutations<Mutations extends MutationBuilder>(
59
+ mutations: Mutations
60
+ ): Mutations
61
+
62
+ // TODO we should enforece the CRUD mutations obj to the callier so they get it auto-typed
63
+ export function mutations<
64
+ Table extends GenericTable,
65
+ Mutations extends Record<string, MutationBuilder>,
66
+ >(table: Table | Mutations, permissions?: Where, mutations?: Mutations): Mutations {
67
+ if (permissions) {
68
+ const tableName = (table as Table).schema.name
69
+
70
+ const createCRUDMutation = (action: CRUDNames) => {
71
+ return async (ctx: MutatorContext, obj: any) => {
72
+ // run permission query - for delete before the mutation runs, for the rest after
73
+ // when the "can" query is unsuccessful it throws an error which will revert everything
74
+ const runServerPermissionCheck = async () => {
75
+ if (ctx.didCanPermissionsRun) {
76
+ // if the user-defined CRUD mutation runs their own "can", we avoid running ours
77
+ return
78
+ }
79
+
80
+ // only validate on the server
81
+ if (process.env.VITE_ENVIRONMENT === 'ssr') {
82
+ await ctx.can(permissions, action, obj)
83
+ }
84
+ }
85
+
86
+ if (action === 'delete') {
87
+ await runServerPermissionCheck()
88
+ }
89
+
90
+ // if user defines insert run theirs, if not run plain zero:
91
+ const existing = mutations?.[action]
92
+
93
+ if (existing) {
94
+ await existing(ctx, obj)
95
+ } else {
96
+ await ctx.tx.mutate[tableName][action](obj)
97
+ }
98
+
99
+ if (action !== 'delete') {
100
+ await runServerPermissionCheck()
101
+ }
102
+ }
103
+ }
104
+
105
+ const crudMutations: CRUDMutations<any> = {
106
+ insert: createCRUDMutation('insert'),
107
+ update: createCRUDMutation('update'),
108
+ delete: createCRUDMutation('delete'),
109
+ upsert: createCRUDMutation('upsert'),
110
+ }
111
+
112
+ return {
113
+ ...mutations,
114
+ // overwrite regular mutations but call them if they are defined by user
115
+ ...crudMutations,
116
+ } as any as Mutations
117
+ }
118
+
119
+ // no schema/permissions don't add CRUD
120
+ return table as any
121
+ }