@rvoh/psychic 2.2.1-alpha.2 → 2.2.2

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 (19) hide show
  1. package/dist/cjs/src/controller/index.js +4 -8
  2. package/dist/cjs/src/generate/helpers/generateControllerContent.js +2 -3
  3. package/dist/cjs/src/generate/helpers/generateResourceControllerSpecContent.js +24 -34
  4. package/dist/cjs/src/openapi-renderer/endpoint.js +19 -16
  5. package/dist/cjs/src/openapi-renderer/helpers/cursorPaginationParamOpenapiProperty.js +6 -0
  6. package/dist/cjs/src/openapi-renderer/helpers/{safelyAttachScrollPaginationParamsToBodySegment.js → safelyAttachCursorPaginationParamToRequestBodySegment.js} +3 -3
  7. package/dist/esm/src/controller/index.js +4 -8
  8. package/dist/esm/src/generate/helpers/generateControllerContent.js +2 -3
  9. package/dist/esm/src/generate/helpers/generateResourceControllerSpecContent.js +24 -34
  10. package/dist/esm/src/openapi-renderer/endpoint.js +19 -16
  11. package/dist/esm/src/openapi-renderer/helpers/cursorPaginationParamOpenapiProperty.js +6 -0
  12. package/dist/esm/src/openapi-renderer/helpers/{safelyAttachScrollPaginationParamsToBodySegment.js → safelyAttachCursorPaginationParamToRequestBodySegment.js} +3 -3
  13. package/dist/types/src/openapi-renderer/endpoint.d.ts +30 -7
  14. package/dist/types/src/openapi-renderer/helpers/cursorPaginationParamOpenapiProperty.d.ts +4 -0
  15. package/dist/types/src/openapi-renderer/helpers/{safelyAttachScrollPaginationParamsToBodySegment.d.ts → safelyAttachCursorPaginationParamToRequestBodySegment.d.ts} +1 -1
  16. package/package.json +3 -3
  17. package/dist/cjs/src/openapi-renderer/helpers/scrollPaginationCursorParamOpenapiProperty.js +0 -6
  18. package/dist/esm/src/openapi-renderer/helpers/scrollPaginationCursorParamOpenapiProperty.js +0 -6
  19. package/dist/types/src/openapi-renderer/helpers/scrollPaginationCursorParamOpenapiProperty.d.ts +0 -4
@@ -557,14 +557,10 @@ export default class PsychicController {
557
557
  return data.map(d => this.singleObjectJson(d, opts));
558
558
  //
559
559
  }
