zenstack 1.0.0 → 1.0.2

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 (300) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +194 -1
  3. package/bin/cli +3 -0
  4. package/bin/post-install.js +24 -0
  5. package/cli/actions/generate.d.ts +13 -0
  6. package/cli/actions/generate.js +71 -0
  7. package/cli/actions/generate.js.map +1 -0
  8. package/cli/actions/index.d.ts +3 -0
  9. package/cli/actions/index.js +20 -0
  10. package/cli/actions/index.js.map +1 -0
  11. package/cli/actions/info.d.ts +4 -0
  12. package/cli/actions/info.js +63 -0
  13. package/cli/actions/info.js.map +1 -0
  14. package/cli/actions/init.d.ts +12 -0
  15. package/cli/actions/init.js +83 -0
  16. package/cli/actions/init.js.map +1 -0
  17. package/cli/cli-error.d.ts +5 -0
  18. package/cli/cli-error.js +10 -0
  19. package/cli/cli-error.js.map +1 -0
  20. package/cli/cli-util.d.ts +21 -0
  21. package/cli/cli-util.js +211 -0
  22. package/cli/cli-util.js.map +1 -0
  23. package/cli/config.d.ts +10 -0
  24. package/cli/config.js +62 -0
  25. package/cli/config.js.map +1 -0
  26. package/cli/index.d.ts +7 -0
  27. package/cli/index.js +128 -0
  28. package/cli/index.js.map +1 -0
  29. package/cli/plugin-runner.d.ts +24 -0
  30. package/cli/plugin-runner.js +229 -0
  31. package/cli/plugin-runner.js.map +1 -0
  32. package/constants.d.ts +1 -0
  33. package/constants.js +6 -0
  34. package/constants.js.map +1 -0
  35. package/language-server/constants.d.ts +22 -0
  36. package/language-server/constants.js +27 -0
  37. package/language-server/constants.js.map +1 -0
  38. package/language-server/main.d.ts +1 -0
  39. package/language-server/main.js +13 -0
  40. package/language-server/main.js.map +1 -0
  41. package/language-server/types.d.ts +10 -0
  42. package/language-server/types.js +3 -0
  43. package/language-server/types.js.map +1 -0
  44. package/language-server/utils.d.ts +5 -0
  45. package/language-server/utils.js +22 -0
  46. package/language-server/utils.js.map +1 -0
  47. package/language-server/validator/attribute-application-validator.d.ts +15 -0
  48. package/language-server/validator/attribute-application-validator.js +246 -0
  49. package/language-server/validator/attribute-application-validator.js.map +1 -0
  50. package/language-server/validator/attribute-validator.d.ts +9 -0
  51. package/language-server/validator/attribute-validator.js +14 -0
  52. package/language-server/validator/attribute-validator.js.map +1 -0
  53. package/language-server/validator/datamodel-validator.d.ts +22 -0
  54. package/language-server/validator/datamodel-validator.js +329 -0
  55. package/language-server/validator/datamodel-validator.js.map +1 -0
  56. package/language-server/validator/datasource-validator.d.ts +12 -0
  57. package/language-server/validator/datasource-validator.js +66 -0
  58. package/language-server/validator/datasource-validator.js.map +1 -0
  59. package/language-server/validator/enum-validator.d.ts +11 -0
  60. package/language-server/validator/enum-validator.js +25 -0
  61. package/language-server/validator/enum-validator.js.map +1 -0
  62. package/language-server/validator/expression-validator.d.ts +10 -0
  63. package/language-server/validator/expression-validator.js +135 -0
  64. package/language-server/validator/expression-validator.js.map +1 -0
  65. package/language-server/validator/function-decl-validator.d.ts +9 -0
  66. package/language-server/validator/function-decl-validator.js +13 -0
  67. package/language-server/validator/function-decl-validator.js.map +1 -0
  68. package/language-server/validator/function-invocation-validator.d.ts +11 -0
  69. package/language-server/validator/function-invocation-validator.js +135 -0
  70. package/language-server/validator/function-invocation-validator.js.map +1 -0
  71. package/language-server/validator/schema-validator.d.ts +13 -0
  72. package/language-server/validator/schema-validator.js +49 -0
  73. package/language-server/validator/schema-validator.js.map +1 -0
  74. package/language-server/validator/utils.d.ts +24 -0
  75. package/language-server/validator/utils.js +154 -0
  76. package/language-server/validator/utils.js.map +1 -0
  77. package/language-server/validator/zmodel-validator.d.ts +25 -0
  78. package/language-server/validator/zmodel-validator.js +83 -0
  79. package/language-server/validator/zmodel-validator.js.map +1 -0
  80. package/language-server/zmodel-code-action.d.ts +15 -0
  81. package/language-server/zmodel-code-action.js +118 -0
  82. package/language-server/zmodel-code-action.js.map +1 -0
  83. package/language-server/zmodel-definition.d.ts +7 -0
  84. package/language-server/zmodel-definition.js +31 -0
  85. package/language-server/zmodel-definition.js.map +1 -0
  86. package/language-server/zmodel-formatter.d.ts +9 -0
  87. package/language-server/zmodel-formatter.js +76 -0
  88. package/language-server/zmodel-formatter.js.map +1 -0
  89. package/language-server/zmodel-linker.d.ts +32 -0
  90. package/language-server/zmodel-linker.js +447 -0
  91. package/language-server/zmodel-linker.js.map +1 -0
  92. package/language-server/zmodel-module.d.ts +41 -0
  93. package/language-server/zmodel-module.js +83 -0
  94. package/language-server/zmodel-module.js.map +1 -0
  95. package/language-server/zmodel-scope.d.ts +16 -0
  96. package/language-server/zmodel-scope.js +100 -0
  97. package/language-server/zmodel-scope.js.map +1 -0
  98. package/language-server/zmodel-workspace-manager.d.ts +12 -0
  99. package/language-server/zmodel-workspace-manager.js +138 -0
  100. package/language-server/zmodel-workspace-manager.js.map +1 -0
  101. package/package.json +140 -8
  102. package/plugins/access-policy/expression-writer.d.ts +46 -0
  103. package/plugins/access-policy/expression-writer.js +580 -0
  104. package/plugins/access-policy/expression-writer.js.map +1 -0
  105. package/plugins/access-policy/index.d.ts +4 -0
  106. package/plugins/access-policy/index.js +22 -0
  107. package/plugins/access-policy/index.js.map +1 -0
  108. package/plugins/access-policy/policy-guard-generator.d.ts +22 -0
  109. package/plugins/access-policy/policy-guard-generator.js +634 -0
  110. package/plugins/access-policy/policy-guard-generator.js.map +1 -0
  111. package/plugins/model-meta/index.d.ts +4 -0
  112. package/plugins/model-meta/index.js +232 -0
  113. package/plugins/model-meta/index.js.map +1 -0
  114. package/plugins/plugin-utils.d.ts +17 -0
  115. package/plugins/plugin-utils.js +80 -0
  116. package/plugins/plugin-utils.js.map +1 -0
  117. package/plugins/prisma/indent-string.d.ts +4 -0
  118. package/plugins/prisma/indent-string.js +12 -0
  119. package/plugins/prisma/indent-string.js.map +1 -0
  120. package/plugins/prisma/index.d.ts +4 -0
  121. package/plugins/prisma/index.js +22 -0
  122. package/plugins/prisma/index.js.map +1 -0
  123. package/plugins/prisma/prisma-builder.d.ts +145 -0
  124. package/plugins/prisma/prisma-builder.js +358 -0
  125. package/plugins/prisma/prisma-builder.js.map +1 -0
  126. package/plugins/prisma/schema-generator.d.ts +29 -0
  127. package/plugins/prisma/schema-generator.js +336 -0
  128. package/plugins/prisma/schema-generator.js.map +1 -0
  129. package/plugins/prisma/zmodel-code-generator.d.ts +30 -0
  130. package/plugins/prisma/zmodel-code-generator.js +124 -0
  131. package/plugins/prisma/zmodel-code-generator.js.map +1 -0
  132. package/plugins/zod/generator.d.ts +4 -0
  133. package/plugins/zod/generator.js +254 -0
  134. package/plugins/zod/generator.js.map +1 -0
  135. package/plugins/zod/index.d.ts +4 -0
  136. package/plugins/zod/index.js +24 -0
  137. package/plugins/zod/index.js.map +1 -0
  138. package/plugins/zod/transformer.d.ts +68 -0
  139. package/plugins/zod/transformer.js +554 -0
  140. package/plugins/zod/transformer.js.map +1 -0
  141. package/plugins/zod/types.d.ts +25 -0
  142. package/plugins/zod/types.js +3 -0
  143. package/plugins/zod/types.js.map +1 -0
  144. package/plugins/zod/utils/removeDir.d.ts +1 -0
  145. package/plugins/zod/utils/removeDir.js +30 -0
  146. package/plugins/zod/utils/removeDir.js.map +1 -0
  147. package/plugins/zod/utils/schema-gen.d.ts +3 -0
  148. package/plugins/zod/utils/schema-gen.js +188 -0
  149. package/plugins/zod/utils/schema-gen.js.map +1 -0
  150. package/res/prism-zmodel.js +20 -0
  151. package/res/starter.zmodel +51 -0
  152. package/res/stdlib.zmodel +506 -0
  153. package/telemetry.d.ts +21 -0
  154. package/telemetry.js +129 -0
  155. package/telemetry.js.map +1 -0
  156. package/utils/ast-utils.d.ts +13 -0
  157. package/utils/ast-utils.js +136 -0
  158. package/utils/ast-utils.js.map +1 -0
  159. package/utils/exec-utils.d.ts +6 -0
  160. package/utils/exec-utils.js +13 -0
  161. package/utils/exec-utils.js.map +1 -0
  162. package/utils/pkg-utils.d.ts +3 -0
  163. package/utils/pkg-utils.js +64 -0
  164. package/utils/pkg-utils.js.map +1 -0
  165. package/utils/typescript-expression-transformer.d.ts +54 -0
  166. package/utils/typescript-expression-transformer.js +326 -0
  167. package/utils/typescript-expression-transformer.js.map +1 -0
  168. package/utils/version-utils.d.ts +1 -0
  169. package/utils/version-utils.js +20 -0
  170. package/utils/version-utils.js.map +1 -0
  171. package/.vscode/extensions.json +0 -7
  172. package/.vscode/launch.json +0 -49
  173. package/.vscode/settings.json +0 -4
  174. package/packages/internal/jest.config.ts +0 -32
  175. package/packages/internal/package.json +0 -42
  176. package/packages/internal/src/constants.ts +0 -1
  177. package/packages/internal/src/handler/data/guard-utils.ts +0 -7
  178. package/packages/internal/src/handler/data/handler.ts +0 -415
  179. package/packages/internal/src/handler/data/query-processor.ts +0 -504
  180. package/packages/internal/src/handler/index.ts +0 -1
  181. package/packages/internal/src/handler/types.ts +0 -20
  182. package/packages/internal/src/index.ts +0 -3
  183. package/packages/internal/src/request-handler.ts +0 -27
  184. package/packages/internal/src/request.ts +0 -101
  185. package/packages/internal/src/types.ts +0 -40
  186. package/packages/internal/tests/query-processor.test.ts +0 -172
  187. package/packages/internal/tsconfig.json +0 -21
  188. package/packages/runtime/auth.d.ts +0 -1
  189. package/packages/runtime/auth.js +0 -3
  190. package/packages/runtime/hooks.d.ts +0 -10
  191. package/packages/runtime/hooks.js +0 -3
  192. package/packages/runtime/index.d.ts +0 -3
  193. package/packages/runtime/index.js +0 -1
  194. package/packages/runtime/package-lock.json +0 -512
  195. package/packages/runtime/package.json +0 -16
  196. package/packages/runtime/server.d.ts +0 -1
  197. package/packages/runtime/server.js +0 -3
  198. package/packages/runtime/types.d.ts +0 -1
  199. package/packages/runtime/types.js +0 -3
  200. package/packages/schema/.eslintrc.json +0 -13
  201. package/packages/schema/.vscodeignore +0 -4
  202. package/packages/schema/asset/logo-dark.png +0 -0
  203. package/packages/schema/asset/logo-light.png +0 -0
  204. package/packages/schema/bin/cli +0 -3
  205. package/packages/schema/jest.config.ts +0 -32
  206. package/packages/schema/langium-config.json +0 -14
  207. package/packages/schema/langium-quickstart.md +0 -41
  208. package/packages/schema/language-configuration.json +0 -30
  209. package/packages/schema/package.json +0 -96
  210. package/packages/schema/src/cli/cli-util.ts +0 -80
  211. package/packages/schema/src/cli/index.ts +0 -64
  212. package/packages/schema/src/extension.ts +0 -76
  213. package/packages/schema/src/generator/constants.ts +0 -5
  214. package/packages/schema/src/generator/index.ts +0 -92
  215. package/packages/schema/src/generator/next-auth/index.ts +0 -197
  216. package/packages/schema/src/generator/package.template.json +0 -9
  217. package/packages/schema/src/generator/prisma/expression-writer.ts +0 -352
  218. package/packages/schema/src/generator/prisma/index.ts +0 -32
  219. package/packages/schema/src/generator/prisma/plain-expression-builder.ts +0 -91
  220. package/packages/schema/src/generator/prisma/prisma-builder.ts +0 -366
  221. package/packages/schema/src/generator/prisma/query-gard-generator.ts +0 -208
  222. package/packages/schema/src/generator/prisma/schema-generator.ts +0 -300
  223. package/packages/schema/src/generator/react-hooks/index.ts +0 -181
  224. package/packages/schema/src/generator/service/index.ts +0 -107
  225. package/packages/schema/src/generator/tsconfig.template.json +0 -17
  226. package/packages/schema/src/generator/types.ts +0 -17
  227. package/packages/schema/src/generator/utils.ts +0 -9
  228. package/packages/schema/src/language-server/generated/ast.ts +0 -603
  229. package/packages/schema/src/language-server/generated/grammar.ts +0 -2190
  230. package/packages/schema/src/language-server/generated/module.ts +0 -24
  231. package/packages/schema/src/language-server/main.ts +0 -12
  232. package/packages/schema/src/language-server/stdlib.zmodel +0 -22
  233. package/packages/schema/src/language-server/types.ts +0 -9
  234. package/packages/schema/src/language-server/zmodel-index.ts +0 -33
  235. package/packages/schema/src/language-server/zmodel-linker.ts +0 -409
  236. package/packages/schema/src/language-server/zmodel-module.ts +0 -90
  237. package/packages/schema/src/language-server/zmodel-scope.ts +0 -21
  238. package/packages/schema/src/language-server/zmodel-validator.ts +0 -35
  239. package/packages/schema/src/language-server/zmodel.langium +0 -186
  240. package/packages/schema/src/utils/exec-utils.ts +0 -5
  241. package/packages/schema/src/utils/indent-string.ts +0 -6
  242. package/packages/schema/syntaxes/zmodel.json +0 -57
  243. package/packages/schema/syntaxes/zmodel.tmLanguage.json +0 -57
  244. package/packages/schema/tests/generator/expression-writer.test.ts +0 -676
  245. package/packages/schema/tests/generator/prisma-builder.test.ts +0 -138
  246. package/packages/schema/tests/schema/parser.test.ts +0 -423
  247. package/packages/schema/tests/schema/sample-todo.test.ts +0 -14
  248. package/packages/schema/tests/utils.ts +0 -38
  249. package/packages/schema/tsconfig.json +0 -23
  250. package/pnpm-workspace.yaml +0 -3
  251. package/samples/todo/.env +0 -2
  252. package/samples/todo/.eslintrc.json +0 -3
  253. package/samples/todo/.vscode/launch.json +0 -11
  254. package/samples/todo/README.md +0 -34
  255. package/samples/todo/components/AuthGuard.tsx +0 -17
  256. package/samples/todo/components/Avatar.tsx +0 -22
  257. package/samples/todo/components/BreadCrumb.tsx +0 -44
  258. package/samples/todo/components/ManageMembers.tsx +0 -134
  259. package/samples/todo/components/NavBar.tsx +0 -57
  260. package/samples/todo/components/SpaceMembers.tsx +0 -76
  261. package/samples/todo/components/Spaces.tsx +0 -28
  262. package/samples/todo/components/TimeInfo.tsx +0 -17
  263. package/samples/todo/components/Todo.tsx +0 -72
  264. package/samples/todo/components/TodoList.tsx +0 -77
  265. package/samples/todo/lib/context.ts +0 -31
  266. package/samples/todo/next.config.js +0 -10
  267. package/samples/todo/package-lock.json +0 -7527
  268. package/samples/todo/package.json +0 -45
  269. package/samples/todo/pages/_app.tsx +0 -50
  270. package/samples/todo/pages/api/auth/[...nextauth].ts +0 -83
  271. package/samples/todo/pages/api/zenstack/[...path].ts +0 -16
  272. package/samples/todo/pages/create-space.tsx +0 -114
  273. package/samples/todo/pages/index.tsx +0 -32
  274. package/samples/todo/pages/space/[slug]/[listId]/index.tsx +0 -88
  275. package/samples/todo/pages/space/[slug]/index.tsx +0 -169
  276. package/samples/todo/postcss.config.js +0 -6
  277. package/samples/todo/public/avatar.jpg +0 -0
  278. package/samples/todo/public/favicon.ico +0 -0
  279. package/samples/todo/public/logo.png +0 -0
  280. package/samples/todo/public/vercel.svg +0 -4
  281. package/samples/todo/styles/globals.css +0 -7
  282. package/samples/todo/tailwind.config.js +0 -11
  283. package/samples/todo/tsconfig.json +0 -28
  284. package/samples/todo/types/next-auth.d.ts +0 -14
  285. package/samples/todo/types/next.d.ts +0 -16
  286. package/samples/todo/zenstack/migrations/20221014084317_init/migration.sql +0 -153
  287. package/samples/todo/zenstack/migrations/20221020094651_upate_cli/migration.sql +0 -23
  288. package/samples/todo/zenstack/migrations/migration_lock.toml +0 -3
  289. package/samples/todo/zenstack/schema.prisma +0 -126
  290. package/samples/todo/zenstack/schema.zmodel +0 -161
  291. package/tests/integration/jest.config.ts +0 -16
  292. package/tests/integration/package-lock.json +0 -1081
  293. package/tests/integration/package.json +0 -27
  294. package/tests/integration/tests/operation-coverate.test.ts +0 -563
  295. package/tests/integration/tests/operations.zmodel +0 -69
  296. package/tests/integration/tests/todo-e2e.test.ts +0 -577
  297. package/tests/integration/tests/todo.zmodel +0 -123
  298. package/tests/integration/tests/tsconfig.template.json +0 -10
  299. package/tests/integration/tests/utils.ts +0 -133
  300. package/tests/integration/tsconfig.json +0 -10
