@appweaver/core 1.3.1 → 1.4.1

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 (48) hide show
  1. package/export/export-service.d.ts +4 -3
  2. package/export/export-service.js +39 -22
  3. package/factory/create-model.js +54 -22
  4. package/factory/create-service.js +5 -3
  5. package/package.json +2 -2
  6. package/prisma/client/commonInputTypes.d.ts +0 -50
  7. package/prisma/client/internal/class.js +3 -3
  8. package/prisma/client/models/File.d.ts +15 -28
  9. package/resource/resource-loader.js +6 -0
  10. package/resource/resource-routes.js +2 -2
  11. package/resource/resource-schema.d.ts +38 -12
  12. package/resource/resource-schema.js +63 -15
  13. package/resource/resource-service.d.ts +25 -16
  14. package/resource/resource-service.js +61 -26
  15. package/resource/schemas/resource-sort-schema.js +2 -2
  16. package/resource/utils/cursor-util.d.ts +60 -0
  17. package/resource/utils/cursor-util.js +117 -0
  18. package/resource/utils/index.d.ts +1 -0
  19. package/resource/utils/index.js +1 -0
  20. package/resource/utils/relation-util.d.ts +3 -3
  21. package/resource/utils/relation-util.js +52 -16
  22. package/resource/utils/sort-util.d.ts +11 -0
  23. package/resource/utils/sort-util.js +18 -0
  24. package/security/api-key/api-key-auth.js +4 -1
  25. package/security/auth-service.d.ts +7 -7
  26. package/security/auth-service.js +43 -5
  27. package/security/create-auth-resources.d.ts +2 -1
  28. package/security/oauth2/create-oauth2-plugin.js +3 -34
  29. package/security/oauth2/oauth2-microsoft.js +1 -1
  30. package/security/oauth2/oauth2-util.d.ts +9 -7
  31. package/security/oauth2/oauth2-util.js +17 -10
  32. package/security/resources/api-key/model.js +2 -0
  33. package/security/resources/connected-account/model.js +2 -0
  34. package/security/store/database-security-store.js +2 -1
  35. package/server/swagger.js +49 -1
  36. package/storage/file-service.d.ts +19 -4
  37. package/storage/file-service.js +199 -146
  38. package/storage/resources/file/model.js +4 -2
  39. package/types/auth.d.ts +8 -8
  40. package/types/generated.d.ts +8 -2
  41. package/types/index.d.ts +1 -0
  42. package/types/index.js +1 -0
  43. package/types/storage.d.ts +16 -0
  44. package/types/storage.js +2 -0
  45. package/utils/index.d.ts +1 -0
  46. package/utils/index.js +1 -0
  47. package/utils/model-util.d.ts +12 -0
  48. package/utils/model-util.js +113 -0