560
- else if (this.currentOpenapiRenderer?.['paginate'] && Array.isArray(paginatedResults)) {
561
- return {
562
- ...data,
563
- results: paginatedResults.map(result => this.singleObjectJson(result, opts)),
564
- };
565
- //
566
- }
567
- else if (this.currentOpenapiRenderer?.['scrollPaginate'] && Array.isArray(paginatedResults)) {
560
+ else if ((this.currentOpenapiRenderer?.['paginate'] ||
561
+ this.currentOpenapiRenderer?.['cursorPaginate'] ||
562
+ this.currentOpenapiRenderer?.['scrollPaginate']) &&
563
+ Array.isArray(paginatedResults)) {
568
564
  return {
569
565
  ...data,
570
566
  results: paginatedResults.map(result => this.singleObjectJson(result, opts)),
@@ -57,14 +57,13 @@ export default function generateControllerContent({ ancestorName, ancestorImport
57
57
  status: 200,
58
58
  tags: openApiTags,
59
59
  description: 'Paginated index of ${pluralize(modelClassName)}',
60
- scrollPaginate: true,
60
+ cursorPaginate: true,
61
61
  serializerKey: '${forAdmin ? 'adminSummary' : 'summary'}',
62
62
  })
63
63
  public async index() {
64
64
  // const ${pluralizedModelAttributeName} = await ${loadQueryBase}
65
65
  // .preloadFor('${forAdmin ? 'adminSummary' : 'summary'}')
66
- // .order({ createdAt: 'desc' })
67
- // .scrollPaginate({ cursor: this.castParam('cursor', 'string', { allowNull: true }) })
66
+ // .cursorPaginate({ cursor: this.castParam('cursor', 'string', { allowNull: true }) })
68
67
  // this.ok(${pluralizedModelAttributeName})
69
68
  }`;
70
69
  else
@@ -63,6 +63,7 @@ function processAttributes(columnsWithTypes, modelConfig, fullyQualifiedModelNam
63
63
  dateAttributeIncluded: false,
64
64
  datetimeAttributeIncluded: false,
65
65
  uuidAttributeIncluded: false,
66
+ uuidAttributes: [],
66
67
  uuidArrayAttributes: [],
67
68
  dreamImports: [],
68
69
  };
@@ -153,7 +154,7 @@ function processEnumAttribute({ attributeName, isArray, enumValues, dotNotationV
153
154
  attributeData.comparableOriginalAttributeKeyValues.push(`${attributeName}: ${dotNotationVariable},`);
154
155
  if (attributeName !== 'type') {
155
156
  attributeData.attributeCreationKeyValues.push(`${attributeName}: ${jsonify(originalEnumValue)},`);
156
- attributeData.attributeUpdateKeyValues.push(`${attributeName}: ${jsonify(updatedEnumValue)},`);
157
+ attributeData.attributeUpdateKeyValues.push({ attributeName, value: jsonify(updatedEnumValue) });
157
158
  attributeData.expectEqualOriginalValue.push(`expect(${dotNotationVariable}).toEqual(${jsonify(originalEnumValue)})`);
158
159
  attributeData.expectEqualUpdatedValue.push(`expect(${dotNotationVariable}).toEqual(${jsonify(updatedEnumValue)})`);
159
160
  }
@@ -164,7 +165,7 @@ function processStringAttribute({ attributeName, isArray, dotNotationVariable, f
164
165
  const originalStringValue = isArray ? [rawOriginalStringValue] : rawOriginalStringValue;
165
166
  const updatedStringValue = isArray ? [rawUpdatedStringValue] : rawUpdatedStringValue;
166
167
  attributeData.attributeCreationKeyValues.push(`${attributeName}: ${jsonify(originalStringValue)},`);
167
- attributeData.attributeUpdateKeyValues.push(`${attributeName}: ${jsonify(updatedStringValue)},`);
168
+ attributeData.attributeUpdateKeyValues.push({ attributeName, value: jsonify(updatedStringValue) });
168
169
  attributeData.comparableOriginalAttributeKeyValues.push(`${attributeName}: ${dotNotationVariable},`);
169
170
  attributeData.expectEqualOriginalValue.push(`expect(${dotNotationVariable}).toEqual(${jsonify(originalStringValue)})`);
170
171
  attributeData.expectEqualUpdatedValue.push(`expect(${dotNotationVariable}).toEqual(${jsonify(updatedStringValue)})`);
@@ -175,7 +176,7 @@ function processNumericAttribute({ attributeName, attributeType, isArray, dotNot
175
176
  const originalValue = isArray ? [rawOriginalValue] : rawOriginalValue;
176
177
  const updatedValue = isArray ? [rawUpdatedValue] : rawUpdatedValue;
177
178
  attributeData.attributeCreationKeyValues.push(`${attributeName}: ${jsonify(originalValue)},`);
178
- attributeData.attributeUpdateKeyValues.push(`${attributeName}: ${jsonify(updatedValue)},`);
179
+ attributeData.attributeUpdateKeyValues.push({ attributeName, value: jsonify(updatedValue) });
179
180
  attributeData.comparableOriginalAttributeKeyValues.push(`${attributeName}: ${dotNotationVariable},`);
180
181
  attributeData.expectEqualOriginalValue.push(`expect(${dotNotationVariable}).toEqual(${jsonify(originalValue)})`);
181
182
  attributeData.expectEqualUpdatedValue.push(`expect(${dotNotationVariable}).toEqual(${jsonify(updatedValue)})`);
@@ -184,7 +185,10 @@ function processDateAttribute({ attributeName, isArray, dotNotationVariable, att
184
185
  attributeData.dreamImports.push('CalendarDate');
185
186
  attributeData.dateAttributeIncluded = true;
186
187
  attributeData.attributeCreationKeyValues.push(`${attributeName}: ${isArray ? '[today.toISO()]' : 'today.toISO()'},`);
187
- attributeData.attributeUpdateKeyValues.push(`${attributeName}: ${isArray ? '[yesterday.toISO()]' : 'yesterday.toISO()'},`);
188
+ attributeData.attributeUpdateKeyValues.push({
189
+ attributeName,
190
+ value: `${isArray ? '[yesterday.toISO()]' : 'yesterday.toISO()'}`,
191
+ });
188
192
  attributeData.comparableOriginalAttributeKeyValues.push(`${attributeName}: ${dotNotationVariable}${isArray ? '.map(date => date.toISO())' : '.toISO()'},`);
189
193
  attributeData.expectEqualOriginalValue.push(`expect(${dotNotationVariable}${isArray ? '[0]' : ''}).toEqualCalendarDate(today)`);
190
194
  attributeData.expectEqualUpdatedValue.push(`expect(${dotNotationVariable}${isArray ? '[0]' : ''}).toEqualCalendarDate(yesterday)`);
@@ -193,13 +197,17 @@ function processDateTimeAttribute({ attributeName, isArray, dotNotationVariable,
193
197
  attributeData.dreamImports.push('DateTime');
194
198
  attributeData.datetimeAttributeIncluded = true;
195
199
  attributeData.attributeCreationKeyValues.push(`${attributeName}: ${isArray ? '[now.toISO()]' : 'now.toISO()'},`);
196
- attributeData.attributeUpdateKeyValues.push(`${attributeName}: ${isArray ? '[lastHour.toISO()]' : 'lastHour.toISO()'},`);
200
+ attributeData.attributeUpdateKeyValues.push({
201
+ attributeName,
202
+ value: `${isArray ? '[lastHour.toISO()]' : 'lastHour.toISO()'}`,
203
+ });
197
204
  attributeData.comparableOriginalAttributeKeyValues.push(`${attributeName}: ${dotNotationVariable}${isArray ? '.map(datetime => datetime.toISO())' : '.toISO()'},`);
198
205
  attributeData.expectEqualOriginalValue.push(`expect(${dotNotationVariable}${isArray ? '[0]' : ''}).toEqualDateTime(now)`);
199
206
  attributeData.expectEqualUpdatedValue.push(`expect(${dotNotationVariable}${isArray ? '[0]' : ''}).toEqualDateTime(lastHour)`);
200
207
  }
201
208
  function processUuidAttribute({ attributeName, isArray, dotNotationVariable, attributeData, }) {
202
209
  attributeData.uuidAttributeIncluded = true;
210
+ attributeData.uuidAttributes.push(attributeName);
203
211
  if (isArray) {
204
212
  attributeData.uuidArrayAttributes.push(attributeName);
205
213
  }
@@ -208,7 +216,7 @@ function processUuidAttribute({ attributeName, isArray, dotNotationVariable, att
208
216
  const uuidValue = attributeName;
209
217
  const newUuidValue = newUuidVariableName;
210
218
  attributeData.attributeCreationKeyValues.push(`${attributeName}: ${uuidValue},`);
211
- attributeData.attributeUpdateKeyValues.push(`${attributeName}: ${newUuidValue},`);
219
+ attributeData.attributeUpdateKeyValues.push({ attributeName, value: newUuidValue });
212
220
  attributeData.comparableOriginalAttributeKeyValues.push(`${attributeName}: ${dotNotationVariable},`);
213
221
  attributeData.expectEqualOriginalValue.push(`expect(${dotNotationVariable}).toEqual(${attributeName})`);
214
222
  attributeData.expectEqualUpdatedValue.push(`expect(${dotNotationVariable}).toEqual(${newUuidVariableName})`);
@@ -336,13 +344,7 @@ function generateCreateActionSpec(options) {
336
344
  return '';
337
345
  const { path, pathParams, modelConfig, fullyQualifiedModelName, forAdmin, singular, attributeData } = options;
338
346
  const subjectFunctionName = `create${modelConfig.modelClassName}`;
339
- const uuidAttributeNames = attributeData.attributeCreationKeyValues
340
- .map(kv => {
341
- const match = kv.match(/^(\w+):\s*\1,?$/);
342
- return match?.[1];
343
- })
344
- .filter((name) => Boolean(name));
345
- const uuidSetup = uuidAttributeNames
347
+ const uuidSetup = attributeData.uuidAttributes
346
348
  .map(attrName => {
347
349
  const isArray = attributeData.uuidArrayAttributes.includes(attrName);
348
350
  return isArray ? `const ${attrName} = [randomUUID()]` : `const ${attrName} = randomUUID()`;
@@ -393,17 +395,7 @@ function generateUpdateActionSpec(options) {
393
395
  return '';
394
396
  const { path, pathParams, modelConfig, fullyQualifiedModelName, singular, forAdmin, attributeData } = options;
395
397
  const subjectFunctionName = `update${modelConfig.modelClassName}`;
396
- const uuidAttributeNames = attributeData.attributeUpdateKeyValues
397
- .filter(kv => {
398
- const match = kv.match(/^(\w+):\s*new([A-Z]\w+),?$/);
399
- return match && match[1] && camelize(match[1]) === camelize(match[2]);
400
- })
401
- .map(kv => {
402
- const match = kv.match(/^(\w+):/);
403
- return match?.[1];
404
- })
405
- .filter(Boolean);
406
- const uuidSetup = uuidAttributeNames
398
+ const uuidSetup = attributeData.uuidAttributes
407
399
  .map(attrName => {
408
400
  const isArray = attributeData.uuidArrayAttributes.includes(attrName);
409
401
  return isArray
@@ -461,15 +453,13 @@ function generateUpdateActionSpec(options) {
461
453
  ${attributeData.attributeUpdateKeyValues.length
462
454
  ? attributeData.attributeUpdateKeyValues
463
455
  .map(kv => {
464
- const match = kv.match(/^(\w+):\s*new([A-Z]\w+),?$/);
465
- if (match && match[1]) {
466
- const attrName = match[1];
467
- const isArray = attributeData.uuidArrayAttributes.includes(attrName);
468
- return isArray
469
- ? kv.replace(/\bnew[A-Z]\w+\b/g, '[randomUUID()]')
470
- : kv.replace(/\bnew[A-Z]\w+\b/g, 'randomUUID()');
471
- }
472
- return kv;
456
+ const isUuid = attributeData.uuidAttributes.includes(kv.attributeName);
457
+ const value = isUuid
458
+ ? attributeData.uuidArrayAttributes.includes(kv.attributeName)
459
+ ? '[randomUUID()]'
460
+ : 'randomUUID()'
461
+ : kv.value;
462
+ return `${kv.attributeName}: ${value},`;
473
463
  })
474
464
  .join('\n ')
475
465
  : ''}
@@ -487,7 +477,7 @@ function generateUpdateActionSpec(options) {
487
477
  ${modelConfig.simpleCreationCommand}
488
478
 
489
479
  await ${subjectFunctionName}(${singular ? '' : `${modelConfig.modelVariableName}, `}{
490
- ${attributeData.attributeUpdateKeyValues.length ? attributeData.attributeUpdateKeyValues.join('\n ') : ''}
480
+ ${attributeData.attributeUpdateKeyValues.length ? attributeData.attributeUpdateKeyValues.map(kv => `${kv.attributeName}: ${kv.value},`).join('\n ') : ''}
491
481
  }, 204)
492
482
 
493
483
  await ${modelConfig.modelVariableName}.reload()
@@ -9,13 +9,13 @@ import PsychicApp from '../psychic-app/index.js';
9
9
  import openapiParamNamesForDreamClass from '../server/helpers/openapiParamNamesForDreamClass.js';
10
10
  import OpenapiSegmentExpander from './body-segment.js';
11
11
  import { DEFAULT_OPENAPI_RESPONSES } from './defaults.js';
12
+ import cursorPaginationParamOpenapiProperty from './helpers/cursorPaginationParamOpenapiProperty.js';
12
13
  import { dreamColumnOpenapiShape } from './helpers/dreamAttributeOpenapiShape.js';
13
14
  import openapiOpts from './helpers/openapiOpts.js';
14
15
  import openapiRoute from './helpers/openapiRoute.js';
15
16
  import paginationPageParamOpenapiProperty from './helpers/paginationPageParamOpenapiProperty.js';
17
+ import safelyAttachCursorPaginationParamToRequestBodySegment from './helpers/safelyAttachCursorPaginationParamToRequestBodySegment.js';
16
18
  import safelyAttachPaginationParamToRequestBodySegment from './helpers/safelyAttachPaginationParamsToBodySegment.js';
17
- import safelyAttachScrollPaginationParamToRequestBodySegment from './helpers/safelyAttachScrollPaginationParamsToBodySegment.js';
18
- import scrollPaginationCursorParamOpenapiProperty from './helpers/scrollPaginationCursorParamOpenapiProperty.js';
19
19
  import SerializerOpenapiRenderer from './SerializerOpenapiRenderer.js';
20
20
  export default class OpenapiEndpointRenderer {
21
21
  dreamsOrSerializers;
@@ -23,6 +23,7 @@ export default class OpenapiEndpointRenderer {
23
23
  action;
24
24
  many;
25
25
  paginate;
26
+ cursorPaginate;
26
27
  scrollPaginate;
27
28
  responses;
28
29
  serializerKey;
@@ -53,7 +54,7 @@ export default class OpenapiEndpointRenderer {
53
54
  * const json = JSON.encode(openapiJsonContents, null, 2)
54
55
  * ```
55
56
  */
56
- constructor(dreamsOrSerializers, controllerClass, action, { requestBody, headers, many, paginate, scrollPaginate, query, responses, serializerKey, status, tags, security, pathParams, description, summary, omitDefaultHeaders, omitDefaultResponses, defaultResponse, validate, } = {}) {
57
+ constructor(dreamsOrSerializers, controllerClass, action, { requestBody, headers, many, paginate, cursorPaginate, scrollPaginate, query, responses, serializerKey, status, tags, security, pathParams, description, summary, omitDefaultHeaders, omitDefaultResponses, defaultResponse, validate, } = {}) {
57
58
  this.dreamsOrSerializers = dreamsOrSerializers;
58
59
  this.controllerClass = controllerClass;
59
60
  this.action = action;
@@ -61,6 +62,7 @@ export default class OpenapiEndpointRenderer {
61
62
  this.headers = headers;
62
63
  this.many = many;
63
64
  this.paginate = paginate;
65
+ this.cursorPaginate = cursorPaginate;
64
66
  this.scrollPaginate = scrollPaginate;
65
67
  this.query = query;
66
68
  this.responses = responses;
@@ -404,13 +406,14 @@ export default class OpenapiEndpointRenderer {
404
406
  },
405
407
  });
406
408
  }
409
+ const cursorPaginationName = this.cursorPaginate === true ? 'cursor' : this.cursorPaginate?.query;
407
410
  const scrollPaginationName = this.scrollPaginate === true ? 'cursor' : this.scrollPaginate?.query;
408
- if (scrollPaginationName) {
411
+ if (cursorPaginationName || scrollPaginationName) {
409
412
  queryParams.push({
410
413
  in: 'query',
411
414
  required: false,
412
- name: scrollPaginationName,
413
- description: 'Scroll pagination cursor',
415
+ name: (cursorPaginationName || scrollPaginationName),
416
+ description: 'Pagination cursor',
414
417
  allowReserved: true,
415
418
  schema: {
416
419
  type: ['string', 'null'],
@@ -442,9 +445,9 @@ export default class OpenapiEndpointRenderer {
442
445
  if (bodyPaginationPageParam) {
443
446
  schema = safelyAttachPaginationParamToRequestBodySegment(bodyPaginationPageParam, schema);
444
447
  }
445
- const bodyScrollPaginationCursorParam = this.scrollPaginate?.body;
446
- if (bodyScrollPaginationCursorParam) {
447
- schema = safelyAttachScrollPaginationParamToRequestBodySegment(bodyScrollPaginationCursorParam, schema);
448
+ const bodyCursorPaginationParam = this.cursorPaginate?.body ?? this.scrollPaginate?.body;
449
+ if (bodyCursorPaginationParam) {
450
+ schema = safelyAttachCursorPaginationParamToRequestBodySegment(bodyCursorPaginationParam, schema);
448
451
  }
449
452
  if (!schema)
450
453
  return undefined;
@@ -458,7 +461,7 @@ export default class OpenapiEndpointRenderer {
458
461
  }
459
462
  defaultRequestBody() {
460
463
  const bodyPaginationPageParam = this.paginate?.body;
461
- const bodyScrollPaginationCursorParam = this.scrollPaginate?.body;
464
+ const bodyCursorPaginationParam = this.cursorPaginate?.body ?? this.scrollPaginate?.body;
462
465
  if (bodyPaginationPageParam) {
463
466
  return {
464
467
  content: {
@@ -473,7 +476,7 @@ export default class OpenapiEndpointRenderer {
473
476
  },
474
477
  };
475
478
  }
476
- else if (bodyScrollPaginationCursorParam) {
479
+ else if (bodyCursorPaginationParam) {
477
480
  return {
478
481
  content: {
479
482
  'application/json': {
@@ -481,7 +484,7 @@ export default class OpenapiEndpointRenderer {
481
484
  type: 'object',
482
485
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
483
486
  properties: {
484
- [bodyScrollPaginationCursorParam]: scrollPaginationCursorParamOpenapiProperty(),
487
+ [bodyCursorPaginationParam]: cursorPaginationParamOpenapiProperty(),
485
488
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
486
489
  },
487
490
  },
@@ -559,9 +562,9 @@ export default class OpenapiEndpointRenderer {
559
562
  if (bodyPaginationPageParam) {
560
563
  processedSchema = safelyAttachPaginationParamToRequestBodySegment(bodyPaginationPageParam, processedSchema);
561
564
  }
562
- const bodyScrollPaginationCursorParam = this.scrollPaginate?.body;
563
- if (bodyScrollPaginationCursorParam) {
564
- processedSchema = safelyAttachScrollPaginationParamToRequestBodySegment(bodyScrollPaginationCursorParam, processedSchema);
565
+ const bodyCursorPaginationParam = this.cursorPaginate?.body ?? this.scrollPaginate?.body;
566
+ if (bodyCursorPaginationParam) {
567
+ processedSchema = safelyAttachCursorPaginationParamToRequestBodySegment(bodyCursorPaginationParam, processedSchema);
565
568
  }
566
569
  return {
567
570
  content: {
@@ -759,7 +762,7 @@ export default class OpenapiEndpointRenderer {
759
762
  },
760
763
  },
761
764
  };
762
- if (this.scrollPaginate)
765
+ if (this.cursorPaginate || this.scrollPaginate)
763
766
  return {
764
767
  type: 'object',
765
768
  required: ['cursor', 'results'],
@@ -0,0 +1,6 @@
1
+ export default function cursorPaginationParamOpenapiProperty() {
2
+ return {
3
+ type: ['string', 'null'],
4
+ description: 'Pagination cursor',
5
+ };
6
+ }
@@ -1,4 +1,4 @@
1
- import scrollPaginationCursorParamOpenapiProperty from './scrollPaginationCursorParamOpenapiProperty.js';
1
+ import cursorPaginationParamOpenapiProperty from './cursorPaginationParamOpenapiProperty.js';
2
2
  /**
3
3
  * @internal
4
4
  *
@@ -13,7 +13,7 @@ import scrollPaginationCursorParamOpenapiProperty from './scrollPaginationCursor
13
13
  * If neither of these apply, it will simply return
14
14
  * what was given to it, without any modifications
15
15
  */
16
- export default function safelyAttachScrollPaginationParamToRequestBodySegment(paramName, bodySegment) {
16
+ export default function safelyAttachCursorPaginationParamToRequestBodySegment(paramName, bodySegment) {
17
17
  bodySegment ||= {
18
18
  type: 'object',
19
19
  properties: {},
@@ -23,7 +23,7 @@ export default function safelyAttachScrollPaginationParamToRequestBodySegment(pa
23
23
  ;
24
24
  bodySegment.properties = {
25
25
  ...bodySegment.properties,
26
- [paramName]: scrollPaginationCursorParamOpenapiProperty(),
26
+ [paramName]: cursorPaginationParamOpenapiProperty(),
27
27
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
28
  };
29
29
  }
@@ -557,14 +557,10 @@ export default class PsychicController {
557
557
  return data.map(d => this.singleObjectJson(d, opts));
558
558
  //
559
559
  }
560
- else if (this.currentOpenapiRenderer?.['paginate'] && Array.isArray(paginatedResults)) {
561
- return {
562
- ...data,
563
- results: paginatedResults.map(result => this.singleObjectJson(result, opts)),
564
- };
565
- //
566
- }
567
- else if (this.currentOpenapiRenderer?.['scrollPaginate'] && Array.isArray(paginatedResults)) {
560
+ else if ((this.currentOpenapiRenderer?.['paginate'] ||
561
+ this.currentOpenapiRenderer?.['cursorPaginate'] ||
562
+ this.currentOpenapiRenderer?.['scrollPaginate']) &&
563
+ Array.isArray(paginatedResults)) {
568
564
  return {
569
565
  ...data,
570
566
  results: paginatedResults.map(result => this.singleObjectJson(result, opts)),
@@ -57,14 +57,13 @@ export default function generateControllerContent({ ancestorName, ancestorImport
57
57
  status: 200,
58
58
  tags: openApiTags,
59
59
  description: 'Paginated index of ${pluralize(modelClassName)}',
60
- scrollPaginate: true,
60
+ cursorPaginate: true,
61
61
  serializerKey: '${forAdmin ? 'adminSummary' : 'summary'}',
62
62
  })
63
63
  public async index() {
64
64
  // const ${pluralizedModelAttributeName} = await ${loadQueryBase}
65
65
  // .preloadFor('${forAdmin ? 'adminSummary' : 'summary'}')
66
- // .order({ createdAt: 'desc' })
67
- // .scrollPaginate({ cursor: this.castParam('cursor', 'string', { allowNull: true }) })
66
+ // .cursorPaginate({ cursor: this.castParam('cursor', 'string', { allowNull: true }) })
68
67
  // this.ok(${pluralizedModelAttributeName})
69
68
  }`;
70
69
  else
@@ -63,6 +63,7 @@ function processAttributes(columnsWithTypes, modelConfig, fullyQualifiedModelNam
63
63
  dateAttributeIncluded: false,
64
64
  datetimeAttributeIncluded: false,
65
65
  uuidAttributeIncluded: false,
66
+ uuidAttributes: [],
66
67
  uuidArrayAttributes: [],
67
68
  dreamImports: [],
68
69
  };
@@ -153,7 +154,7 @@ function processEnumAttribute({ attributeName, isArray, enumValues, dotNotationV
153
154
  attributeData.comparableOriginalAttributeKeyValues.push(`${attributeName}: ${dotNotationVariable},`);
154
155
  if (attributeName !== 'type') {
155
156
  attributeData.attributeCreationKeyValues.push(`${attributeName}: ${jsonify(originalEnumValue)},`);
156
- attributeData.attributeUpdateKeyValues.push(`${attributeName}: ${jsonify(updatedEnumValue)},`);
157
+ attributeData.attributeUpdateKeyValues.push({ attributeName, value: jsonify(updatedEnumValue) });
157
158
  attributeData.expectEqualOriginalValue.push(`expect(${dotNotationVariable}).toEqual(${jsonify(originalEnumValue)})`);
158
159
  attributeData.expectEqualUpdatedValue.push(`expect(${dotNotationVariable}).toEqual(${jsonify(updatedEnumValue)})`);
159
160
  }
@@ -164,7 +165,7 @@ function processStringAttribute({ attributeName, isArray, dotNotationVariable, f
164
165
  const originalStringValue = isArray ? [rawOriginalStringValue] : rawOriginalStringValue;
165
166
  const updatedStringValue = isArray ? [rawUpdatedStringValue] : rawUpdatedStringValue;
166
167
  attributeData.attributeCreationKeyValues.push(`${attributeName}: ${jsonify(originalStringValue)},`);
167
- attributeData.attributeUpdateKeyValues.push(`${attributeName}: ${jsonify(updatedStringValue)},`);
168
+ attributeData.attributeUpdateKeyValues.push({ attributeName, value: jsonify(updatedStringValue) });
168
169
  attributeData.comparableOriginalAttributeKeyValues.push(`${attributeName}: ${dotNotationVariable},`);
169
170
  attributeData.expectEqualOriginalValue.push(`expect(${dotNotationVariable}).toEqual(${jsonify(originalStringValue)})`);
170
171
  attributeData.expectEqualUpdatedValue.push(`expect(${dotNotationVariable}).toEqual(${jsonify(updatedStringValue)})`);
@@ -175,7 +176,7 @@ function processNumericAttribute({ attributeName, attributeType, isArray, dotNot
175
176
  const originalValue = isArray ? [rawOriginalValue] : rawOriginalValue;
176
177
  const updatedValue = isArray ? [rawUpdatedValue] : rawUpdatedValue;
177
178
  attributeData.attributeCreationKeyValues.push(`${attributeName}: ${jsonify(originalValue)},`);
178
- attributeData.attributeUpdateKeyValues.push(`${attributeName}: ${jsonify(updatedValue)},`);
179
+ attributeData.attributeUpdateKeyValues.push({ attributeName, value: jsonify(updatedValue) });
179
180
  attributeData.comparableOriginalAttributeKeyValues.push(`${attributeName}: ${dotNotationVariable},`);
180
181
  attributeData.expectEqualOriginalValue.push(`expect(${dotNotationVariable}).toEqual(${jsonify(originalValue)})`);
181
182
  attributeData.expectEqualUpdatedValue.push(`expect(${dotNotationVariable}).toEqual(${jsonify(updatedValue)})`);
@@ -184,7 +185,10 @@ function processDateAttribute({ attributeName, isArray, dotNotationVariable, att
184
185
  attributeData.dreamImports.push('CalendarDate');
185
186
  attributeData.dateAttributeIncluded = true;
186
187
  attributeData.attributeCreationKeyValues.push(`${attributeName}: ${isArray ? '[today.toISO()]' : 'today.toISO()'},`);
187
- attributeData.attributeUpdateKeyValues.push(`${attributeName}: ${isArray ? '[yesterday.toISO()]' : 'yesterday.toISO()'},`);
188
+ attributeData.attributeUpdateKeyValues.push({
189
+ attributeName,
190
+ value: `${isArray ? '[yesterday.toISO()]' : 'yesterday.toISO()'}`,
191
+ });
188
192
  attributeData.comparableOriginalAttributeKeyValues.push(`${attributeName}: ${dotNotationVariable}${isArray ? '.map(date => date.toISO())' : '.toISO()'},`);
189
193
  attributeData.expectEqualOriginalValue.push(`expect(${dotNotationVariable}${isArray ? '[0]' : ''}).toEqualCalendarDate(today)`);
190
194
  attributeData.expectEqualUpdatedValue.push(`expect(${dotNotationVariable}${isArray ? '[0]' : ''}).toEqualCalendarDate(yesterday)`);
@@ -193,13 +197,17 @@ function processDateTimeAttribute({ attributeName, isArray, dotNotationVariable,
193
197
  attributeData.dreamImports.push('DateTime');
194
198
  attributeData.datetimeAttributeIncluded = true;
195
199
  attributeData.attributeCreationKeyValues.push(`${attributeName}: ${isArray ? '[now.toISO()]' : 'now.toISO()'},`);
196
- attributeData.attributeUpdateKeyValues.push(`${attributeName}: ${isArray ? '[lastHour.toISO()]' : 'lastHour.toISO()'},`);
200
+ attributeData.attributeUpdateKeyValues.push({
201
+ attributeName,
202
+ value: `${isArray ? '[lastHour.toISO()]' : 'lastHour.toISO()'}`,
203
+ });
197
204
  attributeData.comparableOriginalAttributeKeyValues.push(`${attributeName}: ${dotNotationVariable}${isArray ? '.map(datetime => datetime.toISO())' : '.toISO()'},`);
198
205
  attributeData.expectEqualOriginalValue.push(`expect(${dotNotationVariable}${isArray ? '[0]' : ''}).toEqualDateTime(now)`);
199
206
  attributeData.expectEqualUpdatedValue.push(`expect(${dotNotationVariable}${isArray ? '[0]' : ''}).toEqualDateTime(lastHour)`);
200
207
  }
201
208
  function processUuidAttribute({ attributeName, isArray, dotNotationVariable, attributeData, }) {
202
209
  attributeData.uuidAttributeIncluded = true;
210
+ attributeData.uuidAttributes.push(attributeName);
203
211
  if (isArray) {
204
212
  attributeData.uuidArrayAttributes.push(attributeName);
205
213
  }
@@ -208,7 +216,7 @@ function processUuidAttribute({ attributeName, isArray, dotNotationVariable, att
208
216
  const uuidValue = attributeName;
209
217
  const newUuidValue = newUuidVariableName;
210
218
  attributeData.attributeCreationKeyValues.push(`${attributeName}: ${uuidValue},`);
211
- attributeData.attributeUpdateKeyValues.push(`${attributeName}: ${newUuidValue},`);
219
+ attributeData.attributeUpdateKeyValues.push({ attributeName, value: newUuidValue });
212
220
  attributeData.comparableOriginalAttributeKeyValues.push(`${attributeName}: ${dotNotationVariable},`);
213
221
  attributeData.expectEqualOriginalValue.push(`expect(${dotNotationVariable}).toEqual(${attributeName})`);
214
222
  attributeData.expectEqualUpdatedValue.push(`expect(${dotNotationVariable}).toEqual(${newUuidVariableName})`);
@@ -336,13 +344,7 @@ function generateCreateActionSpec(options) {
336
344
  return '';
337
345
  const { path, pathParams, modelConfig, fullyQualifiedModelName, forAdmin, singular, attributeData } = options;
338
346
  const subjectFunctionName = `create${modelConfig.modelClassName}`;
339
- const uuidAttributeNames = attributeData.attributeCreationKeyValues
340
- .map(kv => {
341
- const match = kv.match(/^(\w+):\s*\1,?$/);
342
- return match?.[1];
343
- })
344
- .filter((name) => Boolean(name));
345
- const uuidSetup = uuidAttributeNames
347
+ const uuidSetup = attributeData.uuidAttributes
346
348
  .map(attrName => {
347
349
  const isArray = attributeData.uuidArrayAttributes.includes(attrName);
348
350
  return isArray ? `const ${attrName} = [randomUUID()]` : `const ${attrName} = randomUUID()`;
@@ -393,17 +395,7 @@ function generateUpdateActionSpec(options) {
393
395
  return '';
394
396
  const { path, pathParams, modelConfig, fullyQualifiedModelName, singular, forAdmin, attributeData } = options;
395
397
  const subjectFunctionName = `update${modelConfig.modelClassName}`;
396
- const uuidAttributeNames = attributeData.attributeUpdateKeyValues
397
- .filter(kv => {
398
- const match = kv.match(/^(\w+):\s*new([A-Z]\w+),?$/);
399
- return match && match[1] && camelize(match[1]) === camelize(match[2]);
400
- })
401
- .map(kv => {
402
- const match = kv.match(/^(\w+):/);
403
- return match?.[1];
404
- })
405
- .filter(Boolean);
406
- const uuidSetup = uuidAttributeNames
398
+ const uuidSetup = attributeData.uuidAttributes
407
399
  .map(attrName => {
408
400
  const isArray = attributeData.uuidArrayAttributes.includes(attrName);
409
401
  return isArray
@@ -461,15 +453,13 @@ function generateUpdateActionSpec(options) {
461
453
  ${attributeData.attributeUpdateKeyValues.length
462
454
  ? attributeData.attributeUpdateKeyValues
463
455
  .map(kv => {
464
- const match = kv.match(/^(\w+):\s*new([A-Z]\w+),?$/);
465
- if (match && match[1]) {
466
- const attrName = match[1];
467
- const isArray = attributeData.uuidArrayAttributes.includes(attrName);
468
- return isArray
469
- ? kv.replace(/\bnew[A-Z]\w+\b/g, '[randomUUID()]')
470
- : kv.replace(/\bnew[A-Z]\w+\b/g, 'randomUUID()');
471
- }
472
- return kv;
456
+ const isUuid = attributeData.uuidAttributes.includes(kv.attributeName);
457
+ const value = isUuid
458
+ ? attributeData.uuidArrayAttributes.includes(kv.attributeName)
459
+ ? '[randomUUID()]'
460
+ : 'randomUUID()'
461
+ : kv.value;
462
+ return `${kv.attributeName}: ${value},`;
473
463
  })
474
464
  .join('\n ')
475
465
  : ''}
@@ -487,7 +477,7 @@ function generateUpdateActionSpec(options) {
487
477
  ${modelConfig.simpleCreationCommand}
488
478
 
489
479
  await ${subjectFunctionName}(${singular ? '' : `${modelConfig.modelVariableName}, `}{
490
- ${attributeData.attributeUpdateKeyValues.length ? attributeData.attributeUpdateKeyValues.join('\n ') : ''}
480
+ ${attributeData.attributeUpdateKeyValues.length ? attributeData.attributeUpdateKeyValues.map(kv => `${kv.attributeName}: ${kv.value},`).join('\n ') : ''}
491
481
  }, 204)
492
482
 
493
483
  await ${modelConfig.modelVariableName}.reload()
@@ -9,13 +9,13 @@ import PsychicApp from '../psychic-app/index.js';
9
9
  import openapiParamNamesForDreamClass from '../server/helpers/openapiParamNamesForDreamClass.js';
10
10
  import OpenapiSegmentExpander from './body-segment.js';
11
11
  import { DEFAULT_OPENAPI_RESPONSES } from './defaults.js';
12
+ import cursorPaginationParamOpenapiProperty from './helpers/cursorPaginationParamOpenapiProperty.js';
12
13
  import { dreamColumnOpenapiShape } from './helpers/dreamAttributeOpenapiShape.js';
13
14
  import openapiOpts from './helpers/openapiOpts.js';
14
15
  import openapiRoute from './helpers/openapiRoute.js';
15
16
  import paginationPageParamOpenapiProperty from './helpers/paginationPageParamOpenapiProperty.js';
17
+ import safelyAttachCursorPaginationParamToRequestBodySegment from './helpers/safelyAttachCursorPaginationParamToRequestBodySegment.js';
16
18
  import safelyAttachPaginationParamToRequestBodySegment from './helpers/safelyAttachPaginationParamsToBodySegment.js';
17
- import safelyAttachScrollPaginationParamToRequestBodySegment from './helpers/safelyAttachScrollPaginationParamsToBodySegment.js';
18
- import scrollPaginationCursorParamOpenapiProperty from './helpers/scrollPaginationCursorParamOpenapiProperty.js';
19
19
  import SerializerOpenapiRenderer from './SerializerOpenapiRenderer.js';
20
20
  export default class OpenapiEndpointRenderer {
21
21
  dreamsOrSerializers;
@@ -23,6 +23,7 @@ export default class OpenapiEndpointRenderer {
23
23
  action;
24
24
  many;
25
25
  paginate;
26
+ cursorPaginate;
26
27
  scrollPaginate;
27
28
  responses;
28
29
  serializerKey;
@@ -53,7 +54,7 @@ export default class OpenapiEndpointRenderer {
53
54
  * const json = JSON.encode(openapiJsonContents, null, 2)
54
55
  * ```
55
56
  */
56
- constructor(dreamsOrSerializers, controllerClass, action, { requestBody, headers, many, paginate, scrollPaginate, query, responses, serializerKey, status, tags, security, pathParams, description, summary, omitDefaultHeaders, omitDefaultResponses, defaultResponse, validate, } = {}) {
57
+ constructor(dreamsOrSerializers, controllerClass, action, { requestBody, headers, many, paginate, cursorPaginate, scrollPaginate, query, responses, serializerKey, status, tags, security, pathParams, description, summary, omitDefaultHeaders, omitDefaultResponses, defaultResponse, validate, } = {}) {
57
58
  this.dreamsOrSerializers = dreamsOrSerializers;
58
59
  this.controllerClass = controllerClass;
59
60
  this.action = action;
@@ -61,6 +62,7 @@ export default class OpenapiEndpointRenderer {
61
62
  this.headers = headers;
62
63
  this.many = many;
63
64
  this.paginate = paginate;
65
+ this.cursorPaginate = cursorPaginate;
64
66
  this.scrollPaginate = scrollPaginate;
65
67
  this.query = query;
66
68
  this.responses = responses;
@@ -404,13 +406,14 @@ export default class OpenapiEndpointRenderer {
404
406
  },
405
407
  });
406
408
  }
409
+ const cursorPaginationName = this.cursorPaginate === true ? 'cursor' : this.cursorPaginate?.query;
407
410
  const scrollPaginationName = this.scrollPaginate === true ? 'cursor' : this.scrollPaginate?.query;
408
- if (scrollPaginationName) {
411
+ if (cursorPaginationName || scrollPaginationName) {
409
412
  queryParams.push({
410
413
  in: 'query',
411
414
  required: false,
412
- name: scrollPaginationName,
413
- description: 'Scroll pagination cursor',
415
+ name: (cursorPaginationName || scrollPaginationName),
416
+ description: 'Pagination cursor',
414
417
  allowReserved: true,
415
418
  schema: {
416
419
  type: ['string', 'null'],
@@ -442,9 +445,9 @@ export default class OpenapiEndpointRenderer {
442
445
  if (bodyPaginationPageParam) {
443
446
  schema = safelyAttachPaginationParamToRequestBodySegment(bodyPaginationPageParam, schema);
444
447
  }
445
- const bodyScrollPaginationCursorParam = this.scrollPaginate?.body;
446
- if (bodyScrollPaginationCursorParam) {
447
- schema = safelyAttachScrollPaginationParamToRequestBodySegment(bodyScrollPaginationCursorParam, schema);
448
+ const bodyCursorPaginationParam = this.cursorPaginate?.body ?? this.scrollPaginate?.body;
449
+ if (bodyCursorPaginationParam) {
450
+ schema = safelyAttachCursorPaginationParamToRequestBodySegment(bodyCursorPaginationParam, schema);
448
451
  }
449
452
  if (!schema)
450
453
  return undefined;
@@ -458,7 +461,7 @@ export default class OpenapiEndpointRenderer {
458
461
  }
459
462
  defaultRequestBody() {
460
463
  const bodyPaginationPageParam = this.paginate?.body;
461
- const bodyScrollPaginationCursorParam = this.scrollPaginate?.body;
464
+ const bodyCursorPaginationParam = this.cursorPaginate?.body ?? this.scrollPaginate?.body;
462
465
  if (bodyPaginationPageParam) {
463
466
  return {
464
467
  content: {
@@ -473,7 +476,7 @@ export default class OpenapiEndpointRenderer {
473
476
  },
474
477
  };
475
478
  }
476
- else if (bodyScrollPaginationCursorParam) {
479
+ else if (bodyCursorPaginationParam) {
477
480
  return {
478
481
  content: {
479
482
  'application/json': {
@@ -481,7 +484,7 @@ export default class OpenapiEndpointRenderer {
481
484
  type: 'object',
482
485
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
483
486
  properties: {
484
- [bodyScrollPaginationCursorParam]: scrollPaginationCursorParamOpenapiProperty(),
487
+ [bodyCursorPaginationParam]: cursorPaginationParamOpenapiProperty(),
485
488
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
486
489
  },
487
490
  },
@@ -559,9 +562,9 @@ export default class OpenapiEndpointRenderer {
559
562
  if (bodyPaginationPageParam) {
560
563
  processedSchema = safelyAttachPaginationParamToRequestBodySegment(bodyPaginationPageParam, processedSchema);
561
564
  }
562
- const bodyScrollPaginationCursorParam = this.scrollPaginate?.body;
563
- if (bodyScrollPaginationCursorParam) {
564
- processedSchema = safelyAttachScrollPaginationParamToRequestBodySegment(bodyScrollPaginationCursorParam, processedSchema);
565
+ const bodyCursorPaginationParam = this.cursorPaginate?.body ?? this.scrollPaginate?.body;
566
+ if (bodyCursorPaginationParam) {
567
+ processedSchema = safelyAttachCursorPaginationParamToRequestBodySegment(bodyCursorPaginationParam, processedSchema);
565
568
  }
566
569
  return {
567
570
  content: {
@@ -759,7 +762,7 @@ export default class OpenapiEndpointRenderer {
759
762
  },
760
763
  },
761
764
  };
762
- if (this.scrollPaginate)
765
+ if (this.cursorPaginate || this.scrollPaginate)
763
766
  return {
764
767
  type: 'object',
765
768
  required: ['cursor', 'results'],
@@ -0,0 +1,6 @@
1
+ export default function cursorPaginationParamOpenapiProperty() {
2
+ return {
3
+ type: ['string', 'null'],
4
+ description: 'Pagination cursor',
5
+ };
6
+ }
@@ -1,4 +1,4 @@
1
- import scrollPaginationCursorParamOpenapiProperty from './scrollPaginationCursorParamOpenapiProperty.js';
1
+ import cursorPaginationParamOpenapiProperty from './cursorPaginationParamOpenapiProperty.js';
2
2
  /**
3
3
  * @internal
4
4
  *
@@ -13,7 +13,7 @@ import scrollPaginationCursorParamOpenapiProperty from './scrollPaginationCursor
13
13
  * If neither of these apply, it will simply return
14
14
  * what was given to it, without any modifications
15
15
  */
16
- export default function safelyAttachScrollPaginationParamToRequestBodySegment(paramName, bodySegment) {
16
+ export default function safelyAttachCursorPaginationParamToRequestBodySegment(paramName, bodySegment) {
17
17
  bodySegment ||= {
18
18
  type: 'object',
19
19
  properties: {},
@@ -23,7 +23,7 @@ export default function safelyAttachScrollPaginationParamToRequestBodySegment(pa
23
23
  ;
24
24
  bodySegment.properties = {
25
25
  ...bodySegment.properties,
26
- [paramName]: scrollPaginationCursorParamOpenapiProperty(),
26
+ [paramName]: cursorPaginationParamOpenapiProperty(),
27
27
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
28
  };
29
29
  }
@@ -29,6 +29,7 @@ export default class OpenapiEndpointRenderer<DreamsOrSerializersOrViewModels ext
29
29
  private action;
30
30
  private many;
31
31
  private paginate;
32
+ private cursorPaginate;
32
33
  private scrollPaginate;
33
34
  private responses;
34
35
  private serializerKey;
@@ -59,7 +60,7 @@ export default class OpenapiEndpointRenderer<DreamsOrSerializersOrViewModels ext
59
60
  * const json = JSON.encode(openapiJsonContents, null, 2)
60
61
  * ```
61
62
  */
62
- constructor(dreamsOrSerializers: DreamsOrSerializersOrViewModels | null, controllerClass: typeof PsychicController, action: string, { requestBody, headers, many, paginate, scrollPaginate, query, responses, serializerKey, status, tags, security, pathParams, description, summary, omitDefaultHeaders, omitDefaultResponses, defaultResponse, validate, }?: OpenapiEndpointRendererOpts<DreamsOrSerializersOrViewModels, ForOption>);
63
+ constructor(dreamsOrSerializers: DreamsOrSerializersOrViewModels | null, controllerClass: typeof PsychicController, action: string, { requestBody, headers, many, paginate, cursorPaginate, scrollPaginate, query, responses, serializerKey, status, tags, security, pathParams, description, summary, omitDefaultHeaders, omitDefaultResponses, defaultResponse, validate, }?: OpenapiEndpointRendererOpts<DreamsOrSerializersOrViewModels, ForOption>);
63
64
  /**
64
65
  * @internal
65
66
  *
@@ -341,6 +342,28 @@ export interface OpenapiEndpointRendererOpts<I extends DreamSerializable | Dream
341
342
  */
342
343
  paginate?: boolean | CustomPaginationOpts;
343
344
  /**
345
+ * when true, it will render an openapi document which
346
+ * produces an object containing cursorPagination data. This
347
+ * output is formatted to match the format returned
348
+ * when calling the `cursorPaginate` method
349
+ * within Dream, i.e.
350
+ *
351
+ * ```ts
352
+ * \@OpenAPI(Post, {
353
+ * cursorPaginate: true,
354
+ * status: 200,
355
+ * })
356
+ * public async index() {
357
+ * const cursor = this.castParam('cursor', 'string', { allowNull: true })
358
+ * const posts = await Post.where(...).cursorPaginate({ pageSize: 100, cursor })
359
+ * this.ok(posts)
360
+ * }
361
+ * ```
362
+ */
363
+ cursorPaginate?: boolean | CustomCursorPaginationOpts;
364
+ /**
365
+ * @deprecated Use `cursorPaginate` instead. This option is deprecated in favor of the `cursorPaginate` option.
366
+ *
344
367
  * when true, it will render an openapi document which
345
368
  * produces an object containing scrollPagination data. This
346
369
  * output is formatted to match the format returned
@@ -359,7 +382,7 @@ export interface OpenapiEndpointRendererOpts<I extends DreamSerializable | Dream
359
382
  * }
360
383
  * ```
361
384
  */
362
- scrollPaginate?: boolean | CustomScrollPaginationOpts;
385
+ scrollPaginate?: boolean | CustomCursorPaginationOpts;
363
386
  /**
364
387
  * specify path params. This is usually not necessary, since path params
365
388
  * are automatically computed for your endpoint by matching against the
@@ -665,14 +688,14 @@ export type CustomPaginationOpts = {
665
688
  */
666
689
  body: string;
667
690
  };
668
- export type CustomScrollPaginationOpts = {
691
+ export type CustomCursorPaginationOpts = {
669
692
  /**
670
693
  * if provided, a query param will be added to the
671
694
  * openapi spec with the name provided.
672
695
  *
673
696
  * ```ts
674
697
  * @OpenAPI(Post, {
675
- * scrollPaginate: {
698
+ * cursorPaginate: {
676
699
  * query: 'cursor'
677
700
  * },
678
701
  * })
@@ -680,7 +703,7 @@ export type CustomScrollPaginationOpts = {
680
703
  * this.ok(
681
704
  * await this.currentUser
682
705
  * .associationQuery('posts')
683
- * .scrollPaginate({
706
+ * .cursorPaginate({
684
707
  * cursor: this.castParam('page', 'string', { allowNull: true })
685
708
  * })
686
709
  * )
@@ -695,7 +718,7 @@ export type CustomScrollPaginationOpts = {
695
718
  *
696
719
  * ```ts
697
720
  * @OpenAPI(Post, {
698
- * scrollPaginate: {
721
+ * cursorPaginate: {
699
722
  * body: 'page'
700
723
  * },
701
724
  * })
@@ -703,7 +726,7 @@ export type CustomScrollPaginationOpts = {
703
726
  * this.ok(
704
727
  * await this.currentUser
705
728
  * .associationQuery('posts')
706
- * .scrollPaginate({
729
+ * .cursorPaginate({
707
730
  * cursor: this.castParam('page', 'string', { allowNull: true })
708
731
  * })
709
732
  * )
@@ -0,0 +1,4 @@
1
+ export default function cursorPaginationParamOpenapiProperty(): {
2
+ readonly type: readonly ["string", "null"];
3
+ readonly description: "Pagination cursor";
4
+ };
@@ -12,4 +12,4 @@
12
12
  * If neither of these apply, it will simply return
13
13
  * what was given to it, without any modifications
14
14
  */
15
- export default function safelyAttachScrollPaginationParamToRequestBodySegment<T>(paramName: string, bodySegment: T): T;
15
+ export default function safelyAttachCursorPaginationParamToRequestBodySegment<T>(paramName: string, bodySegment: T): T;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "name": "@rvoh/psychic",
4
4
  "description": "Typescript web framework",
5
- "version": "2.2.1-alpha.2",
5
+ "version": "2.2.2",
6
6
  "author": "RVOHealth",
7
7
  "repository": {
8
8
  "type": "git",
@@ -79,7 +79,7 @@
79
79
  "yoctocolors": "^2.1.1"
80
80
  },
81
81
  "peerDependencies": {
82
- "@rvoh/dream": "^2.1.4-alpha.3",
82
+ "@rvoh/dream": "^2.2.0",
83
83
  "@types/express": "^5.0.1",
84
84
  "commander": "^12.1.0",
85
85
  "express": "^5.2.1",
@@ -88,7 +88,7 @@
88
88
  "devDependencies": {
89
89
  "@eslint/js": "^9.39.1",
90
90
  "@jest-mock/express": "^3.0.0",
91
- "@rvoh/dream": "^2.1.4-alpha.3",
91
+ "@rvoh/dream": "^2.2.0",
92
92
  "@rvoh/dream-spec-helpers": "^2.0.0",
93
93
  "@rvoh/psychic-spec-helpers": "^2.0.0",
94
94
  "@types/body-parser": "^1.19.6",
@@ -1,6 +0,0 @@
1
- export default function scrollPaginationCursorParamOpenapiProperty() {
2
- return {
3
- type: ['string', 'null'],
4
- description: 'Scroll pagination cursor',
5
- };
6
- }
@@ -1,6 +0,0 @@
1
- export default function scrollPaginationCursorParamOpenapiProperty() {
2
- return {
3
- type: ['string', 'null'],
4
- description: 'Scroll pagination cursor',
5
- };
6
- }
@@ -1,4 +0,0 @@
1
- export default function scrollPaginationCursorParamOpenapiProperty(): {
2
- readonly type: readonly ["string", "null"];
3
- readonly description: "Scroll pagination cursor";
4
- };