@snowtop/ent 0.1.0-alpha160-test5 → 0.1.0-alpha160-test7

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 (317) hide show
  1. package/dist/package.json +64 -0
  2. package/package.json +52 -7
  3. package/src/action/action.ts +330 -0
  4. package/src/action/executor.ts +453 -0
  5. package/src/action/experimental_action.ts +277 -0
  6. package/src/action/index.ts +31 -0
  7. package/src/action/operations.ts +967 -0
  8. package/src/action/orchestrator.ts +1527 -0
  9. package/src/action/privacy.ts +37 -0
  10. package/src/action/relative_value.ts +242 -0
  11. package/src/action/transaction.ts +38 -0
  12. package/src/auth/auth.ts +77 -0
  13. package/src/auth/index.ts +8 -0
  14. package/src/core/base.ts +367 -0
  15. package/src/core/clause.ts +1065 -0
  16. package/src/core/config.ts +219 -0
  17. package/src/core/const.ts +5 -0
  18. package/src/core/context.ts +135 -0
  19. package/src/core/convert.ts +106 -0
  20. package/src/core/date.ts +23 -0
  21. package/src/core/db.ts +498 -0
  22. package/src/core/ent.ts +1740 -0
  23. package/src/core/global_schema.ts +49 -0
  24. package/src/core/loaders/assoc_count_loader.ts +99 -0
  25. package/src/core/loaders/assoc_edge_loader.ts +250 -0
  26. package/src/core/loaders/index.ts +12 -0
  27. package/src/core/loaders/loader.ts +66 -0
  28. package/src/core/loaders/object_loader.ts +489 -0
  29. package/src/core/loaders/query_loader.ts +314 -0
  30. package/src/core/loaders/raw_count_loader.ts +175 -0
  31. package/src/core/logger.ts +49 -0
  32. package/src/core/privacy.ts +660 -0
  33. package/src/core/query/assoc_query.ts +240 -0
  34. package/src/core/query/custom_clause_query.ts +174 -0
  35. package/src/core/query/custom_query.ts +302 -0
  36. package/src/core/query/index.ts +9 -0
  37. package/src/core/query/query.ts +674 -0
  38. package/src/core/query_impl.ts +32 -0
  39. package/src/core/viewer.ts +52 -0
  40. package/src/ent.code-workspace +73 -0
  41. package/src/graphql/builtins/connection.ts +25 -0
  42. package/src/graphql/builtins/edge.ts +16 -0
  43. package/src/graphql/builtins/node.ts +12 -0
  44. package/src/graphql/graphql.ts +891 -0
  45. package/src/graphql/graphql_field_helpers.ts +221 -0
  46. package/src/graphql/index.ts +42 -0
  47. package/src/graphql/mutations/union.ts +39 -0
  48. package/src/graphql/node_resolver.ts +122 -0
  49. package/src/graphql/query/connection_type.ts +113 -0
  50. package/src/graphql/query/edge_connection.ts +171 -0
  51. package/src/graphql/query/page_info.ts +34 -0
  52. package/src/graphql/query/shared_edge_connection.ts +287 -0
  53. package/src/graphql/scalars/orderby_direction.ts +13 -0
  54. package/src/graphql/scalars/time.ts +38 -0
  55. package/src/imports/dataz/example1/_auth.ts +51 -0
  56. package/src/imports/dataz/example1/_viewer.ts +35 -0
  57. package/src/imports/index.ts +213 -0
  58. package/src/index.ts +145 -0
  59. package/src/parse_schema/parse.ts +585 -0
  60. package/src/schema/base_schema.ts +224 -0
  61. package/src/schema/field.ts +1087 -0
  62. package/src/schema/index.ts +53 -0
  63. package/src/schema/json_field.ts +94 -0
  64. package/src/schema/schema.ts +1028 -0
  65. package/src/schema/struct_field.ts +234 -0
  66. package/src/schema/union_field.ts +105 -0
  67. package/src/scripts/custom_compiler.ts +331 -0
  68. package/src/scripts/custom_graphql.ts +550 -0
  69. package/src/scripts/migrate_v0.1.ts +41 -0
  70. package/src/scripts/move_types.ts +131 -0
  71. package/src/scripts/read_schema.ts +67 -0
  72. package/src/setupPackage.js +42 -0
  73. package/src/testutils/action/complex_schemas.ts +517 -0
  74. package/src/testutils/builder.ts +422 -0
  75. package/src/testutils/context/test_context.ts +25 -0
  76. package/src/testutils/db/fixture.ts +32 -0
  77. package/src/testutils/db/temp_db.ts +941 -0
  78. package/src/testutils/db/value.ts +294 -0
  79. package/src/testutils/db_mock.ts +351 -0
  80. package/src/testutils/db_time_zone.ts +40 -0
  81. package/src/testutils/ent-graphql-tests/index.ts +653 -0
  82. package/src/testutils/fake_comms.ts +50 -0
  83. package/src/testutils/fake_data/const.ts +64 -0
  84. package/src/testutils/fake_data/events_query.ts +145 -0
  85. package/src/testutils/fake_data/fake_contact.ts +150 -0
  86. package/src/testutils/fake_data/fake_event.ts +150 -0
  87. package/src/testutils/fake_data/fake_tag.ts +139 -0
  88. package/src/testutils/fake_data/fake_user.ts +232 -0
  89. package/src/testutils/fake_data/index.ts +1 -0
  90. package/src/testutils/fake_data/internal.ts +8 -0
  91. package/src/testutils/fake_data/tag_query.ts +56 -0
  92. package/src/testutils/fake_data/test_helpers.ts +388 -0
  93. package/src/testutils/fake_data/user_query.ts +524 -0
  94. package/src/testutils/fake_log.ts +52 -0
  95. package/src/testutils/mock_date.ts +10 -0
  96. package/src/testutils/mock_log.ts +39 -0
  97. package/src/testutils/parse_sql.ts +685 -0
  98. package/src/testutils/test_edge_global_schema.ts +49 -0
  99. package/src/testutils/write.ts +70 -0
  100. package/src/tsc/ast.ts +351 -0
  101. package/src/tsc/compilerOptions.ts +85 -0
  102. package/src/tsc/move_generated.ts +191 -0
  103. package/src/tsc/transform.ts +226 -0
  104. package/src/tsc/transform_action.ts +224 -0
  105. package/src/tsc/transform_ent.ts +66 -0
  106. package/src/tsc/transform_schema.ts +546 -0
  107. package/tsconfig.json +20 -0
  108. package/core/query/shared_assoc_test.d.ts +0 -2
  109. package/core/query/shared_assoc_test.js +0 -804
  110. package/core/query/shared_test.d.ts +0 -21
  111. package/core/query/shared_test.js +0 -736
  112. package/graphql/query/shared_assoc_test.d.ts +0 -1
  113. package/graphql/query/shared_assoc_test.js +0 -203
  114. /package/{action → dist/action}/action.d.ts +0 -0
  115. /package/{action → dist/action}/action.js +0 -0
  116. /package/{action → dist/action}/executor.d.ts +0 -0
  117. /package/{action → dist/action}/executor.js +0 -0
  118. /package/{action → dist/action}/experimental_action.d.ts +0 -0
  119. /package/{action → dist/action}/experimental_action.js +0 -0
  120. /package/{action → dist/action}/index.d.ts +0 -0
  121. /package/{action → dist/action}/index.js +0 -0
  122. /package/{action → dist/action}/operations.d.ts +0 -0
  123. /package/{action → dist/action}/operations.js +0 -0
  124. /package/{action → dist/action}/orchestrator.d.ts +0 -0
  125. /package/{action → dist/action}/orchestrator.js +0 -0
  126. /package/{action → dist/action}/privacy.d.ts +0 -0
  127. /package/{action → dist/action}/privacy.js +0 -0
  128. /package/{action → dist/action}/relative_value.d.ts +0 -0
  129. /package/{action → dist/action}/relative_value.js +0 -0
  130. /package/{action → dist/action}/transaction.d.ts +0 -0
  131. /package/{action → dist/action}/transaction.js +0 -0
  132. /package/{auth → dist/auth}/auth.d.ts +0 -0
  133. /package/{auth → dist/auth}/auth.js +0 -0
  134. /package/{auth → dist/auth}/index.d.ts +0 -0
  135. /package/{auth → dist/auth}/index.js +0 -0
  136. /package/{core → dist/core}/base.d.ts +0 -0
  137. /package/{core → dist/core}/base.js +0 -0
  138. /package/{core → dist/core}/clause.d.ts +0 -0
  139. /package/{core → dist/core}/clause.js +0 -0
  140. /package/{core → dist/core}/config.d.ts +0 -0
  141. /package/{core → dist/core}/config.js +0 -0
  142. /package/{core → dist/core}/const.d.ts +0 -0
  143. /package/{core → dist/core}/const.js +0 -0
  144. /package/{core → dist/core}/context.d.ts +0 -0
  145. /package/{core → dist/core}/context.js +0 -0
  146. /package/{core → dist/core}/convert.d.ts +0 -0
  147. /package/{core → dist/core}/convert.js +0 -0
  148. /package/{core → dist/core}/date.d.ts +0 -0
  149. /package/{core → dist/core}/date.js +0 -0
  150. /package/{core → dist/core}/db.d.ts +0 -0
  151. /package/{core → dist/core}/db.js +0 -0
  152. /package/{core → dist/core}/ent.d.ts +0 -0
  153. /package/{core → dist/core}/ent.js +0 -0
  154. /package/{core → dist/core}/global_schema.d.ts +0 -0
  155. /package/{core → dist/core}/global_schema.js +0 -0
  156. /package/{core → dist/core}/loaders/assoc_count_loader.d.ts +0 -0
  157. /package/{core → dist/core}/loaders/assoc_count_loader.js +0 -0
  158. /package/{core → dist/core}/loaders/assoc_edge_loader.d.ts +0 -0
  159. /package/{core → dist/core}/loaders/assoc_edge_loader.js +0 -0
  160. /package/{core → dist/core}/loaders/index.d.ts +0 -0
  161. /package/{core → dist/core}/loaders/index.js +0 -0
  162. /package/{core → dist/core}/loaders/loader.d.ts +0 -0
  163. /package/{core → dist/core}/loaders/loader.js +0 -0
  164. /package/{core → dist/core}/loaders/object_loader.d.ts +0 -0
  165. /package/{core → dist/core}/loaders/object_loader.js +0 -0
  166. /package/{core → dist/core}/loaders/query_loader.d.ts +0 -0
  167. /package/{core → dist/core}/loaders/query_loader.js +0 -0
  168. /package/{core → dist/core}/loaders/raw_count_loader.d.ts +0 -0
  169. /package/{core → dist/core}/loaders/raw_count_loader.js +0 -0
  170. /package/{core → dist/core}/logger.d.ts +0 -0
  171. /package/{core → dist/core}/logger.js +0 -0
  172. /package/{core → dist/core}/privacy.d.ts +0 -0
  173. /package/{core → dist/core}/privacy.js +0 -0
  174. /package/{core → dist/core}/query/assoc_query.d.ts +0 -0
  175. /package/{core → dist/core}/query/assoc_query.js +0 -0
  176. /package/{core → dist/core}/query/custom_clause_query.d.ts +0 -0
  177. /package/{core → dist/core}/query/custom_clause_query.js +0 -0
  178. /package/{core → dist/core}/query/custom_query.d.ts +0 -0
  179. /package/{core → dist/core}/query/custom_query.js +0 -0
  180. /package/{core → dist/core}/query/index.d.ts +0 -0
  181. /package/{core → dist/core}/query/index.js +0 -0
  182. /package/{core → dist/core}/query/query.d.ts +0 -0
  183. /package/{core → dist/core}/query/query.js +0 -0
  184. /package/{core → dist/core}/query_impl.d.ts +0 -0
  185. /package/{core → dist/core}/query_impl.js +0 -0
  186. /package/{core → dist/core}/viewer.d.ts +0 -0
  187. /package/{core → dist/core}/viewer.js +0 -0
  188. /package/{graphql → dist/graphql}/builtins/connection.d.ts +0 -0
  189. /package/{graphql → dist/graphql}/builtins/connection.js +0 -0
  190. /package/{graphql → dist/graphql}/builtins/edge.d.ts +0 -0
  191. /package/{graphql → dist/graphql}/builtins/edge.js +0 -0
  192. /package/{graphql → dist/graphql}/builtins/node.d.ts +0 -0
  193. /package/{graphql → dist/graphql}/builtins/node.js +0 -0
  194. /package/{graphql → dist/graphql}/graphql.d.ts +0 -0
  195. /package/{graphql → dist/graphql}/graphql.js +0 -0
  196. /package/{graphql → dist/graphql}/graphql_field_helpers.d.ts +0 -0
  197. /package/{graphql → dist/graphql}/graphql_field_helpers.js +0 -0
  198. /package/{graphql → dist/graphql}/index.d.ts +0 -0
  199. /package/{graphql → dist/graphql}/index.js +0 -0
  200. /package/{graphql → dist/graphql}/mutations/union.d.ts +0 -0
  201. /package/{graphql → dist/graphql}/mutations/union.js +0 -0
  202. /package/{graphql → dist/graphql}/node_resolver.d.ts +0 -0
  203. /package/{graphql → dist/graphql}/node_resolver.js +0 -0
  204. /package/{graphql → dist/graphql}/query/connection_type.d.ts +0 -0
  205. /package/{graphql → dist/graphql}/query/connection_type.js +0 -0
  206. /package/{graphql → dist/graphql}/query/edge_connection.d.ts +0 -0
  207. /package/{graphql → dist/graphql}/query/edge_connection.js +0 -0
  208. /package/{graphql → dist/graphql}/query/page_info.d.ts +0 -0
  209. /package/{graphql → dist/graphql}/query/page_info.js +0 -0
  210. /package/{graphql → dist/graphql}/query/shared_edge_connection.d.ts +0 -0
  211. /package/{graphql → dist/graphql}/query/shared_edge_connection.js +0 -0
  212. /package/{graphql → dist/graphql}/scalars/orderby_direction.d.ts +0 -0
  213. /package/{graphql → dist/graphql}/scalars/orderby_direction.js +0 -0
  214. /package/{graphql → dist/graphql}/scalars/time.d.ts +0 -0
  215. /package/{graphql → dist/graphql}/scalars/time.js +0 -0
  216. /package/{imports → dist/imports}/dataz/example1/_auth.d.ts +0 -0
  217. /package/{imports → dist/imports}/dataz/example1/_auth.js +0 -0
  218. /package/{imports → dist/imports}/dataz/example1/_viewer.d.ts +0 -0
  219. /package/{imports → dist/imports}/dataz/example1/_viewer.js +0 -0
  220. /package/{imports → dist/imports}/index.d.ts +0 -0
  221. /package/{imports → dist/imports}/index.js +0 -0
  222. /package/{index.d.ts → dist/index.d.ts} +0 -0
  223. /package/{index.js → dist/index.js} +0 -0
  224. /package/{parse_schema → dist/parse_schema}/parse.d.ts +0 -0
  225. /package/{parse_schema → dist/parse_schema}/parse.js +0 -0
  226. /package/{schema → dist/schema}/base_schema.d.ts +0 -0
  227. /package/{schema → dist/schema}/base_schema.js +0 -0
  228. /package/{schema → dist/schema}/field.d.ts +0 -0
  229. /package/{schema → dist/schema}/field.js +0 -0
  230. /package/{schema → dist/schema}/index.d.ts +0 -0
  231. /package/{schema → dist/schema}/index.js +0 -0
  232. /package/{schema → dist/schema}/json_field.d.ts +0 -0
  233. /package/{schema → dist/schema}/json_field.js +0 -0
  234. /package/{schema → dist/schema}/schema.d.ts +0 -0
  235. /package/{schema → dist/schema}/schema.js +0 -0
  236. /package/{schema → dist/schema}/struct_field.d.ts +0 -0
  237. /package/{schema → dist/schema}/struct_field.js +0 -0
  238. /package/{schema → dist/schema}/union_field.d.ts +0 -0
  239. /package/{schema → dist/schema}/union_field.js +0 -0
  240. /package/{scripts → dist/scripts}/custom_compiler.d.ts +0 -0
  241. /package/{scripts → dist/scripts}/custom_compiler.js +0 -0
  242. /package/{scripts → dist/scripts}/custom_graphql.d.ts +0 -0
  243. /package/{scripts → dist/scripts}/custom_graphql.js +0 -0
  244. /package/{scripts → dist/scripts}/migrate_v0.1.d.ts +0 -0
  245. /package/{scripts → dist/scripts}/migrate_v0.1.js +0 -0
  246. /package/{scripts → dist/scripts}/move_types.d.ts +0 -0
  247. /package/{scripts → dist/scripts}/move_types.js +0 -0
  248. /package/{scripts → dist/scripts}/read_schema.d.ts +0 -0
  249. /package/{scripts → dist/scripts}/read_schema.js +0 -0
  250. /package/{testutils → dist/testutils}/action/complex_schemas.d.ts +0 -0
  251. /package/{testutils → dist/testutils}/action/complex_schemas.js +0 -0
  252. /package/{testutils → dist/testutils}/builder.d.ts +0 -0
  253. /package/{testutils → dist/testutils}/builder.js +0 -0
  254. /package/{testutils → dist/testutils}/context/test_context.d.ts +0 -0
  255. /package/{testutils → dist/testutils}/context/test_context.js +0 -0
  256. /package/{testutils → dist/testutils}/db/fixture.d.ts +0 -0
  257. /package/{testutils → dist/testutils}/db/fixture.js +0 -0
  258. /package/{testutils → dist/testutils}/db/temp_db.d.ts +0 -0
  259. /package/{testutils → dist/testutils}/db/temp_db.js +0 -0
  260. /package/{testutils → dist/testutils}/db/value.d.ts +0 -0
  261. /package/{testutils → dist/testutils}/db/value.js +0 -0
  262. /package/{testutils → dist/testutils}/db_mock.d.ts +0 -0
  263. /package/{testutils → dist/testutils}/db_mock.js +0 -0
  264. /package/{testutils → dist/testutils}/db_time_zone.d.ts +0 -0
  265. /package/{testutils → dist/testutils}/db_time_zone.js +0 -0
  266. /package/{testutils → dist/testutils}/ent-graphql-tests/index.d.ts +0 -0
  267. /package/{testutils → dist/testutils}/ent-graphql-tests/index.js +0 -0
  268. /package/{testutils → dist/testutils}/fake_comms.d.ts +0 -0
  269. /package/{testutils → dist/testutils}/fake_comms.js +0 -0
  270. /package/{testutils → dist/testutils}/fake_data/const.d.ts +0 -0
  271. /package/{testutils → dist/testutils}/fake_data/const.js +0 -0
  272. /package/{testutils → dist/testutils}/fake_data/events_query.d.ts +0 -0
  273. /package/{testutils → dist/testutils}/fake_data/events_query.js +0 -0
  274. /package/{testutils → dist/testutils}/fake_data/fake_contact.d.ts +0 -0
  275. /package/{testutils → dist/testutils}/fake_data/fake_contact.js +0 -0
  276. /package/{testutils → dist/testutils}/fake_data/fake_event.d.ts +0 -0
  277. /package/{testutils → dist/testutils}/fake_data/fake_event.js +0 -0
  278. /package/{testutils → dist/testutils}/fake_data/fake_tag.d.ts +0 -0
  279. /package/{testutils → dist/testutils}/fake_data/fake_tag.js +0 -0
  280. /package/{testutils → dist/testutils}/fake_data/fake_user.d.ts +0 -0
  281. /package/{testutils → dist/testutils}/fake_data/fake_user.js +0 -0
  282. /package/{testutils → dist/testutils}/fake_data/index.d.ts +0 -0
  283. /package/{testutils → dist/testutils}/fake_data/index.js +0 -0
  284. /package/{testutils → dist/testutils}/fake_data/internal.d.ts +0 -0
  285. /package/{testutils → dist/testutils}/fake_data/internal.js +0 -0
  286. /package/{testutils → dist/testutils}/fake_data/tag_query.d.ts +0 -0
  287. /package/{testutils → dist/testutils}/fake_data/tag_query.js +0 -0
  288. /package/{testutils → dist/testutils}/fake_data/test_helpers.d.ts +0 -0
  289. /package/{testutils → dist/testutils}/fake_data/test_helpers.js +0 -0
  290. /package/{testutils → dist/testutils}/fake_data/user_query.d.ts +0 -0
  291. /package/{testutils → dist/testutils}/fake_data/user_query.js +0 -0
  292. /package/{testutils → dist/testutils}/fake_log.d.ts +0 -0
  293. /package/{testutils → dist/testutils}/fake_log.js +0 -0
  294. /package/{testutils → dist/testutils}/mock_date.d.ts +0 -0
  295. /package/{testutils → dist/testutils}/mock_date.js +0 -0
  296. /package/{testutils → dist/testutils}/mock_log.d.ts +0 -0
  297. /package/{testutils → dist/testutils}/mock_log.js +0 -0
  298. /package/{testutils → dist/testutils}/parse_sql.d.ts +0 -0
  299. /package/{testutils → dist/testutils}/parse_sql.js +0 -0
  300. /package/{testutils → dist/testutils}/test_edge_global_schema.d.ts +0 -0
  301. /package/{testutils → dist/testutils}/test_edge_global_schema.js +0 -0
  302. /package/{testutils → dist/testutils}/write.d.ts +0 -0
  303. /package/{testutils → dist/testutils}/write.js +0 -0
  304. /package/{tsc → dist/tsc}/ast.d.ts +0 -0
  305. /package/{tsc → dist/tsc}/ast.js +0 -0
  306. /package/{tsc → dist/tsc}/compilerOptions.d.ts +0 -0
  307. /package/{tsc → dist/tsc}/compilerOptions.js +0 -0
  308. /package/{tsc → dist/tsc}/move_generated.d.ts +0 -0
  309. /package/{tsc → dist/tsc}/move_generated.js +0 -0
  310. /package/{tsc → dist/tsc}/transform.d.ts +0 -0
  311. /package/{tsc → dist/tsc}/transform.js +0 -0
  312. /package/{tsc → dist/tsc}/transform_action.d.ts +0 -0
  313. /package/{tsc → dist/tsc}/transform_action.js +0 -0
  314. /package/{tsc → dist/tsc}/transform_ent.d.ts +0 -0
  315. /package/{tsc → dist/tsc}/transform_ent.js +0 -0
  316. /package/{tsc → dist/tsc}/transform_schema.d.ts +0 -0
  317. /package/{tsc → dist/tsc}/transform_schema.js +0 -0