@@ -15,12 +15,10 @@ export type AggregateFile = {
15
15
  export type FileAvgAggregateOutputType = {
16
16
  id: number | null;
17
17
  sizeBytes: number | null;
18
- resourceId: number | null;
19
18
  };
20
19
  export type FileSumAggregateOutputType = {
21
20
  id: number | null;
22
21
  sizeBytes: number | null;
23
- resourceId: number | null;
24
22
  };
25
23
  export type FileMinAggregateOutputType = {
26
24
  id: number | null;
@@ -33,7 +31,7 @@ export type FileMinAggregateOutputType = {
33
31
  description: string | null;
34
32
  resourceField: string | null;
35
33
  resourceName: string | null;
36
- resourceId: number | null;
34
+ resourceId: string | null;
37
35
  updatedAt: Date | null;
38
36
  createdAt: Date | null;
39
37
  };
@@ -48,7 +46,7 @@ export type FileMaxAggregateOutputType = {
48
46
  description: string | null;
49
47
  resourceField: string | null;
50
48
  resourceName: string | null;
51
- resourceId: number | null;
49
+ resourceId: string | null;
52
50
  updatedAt: Date | null;
53
51
  createdAt: Date | null;
54
52
  };
@@ -71,12 +69,10 @@ export type FileCountAggregateOutputType = {
71
69
  export type FileAvgAggregateInputType = {
72
70
  id?: true;
73
71
  sizeBytes?: true;
74
- resourceId?: true;
75
72
  };
76
73
  export type FileSumAggregateInputType = {
77
74
  id?: true;
78
75
  sizeBytes?: true;
79
- resourceId?: true;
80
76
  };
81
77
  export type FileMinAggregateInputType = {
82
78
  id?: true;
@@ -211,7 +207,7 @@ export type FileGroupByOutputType = {
211
207
  description: string | null;
212
208
  resourceField: string | null;
213
209
  resourceName: string | null;
214
- resourceId: number | null;
210
+ resourceId: string | null;
215
211
  updatedAt: Date;
216
212
  createdAt: Date;
217
213
  _count: FileCountAggregateOutputType | null;
@@ -237,7 +233,7 @@ export type FileWhereInput = {
237
233
  description?: Prisma.StringNullableFilter<"File"> | string | null;
238
234
  resourceField?: Prisma.StringNullableFilter<"File"> | string | null;
239
235
  resourceName?: Prisma.StringNullableFilter<"File"> | string | null;
240
- resourceId?: Prisma.IntNullableFilter<"File"> | number | null;
236
+ resourceId?: Prisma.StringNullableFilter<"File"> | string | null;
241
237
  updatedAt?: Prisma.DateTimeFilter<"File"> | Date | string;
242
238
  createdAt?: Prisma.DateTimeFilter<"File"> | Date | string;
243
239
  };
@@ -270,7 +266,7 @@ export type FileWhereUniqueInput = Prisma.AtLeast<{
270
266
  description?: Prisma.StringNullableFilter<"File"> | string | null;
271
267
  resourceField?: Prisma.StringNullableFilter<"File"> | string | null;
272
268
  resourceName?: Prisma.StringNullableFilter<"File"> | string | null;
273
- resourceId?: Prisma.IntNullableFilter<"File"> | number | null;
269
+ resourceId?: Prisma.StringNullableFilter<"File"> | string | null;
274
270
  updatedAt?: Prisma.DateTimeFilter<"File"> | Date | string;
275
271
  createdAt?: Prisma.DateTimeFilter<"File"> | Date | string;
276
272
  }, "id" | "name">;
@@ -308,7 +304,7 @@ export type FileScalarWhereWithAggregatesInput = {
308
304
  description?: Prisma.StringNullableWithAggregatesFilter<"File"> | string | null;
309
305
  resourceField?: Prisma.StringNullableWithAggregatesFilter<"File"> | string | null;
310
306
  resourceName?: Prisma.StringNullableWithAggregatesFilter<"File"> | string | null;
311
- resourceId?: Prisma.IntNullableWithAggregatesFilter<"File"> | number | null;
307
+ resourceId?: Prisma.StringNullableWithAggregatesFilter<"File"> | string | null;
312
308
  updatedAt?: Prisma.DateTimeWithAggregatesFilter<"File"> | Date | string;
313
309
  createdAt?: Prisma.DateTimeWithAggregatesFilter<"File"> | Date | string;
314
310
  };
@@ -322,7 +318,7 @@ export type FileCreateInput = {
322
318
  description?: string | null;
323
319
  resourceField?: string | null;
324
320
  resourceName?: string | null;
325
- resourceId?: number | null;
321
+ resourceId?: string | null;
326
322
  updatedAt?: Date | string;
327
323
  createdAt?: Date | string;
328
324
  };
@@ -337,7 +333,7 @@ export type FileUncheckedCreateInput = {
337
333
  description?: string | null;
338
334
  resourceField?: string | null;
339
335
  resourceName?: string | null;
340
- resourceId?: number | null;
336
+ resourceId?: string | null;
341
337
  updatedAt?: Date | string;
342
338
  createdAt?: Date | string;
343
339
  };
@@ -351,7 +347,7 @@ export type FileUpdateInput = {
351
347
  description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
352
348
  resourceField?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
353
349
  resourceName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
354
- resourceId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null;
350
+ resourceId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
355
351
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
356
352
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
357
353
  };
@@ -366,7 +362,7 @@ export type FileUncheckedUpdateInput = {
366
362
  description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
367
363
  resourceField?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
368
364
  resourceName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
369
- resourceId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null;
365
+ resourceId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
370
366
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
371
367
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
372
368
  };
@@ -381,7 +377,7 @@ export type FileCreateManyInput = {
381
377
  description?: string | null;
382
378
  resourceField?: string | null;
383
379
  resourceName?: string | null;
384
- resourceId?: number | null;
380
+ resourceId?: string | null;
385
381
  updatedAt?: Date | string;
386
382
  createdAt?: Date | string;
387
383
  };
@@ -395,7 +391,7 @@ export type FileUpdateManyMutationInput = {
395
391
  description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
396
392
  resourceField?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
397
393
  resourceName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
398
- resourceId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null;
394
+ resourceId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
399
395
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
400
396
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
401
397
  };
@@ -410,7 +406,7 @@ export type FileUncheckedUpdateManyInput = {
410
406
  description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
411
407
  resourceField?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
412
408
  resourceName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
413
- resourceId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null;
409
+ resourceId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null;
414
410
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
415
411
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string;
416
412
  };
@@ -432,7 +428,6 @@ export type FileCountOrderByAggregateInput = {
432
428
  export type FileAvgOrderByAggregateInput = {
433
429
  id?: Prisma.SortOrder;
434
430
  sizeBytes?: Prisma.SortOrder;
435
- resourceId?: Prisma.SortOrder;
436
431
  };
437
432
  export type FileMaxOrderByAggregateInput = {
438
433
  id?: Prisma.SortOrder;
@@ -467,14 +462,6 @@ export type FileMinOrderByAggregateInput = {
467
462
  export type FileSumOrderByAggregateInput = {
468
463
  id?: Prisma.SortOrder;
469
464
  sizeBytes?: Prisma.SortOrder;
470
- resourceId?: Prisma.SortOrder;
471
- };
472
- export type NullableIntFieldUpdateOperationsInput = {
473
- set?: number | null;
474
- increment?: number;
475
- decrement?: number;
476
- multiply?: number;
477
- divide?: number;
478
465
  };
479
466
  export type FileSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
480
467
  id?: boolean;
@@ -551,7 +538,7 @@ export type $FilePayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
551
538
  description: string | null;
552
539
  resourceField: string | null;
553
540
  resourceName: string | null;
554
- resourceId: number | null;
541
+ resourceId: string | null;
555
542
  /**
556
543
  * Audit columns
557
544
  */
@@ -921,7 +908,7 @@ export interface FileFieldRefs {
921
908
  readonly description: Prisma.FieldRef<"File", 'String'>;
922
909
  readonly resourceField: Prisma.FieldRef<"File", 'String'>;
923
910
  readonly resourceName: Prisma.FieldRef<"File", 'String'>;
924
- readonly resourceId: Prisma.FieldRef<"File", 'Int'>;
911
+ readonly resourceId: Prisma.FieldRef<"File", 'String'>;
925
912
  readonly updatedAt: Prisma.FieldRef<"File", 'DateTime'>;
926
913
  readonly createdAt: Prisma.FieldRef<"File", 'DateTime'>;
927
914
  }
@@ -51,6 +51,12 @@ async function loadModels(baseDir, modelPattern) {
51
51
  }
52
52
  }
53
53
  }
54
+ // Resolve the schemas referencing the primary key of another model, now that
55
+ // every model is registered
56
+ (0, common_1.relinkResourceModels)(models);
57
+ // Reject a default value that its own field constraints would reject, since
58
+ // the records it produces cannot be serialized by the routes of their model
59
+ (0, utils_1.validateScalarDefaults)(models);
54
60
  // Reject file name patterns writing into a reserved storage path before any
55
61
  // upload can reach the storage layer.
56
62
  (0, utils_1.validateFileNamePatterns)(models);
@@ -76,8 +76,8 @@ function resourceRoutes(name, routesConfig = {}) {
76
76
  onRequest: routeAuth(queryConfig),
77
77
  config: queryConfig
78
78
  }, async (request, reply) => {
79
- const { page, size, sort, ...body } = request.body;
80
- const response = await service.query(body.filter, page, size, sort);
79
+ const { page, size, sort, cursor, totalCount, ...body } = request.body;
80
+ const response = await service.query(body.filter, page, size, sort, cursor, totalCount);
81
81
  return reply.send(response);
82
82
  });
83
83
  }
@@ -1,24 +1,25 @@
1
- import { AuthType, RecaptchaConfig, ResourceRoutesConfig, ResourceSchemaConfig } from '@appweaver/common';
2
- export declare const Id: import("@sinclair/typebox").TObject<{
1
+ import { TObject, TSchema } from '@sinclair/typebox';
2
+ import { AuthType, IdField, RecaptchaConfig, ResourceRoutesConfig, ResourceSchemaConfig } from '@appweaver/common';
3
+ export declare const ID_STRING_MAX_LENGTH = 36;
4
+ export declare const Id: TObject<{
3
5
  id: import("@sinclair/typebox").TInteger;
4
6
  }>;
5
- export declare const IdString: import("@sinclair/typebox").TObject<{
7
+ export declare const IdString: TObject<{
6
8
  id: import("@sinclair/typebox").TString;
7
9
  }>;
8
- export declare const AuditData: import("@sinclair/typebox").TObject<{
9
- updatedAt: import("@sinclair/typebox").TUnsafe<Date>;
10
- createdAt: import("@sinclair/typebox").TUnsafe<Date>;
11
- createdById: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<number | null>>;
12
- }>;
13
- export declare const QueryRequestData: import("@sinclair/typebox").TObject<{
10
+ export declare const QueryRequestData: TObject<{
14
11
  page: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
15
12
  size: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
13
+ cursor: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<string | null>>;
14
+ totalCount: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
16
15
  }>;
17
- export declare const QueryResponseData: import("@sinclair/typebox").TObject<{
16
+ export declare const QueryResponseData: TObject<{
18
17
  resultCount: import("@sinclair/typebox").TNumber;
19
- totalCount: import("@sinclair/typebox").TNumber;
18
+ totalCount: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<number | null>>;
19
+ nextCursor: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<string | null>>;
20
+ prevCursor: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<string | null>>;
20
21
  }>;
21
- export declare const AggregateRequestData: import("@sinclair/typebox").TObject<{
22
+ export declare const AggregateRequestData: TObject<{
22
23
  from: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<Date>>;
23
24
  to: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<Date>>;
24
25
  step: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
@@ -26,3 +27,28 @@ export declare const AggregateRequestData: import("@sinclair/typebox").TObject<{
26
27
  }>;
27
28
  export declare const AggregateResponseData: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
28
29
  export declare function createSchema(name: string, routeAuthTypes: Record<keyof ResourceRoutesConfig, AuthType[] | undefined>, routeRecaptcha: Record<keyof ResourceRoutesConfig, RecaptchaConfig>): ResourceSchemaConfig;
30
+ /**
31
+ * Builds the schema of a single primary key value, used wherever an id is
32
+ * accepted on its own (i.e. a relation input connecting an existing record).
33
+ *
34
+ * @param {IdField} [idField] - The id configuration, integer by default.
35
+ * @return {TSchema} The schema of the id value.
36
+ */
37
+ export declare function idValueSchema(idField?: IdField): TSchema;
38
+ /**
39
+ * Builds the primary key schema of a model, used as the route path parameter
40
+ * and as the connection shape of the relation inputs pointing at the model. A
41
+ * fresh object per call, since each model annotates its own schemas.
42
+ *
43
+ * @param {IdField} [idField] - The id configuration, integer by default.
44
+ * @return {TObject} The primary key schema of the model.
45
+ */
46
+ export declare function idSchema(idField?: IdField): TObject;
47
+ /**
48
+ * Builds the audit field schema of a model. The `createdById` field references
49
+ * the authentication model, so it follows that model's id type.
50
+ *
51
+ * @param {IdField} [authIdField] - The auth model id configuration, integer by default.
52
+ * @return {TObject} The schema holding all supported audit fields.
53
+ */
54
+ export declare function auditSchema(authIdField?: IdField): TObject;
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AggregateResponseData = exports.AggregateRequestData = exports.QueryResponseData = exports.QueryRequestData = exports.AuditData = exports.IdString = exports.Id = void 0;
3
+ exports.AggregateResponseData = exports.AggregateRequestData = exports.QueryResponseData = exports.QueryRequestData = exports.IdString = exports.Id = exports.ID_STRING_MAX_LENGTH = void 0;
4
4
  exports.createSchema = createSchema;
5
+ exports.idValueSchema = idValueSchema;
6
+ exports.idSchema = idSchema;
7
+ exports.auditSchema = auditSchema;
5
8
  const typebox_1 = require("@sinclair/typebox");
6
9
  const common_1 = require("@appweaver/common");
7
10
  const context_1 = require("../context");
@@ -9,26 +12,31 @@ const security_1 = require("../security");
9
12
  const errors_1 = require("../errors");
10
13
  const utils_1 = require("../utils");
11
14
  const schemas_1 = require("./schemas");
15
+ // Maximum length of a string primary key, sized for the longest value the
16
+ // supported generators produce (a 36-character UUID)
17
+ exports.ID_STRING_MAX_LENGTH = 36;
12
18
  exports.Id = typebox_1.Type.Object({
13
- id: typebox_1.Type.Integer({ minimum: 1 })
19
+ id: typebox_1.Type.Integer({ minimum: 1, example: 1 })
14
20
  });
15
21
  exports.IdString = typebox_1.Type.Object({
16
- id: typebox_1.Type.String({ maxLength: 36 })
17
- });
18
- exports.AuditData = typebox_1.Type.Object({
19
- updatedAt: (0, common_1.StringDate)(),
20
- createdAt: (0, common_1.StringDate)(),
21
- createdById: (0, common_1.Nullable)(typebox_1.Type.Integer({ minimum: 1, example: 1 }))
22
+ id: typebox_1.Type.String({ maxLength: exports.ID_STRING_MAX_LENGTH, example: 'a1b2c3d4' })
22
23
  });
24
+ const CURSOR_EXAMPLE = 'eyJpIjo0MiwiZiI6IkhkQjVfa2VMTVlyNyJ9';
23
25
  // The sort property is declared per model instead, since its object form
24
26
  // references the sortable fields of the queried resource
25
27
  exports.QueryRequestData = typebox_1.Type.Object({
26
28
  page: typebox_1.Type.Optional(typebox_1.Type.Number({ minimum: 1, example: 1 })),
27
- size: typebox_1.Type.Optional(typebox_1.Type.Number({ minimum: 0, maximum: 1000, example: 50 }))
29
+ size: typebox_1.Type.Optional(typebox_1.Type.Number({ minimum: 0, maximum: 2000, example: 50 })),
30
+ cursor: (0, common_1.Nullable)(typebox_1.Type.String({ example: CURSOR_EXAMPLE })),
31
+ totalCount: typebox_1.Type.Optional(typebox_1.Type.Boolean({ default: true, example: true }))
28
32
  });
33
+ // The absent values are returned as null rather than omitted, so a client reads
34
+ // "no further page" and "not counted" off the response instead of a missing key
29
35
  exports.QueryResponseData = typebox_1.Type.Object({
30
36
  resultCount: typebox_1.Type.Number({ example: 10 }),
31
- totalCount: typebox_1.Type.Number({ example: 100 })
37
+ totalCount: (0, common_1.Nullable)(typebox_1.Type.Number({ example: 100 })),
38
+ nextCursor: (0, common_1.Nullable)(typebox_1.Type.String({ example: CURSOR_EXAMPLE })),
39
+ prevCursor: (0, common_1.Nullable)(typebox_1.Type.String({ example: CURSOR_EXAMPLE }))
32
40
  });
33
41
  // The select and dateField properties are declared per model instead, since
34
42
  // they reference the aggregatable fields of the aggregated resource
@@ -43,6 +51,9 @@ function createSchema(name, routeAuthTypes, routeRecaptcha) {
43
51
  const resourceModel = (0, context_1.injectModel)(name);
44
52
  const resourceName = (0, common_1.camelToSnakeCase)(name, ' ');
45
53
  const tag = (0, common_1.plural)(name);
54
+ // The path parameter follows the primary key type of this model, so a string
55
+ // id is neither coerced to a number nor rejected by the request validation
56
+ const idParams = resourceModel.idModel;
46
57
  // Register the recursive query filter and sort schemas of all loaded models,
47
58
  // referenced by the query, aggregate, and export request bodies
48
59
  (0, schemas_1.registerQueryFilterSchemas)();
@@ -85,7 +96,7 @@ function createSchema(name, routeAuthTypes, routeRecaptcha) {
85
96
  200: resourceModel.readOneModel,
86
97
  ...errors_1.AllErrorResponses
87
98
  },
88
- params: exports.Id
99
+ params: idParams
89
100
  },
90
101
  querySchema: {
91
102
  tags: [tag],
@@ -134,7 +145,7 @@ function createSchema(name, routeAuthTypes, routeRecaptcha) {
134
145
  ...errors_1.AllErrorResponses
135
146
  },
136
147
  body: resourceModel.updateOneModel,
137
- params: exports.Id
148
+ params: idParams
138
149
  },
139
150
  deleteSchema: {
140
151
  tags: [tag],
@@ -146,7 +157,7 @@ function createSchema(name, routeAuthTypes, routeRecaptcha) {
146
157
  200: resourceModel.readOneModel,
147
158
  ...errors_1.AllErrorResponses
148
159
  },
149
- params: exports.Id
160
+ params: idParams
150
161
  },
151
162
  exportSchema: {
152
163
  tags: [tag],
@@ -178,7 +189,7 @@ function createSchema(name, routeAuthTypes, routeRecaptcha) {
178
189
  ...errors_1.AllErrorResponses
179
190
  },
180
191
  body: resourceModel.fileUploadModel,
181
- params: exports.Id
192
+ params: idParams
182
193
  },
183
194
  fileDeleteSchema: {
184
195
  tags: [tag],
@@ -191,7 +202,7 @@ function createSchema(name, routeAuthTypes, routeRecaptcha) {
191
202
  ...errors_1.AllErrorResponses
192
203
  },
193
204
  body: resourceModel.fileDeleteModel,
194
- params: exports.Id
205
+ params: idParams
195
206
  }
196
207
  };
197
208
  for (const resourceConfig of Object.values(resourceSchemaConfig)) {
@@ -199,3 +210,40 @@ function createSchema(name, routeAuthTypes, routeRecaptcha) {
199
210
  }
200
211
  return resourceSchemaConfig;
201
212
  }
213
+ /**
214
+ * Builds the schema of a single primary key value, used wherever an id is
215
+ * accepted on its own (i.e. a relation input connecting an existing record).
216
+ *
217
+ * @param {IdField} [idField] - The id configuration, integer by default.
218
+ * @return {TSchema} The schema of the id value.
219
+ */
220
+ function idValueSchema(idField) {
221
+ return (0, common_1.idFieldType)(idField) === 'string'
222
+ ? exports.IdString.properties.id
223
+ : exports.Id.properties.id;
224
+ }
225
+ /**
226
+ * Builds the primary key schema of a model, used as the route path parameter
227
+ * and as the connection shape of the relation inputs pointing at the model. A
228
+ * fresh object per call, since each model annotates its own schemas.
229
+ *
230
+ * @param {IdField} [idField] - The id configuration, integer by default.
231
+ * @return {TObject} The primary key schema of the model.
232
+ */
233
+ function idSchema(idField) {
234
+ return typebox_1.Type.Object({ id: idValueSchema(idField) });
235
+ }
236
+ /**
237
+ * Builds the audit field schema of a model. The `createdById` field references
238
+ * the authentication model, so it follows that model's id type.
239
+ *
240
+ * @param {IdField} [authIdField] - The auth model id configuration, integer by default.
241
+ * @return {TObject} The schema holding all supported audit fields.
242
+ */
243
+ function auditSchema(authIdField) {
244
+ return typebox_1.Type.Object({
245
+ updatedAt: (0, common_1.StringDate)(),
246
+ createdAt: (0, common_1.StringDate)(),
247
+ createdById: (0, common_1.Nullable)(idValueSchema(authIdField))
248
+ });
249
+ }
@@ -1,4 +1,4 @@
1
- import { ActionType, AggregateResponse, AggregateSelect, IResourceService, QueryFilter, QueryResponse, QuerySort, Resource, ResourceClient, ResourceData } from '@appweaver/common';
1
+ import { ActionType, AggregateResponse, AggregateSelect, AggregateSelected, IResourceService, QueryFilter, QueryResponse, QuerySort, Resource, ResourceId, ResourceClient, ResourceData } from '@appweaver/common';
2
2
  export declare abstract class ResourceService<ReadOne = Resource, ReadMany = Resource, Create = ResourceData<Resource>, Update = Partial<ResourceData<Resource>>, Query = QueryFilter<ReadOne>> implements IResourceService<ReadOne, ReadMany, Create, Update, Query> {
3
3
  readonly modelName: string;
4
4
  /**
@@ -24,14 +24,14 @@ export declare abstract class ResourceService<ReadOne = Resource, ReadMany = Res
24
24
  * configured for output on the find action. A resource event is emitted after
25
25
  * a successful lookup.
26
26
  *
27
- * @param {number} id The id of the resource to find.
27
+ * @param {ResourceId} id The id of the resource to find.
28
28
  * @returns {Promise<Object>} The found resource with its virtual fields and
29
29
  * relation counts projected.
30
30
  * @throws {@link HttpError} 404 if the resource does not exist or is filtered
31
31
  * out by the read restrictions, 403 if the access check denies it, and 500 on
32
32
  * a database error.
33
33
  */
34
- find(id: number): Promise<ReadOne>;
34
+ find(id: ResourceId): Promise<ReadOne>;
35
35
  /**
36
36
  * Queries a page of resources matching the provided filter. The filter is
37
37
  * mapped to a database query, combined with the optional `searchText` full
@@ -47,21 +47,28 @@ export declare abstract class ResourceService<ReadOne = Resource, ReadMany = Res
47
47
  * as plain field values. Its `searchText` property, if present, is passed to
48
48
  * {@link ResourceService.textSearchQuery} instead of being matched as a
49
49
  * field.
50
- * @param {number} [page] The one-based page number of results to return.
50
+ * @param {number} [page] The one-based page number of results to return,
51
+ * ignored when a cursor is given.
51
52
  * @param {number} [size] The maximum number of results per page.
52
53
  * @param {QuerySort} [sort] The fields to sort by, either as a comma-separated
53
54
  * list where a field prefixed with `-` is sorted in descending order
54
- * (i.e. `-createdAt,id`), or as an object of field directions
55
+ * (i.e. `-createdAt,title`), or as an object of field directions
55
56
  * (i.e. `{ createdAt: 'desc', id: 'asc' }`). Both forms support the fields of
56
57
  * the included to-one relations, given with a dot notation (`author.createdAt`)
57
58
  * or as a nested object (`{ author: { createdAt: 'desc' } }`).
59
+ * @param {string} [cursor] The cursor of the page to return, as issued in the
60
+ * `nextCursor` or `prevCursor` of an earlier response, which also carries the
61
+ * direction the page runs in. Takes precedence over `page`.
62
+ * @param {boolean} [totalCount] Whether to count all matching resources, which
63
+ * costs a scan of every one of them.
58
64
  * @returns {Promise<QueryResponse<Object>>} The paged query response containing
59
- * the returned resources, the count of the returned items and the total count
60
- * of matching resources.
65
+ * the returned resources, the count of the returned items, the cursors of the
66
+ * adjacent pages, and the total count unless it was opted out of.
61
67
  * @throws {@link HttpError} 400 if the sort input names a field that cannot be
62
- * sorted by, and 500 on a database error.
68
+ * sorted by or the cursor was issued for another filter or sort order, and 500
69
+ * on a database error.
63
70
  */
64
- query(filter?: Query, page?: number, size?: number, sort?: QuerySort<ReadMany>): Promise<QueryResponse<ReadMany>>;
71
+ query(filter?: Query, page?: number, size?: number, sort?: QuerySort<ReadMany>, cursor?: string | null, totalCount?: boolean): Promise<QueryResponse<ReadMany>>;
65
72
  /**
66
73
  * Aggregates resources matching the provided filter over a date range, both
67
74
  * as a single overall result and as a series of results for the equally sized
@@ -92,11 +99,13 @@ export declare abstract class ResourceService<ReadOne = Resource, ReadMany = Res
92
99
  * When false, the step is interpreted in seconds.
93
100
  * @returns {Promise<AggregateResponse<Object>>} The aggregation response with
94
101
  * the overall total and one result per period, each labeled with the median
95
- * date of its period.
102
+ * date of its period. It is typed by the fields the selection named, not by
103
+ * the whole model, whenever the selection is passed as an object literal or
104
+ * annotated with `satisfies`.
96
105
  * @throws {@link HttpError} 400 if the selection is empty or names a field or
97
106
  * operator that cannot be aggregated, and 500 on a database error.
98
107
  */
99
- aggregate(filter: Query | undefined, select: AggregateSelect<ReadOne>, dateField?: string, from?: string, to?: string, step?: number, safeIncrement?: boolean): Promise<AggregateResponse<ReadOne>>;
108
+ aggregate<S extends AggregateSelect<ReadOne>>(filter: Query | undefined, select: S, dateField?: string, from?: string, to?: string, step?: number, safeIncrement?: boolean): Promise<AggregateResponse<AggregateSelected<ReadOne, S>>>;
100
109
  /**
101
110
  * Creates a new resource. The provided data is merged with the write
102
111
  * restrictions, checked for access, sanitized against the model configuration
@@ -124,7 +133,7 @@ export declare abstract class ResourceService<ReadOne = Resource, ReadMany = Res
124
133
  * and a resource event carrying both the previous and the current state is
125
134
  * emitted after a successful update.
126
135
  *
127
- * @param {number} id The id of the resource to update.
136
+ * @param {ResourceId} id The id of the resource to update.
128
137
  * @param {Object} data The partial data to update the resource with, including
129
138
  * any inline relation and file payloads.
130
139
  * @returns {Promise<Object>} The updated resource with its virtual fields and
@@ -134,21 +143,21 @@ export declare abstract class ResourceService<ReadOne = Resource, ReadMany = Res
134
143
  * if an inline relation payload is missing required fields or the relation
135
144
  * does not accept new records, and 500 on a database error.
136
145
  */
137
- update(id: number, data: Update): Promise<ReadOne>;
146
+ update(id: ResourceId, data: Update): Promise<ReadOne>;
138
147
  /**
139
148
  * Deletes an existing resource by its id. The current record is loaded with
140
149
  * the read restrictions applied and checked for access before it is deleted
141
150
  * inside a single transaction. The resource cache is invalidated and a
142
151
  * resource event is emitted after a successful delete.
143
152
  *
144
- * @param {number} id The id of the resource to delete.
153
+ * @param {ResourceId} id The id of the resource to delete.
145
154
  * @returns {Promise<Object>} The deleted resource with its virtual fields and
146
155
  * relation counts projected.
147
156
  * @throws {@link HttpError} 404 if the resource does not exist or is filtered
148
157
  * out by the read restrictions, 403 if the access check denies the action, and
149
158
  * 500 on a database error.
150
159
  */
151
- delete(id: number): Promise<ReadOne>;
160
+ delete(id: ResourceId): Promise<ReadOne>;
152
161
  /**
153
162
  * This method should be overridden with custom logic for restricting read
154
163
  * operations on specific data for currently logged-in user and other
@@ -160,7 +169,7 @@ export declare abstract class ResourceService<ReadOne = Resource, ReadMany = Res
160
169
  *
161
170
  * @param {ActionType} action The called action method on this service (find,
162
171
  * query, aggregate, update, or delete)
163
- * @param {Object|number} data The passed data to the called function can be
172
+ * @param {Object|ResourceId} data The passed data to the called function can be
164
173
  * number or object. If the data is a type of number, then it represents the
165
174
  * resource id, otherwise it depends on the action and can be one of the
166
175
  * following: