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,281 @@
1
+ import type {
2
+ Condition,
3
+ ExpressionBuilder,
4
+ Query,
5
+ Transaction,
6
+ Schema as ZeroSchema,
7
+ } from '@rocicorp/zero'
8
+ import { ANYONE_CAN, definePermissions } from '@rocicorp/zero'
9
+ import { ensure, EnsureError, objectEntries } from '@vxrn/helpers'
10
+ import type { AuthData } from 'start/types'
11
+ // import { models } from '~/data/models'
12
+ // import { objectEntries } from '~/helpers/types/object'
13
+ import { prettyFormatZeroQuery } from './helpers/prettyFormatZeroQuery'
14
+ import type { MutatorContext, Where } from './types'
15
+
16
+ // TODO this will go away soon if we can get a good sync queries setup
17
+
18
+ export function createPermissions<Schema extends ZeroSchema, TableName extends string>({
19
+ schema,
20
+ getContext,
21
+ }: {
22
+ schema: Schema
23
+ getContext: () => MutatorContext
24
+ }) {
25
+ runEnvironmentSafetyCheck()
26
+
27
+ // we don't want flickers as you move around and these queries are re-run
28
+ // and things generally aren't changing with permissions rapidly, so lets
29
+ // cache the last results and use that when first rendering, they will
30
+ // always update once the query resolves
31
+
32
+ // Where defaults to letting you return basically anything because we want to allow it to be usable
33
+ // flexibly, one example is for permissions where you can return an Object with multiple wheres as values
34
+ // for example, PermissionsWhere below
35
+
36
+ function where<Table extends TableName, Builder extends Where = Where<Table>>(
37
+ tableName: Table,
38
+ builder: Builder
39
+ ): Builder
40
+
41
+ function where<Table extends TableName, Builder extends Where = Where<Table>>(
42
+ builder: Builder
43
+ ): Builder
44
+
45
+ function where<Table extends TableName, Builder extends Where = Where<Table>>(
46
+ a: Table | Builder,
47
+ b?: Builder
48
+ ): Builder {
49
+ if (b) {
50
+ WhereTableNameMap.set(b, a as Table)
51
+ }
52
+ return (b || a) as any
53
+ }
54
+
55
+ // permissions where:
56
+
57
+ const WhereTableNameMap = new WeakMap<Where, TableName>()
58
+
59
+ function getWhereTableName(where: Where) {
60
+ return WhereTableNameMap.get(where)
61
+ }
62
+
63
+ // example of a custom where we use for permissions
64
+
65
+ type PermissionPresetActions =
66
+ | 'read'
67
+ | 'write'
68
+ | 'insert'
69
+ | 'update'
70
+ | 'delete'
71
+ | 'select'
72
+
73
+ type PermissionsConditions = Partial<
74
+ Record<PermissionPresetActions | (string & {}), Condition | boolean>
75
+ >
76
+
77
+ type PermissionsWhere<Table extends TableName = TableName> = Where<
78
+ Table,
79
+ PermissionsConditions
80
+ >
81
+
82
+ const fallbackActions: Record<string, string> = {
83
+ select: 'read',
84
+ insert: 'write',
85
+ update: 'write',
86
+ upsert: 'write',
87
+ delete: 'write',
88
+ }
89
+
90
+ function buildPermissionQuery<
91
+ PermissionWhere extends PermissionsWhere,
92
+ Action extends string,
93
+ >(
94
+ authData: AuthData | null,
95
+ eb: ExpressionBuilder<any, any>,
96
+ permissionWhere: PermissionWhere,
97
+ action: Action,
98
+ // TODO until i can get a working PickPrimaryKeys<'message'>
99
+ objOrId: Record<string, any> | string
100
+ ) {
101
+ const tableName = getWhereTableName(permissionWhere)
102
+
103
+ if (!tableName) {
104
+ throw new Error(`Must use PermissionWhere for buildPermissionQuery`)
105
+ }
106
+
107
+ const tableSchema = schema.tables[tableName]
108
+ const primaryKeys = tableSchema.primaryKey
109
+ const permissionQueryBuilder = permissionWhere(eb, authData)
110
+ const fallbackAction = fallbackActions[action]
111
+
112
+ const permissionCondition =
113
+ permissionQueryBuilder[action] ||
114
+ (fallbackAction ? permissionQueryBuilder[fallbackAction] : undefined)
115
+
116
+ if (permissionCondition == null) {
117
+ throw new Error(`No permission defined for ${action} (or ${fallbackAction})`)
118
+ }
119
+
120
+ if (permissionCondition === true) {
121
+ return eb.cmpLit(true, '=', true)
122
+ }
123
+
124
+ if (permissionCondition === false) {
125
+ return eb.cmpLit(true, '=', false)
126
+ }
127
+
128
+ const primaryKeyWheres: Condition[] = []
129
+
130
+ for (const key of primaryKeys) {
131
+ const value = typeof objOrId === 'string' ? objOrId : objOrId[key]
132
+ primaryKeyWheres.push(eb.cmp(key as any, value))
133
+ }
134
+
135
+ return eb.and(permissionCondition, ...primaryKeyWheres)
136
+ }
137
+
138
+ async function can<
139
+ PWhere extends PermissionsWhere,
140
+ Action extends keyof ReturnType<PWhere>,
141
+ >(where: PWhere, action: Action, obj: any) {
142
+ const ctx = getContext()
143
+ const tableName = getWhereTableName(where)
144
+ if (!tableName) {
145
+ throw new Error(`Must use where('table') style where to pass to can()`)
146
+ }
147
+
148
+ // on client we always allow!
149
+ if (process.env.VITE_ENVIRONMENT === 'ssr') {
150
+ await ensurePermission(
151
+ ctx.tx,
152
+ ctx.authData,
153
+ tableName,
154
+ where,
155
+ action as string,
156
+ obj
157
+ )
158
+ ctx.didCanPermissionsRun = true
159
+ }
160
+ }
161
+
162
+ type TX = Transaction<Schema>
163
+
164
+ async function ensurePermission<
165
+ PW extends PermissionsWhere,
166
+ Action extends keyof ReturnType<PW>,
167
+ >(
168
+ tx: TX,
169
+ authData: AuthData | null,
170
+ tableName: TableName,
171
+ where: Where,
172
+ actionIn: Action,
173
+ obj: any // TODO until i can get a working PickPrimaryKeys<'message'>
174
+ ): Promise<void> {
175
+ if (authData?.role === 'admin') {
176
+ // admin role can do any mutation
177
+ return
178
+ }
179
+
180
+ const action = String(actionIn)
181
+ const name = `${tableName}.${action}`
182
+ const queryBase = tx.query[tableName] as Query<any, any>
183
+ let query: Query<any, any, any> | null = null
184
+
185
+ try {
186
+ query = queryBase
187
+ .where((eb) => {
188
+ return buildPermissionQuery(authData, eb, where, action, obj)
189
+ })
190
+ .one()
191
+
192
+ ensure(await query)
193
+ } catch (err) {
194
+ const errorTitle = `${name} with auth id: ${authData?.id}`
195
+
196
+ if (err instanceof EnsureError) {
197
+ let msg = `[permission] 🚫 Not Allowed: ${errorTitle}`
198
+ if (process.env.NODE_ENV === 'development' && query) {
199
+ msg += `\n ${prettyFormatZeroQuery(query)}`
200
+ }
201
+ throw new Error(msg)
202
+ }
203
+
204
+ throw new Error(`Error running permission ${errorTitle}\n${err}`)
205
+ }
206
+ }
207
+
208
+ const readPermissions = definePermissions<AuthData, Schema>(schema, async () => {
209
+ const permissionsEntries = await Promise.all(
210
+ objectEntries(models).map(async ([key, model]) => {
211
+ return await runWithContext(
212
+ {
213
+ authData: { id: '', role: undefined, email: '' },
214
+ } as any,
215
+ () => {
216
+ return [
217
+ key,
218
+ {
219
+ row: {
220
+ select: [
221
+ (authData: AuthData, eb: ExpressionBuilder<any, any>) => {
222
+ const out = model.permissions(eb, authData).read
223
+
224
+ if (out === true) {
225
+ return eb.and()
226
+ }
227
+
228
+ if (out === false) {
229
+ return eb.cmpLit(true, '=', false)
230
+ }
231
+
232
+ return out
233
+ },
234
+ ],
235
+ // we have permissions on these through our model system with custom mutators:
236
+ insert: ANYONE_CAN,
237
+ update: ANYONE_CAN,
238
+ delete: ANYONE_CAN,
239
+ },
240
+ },
241
+ ]
242
+ }
243
+ )
244
+ })
245
+ )
246
+
247
+ const permissions = Object.fromEntries(permissionsEntries)
248
+
249
+ return permissions as any
250
+ })
251
+
252
+ return {
253
+ where,
254
+ can,
255
+ buildPermissionQuery,
256
+ readPermissions,
257
+ }
258
+ }
259
+
260
+ // this is just so we have some assurance that we aren't skipping permissions
261
+ // checks due to bad VITE_ENVIRONMENT variable
262
+ function runEnvironmentSafetyCheck() {
263
+ if (typeof document !== 'undefined') {
264
+ // web!
265
+ } else if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
266
+ // react-native!
267
+ } else {
268
+ // server!
269
+ if (process.env.VITE_ENVIRONMENT !== 'ssr') {
270
+ console.error(`❌❌❌❌
271
+
272
+ ERROR: VITE_ENVIRONMENT is not set to "ssr" on server, which means permissions checks won't run when they should
273
+ This is makes Zero entirely insecure and needs to be fixed immediately.
274
+
275
+ This is likely a One framework issue, unless the user Vite config is overwriting the value.
276
+ One automatically sets this value.
277
+
278
+ `)
279
+ }
280
+ }
281
+ }
@@ -0,0 +1,191 @@
1
+ import type { Row, ZeroOptions, Schema as ZeroSchema } from '@rocicorp/zero'
2
+ import { useZero, ZeroProvider, useQuery as zeroUseQuery } from '@rocicorp/zero/react'
3
+ import { createLocalStorage, mapObject } from '@vxrn/helpers'
4
+ import { createContext, use, useMemo, type ReactNode } from 'react'
5
+ import { createPermissions } from './createPermissions'
6
+ import { context } from './helpers/context'
7
+ import { createMutators } from './helpers/createMutators'
8
+ import { prettyFormatZeroQuery } from './helpers/prettyFormatZeroQuery'
9
+ import { useZeroDebug } from './helpers/useZeroDebug'
10
+ import { zeroEmitter } from './helpers/zeroEmitter'
11
+ import type { AuthData } from './types'
12
+
13
+ export function createZero<Schema extends ZeroSchema>({
14
+ schema,
15
+ models,
16
+ disable,
17
+ }: {
18
+ schema: Schema
19
+ models: any
20
+ disable?: boolean
21
+ }) {
22
+ // TODO
23
+ type TableName = string
24
+ type Mutators = any // typeof mutators
25
+ type ZeroInstance = ReturnType<typeof useZero<Schema, Mutators>>
26
+
27
+ const modelPermissions = mapObject(models, (val) => val.permissions) as {
28
+ [K in keyof typeof models]: (typeof models)[K]['permissions']
29
+ }
30
+
31
+ const permissionsHelpers = createPermissions<Schema, TableName>({
32
+ schema,
33
+ getContext: context,
34
+ })
35
+
36
+ const permissionCache = createLocalStorage<string, boolean>('permissions-cache', {
37
+ storageLimit: 24,
38
+ })
39
+
40
+ const AuthDataContext = createContext<AuthData>(null)
41
+ const useAuthData = () => use(AuthDataContext)
42
+
43
+ function usePermission<
44
+ K extends TableName,
45
+ Action extends 'insert' | 'update' | 'delete' | 'select',
46
+ >(
47
+ table: K,
48
+ action: Action,
49
+ objOrId: string | Partial<Row<Schema['tables'][K]>> | undefined,
50
+ enabled = typeof objOrId !== 'undefined',
51
+ debug = false
52
+ ): boolean | null {
53
+ // we fallback to just table.action, to avoid flickers for now
54
+ const keyBase = `${table}${action}`
55
+ const key = `${keyBase}${typeof objOrId === 'string' ? objOrId : JSON.stringify(objOrId)}`
56
+ const cacheVal = permissionCache.get(key) ?? permissionCache.get(keyBase)
57
+ const authData = useAuthData()
58
+ const permission = modelPermissions[table]
59
+
60
+ const query = (() => {
61
+ let baseQuery = zero.query[table].one()
62
+
63
+ if (!enabled) {
64
+ return baseQuery
65
+ }
66
+
67
+ return baseQuery.where((eb) => {
68
+ return permissionsHelpers.buildPermissionQuery(
69
+ authData,
70
+ eb,
71
+ permission,
72
+ action,
73
+ objOrId as any
74
+ )
75
+ })
76
+ })()
77
+
78
+ const [data, status] = useQuery(query, {
79
+ enabled: Boolean(enabled && authData && objOrId),
80
+ })
81
+
82
+ if (debug) {
83
+ console.info(
84
+ `usePermission()`,
85
+ { data, status, action, authData, permission },
86
+ prettyFormatZeroQuery(query)
87
+ )
88
+ }
89
+
90
+ const result = data
91
+
92
+ const allowed = Boolean(result)
93
+
94
+ if (!objOrId) {
95
+ return false
96
+ }
97
+
98
+ return allowed
99
+ }
100
+
101
+ let latestZeroInstance: ZeroInstance | null = null
102
+
103
+ // when we log in this is swapped out and later the exported zero is just a proxy
104
+ // may cause some issues, ideally zero itself supports this
105
+ const zero: ZeroInstance = new Proxy({} as never, {
106
+ get(_, key) {
107
+ return Reflect.get(latestZeroInstance!, key, latestZeroInstance)
108
+ },
109
+ })
110
+
111
+ const useQuery: typeof zeroUseQuery = (query, options) => {
112
+ if (disable) {
113
+ return [null, { type: 'unknown' }] as never
114
+ }
115
+
116
+ const out = zeroUseQuery(query, options)
117
+
118
+ if (process.env.NODE_ENV === 'development') {
119
+ // biome-ignore lint/correctness/useHookAtTopLevel: ok
120
+ useZeroDebug(query, options, out)
121
+ }
122
+
123
+ return out
124
+ }
125
+
126
+ const ProvideZero = ({
127
+ children,
128
+ authData,
129
+ ...props
130
+ }: Omit<ZeroOptions<Schema, Mutators>, 'schema' | 'mutators'> & {
131
+ children: ReactNode
132
+ authData?: any
133
+ }) => {
134
+ const mutators = useMemo(() => {
135
+ return createMutators({
136
+ models,
137
+ environment: 'client',
138
+ authData,
139
+ can: permissionsHelpers.can,
140
+ })
141
+ }, [models, authData])
142
+
143
+ if (disable) {
144
+ return children
145
+ }
146
+
147
+ return (
148
+ <AuthDataContext.Provider value={authData}>
149
+ <ZeroProvider
150
+ schema={schema}
151
+ kvStore={'mem'}
152
+ onError={(error) => {
153
+ console.error(`Zero Error:`, error)
154
+ zeroEmitter.emit({
155
+ type: 'error',
156
+ message: error,
157
+ })
158
+ }}
159
+ mutators={mutators}
160
+ {...props}
161
+ >
162
+ <SetZeroInstance />
163
+ {children}
164
+ </ZeroProvider>
165
+ </AuthDataContext.Provider>
166
+ )
167
+ }
168
+
169
+ const SetZeroInstance = () => {
170
+ const zero = useZero<Schema, Mutators>()
171
+
172
+ // TODO last hack zero wants us to use useZero but its a big migration
173
+ // and has some downsides (global zero import leads to simpler code)
174
+ // they plan to support .setAuth() at some point, and so long as we refresh
175
+ // when we do change zero, this should be safe - that said we don't refresh
176
+ // the browser for now, but we also don't handle new auth keys in general
177
+ // we'll need to add that soon
178
+ if (zero !== latestZeroInstance) {
179
+ latestZeroInstance = zero
180
+ }
181
+
182
+ return null
183
+ }
184
+
185
+ return {
186
+ ProvideZero,
187
+ useQuery,
188
+ usePermission,
189
+ zero,
190
+ }
191
+ }
@@ -0,0 +1,153 @@
1
+ import type { Schema as ZeroSchema } from '@rocicorp/zero'
2
+ import type { TransactionProviderInput } from '@rocicorp/zero/pg'
3
+ import { PostgresJSConnection } from '@rocicorp/zero/pg'
4
+ import { ZQLDatabase } from '@rocicorp/zero/server'
5
+ import postgres from 'postgres'
6
+ // import { context, isInZeroMutation } from 'src/context'
7
+ import type { AuthData } from './types'
8
+ // import { type Mutators, createMutators } from '~/data/helpers/createMutators'
9
+ // import { schema } from '~/data/schema'
10
+ // import { createServerActions } from '~/data/server/createServerActions'
11
+ // import type { TX } from '~/data/types'
12
+ import { PushProcessor } from '@rocicorp/zero/pg'
13
+ import { assertString, randomId } from '@vxrn/helpers'
14
+ import { createMutators } from './helpers/createMutators'
15
+ // import type { Endpoint } from 'one'
16
+ // import type { AuthData } from 'start/data'
17
+ // import { getAuthHeader, NotAuthenticatedError } from '~/auth/validateAuthHeader'
18
+ // import { randomId } from '@vxrn/helpers'
19
+ // import { createMutators } from '~/data/helpers/createMutators'
20
+ // import { createServerActions } from '~/data/server/createServerActions'
21
+ // import { GenericTransaction } from './types'
22
+
23
+ // this sets up custom server-side mutators
24
+ // see: https://zero.rocicorp.dev/docs/custom-mutators
25
+
26
+ export function createZeroServer<
27
+ Schema extends ZeroSchema,
28
+ ServerActions extends Record<string, any>,
29
+ >({
30
+ createServerActions,
31
+ schema,
32
+ models,
33
+ disable,
34
+ }: {
35
+ schema: Schema
36
+ models: any
37
+ createServerActions: (authData: AuthData | null) => ServerActions
38
+ disable?: boolean
39
+ }) {
40
+ // TODO
41
+ type TX = any
42
+ type Mutators = any
43
+
44
+ const dbString = assertString(process.env.ZERO_UPSTREAM_DB)
45
+
46
+ const zeroServerDatabase = new ZQLDatabase(
47
+ new PostgresJSConnection(postgres(dbString)),
48
+ schema
49
+ )
50
+
51
+ const processor = new PushProcessor(zeroServerDatabase)
52
+
53
+ const handleMutationRequest = async ({
54
+ authData,
55
+ request,
56
+ skipAsyncTasks,
57
+ }: {
58
+ authData: AuthData
59
+ request: Request
60
+ skipAsyncTasks?: boolean
61
+ }) => {
62
+ // since mutations do DB work in transaction, avoid any async tasks during
63
+ const asyncTasks: Array<() => Promise<void>> = []
64
+
65
+ const response = await processor.process(
66
+ createMutators({
67
+ environment: 'server',
68
+ asyncTasks,
69
+ authData,
70
+ createServerActions,
71
+ }),
72
+ request
73
+ )
74
+
75
+ // now finish
76
+ if (!skipAsyncTasks && asyncTasks.length) {
77
+ const id = randomId()
78
+ console.info(`[push] complete, running async tasks ${asyncTasks.length} id ${id}`)
79
+ Promise.all(asyncTasks.map((task) => task()))
80
+ .then(() => {
81
+ console.info(`[push] async tasks complete ${id}`)
82
+ })
83
+ .catch((err) => {
84
+ console.error(`[push] error: async tasks failed 😞`, err)
85
+ })
86
+ }
87
+
88
+ return {
89
+ response,
90
+ asyncTasks,
91
+ }
92
+ }
93
+
94
+ const serverMutate = async (
95
+ run: (tx: TX, mutators: Mutators) => Promise<void>,
96
+ authData?: Pick<AuthData, 'email' | 'id'> & Partial<AuthData>
97
+ ) => {
98
+ const asyncTasks: Array<() => Promise<void>> = []
99
+
100
+ const mutators = createMutators({
101
+ environment: 'server',
102
+ asyncTasks,
103
+ authData: {
104
+ id: '',
105
+ email: 'admin@start.chat',
106
+ role: 'admin',
107
+ ...authData,
108
+ },
109
+ createServerActions,
110
+ })
111
+
112
+ await serverTransaction(async (tx) => {
113
+ await run(tx, mutators)
114
+ })
115
+
116
+ await Promise.all(asyncTasks.map((t) => t()))
117
+ }
118
+
119
+ // shorthand but nicer for single queries
120
+ // TODO should unwrap q.query
121
+ const serverQuery = serverTransaction
122
+
123
+ // This is needed temporarily and will be cleaned up in the future.
124
+ const dummyTransactionInput: TransactionProviderInput = {
125
+ clientGroupID: 'unused',
126
+ clientID: 'unused',
127
+ mutationID: 42,
128
+ upstreamSchema: 'unused',
129
+ }
130
+
131
+ async function serverTransaction<
132
+ CB extends (tx: TX) => Promise<any>,
133
+ Returns extends CB extends (tx: TX) => Promise<infer X> ? X : never,
134
+ >(query: CB): Promise<Returns> {
135
+ try {
136
+ if (isInZeroMutation()) {
137
+ const { tx } = context()
138
+ return await query(tx)
139
+ }
140
+ return (await zeroServerDatabase.transaction(query, dummyTransactionInput)) as any
141
+ } catch (err) {
142
+ console.error(`Error running serverTransaction(): ${err}`)
143
+ throw err
144
+ }
145
+ }
146
+
147
+ return {
148
+ handleMutationRequest,
149
+ transaction: serverTransaction,
150
+ mutate: serverMutate,
151
+ query: serverQuery,
152
+ }
153
+ }
@@ -0,0 +1,45 @@
1
+ import type { Query, Row } from '@rocicorp/zero'
2
+ import { sleep } from '@vxrn/helpers'
3
+
4
+ export async function batchQuery<Q extends Query<any, any, any>, Item extends Row<Q>>(
5
+ q: Q,
6
+ mapper: (items: Item[]) => Promise<void>,
7
+ {
8
+ chunk,
9
+ pause = 0,
10
+ stopAfter = 100_000,
11
+ }: {
12
+ chunk: number
13
+ pause?: number
14
+ stopAfter?: number
15
+ } = { chunk: 20 }
16
+ ) {
17
+ let hasMore = true
18
+ let last: Item | null = null
19
+ let iterations = 0
20
+
21
+ while (hasMore) {
22
+ let query = q.limit(chunk)
23
+
24
+ if (last) {
25
+ query = query.start(last)
26
+ }
27
+
28
+ const results = await query.run({ type: 'complete' })
29
+
30
+ await mapper(results as Item[])
31
+
32
+ if (results.length < chunk) {
33
+ hasMore = false
34
+ }
35
+
36
+ if (iterations > stopAfter) {
37
+ console.error(`[batchQuery] ‼️ stopping batch, ran ${stopAfter} chunks`)
38
+ break
39
+ }
40
+
41
+ if (pause) {
42
+ await sleep(pause)
43
+ }
44
+ }
45
+ }