@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
@@ -1,6 +1,7 @@
1
1
  import type { Request, Response, Router } from 'express'
2
2
  import sharp from 'sharp'
3
3
  import { getDbClient } from '../../db'
4
+ import { isNotModified, setRevalidatingCacheHeaders } from './assetCache'
4
5
 
5
6
  export interface ScreenshotRestControllerConfig {
6
7
  /**
@@ -133,6 +134,7 @@ export function registerScreenshotRestController(
133
134
  content: true,
134
135
  mimeType: true,
135
136
  name: true,
137
+ checksum: true,
136
138
  },
137
139
  })
138
140
 
@@ -141,6 +143,17 @@ export function registerScreenshotRestController(
141
143
  return
142
144
  }
143
145
 
146
+ // Answered before resizing, so an unchanged screenshot costs no image work.
147
+ const etag = setRevalidatingCacheHeaders(
148
+ res,
149
+ screenshot.checksum,
150
+ targetSize && targetSize > 0 ? `s${targetSize}` : undefined,
151
+ )
152
+ if (isNotModified(req, etag)) {
153
+ res.status(304).end()
154
+ return
155
+ }
156
+
144
157
  let content: Uint8Array | Buffer = screenshot.content
145
158
 
146
159
  // Resize if size parameter provided
@@ -164,7 +177,6 @@ export function registerScreenshotRestController(
164
177
  'Content-Disposition',
165
178
  `inline; filename="${screenshot.name}"`,
166
179
  )
167
- res.setHeader('Cache-Control', 'public, max-age=86400') // Cache for 1 day
168
180
 
169
181
  // Send binary content
170
182
  res.send(content)
@@ -27,6 +27,14 @@ export async function upsertAsset({
27
27
  })
28
28
 
29
29
  if (existing) {
30
+ // Reusing the stored binary must not keep a mimeType we no longer derive:
31
+ // rows written by an older, wrong derivation would never be corrected.
32
+ if (existing.mimeType !== mimeType) {
33
+ await prisma.dbAsset.update({
34
+ where: { id: existing.id },
35
+ data: { mimeType },
36
+ })
37
+ }
30
38
  return existing.id
31
39
  }
32
40
 
@@ -3,6 +3,10 @@ import multer from 'multer'
3
3
  import { createHash } from 'node:crypto'
4
4
  import { getDbClient } from '../../db'
5
5
  import { getExtensionFromFilename, getExtensionFromMimeType } from './iconUtils'
6
+ import {
7
+ isNotModified,
8
+ setRevalidatingCacheHeaders,
9
+ } from '../assets/assetCache'
6
10
 
7
11
  // Configure multer for memory storage
8
12
  const upload = multer({
@@ -112,6 +116,7 @@ export function registerIconRestController(
112
116
  content: true,
113
117
  mimeType: true,
114
118
  name: true,
119
+ checksum: true,
115
120
  },
116
121
  })
117
122
 
@@ -120,10 +125,15 @@ export function registerIconRestController(
120
125
  return
121
126
  }
122
127
 
128
+ const etag = setRevalidatingCacheHeaders(res, icon.checksum)
129
+ if (isNotModified(req, etag)) {
130
+ res.status(304).end()
131
+ return
132
+ }
133
+
123
134
  // Set appropriate headers
124
135
  res.setHeader('Content-Type', icon.mimeType)
125
136
  res.setHeader('Content-Disposition', `inline; filename="${icon.name}"`)
126
- res.setHeader('Cache-Control', 'public, max-age=86400') // Cache for 1 day
127
137
 
128
138
  // Send binary content
129
139
  res.send(icon.content)
@@ -1,8 +1,6 @@
1
1
  import type { Tool } from 'ai'
2
2
  import { z } from 'zod'
3
- import { PrismaClient } from '../../generated/prisma/client'
4
- import { PrismaPg } from '@prisma/adapter-pg'
5
- import pg from 'pg'
3
+ import { createCorePrismaClient } from '../../db/client'
6
4
  import type { AcDatabaseConfig } from '../../middleware/types.js'
7
5
 
8
6
  // ============================================================================
@@ -27,10 +25,11 @@ function getDatabaseUrl(config: AcDatabaseConfig): string {
27
25
  export function createAppCatalogAITools(
28
26
  databaseConfig: AcDatabaseConfig,
29
27
  ): Record<string, Tool> {
30
- const databaseUrl = getDatabaseUrl(databaseConfig)
31
- const pool = new pg.Pool({ connectionString: databaseUrl })
32
- const adapter = new PrismaPg(pool)
33
- const prisma = new PrismaClient({ adapter })
28
+ const { client: prisma } = createCorePrismaClient({
29
+ connectionString: getDatabaseUrl(databaseConfig),
30
+ configuredSchema:
31
+ 'url' in databaseConfig ? undefined : databaseConfig.schema,
32
+ })
34
33
 
35
34
  const getAppCardSchema = z.object({
36
35
  slug: z.string().describe('The app slug to fetch'),
@@ -46,6 +46,13 @@ export interface SourceReference {
46
46
  export interface Freshness {
47
47
  /** ISO-8601 timestamp the app's sources were last verified, or null if never. */
48
48
  lastCheckedAt: string | null
49
+ /**
50
+ * ISO-8601 timestamp the source content last actually CHANGED — what the UI
51
+ * shows as "Updated". Distinct from `lastCheckedAt`: a source can be re-read
52
+ * repeatedly without changing. Null for entries scanned before this was
53
+ * tracked, in which case the frontend falls back to `lastCheckedAt`.
54
+ */
55
+ lastContentChangeAt: string | null
49
56
  /** True when the entry is overdue for a re-check by more than the grace period. */
50
57
  isStale: boolean
51
58
  }
@@ -118,6 +125,7 @@ export interface Resource {
118
125
  */
119
126
  lastCheckedAt?: string | null
120
127
  nextCheckAfter?: string | null
128
+ lastContentChangeAt?: string | null
121
129
  /** ISO-8601 timestamp of when this entry was first created in the catalog DB. */
122
130
  createdAt?: string
123
131
  }