@expo/entity-database-adapter-knex 0.55.0 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/src/AuthorizationResultBasedKnexEntityLoader.d.ts +279 -0
- package/build/src/AuthorizationResultBasedKnexEntityLoader.js +127 -0
- package/build/src/AuthorizationResultBasedKnexEntityLoader.js.map +1 -0
- package/build/src/BasePostgresEntityDatabaseAdapter.d.ts +150 -0
- package/build/src/BasePostgresEntityDatabaseAdapter.js +119 -0
- package/build/src/BasePostgresEntityDatabaseAdapter.js.map +1 -0
- package/build/src/BaseSQLQueryBuilder.d.ts +61 -0
- package/build/src/BaseSQLQueryBuilder.js +87 -0
- package/build/src/BaseSQLQueryBuilder.js.map +1 -0
- package/build/src/EnforcingKnexEntityLoader.d.ts +124 -0
- package/build/src/EnforcingKnexEntityLoader.js +166 -0
- package/build/src/EnforcingKnexEntityLoader.js.map +1 -0
- package/build/src/KnexEntityLoaderFactory.d.ts +25 -0
- package/build/src/KnexEntityLoaderFactory.js +39 -0
- package/build/src/KnexEntityLoaderFactory.js.map +1 -0
- package/build/src/PaginationStrategy.d.ts +30 -0
- package/build/src/PaginationStrategy.js +35 -0
- package/build/src/PaginationStrategy.js.map +1 -0
- package/build/src/PostgresEntity.d.ts +25 -0
- package/build/src/PostgresEntity.js +39 -0
- package/build/src/PostgresEntity.js.map +1 -0
- package/build/src/PostgresEntityDatabaseAdapter.d.ts +12 -5
- package/build/src/PostgresEntityDatabaseAdapter.js +32 -11
- package/build/src/PostgresEntityDatabaseAdapter.js.map +1 -1
- package/build/src/PostgresEntityDatabaseAdapterProvider.d.ts +9 -0
- package/build/src/PostgresEntityDatabaseAdapterProvider.js +5 -1
- package/build/src/PostgresEntityDatabaseAdapterProvider.js.map +1 -1
- package/build/src/ReadonlyPostgresEntity.d.ts +25 -0
- package/build/src/ReadonlyPostgresEntity.js +39 -0
- package/build/src/ReadonlyPostgresEntity.js.map +1 -0
- package/build/src/SQLOperator.d.ts +261 -0
- package/build/src/SQLOperator.js +464 -0
- package/build/src/SQLOperator.js.map +1 -0
- package/build/src/index.d.ts +15 -0
- package/build/src/index.js +15 -0
- package/build/src/index.js.map +1 -1
- package/build/src/internal/EntityKnexDataManager.d.ts +147 -0
- package/build/src/internal/EntityKnexDataManager.js +453 -0
- package/build/src/internal/EntityKnexDataManager.js.map +1 -0
- package/build/src/internal/getKnexDataManager.d.ts +3 -0
- package/build/src/internal/getKnexDataManager.js +19 -0
- package/build/src/internal/getKnexDataManager.js.map +1 -0
- package/build/src/internal/getKnexEntityLoaderFactory.d.ts +3 -0
- package/build/src/internal/getKnexEntityLoaderFactory.js +11 -0
- package/build/src/internal/getKnexEntityLoaderFactory.js.map +1 -0
- package/build/src/internal/utilityTypes.d.ts +5 -0
- package/build/src/internal/utilityTypes.js +5 -0
- package/build/src/internal/utilityTypes.js.map +1 -0
- package/build/src/internal/weakMaps.d.ts +9 -0
- package/build/src/internal/weakMaps.js +20 -0
- package/build/src/internal/weakMaps.js.map +1 -0
- package/build/src/knexLoader.d.ts +18 -0
- package/build/src/knexLoader.js +31 -0
- package/build/src/knexLoader.js.map +1 -0
- package/package.json +6 -5
- package/src/AuthorizationResultBasedKnexEntityLoader.ts +538 -0
- package/src/BasePostgresEntityDatabaseAdapter.ts +317 -0
- package/src/BaseSQLQueryBuilder.ts +114 -0
- package/src/EnforcingKnexEntityLoader.ts +271 -0
- package/src/KnexEntityLoaderFactory.ts +130 -0
- package/src/PaginationStrategy.ts +32 -0
- package/src/PostgresEntity.ts +118 -0
- package/src/PostgresEntityDatabaseAdapter.ts +78 -24
- package/src/PostgresEntityDatabaseAdapterProvider.ts +11 -1
- package/src/ReadonlyPostgresEntity.ts +115 -0
- package/src/SQLOperator.ts +603 -0
- package/src/__integration-tests__/EntityCreationUtils-test.ts +25 -31
- package/src/__integration-tests__/PostgresEntityIntegration-test.ts +3192 -330
- package/src/__integration-tests__/PostgresEntityQueryContextProvider-test.ts +7 -7
- package/src/__testfixtures__/PostgresTestEntity.ts +17 -3
- package/src/__tests__/AuthorizationResultBasedKnexEntityLoader-test.ts +1167 -0
- package/src/__tests__/BasePostgresEntityDatabaseAdapter-test.ts +160 -0
- package/src/__tests__/EnforcingKnexEntityLoader-test.ts +384 -0
- package/src/__tests__/EntityFields-test.ts +1 -1
- package/src/__tests__/PostgresEntity-test.ts +172 -0
- package/src/__tests__/ReadonlyEntity-test.ts +32 -0
- package/src/__tests__/SQLOperator-test.ts +831 -0
- package/src/__tests__/fixtures/StubPostgresDatabaseAdapter.ts +302 -0
- package/src/__tests__/fixtures/StubPostgresDatabaseAdapterProvider.ts +17 -0
- package/src/__tests__/fixtures/TestEntity.ts +131 -0
- package/src/__tests__/fixtures/TestPaginationEntity.ts +107 -0
- package/src/__tests__/fixtures/createUnitTestPostgresEntityCompanionProvider.ts +42 -0
- package/src/index.ts +15 -0
- package/src/internal/EntityKnexDataManager.ts +832 -0
- package/src/internal/__tests__/EntityKnexDataManager-test.ts +378 -0
- package/src/internal/__tests__/weakMaps-test.ts +25 -0
- package/src/internal/getKnexDataManager.ts +43 -0
- package/src/internal/getKnexEntityLoaderFactory.ts +60 -0
- package/src/internal/utilityTypes.ts +11 -0
- package/src/internal/weakMaps.ts +19 -0
- package/src/knexLoader.ts +110 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EntityCompanion,
|
|
3
|
+
EntityConstructionUtils,
|
|
4
|
+
EntityPrivacyPolicy,
|
|
5
|
+
EntityPrivacyPolicyEvaluationContext,
|
|
6
|
+
EntityQueryContext,
|
|
7
|
+
ReadonlyEntity,
|
|
8
|
+
ViewerContext,
|
|
9
|
+
IEntityMetricsAdapter,
|
|
10
|
+
} from '@expo/entity';
|
|
11
|
+
|
|
12
|
+
import { AuthorizationResultBasedKnexEntityLoader } from './AuthorizationResultBasedKnexEntityLoader';
|
|
13
|
+
import { EnforcingKnexEntityLoader } from './EnforcingKnexEntityLoader';
|
|
14
|
+
import { EntityKnexDataManager } from './internal/EntityKnexDataManager';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The primary entry point for loading entities via knex queries (non-data-loader methods).
|
|
18
|
+
*/
|
|
19
|
+
export class KnexEntityLoaderFactory<
|
|
20
|
+
TFields extends Record<string, any>,
|
|
21
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
22
|
+
TViewerContext extends ViewerContext,
|
|
23
|
+
TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
|
|
24
|
+
TPrivacyPolicy extends EntityPrivacyPolicy<
|
|
25
|
+
TFields,
|
|
26
|
+
TIDField,
|
|
27
|
+
TViewerContext,
|
|
28
|
+
TEntity,
|
|
29
|
+
TSelectedFields
|
|
30
|
+
>,
|
|
31
|
+
TSelectedFields extends keyof TFields,
|
|
32
|
+
> {
|
|
33
|
+
constructor(
|
|
34
|
+
private readonly entityCompanion: EntityCompanion<
|
|
35
|
+
TFields,
|
|
36
|
+
TIDField,
|
|
37
|
+
TViewerContext,
|
|
38
|
+
TEntity,
|
|
39
|
+
TPrivacyPolicy,
|
|
40
|
+
TSelectedFields
|
|
41
|
+
>,
|
|
42
|
+
private readonly knexDataManager: EntityKnexDataManager<TFields, TIDField>,
|
|
43
|
+
protected readonly metricsAdapter: IEntityMetricsAdapter,
|
|
44
|
+
) {}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Vend knex loader for loading an entity in a given query context.
|
|
48
|
+
* @param viewerContext - viewer context of loading user
|
|
49
|
+
* @param queryContext - query context in which to perform the load
|
|
50
|
+
*/
|
|
51
|
+
forLoad(
|
|
52
|
+
viewerContext: TViewerContext,
|
|
53
|
+
queryContext: EntityQueryContext,
|
|
54
|
+
privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
55
|
+
TFields,
|
|
56
|
+
TIDField,
|
|
57
|
+
TViewerContext,
|
|
58
|
+
TEntity,
|
|
59
|
+
TSelectedFields
|
|
60
|
+
>,
|
|
61
|
+
): AuthorizationResultBasedKnexEntityLoader<
|
|
62
|
+
TFields,
|
|
63
|
+
TIDField,
|
|
64
|
+
TViewerContext,
|
|
65
|
+
TEntity,
|
|
66
|
+
TPrivacyPolicy,
|
|
67
|
+
TSelectedFields
|
|
68
|
+
> {
|
|
69
|
+
const constructionUtils = new EntityConstructionUtils(
|
|
70
|
+
viewerContext,
|
|
71
|
+
queryContext,
|
|
72
|
+
privacyPolicyEvaluationContext,
|
|
73
|
+
this.entityCompanion.entityCompanionDefinition.entityConfiguration,
|
|
74
|
+
this.entityCompanion.entityCompanionDefinition.entityClass,
|
|
75
|
+
this.entityCompanion.entityCompanionDefinition.entitySelectedFields,
|
|
76
|
+
this.entityCompanion.privacyPolicy,
|
|
77
|
+
this.metricsAdapter,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return new AuthorizationResultBasedKnexEntityLoader(
|
|
81
|
+
queryContext,
|
|
82
|
+
this.knexDataManager,
|
|
83
|
+
this.metricsAdapter,
|
|
84
|
+
constructionUtils,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Vend enforcing knex loader for loading an entity in a given query context.
|
|
90
|
+
* @param viewerContext - viewer context of loading user
|
|
91
|
+
* @param queryContext - query context in which to perform the load
|
|
92
|
+
*/
|
|
93
|
+
forLoadEnforcing(
|
|
94
|
+
viewerContext: TViewerContext,
|
|
95
|
+
queryContext: EntityQueryContext,
|
|
96
|
+
privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
|
|
97
|
+
TFields,
|
|
98
|
+
TIDField,
|
|
99
|
+
TViewerContext,
|
|
100
|
+
TEntity,
|
|
101
|
+
TSelectedFields
|
|
102
|
+
>,
|
|
103
|
+
): EnforcingKnexEntityLoader<
|
|
104
|
+
TFields,
|
|
105
|
+
TIDField,
|
|
106
|
+
TViewerContext,
|
|
107
|
+
TEntity,
|
|
108
|
+
TPrivacyPolicy,
|
|
109
|
+
TSelectedFields
|
|
110
|
+
> {
|
|
111
|
+
const constructionUtils = new EntityConstructionUtils(
|
|
112
|
+
viewerContext,
|
|
113
|
+
queryContext,
|
|
114
|
+
privacyPolicyEvaluationContext,
|
|
115
|
+
this.entityCompanion.entityCompanionDefinition.entityConfiguration,
|
|
116
|
+
this.entityCompanion.entityCompanionDefinition.entityClass,
|
|
117
|
+
this.entityCompanion.entityCompanionDefinition.entitySelectedFields,
|
|
118
|
+
this.entityCompanion.privacyPolicy,
|
|
119
|
+
this.metricsAdapter,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return new EnforcingKnexEntityLoader(
|
|
123
|
+
this.forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext),
|
|
124
|
+
queryContext,
|
|
125
|
+
this.knexDataManager,
|
|
126
|
+
this.metricsAdapter,
|
|
127
|
+
constructionUtils,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search strategy for SQL-based pagination.
|
|
3
|
+
*/
|
|
4
|
+
export enum PaginationStrategy {
|
|
5
|
+
/**
|
|
6
|
+
* Standard pagination with ORDER BY. Results are ordered by the specified orderBy fields, with ID field automatically included for stable pagination if not already present.
|
|
7
|
+
*/
|
|
8
|
+
STANDARD = 'standard',
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Case-insensitive pattern matching search using SQL ILIKE operator.
|
|
12
|
+
* Results are ordered by the fields being searched within in the order specified, then by ID for tie-breaking and stable pagination.
|
|
13
|
+
*/
|
|
14
|
+
ILIKE_SEARCH = 'ilike-search',
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Similarity search using PostgreSQL trigram similarity. Results are ordered by exact match priority, then by similarity score, then by specified extra order by fields if provided, then by ID for tie-breaking and stable pagination.
|
|
18
|
+
*
|
|
19
|
+
* Performance considerations:
|
|
20
|
+
* - Trigram search can be significantly slower than ILIKE search, especially on large datasets without appropriate indexes.
|
|
21
|
+
* - Consider using ILIKE search for smaller datasets or when exact substring matching is sufficient
|
|
22
|
+
* - For larger datasets, ensure proper indexing or consider dedicated full-text search solutions.
|
|
23
|
+
* - For optimal performance, create GIN or GIST indexes on searchable columns:
|
|
24
|
+
* ```sql
|
|
25
|
+
* CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
|
26
|
+
* CREATE INDEX idx_table_field_trigram ON table_name USING gin(field_name gin_trgm_ops);
|
|
27
|
+
* -- Or for multiple columns:
|
|
28
|
+
* CREATE INDEX idx_table_search ON table_name USING gin((field1 || ' ' || field2) gin_trgm_ops);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
TRIGRAM_SEARCH = 'trigram',
|
|
32
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Entity,
|
|
3
|
+
EntityPrivacyPolicy,
|
|
4
|
+
EntityQueryContext,
|
|
5
|
+
IEntityClass,
|
|
6
|
+
ReadonlyEntity,
|
|
7
|
+
ViewerContext,
|
|
8
|
+
} from '@expo/entity';
|
|
9
|
+
|
|
10
|
+
import { AuthorizationResultBasedKnexEntityLoader } from './AuthorizationResultBasedKnexEntityLoader';
|
|
11
|
+
import { EnforcingKnexEntityLoader } from './EnforcingKnexEntityLoader';
|
|
12
|
+
import {
|
|
13
|
+
knexLoader as knexLoaderFn,
|
|
14
|
+
knexLoaderWithAuthorizationResults as knexLoaderWithAuthorizationResultsFn,
|
|
15
|
+
} from './knexLoader';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Abstract base class for mutable entities backed by Postgres.
|
|
19
|
+
* Provides `knexLoader` and `knexLoaderWithAuthorizationResults` as inherited static methods,
|
|
20
|
+
* in addition to the mutation methods inherited from `Entity`.
|
|
21
|
+
*/
|
|
22
|
+
export abstract class PostgresEntity<
|
|
23
|
+
TFields extends Record<string, any>,
|
|
24
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
25
|
+
TViewerContext extends ViewerContext,
|
|
26
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
27
|
+
> extends Entity<TFields, TIDField, TViewerContext, TSelectedFields> {
|
|
28
|
+
/**
|
|
29
|
+
* Vend knex loader for loading entities via knex-specific methods in a given query context.
|
|
30
|
+
*
|
|
31
|
+
* @param viewerContext - viewer context of loading user
|
|
32
|
+
* @param queryContext - query context in which to perform the load
|
|
33
|
+
*/
|
|
34
|
+
static knexLoader<
|
|
35
|
+
TMFields extends object,
|
|
36
|
+
TMIDField extends keyof NonNullable<Pick<TMFields, TMSelectedFields>>,
|
|
37
|
+
TMViewerContext extends ViewerContext,
|
|
38
|
+
TMViewerContext2 extends TMViewerContext,
|
|
39
|
+
TMEntity extends ReadonlyEntity<TMFields, TMIDField, TMViewerContext, TMSelectedFields>,
|
|
40
|
+
TMPrivacyPolicy extends EntityPrivacyPolicy<
|
|
41
|
+
TMFields,
|
|
42
|
+
TMIDField,
|
|
43
|
+
TMViewerContext,
|
|
44
|
+
TMEntity,
|
|
45
|
+
TMSelectedFields
|
|
46
|
+
>,
|
|
47
|
+
TMSelectedFields extends keyof TMFields = keyof TMFields,
|
|
48
|
+
>(
|
|
49
|
+
this: IEntityClass<
|
|
50
|
+
TMFields,
|
|
51
|
+
TMIDField,
|
|
52
|
+
TMViewerContext,
|
|
53
|
+
TMEntity,
|
|
54
|
+
TMPrivacyPolicy,
|
|
55
|
+
TMSelectedFields
|
|
56
|
+
>,
|
|
57
|
+
viewerContext: TMViewerContext2,
|
|
58
|
+
queryContext: EntityQueryContext = viewerContext
|
|
59
|
+
.getViewerScopedEntityCompanionForClass(this)
|
|
60
|
+
.getQueryContextProvider()
|
|
61
|
+
.getQueryContext(),
|
|
62
|
+
): EnforcingKnexEntityLoader<
|
|
63
|
+
TMFields,
|
|
64
|
+
TMIDField,
|
|
65
|
+
TMViewerContext,
|
|
66
|
+
TMEntity,
|
|
67
|
+
TMPrivacyPolicy,
|
|
68
|
+
TMSelectedFields
|
|
69
|
+
> {
|
|
70
|
+
return knexLoaderFn(this, viewerContext, queryContext);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Vend knex loader for loading entities via knex-specific methods in a given query context.
|
|
75
|
+
* Returns authorization results instead of throwing on authorization errors.
|
|
76
|
+
*
|
|
77
|
+
* @param viewerContext - viewer context of loading user
|
|
78
|
+
* @param queryContext - query context in which to perform the load
|
|
79
|
+
*/
|
|
80
|
+
static knexLoaderWithAuthorizationResults<
|
|
81
|
+
TMFields extends object,
|
|
82
|
+
TMIDField extends keyof NonNullable<Pick<TMFields, TMSelectedFields>>,
|
|
83
|
+
TMViewerContext extends ViewerContext,
|
|
84
|
+
TMViewerContext2 extends TMViewerContext,
|
|
85
|
+
TMEntity extends ReadonlyEntity<TMFields, TMIDField, TMViewerContext, TMSelectedFields>,
|
|
86
|
+
TMPrivacyPolicy extends EntityPrivacyPolicy<
|
|
87
|
+
TMFields,
|
|
88
|
+
TMIDField,
|
|
89
|
+
TMViewerContext,
|
|
90
|
+
TMEntity,
|
|
91
|
+
TMSelectedFields
|
|
92
|
+
>,
|
|
93
|
+
TMSelectedFields extends keyof TMFields = keyof TMFields,
|
|
94
|
+
>(
|
|
95
|
+
this: IEntityClass<
|
|
96
|
+
TMFields,
|
|
97
|
+
TMIDField,
|
|
98
|
+
TMViewerContext,
|
|
99
|
+
TMEntity,
|
|
100
|
+
TMPrivacyPolicy,
|
|
101
|
+
TMSelectedFields
|
|
102
|
+
>,
|
|
103
|
+
viewerContext: TMViewerContext2,
|
|
104
|
+
queryContext: EntityQueryContext = viewerContext
|
|
105
|
+
.getViewerScopedEntityCompanionForClass(this)
|
|
106
|
+
.getQueryContextProvider()
|
|
107
|
+
.getQueryContext(),
|
|
108
|
+
): AuthorizationResultBasedKnexEntityLoader<
|
|
109
|
+
TMFields,
|
|
110
|
+
TMIDField,
|
|
111
|
+
TMViewerContext,
|
|
112
|
+
TMEntity,
|
|
113
|
+
TMPrivacyPolicy,
|
|
114
|
+
TMSelectedFields
|
|
115
|
+
> {
|
|
116
|
+
return knexLoaderWithAuthorizationResultsFn(this, viewerContext, queryContext);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -1,21 +1,38 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
EntityConfiguration,
|
|
3
3
|
FieldTransformer,
|
|
4
4
|
FieldTransformerMap,
|
|
5
|
-
|
|
6
|
-
TableFieldSingleValueEqualityCondition,
|
|
7
|
-
TableQuerySelectionModifiers,
|
|
8
|
-
TableQuerySelectionModifiersWithOrderByRaw,
|
|
5
|
+
getDatabaseFieldForEntityField,
|
|
9
6
|
} from '@expo/entity';
|
|
10
7
|
import { Knex } from 'knex';
|
|
11
8
|
|
|
9
|
+
import {
|
|
10
|
+
BasePostgresEntityDatabaseAdapter,
|
|
11
|
+
NullsOrdering,
|
|
12
|
+
TableFieldMultiValueEqualityCondition,
|
|
13
|
+
TableFieldSingleValueEqualityCondition,
|
|
14
|
+
TableQuerySelectionModifiers,
|
|
15
|
+
} from './BasePostgresEntityDatabaseAdapter';
|
|
12
16
|
import { JSONArrayField, MaybeJSONArrayField } from './EntityFields';
|
|
17
|
+
import { PostgresEntityDatabaseAdapterConfiguration } from './PostgresEntityDatabaseAdapterProvider';
|
|
18
|
+
import { SQLFragment } from './SQLOperator';
|
|
13
19
|
import { wrapNativePostgresCallAsync } from './errors/wrapNativePostgresCallAsync';
|
|
14
20
|
|
|
15
21
|
export class PostgresEntityDatabaseAdapter<
|
|
16
22
|
TFields extends Record<string, any>,
|
|
17
23
|
TIDField extends keyof TFields,
|
|
18
|
-
> extends
|
|
24
|
+
> extends BasePostgresEntityDatabaseAdapter<TFields, TIDField> {
|
|
25
|
+
constructor(
|
|
26
|
+
entityConfiguration: EntityConfiguration<TFields, TIDField>,
|
|
27
|
+
private readonly adapterConfiguration: PostgresEntityDatabaseAdapterConfiguration = {},
|
|
28
|
+
) {
|
|
29
|
+
super(entityConfiguration);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override get paginationMaxPageSize(): number | undefined {
|
|
33
|
+
return this.adapterConfiguration.paginationMaxPageSize;
|
|
34
|
+
}
|
|
35
|
+
|
|
19
36
|
protected getFieldTransformerMap(): FieldTransformerMap {
|
|
20
37
|
return new Map<string, FieldTransformer<any>>([
|
|
21
38
|
[
|
|
@@ -86,23 +103,28 @@ export class PostgresEntityDatabaseAdapter<
|
|
|
86
103
|
);
|
|
87
104
|
}
|
|
88
105
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
protected async fetchOneWhereInternalAsync(
|
|
107
|
+
queryInterface: Knex,
|
|
108
|
+
tableName: string,
|
|
109
|
+
tableColumns: readonly string[],
|
|
110
|
+
tableTuple: readonly any[],
|
|
111
|
+
): Promise<object | null> {
|
|
112
|
+
const results = await this.fetchManyByFieldEqualityConjunctionInternalAsync(
|
|
113
|
+
queryInterface,
|
|
114
|
+
tableName,
|
|
115
|
+
tableColumns.map((column, index) => ({
|
|
116
|
+
tableField: column,
|
|
117
|
+
tableValue: tableTuple[index],
|
|
118
|
+
})),
|
|
119
|
+
[],
|
|
120
|
+
{ limit: 1, orderBy: undefined, offset: undefined },
|
|
121
|
+
);
|
|
122
|
+
return results[0] ?? null;
|
|
101
123
|
}
|
|
102
124
|
|
|
103
125
|
private applyQueryModifiersToQuery(
|
|
104
126
|
query: Knex.QueryBuilder,
|
|
105
|
-
querySelectionModifiers: TableQuerySelectionModifiers
|
|
127
|
+
querySelectionModifiers: TableQuerySelectionModifiers<TFields>,
|
|
106
128
|
): Knex.QueryBuilder {
|
|
107
129
|
const { orderBy, offset, limit } = querySelectionModifiers;
|
|
108
130
|
|
|
@@ -110,7 +132,23 @@ export class PostgresEntityDatabaseAdapter<
|
|
|
110
132
|
|
|
111
133
|
if (orderBy !== undefined) {
|
|
112
134
|
for (const orderBySpecification of orderBy) {
|
|
113
|
-
|
|
135
|
+
if ('columnName' in orderBySpecification) {
|
|
136
|
+
ret = ret.orderBy(
|
|
137
|
+
orderBySpecification.columnName,
|
|
138
|
+
orderBySpecification.order,
|
|
139
|
+
orderBySpecification.nulls,
|
|
140
|
+
);
|
|
141
|
+
} else {
|
|
142
|
+
const nullsSuffix = orderBySpecification.nulls
|
|
143
|
+
? ` NULLS ${orderBySpecification.nulls === NullsOrdering.FIRST ? 'FIRST' : 'LAST'}`
|
|
144
|
+
: '';
|
|
145
|
+
ret = ret.orderByRaw(
|
|
146
|
+
`(${orderBySpecification.columnFragment.sql}) ${orderBySpecification.order}${nullsSuffix}`,
|
|
147
|
+
orderBySpecification.columnFragment.getKnexBindings((fieldName) =>
|
|
148
|
+
getDatabaseFieldForEntityField(this.entityConfiguration, fieldName),
|
|
149
|
+
),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
114
152
|
}
|
|
115
153
|
}
|
|
116
154
|
|
|
@@ -130,7 +168,7 @@ export class PostgresEntityDatabaseAdapter<
|
|
|
130
168
|
tableName: string,
|
|
131
169
|
tableFieldSingleValueEqualityOperands: TableFieldSingleValueEqualityCondition[],
|
|
132
170
|
tableFieldMultiValueEqualityOperands: TableFieldMultiValueEqualityCondition[],
|
|
133
|
-
querySelectionModifiers: TableQuerySelectionModifiers
|
|
171
|
+
querySelectionModifiers: TableQuerySelectionModifiers<TFields>,
|
|
134
172
|
): Promise<object[]> {
|
|
135
173
|
let query = queryInterface.select().from(tableName);
|
|
136
174
|
|
|
@@ -176,13 +214,29 @@ export class PostgresEntityDatabaseAdapter<
|
|
|
176
214
|
tableName: string,
|
|
177
215
|
rawWhereClause: string,
|
|
178
216
|
bindings: object | any[],
|
|
179
|
-
querySelectionModifiers:
|
|
217
|
+
querySelectionModifiers: TableQuerySelectionModifiers<TFields>,
|
|
218
|
+
): Promise<object[]> {
|
|
219
|
+
let query = queryInterface.select().from(tableName).whereRaw(rawWhereClause, bindings);
|
|
220
|
+
query = this.applyQueryModifiersToQuery(query, querySelectionModifiers);
|
|
221
|
+
return await wrapNativePostgresCallAsync(() => query);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
protected async fetchManyBySQLFragmentInternalAsync(
|
|
225
|
+
queryInterface: Knex,
|
|
226
|
+
tableName: string,
|
|
227
|
+
sqlFragment: SQLFragment<TFields>,
|
|
228
|
+
querySelectionModifiers: TableQuerySelectionModifiers<TFields>,
|
|
180
229
|
): Promise<object[]> {
|
|
181
230
|
let query = queryInterface
|
|
182
231
|
.select()
|
|
183
232
|
.from(tableName)
|
|
184
|
-
.whereRaw(
|
|
185
|
-
|
|
233
|
+
.whereRaw(
|
|
234
|
+
sqlFragment.sql,
|
|
235
|
+
sqlFragment.getKnexBindings((fieldName) =>
|
|
236
|
+
getDatabaseFieldForEntityField(this.entityConfiguration, fieldName),
|
|
237
|
+
),
|
|
238
|
+
);
|
|
239
|
+
query = this.applyQueryModifiersToQuery(query, querySelectionModifiers);
|
|
186
240
|
return await wrapNativePostgresCallAsync(() => query);
|
|
187
241
|
}
|
|
188
242
|
|
|
@@ -6,10 +6,20 @@ import {
|
|
|
6
6
|
|
|
7
7
|
import { PostgresEntityDatabaseAdapter } from './PostgresEntityDatabaseAdapter';
|
|
8
8
|
|
|
9
|
+
export interface PostgresEntityDatabaseAdapterConfiguration {
|
|
10
|
+
/**
|
|
11
|
+
* Maximum page size for pagination (first/last parameters).
|
|
12
|
+
* If not specified, no limit is enforced.
|
|
13
|
+
*/
|
|
14
|
+
paginationMaxPageSize?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
export class PostgresEntityDatabaseAdapterProvider implements IEntityDatabaseAdapterProvider {
|
|
18
|
+
constructor(private readonly configuration: PostgresEntityDatabaseAdapterConfiguration = {}) {}
|
|
19
|
+
|
|
10
20
|
getDatabaseAdapter<TFields extends Record<string, any>, TIDField extends keyof TFields>(
|
|
11
21
|
entityConfiguration: EntityConfiguration<TFields, TIDField>,
|
|
12
22
|
): EntityDatabaseAdapter<TFields, TIDField> {
|
|
13
|
-
return new PostgresEntityDatabaseAdapter(entityConfiguration);
|
|
23
|
+
return new PostgresEntityDatabaseAdapter(entityConfiguration, this.configuration);
|
|
14
24
|
}
|
|
15
25
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EntityPrivacyPolicy,
|
|
3
|
+
EntityQueryContext,
|
|
4
|
+
IEntityClass,
|
|
5
|
+
ReadonlyEntity,
|
|
6
|
+
ViewerContext,
|
|
7
|
+
} from '@expo/entity';
|
|
8
|
+
|
|
9
|
+
import { AuthorizationResultBasedKnexEntityLoader } from './AuthorizationResultBasedKnexEntityLoader';
|
|
10
|
+
import { EnforcingKnexEntityLoader } from './EnforcingKnexEntityLoader';
|
|
11
|
+
import {
|
|
12
|
+
knexLoader as knexLoaderFn,
|
|
13
|
+
knexLoaderWithAuthorizationResults as knexLoaderWithAuthorizationResultsFn,
|
|
14
|
+
} from './knexLoader';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Abstract base class for readonly entities backed by Postgres.
|
|
18
|
+
* Provides `knexLoader` and `knexLoaderWithAuthorizationResults` as inherited static methods.
|
|
19
|
+
*
|
|
20
|
+
* Entities that should not be mutated (e.g., representing SQL views or immutable tables)
|
|
21
|
+
* can extend this class instead of `ReadonlyEntity` to get knex loader ergonomics.
|
|
22
|
+
*/
|
|
23
|
+
export abstract class ReadonlyPostgresEntity<
|
|
24
|
+
TFields extends Record<string, any>,
|
|
25
|
+
TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
|
|
26
|
+
TViewerContext extends ViewerContext,
|
|
27
|
+
TSelectedFields extends keyof TFields = keyof TFields,
|
|
28
|
+
> extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields> {
|
|
29
|
+
/**
|
|
30
|
+
* Vend knex loader for loading entities via non-data-loader methods in a given query context.
|
|
31
|
+
* @param viewerContext - viewer context of loading user
|
|
32
|
+
* @param queryContext - query context in which to perform the load
|
|
33
|
+
*/
|
|
34
|
+
static knexLoader<
|
|
35
|
+
TMFields extends object,
|
|
36
|
+
TMIDField extends keyof NonNullable<Pick<TMFields, TMSelectedFields>>,
|
|
37
|
+
TMViewerContext extends ViewerContext,
|
|
38
|
+
TMEntity extends ReadonlyEntity<TMFields, TMIDField, TMViewerContext, TMSelectedFields>,
|
|
39
|
+
TMPrivacyPolicy extends EntityPrivacyPolicy<
|
|
40
|
+
TMFields,
|
|
41
|
+
TMIDField,
|
|
42
|
+
TMViewerContext,
|
|
43
|
+
TMEntity,
|
|
44
|
+
TMSelectedFields
|
|
45
|
+
>,
|
|
46
|
+
TMSelectedFields extends keyof TMFields = keyof TMFields,
|
|
47
|
+
>(
|
|
48
|
+
this: IEntityClass<
|
|
49
|
+
TMFields,
|
|
50
|
+
TMIDField,
|
|
51
|
+
TMViewerContext,
|
|
52
|
+
TMEntity,
|
|
53
|
+
TMPrivacyPolicy,
|
|
54
|
+
TMSelectedFields
|
|
55
|
+
>,
|
|
56
|
+
viewerContext: TMViewerContext,
|
|
57
|
+
queryContext: EntityQueryContext = viewerContext
|
|
58
|
+
.getViewerScopedEntityCompanionForClass(this)
|
|
59
|
+
.getQueryContextProvider()
|
|
60
|
+
.getQueryContext(),
|
|
61
|
+
): EnforcingKnexEntityLoader<
|
|
62
|
+
TMFields,
|
|
63
|
+
TMIDField,
|
|
64
|
+
TMViewerContext,
|
|
65
|
+
TMEntity,
|
|
66
|
+
TMPrivacyPolicy,
|
|
67
|
+
TMSelectedFields
|
|
68
|
+
> {
|
|
69
|
+
return knexLoaderFn(this, viewerContext, queryContext);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Vend knex loader for loading entities via non-data-loader methods in a given query context.
|
|
74
|
+
* Returns authorization results instead of throwing on authorization errors.
|
|
75
|
+
* @param viewerContext - viewer context of loading user
|
|
76
|
+
* @param queryContext - query context in which to perform the load
|
|
77
|
+
*/
|
|
78
|
+
static knexLoaderWithAuthorizationResults<
|
|
79
|
+
TMFields extends object,
|
|
80
|
+
TMIDField extends keyof NonNullable<Pick<TMFields, TMSelectedFields>>,
|
|
81
|
+
TMViewerContext extends ViewerContext,
|
|
82
|
+
TMEntity extends ReadonlyEntity<TMFields, TMIDField, TMViewerContext, TMSelectedFields>,
|
|
83
|
+
TMPrivacyPolicy extends EntityPrivacyPolicy<
|
|
84
|
+
TMFields,
|
|
85
|
+
TMIDField,
|
|
86
|
+
TMViewerContext,
|
|
87
|
+
TMEntity,
|
|
88
|
+
TMSelectedFields
|
|
89
|
+
>,
|
|
90
|
+
TMSelectedFields extends keyof TMFields = keyof TMFields,
|
|
91
|
+
>(
|
|
92
|
+
this: IEntityClass<
|
|
93
|
+
TMFields,
|
|
94
|
+
TMIDField,
|
|
95
|
+
TMViewerContext,
|
|
96
|
+
TMEntity,
|
|
97
|
+
TMPrivacyPolicy,
|
|
98
|
+
TMSelectedFields
|
|
99
|
+
>,
|
|
100
|
+
viewerContext: TMViewerContext,
|
|
101
|
+
queryContext: EntityQueryContext = viewerContext
|
|
102
|
+
.getViewerScopedEntityCompanionForClass(this)
|
|
103
|
+
.getQueryContextProvider()
|
|
104
|
+
.getQueryContext(),
|
|
105
|
+
): AuthorizationResultBasedKnexEntityLoader<
|
|
106
|
+
TMFields,
|
|
107
|
+
TMIDField,
|
|
108
|
+
TMViewerContext,
|
|
109
|
+
TMEntity,
|
|
110
|
+
TMPrivacyPolicy,
|
|
111
|
+
TMSelectedFields
|
|
112
|
+
> {
|
|
113
|
+
return knexLoaderWithAuthorizationResultsFn(this, viewerContext, queryContext);
|
|
114
|
+
}
|
|
115
|
+
}
|