@igstack/app-catalog-backend-core 0.15.0 → 0.17.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 (70) hide show
  1. package/dist/db/client.d.mts +54 -1
  2. package/dist/db/client.d.mts.map +1 -1
  3. package/dist/db/client.mjs +88 -7
  4. package/dist/db/client.mjs.map +1 -1
  5. package/dist/db/index.d.mts +1 -1
  6. package/dist/db/syncAppCatalog.mjs +2 -1
  7. package/dist/db/syncAppCatalog.mjs.map +1 -1
  8. package/dist/generated/prisma/internal/class.mjs +4 -4
  9. package/dist/generated/prisma/internal/class.mjs.map +1 -1
  10. package/dist/generated/prisma/internal/prismaNamespace.d.mts +1 -0
  11. package/dist/generated/prisma/internal/prismaNamespace.d.mts.map +1 -1
  12. package/dist/generated/prisma/models/DbResource.d.mts +43 -1
  13. package/dist/generated/prisma/models/DbResource.d.mts.map +1 -1
  14. package/dist/index.d.mts +2 -2
  15. package/dist/index.mjs +2 -2
  16. package/dist/middleware/database.d.mts +1 -0
  17. package/dist/middleware/database.d.mts.map +1 -1
  18. package/dist/middleware/database.mjs +10 -13
  19. package/dist/middleware/database.mjs.map +1 -1
  20. package/dist/modules/appCatalog/freshness.mjs +5 -3
  21. package/dist/modules/appCatalog/freshness.mjs.map +1 -1
  22. package/dist/modules/appCatalog/service.mjs +5 -1
  23. package/dist/modules/appCatalog/service.mjs.map +1 -1
  24. package/dist/modules/assets/assetCache.mjs +25 -0
  25. package/dist/modules/assets/assetCache.mjs.map +1 -0
  26. package/dist/modules/assets/assetRestController.d.mts.map +1 -1
  27. package/dist/modules/assets/assetRestController.mjs +17 -6
  28. package/dist/modules/assets/assetRestController.mjs.map +1 -1
  29. package/dist/modules/assets/assetUtils.mjs +1 -0
  30. package/dist/modules/assets/assetUtils.mjs.map +1 -1
  31. package/dist/modules/assets/screenshotRestController.d.mts.map +1 -1
  32. package/dist/modules/assets/screenshotRestController.mjs +7 -2
  33. package/dist/modules/assets/screenshotRestController.mjs.map +1 -1
  34. package/dist/modules/assets/upsertAsset.mjs +7 -1
  35. package/dist/modules/assets/upsertAsset.mjs.map +1 -1
  36. package/dist/modules/icons/iconRestController.d.mts.map +1 -1
  37. package/dist/modules/icons/iconRestController.mjs +7 -2
  38. package/dist/modules/icons/iconRestController.mjs.map +1 -1
  39. package/dist/modules/lighthouseKeeper/tools.d.mts.map +1 -1
  40. package/dist/modules/lighthouseKeeper/tools.mjs +5 -5
  41. package/dist/modules/lighthouseKeeper/tools.mjs.map +1 -1
  42. package/dist/types/common/appCatalogTypes.d.mts +8 -0
  43. package/dist/types/common/appCatalogTypes.d.mts.map +1 -1
  44. package/package.json +4 -4
  45. package/prisma/schema.prisma +5 -2
  46. package/src/__tests__/assetMime.test.ts +74 -0
  47. package/src/__tests__/dbSchema.test.ts +129 -0
  48. package/src/__tests__/dbSchemaAdapter.test.ts +118 -0
  49. package/src/__tests__/freshness.test.ts +56 -17
  50. package/src/__tests__/iconCache.test.ts +81 -0
  51. package/src/db/client.ts +118 -12
  52. package/src/db/index.ts +10 -1
  53. package/src/db/syncAppCatalog.ts +3 -0
  54. package/src/generated/prisma/internal/class.ts +4 -4
  55. package/src/generated/prisma/internal/prismaNamespace.ts +1 -0
  56. package/src/generated/prisma/internal/prismaNamespaceBrowser.ts +1 -0
  57. package/src/generated/prisma/models/DbResource.ts +44 -1
  58. package/src/index.ts +4 -0
  59. package/src/middleware/database.ts +20 -19
  60. package/src/modules/admin/chat/createDatabaseTools.ts +6 -2
  61. package/src/modules/appCatalog/freshness.ts +29 -11
  62. package/src/modules/appCatalog/service.ts +9 -4
  63. package/src/modules/assets/assetCache.ts +30 -0
  64. package/src/modules/assets/assetRestController.ts +25 -2
  65. package/src/modules/assets/assetUtils.ts +1 -0
  66. package/src/modules/assets/screenshotRestController.ts +13 -1
  67. package/src/modules/assets/upsertAsset.ts +8 -0
  68. package/src/modules/icons/iconRestController.ts +11 -1
  69. package/src/modules/lighthouseKeeper/tools.ts +6 -7
  70. package/src/types/common/appCatalogTypes.ts +8 -0
