@expo/entity-database-adapter-knex 0.55.0 → 0.58.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 +278 -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 +33 -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 +267 -0
  32. package/build/src/SQLOperator.js +474 -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 +537 -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 +81 -24
  64. package/src/PostgresEntityDatabaseAdapterProvider.ts +11 -1
  65. package/src/ReadonlyPostgresEntity.ts +115 -0
  66. package/src/SQLOperator.ts +630 -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 +871 -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,537 @@
1
+ import {
2
+ EntityConstructionUtils,
3
+ EntityPrivacyPolicy,
4
+ EntityQueryContext,
5
+ ReadonlyEntity,
6
+ ViewerContext,
7
+ IEntityMetricsAdapter,
8
+ } from '@expo/entity';
9
+ import { Result } from '@expo/results';
10
+
11
+ import {
12
+ FieldEqualityCondition,
13
+ isSingleValueFieldEqualityCondition,
14
+ NullsOrdering,
15
+ OrderByOrdering,
16
+ } from './BasePostgresEntityDatabaseAdapter';
17
+ import { BaseSQLQueryBuilder } from './BaseSQLQueryBuilder';
18
+ import { PaginationStrategy } from './PaginationStrategy';
19
+ import { SQLFragment } from './SQLOperator';
20
+ import type { Connection, PageInfo } from './internal/EntityKnexDataManager';
21
+ import { EntityKnexDataManager } from './internal/EntityKnexDataManager';
22
+ import { NonNullableKeys } from './internal/utilityTypes';
23
+
24
+ export type EntityLoaderBaseOrderByClause = {
25
+ /**
26
+ * The OrderByOrdering to order by.
27
+ */
28
+ order: OrderByOrdering;
29
+
30
+ /**
31
+ * Optional nulls ordering for this order by clause. If not provided, no specific nulls ordering is applied.
32
+ */
33
+ nulls?: NullsOrdering | undefined;
34
+ };
35
+
36
+ export type EntityLoaderFieldNameOrderByClause<
37
+ TFields extends Record<string, any>,
38
+ TSelectedFields extends keyof TFields = keyof TFields,
39
+ > = EntityLoaderBaseOrderByClause & {
40
+ /**
41
+ * The field name to order by.
42
+ */
43
+ fieldName: TSelectedFields;
44
+ };
45
+
46
+ export type EntityLoaderFieldFragmentOrderByClause<
47
+ TFields extends Record<string, any>,
48
+ TSelectedFields extends keyof TFields = keyof TFields,
49
+ > = EntityLoaderBaseOrderByClause & {
50
+ /**
51
+ * The SQL fragment to order by, which can reference selected fields. Example: `COALESCE(NULLIF(display_name, ''), split_part(full_name, '/', 2))`.
52
+ * May not contain ASC or DESC, as ordering direction is controlled by the `order` property.
53
+ */
54
+ fieldFragment: SQLFragment<Pick<TFields, TSelectedFields>>;
55
+ };
56
+
57
+ export type EntityLoaderOrderByClause<
58
+ TFields extends Record<string, any>,
59
+ TSelectedFields extends keyof TFields = keyof TFields,
60
+ > =
61
+ | EntityLoaderFieldNameOrderByClause<TFields, TSelectedFields>
62
+ | EntityLoaderFieldFragmentOrderByClause<TFields, TSelectedFields>;
63
+
64
+ /**
65
+ * SQL modifiers that only affect the selection but not the projection.
66
+ */
67
+ export interface EntityLoaderQuerySelectionModifiers<
68
+ TFields extends Record<string, any>,
69
+ TSelectedFields extends keyof TFields = keyof TFields,
70
+ > {
71
+ /**
72
+ * Order the entities by specified columns and orders.
73
+ */
74
+ orderBy?: readonly EntityLoaderOrderByClause<TFields, TSelectedFields>[];
75
+
76
+ /**
77
+ * Skip the specified number of entities queried before returning.
78
+ */
79
+ offset?: number;
80
+
81
+ /**
82
+ * Limit the number of entities returned.
83
+ */
84
+ limit?: number;
85
+ }
86
+
87
+ /**
88
+ * Function to be used for constructing search fields based on field names.
89
+ * The function takes a field name and returns a SQLFragment that can be used in the search field portion.
90
+ */
91
+ export type EntityLoaderFieldNameConstructorFn<
92
+ TFields extends Record<string, any>,
93
+ TSelectedFields extends keyof TFields = keyof TFields,
94
+ > = (fieldName: TSelectedFields) => SQLFragment<TFields>;
95
+
96
+ /**
97
+ * Specification for a search field that is a manually constructed SQLFragment. Useful for complex search fields that require
98
+ * transformations to make nullable fields non-null or to make combinations of multiple fields.
99
+ */
100
+ export type EntityLoaderSearchFieldSQLFragmentFnSpecification<
101
+ TFields extends Record<string, any>,
102
+ TSelectedFields extends keyof TFields = keyof TFields,
103
+ > = {
104
+ /**
105
+ * Function that constructs the SQLFragment for the search field.
106
+ *
107
+ * @param getFragmentForFieldName - A helper function that takes a field name and returns a SQLFragment for that field, which should be used
108
+ * to construct the final SQLFragment for the search field.
109
+ * @returns a SQLFragment representing the search field, which must reference selected fields through the provided helper function.
110
+ *
111
+ * @example
112
+ * For example, to construct a search field that searches by display name (nullable column) with fallback to full name,
113
+ * the fieldConstructor function could be implemented as follows:
114
+ * ```
115
+ * fieldConstructor: (getFragmentForFieldName) => {
116
+ * const displayNameFragment = getFragmentForFieldName('display_name');
117
+ * const fullNameFragment = getFragmentForFieldName('full_name');
118
+ * return sql`COALESCE(NULLIF(${displayNameFragment}, ''), split_part(${fullNameFragment}, '/', 2))`;
119
+ * }
120
+ * ```
121
+ */
122
+ fieldConstructor: (
123
+ /**
124
+ * Helper function to get a SQLFragment for a given field name, which should be used to construct the final SQLFragment for the search field.
125
+ */
126
+ getFragmentForFieldName: EntityLoaderFieldNameConstructorFn<TFields, TSelectedFields>,
127
+ ) => SQLFragment<Pick<TFields, TSelectedFields>>;
128
+ };
129
+
130
+ /**
131
+ * Set of selected fields that are non-nullable, which can be used for search fields that require non-nullable fields.
132
+ * To use a nullable field as a search field, use an EntityLoaderSearchFieldSQLFragmentFnSpecification with appropriate SQL
133
+ * to handle null values, such as `COALESCE(field_name, '')` to treat nulls as empty strings for searching.
134
+ */
135
+ export type NonNullableSelectedFields<
136
+ TFields extends Record<string, any>,
137
+ TSelectedFields extends keyof TFields,
138
+ > = TSelectedFields & NonNullableKeys<TFields>;
139
+
140
+ /**
141
+ * Specification for a search field that can be either a simple selected field name (which must be non-nullable) or a manually constructed
142
+ * SQLFragment using EntityLoaderSearchFieldSQLFragmentFnSpecification.
143
+ */
144
+ export type EntityLoaderSearchFieldSpecification<
145
+ TFields extends Record<string, any>,
146
+ TSelectedFields extends keyof TFields = keyof TFields,
147
+ > =
148
+ | NonNullableSelectedFields<TFields, TSelectedFields>
149
+ | EntityLoaderSearchFieldSQLFragmentFnSpecification<TFields, TSelectedFields>;
150
+
151
+ interface SearchSpecificationBase<
152
+ TFields extends Record<string, any>,
153
+ TSelectedFields extends keyof TFields = keyof TFields,
154
+ > {
155
+ /**
156
+ * The search term to search for. Must be a non-empty string.
157
+ */
158
+ term: string;
159
+
160
+ /**
161
+ * The fields to search within.
162
+ */
163
+ fields: readonly EntityLoaderSearchFieldSpecification<TFields, TSelectedFields>[];
164
+ }
165
+
166
+ interface ILikeSearchSpecification<
167
+ TFields extends Record<string, any>,
168
+ TSelectedFields extends keyof TFields = keyof TFields,
169
+ > extends SearchSpecificationBase<TFields, TSelectedFields> {
170
+ /**
171
+ * Case-insensitive pattern matching search using SQL ILIKE operator.
172
+ * Results are ordered by the fields being searched within in the order specified, then by ID for tie-breaking and stable pagination.
173
+ */
174
+ strategy: PaginationStrategy.ILIKE_SEARCH;
175
+ }
176
+
177
+ interface TrigramSearchSpecification<
178
+ TFields extends Record<string, any>,
179
+ TSelectedFields extends keyof TFields = keyof TFields,
180
+ > extends SearchSpecificationBase<TFields, TSelectedFields> {
181
+ /**
182
+ * 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.
183
+ * Note that trigram similarity search can be significantly slower than ILIKE search, especially on large datasets without appropriate indexes, and results may not be as relevant as more advanced full-text search solutions.
184
+ * It is recommended to use this strategy only when ILIKE search does not meet the application's needs and to ensure appropriate database indexing for performance.
185
+ */
186
+ strategy: PaginationStrategy.TRIGRAM_SEARCH;
187
+
188
+ /**
189
+ * Similarity threshold for trigram matching.
190
+ * Must be between 0 and 1, where:
191
+ * - 0 matches everything
192
+ * - 1 requires exact match
193
+ *
194
+ * Recommended threshold values:
195
+ * - 0.3: Loose matching, allows more variation (default PostgreSQL similarity threshold)
196
+ * - 0.4-0.5: Moderate matching, good balance for most use cases
197
+ * - 0.6+: Strict matching, requires high similarity
198
+ */
199
+ threshold: number;
200
+
201
+ /**
202
+ * Optional additional fields to order by after similarity score and before ID for tie-breaking.
203
+ * These fields are independent of search fields and can be used to provide meaningful
204
+ * ordering when multiple results have the same similarity score.
205
+ */
206
+ extraOrderByFields?: readonly EntityLoaderSearchFieldSpecification<TFields, TSelectedFields>[];
207
+ }
208
+
209
+ interface StandardPaginationSpecification<
210
+ TFields extends Record<string, any>,
211
+ TSelectedFields extends keyof TFields = keyof TFields,
212
+ > {
213
+ /**
214
+ * Standard pagination without search. Results are ordered by the specified orderBy fields.
215
+ */
216
+ strategy: PaginationStrategy.STANDARD;
217
+
218
+ /**
219
+ * Order the entities by specified columns and orders. If the ID field is not included, it will be automatically added for stable pagination.
220
+ */
221
+ orderBy: readonly EntityLoaderOrderByClause<TFields, TSelectedFields>[];
222
+ }
223
+
224
+ /**
225
+ * Pagination specification for SQL-based pagination (with or without search).
226
+ */
227
+ export type PaginationSpecification<
228
+ TFields extends Record<string, any>,
229
+ TSelectedFields extends keyof TFields = keyof TFields,
230
+ > =
231
+ | StandardPaginationSpecification<TFields, TSelectedFields>
232
+ | ILikeSearchSpecification<TFields, TSelectedFields>
233
+ | TrigramSearchSpecification<TFields, TSelectedFields>;
234
+
235
+ /**
236
+ * Base unified pagination arguments
237
+ */
238
+ interface EntityLoaderBaseUnifiedPaginationArgs<
239
+ TFields extends Record<string, any>,
240
+ TSelectedFields extends keyof TFields = keyof TFields,
241
+ > {
242
+ /**
243
+ * SQLFragment representing the WHERE clause to filter the entities being paginated.
244
+ */
245
+ where?: SQLFragment<Pick<TFields, TSelectedFields>>;
246
+
247
+ /**
248
+ * Pagination specification determining how to order and paginate results.
249
+ */
250
+ pagination: PaginationSpecification<TFields, TSelectedFields>;
251
+ }
252
+
253
+ /**
254
+ * Forward unified pagination arguments
255
+ */
256
+ export interface EntityLoaderForwardUnifiedPaginationArgs<
257
+ TFields extends Record<string, any>,
258
+ TSelectedFields extends keyof TFields = keyof TFields,
259
+ > extends EntityLoaderBaseUnifiedPaginationArgs<TFields, TSelectedFields> {
260
+ /**
261
+ * The number of entities to return starting from the entity after the cursor. Must be a positive integer.
262
+ */
263
+ first: number;
264
+
265
+ /**
266
+ * The number of entities to return starting from the entity before the cursor. Must be a positive integer.
267
+ * Not allowed with forward pagination.
268
+ */
269
+ last?: never;
270
+
271
+ /**
272
+ * The cursor to paginate after for forward pagination.
273
+ */
274
+ after?: string;
275
+
276
+ /**
277
+ * The cursor to paginate before for backward pagination.
278
+ * Not allowed with forward pagination.
279
+ */
280
+ before?: never;
281
+ }
282
+
283
+ /**
284
+ * Backward unified pagination arguments
285
+ */
286
+ export interface EntityLoaderBackwardUnifiedPaginationArgs<
287
+ TFields extends Record<string, any>,
288
+ TSelectedFields extends keyof TFields = keyof TFields,
289
+ > extends EntityLoaderBaseUnifiedPaginationArgs<TFields, TSelectedFields> {
290
+ /**
291
+ * The number of entities to return starting from the entity after the cursor. Must be a positive integer.
292
+ * Not allowed with backward pagination.
293
+ */
294
+ first?: never;
295
+
296
+ /**
297
+ * The number of entities to return starting from the entity before the cursor. Must be a positive integer.
298
+ */
299
+ last: number;
300
+
301
+ /**
302
+ * The cursor to paginate after for forward pagination.
303
+ * Not allowed with backward pagination.
304
+ */
305
+ after?: never;
306
+
307
+ /**
308
+ * The cursor to paginate before for backward pagination.
309
+ */
310
+ before?: string;
311
+ }
312
+
313
+ /**
314
+ * Load page pagination arguments, which can be either forward or backward unified pagination arguments.
315
+ */
316
+ export type EntityLoaderLoadPageArgs<
317
+ TFields extends Record<string, any>,
318
+ TSelectedFields extends keyof TFields = keyof TFields,
319
+ > =
320
+ | EntityLoaderForwardUnifiedPaginationArgs<TFields, TSelectedFields>
321
+ | EntityLoaderBackwardUnifiedPaginationArgs<TFields, TSelectedFields>;
322
+
323
+ /**
324
+ * Authorization-result-based knex entity loader for non-data-loader-based load methods.
325
+ * All loads through this loader are results (or null for some loader methods), where an
326
+ * unsuccessful result means an authorization error or entity construction error occurred.
327
+ * Other errors are thrown.
328
+ */
329
+ export class AuthorizationResultBasedKnexEntityLoader<
330
+ TFields extends Record<string, any>,
331
+ TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
332
+ TViewerContext extends ViewerContext,
333
+ TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
334
+ TPrivacyPolicy extends EntityPrivacyPolicy<
335
+ TFields,
336
+ TIDField,
337
+ TViewerContext,
338
+ TEntity,
339
+ TSelectedFields
340
+ >,
341
+ TSelectedFields extends keyof TFields = keyof TFields,
342
+ > {
343
+ constructor(
344
+ private readonly queryContext: EntityQueryContext,
345
+ private readonly knexDataManager: EntityKnexDataManager<TFields, TIDField>,
346
+ protected readonly metricsAdapter: IEntityMetricsAdapter,
347
+ private readonly constructionUtils: EntityConstructionUtils<
348
+ TFields,
349
+ TIDField,
350
+ TViewerContext,
351
+ TEntity,
352
+ TPrivacyPolicy,
353
+ TSelectedFields
354
+ >,
355
+ ) {}
356
+
357
+ /**
358
+ * Authorization-result-based version of the EnforcingKnexEntityLoader method by the same name.
359
+ * @returns the first entity results that matches the query, where result error can be
360
+ * UnauthorizedError
361
+ */
362
+ async loadFirstByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(
363
+ fieldEqualityOperands: readonly FieldEqualityCondition<TFields, N>[],
364
+ querySelectionModifiers: Omit<
365
+ EntityLoaderQuerySelectionModifiers<TFields, TSelectedFields>,
366
+ 'limit'
367
+ > &
368
+ Required<Pick<EntityLoaderQuerySelectionModifiers<TFields, TSelectedFields>, 'orderBy'>>,
369
+ ): Promise<Result<TEntity> | null> {
370
+ const results = await this.loadManyByFieldEqualityConjunctionAsync(fieldEqualityOperands, {
371
+ ...querySelectionModifiers,
372
+ limit: 1,
373
+ });
374
+ return results[0] ?? null;
375
+ }
376
+
377
+ /**
378
+ * Authorization-result-based version of the EnforcingKnexEntityLoader method by the same name.
379
+ * @returns array of entity results that match the query, where result error can be UnauthorizedError
380
+ */
381
+ async loadManyByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(
382
+ fieldEqualityOperands: readonly FieldEqualityCondition<TFields, N>[],
383
+ querySelectionModifiers: EntityLoaderQuerySelectionModifiers<TFields, TSelectedFields> = {},
384
+ ): Promise<readonly Result<TEntity>[]> {
385
+ for (const fieldEqualityOperand of fieldEqualityOperands) {
386
+ const fieldValues = isSingleValueFieldEqualityCondition(fieldEqualityOperand)
387
+ ? [fieldEqualityOperand.fieldValue]
388
+ : fieldEqualityOperand.fieldValues;
389
+ this.constructionUtils.validateFieldAndValues(fieldEqualityOperand.fieldName, fieldValues);
390
+ }
391
+
392
+ const fieldObjects = await this.knexDataManager.loadManyByFieldEqualityConjunctionAsync(
393
+ this.queryContext,
394
+ fieldEqualityOperands,
395
+ querySelectionModifiers,
396
+ );
397
+ return await this.constructionUtils.constructAndAuthorizeEntitiesArrayAsync(fieldObjects);
398
+ }
399
+
400
+ /**
401
+ * Authorization-result-based version of the EnforcingKnexEntityLoader method by the same name.
402
+ * @returns array of entity results that match the query, where result error can be UnauthorizedError
403
+ * @throws Error when rawWhereClause or bindings are invalid
404
+ *
405
+ * @deprecated Use loadManyBySQL instead for safer value bindings and more flexible query building.
406
+ */
407
+ async loadManyByRawWhereClauseAsync(
408
+ rawWhereClause: string,
409
+ bindings: any[] | object,
410
+ querySelectionModifiers: EntityLoaderQuerySelectionModifiers<TFields, TSelectedFields> = {},
411
+ ): Promise<readonly Result<TEntity>[]> {
412
+ const fieldObjects = await this.knexDataManager.loadManyByRawWhereClauseAsync(
413
+ this.queryContext,
414
+ rawWhereClause,
415
+ bindings,
416
+ querySelectionModifiers,
417
+ );
418
+ return await this.constructionUtils.constructAndAuthorizeEntitiesArrayAsync(fieldObjects);
419
+ }
420
+
421
+ /**
422
+ * Authorization-result-based version of the EnforcingKnexEntityLoader method by the same name.
423
+ * @returns SQL query builder for building and executing SQL queries that when executed returns entity results where result error can be UnauthorizedError.
424
+ */
425
+ loadManyBySQL(
426
+ fragment: SQLFragment<Pick<TFields, TSelectedFields>>,
427
+ modifiers: EntityLoaderQuerySelectionModifiers<TFields, TSelectedFields> = {},
428
+ ): AuthorizationResultBasedSQLQueryBuilder<
429
+ TFields,
430
+ TIDField,
431
+ TViewerContext,
432
+ TEntity,
433
+ TPrivacyPolicy,
434
+ TSelectedFields
435
+ > {
436
+ return new AuthorizationResultBasedSQLQueryBuilder(
437
+ this.knexDataManager,
438
+ this.constructionUtils,
439
+ this.queryContext,
440
+ fragment,
441
+ modifiers,
442
+ );
443
+ }
444
+
445
+ /**
446
+ * Load a page of entities with Relay-style cursor pagination using a unified pagination specification.
447
+ * Only returns successfully authorized entities for cursor stability; failed authorization results are filtered out.
448
+ *
449
+ * @returns Connection with only successfully authorized entities
450
+ */
451
+ async loadPageAsync(
452
+ args: EntityLoaderLoadPageArgs<TFields, TSelectedFields>,
453
+ ): Promise<Connection<TEntity>> {
454
+ const pageResult = await this.knexDataManager.loadPageAsync(this.queryContext, args);
455
+
456
+ const edgeResults = await Promise.all(
457
+ pageResult.edges.map(async (edge) => {
458
+ const entityResult = await this.constructionUtils.constructAndAuthorizeEntityAsync(
459
+ edge.node,
460
+ );
461
+ if (!entityResult.ok) {
462
+ return null;
463
+ }
464
+ return {
465
+ ...edge,
466
+ node: entityResult.value,
467
+ };
468
+ }),
469
+ );
470
+ const edges = edgeResults.filter((edge) => edge !== null);
471
+ const pageInfo: PageInfo = {
472
+ ...pageResult.pageInfo,
473
+ startCursor: edges[0]?.cursor ?? null,
474
+ endCursor: edges[edges.length - 1]?.cursor ?? null,
475
+ };
476
+
477
+ return {
478
+ edges,
479
+ pageInfo,
480
+ };
481
+ }
482
+
483
+ /**
484
+ * Authorization-result-based version of the EnforcingKnexEntityLoader method by the same name.
485
+ * @returns The pagination cursor for the given entity.
486
+ */
487
+ getPaginationCursorForEntity(entity: TEntity): string {
488
+ return this.knexDataManager.getCursorForEntityID(entity.getID());
489
+ }
490
+ }
491
+
492
+ /**
493
+ * SQL query builder implementation for AuthorizationResultBasedKnexEntityLoader.
494
+ */
495
+ export class AuthorizationResultBasedSQLQueryBuilder<
496
+ TFields extends Record<string, any>,
497
+ TIDField extends keyof NonNullable<Pick<TFields, TSelectedFields>>,
498
+ TViewerContext extends ViewerContext,
499
+ TEntity extends ReadonlyEntity<TFields, TIDField, TViewerContext, TSelectedFields>,
500
+ TPrivacyPolicy extends EntityPrivacyPolicy<
501
+ TFields,
502
+ TIDField,
503
+ TViewerContext,
504
+ TEntity,
505
+ TSelectedFields
506
+ >,
507
+ TSelectedFields extends keyof TFields,
508
+ > extends BaseSQLQueryBuilder<TFields, TSelectedFields, Result<TEntity>> {
509
+ constructor(
510
+ private readonly knexDataManager: EntityKnexDataManager<TFields, TIDField>,
511
+ private readonly constructionUtils: EntityConstructionUtils<
512
+ TFields,
513
+ TIDField,
514
+ TViewerContext,
515
+ TEntity,
516
+ TPrivacyPolicy,
517
+ TSelectedFields
518
+ >,
519
+ private readonly queryContext: EntityQueryContext,
520
+ sqlFragment: SQLFragment<Pick<TFields, TSelectedFields>>,
521
+ modifiers: EntityLoaderQuerySelectionModifiers<TFields, TSelectedFields>,
522
+ ) {
523
+ super(sqlFragment, modifiers);
524
+ }
525
+
526
+ /**
527
+ * Execute the query and return results.
528
+ */
529
+ async executeInternalAsync(): Promise<readonly Result<TEntity>[]> {
530
+ const fieldObjects = await this.knexDataManager.loadManyBySQLFragmentAsync(
531
+ this.queryContext,
532
+ this.getSQLFragment(),
533
+ this.getModifiers(),
534
+ );
535
+ return await this.constructionUtils.constructAndAuthorizeEntitiesArrayAsync(fieldObjects);
536
+ }
537
+ }