@@ -0,0 +1,1740 @@
1
+ import DB, {
2
+ Dialect,
3
+ Queryer,
4
+ SyncQueryer,
5
+ QueryResult,
6
+ QueryResultRow,
7
+ } from "./db";
8
+ import {
9
+ Viewer,
10
+ Ent,
11
+ ID,
12
+ LoadRowsOptions,
13
+ LoadRowOptions,
14
+ Data,
15
+ DataOptions,
16
+ QueryableDataOptions,
17
+ EditRowOptions,
18
+ LoadEntOptions,
19
+ LoadCustomEntOptions,
20
+ EdgeQueryableDataOptions,
21
+ Context,
22
+ SelectDataOptions,
23
+ CreateRowOptions,
24
+ QueryDataOptions,
25
+ EntConstructor,
26
+ PrivacyPolicy,
27
+ SelectCustomDataOptions,
28
+ PrimableLoader,
29
+ Loader,
30
+ LoaderWithLoadMany,
31
+ } from "./base";
32
+
33
+ import { applyPrivacyPolicy, applyPrivacyPolicyImpl } from "./privacy";
34
+
35
+ import * as clause from "./clause";
36
+ import { log, logEnabled, logTrace } from "./logger";
37
+ import DataLoader from "dataloader";
38
+ import { __getGlobalSchema } from "./global_schema";
39
+ import { OrderBy, getOrderByPhrase } from "./query_impl";
40
+ import { CacheMap } from "./loaders/loader";
41
+
42
+ class entCacheMap<TViewer extends Viewer, TEnt extends Ent<TViewer>> {
43
+ private m = new Map();
44
+ private logEnabled = false;
45
+ constructor(
46
+ private viewer: TViewer,
47
+ private options: LoadEntOptions<TEnt, TViewer>,
48
+ ) {
49
+ this.logEnabled = logEnabled("cache");
50
+ }
51
+
52
+ get(id: ID) {
53
+ const ret = this.m.get(id);
54
+ if (this.logEnabled && ret) {
55
+ const key = getEntKey(this.viewer, id, this.options);
56
+ log("cache", {
57
+ "ent-cache-hit": key,
58
+ });
59
+ }
60
+ return ret;
61
+ }
62
+
63
+ set(key: string, value: any) {
64
+ return this.m.set(key, value);
65
+ }
66
+
67
+ delete(key: string) {
68
+ return this.m.delete(key);
69
+ }
70
+
71
+ clear() {
72
+ return this.m.clear();
73
+ }
74
+ }
75
+
76
+ function createAssocEdgeConfigLoader(options: SelectDataOptions) {
77
+ const loaderOptions: DataLoader.Options<ID, Data | null> = {};
78
+
79
+ // if query logging is enabled, we should log what's happening with loader
80
+ if (logEnabled("query")) {
81
+ loaderOptions.cacheMap = new CacheMap(options);
82
+ }
83
+
84
+ // something here brokwn with strict:true
85
+ return new DataLoader<ID, Data | null>(async (ids: ID[]) => {
86
+ if (!ids.length) {
87
+ return [];
88
+ }
89
+ let col = options.key;
90
+ // defaults to uuid
91
+ let typ = options.keyType || "uuid";
92
+
93
+ const rowOptions: LoadRowOptions = {
94
+ ...options,
95
+ clause: clause.DBTypeIn(col, ids, typ),
96
+ };
97
+
98
+ // TODO is there a better way of doing this?
99
+ // context not needed because we're creating a loader which has its own cache which is being used here
100
+ const nodes = await loadRows(rowOptions);
101
+ let result: (Data | null)[] = ids.map((id) => {
102
+ for (const node of nodes) {
103
+ if (node[col] === id) {
104
+ return node;
105
+ }
106
+ }
107
+ return null;
108
+ });
109
+
110
+ return result;
111
+ }, loaderOptions);
112
+ }
113
+
114
+ // used to wrap errors that would eventually be thrown in ents
115
+ // not an Error because DataLoader automatically rejects that
116
+ class ErrorWrapper {
117
+ constructor(public error: Error) {}
118
+ }
119
+
120
+ // note if storing the result of this in something that checks instanceof Error e.g. DataLoader, we need to check instanceof at that callsite
121
+ export function rowIsError(row: any): row is Error {
122
+ // jest does things that break instanceof checks
123
+ // so we need to check the name as well for native error SqliteError
124
+ return row instanceof Error || row?.constructor?.name === "SqliteError";
125
+ }
126
+
127
+ function createEntLoader<TEnt extends Ent<TViewer>, TViewer extends Viewer>(
128
+ viewer: Viewer,
129
+ options: LoadEntOptions<TEnt, TViewer>,
130
+ map: entCacheMap<TViewer, TEnt>,
131
+ ): DataLoader<ID, TEnt | ErrorWrapper> {
132
+ // share the cache across loaders even if we create a new instance
133
+ const loaderOptions: DataLoader.Options<any, any> = {};
134
+ loaderOptions.cacheMap = map;
135
+
136
+ return new DataLoader(async (ids: ID[]) => {
137
+ if (!ids.length) {
138
+ return [];
139
+ }
140
+
141
+ let result: (TEnt | ErrorWrapper | Error)[] = [];
142
+
143
+ const tableName = options.loaderFactory.options?.tableName;
144
+ const loader = options.loaderFactory.createLoader(viewer.context);
145
+ const rows = await loader.loadMany(ids);
146
+ // this is a loader which should return the same order based on passed-in ids
147
+ // so let's depend on that...
148
+
149
+ for (let idx = 0; idx < rows.length; idx++) {
150
+ const row = rows[idx];
151
+
152
+ // db error
153
+ if (rowIsError(row)) {
154
+ if (row instanceof Error) {
155
+ result[idx] = row;
156
+ } else {
157
+ // @ts-ignore SqliteError
158
+ result[idx] = new Error(row.message);
159
+ }
160
+ continue;
161
+ } else if (!row) {
162
+ if (tableName) {
163
+ result[idx] = new ErrorWrapper(
164
+ new Error(
165
+ `couldn't find row for value ${ids[idx]} in table ${tableName}`,
166
+ ),
167
+ );
168
+ } else {
169
+ result[idx] = new ErrorWrapper(
170
+ new Error(`couldn't find row for value ${ids[idx]}`),
171
+ );
172
+ }
173
+ } else {
174
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
175
+ if (rowIsError(r)) {
176
+ result[idx] = new ErrorWrapper(r);
177
+ } else {
178
+ result[idx] = r;
179
+ }
180
+ }
181
+ }
182
+
183
+ return result;
184
+ }, loaderOptions);
185
+ }
186
+
187
+ class EntLoader<TViewer extends Viewer, TEnt extends Ent<TViewer>>
188
+ implements LoaderWithLoadMany<ID, TEnt | ErrorWrapper | Error>
189
+ {
190
+ private loader: DataLoader<ID, TEnt | ErrorWrapper>;
191
+ private map: entCacheMap<TViewer, TEnt>;
192
+
193
+ constructor(
194
+ private viewer: TViewer,
195
+ private options: LoadEntOptions<TEnt, TViewer>,
196
+ ) {
197
+ this.map = new entCacheMap(viewer, options);
198
+ this.loader = createEntLoader(this.viewer, this.options, this.map);
199
+ }
200
+
201
+ getMap() {
202
+ return this.map;
203
+ }
204
+
205
+ async load(id: ID): Promise<TEnt | ErrorWrapper> {
206
+ return this.loader.load(id);
207
+ }
208
+
209
+ async loadMany(ids: ID[]): Promise<Array<TEnt | ErrorWrapper | Error>> {
210
+ return this.loader.loadMany(ids);
211
+ }
212
+
213
+ prime(id: ID, ent: TEnt | ErrorWrapper) {
214
+ this.loader.prime(id, ent);
215
+ }
216
+
217
+ clear(id: ID) {
218
+ this.loader.clear(id);
219
+ }
220
+
221
+ clearAll() {
222
+ this.loader.clearAll();
223
+ }
224
+ }
225
+
226
+ export function getEntLoader<TViewer extends Viewer, TEnt extends Ent<TViewer>>(
227
+ viewer: TViewer,
228
+ options: LoadEntOptions<TEnt, TViewer>,
229
+ ): EntLoader<TViewer, TEnt> {
230
+ if (!viewer.context?.cache) {
231
+ return new EntLoader(viewer, options);
232
+ }
233
+ const name = `ent-loader:${viewer.instanceKey()}:${
234
+ options.loaderFactory.name
235
+ }`;
236
+
237
+ return viewer.context.cache.getLoaderWithLoadMany(
238
+ name,
239
+ () => new EntLoader(viewer, options),
240
+ ) as EntLoader<TViewer, TEnt>;
241
+ }
242
+
243
+ export function getEntKey<TEnt extends Ent<TViewer>, TViewer extends Viewer>(
244
+ viewer: TViewer,
245
+ id: ID,
246
+ options: LoadEntOptions<TEnt, TViewer>,
247
+ ) {
248
+ return `${viewer.instanceKey()}:${options.loaderFactory.name}:${id}`;
249
+ }
250
+
251
+ export async function loadEnt<
252
+ TEnt extends Ent<TViewer>,
253
+ TViewer extends Viewer,
254
+ >(
255
+ viewer: TViewer,
256
+ id: ID,
257
+ options: LoadEntOptions<TEnt, TViewer>,
258
+ ): Promise<TEnt | null> {
259
+ if (
260
+ typeof id !== "string" &&
261
+ typeof id !== "number" &&
262
+ typeof id !== "bigint"
263
+ ) {
264
+ throw new Error(`invalid id ${id} passed to loadEnt`);
265
+ }
266
+ const r = await getEntLoader(viewer, options).load(id);
267
+ return r instanceof ErrorWrapper ? null : r;
268
+ }
269
+
270
+ async function applyPrivacyPolicyForRowAndStoreInEntLoader<
271
+ TEnt extends Ent<TViewer>,
272
+ TViewer extends Viewer,
273
+ >(
274
+ viewer: TViewer,
275
+ row: Data,
276
+ options: LoadEntOptions<TEnt, TViewer>,
277
+ // can pass in loader when calling this for multi-id cases...
278
+ loader?: EntLoader<TViewer, TEnt>,
279
+ ) {
280
+ if (!loader) {
281
+ loader = getEntLoader(viewer, options);
282
+ }
283
+ // TODO every row.id needs to be audited...
284
+ // https://github.com/lolopinto/ent/issues/1064
285
+ const id = row.id;
286
+
287
+ // we should check the ent loader cache to see if this is already there
288
+ // TODO hmm... we eventually need a custom data-loader for this too so that it's all done correctly if there's a complicated fetch deep down in graphql
289
+ const result = loader.getMap().get(id);
290
+ if (result !== undefined) {
291
+ return result;
292
+ }
293
+
294
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
295
+ if (rowIsError(r)) {
296
+ loader.prime(id, new ErrorWrapper(r));
297
+ return new ErrorWrapper(r);
298
+ } else {
299
+ loader.prime(id, r);
300
+ return r;
301
+ }
302
+ }
303
+
304
+ // this is the same implementation-wise (right now) as loadEnt. it's just clearer that it's not loaded via ID.
305
+ // used for load via email address etc
306
+ export async function loadEntViaKey<
307
+ TEnt extends Ent<TViewer>,
308
+ TViewer extends Viewer,
309
+ >(
310
+ viewer: TViewer,
311
+ key: any,
312
+ options: LoadEntOptions<TEnt, TViewer>,
313
+ ): Promise<TEnt | null> {
314
+ const row = await options.loaderFactory
315
+ .createLoader(viewer.context)
316
+ .load(key);
317
+ if (!row) {
318
+ return null;
319
+ }
320
+
321
+ const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(
322
+ viewer,
323
+ row,
324
+ options,
325
+ );
326
+ return r instanceof ErrorWrapper ? null : r;
327
+ }
328
+
329
+ export async function loadEntX<
330
+ TEnt extends Ent<TViewer>,
331
+ TViewer extends Viewer,
332
+ >(
333
+ viewer: TViewer,
334
+ id: ID,
335
+ options: LoadEntOptions<TEnt, TViewer>,
336
+ ): Promise<TEnt> {
337
+ if (
338
+ typeof id !== "string" &&
339
+ typeof id !== "number" &&
340
+ typeof id !== "bigint"
341
+ ) {
342
+ throw new Error(`invalid id ${id} passed to loadEntX`);
343
+ }
344
+ const r = await getEntLoader(viewer, options).load(id);
345
+ if (r instanceof ErrorWrapper) {
346
+ throw r.error;
347
+ }
348
+ return r;
349
+ }
350
+
351
+ export async function loadEntXViaKey<
352
+ TEnt extends Ent<TViewer>,
353
+ TViewer extends Viewer,
354
+ >(
355
+ viewer: TViewer,
356
+ key: any,
357
+ options: LoadEntOptions<TEnt, TViewer>,
358
+ ): Promise<TEnt> {
359
+ const row = await options.loaderFactory
360
+ .createLoader(viewer.context)
361
+ .load(key);
362
+ if (!row) {
363
+ // todo make this better
364
+ throw new Error(
365
+ `${options.loaderFactory.name}: couldn't find row for value ${key}`,
366
+ );
367
+ }
368
+ const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(
369
+ viewer,
370
+ row,
371
+ options,
372
+ );
373
+ if (r instanceof ErrorWrapper) {
374
+ throw r.error;
375
+ }
376
+ return r;
377
+ }
378
+
379
+ /**
380
+ * @deprecated use loadCustomEnts
381
+ */
382
+ export async function loadEntFromClause<
383
+ TEnt extends Ent<TViewer>,
384
+ TViewer extends Viewer,
385
+ >(
386
+ viewer: TViewer,
387
+ options: LoadEntOptions<TEnt, TViewer>,
388
+ clause: clause.Clause,
389
+ ): Promise<TEnt | null> {
390
+ const rowOptions: LoadRowOptions = {
391
+ ...options,
392
+ clause: clause,
393
+ context: viewer.context,
394
+ };
395
+ const row = await loadRow(rowOptions);
396
+ if (row === null) {
397
+ return null;
398
+ }
399
+ return applyPrivacyPolicyForRow(viewer, options, row);
400
+ }
401
+
402
+ // same as loadEntFromClause
403
+ // only works for ents where primary key is "id"
404
+ // use loadEnt with a loaderFactory if different
405
+ /**
406
+ * @deprecated use loadCustomEnts
407
+ */
408
+ export async function loadEntXFromClause<
409
+ TEnt extends Ent<TViewer>,
410
+ TViewer extends Viewer,
411
+ >(
412
+ viewer: TViewer,
413
+ options: LoadEntOptions<TEnt, TViewer>,
414
+ clause: clause.Clause,
415
+ ): Promise<TEnt> {
416
+ const rowOptions: LoadRowOptions = {
417
+ ...options,
418
+ clause: clause,
419
+ context: viewer.context,
420
+ };
421
+ const row = await loadRowX(rowOptions);
422
+ return await applyPrivacyPolicyForRowX(viewer, options, row);
423
+ }
424
+
425
+ export async function loadEnts<
426
+ TEnt extends Ent<TViewer>,
427
+ TViewer extends Viewer,
428
+ >(
429
+ viewer: TViewer,
430
+ options: LoadEntOptions<TEnt, TViewer>,
431
+ ...ids: ID[]
432
+ ): Promise<Map<ID, TEnt>> {
433
+ if (!ids.length) {
434
+ return new Map();
435
+ }
436
+
437
+ // result
438
+ let m: Map<ID, TEnt> = new Map();
439
+
440
+ const ret = await getEntLoader(viewer, options).loadMany(ids);
441
+ for (const r of ret) {
442
+ if (rowIsError(r)) {
443
+ throw r;
444
+ }
445
+ if (r instanceof ErrorWrapper) {
446
+ continue;
447
+ }
448
+
449
+ m.set(r.id, r);
450
+ }
451
+
452
+ return m;
453
+ }
454
+
455
+ // calls loadEnts and returns the results sorted in the order they were passed in
456
+ // useful for EntQuery and other paths where the order matters
457
+ export async function loadEntsList<
458
+ TEnt extends Ent<TViewer>,
459
+ TViewer extends Viewer,
460
+ >(
461
+ viewer: TViewer,
462
+ options: LoadEntOptions<TEnt, TViewer>,
463
+ ...ids: ID[]
464
+ ): Promise<TEnt[]> {
465
+ const m = await loadEnts(viewer, options, ...ids);
466
+ const result: TEnt[] = [];
467
+ ids.forEach((id) => {
468
+ let ent = m.get(id);
469
+ if (ent) {
470
+ result.push(ent);
471
+ }
472
+ });
473
+ return result;
474
+ }
475
+
476
+ // we return a map here so that any sorting for queries that exist
477
+ // can be done in O(N) time
478
+ /**
479
+ * @deperecated use loadCustomEnts
480
+ */
481
+ export async function loadEntsFromClause<
482
+ TEnt extends Ent<TViewer>,
483
+ TViewer extends Viewer,
484
+ >(
485
+ viewer: TViewer,
486
+ clause: clause.Clause,
487
+ options: LoadEntOptions<TEnt, TViewer>,
488
+ ): Promise<Map<ID, TEnt>> {
489
+ const rowOptions: LoadRowOptions = {
490
+ ...options,
491
+ clause: clause,
492
+ context: viewer.context,
493
+ };
494
+
495
+ const rows = await loadRows(rowOptions);
496
+ return applyPrivacyPolicyForRowsDeprecated(viewer, rows, options);
497
+ }
498
+
499
+ export async function loadCustomEnts<
500
+ TEnt extends Ent<TViewer>,
501
+ TViewer extends Viewer,
502
+ TQueryData extends Data = Data,
503
+ TResultData extends Data = TQueryData,
504
+ TKey = keyof TQueryData,
505
+ >(
506
+ viewer: TViewer,
507
+ options: LoadCustomEntOptions<TEnt, TViewer, TResultData>,
508
+ query: CustomQuery<TQueryData, TKey>,
509
+ ) {
510
+ const rows = await loadCustomData<TQueryData, TResultData, TKey>(
511
+ options,
512
+ query,
513
+ viewer.context,
514
+ );
515
+
516
+ return applyPrivacyPolicyForRows(viewer, rows, options);
517
+ }
518
+
519
+ export interface parameterizedQueryOptions {
520
+ query: string;
521
+ values?: any[];
522
+ logValues?: any[];
523
+ }
524
+
525
+ export type CustomQuery<T extends Data = Data, K = keyof T> =
526
+ | string
527
+ | parameterizedQueryOptions
528
+ | clause.Clause<T, K>
529
+ | QueryDataOptions<T, K>;
530
+
531
+ function isClause<T extends Data = Data, K = keyof T>(
532
+ opts:
533
+ | clause.Clause<T, K>
534
+ | QueryDataOptions<T, K>
535
+ | parameterizedQueryOptions,
536
+ ): opts is clause.Clause<T, K> {
537
+ const cls = opts as clause.Clause<T, K>;
538
+
539
+ return cls.clause !== undefined && cls.values !== undefined;
540
+ }
541
+
542
+ function isParameterizedQuery<T extends Data = Data, K = keyof T>(
543
+ opts: QueryDataOptions<T, K> | parameterizedQueryOptions,
544
+ ): opts is parameterizedQueryOptions {
545
+ return (opts as parameterizedQueryOptions).query !== undefined;
546
+ }
547
+
548
+ /**
549
+ * Note that if there's default read transformations (e.g. soft delete) and a clause is passed in
550
+ * either as Clause or QueryDataOptions without {disableTransformations: true}, the default transformation
551
+ * (e.g. soft delete) is applied.
552
+ *
553
+ * Passing a full SQL string or Paramterized SQL string doesn't apply it and the given string is sent to the
554
+ * database as written.
555
+ *
556
+ * e.g.
557
+ * Foo.loadCustom(opts, 'SELECT * FROM foo') // doesn't change the query
558
+ * Foo.loadCustom(opts, { query: 'SELECT * FROM foo WHERE id = ?', values: [1]}) // doesn't change the query
559
+ * Foo.loadCustom(opts, query.Eq('time', Date.now())) // changes the query
560
+ * Foo.loadCustom(opts, {
561
+ * clause: query.LessEq('time', Date.now()),
562
+ * limit: 100,
563
+ * orderby: 'time',
564
+ * }) // changes the query
565
+ * Foo.loadCustom(opts, {
566
+ * clause: query.LessEq('time', Date.now()),
567
+ * limit: 100,
568
+ * orderby: 'time',
569
+ * disableTransformations: false
570
+ * }) // doesn't change the query
571
+ *
572
+ * For queries that pass in a clause, we batch them with an underlying dataloader so that multiple queries with the same clause
573
+ * or parallel queries with the same clause are batched together.
574
+ *
575
+ * If a raw or parameterized query is passed in, we don't attempt to batch them together and they're executed as is.
576
+ * If you end up with a scenario where you may need to coalesce or batch (non-clause) queries here, you should use some kind of memoization here.
577
+ */
578
+ export async function loadCustomData<
579
+ TQueryData extends Data = Data,
580
+ TResultData extends Data = TQueryData,
581
+ K = keyof TQueryData,
582
+ >(
583
+ options: SelectCustomDataOptions<TResultData>,
584
+ query: CustomQuery<TQueryData, K>,
585
+ context: Context | undefined,
586
+ ): Promise<TResultData[]> {
587
+ const rows = await loadCustomDataImpl<TQueryData, TResultData, K>(
588
+ options,
589
+ query,
590
+ context,
591
+ );
592
+
593
+ // prime the data so that subsequent fetches of the row with this id are a cache hit.
594
+ if (options.prime) {
595
+ const loader = options.loaderFactory.createLoader(context);
596
+ if (isPrimableLoader(loader) && loader.primeAll !== undefined) {
597
+ for (const row of rows) {
598
+ loader.primeAll(row);
599
+ }
600
+ }
601
+ }
602
+ return rows;
603
+ }
604
+
605
+ // NOTE: if you use a raw query or paramterized query with this,
606
+ // you should use `SELECT count(*) as count...`
607
+ export async function loadCustomCount<T extends Data = Data, K = keyof T>(
608
+ options: SelectCustomDataOptions<T>,
609
+ query: CustomQuery<T, K>,
610
+ context: Context | undefined,
611
+ ): Promise<number> {
612
+ // if clause, we'll use the loader and strong typing/coalescing it provides
613
+ if (typeof query !== "string" && isClause(query)) {
614
+ return options.loaderFactory.createCountLoader<K>(context).load(query);
615
+ }
616
+
617
+ const rows = await loadCustomDataImpl(
618
+ {
619
+ ...options,
620
+ fields: ["count(1) as count"],
621
+ },
622
+ query,
623
+ context,
624
+ );
625
+
626
+ if (rows.length) {
627
+ return parseInt(rows[0].count);
628
+ }
629
+ return 0;
630
+ }
631
+
632
+ function isPrimableLoader(
633
+ loader: Loader<any, Data | null>,
634
+ ): loader is PrimableLoader<any, Data> {
635
+ return (loader as PrimableLoader<any, Data>) != undefined;
636
+ }
637
+
638
+ async function loadCustomDataImpl<
639
+ TQueryData extends Data = Data,
640
+ TResultData extends Data = TQueryData,
641
+ K = keyof TQueryData,
642
+ >(
643
+ options: SelectCustomDataOptions<TResultData>,
644
+ query: CustomQuery<TQueryData, K>,
645
+ context: Context | undefined,
646
+ ): Promise<TResultData[]> {
647
+ if (typeof query === "string") {
648
+ // no caching, perform raw query
649
+ return performRawQuery(query, [], []) as Promise<TResultData[]>;
650
+ } else if (isClause(query)) {
651
+ const r = await options.loaderFactory
652
+ .createTypedLoader<TQueryData, TResultData, K>(context)
653
+ .load(query);
654
+ return r as unknown as TResultData[];
655
+ } else if (isParameterizedQuery(query)) {
656
+ // no caching, perform raw query
657
+ return performRawQuery(
658
+ query.query,
659
+ query.values || [],
660
+ query.logValues,
661
+ ) as Promise<TResultData[]>;
662
+ } else {
663
+ // this will have rudimentary caching but nothing crazy
664
+ let cls = query.clause;
665
+ if (!query.disableTransformations) {
666
+ cls = clause.getCombinedClause(
667
+ options.loaderFactory.options,
668
+ query.clause,
669
+ );
670
+ }
671
+ return loadRows({
672
+ ...query,
673
+ ...options,
674
+ context: context,
675
+ // @ts-expect-error
676
+ clause: cls,
677
+ }) as Promise<TResultData[]>;
678
+ }
679
+ }
680
+
681
+ // Derived ents
682
+ // no ent caching
683
+ export async function loadDerivedEnt<
684
+ TEnt extends Ent<TViewer>,
685
+ TViewer extends Viewer,
686
+ >(
687
+ viewer: TViewer,
688
+ data: Data,
689
+ loader: new (viewer: TViewer, data: Data) => TEnt,
690
+ ): Promise<TEnt | null> {
691
+ const ent = new loader(viewer, data);
692
+ const r = await applyPrivacyPolicyForEnt(viewer, ent, data, {
693
+ ent: loader,
694
+ });
695
+ if (rowIsError(r)) {
696
+ return null;
697
+ }
698
+ return r as TEnt | null;
699
+ }
700
+
701
+ // won't have caching yet either
702
+ export async function loadDerivedEntX<
703
+ TEnt extends Ent<TViewer>,
704
+ TViewer extends Viewer,
705
+ >(
706
+ viewer: TViewer,
707
+ data: Data,
708
+ loader: new (viewer: TViewer, data: Data) => TEnt,
709
+ ): Promise<TEnt> {
710
+ const ent = new loader(viewer, data);
711
+ return await applyPrivacyPolicyForEntX(viewer, ent, data, { ent: loader });
712
+ }
713
+
714
+ interface FieldPrivacyOptions<
715
+ TEnt extends Ent,
716
+ TViewer extends Viewer = Viewer,
717
+ > {
718
+ ent: EntConstructor<TEnt, TViewer>;
719
+ fieldPrivacy?: Map<string, PrivacyPolicy>;
720
+ }
721
+
722
+ // everything calls into this two so should be fine
723
+ // TODO is there a smarter way to not instantiate two objects here?
724
+ async function applyPrivacyPolicyForEnt<
725
+ TEnt extends Ent<TViewer>,
726
+ TViewer extends Viewer,
727
+ >(
728
+ viewer: TViewer,
729
+ ent: TEnt,
730
+ data: Data,
731
+ fieldPrivacyOptions: FieldPrivacyOptions<TEnt, TViewer>,
732
+ ): Promise<TEnt | Error> {
733
+ const error = await applyPrivacyPolicyImpl(
734
+ viewer,
735
+ ent.getPrivacyPolicy(),
736
+ ent,
737
+ );
738
+ if (error === null) {
739
+ return doFieldPrivacy(viewer, ent, data, fieldPrivacyOptions);
740
+ }
741
+ return error;
742
+ }
743
+
744
+ async function applyPrivacyPolicyForEntX<
745
+ TEnt extends Ent<TViewer>,
746
+ TViewer extends Viewer,
747
+ >(
748
+ viewer: TViewer,
749
+ ent: TEnt,
750
+ data: Data,
751
+ options: FieldPrivacyOptions<TEnt, TViewer>,
752
+ ): Promise<TEnt> {
753
+ const r = await applyPrivacyPolicyForEnt(viewer, ent, data, options);
754
+ if (rowIsError(r)) {
755
+ throw r;
756
+ }
757
+ if (r === null) {
758
+ throw new Error(`couldn't apply privacyPoliy for ent ${ent.id}`);
759
+ }
760
+ return r;
761
+ }
762
+
763
+ async function doFieldPrivacy<
764
+ TEnt extends Ent<TViewer>,
765
+ TViewer extends Viewer,
766
+ >(
767
+ viewer: TViewer,
768
+ ent: TEnt,
769
+ data: Data,
770
+ options: FieldPrivacyOptions<TEnt, TViewer>,
771
+ ): Promise<TEnt> {
772
+ if (!options.fieldPrivacy) {
773
+ return ent;
774
+ }
775
+ const promises: Promise<void>[] = [];
776
+ let somethingChanged = false;
777
+ const clone = { ...data };
778
+ const origData = {
779
+ ...data,
780
+ };
781
+ for (const [k, policy] of options.fieldPrivacy) {
782
+ const curr = clone[k];
783
+ if (curr === null || curr === undefined) {
784
+ continue;
785
+ }
786
+
787
+ promises.push(
788
+ (async () => {
789
+ // don't do anything if key is null or for some reason missing
790
+ const r = await applyPrivacyPolicy(viewer, policy, ent);
791
+ if (!r) {
792
+ clone[k] = null;
793
+ somethingChanged = true;
794
+ }
795
+ })(),
796
+ );
797
+ }
798
+ await Promise.all(promises);
799
+ if (somethingChanged) {
800
+ // have to create new instance
801
+ const ent = new options.ent(viewer, clone);
802
+ ent.__setRawDBData(origData);
803
+ return ent;
804
+ }
805
+ ent.__setRawDBData(origData);
806
+ return ent;
807
+ }
808
+
809
+ export function logQuery(query: string, logValues: any[]) {
810
+ log("query", {
811
+ query: query,
812
+ values: logValues,
813
+ });
814
+ logTrace();
815
+ }
816
+
817
+ // TODO long term figure out if this API should be exposed
818
+ export async function loadRowX(options: LoadRowOptions): Promise<Data> {
819
+ const result = await loadRow(options);
820
+ if (result == null) {
821
+ // todo make this better
822
+ // make clause have a description
823
+ throw new Error(
824
+ `couldn't find row for query ${options.clause.clause(
825
+ 1,
826
+ )} with values ${options.clause.values()}`,
827
+ );
828
+ }
829
+ return result;
830
+ }
831
+
832
+ // primitive data fetching. called by loaders
833
+ export async function loadRow(options: LoadRowOptions): Promise<Data | null> {
834
+ let cache = options.context?.cache;
835
+ if (cache) {
836
+ let row = cache.getCachedRow(options);
837
+ if (row !== null) {
838
+ return row;
839
+ }
840
+ }
841
+
842
+ const query = buildQuery(options);
843
+ logQuery(query, options.clause.logValues());
844
+ const pool = DB.getInstance().getPool();
845
+
846
+ const res = await pool.query(query, options.clause.values());
847
+ if (res.rowCount != 1) {
848
+ if (res.rowCount > 1) {
849
+ log("error", "got more than one row for query " + query);
850
+ }
851
+ return null;
852
+ }
853
+
854
+ // put the row in the cache...
855
+ if (cache) {
856
+ cache.primeCache(options, res.rows[0]);
857
+ }
858
+
859
+ return res.rows[0];
860
+ }
861
+
862
+ var _logQueryWithError = false;
863
+
864
+ export function ___setLogQueryErrorWithError(val: boolean | undefined) {
865
+ _logQueryWithError = val || false;
866
+ }
867
+
868
+ // this always goes to the db, no cache, nothing
869
+ export async function performRawQuery(
870
+ query: string,
871
+ values: any[],
872
+ logValues?: any[],
873
+ ): Promise<Data[]> {
874
+ const pool = DB.getInstance().getPool();
875
+
876
+ logQuery(query, logValues || []);
877
+ try {
878
+ const res = await pool.queryAll(query, values);
879
+ return res.rows;
880
+ } catch (e) {
881
+ if (_logQueryWithError) {
882
+ const msg = (e as Error).message;
883
+ throw new Error(
884
+ `error \`${msg}\` running query: \`${query}\` with values: \`${logValues}\``,
885
+ );
886
+ }
887
+ throw e;
888
+ }
889
+ }
890
+
891
+ // TODO this should throw, we can't be hiding errors here
892
+ export async function loadRows(options: LoadRowsOptions): Promise<Data[]> {
893
+ let cache = options.context?.cache;
894
+ if (cache) {
895
+ let rows = cache.getCachedRows(options);
896
+ if (rows !== null) {
897
+ return rows;
898
+ }
899
+ }
900
+
901
+ const query = buildQuery(options);
902
+ const r = await performRawQuery(
903
+ query,
904
+ options.clause.values(),
905
+ options.clause.logValues(),
906
+ );
907
+ if (cache) {
908
+ // put the rows in the cache...
909
+ cache.primeCache(options, r);
910
+ }
911
+ return r;
912
+ }
913
+
914
+ // private to ent
915
+ export function buildQuery(options: QueryableDataOptions): string {
916
+ const fields = options.fields.join(", ");
917
+ // always start at 1
918
+ const whereClause = options.clause.clause(1);
919
+ const parts: string[] = [];
920
+ parts.push(`SELECT ${fields} FROM ${options.tableName} WHERE ${whereClause}`);
921
+ if (options.groupby) {
922
+ parts.push(`GROUP BY ${options.groupby}`);
923
+ }
924
+ if (options.orderby) {
925
+ parts.push(`ORDER BY ${getOrderByPhrase(options.orderby)}`);
926
+ }
927
+ if (options.limit) {
928
+ parts.push(`LIMIT ${options.limit}`);
929
+ }
930
+ return parts.join(" ");
931
+ }
932
+
933
+ interface GroupQueryOptions<T extends Data, K = keyof T> {
934
+ tableName: string;
935
+
936
+ // extra clause to join
937
+ clause?: clause.Clause<T, K>;
938
+ groupColumn: K;
939
+ fields: K[];
940
+ values: any[];
941
+ orderby?: OrderBy;
942
+ limit: number;
943
+ }
944
+
945
+ // this is used for queries when we select multiple ids at once
946
+ export function buildGroupQuery<T extends Data = Data, K = keyof T>(
947
+ options: GroupQueryOptions<T, K>,
948
+ ): [string, clause.Clause<T, K>] {
949
+ const fields = [...options.fields, "row_number()"];
950
+
951
+ let cls = clause.In<T, K>(options.groupColumn, ...options.values);
952
+ if (options.clause) {
953
+ cls = clause.And<T, K>(cls, options.clause);
954
+ }
955
+ let orderby = "";
956
+ if (options.orderby) {
957
+ orderby = `ORDER BY ${getOrderByPhrase(options.orderby)}`;
958
+ }
959
+
960
+ // window functions work in sqlite!
961
+ // https://www.sqlite.org/windowfunctions.html
962
+ return [
963
+ `SELECT * FROM (SELECT ${fields.join(",")} OVER (PARTITION BY ${
964
+ options.groupColumn
965
+ } ${orderby}) as row_num FROM ${options.tableName} WHERE ${cls.clause(
966
+ 1,
967
+ )}) t WHERE row_num <= ${options.limit}`,
968
+ cls,
969
+ ];
970
+ }
971
+
972
+ function isSyncQueryer(queryer: Queryer): queryer is SyncQueryer {
973
+ return (queryer as SyncQueryer).execSync !== undefined;
974
+ }
975
+
976
+ async function mutateRow(
977
+ queryer: Queryer,
978
+ query: string,
979
+ values: any[],
980
+ logValues: any[],
981
+ options: DataOptions,
982
+ ) {
983
+ logQuery(query, logValues);
984
+
985
+ let cache = options.context?.cache;
986
+ let res: QueryResult<QueryResultRow>;
987
+ try {
988
+ if (isSyncQueryer(queryer)) {
989
+ res = queryer.execSync(query, values);
990
+ } else {
991
+ res = await queryer.exec(query, values);
992
+ }
993
+ } catch (e) {
994
+ if (_logQueryWithError) {
995
+ const msg = (e as Error).message;
996
+ throw new Error(`error \`${msg}\` running query: \`${query}\``);
997
+ }
998
+ throw e;
999
+ }
1000
+ if (cache) {
1001
+ cache.clearCache();
1002
+ }
1003
+ return res;
1004
+ }
1005
+
1006
+ function mutateRowSync(
1007
+ queryer: SyncQueryer,
1008
+ query: string,
1009
+ values: any[],
1010
+ logValues: any[],
1011
+ options: DataOptions,
1012
+ ) {
1013
+ logQuery(query, logValues);
1014
+
1015
+ let cache = options.context?.cache;
1016
+ try {
1017
+ const res = queryer.execSync(query, values);
1018
+ if (cache) {
1019
+ cache.clearCache();
1020
+ }
1021
+ return res;
1022
+ } catch (e) {
1023
+ if (_logQueryWithError) {
1024
+ const msg = (e as Error).message;
1025
+ throw new Error(`error \`${msg}\` running query: \`${query}\``);
1026
+ }
1027
+ throw e;
1028
+ }
1029
+ }
1030
+
1031
+ export function buildInsertQuery(
1032
+ options: CreateRowOptions,
1033
+ suffix?: string,
1034
+ ): [string, string[], string[]] {
1035
+ let fields: string[] = [];
1036
+ let values: any[] = [];
1037
+ let logValues: any[] = [];
1038
+ let valsString: string[] = [];
1039
+ let idx = 1;
1040
+ const dialect = DB.getDialect();
1041
+ for (const key in options.fields) {
1042
+ fields.push(key);
1043
+ values.push(options.fields[key]);
1044
+ if (options.fieldsToLog) {
1045
+ logValues.push(options.fieldsToLog[key]);
1046
+ }
1047
+ if (dialect === Dialect.Postgres) {
1048
+ valsString.push(`$${idx}`);
1049
+ } else {
1050
+ valsString.push("?");
1051
+ }
1052
+ idx++;
1053
+ }
1054
+
1055
+ const cols = fields.join(", ");
1056
+ const vals = valsString.join(", ");
1057
+
1058
+ let query = `INSERT INTO ${options.tableName} (${cols}) VALUES (${vals})`;
1059
+
1060
+ if (options.onConflict) {
1061
+ let onConflict = "";
1062
+ if (options.onConflict.onConflictConstraint) {
1063
+ onConflict = `ON CONFLICT ON CONSTRAINT ${options.onConflict.onConflictConstraint}`;
1064
+ } else {
1065
+ onConflict = `ON CONFLICT(${options.onConflict.onConflictCols.join(
1066
+ ", ",
1067
+ )})`;
1068
+ }
1069
+ if (options.onConflict.updateCols?.length) {
1070
+ onConflict += ` DO UPDATE SET ${options.onConflict.updateCols
1071
+ .map((f) => `${f} = EXCLUDED.${f}`)
1072
+ .join(", ")}`;
1073
+ } else {
1074
+ onConflict += ` DO NOTHING`;
1075
+ }
1076
+ query = query + " " + onConflict;
1077
+ }
1078
+
1079
+ if (suffix) {
1080
+ query += " " + suffix;
1081
+ }
1082
+
1083
+ return [query, values, logValues];
1084
+ }
1085
+
1086
+ // TODO: these three are not to be exported out of this package
1087
+ // only from this file
1088
+ export async function createRow(
1089
+ queryer: Queryer,
1090
+ options: CreateRowOptions,
1091
+ suffix: string,
1092
+ ): Promise<Data | null> {
1093
+ const [query, values, logValues] = buildInsertQuery(options, suffix);
1094
+
1095
+ const res = await mutateRow(queryer, query, values, logValues, options);
1096
+
1097
+ if (res?.rowCount === 1) {
1098
+ return res.rows[0];
1099
+ }
1100
+ return null;
1101
+ }
1102
+
1103
+ export function createRowSync(
1104
+ queryer: SyncQueryer,
1105
+ options: CreateRowOptions,
1106
+ suffix: string,
1107
+ ): Data | null {
1108
+ const [query, values, logValues] = buildInsertQuery(options, suffix);
1109
+
1110
+ const res = mutateRowSync(queryer, query, values, logValues, options);
1111
+
1112
+ if (res?.rowCount === 1) {
1113
+ return res.rows[0];
1114
+ }
1115
+ return null;
1116
+ }
1117
+
1118
+ export function buildUpdateQuery(
1119
+ options: EditRowOptions,
1120
+ suffix?: string,
1121
+ ): [string, any[], any[]] {
1122
+ let valsString: string[] = [];
1123
+ let values: any[] = [];
1124
+ let logValues: any[] = [];
1125
+ const dialect = DB.getDialect();
1126
+
1127
+ let idx = 1;
1128
+ for (const key in options.fields) {
1129
+ if (options.expressions && options.expressions.has(key)) {
1130
+ const cls = options.expressions.get(key)!;
1131
+ valsString.push(`${key} = ${cls.clause(idx)}`);
1132
+ // TODO need to test a clause with more than one value...
1133
+ const newVals = cls.values();
1134
+ idx += newVals.length;
1135
+ values.push(...newVals);
1136
+ logValues.push(...cls.logValues());
1137
+ } else {
1138
+ const val = options.fields[key];
1139
+ values.push(val);
1140
+ if (options.fieldsToLog) {
1141
+ logValues.push(options.fieldsToLog[key]);
1142
+ }
1143
+ // TODO would be nice to use clause here. need update version of the queries so that
1144
+ // we don't have to handle dialect specifics here
1145
+ // can't use clause because of IS NULL
1146
+ // valsString.push(clause.Eq(key, val).clause(idx));
1147
+ if (dialect === Dialect.Postgres) {
1148
+ valsString.push(`${key} = $${idx}`);
1149
+ } else {
1150
+ valsString.push(`${key} = ?`);
1151
+ }
1152
+ idx++;
1153
+ }
1154
+ }
1155
+
1156
+ const vals = valsString.join(", ");
1157
+
1158
+ let query = `UPDATE ${options.tableName} SET ${vals} WHERE `;
1159
+
1160
+ query = query + options.whereClause.clause(idx);
1161
+ values.push(...options.whereClause.values());
1162
+ if (options.fieldsToLog) {
1163
+ logValues.push(...options.whereClause.logValues());
1164
+ }
1165
+
1166
+ if (suffix) {
1167
+ query = query + " " + suffix;
1168
+ }
1169
+
1170
+ return [query, values, logValues];
1171
+ }
1172
+
1173
+ export async function editRow(
1174
+ queryer: Queryer,
1175
+ options: EditRowOptions,
1176
+ suffix?: string,
1177
+ ): Promise<Data | null> {
1178
+ const [query, values, logValues] = buildUpdateQuery(options, suffix);
1179
+
1180
+ const res = await mutateRow(queryer, query, values, logValues, options);
1181
+
1182
+ if (res?.rowCount == 1) {
1183
+ // for now assume id primary key
1184
+ // TODO make this extensible as needed.
1185
+ let row = res.rows[0];
1186
+ return row;
1187
+ }
1188
+ return null;
1189
+ }
1190
+
1191
+ export function editRowSync(
1192
+ queryer: SyncQueryer,
1193
+ options: EditRowOptions,
1194
+ suffix?: string,
1195
+ ): Data | null {
1196
+ const [query, values, logValues] = buildUpdateQuery(options, suffix);
1197
+
1198
+ const res = mutateRowSync(queryer, query, values, logValues, options);
1199
+
1200
+ if (res?.rowCount == 1) {
1201
+ // for now assume id primary key
1202
+ // TODO make this extensible as needed.
1203
+ let row = res.rows[0];
1204
+ return row;
1205
+ }
1206
+ return null;
1207
+ }
1208
+
1209
+ export async function deleteRows(
1210
+ queryer: Queryer,
1211
+ options: DataOptions,
1212
+ cls: clause.Clause,
1213
+ ): Promise<void> {
1214
+ const query = `DELETE FROM ${options.tableName} WHERE ${cls.clause(1)}`;
1215
+ await mutateRow(queryer, query, cls.values(), cls.logValues(), options);
1216
+ }
1217
+
1218
+ export function deleteRowsSync(
1219
+ queryer: SyncQueryer,
1220
+ options: DataOptions,
1221
+ cls: clause.Clause,
1222
+ ): void {
1223
+ const query = `DELETE FROM ${options.tableName} WHERE ${cls.clause(1)}`;
1224
+ mutateRowSync(queryer, query, cls.values(), cls.logValues(), options);
1225
+ }
1226
+
1227
+ export class AssocEdge {
1228
+ id1: ID;
1229
+ id1Type: string;
1230
+ edgeType: string;
1231
+ id2: ID;
1232
+ id2Type: string;
1233
+ time: Date;
1234
+ data?: string | null;
1235
+
1236
+ private rawData: Data;
1237
+
1238
+ constructor(data: Data) {
1239
+ this.id1 = data.id1;
1240
+ this.id1Type = data.id1_type;
1241
+ this.id2 = data.id2;
1242
+ this.id2Type = data.id2_type;
1243
+ this.edgeType = data.edge_type;
1244
+ this.time = data.time;
1245
+ this.data = data.data;
1246
+ this.rawData = data;
1247
+ }
1248
+
1249
+ __getRawData() {
1250
+ // incase there's extra db fields. useful for tests
1251
+ // in production, a subclass of this should be in use so we won't need this...
1252
+ return this.rawData;
1253
+ }
1254
+
1255
+ getCursor(): string {
1256
+ return getCursor({
1257
+ row: this,
1258
+ col: "id2",
1259
+ });
1260
+ }
1261
+ }
1262
+
1263
+ interface cursorOptions {
1264
+ row: Data;
1265
+ col: string;
1266
+ cursorKey?: string; // used by tests. if cursor is from one column but the key in the name is different e.g. time for assocs and created_at when taken from the object
1267
+ conv?: (any: any) => any;
1268
+ }
1269
+
1270
+ // TODO eventually update this for sortCol time unique keys
1271
+ export function getCursor(opts: cursorOptions) {
1272
+ const { row, col, conv } = opts;
1273
+ // row: Data, col: string, conv?: (any) => any) {
1274
+ if (!row) {
1275
+ throw new Error(`no row passed to getCursor`);
1276
+ }
1277
+ let datum = row[col];
1278
+ if (!datum) {
1279
+ return "";
1280
+ }
1281
+ if (conv) {
1282
+ datum = conv(datum);
1283
+ }
1284
+
1285
+ const cursorKey = opts.cursorKey || col;
1286
+ const str = `${cursorKey}:${datum}`;
1287
+ return Buffer.from(str, "ascii").toString("base64");
1288
+ }
1289
+
1290
+ export class AssocEdgeData {
1291
+ edgeType: string;
1292
+ edgeName: string;
1293
+ symmetricEdge: boolean;
1294
+ inverseEdgeType?: string;
1295
+ edgeTable: string;
1296
+
1297
+ constructor(data: Data) {
1298
+ this.edgeType = data.edge_type;
1299
+ this.edgeName = data.edge_name;
1300
+ this.symmetricEdge = data.symmetric_edge;
1301
+ this.inverseEdgeType = data.inverse_edge_type;
1302
+ this.edgeTable = data.edge_table;
1303
+ }
1304
+ }
1305
+
1306
+ const assocEdgeFields = [
1307
+ "edge_type",
1308
+ "edge_name",
1309
+ "symmetric_edge",
1310
+ "inverse_edge_type",
1311
+ "edge_table",
1312
+ ];
1313
+
1314
+ export const assocEdgeLoader = createAssocEdgeConfigLoader({
1315
+ tableName: "assoc_edge_config",
1316
+ fields: assocEdgeFields,
1317
+ key: "edge_type",
1318
+ keyType: "uuid",
1319
+ });
1320
+
1321
+ // we don't expect assoc_edge_config information to change
1322
+ // so not using ContextCache but just caching it as needed once per server
1323
+
1324
+ export async function loadEdgeData(
1325
+ edgeType: string,
1326
+ ): Promise<AssocEdgeData | null> {
1327
+ const row = await assocEdgeLoader.load(edgeType);
1328
+ if (!row) {
1329
+ return null;
1330
+ }
1331
+ return new AssocEdgeData(row);
1332
+ }
1333
+
1334
+ export async function loadEdgeDatas(
1335
+ ...edgeTypes: string[]
1336
+ ): Promise<Map<string, AssocEdgeData>> {
1337
+ if (!edgeTypes.length) {
1338
+ return new Map();
1339
+ }
1340
+
1341
+ const rows = await assocEdgeLoader.loadMany(edgeTypes);
1342
+ const m = new Map<string, AssocEdgeData>();
1343
+ rows.forEach((row) => {
1344
+ if (!row) {
1345
+ return;
1346
+ }
1347
+ if (rowIsError(row)) {
1348
+ throw row;
1349
+ }
1350
+ m.set(row["edge_type"], new AssocEdgeData(row));
1351
+ });
1352
+ return m;
1353
+ }
1354
+
1355
+ const edgeFields = [
1356
+ "id1",
1357
+ "id1_type",
1358
+ "edge_type",
1359
+ "id2",
1360
+ "id2_type",
1361
+ "time",
1362
+ "data",
1363
+ ];
1364
+
1365
+ export interface AssocEdgeConstructor<T extends AssocEdge> {
1366
+ new (row: Data): T;
1367
+ }
1368
+
1369
+ interface loadEdgesOptions {
1370
+ id1: ID;
1371
+ edgeType: string;
1372
+ context?: Context;
1373
+ queryOptions?: EdgeQueryableDataOptions;
1374
+ }
1375
+
1376
+ interface loadCustomEdgesOptions<T extends AssocEdge> extends loadEdgesOptions {
1377
+ ctr: AssocEdgeConstructor<T>;
1378
+ }
1379
+
1380
+ let defaultLimit = 1000;
1381
+
1382
+ export function setDefaultLimit(limit: number) {
1383
+ defaultLimit = limit;
1384
+ }
1385
+
1386
+ export function getDefaultLimit() {
1387
+ return defaultLimit;
1388
+ }
1389
+
1390
+ function defaultEdgeQueryOptions(
1391
+ id1: ID,
1392
+ edgeType: string,
1393
+ id2?: ID,
1394
+ ): EdgeQueryableDataOptions {
1395
+ let cls = clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType));
1396
+ if (id2) {
1397
+ cls = clause.And(cls, clause.Eq("id2", id2));
1398
+ }
1399
+ return {
1400
+ clause: cls,
1401
+ orderby: [
1402
+ {
1403
+ column: "time",
1404
+ direction: "DESC",
1405
+ },
1406
+ ],
1407
+ limit: defaultLimit,
1408
+ };
1409
+ }
1410
+
1411
+ export async function loadEdges(
1412
+ options: loadEdgesOptions,
1413
+ ): Promise<AssocEdge[]> {
1414
+ return loadCustomEdges({ ...options, ctr: AssocEdge });
1415
+ }
1416
+
1417
+ export function getEdgeClauseAndFields(
1418
+ cls: clause.Clause,
1419
+ options: Pick<loadEdgesOptions, "queryOptions">,
1420
+ ) {
1421
+ let fields = edgeFields;
1422
+
1423
+ const transformEdgeRead = __getGlobalSchema()?.transformEdgeRead;
1424
+ if (transformEdgeRead) {
1425
+ const transformClause = transformEdgeRead();
1426
+ if (!options.queryOptions?.disableTransformations) {
1427
+ cls = clause.And(cls, transformClause);
1428
+ }
1429
+ fields = edgeFields.concat(transformClause.columns() as string[]);
1430
+ }
1431
+ return {
1432
+ cls,
1433
+ fields,
1434
+ };
1435
+ }
1436
+
1437
+ export async function loadCustomEdges<T extends AssocEdge>(
1438
+ options: loadCustomEdgesOptions<T>,
1439
+ ): Promise<T[]> {
1440
+ const {
1441
+ cls: actualClause,
1442
+ fields,
1443
+ defaultOptions,
1444
+ tableName,
1445
+ } = await loadEgesInfo(options);
1446
+
1447
+ const rows = await loadRows({
1448
+ tableName,
1449
+ fields: fields,
1450
+ clause: actualClause,
1451
+ orderby: options.queryOptions?.orderby || defaultOptions.orderby,
1452
+ limit: options.queryOptions?.limit || defaultOptions.limit,
1453
+ context: options.context,
1454
+ });
1455
+ return rows.map((row) => {
1456
+ return new options.ctr(row);
1457
+ });
1458
+ }
1459
+
1460
+ async function loadEgesInfo<T extends AssocEdge>(
1461
+ options: loadCustomEdgesOptions<T>,
1462
+ id2?: ID,
1463
+ ) {
1464
+ const { id1, edgeType } = options;
1465
+ const edgeData = await loadEdgeData(edgeType);
1466
+ if (!edgeData) {
1467
+ throw new Error(`error loading edge data for ${edgeType}`);
1468
+ }
1469
+
1470
+ const defaultOptions = defaultEdgeQueryOptions(id1, edgeType, id2);
1471
+ let cls = defaultOptions.clause!;
1472
+ if (options.queryOptions?.clause) {
1473
+ cls = clause.And(cls, options.queryOptions.clause);
1474
+ }
1475
+
1476
+ return {
1477
+ ...getEdgeClauseAndFields(cls, options),
1478
+ defaultOptions,
1479
+ tableName: edgeData.edgeTable,
1480
+ };
1481
+ }
1482
+
1483
+ export async function loadUniqueEdge(
1484
+ options: loadEdgesOptions,
1485
+ ): Promise<AssocEdge | null> {
1486
+ const { id1, edgeType, context } = options;
1487
+
1488
+ const edgeData = await loadEdgeData(edgeType);
1489
+ if (!edgeData) {
1490
+ throw new Error(`error loading edge data for ${edgeType}`);
1491
+ }
1492
+ const { cls, fields } = getEdgeClauseAndFields(
1493
+ clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)),
1494
+ options,
1495
+ );
1496
+
1497
+ const row = await loadRow({
1498
+ tableName: edgeData.edgeTable,
1499
+ fields: fields,
1500
+ clause: cls,
1501
+ context,
1502
+ });
1503
+ if (!row) {
1504
+ return null;
1505
+ }
1506
+ return new AssocEdge(row);
1507
+ }
1508
+
1509
+ export async function loadUniqueNode<
1510
+ TEnt extends Ent<TViewer>,
1511
+ TViewer extends Viewer,
1512
+ >(
1513
+ viewer: TViewer,
1514
+ id1: ID,
1515
+ edgeType: string,
1516
+ options: LoadEntOptions<TEnt, TViewer>,
1517
+ ): Promise<TEnt | null> {
1518
+ const edge = await loadUniqueEdge({
1519
+ id1,
1520
+ edgeType,
1521
+ context: viewer.context,
1522
+ });
1523
+ if (!edge) {
1524
+ return null;
1525
+ }
1526
+ return await loadEnt(viewer, edge.id2, options);
1527
+ }
1528
+
1529
+ export async function loadRawEdgeCountX(
1530
+ options: loadEdgesOptions,
1531
+ ): Promise<number> {
1532
+ const { id1, edgeType, context } = options;
1533
+ const edgeData = await loadEdgeData(edgeType);
1534
+ if (!edgeData) {
1535
+ throw new Error(`error loading edge data for ${edgeType}`);
1536
+ }
1537
+
1538
+ const { cls } = getEdgeClauseAndFields(
1539
+ clause.And(clause.Eq("id1", id1), clause.Eq("edge_type", edgeType)),
1540
+ options,
1541
+ );
1542
+ const row = await loadRowX({
1543
+ tableName: edgeData.edgeTable,
1544
+ // sqlite needs as count otherwise it returns count(1)
1545
+ fields: ["count(1) as count"],
1546
+ clause: cls,
1547
+ context,
1548
+ });
1549
+ return parseInt(row["count"], 10) || 0;
1550
+ }
1551
+
1552
+ interface loadEdgeForIDOptions<T extends AssocEdge>
1553
+ extends loadCustomEdgesOptions<T> {
1554
+ id2: ID;
1555
+ }
1556
+
1557
+ export async function loadEdgeForID2<T extends AssocEdge>(
1558
+ options: loadEdgeForIDOptions<T>,
1559
+ ): Promise<T | undefined> {
1560
+ const {
1561
+ cls: actualClause,
1562
+ fields,
1563
+ tableName,
1564
+ } = await loadEgesInfo(options, options.id2);
1565
+
1566
+ const row = await loadRow({
1567
+ tableName,
1568
+ fields: fields,
1569
+ clause: actualClause,
1570
+ context: options.context,
1571
+ });
1572
+ if (row) {
1573
+ return new options.ctr(row);
1574
+ }
1575
+ }
1576
+
1577
+ export async function loadNodesByEdge<T extends Ent>(
1578
+ viewer: Viewer,
1579
+ id1: ID,
1580
+ edgeType: string,
1581
+ options: LoadEntOptions<T>,
1582
+ ): Promise<T[]> {
1583
+ // load edges
1584
+ const rows = await loadEdges({
1585
+ id1,
1586
+ edgeType,
1587
+ context: viewer.context,
1588
+ });
1589
+
1590
+ // extract id2s
1591
+ const ids = rows.map((row) => row.id2);
1592
+
1593
+ return loadEntsList(viewer, options, ...ids);
1594
+ }
1595
+
1596
+ export async function applyPrivacyPolicyForRow<
1597
+ TEnt extends Ent<TViewer>,
1598
+ TViewer extends Viewer,
1599
+ >(
1600
+ viewer: TViewer,
1601
+ options: LoadEntOptions<TEnt, TViewer>,
1602
+ row: Data,
1603
+ ): Promise<TEnt | null> {
1604
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
1605
+ return rowIsError(r) ? null : r;
1606
+ }
1607
+
1608
+ async function applyPrivacyPolicyForRowImpl<
1609
+ TEnt extends Ent<TViewer>,
1610
+ TViewer extends Viewer,
1611
+ >(
1612
+ viewer: TViewer,
1613
+ options: LoadEntOptions<TEnt, TViewer>,
1614
+ row: Data,
1615
+ ): Promise<TEnt | Error> {
1616
+ const ent = new options.ent(viewer, row);
1617
+ return applyPrivacyPolicyForEnt(viewer, ent, row, options);
1618
+ }
1619
+
1620
+ async function applyPrivacyPolicyForRowX<
1621
+ TEnt extends Ent<TViewer>,
1622
+ TViewer extends Viewer,
1623
+ >(
1624
+ viewer: TViewer,
1625
+ options: LoadEntOptions<TEnt, TViewer>,
1626
+ row: Data,
1627
+ ): Promise<TEnt> {
1628
+ const ent = new options.ent(viewer, row);
1629
+ return await applyPrivacyPolicyForEntX(viewer, ent, row, options);
1630
+ }
1631
+
1632
+ // deprecated. doesn't use entcache
1633
+ async function applyPrivacyPolicyForRowsDeprecated<
1634
+ TEnt extends Ent<TViewer>,
1635
+ TViewer extends Viewer,
1636
+ >(viewer: TViewer, rows: Data[], options: LoadEntOptions<TEnt, TViewer>) {
1637
+ let m: Map<ID, TEnt> = new Map();
1638
+ // apply privacy logic
1639
+ await Promise.all(
1640
+ rows.map(async (row) => {
1641
+ let privacyEnt = await applyPrivacyPolicyForRow(viewer, options, row);
1642
+ if (privacyEnt) {
1643
+ m.set(privacyEnt.id, privacyEnt);
1644
+ }
1645
+ }),
1646
+ );
1647
+ return m;
1648
+ }
1649
+
1650
+ export async function applyPrivacyPolicyForRows<
1651
+ TEnt extends Ent<TViewer>,
1652
+ TViewer extends Viewer,
1653
+ >(viewer: TViewer, rows: Data[], options: LoadEntOptions<TEnt, TViewer>) {
1654
+ const result: TEnt[] = new Array(rows.length);
1655
+
1656
+ if (!rows.length) {
1657
+ return [];
1658
+ }
1659
+
1660
+ const entLoader = getEntLoader(viewer, options);
1661
+
1662
+ await Promise.all(
1663
+ rows.map(async (row, idx) => {
1664
+ const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(
1665
+ viewer,
1666
+ row,
1667
+ options,
1668
+ entLoader,
1669
+ );
1670
+ if (r instanceof ErrorWrapper) {
1671
+ return;
1672
+ }
1673
+ result[idx] = r;
1674
+ }),
1675
+ );
1676
+ // filter ents that aren't visible because of privacy
1677
+ return result.filter((r) => r !== undefined);
1678
+ }
1679
+
1680
+ // given a viewer, an id pair, and a map of edgeEnum to EdgeType
1681
+ // return the edgeEnum that's set in the group
1682
+ export async function getEdgeTypeInGroup<T extends string>(
1683
+ viewer: Viewer,
1684
+ id1: ID,
1685
+ id2: ID,
1686
+ m: Map<T, string>,
1687
+ ): Promise<[T, AssocEdge] | undefined> {
1688
+ let promises: Promise<[T, AssocEdge | undefined] | undefined>[] = [];
1689
+ const edgeDatas = await loadEdgeDatas(...Array.from(m.values()));
1690
+
1691
+ let tableToEdgeEnumMap = new Map<string, T[]>();
1692
+ for (const [edgeEnum, edgeType] of m) {
1693
+ const edgeData = edgeDatas.get(edgeType);
1694
+ if (!edgeData) {
1695
+ throw new Error(`could not load edge data for '${edgeType}'`);
1696
+ }
1697
+ const l = tableToEdgeEnumMap.get(edgeData.edgeTable) ?? [];
1698
+ l.push(edgeEnum);
1699
+ tableToEdgeEnumMap.set(edgeData.edgeTable, l);
1700
+ }
1701
+ tableToEdgeEnumMap.forEach((edgeEnums, tableName) => {
1702
+ promises.push(
1703
+ (async () => {
1704
+ const edgeTypes = edgeEnums.map((edgeEnum) => m.get(edgeEnum)!);
1705
+
1706
+ const { cls, fields } = getEdgeClauseAndFields(
1707
+ clause.And(
1708
+ clause.Eq("id1", id1),
1709
+ clause.UuidIn("edge_type", edgeTypes),
1710
+ clause.Eq("id2", id2),
1711
+ ),
1712
+ {},
1713
+ );
1714
+
1715
+ const rows = await loadRows({
1716
+ tableName,
1717
+ fields,
1718
+ clause: cls,
1719
+ context: viewer.context,
1720
+ });
1721
+
1722
+ const row = rows[0];
1723
+ if (row) {
1724
+ const edgeType = row.edge_type;
1725
+ for (const [k, v] of m) {
1726
+ if (v === edgeType) {
1727
+ return [k, new AssocEdge(row)];
1728
+ }
1729
+ }
1730
+ }
1731
+ })(),
1732
+ );
1733
+ });
1734
+ const results = await Promise.all(promises);
1735
+ for (const res of results) {
1736
+ if (res && res[1]) {
1737
+ return [res[0], res[1]];
1738
+ }
1739
+ }
1740
+ }