@@ -1,504 +0,0 @@
1
- import deepcopy from 'deepcopy';
2
- import { TRANSACTION_FIELD_NAME } from '../../constants';
3
- import {
4
- PolicyOperationKind,
5
- QueryContext,
6
- ServerErrorCode,
7
- Service,
8
- } from '../../types';
9
- import { RequestHandlerError } from '../types';
10
- import { and } from './guard-utils';
11
-
12
- export class QueryProcessor {
13
- constructor(private readonly service: Service) {}
14
-
15
- async processQueryArgsForWrite(
16
- model: string,
17
- args: any,
18
- operation: PolicyOperationKind,
19
- context: QueryContext,
20
- transactionId: string
21
- ) {
22
- const preWriteGuard = args ? deepcopy(args) : {};
23
- delete preWriteGuard.data;
24
- delete preWriteGuard.include;
25
- delete preWriteGuard.select;
26
-
27
- if (operation === 'update') {
28
- preWriteGuard.select = { id: true };
29
- await this.injectSelectForToOneRelation(
30
- model,
31
- preWriteGuard.select,
32
- args.data,
33
- operation
34
- );
35
- }
36
-
37
- await this.processQueryArgs(
38
- model,
39
- preWriteGuard,
40
- operation,
41
- context,
42
- true
43
- );
44
-
45
- const writeArgs = args ? deepcopy(args) : {};
46
- delete writeArgs.include;
47
- delete writeArgs.select;
48
-
49
- const includedModels = new Set<string>([model]);
50
- await this.collectModelsInNestedWrites(
51
- model,
52
- writeArgs.data,
53
- operation,
54
- context,
55
- includedModels,
56
- transactionId
57
- );
58
-
59
- return { preWriteGuard, writeArgs, includedModels };
60
- }
61
-
62
- async injectSelectForToOneRelation(
63
- model: string,
64
- select: any,
65
- updateData: any,
66
- operation: string
67
- ) {
68
- if (!updateData) {
69
- return;
70
- }
71
- for (const [k, v] of Object.entries(updateData)) {
72
- const fieldInfo = await this.service.resolveField(model, k);
73
- if (fieldInfo) {
74
- if (fieldInfo.isArray) {
75
- select[k] = { select: { ...select?.[k]?.select } };
76
- await this.injectSelectForToOneRelation(
77
- fieldInfo.type,
78
- select[k].select,
79
- (v as any)?.update,
80
- operation
81
- );
82
- if (Object.keys(select[k].select).length === 0) {
83
- delete select[k].select;
84
- }
85
- } else {
86
- select[k] = {
87
- select: { ...select?.[k]?.select, id: true },
88
- };
89
- await this.injectSelectForToOneRelation(
90
- fieldInfo.type,
91
- select[k].select,
92
- v,
93
- operation
94
- );
95
- }
96
- }
97
- }
98
- }
99
-
100
- private async collectModelsInNestedWrites(
101
- model: string,
102
- data: any,
103
- operation: PolicyOperationKind,
104
- context: QueryContext,
105
- includedModels: Set<string>,
106
- transactionId: string
107
- ) {
108
- if (!data) {
109
- return;
110
- }
111
-
112
- const arr = Array.isArray(data) ? data : [data];
113
-
114
- for (const item of arr) {
115
- item[TRANSACTION_FIELD_NAME] = transactionId + ':' + operation;
116
-
117
- for (const [k, v] of Object.entries<any>(item)) {
118
- if (!v) {
119
- continue;
120
- }
121
- const fieldInfo = await this.service.resolveField(model, k);
122
- if (fieldInfo) {
123
- includedModels.add(fieldInfo.type);
124
-
125
- for (const [op, payload] of Array.from(
126
- Object.entries<any>(v)
127
- )) {
128
- if (!payload) {
129
- continue;
130
- }
131
- switch (op) {
132
- case 'create':
133
- await this.collectModelsInNestedWrites(
134
- fieldInfo.type,
135
- payload,
136
- 'create',
137
- context,
138
- includedModels,
139
- transactionId
140
- );
141
- break;
142
-
143
- case 'connectOrCreate':
144
- if (payload.create) {
145
- await this.collectModelsInNestedWrites(
146
- fieldInfo.type,
147
- payload.create,
148
- 'create',
149
- context,
150
- includedModels,
151
- transactionId
152
- );
153
- }
154
- break;
155
-
156
- case 'upsert':
157
- if (payload.update) {
158
- await this.collectModelsInNestedWrites(
159
- fieldInfo.type,
160
- payload.update,
161
- 'update',
162
- context,
163
- includedModels,
164
- transactionId
165
- );
166
- }
167
- if (payload.update) {
168
- await this.collectModelsInNestedWrites(
169
- fieldInfo.type,
170
- payload.create,
171
- 'create',
172
- context,
173
- includedModels,
174
- transactionId
175
- );
176
- }
177
- break;
178
-
179
- case 'createMany':
180
- if (
181
- payload.data &&
182
- typeof payload.data[Symbol.iterator] ===
183
- 'function'
184
- ) {
185
- for (const item of payload.data) {
186
- await this.collectModelsInNestedWrites(
187
- fieldInfo.type,
188
- item,
189
- 'create',
190
- context,
191
- includedModels,
192
- transactionId
193
- );
194
- }
195
- break;
196
- }
197
- break;
198
-
199
- case 'update':
200
- if (fieldInfo.isArray) {
201
- const guard =
202
- await this.service.buildQueryGuard(
203
- fieldInfo.type,
204
- 'update',
205
- context
206
- );
207
-
208
- if (
209
- guard &&
210
- Object.keys(guard).length > 0
211
- ) {
212
- payload.where = and(
213
- payload.where,
214
- guard
215
- );
216
- v.updateMany = payload;
217
- delete v.update;
218
- }
219
-
220
- // to-many updates, data is in 'data' field
221
- await this.collectModelsInNestedWrites(
222
- fieldInfo.type,
223
- payload.data,
224
- 'update',
225
- context,
226
- includedModels,
227
- transactionId
228
- );
229
- } else {
230
- // to-one update, payload is data
231
- await this.collectModelsInNestedWrites(
232
- fieldInfo.type,
233
- payload,
234
- 'update',
235
- context,
236
- includedModels,
237
- transactionId
238
- );
239
- }
240
- break;
241
-
242
- case 'updateMany': {
243
- const guard =
244
- await this.service.buildQueryGuard(
245
- fieldInfo.type,
246
- 'update',
247
- context
248
- );
249
-
250
- if (guard && Object.keys(guard).length > 0) {
251
- payload.where = and(payload.where, guard);
252
- v.updateMany = payload;
253
- }
254
- await this.collectModelsInNestedWrites(
255
- fieldInfo.type,
256
- payload,
257
- 'update',
258
- context,
259
- includedModels,
260
- transactionId
261
- );
262
- break;
263
- }
264
-
265
- case 'delete':
266
- case 'deleteMany': {
267
- if (fieldInfo.isArray) {
268
- v[
269
- op === 'delete'
270
- ? 'update'
271
- : 'updateMany'
272
- ] = {
273
- where: payload,
274
- data: {
275
- [TRANSACTION_FIELD_NAME]:
276
- transactionId + ':delete',
277
- },
278
- };
279
- } else {
280
- v[
281
- op === 'delete'
282
- ? 'update'
283
- : 'updateMany'
284
- ] = {
285
- [TRANSACTION_FIELD_NAME]:
286
- transactionId + ':delete',
287
- };
288
- }
289
- delete v[op];
290
- break;
291
- }
292
-
293
- case 'connect':
294
- // noop
295
- break;
296
-
297
- default:
298
- throw new RequestHandlerError(
299
- ServerErrorCode.INVALID_REQUEST_PARAMS,
300
- `Unsupported nested operation '${op}'`
301
- );
302
- }
303
- }
304
- }
305
- }
306
- }
307
- }
308
-
309
- async processQueryArgs(
310
- model: string,
311
- args: any,
312
- operation: PolicyOperationKind,
313
- context: QueryContext,
314
- injectWhere: boolean = true
315
- ) {
316
- const r = args ? deepcopy(args) : {};
317
-
318
- if (injectWhere) {
319
- const guard = await this.service.buildQueryGuard(
320
- model,
321
- operation,
322
- context
323
- );
324
- if (guard) {
325
- if (!r.where) {
326
- r.where = guard;
327
- } else {
328
- r.where = {
329
- AND: [guard, r.where],
330
- };
331
- }
332
- }
333
- }
334
-
335
- if (r.include || r.select) {
336
- if (r.include && r.select) {
337
- throw new RequestHandlerError(
338
- ServerErrorCode.INVALID_REQUEST_PARAMS,
339
- 'Passing both "include" and "select" at the same level of query is not supported'
340
- );
341
- }
342
-
343
- // "include" and "select" are mutually exclusive
344
- const selector = r.include ? 'include' : 'select';
345
- for (const [field, value] of Object.entries(r[selector])) {
346
- const fieldInfo = await this.service.resolveField(model, field);
347
- if (fieldInfo) {
348
- if (fieldInfo.isArray) {
349
- // note that Prisma only allows to attach filter for "to-many" relation
350
- // query, so we need to handle "to-one" filter separately in post-processing
351
- const fieldGuard = await this.processQueryArgs(
352
- fieldInfo.type,
353
- value === true ? {} : value,
354
- operation,
355
- context
356
- );
357
- r[selector][field] = fieldGuard;
358
- } else {
359
- // make sure "id" field is included so that we can do post-process filtering
360
- if (selector === 'select') {
361
- r[selector].id = true;
362
- }
363
- }
364
- }
365
- }
366
- }
367
-
368
- return r;
369
- }
370
-
371
- private async getToOneFieldInfo(
372
- model: string,
373
- fieldName: string,
374
- fieldValue: any
375
- ) {
376
- if (
377
- !fieldValue ||
378
- Array.isArray(fieldValue) ||
379
- typeof fieldValue !== 'object'
380
- ) {
381
- return null;
382
- }
383
-
384
- const fieldInfo = await this.service.resolveField(model, fieldName);
385
- if (!fieldInfo || fieldInfo.isArray) {
386
- return null;
387
- }
388
-
389
- return fieldInfo;
390
- }
391
-
392
- private async collectRelationFields(
393
- model: string,
394
- data: any,
395
- map: Map<string, string[]>
396
- ) {
397
- for (const [fieldName, fieldValue] of Object.entries(data)) {
398
- const val: any = fieldValue;
399
- const fieldInfo = await this.getToOneFieldInfo(
400
- model,
401
- fieldName,
402
- fieldValue
403
- );
404
- if (!fieldInfo) {
405
- continue;
406
- }
407
-
408
- if (!map.has(fieldInfo.type)) {
409
- map.set(fieldInfo.type, []);
410
- }
411
- map.get(fieldInfo.type)!.push(val.id);
412
-
413
- // recurse into field value
414
- this.collectRelationFields(fieldInfo.type, val, map);
415
- }
416
- }
417
-
418
- private async checkIdsAgainstPolicy(
419
- relationFieldMap: Map<string, string[]>,
420
- operation: PolicyOperationKind,
421
- context: QueryContext
422
- ) {
423
- const promises = Array.from(relationFieldMap.entries()).map(
424
- async ([model, ids]) => {
425
- const args = {
426
- select: { id: true },
427
- where: {
428
- id: { in: ids },
429
- },
430
- };
431
-
432
- const processedArgs = await this.processQueryArgs(
433
- model,
434
- args,
435
- operation,
436
- context,
437
- true
438
- );
439
-
440
- const checkedIds: Array<{ id: string }> = await this.service.db[
441
- model
442
- ].findMany(processedArgs);
443
- return [model, checkedIds.map((r) => r.id)] as [
444
- string,
445
- string[]
446
- ];
447
- }
448
- );
449
- return new Map<string, string[]>(await Promise.all(promises));
450
- }
451
-
452
- private async sanitizeData(
453
- model: string,
454
- data: any,
455
- validatedIds: Map<string, string[]>
456
- ): Promise<boolean> {
457
- let deleted = false;
458
- for (const [fieldName, fieldValue] of Object.entries(data)) {
459
- const fieldInfo = await this.getToOneFieldInfo(
460
- model,
461
- fieldName,
462
- fieldValue
463
- );
464
- if (!fieldInfo) {
465
- continue;
466
- }
467
- const fv = fieldValue as { id: string };
468
- const valIds = validatedIds.get(fieldInfo.type);
469
-
470
- if (!valIds || !valIds.includes(fv.id)) {
471
- console.log(
472
- `Deleting field ${fieldName} from ${model}#${data.id}, because field value #${fv.id} failed policy check`
473
- );
474
- delete data[fieldName];
475
- deleted = true;
476
- }
477
-
478
- const r = await this.sanitizeData(
479
- fieldInfo.type,
480
- fieldValue,
481
- validatedIds
482
- );
483
- deleted = deleted || r;
484
- }
485
-
486
- return deleted;
487
- }
488
-
489
- async postProcess(
490
- model: string,
491
- data: any,
492
- operation: PolicyOperationKind,
493
- context: QueryContext
494
- ) {
495
- const relationFieldMap = new Map<string, string[]>();
496
- await this.collectRelationFields(model, data, relationFieldMap);
497
- const validatedIds = await this.checkIdsAgainstPolicy(
498
- relationFieldMap,
499
- operation,
500
- context
501
- );
502
- return this.sanitizeData(model, data, validatedIds);
503
- }
504
- }
@@ -1 +0,0 @@
1
- export { default as DataHandler } from './data/handler';
@@ -1,20 +0,0 @@
1
- import { NextApiRequest, NextApiResponse } from 'next';
2
- import { ServerErrorCode } from '../types';
3
-
4
- export interface RequestHandler {
5
- handle(
6
- req: NextApiRequest,
7
- res: NextApiResponse,
8
- path: string[]
9
- ): Promise<void>;
10
- }
11
-
12
- export class RequestHandlerError extends Error {
13
- constructor(public readonly code: ServerErrorCode, message: string) {
14
- super(message);
15
- }
16
-
17
- toString() {
18
- return `Request handler error: ${this.code}, ${this.message}`;
19
- }
20
- }
@@ -1,3 +0,0 @@
1
- export * from './types';
2
- export * from './request-handler';
3
- export * as request from './request';
@@ -1,27 +0,0 @@
1
- import { NextApiRequest, NextApiResponse } from 'next';
2
- import { DataHandler } from './handler';
3
- import { AuthUser, Service } from './types';
4
-
5
- export type RequestHandlerOptions = {
6
- getServerUser: (
7
- req: NextApiRequest,
8
- res: NextApiResponse
9
- ) => Promise<AuthUser | undefined>;
10
- };
11
-
12
- export function requestHandler<DbClient>(
13
- service: Service<DbClient>,
14
- options: RequestHandlerOptions
15
- ) {
16
- const dataHandler = new DataHandler<DbClient>(service, options);
17
- return async (req: NextApiRequest, res: NextApiResponse) => {
18
- const [route, ...rest] = req.query.path as string[];
19
- switch (route) {
20
- case 'data':
21
- return dataHandler.handle(req, res, rest);
22
-
23
- default:
24
- res.status(404).json({ error: 'Unknown route: ' + route });
25
- }
26
- };
27
- }
@@ -1,101 +0,0 @@
1
- import useSWR, { useSWRConfig } from 'swr';
2
- import type { MutatorCallback, MutatorOptions } from 'swr/dist/types';
3
-
4
- const fetcher = async (url: string, options?: RequestInit) => {
5
- const res = await fetch(url, options);
6
- if (!res.ok) {
7
- const error: Error & { info?: any; status?: number } = new Error(
8
- 'An error occurred while fetching the data.'
9
- );
10
- error.info = await res.json();
11
- error.status = res.status;
12
- throw error;
13
- }
14
- return res.json();
15
- };
16
-
17
- function makeUrl(url: string, args: unknown) {
18
- return args ? url + `?q=${encodeURIComponent(JSON.stringify(args))}` : url;
19
- }
20
-
21
- export function get<Data, Error = any>(url: string | null, args?: unknown) {
22
- return useSWR<Data, Error>(url && makeUrl(url, args), fetcher);
23
- }
24
-
25
- export async function post<Data, Result>(
26
- url: string,
27
- data: Data,
28
- mutate: Mutator
29
- ) {
30
- const r: Result = await fetcher(url, {
31
- method: 'POST',
32
- headers: {
33
- 'content-type': 'application/json',
34
- },
35
- body: JSON.stringify(data),
36
- });
37
- mutate(url, true);
38
- return r;
39
- }
40
-
41
- export async function put<Data, Result>(
42
- url: string,
43
- data: Data,
44
- mutate: Mutator
45
- ) {
46
- const r: Result = await fetcher(url, {
47
- method: 'PUT',
48
- headers: {
49
- 'content-type': 'application/json',
50
- },
51
- body: JSON.stringify(data),
52
- });
53
- mutate(url, true);
54
- return r;
55
- }
56
-
57
- export async function del<Result>(url: string, args: unknown, mutate: Mutator) {
58
- const reqUrl = makeUrl(url, args);
59
- const r: Result = await fetcher(reqUrl, {
60
- method: 'DELETE',
61
- });
62
- const path = url.split('/');
63
- path.pop();
64
- mutate(path.join('/'), true);
65
- return r;
66
- }
67
-
68
- type Mutator = (
69
- key: string,
70
- prefix: boolean,
71
- data?: any | Promise<any> | MutatorCallback,
72
- opts?: boolean | MutatorOptions
73
- ) => Promise<any[]>;
74
-
75
- export function getMutate(): Mutator {
76
- // https://swr.vercel.app/docs/advanced/cache#mutate-multiple-keys-from-regex
77
- const { cache, mutate } = useSWRConfig();
78
- return (
79
- key: string,
80
- prefix: boolean,
81
- data?: any | Promise<any> | MutatorCallback,
82
- opts?: boolean | MutatorOptions
83
- ) => {
84
- if (!prefix) {
85
- return mutate(key, data, opts);
86
- }
87
-
88
- if (!(cache instanceof Map)) {
89
- throw new Error(
90
- 'mutate requires the cache provider to be a Map instance'
91
- );
92
- }
93
-
94
- const keys = Array.from(cache.keys()).filter(
95
- (k) => typeof k === 'string' && k.startsWith(key)
96
- ) as string[];
97
- console.log('Mutating keys:', JSON.stringify(keys));
98
- const mutations = keys.map((key) => mutate(key, data, opts));
99
- return Promise.all(mutations);
100
- };
101
- }
@@ -1,40 +0,0 @@
1
- export interface DbOperations {
2
- findMany(args: any): Promise<any[]>;
3
- findFirst(args: any): Promise<any>;
4
- create(args: any): Promise<any>;
5
- update(args: any): Promise<any>;
6
- delete(args: any): Promise<any>;
7
- }
8
-
9
- export type PolicyKind = 'allow' | 'deny';
10
-
11
- export type PolicyOperationKind = 'create' | 'update' | 'read' | 'delete';
12
-
13
- export type AuthUser = { id: string } & Record<string, any>;
14
-
15
- export type QueryContext = {
16
- user?: AuthUser;
17
- };
18
-
19
- export type FieldInfo = { type: string; isArray: boolean };
20
-
21
- export interface Service<DbClient = any> {
22
- get db(): DbClient;
23
-
24
- resolveField(model: string, field: string): Promise<FieldInfo | undefined>;
25
-
26
- buildQueryGuard(
27
- model: string,
28
- operation: PolicyOperationKind,
29
- context: QueryContext
30
- ): any;
31
- }
32
-
33
- export enum ServerErrorCode {
34
- ENTITY_NOT_FOUND = 'ENTITY_NOT_FOUND',
35
- INVALID_REQUEST_PARAMS = 'INVALID_REQUEST_PARAMS',
36
- DENIED_BY_POLICY = 'DENIED_BY_POLICY',
37
- UNIQUE_CONSTRAINT_VIOLATION = 'UNIQUE_CONSTRAINT_VIOLATION',
38
- REFERENCE_CONSTRAINT_VIOLATION = 'REFERENCE_CONSTRAINT_VIOLATION',
39
- UNKNOWN = 'UNKNOWN',
40
- }