@snowtop/ent 0.1.0-alpha160-test7 → 0.1.0-alpha160
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/query/shared_assoc_test.d.ts +2 -0
- package/core/query/shared_assoc_test.js +804 -0
- package/core/query/shared_test.d.ts +21 -0
- package/core/query/shared_test.js +736 -0
- package/graphql/query/shared_assoc_test.d.ts +1 -0
- package/graphql/query/shared_assoc_test.js +203 -0
- package/package.json +8 -53
- package/dist/package.json +0 -64
- package/src/action/action.ts +0 -330
- package/src/action/executor.ts +0 -453
- package/src/action/experimental_action.ts +0 -277
- package/src/action/index.ts +0 -31
- package/src/action/operations.ts +0 -967
- package/src/action/orchestrator.ts +0 -1527
- package/src/action/privacy.ts +0 -37
- package/src/action/relative_value.ts +0 -242
- package/src/action/transaction.ts +0 -38
- package/src/auth/auth.ts +0 -77
- package/src/auth/index.ts +0 -8
- package/src/core/base.ts +0 -367
- package/src/core/clause.ts +0 -1065
- package/src/core/config.ts +0 -219
- package/src/core/const.ts +0 -5
- package/src/core/context.ts +0 -135
- package/src/core/convert.ts +0 -106
- package/src/core/date.ts +0 -23
- package/src/core/db.ts +0 -498
- package/src/core/ent.ts +0 -1740
- package/src/core/global_schema.ts +0 -49
- package/src/core/loaders/assoc_count_loader.ts +0 -99
- package/src/core/loaders/assoc_edge_loader.ts +0 -250
- package/src/core/loaders/index.ts +0 -12
- package/src/core/loaders/loader.ts +0 -66
- package/src/core/loaders/object_loader.ts +0 -489
- package/src/core/loaders/query_loader.ts +0 -314
- package/src/core/loaders/raw_count_loader.ts +0 -175
- package/src/core/logger.ts +0 -49
- package/src/core/privacy.ts +0 -660
- package/src/core/query/assoc_query.ts +0 -240
- package/src/core/query/custom_clause_query.ts +0 -174
- package/src/core/query/custom_query.ts +0 -302
- package/src/core/query/index.ts +0 -9
- package/src/core/query/query.ts +0 -674
- package/src/core/query_impl.ts +0 -32
- package/src/core/viewer.ts +0 -52
- package/src/ent.code-workspace +0 -73
- package/src/graphql/builtins/connection.ts +0 -25
- package/src/graphql/builtins/edge.ts +0 -16
- package/src/graphql/builtins/node.ts +0 -12
- package/src/graphql/graphql.ts +0 -891
- package/src/graphql/graphql_field_helpers.ts +0 -221
- package/src/graphql/index.ts +0 -42
- package/src/graphql/mutations/union.ts +0 -39
- package/src/graphql/node_resolver.ts +0 -122
- package/src/graphql/query/connection_type.ts +0 -113
- package/src/graphql/query/edge_connection.ts +0 -171
- package/src/graphql/query/page_info.ts +0 -34
- package/src/graphql/query/shared_edge_connection.ts +0 -287
- package/src/graphql/scalars/orderby_direction.ts +0 -13
- package/src/graphql/scalars/time.ts +0 -38
- package/src/imports/dataz/example1/_auth.ts +0 -51
- package/src/imports/dataz/example1/_viewer.ts +0 -35
- package/src/imports/index.ts +0 -213
- package/src/index.ts +0 -145
- package/src/parse_schema/parse.ts +0 -585
- package/src/schema/base_schema.ts +0 -224
- package/src/schema/field.ts +0 -1087
- package/src/schema/index.ts +0 -53
- package/src/schema/json_field.ts +0 -94
- package/src/schema/schema.ts +0 -1028
- package/src/schema/struct_field.ts +0 -234
- package/src/schema/union_field.ts +0 -105
- package/src/scripts/custom_compiler.ts +0 -331
- package/src/scripts/custom_graphql.ts +0 -550
- package/src/scripts/migrate_v0.1.ts +0 -41
- package/src/scripts/move_types.ts +0 -131
- package/src/scripts/read_schema.ts +0 -67
- package/src/setupPackage.js +0 -42
- package/src/testutils/action/complex_schemas.ts +0 -517
- package/src/testutils/builder.ts +0 -422
- package/src/testutils/context/test_context.ts +0 -25
- package/src/testutils/db/fixture.ts +0 -32
- package/src/testutils/db/temp_db.ts +0 -941
- package/src/testutils/db/value.ts +0 -294
- package/src/testutils/db_mock.ts +0 -351
- package/src/testutils/db_time_zone.ts +0 -40
- package/src/testutils/ent-graphql-tests/index.ts +0 -653
- package/src/testutils/fake_comms.ts +0 -50
- package/src/testutils/fake_data/const.ts +0 -64
- package/src/testutils/fake_data/events_query.ts +0 -145
- package/src/testutils/fake_data/fake_contact.ts +0 -150
- package/src/testutils/fake_data/fake_event.ts +0 -150
- package/src/testutils/fake_data/fake_tag.ts +0 -139
- package/src/testutils/fake_data/fake_user.ts +0 -232
- package/src/testutils/fake_data/index.ts +0 -1
- package/src/testutils/fake_data/internal.ts +0 -8
- package/src/testutils/fake_data/tag_query.ts +0 -56
- package/src/testutils/fake_data/test_helpers.ts +0 -388
- package/src/testutils/fake_data/user_query.ts +0 -524
- package/src/testutils/fake_log.ts +0 -52
- package/src/testutils/mock_date.ts +0 -10
- package/src/testutils/mock_log.ts +0 -39
- package/src/testutils/parse_sql.ts +0 -685
- package/src/testutils/test_edge_global_schema.ts +0 -49
- package/src/testutils/write.ts +0 -70
- package/src/tsc/ast.ts +0 -351
- package/src/tsc/compilerOptions.ts +0 -85
- package/src/tsc/move_generated.ts +0 -191
- package/src/tsc/transform.ts +0 -226
- package/src/tsc/transform_action.ts +0 -224
- package/src/tsc/transform_ent.ts +0 -66
- package/src/tsc/transform_schema.ts +0 -546
- package/tsconfig.json +0 -20
- /package/{dist/action → action}/action.d.ts +0 -0
- /package/{dist/action → action}/action.js +0 -0
- /package/{dist/action → action}/executor.d.ts +0 -0
- /package/{dist/action → action}/executor.js +0 -0
- /package/{dist/action → action}/experimental_action.d.ts +0 -0
- /package/{dist/action → action}/experimental_action.js +0 -0
- /package/{dist/action → action}/index.d.ts +0 -0
- /package/{dist/action → action}/index.js +0 -0
- /package/{dist/action → action}/operations.d.ts +0 -0
- /package/{dist/action → action}/operations.js +0 -0
- /package/{dist/action → action}/orchestrator.d.ts +0 -0
- /package/{dist/action → action}/orchestrator.js +0 -0
- /package/{dist/action → action}/privacy.d.ts +0 -0
- /package/{dist/action → action}/privacy.js +0 -0
- /package/{dist/action → action}/relative_value.d.ts +0 -0
- /package/{dist/action → action}/relative_value.js +0 -0
- /package/{dist/action → action}/transaction.d.ts +0 -0
- /package/{dist/action → action}/transaction.js +0 -0
- /package/{dist/auth → auth}/auth.d.ts +0 -0
- /package/{dist/auth → auth}/auth.js +0 -0
- /package/{dist/auth → auth}/index.d.ts +0 -0
- /package/{dist/auth → auth}/index.js +0 -0
- /package/{dist/core → core}/base.d.ts +0 -0
- /package/{dist/core → core}/base.js +0 -0
- /package/{dist/core → core}/clause.d.ts +0 -0
- /package/{dist/core → core}/clause.js +0 -0
- /package/{dist/core → core}/config.d.ts +0 -0
- /package/{dist/core → core}/config.js +0 -0
- /package/{dist/core → core}/const.d.ts +0 -0
- /package/{dist/core → core}/const.js +0 -0
- /package/{dist/core → core}/context.d.ts +0 -0
- /package/{dist/core → core}/context.js +0 -0
- /package/{dist/core → core}/convert.d.ts +0 -0
- /package/{dist/core → core}/convert.js +0 -0
- /package/{dist/core → core}/date.d.ts +0 -0
- /package/{dist/core → core}/date.js +0 -0
- /package/{dist/core → core}/db.d.ts +0 -0
- /package/{dist/core → core}/db.js +0 -0
- /package/{dist/core → core}/ent.d.ts +0 -0
- /package/{dist/core → core}/ent.js +0 -0
- /package/{dist/core → core}/global_schema.d.ts +0 -0
- /package/{dist/core → core}/global_schema.js +0 -0
- /package/{dist/core → core}/loaders/assoc_count_loader.d.ts +0 -0
- /package/{dist/core → core}/loaders/assoc_count_loader.js +0 -0
- /package/{dist/core → core}/loaders/assoc_edge_loader.d.ts +0 -0
- /package/{dist/core → core}/loaders/assoc_edge_loader.js +0 -0
- /package/{dist/core → core}/loaders/index.d.ts +0 -0
- /package/{dist/core → core}/loaders/index.js +0 -0
- /package/{dist/core → core}/loaders/loader.d.ts +0 -0
- /package/{dist/core → core}/loaders/loader.js +0 -0
- /package/{dist/core → core}/loaders/object_loader.d.ts +0 -0
- /package/{dist/core → core}/loaders/object_loader.js +0 -0
- /package/{dist/core → core}/loaders/query_loader.d.ts +0 -0
- /package/{dist/core → core}/loaders/query_loader.js +0 -0
- /package/{dist/core → core}/loaders/raw_count_loader.d.ts +0 -0
- /package/{dist/core → core}/loaders/raw_count_loader.js +0 -0
- /package/{dist/core → core}/logger.d.ts +0 -0
- /package/{dist/core → core}/logger.js +0 -0
- /package/{dist/core → core}/privacy.d.ts +0 -0
- /package/{dist/core → core}/privacy.js +0 -0
- /package/{dist/core → core}/query/assoc_query.d.ts +0 -0
- /package/{dist/core → core}/query/assoc_query.js +0 -0
- /package/{dist/core → core}/query/custom_clause_query.d.ts +0 -0
- /package/{dist/core → core}/query/custom_clause_query.js +0 -0
- /package/{dist/core → core}/query/custom_query.d.ts +0 -0
- /package/{dist/core → core}/query/custom_query.js +0 -0
- /package/{dist/core → core}/query/index.d.ts +0 -0
- /package/{dist/core → core}/query/index.js +0 -0
- /package/{dist/core → core}/query/query.d.ts +0 -0
- /package/{dist/core → core}/query/query.js +0 -0
- /package/{dist/core → core}/query_impl.d.ts +0 -0
- /package/{dist/core → core}/query_impl.js +0 -0
- /package/{dist/core → core}/viewer.d.ts +0 -0
- /package/{dist/core → core}/viewer.js +0 -0
- /package/{dist/graphql → graphql}/builtins/connection.d.ts +0 -0
- /package/{dist/graphql → graphql}/builtins/connection.js +0 -0
- /package/{dist/graphql → graphql}/builtins/edge.d.ts +0 -0
- /package/{dist/graphql → graphql}/builtins/edge.js +0 -0
- /package/{dist/graphql → graphql}/builtins/node.d.ts +0 -0
- /package/{dist/graphql → graphql}/builtins/node.js +0 -0
- /package/{dist/graphql → graphql}/graphql.d.ts +0 -0
- /package/{dist/graphql → graphql}/graphql.js +0 -0
- /package/{dist/graphql → graphql}/graphql_field_helpers.d.ts +0 -0
- /package/{dist/graphql → graphql}/graphql_field_helpers.js +0 -0
- /package/{dist/graphql → graphql}/index.d.ts +0 -0
- /package/{dist/graphql → graphql}/index.js +0 -0
- /package/{dist/graphql → graphql}/mutations/union.d.ts +0 -0
- /package/{dist/graphql → graphql}/mutations/union.js +0 -0
- /package/{dist/graphql → graphql}/node_resolver.d.ts +0 -0
- /package/{dist/graphql → graphql}/node_resolver.js +0 -0
- /package/{dist/graphql → graphql}/query/connection_type.d.ts +0 -0
- /package/{dist/graphql → graphql}/query/connection_type.js +0 -0
- /package/{dist/graphql → graphql}/query/edge_connection.d.ts +0 -0
- /package/{dist/graphql → graphql}/query/edge_connection.js +0 -0
- /package/{dist/graphql → graphql}/query/page_info.d.ts +0 -0
- /package/{dist/graphql → graphql}/query/page_info.js +0 -0
- /package/{dist/graphql → graphql}/query/shared_edge_connection.d.ts +0 -0
- /package/{dist/graphql → graphql}/query/shared_edge_connection.js +0 -0
- /package/{dist/graphql → graphql}/scalars/orderby_direction.d.ts +0 -0
- /package/{dist/graphql → graphql}/scalars/orderby_direction.js +0 -0
- /package/{dist/graphql → graphql}/scalars/time.d.ts +0 -0
- /package/{dist/graphql → graphql}/scalars/time.js +0 -0
- /package/{dist/imports → imports}/dataz/example1/_auth.d.ts +0 -0
- /package/{dist/imports → imports}/dataz/example1/_auth.js +0 -0
- /package/{dist/imports → imports}/dataz/example1/_viewer.d.ts +0 -0
- /package/{dist/imports → imports}/dataz/example1/_viewer.js +0 -0
- /package/{dist/imports → imports}/index.d.ts +0 -0
- /package/{dist/imports → imports}/index.js +0 -0
- /package/{dist/index.d.ts → index.d.ts} +0 -0
- /package/{dist/index.js → index.js} +0 -0
- /package/{dist/parse_schema → parse_schema}/parse.d.ts +0 -0
- /package/{dist/parse_schema → parse_schema}/parse.js +0 -0
- /package/{dist/schema → schema}/base_schema.d.ts +0 -0
- /package/{dist/schema → schema}/base_schema.js +0 -0
- /package/{dist/schema → schema}/field.d.ts +0 -0
- /package/{dist/schema → schema}/field.js +0 -0
- /package/{dist/schema → schema}/index.d.ts +0 -0
- /package/{dist/schema → schema}/index.js +0 -0
- /package/{dist/schema → schema}/json_field.d.ts +0 -0
- /package/{dist/schema → schema}/json_field.js +0 -0
- /package/{dist/schema → schema}/schema.d.ts +0 -0
- /package/{dist/schema → schema}/schema.js +0 -0
- /package/{dist/schema → schema}/struct_field.d.ts +0 -0
- /package/{dist/schema → schema}/struct_field.js +0 -0
- /package/{dist/schema → schema}/union_field.d.ts +0 -0
- /package/{dist/schema → schema}/union_field.js +0 -0
- /package/{dist/scripts → scripts}/custom_compiler.d.ts +0 -0
- /package/{dist/scripts → scripts}/custom_compiler.js +0 -0
- /package/{dist/scripts → scripts}/custom_graphql.d.ts +0 -0
- /package/{dist/scripts → scripts}/custom_graphql.js +0 -0
- /package/{dist/scripts → scripts}/migrate_v0.1.d.ts +0 -0
- /package/{dist/scripts → scripts}/migrate_v0.1.js +0 -0
- /package/{dist/scripts → scripts}/move_types.d.ts +0 -0
- /package/{dist/scripts → scripts}/move_types.js +0 -0
- /package/{dist/scripts → scripts}/read_schema.d.ts +0 -0
- /package/{dist/scripts → scripts}/read_schema.js +0 -0
- /package/{dist/testutils → testutils}/action/complex_schemas.d.ts +0 -0
- /package/{dist/testutils → testutils}/action/complex_schemas.js +0 -0
- /package/{dist/testutils → testutils}/builder.d.ts +0 -0
- /package/{dist/testutils → testutils}/builder.js +0 -0
- /package/{dist/testutils → testutils}/context/test_context.d.ts +0 -0
- /package/{dist/testutils → testutils}/context/test_context.js +0 -0
- /package/{dist/testutils → testutils}/db/fixture.d.ts +0 -0
- /package/{dist/testutils → testutils}/db/fixture.js +0 -0
- /package/{dist/testutils → testutils}/db/temp_db.d.ts +0 -0
- /package/{dist/testutils → testutils}/db/temp_db.js +0 -0
- /package/{dist/testutils → testutils}/db/value.d.ts +0 -0
- /package/{dist/testutils → testutils}/db/value.js +0 -0
- /package/{dist/testutils → testutils}/db_mock.d.ts +0 -0
- /package/{dist/testutils → testutils}/db_mock.js +0 -0
- /package/{dist/testutils → testutils}/db_time_zone.d.ts +0 -0
- /package/{dist/testutils → testutils}/db_time_zone.js +0 -0
- /package/{dist/testutils → testutils}/ent-graphql-tests/index.d.ts +0 -0
- /package/{dist/testutils → testutils}/ent-graphql-tests/index.js +0 -0
- /package/{dist/testutils → testutils}/fake_comms.d.ts +0 -0
- /package/{dist/testutils → testutils}/fake_comms.js +0 -0
- /package/{dist/testutils → testutils}/fake_data/const.d.ts +0 -0
- /package/{dist/testutils → testutils}/fake_data/const.js +0 -0
- /package/{dist/testutils → testutils}/fake_data/events_query.d.ts +0 -0
- /package/{dist/testutils → testutils}/fake_data/events_query.js +0 -0
- /package/{dist/testutils → testutils}/fake_data/fake_contact.d.ts +0 -0
- /package/{dist/testutils → testutils}/fake_data/fake_contact.js +0 -0
- /package/{dist/testutils → testutils}/fake_data/fake_event.d.ts +0 -0
- /package/{dist/testutils → testutils}/fake_data/fake_event.js +0 -0
- /package/{dist/testutils → testutils}/fake_data/fake_tag.d.ts +0 -0
- /package/{dist/testutils → testutils}/fake_data/fake_tag.js +0 -0
- /package/{dist/testutils → testutils}/fake_data/fake_user.d.ts +0 -0
- /package/{dist/testutils → testutils}/fake_data/fake_user.js +0 -0
- /package/{dist/testutils → testutils}/fake_data/index.d.ts +0 -0
- /package/{dist/testutils → testutils}/fake_data/index.js +0 -0
- /package/{dist/testutils → testutils}/fake_data/internal.d.ts +0 -0
- /package/{dist/testutils → testutils}/fake_data/internal.js +0 -0
- /package/{dist/testutils → testutils}/fake_data/tag_query.d.ts +0 -0
- /package/{dist/testutils → testutils}/fake_data/tag_query.js +0 -0
- /package/{dist/testutils → testutils}/fake_data/test_helpers.d.ts +0 -0
- /package/{dist/testutils → testutils}/fake_data/test_helpers.js +0 -0
- /package/{dist/testutils → testutils}/fake_data/user_query.d.ts +0 -0
- /package/{dist/testutils → testutils}/fake_data/user_query.js +0 -0
- /package/{dist/testutils → testutils}/fake_log.d.ts +0 -0
- /package/{dist/testutils → testutils}/fake_log.js +0 -0
- /package/{dist/testutils → testutils}/mock_date.d.ts +0 -0
- /package/{dist/testutils → testutils}/mock_date.js +0 -0
- /package/{dist/testutils → testutils}/mock_log.d.ts +0 -0
- /package/{dist/testutils → testutils}/mock_log.js +0 -0
- /package/{dist/testutils → testutils}/parse_sql.d.ts +0 -0
- /package/{dist/testutils → testutils}/parse_sql.js +0 -0
- /package/{dist/testutils → testutils}/test_edge_global_schema.d.ts +0 -0
- /package/{dist/testutils → testutils}/test_edge_global_schema.js +0 -0
- /package/{dist/testutils → testutils}/write.d.ts +0 -0
- /package/{dist/testutils → testutils}/write.js +0 -0
- /package/{dist/tsc → tsc}/ast.d.ts +0 -0
- /package/{dist/tsc → tsc}/ast.js +0 -0
- /package/{dist/tsc → tsc}/compilerOptions.d.ts +0 -0
- /package/{dist/tsc → tsc}/compilerOptions.js +0 -0
- /package/{dist/tsc → tsc}/move_generated.d.ts +0 -0
- /package/{dist/tsc → tsc}/move_generated.js +0 -0
- /package/{dist/tsc → tsc}/transform.d.ts +0 -0
- /package/{dist/tsc → tsc}/transform.js +0 -0
- /package/{dist/tsc → tsc}/transform_action.d.ts +0 -0
- /package/{dist/tsc → tsc}/transform_action.js +0 -0
- /package/{dist/tsc → tsc}/transform_ent.d.ts +0 -0
- /package/{dist/tsc → tsc}/transform_ent.js +0 -0
- /package/{dist/tsc → tsc}/transform_schema.d.ts +0 -0
- /package/{dist/tsc → tsc}/transform_schema.js +0 -0
package/src/core/ent.ts
DELETED
|
@@ -1,1740 +0,0 @@
|
|
|
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
|
-
}
|