@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.
Files changed (91) hide show
  1. package/build/src/AuthorizationResultBasedKnexEntityLoader.d.ts +279 -0
  2. package/build/src/AuthorizationResultBasedKnexEntityLoader.js +127 -0
  3. package/build/src/AuthorizationResultBasedKnexEntityLoader.js.map +1 -0
  4. package/build/src/BasePostgresEntityDatabaseAdapter.d.ts +150 -0
  5. package/build/src/BasePostgresEntityDatabaseAdapter.js +119 -0
  6. package/build/src/BasePostgresEntityDatabaseAdapter.js.map +1 -0
  7. package/build/src/BaseSQLQueryBuilder.d.ts +61 -0
  8. package/build/src/BaseSQLQueryBuilder.js +87 -0
  9. package/build/src/BaseSQLQueryBuilder.js.map +1 -0
  10. package/build/src/EnforcingKnexEntityLoader.d.ts +124 -0
  11. package/build/src/EnforcingKnexEntityLoader.js +166 -0
  12. package/build/src/EnforcingKnexEntityLoader.js.map +1 -0
  13. package/build/src/KnexEntityLoaderFactory.d.ts +25 -0
  14. package/build/src/KnexEntityLoaderFactory.js +39 -0
  15. package/build/src/KnexEntityLoaderFactory.js.map +1 -0
  16. package/build/src/PaginationStrategy.d.ts +30 -0
  17. package/build/src/PaginationStrategy.js +35 -0
  18. package/build/src/PaginationStrategy.js.map +1 -0
  19. package/build/src/PostgresEntity.d.ts +25 -0
  20. package/build/src/PostgresEntity.js +39 -0
  21. package/build/src/PostgresEntity.js.map +1 -0
  22. package/build/src/PostgresEntityDatabaseAdapter.d.ts +12 -5
  23. package/build/src/PostgresEntityDatabaseAdapter.js +32 -11
  24. package/build/src/PostgresEntityDatabaseAdapter.js.map +1 -1
  25. package/build/src/PostgresEntityDatabaseAdapterProvider.d.ts +9 -0
  26. package/build/src/PostgresEntityDatabaseAdapterProvider.js +5 -1
  27. package/build/src/PostgresEntityDatabaseAdapterProvider.js.map +1 -1
  28. package/build/src/ReadonlyPostgresEntity.d.ts +25 -0
  29. package/build/src/ReadonlyPostgresEntity.js +39 -0
  30. package/build/src/ReadonlyPostgresEntity.js.map +1 -0
  31. package/build/src/SQLOperator.d.ts +261 -0
  32. package/build/src/SQLOperator.js +464 -0
  33. package/build/src/SQLOperator.js.map +1 -0
  34. package/build/src/index.d.ts +15 -0
  35. package/build/src/index.js +15 -0
  36. package/build/src/index.js.map +1 -1
  37. package/build/src/internal/EntityKnexDataManager.d.ts +147 -0
  38. package/build/src/internal/EntityKnexDataManager.js +453 -0
  39. package/build/src/internal/EntityKnexDataManager.js.map +1 -0
  40. package/build/src/internal/getKnexDataManager.d.ts +3 -0
  41. package/build/src/internal/getKnexDataManager.js +19 -0
  42. package/build/src/internal/getKnexDataManager.js.map +1 -0
  43. package/build/src/internal/getKnexEntityLoaderFactory.d.ts +3 -0
  44. package/build/src/internal/getKnexEntityLoaderFactory.js +11 -0
  45. package/build/src/internal/getKnexEntityLoaderFactory.js.map +1 -0
  46. package/build/src/internal/utilityTypes.d.ts +5 -0
  47. package/build/src/internal/utilityTypes.js +5 -0
  48. package/build/src/internal/utilityTypes.js.map +1 -0
  49. package/build/src/internal/weakMaps.d.ts +9 -0
  50. package/build/src/internal/weakMaps.js +20 -0
  51. package/build/src/internal/weakMaps.js.map +1 -0
  52. package/build/src/knexLoader.d.ts +18 -0
  53. package/build/src/knexLoader.js +31 -0
  54. package/build/src/knexLoader.js.map +1 -0
  55. package/package.json +6 -5
  56. package/src/AuthorizationResultBasedKnexEntityLoader.ts +538 -0
  57. package/src/BasePostgresEntityDatabaseAdapter.ts +317 -0
  58. package/src/BaseSQLQueryBuilder.ts +114 -0
  59. package/src/EnforcingKnexEntityLoader.ts +271 -0
  60. package/src/KnexEntityLoaderFactory.ts +130 -0
  61. package/src/PaginationStrategy.ts +32 -0
  62. package/src/PostgresEntity.ts +118 -0
  63. package/src/PostgresEntityDatabaseAdapter.ts +78 -24
  64. package/src/PostgresEntityDatabaseAdapterProvider.ts +11 -1
  65. package/src/ReadonlyPostgresEntity.ts +115 -0
  66. package/src/SQLOperator.ts +603 -0
  67. package/src/__integration-tests__/EntityCreationUtils-test.ts +25 -31
  68. package/src/__integration-tests__/PostgresEntityIntegration-test.ts +3192 -330
  69. package/src/__integration-tests__/PostgresEntityQueryContextProvider-test.ts +7 -7
  70. package/src/__testfixtures__/PostgresTestEntity.ts +17 -3
  71. package/src/__tests__/AuthorizationResultBasedKnexEntityLoader-test.ts +1167 -0
  72. package/src/__tests__/BasePostgresEntityDatabaseAdapter-test.ts +160 -0
  73. package/src/__tests__/EnforcingKnexEntityLoader-test.ts +384 -0
  74. package/src/__tests__/EntityFields-test.ts +1 -1
  75. package/src/__tests__/PostgresEntity-test.ts +172 -0
  76. package/src/__tests__/ReadonlyEntity-test.ts +32 -0
  77. package/src/__tests__/SQLOperator-test.ts +831 -0
  78. package/src/__tests__/fixtures/StubPostgresDatabaseAdapter.ts +302 -0
  79. package/src/__tests__/fixtures/StubPostgresDatabaseAdapterProvider.ts +17 -0
  80. package/src/__tests__/fixtures/TestEntity.ts +131 -0
  81. package/src/__tests__/fixtures/TestPaginationEntity.ts +107 -0
  82. package/src/__tests__/fixtures/createUnitTestPostgresEntityCompanionProvider.ts +42 -0
  83. package/src/index.ts +15 -0
  84. package/src/internal/EntityKnexDataManager.ts +832 -0
  85. package/src/internal/__tests__/EntityKnexDataManager-test.ts +378 -0
  86. package/src/internal/__tests__/weakMaps-test.ts +25 -0
  87. package/src/internal/getKnexDataManager.ts +43 -0
  88. package/src/internal/getKnexEntityLoaderFactory.ts +60 -0
  89. package/src/internal/utilityTypes.ts +11 -0
  90. package/src/internal/weakMaps.ts +19 -0
  91. 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
- EntityDatabaseAdapter,
2
+ EntityConfiguration,
3
3
  FieldTransformer,
4
4
  FieldTransformerMap,
5
- TableFieldMultiValueEqualityCondition,
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 EntityDatabaseAdapter<TFields, TIDField> {
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
- private applyQueryModifiersToQueryOrderByRaw(
90
- query: Knex.QueryBuilder,
91
- querySelectionModifiers: TableQuerySelectionModifiersWithOrderByRaw,
92
- ): Knex.QueryBuilder {
93
- let ret = this.applyQueryModifiersToQuery(query, querySelectionModifiers);
94
-
95
- const { orderByRaw } = querySelectionModifiers;
96
- if (orderByRaw !== undefined) {
97
- ret = ret.orderByRaw(orderByRaw);
98
- }
99
-
100
- return ret;
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
- ret = ret.orderBy(orderBySpecification.columnName, orderBySpecification.order);
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: TableQuerySelectionModifiersWithOrderByRaw,
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(rawWhereClause, bindings as any);
185
- query = this.applyQueryModifiersToQueryOrderByRaw(query, querySelectionModifiers);
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
+ }