@@ -44,6 +44,7 @@ export type DbResourceMinAggregateOutputType = {
44
44
  accessComments: string | null
45
45
  lastCheckedAt: Date | null
46
46
  nextCheckAfter: Date | null
47
+ lastContentChangeAt: Date | null
47
48
  createdAt: Date | null
48
49
  updatedAt: Date | null
49
50
  }
@@ -66,6 +67,7 @@ export type DbResourceMaxAggregateOutputType = {
66
67
  accessComments: string | null
67
68
  lastCheckedAt: Date | null
68
69
  nextCheckAfter: Date | null
70
+ lastContentChangeAt: Date | null
69
71
  createdAt: Date | null
70
72
  updatedAt: Date | null
71
73
  }
@@ -101,6 +103,7 @@ export type DbResourceCountAggregateOutputType = {
101
103
  extra:PrismaJson.SubResourceExtra
102
104
  lastCheckedAt: number
103
105
  nextCheckAfter: number
106
+ lastContentChangeAt: number
104
107
  createdAt: number
105
108
  updatedAt: number
106
109
  _all: number
@@ -125,6 +128,7 @@ export type DbResourceMinAggregateInputType = {
125
128
  accessComments?: true
126
129
  lastCheckedAt?: true
127
130
  nextCheckAfter?: true
131
+ lastContentChangeAt?: true
128
132
  createdAt?: true
129
133
  updatedAt?: true
130
134
  }
@@ -147,6 +151,7 @@ export type DbResourceMaxAggregateInputType = {
147
151
  accessComments?: true
148
152
  lastCheckedAt?: true
149
153
  nextCheckAfter?: true
154
+ lastContentChangeAt?: true
150
155
  createdAt?: true
151
156
  updatedAt?: true
152
157
  }
@@ -182,6 +187,7 @@ export type DbResourceCountAggregateInputType = {
182
187
  extra?: true
183
188
  lastCheckedAt?: true
184
189
  nextCheckAfter?: true
190
+ lastContentChangeAt?: true
185
191
  createdAt?: true
186
192
  updatedAt?: true
187
193
  _all?: true
@@ -290,6 +296,7 @@ export type DbResourceGroupByOutputType = {
290
296
  extra:PrismaJson.SubResourceExtra | null
291
297
  lastCheckedAt: Date | null
292
298
  nextCheckAfter: Date | null
299
+ lastContentChangeAt: Date | null
293
300
  createdAt: Date
294
301
  updatedAt: Date
295
302
  _count: DbResourceCountAggregateOutputType | null
@@ -346,6 +353,7 @@ export type DbResourceWhereInput = {
346
353
  extra?: Prisma.JsonNullableFilter<"DbResource">
347
354
  lastCheckedAt?: Prisma.DateTimeNullableFilter<"DbResource"> | Date | string | null
348
355
  nextCheckAfter?: Prisma.DateTimeNullableFilter<"DbResource"> | Date | string | null
356
+ lastContentChangeAt?: Prisma.DateTimeNullableFilter<"DbResource"> | Date | string | null
349
357
  createdAt?: Prisma.DateTimeFilter<"DbResource"> | Date | string
350
358
  updatedAt?: Prisma.DateTimeFilter<"DbResource"> | Date | string
351
359
  sourceRefs?: Prisma.SourceReferenceListRelationFilter
@@ -384,6 +392,7 @@ export type DbResourceOrderByWithRelationInput = {
384
392
  extra?: Prisma.SortOrderInput | Prisma.SortOrder
385
393
  lastCheckedAt?: Prisma.SortOrderInput | Prisma.SortOrder
386
394
  nextCheckAfter?: Prisma.SortOrderInput | Prisma.SortOrder
395
+ lastContentChangeAt?: Prisma.SortOrderInput | Prisma.SortOrder
387
396
  createdAt?: Prisma.SortOrder
388
397
  updatedAt?: Prisma.SortOrder
389
398
  sourceRefs?: Prisma.SourceReferenceOrderByRelationAggregateInput
@@ -425,6 +434,7 @@ export type DbResourceWhereUniqueInput = Prisma.AtLeast<{
425
434
  extra?: Prisma.JsonNullableFilter<"DbResource">
426
435
  lastCheckedAt?: Prisma.DateTimeNullableFilter<"DbResource"> | Date | string | null
427
436
  nextCheckAfter?: Prisma.DateTimeNullableFilter<"DbResource"> | Date | string | null
437
+ lastContentChangeAt?: Prisma.DateTimeNullableFilter<"DbResource"> | Date | string | null
428
438
  createdAt?: Prisma.DateTimeFilter<"DbResource"> | Date | string
429
439
  updatedAt?: Prisma.DateTimeFilter<"DbResource"> | Date | string
430
440
  sourceRefs?: Prisma.SourceReferenceListRelationFilter
@@ -463,6 +473,7 @@ export type DbResourceOrderByWithAggregationInput = {
463
473
  extra?: Prisma.SortOrderInput | Prisma.SortOrder
464
474
  lastCheckedAt?: Prisma.SortOrderInput | Prisma.SortOrder
465
475
  nextCheckAfter?: Prisma.SortOrderInput | Prisma.SortOrder
476
+ lastContentChangeAt?: Prisma.SortOrderInput | Prisma.SortOrder
466
477
  createdAt?: Prisma.SortOrder
467
478
  updatedAt?: Prisma.SortOrder
468
479
  _count?: Prisma.DbResourceCountOrderByAggregateInput
@@ -504,6 +515,7 @@ export type DbResourceScalarWhereWithAggregatesInput = {
504
515
  extra?: Prisma.JsonNullableWithAggregatesFilter<"DbResource">
505
516
  lastCheckedAt?: Prisma.DateTimeNullableWithAggregatesFilter<"DbResource"> | Date | string | null
506
517
  nextCheckAfter?: Prisma.DateTimeNullableWithAggregatesFilter<"DbResource"> | Date | string | null
518
+ lastContentChangeAt?: Prisma.DateTimeNullableWithAggregatesFilter<"DbResource"> | Date | string | null
507
519
  createdAt?: Prisma.DateTimeWithAggregatesFilter<"DbResource"> | Date | string
508
520
  updatedAt?: Prisma.DateTimeWithAggregatesFilter<"DbResource"> | Date | string
509
521
  }
@@ -538,6 +550,7 @@ export type DbResourceCreateInput = {
538
550
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
539
551
  lastCheckedAt?: Date | string | null
540
552
  nextCheckAfter?: Date | string | null
553
+ lastContentChangeAt?: Date | string | null
541
554
  createdAt?: Date | string
542
555
  updatedAt?: Date | string
543
556
  sourceRefs?: Prisma.SourceReferenceCreateNestedManyWithoutResourceInput
@@ -576,6 +589,7 @@ export type DbResourceUncheckedCreateInput = {
576
589
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
577
590
  lastCheckedAt?: Date | string | null
578
591
  nextCheckAfter?: Date | string | null
592
+ lastContentChangeAt?: Date | string | null
579
593
  createdAt?: Date | string
580
594
  updatedAt?: Date | string
581
595
  sourceRefs?: Prisma.SourceReferenceUncheckedCreateNestedManyWithoutResourceInput
@@ -612,6 +626,7 @@ export type DbResourceUpdateInput = {
612
626
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
613
627
  lastCheckedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
614
628
  nextCheckAfter?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
629
+ lastContentChangeAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
615
630
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
616
631
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
617
632
  sourceRefs?: Prisma.SourceReferenceUpdateManyWithoutResourceNestedInput
@@ -650,6 +665,7 @@ export type DbResourceUncheckedUpdateInput = {
650
665
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
651
666
  lastCheckedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
652
667
  nextCheckAfter?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
668
+ lastContentChangeAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
653
669
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
654
670
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
655
671
  sourceRefs?: Prisma.SourceReferenceUncheckedUpdateManyWithoutResourceNestedInput
@@ -687,6 +703,7 @@ export type DbResourceCreateManyInput = {
687
703
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
688
704
  lastCheckedAt?: Date | string | null
689
705
  nextCheckAfter?: Date | string | null
706
+ lastContentChangeAt?: Date | string | null
690
707
  createdAt?: Date | string
691
708
  updatedAt?: Date | string
692
709
  }
@@ -721,6 +738,7 @@ export type DbResourceUpdateManyMutationInput = {
721
738
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
722
739
  lastCheckedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
723
740
  nextCheckAfter?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
741
+ lastContentChangeAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
724
742
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
725
743
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
726
744
  }
@@ -756,6 +774,7 @@ export type DbResourceUncheckedUpdateManyInput = {
756
774
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
757
775
  lastCheckedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
758
776
  nextCheckAfter?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
777
+ lastContentChangeAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
759
778
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
760
779
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
761
780
  }
@@ -806,6 +825,7 @@ export type DbResourceCountOrderByAggregateInput = {
806
825
  extra?: Prisma.SortOrder
807
826
  lastCheckedAt?: Prisma.SortOrder
808
827
  nextCheckAfter?: Prisma.SortOrder
828
+ lastContentChangeAt?: Prisma.SortOrder
809
829
  createdAt?: Prisma.SortOrder
810
830
  updatedAt?: Prisma.SortOrder
811
831
  }
@@ -828,6 +848,7 @@ export type DbResourceMaxOrderByAggregateInput = {
828
848
  accessComments?: Prisma.SortOrder
829
849
  lastCheckedAt?: Prisma.SortOrder
830
850
  nextCheckAfter?: Prisma.SortOrder
851
+ lastContentChangeAt?: Prisma.SortOrder
831
852
  createdAt?: Prisma.SortOrder
832
853
  updatedAt?: Prisma.SortOrder
833
854
  }
@@ -850,6 +871,7 @@ export type DbResourceMinOrderByAggregateInput = {
850
871
  accessComments?: Prisma.SortOrder
851
872
  lastCheckedAt?: Prisma.SortOrder
852
873
  nextCheckAfter?: Prisma.SortOrder
874
+ lastContentChangeAt?: Prisma.SortOrder
853
875
  createdAt?: Prisma.SortOrder
854
876
  updatedAt?: Prisma.SortOrder
855
877
  }
@@ -1024,6 +1046,7 @@ export type DbResourceCreateWithoutChildrenInput = {
1024
1046
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
1025
1047
  lastCheckedAt?: Date | string | null
1026
1048
  nextCheckAfter?: Date | string | null
1049
+ lastContentChangeAt?: Date | string | null
1027
1050
  createdAt?: Date | string
1028
1051
  updatedAt?: Date | string
1029
1052
  sourceRefs?: Prisma.SourceReferenceCreateNestedManyWithoutResourceInput
@@ -1061,6 +1084,7 @@ export type DbResourceUncheckedCreateWithoutChildrenInput = {
1061
1084
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
1062
1085
  lastCheckedAt?: Date | string | null
1063
1086
  nextCheckAfter?: Date | string | null
1087
+ lastContentChangeAt?: Date | string | null
1064
1088
  createdAt?: Date | string
1065
1089
  updatedAt?: Date | string
1066
1090
  sourceRefs?: Prisma.SourceReferenceUncheckedCreateNestedManyWithoutResourceInput
@@ -1101,6 +1125,7 @@ export type DbResourceCreateWithoutParentInput = {
1101
1125
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
1102
1126
  lastCheckedAt?: Date | string | null
1103
1127
  nextCheckAfter?: Date | string | null
1128
+ lastContentChangeAt?: Date | string | null
1104
1129
  createdAt?: Date | string
1105
1130
  updatedAt?: Date | string
1106
1131
  sourceRefs?: Prisma.SourceReferenceCreateNestedManyWithoutResourceInput
@@ -1137,6 +1162,7 @@ export type DbResourceUncheckedCreateWithoutParentInput = {
1137
1162
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
1138
1163
  lastCheckedAt?: Date | string | null
1139
1164
  nextCheckAfter?: Date | string | null
1165
+ lastContentChangeAt?: Date | string | null
1140
1166
  createdAt?: Date | string
1141
1167
  updatedAt?: Date | string
1142
1168
  sourceRefs?: Prisma.SourceReferenceUncheckedCreateNestedManyWithoutResourceInput
@@ -1194,6 +1220,7 @@ export type DbResourceUpdateWithoutChildrenInput = {
1194
1220
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
1195
1221
  lastCheckedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1196
1222
  nextCheckAfter?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1223
+ lastContentChangeAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1197
1224
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
1198
1225
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
1199
1226
  sourceRefs?: Prisma.SourceReferenceUpdateManyWithoutResourceNestedInput
@@ -1231,6 +1258,7 @@ export type DbResourceUncheckedUpdateWithoutChildrenInput = {
1231
1258
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
1232
1259
  lastCheckedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1233
1260
  nextCheckAfter?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1261
+ lastContentChangeAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1234
1262
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
1235
1263
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
1236
1264
  sourceRefs?: Prisma.SourceReferenceUncheckedUpdateManyWithoutResourceNestedInput
@@ -1286,6 +1314,7 @@ export type DbResourceScalarWhereInput = {
1286
1314
  extra?: Prisma.JsonNullableFilter<"DbResource">
1287
1315
  lastCheckedAt?: Prisma.DateTimeNullableFilter<"DbResource"> | Date | string | null
1288
1316
  nextCheckAfter?: Prisma.DateTimeNullableFilter<"DbResource"> | Date | string | null
1317
+ lastContentChangeAt?: Prisma.DateTimeNullableFilter<"DbResource"> | Date | string | null
1289
1318
  createdAt?: Prisma.DateTimeFilter<"DbResource"> | Date | string
1290
1319
  updatedAt?: Prisma.DateTimeFilter<"DbResource"> | Date | string
1291
1320
  }
@@ -1320,6 +1349,7 @@ export type DbResourceCreateWithoutSourceRefsInput = {
1320
1349
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
1321
1350
  lastCheckedAt?: Date | string | null
1322
1351
  nextCheckAfter?: Date | string | null
1352
+ lastContentChangeAt?: Date | string | null
1323
1353
  createdAt?: Date | string
1324
1354
  updatedAt?: Date | string
1325
1355
  parent?: Prisma.DbResourceCreateNestedOneWithoutChildrenInput
@@ -1357,6 +1387,7 @@ export type DbResourceUncheckedCreateWithoutSourceRefsInput = {
1357
1387
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
1358
1388
  lastCheckedAt?: Date | string | null
1359
1389
  nextCheckAfter?: Date | string | null
1390
+ lastContentChangeAt?: Date | string | null
1360
1391
  createdAt?: Date | string
1361
1392
  updatedAt?: Date | string
1362
1393
  children?: Prisma.DbResourceUncheckedCreateNestedManyWithoutParentInput
@@ -1408,6 +1439,7 @@ export type DbResourceUpdateWithoutSourceRefsInput = {
1408
1439
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
1409
1440
  lastCheckedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1410
1441
  nextCheckAfter?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1442
+ lastContentChangeAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1411
1443
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
1412
1444
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
1413
1445
  parent?: Prisma.DbResourceUpdateOneWithoutChildrenNestedInput
@@ -1445,6 +1477,7 @@ export type DbResourceUncheckedUpdateWithoutSourceRefsInput = {
1445
1477
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
1446
1478
  lastCheckedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1447
1479
  nextCheckAfter?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1480
+ lastContentChangeAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1448
1481
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
1449
1482
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
1450
1483
  children?: Prisma.DbResourceUncheckedUpdateManyWithoutParentNestedInput
@@ -1480,6 +1513,7 @@ export type DbResourceCreateManyParentInput = {
1480
1513
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
1481
1514
  lastCheckedAt?: Date | string | null
1482
1515
  nextCheckAfter?: Date | string | null
1516
+ lastContentChangeAt?: Date | string | null
1483
1517
  createdAt?: Date | string
1484
1518
  updatedAt?: Date | string
1485
1519
  }
@@ -1514,6 +1548,7 @@ export type DbResourceUpdateWithoutParentInput = {
1514
1548
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
1515
1549
  lastCheckedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1516
1550
  nextCheckAfter?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1551
+ lastContentChangeAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1517
1552
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
1518
1553
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
1519
1554
  sourceRefs?: Prisma.SourceReferenceUpdateManyWithoutResourceNestedInput
@@ -1550,6 +1585,7 @@ export type DbResourceUncheckedUpdateWithoutParentInput = {
1550
1585
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
1551
1586
  lastCheckedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1552
1587
  nextCheckAfter?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1588
+ lastContentChangeAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1553
1589
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
1554
1590
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
1555
1591
  sourceRefs?: Prisma.SourceReferenceUncheckedUpdateManyWithoutResourceNestedInput
@@ -1586,6 +1622,7 @@ export type DbResourceUncheckedUpdateManyWithoutParentInput = {
1586
1622
  extra?:PrismaJson.SubResourceExtra | Prisma.NullableJsonNullValueInput
1587
1623
  lastCheckedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1588
1624
  nextCheckAfter?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1625
+ lastContentChangeAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
1589
1626
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
1590
1627
  updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
1591
1628
  }
@@ -1661,6 +1698,7 @@ export type DbResourceSelect<ExtArgs extends runtime.Types.Extensions.InternalAr
1661
1698
  extra?: boolean
1662
1699
  lastCheckedAt?: boolean
1663
1700
  nextCheckAfter?: boolean
1701
+ lastContentChangeAt?: boolean
1664
1702
  createdAt?: boolean
1665
1703
  updatedAt?: boolean
1666
1704
  sourceRefs?: boolean | Prisma.DbResource$sourceRefsArgs<ExtArgs>
@@ -1700,6 +1738,7 @@ export type DbResourceSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Ex
1700
1738
  extra?: boolean
1701
1739
  lastCheckedAt?: boolean
1702
1740
  nextCheckAfter?: boolean
1741
+ lastContentChangeAt?: boolean
1703
1742
  createdAt?: boolean
1704
1743
  updatedAt?: boolean
1705
1744
  parent?: boolean | Prisma.DbResource$parentArgs<ExtArgs>
@@ -1736,6 +1775,7 @@ export type DbResourceSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Ex
1736
1775
  extra?: boolean
1737
1776
  lastCheckedAt?: boolean
1738
1777
  nextCheckAfter?: boolean
1778
+ lastContentChangeAt?: boolean
1739
1779
  createdAt?: boolean
1740
1780
  updatedAt?: boolean
1741
1781
  parent?: boolean | Prisma.DbResource$parentArgs<ExtArgs>
@@ -1772,11 +1812,12 @@ export type DbResourceSelectScalar = {
1772
1812
  extra?: boolean
1773
1813
  lastCheckedAt?: boolean
1774
1814
  nextCheckAfter?: boolean
1815
+ lastContentChangeAt?: boolean
1775
1816
  createdAt?: boolean
1776
1817
  updatedAt?: boolean
1777
1818
  }
1778
1819
 
1779
- export type DbResourceOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "slug" | "type" | "displayName" | "abbreviation" | "nicknames" | "description" | "access" | "teams" | "accessRequest" | "notes" | "tags" | "appUrl" | "links" | "iconName" | "screenshotIds" | "deprecated" | "aiPrompt" | "urlIssues" | "tiers" | "parentSlug" | "tier" | "familySlug" | "aliases" | "ownerPersonSlug" | "accessMaintainerGroupSlugs" | "accessComments" | "extra" | "lastCheckedAt" | "nextCheckAfter" | "createdAt" | "updatedAt", ExtArgs["result"]["dbResource"]>
1820
+ export type DbResourceOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "slug" | "type" | "displayName" | "abbreviation" | "nicknames" | "description" | "access" | "teams" | "accessRequest" | "notes" | "tags" | "appUrl" | "links" | "iconName" | "screenshotIds" | "deprecated" | "aiPrompt" | "urlIssues" | "tiers" | "parentSlug" | "tier" | "familySlug" | "aliases" | "ownerPersonSlug" | "accessMaintainerGroupSlugs" | "accessComments" | "extra" | "lastCheckedAt" | "nextCheckAfter" | "lastContentChangeAt" | "createdAt" | "updatedAt", ExtArgs["result"]["dbResource"]>
1780
1821
  export type DbResourceInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
1781
1822
  sourceRefs?: boolean | Prisma.DbResource$sourceRefsArgs<ExtArgs>
1782
1823
  parent?: boolean | Prisma.DbResource$parentArgs<ExtArgs>
@@ -1846,6 +1887,7 @@ export type $DbResourcePayload<ExtArgs extends runtime.Types.Extensions.Internal
1846
1887
  extra:PrismaJson.SubResourceExtra | null
1847
1888
  lastCheckedAt: Date | null
1848
1889
  nextCheckAfter: Date | null
1890
+ lastContentChangeAt: Date | null
1849
1891
  createdAt: Date
1850
1892
  updatedAt: Date
1851
1893
  }, ExtArgs["result"]["dbResource"]>
@@ -2304,6 +2346,7 @@ export interface DbResourceFieldRefs {
2304
2346
  readonly extra: Prisma.FieldRef<"DbResource", 'Json'>
2305
2347
  readonly lastCheckedAt: Prisma.FieldRef<"DbResource", 'DateTime'>
2306
2348
  readonly nextCheckAfter: Prisma.FieldRef<"DbResource", 'DateTime'>
2349
+ readonly lastContentChangeAt: Prisma.FieldRef<"DbResource", 'DateTime'>
2307
2350
  readonly createdAt: Prisma.FieldRef<"DbResource", 'DateTime'>
2308
2351
  readonly updatedAt: Prisma.FieldRef<"DbResource", 'DateTime'>
2309
2352
  }
package/src/index.ts CHANGED
@@ -59,14 +59,18 @@ export {
59
59
 
60
60
  // Database utilities
61
61
  export {
62
+ buildPgSchemaOptions,
62
63
  buildPgSslConfig,
63
64
  connectDb,
65
+ createCorePrismaClient,
64
66
  disconnectDb,
65
67
  getDbClient,
68
+ resolveDbSchema,
66
69
  setDbClient,
67
70
  syncAppCatalog,
68
71
  TABLE_SYNC_MAGAZINE,
69
72
  tableSyncPrisma,
73
+ verifyDbSchema,
70
74
  type MakeTFromPrismaModel,
71
75
  type ObjectKeys,
72
76
  type ScalarFilter,
@@ -1,9 +1,11 @@
1
- import { PrismaClient } from '../generated/prisma/client'
2
- import { PrismaPg } from '@prisma/adapter-pg'
3
- import pg from 'pg'
1
+ import type { PrismaClient } from '../generated/prisma/client'
2
+ import type pg from 'pg'
4
3
  import type { AcDatabaseConfig } from './types'
5
- import { setDbClient } from '../db/client'
6
- import { buildPgSslConfig } from '../db/sslConfig'
4
+ import {
5
+ createCorePrismaClient,
6
+ setDbClient,
7
+ verifyDbSchema,
8
+ } from '../db/client'
7
9
 
8
10
  /**
9
11
  * Formats a database connection URL from structured config.
@@ -36,25 +38,19 @@ export class AcDatabaseManager {
36
38
  */
37
39
  getClient(): PrismaClient {
38
40
  if (!this.client) {
39
- const datasourceUrl = formatConnectionUrl(this.config)
40
-
41
- // Prisma 7 with adapter: Create pg pool and wrap with adapter.
42
- // SSL from PGSSLMODE/PGSSLROOTCERT via buildPgSslConfig (node-postgres
43
- // doesn't apply those correctly to a connection-string pool).
44
- const ssl = buildPgSslConfig()
45
- this.pool = new pg.Pool({
46
- connectionString: datasourceUrl,
47
- ...(ssl === undefined ? {} : { ssl }),
48
- })
49
- const adapter = new PrismaPg(this.pool)
50
-
51
- this.client = new PrismaClient({
52
- adapter,
41
+ // This client - not getDbClient()'s - is the one the app ends up using.
42
+ // The `?schema=` in the url is inert (node-postgres drops unknown URL
43
+ // params), so the schema has to travel via createCorePrismaClient.
44
+ const created = createCorePrismaClient({
45
+ connectionString: formatConnectionUrl(this.config),
46
+ configuredSchema: this.configuredSchema(),
53
47
  log:
54
48
  process.env.NODE_ENV === 'development'
55
49
  ? ['warn', 'error']
56
50
  : ['warn', 'error'],
57
51
  })
52
+ this.pool = created.pool
53
+ this.client = created.client
58
54
 
59
55
  // Bridge with existing backend-core getDbClient() usage
60
56
  setDbClient(this.client)
@@ -65,6 +61,7 @@ export class AcDatabaseManager {
65
61
  async connect(): Promise<void> {
66
62
  const client = this.getClient()
67
63
  await client.$connect()
64
+ if (this.pool) await verifyDbSchema(this.pool, this.configuredSchema())
68
65
  }
69
66
 
70
67
  async disconnect(): Promise<void> {
@@ -77,4 +74,8 @@ export class AcDatabaseManager {
77
74
  this.pool = null
78
75
  }
79
76
  }
77
+
78
+ private configuredSchema(): string | undefined {
79
+ return 'url' in this.config ? undefined : this.config.schema
80
+ }
80
81
  }
@@ -37,7 +37,11 @@ export function createPrismaDatabaseClient(prisma: {
37
37
  },
38
38
  getTables: async () => {
39
39
  const tables = await prisma.$queryRawUnsafe<{ tablename: string }[]>(
40
- `SELECT tablename FROM pg_tables WHERE schemaname = 'public'`,
40
+ // current_schema() - not a hardcoded 'public' - because the DML these
41
+ // tools go on to run is unqualified and therefore resolves through
42
+ // search_path. A schema-isolated deployment would otherwise be told
43
+ // about tables it cannot see.
44
+ `SELECT tablename FROM pg_tables WHERE schemaname = current_schema()`,
41
45
  )
42
46
  return tables.map((t) => t.tablename)
43
47
  },
@@ -51,7 +55,7 @@ export function createPrismaDatabaseClient(prisma: {
51
55
  >(
52
56
  `SELECT column_name, data_type, is_nullable
53
57
  FROM information_schema.columns
54
- WHERE table_name = '${tableName}' AND table_schema = 'public'`,
58
+ WHERE table_name = '${tableName}' AND table_schema = current_schema()`,
55
59
  )
56
60
  return columns.map((c) => ({
57
61
  name: c.column_name,
@@ -2,13 +2,17 @@
2
2
  * Freshness semantic for a catalog resource.
3
3
  *
4
4
  * The backend owns this calculation so the frontend stays dumb: it just renders
5
- * `lastCheckedAt` and, when `isStale` is true, a muted "may be out of date" note.
5
+ * `lastContentChangeAt` (falling back to `lastCheckedAt`) and, when `isStale` is
6
+ * true, a muted "may be out of date" note.
6
7
  *
7
8
  * Data source: the autoupdate scan records, per app, when its sources were last
8
- * verified (`lastCheckedAt`) and when the next check is due (`nextCheckAfter`,
9
- * adaptive backoff). "Stale" means the entry is more than a grace period past
10
- * its due date i.e. the automated freshness loop has fallen behind for this
11
- * app, so a human reader should treat the info with mild caution.
9
+ * verified (`lastCheckedAt`), when their content last actually changed
10
+ * (`lastContentChangeAt`), and when the next check is due (`nextCheckAfter`,
11
+ * adaptive backoff). The two "last" dates are deliberately distinct a source
12
+ * can be re-read many times without changing, so "checked" is not "updated".
13
+ * "Stale" means the entry is more than a grace period past its due date — i.e.
14
+ * the automated freshness loop has fallen behind for this app, so a human reader
15
+ * should treat the info with mild caution.
12
16
  */
13
17
 
14
18
  import type { Freshness } from '../../types/common/appCatalogTypes'
@@ -18,21 +22,31 @@ export type { Freshness }
18
22
  /** A check is "stale" once it is this far past its due date (ms). Default: 7 days. */
19
23
  export const STALE_GRACE_MS = 7 * 24 * 60 * 60 * 1000
20
24
 
25
+ /** The stored dates a resource's freshness is computed from (ISO strings). */
26
+ export interface FreshnessInput {
27
+ /** When the sources were last verified — only means the schedule advanced. */
28
+ lastCheckedAt?: string | null
29
+ /** When the next check is due (adaptive backoff). */
30
+ nextCheckAfter?: string | null
31
+ /** When the source content last actually changed. Null on pre-existing rows. */
32
+ lastContentChangeAt?: string | null
33
+ }
34
+
21
35
  /**
22
36
  * Compute the freshness semantic from the stored dates.
23
37
  *
24
- * @param lastCheckedAt ISO string or null — when sources were last verified.
25
- * @param nextCheckAfter ISO string or null — when the next check is due.
38
+ * @param dates the resource's stored freshness timestamps.
26
39
  * @param now current time (defaults to Date.now()); injectable for tests.
27
40
  */
28
41
  export function computeFreshness(
29
- lastCheckedAt: string | null,
30
- nextCheckAfter: string | null,
42
+ dates: FreshnessInput,
31
43
  now: number = Date.now(),
32
44
  ): Freshness {
45
+ const { lastCheckedAt, nextCheckAfter, lastContentChangeAt } = dates
46
+
33
47
  // Never checked → no line is shown at all (frontend hides null lastCheckedAt).
34
48
  if (!lastCheckedAt) {
35
- return { lastCheckedAt: null, isStale: false }
49
+ return { lastCheckedAt: null, lastContentChangeAt: null, isStale: false }
36
50
  }
37
51
 
38
52
  let isStale = false
@@ -43,5 +57,9 @@ export function computeFreshness(
43
57
  }
44
58
  }
45
59
 
46
- return { lastCheckedAt, isStale }
60
+ return {
61
+ lastCheckedAt,
62
+ lastContentChangeAt: lastContentChangeAt ?? null,
63
+ isStale,
64
+ }
47
65
  }
@@ -158,10 +158,15 @@ function rowToResource(row: ResourceRowWithSourceRefs): Resource {
158
158
  : undefined
159
159
  const tiers =
160
160
  row.tiers == null ? undefined : (row.tiers as unknown as Resource['tiers'])
161
- const freshness = computeFreshness(
162
- row.lastCheckedAt ? row.lastCheckedAt.toISOString() : null,
163
- row.nextCheckAfter ? row.nextCheckAfter.toISOString() : null,
164
- )
161
+ const freshness = computeFreshness({
162
+ lastCheckedAt: row.lastCheckedAt ? row.lastCheckedAt.toISOString() : null,
163
+ nextCheckAfter: row.nextCheckAfter
164
+ ? row.nextCheckAfter.toISOString()
165
+ : null,
166
+ lastContentChangeAt: row.lastContentChangeAt
167
+ ? row.lastContentChangeAt.toISOString()
168
+ : null,
169
+ })
165
170
 
166
171
  return {
167
172
  id: row.id,
@@ -0,0 +1,30 @@
1
+ import type { Request, Response } from 'express'
2
+
3
+ /**
4
+ * Asset URLs are keyed by row id or by icon name, and `upsertAsset` replaces
5
+ * content in place rather than inserting a new row — so the bytes behind a
6
+ * given URL can change. A long `max-age` would then serve the old artwork for
7
+ * the whole window (an icon replacement stayed invisible for a day). Revalidate
8
+ * against the stored checksum instead: unchanged content still costs only a
9
+ * conditional request answered with 304, and a replacement shows up at once.
10
+ *
11
+ * `variant` distinguishes derived renditions of the same row (a resize width,
12
+ * for example) so they do not share one entity tag.
13
+ */
14
+ export function setRevalidatingCacheHeaders(
15
+ res: Response,
16
+ checksum: string,
17
+ variant?: string | number,
18
+ ): string {
19
+ const etag =
20
+ variant === undefined ? `"${checksum}"` : `"${checksum}-${variant}"`
21
+
22
+ res.setHeader('ETag', etag)
23
+ res.setHeader('Cache-Control', 'public, max-age=0, must-revalidate')
24
+
25
+ return etag
26
+ }
27
+
28
+ export function isNotModified(req: Request, etag: string): boolean {
29
+ return req.headers['if-none-match'] === etag
30
+ }
@@ -4,6 +4,7 @@ import sharp from 'sharp'
4
4
  import { getDbClient } from '../../db'
5
5
  import { getImageFormat, isRasterImage, resizeImage } from './assetUtils'
6
6
  import { upsertAsset } from './upsertAsset'
7
+ import { isNotModified, setRevalidatingCacheHeaders } from './assetCache'
7
8
 
8
9
  // Configure multer for memory storage
9
10
  const upload = multer({
@@ -106,6 +107,7 @@ export function registerAssetRestController(
106
107
  name: true,
107
108
  width: true,
108
109
  height: true,
110
+ checksum: true,
109
111
  },
110
112
  })
111
113
 
@@ -129,6 +131,17 @@ export function registerAssetRestController(
129
131
  Number.isFinite(width) &&
130
132
  width > 0
131
133
 
134
+ // Answered before resizing, so an unchanged asset costs no image work.
135
+ const etag = setRevalidatingCacheHeaders(
136
+ res,
137
+ asset.checksum,
138
+ shouldResize ? `w${width}` : undefined,
139
+ )
140
+ if (isNotModified(req, etag)) {
141
+ res.status(304).end()
142
+ return
143
+ }
144
+
132
145
  if (shouldResize) {
133
146
  const fmt = getImageFormat(asset.mimeType) || 'jpeg'
134
147
  const buf = await resizeImage(
@@ -144,7 +157,6 @@ export function registerAssetRestController(
144
157
  // Set appropriate headers
145
158
  res.setHeader('Content-Type', outMime)
146
159
  res.setHeader('Content-Disposition', `inline; filename="${asset.name}"`)
147
- res.setHeader('Cache-Control', 'public, max-age=86400') // Cache for 1 day
148
160
 
149
161
  // Send binary content (resized if requested)
150
162
  res.send(outBuffer)
@@ -204,6 +216,7 @@ export function registerAssetRestController(
204
216
  name: true,
205
217
  width: true,
206
218
  height: true,
219
+ checksum: true,
207
220
  },
208
221
  })
209
222
 
@@ -229,6 +242,17 @@ export function registerAssetRestController(
229
242
  Number.isFinite(width) &&
230
243
  width > 0
231
244
 
245
+ // Answered before resizing, so an unchanged asset costs no image work.
246
+ const etag = setRevalidatingCacheHeaders(
247
+ res,
248
+ asset.checksum,
249
+ shouldResize ? `w${width}` : undefined,
250
+ )
251
+ if (isNotModified(req, etag)) {
252
+ res.status(304).end()
253
+ return
254
+ }
255
+
232
256
  if (shouldResize) {
233
257
  const fmt = asset.mimeType.includes('png')
234
258
  ? 'png'
@@ -258,7 +282,6 @@ export function registerAssetRestController(
258
282
  // Set appropriate headers
259
283
  res.setHeader('Content-Type', outMime)
260
284
  res.setHeader('Content-Disposition', `inline; filename="${asset.name}"`)
261
- res.setHeader('Cache-Control', 'public, max-age=86400') // Cache for 1 day
262
285
 
263
286
  // Send binary content (resized if requested)
264
287
  res.send(outBuffer)
@@ -99,6 +99,7 @@ export async function parseAssetMeta(
99
99
  tiff: 'image/tiff',
100
100
  gif: 'image/gif',
101
101
  heif: 'image/heif',
102
+ svg: 'image/svg+xml',
102
103
  raw: 'application/octet-stream',
103
104
  }
104
105