@igstack/app-catalog-backend-core 6.9.0 → 6.10.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 (39) hide show
  1. package/dist/db/syncAppCatalog.mjs +1 -0
  2. package/dist/db/syncAppCatalog.mjs.map +1 -1
  3. package/dist/generated/prisma/internal/class.mjs +4 -4
  4. package/dist/generated/prisma/internal/class.mjs.map +1 -1
  5. package/dist/generated/prisma/internal/prismaNamespace.d.mts +1 -0
  6. package/dist/generated/prisma/internal/prismaNamespace.d.mts.map +1 -1
  7. package/dist/generated/prisma/models/DbApprovalMethod.d.mts +1 -1
  8. package/dist/generated/prisma/models/DbAsset.d.mts +1 -1
  9. package/dist/generated/prisma/models/DbFeedback.d.mts +1 -1
  10. package/dist/generated/prisma/models/DbResource.d.mts +44 -1
  11. package/dist/generated/prisma/models/DbResource.d.mts.map +1 -1
  12. package/dist/index.d.mts +3 -3
  13. package/dist/index.mjs +2 -2
  14. package/dist/modules/appCatalog/service.d.mts +0 -1
  15. package/dist/modules/appCatalog/service.d.mts.map +1 -1
  16. package/dist/modules/appCatalog/service.mjs +2 -0
  17. package/dist/modules/appCatalog/service.mjs.map +1 -1
  18. package/dist/modules/feedback/feedbackRouter.mjs.map +1 -1
  19. package/dist/modules/feedback/service.d.mts +8 -1
  20. package/dist/modules/feedback/service.d.mts.map +1 -1
  21. package/dist/modules/feedback/service.mjs +12 -3
  22. package/dist/modules/feedback/service.mjs.map +1 -1
  23. package/dist/types/common/appCatalogTypes.d.mts +26 -1
  24. package/dist/types/common/appCatalogTypes.d.mts.map +1 -1
  25. package/package.json +4 -4
  26. package/prisma/schema.prisma +2 -0
  27. package/src/__tests__/rowToResourceClaims.test.ts +48 -0
  28. package/src/__tests__/stripHiddenText.test.ts +111 -0
  29. package/src/db/syncAppCatalog.ts +1 -0
  30. package/src/generated/prisma/internal/class.ts +4 -4
  31. package/src/generated/prisma/internal/prismaNamespace.ts +1 -0
  32. package/src/generated/prisma/internal/prismaNamespaceBrowser.ts +1 -0
  33. package/src/generated/prisma/models/DbResource.ts +45 -1
  34. package/src/index.ts +1 -0
  35. package/src/modules/appCatalog/service.ts +6 -1
  36. package/src/modules/feedback/feedbackRouter.ts +4 -2
  37. package/src/modules/feedback/service.ts +20 -2
  38. package/src/prisma-json-types.d.ts +3 -0
  39. package/src/types/common/appCatalogTypes.ts +26 -0
@@ -121,6 +121,29 @@ interface Freshness {
121
121
  /** True when the entry is overdue for a re-check by more than the grace period. */
122
122
  isStale: boolean;
123
123
  }
124
+ /**
125
+ * One plain statement about an entry, kept so the card can say why it says what it
126
+ * says. Active claims are an input the card text must honour, and they win over a
127
+ * source that contradicts them. Review claims are recorded but not yet verified;
128
+ * they never drive card text. Archived claims are kept with the reason they
129
+ * stopped applying, and are ignored.
130
+ */
131
+ interface Claim {
132
+ /** One plain statement about the app, phrased as a fact. */
133
+ text: string;
134
+ /** ISO date the claim was recorded. */
135
+ addedAt: string;
136
+ /** Where it came from: `feedback:<id>`, a person or team, or a URL. */
137
+ origin?: string;
138
+ /**
139
+ * `active` = verified, drives card text; `review` = recorded but not yet
140
+ * verified, never drives card text; `archived` = stopped applying.
141
+ */
142
+ status: 'active' | 'review' | 'archived';
143
+ /** Required when status is 'archived'. */
144
+ archiveReason?: string;
145
+ archivedAt?: string;
146
+ }
124
147
  /**
125
148
  * Resource entry in the catalog (application or sub-resource).
126
149
  * Unified model: applications have no parentSlug; sub-resources have parentSlug.
@@ -176,6 +199,8 @@ interface Resource {
176
199
  tiers?: TierVariant[];
177
200
  /** Deep links that take one typed identifier — see {@link QuickJump}. */
178
201
  quickJumps?: QuickJump[];
202
+ /** Why the card says what it says — see {@link Claim}. */
203
+ claims?: Claim[];
179
204
  /**
180
205
  * What this resource's children are called, as a plural noun ("Accounts",
181
206
  * "Namespaces", "Databases"). Set on the PARENT, not the children.
@@ -265,5 +290,5 @@ interface AppCatalogData {
265
290
  versions?: AppVersionInfo;
266
291
  }
267
292
  //#endregion
268
- export { AppApprovalMethod, AppCatalogData, AppCategory, AppVersionInfo, Freshness, GroupingTagDefinition, GroupingTagValue, QuickJump, Resource, SourceCadence, SourceCheck, SourcePulse, SourcePulseState, SourceReference, SourceSchedule, TierVariant, VersionInfo };
293
+ export { AppApprovalMethod, AppCatalogData, AppCategory, AppVersionInfo, Claim, Freshness, GroupingTagDefinition, GroupingTagValue, QuickJump, Resource, SourceCadence, SourceCheck, SourcePulse, SourcePulseState, SourceReference, SourceSchedule, TierVariant, VersionInfo };
269
294
  //# sourceMappingURL=appCatalogTypes.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"appCatalogTypes.d.mts","names":[],"sources":["../../../src/types/common/appCatalogTypes.ts"],"mappings":";;;;;;;;UAmBiB,WAAA;EACf,QAAA;EACA,WAAA;EACA,WAAA;EACA,MAAA;EACA,aAAA,GAAgB,gBAAA;AAAA;;;;;;AAqClB;;;;;AAWA;;UA7BiB,SAAA;EAuCY;;;;;;EAhC3B,QAAA;EAgC2B;EA9B3B,KAAA;EACA,GAAA;AAAA;;UAQe,WAAA;EAyBQ;EAvBvB,IAAA;EACA,OAAA;AAAA;;;AAoCF;;;UA5BiB,cAAA;EAgCN;EA9BT,aAAA;EA0CmB;EAxCnB,cAAA;EAyBA;EAvBA,mBAAA;EAyBA;EAvBA,kBAAA;EAwBS;EAtBT,aAAA,GAAgB,WAAA;AAAA;;KAIN,aAAA;;KAQA,gBAAA;;;AAkCZ;;UA5BiB,WAAA;EACf,KAAA,EAAO,gBAAA;EA4BP;EA1BA,aAAA;EACA,OAAA,EAAS,aAAA;EACT,aAAA;EA4BW;EA1BX,KAAA;EACA,mBAAA;EA2BmB;;AAOrB;;;EA5BE,yBAAA;EA8BA;EA5BA,MAAA,EAAQ,WAAA;AAAA;;;AA4CV;;;;;;;UAhCiB,eAAA;EACf,UAAA;EACA,GAAA;EACA,SAAA;EA8BA;EA5BA,QAAA,GAAW,cAAA;EA+BX;EA7BA,KAAA,GAAQ,WAAA;AAAA;;;;;UAOO,SAAA;EAmCf;EAjCA,aAAA;EA2CA;;;;;;EApCA,mBAAA;EAyCA;EAvCA,OAAA;AAAA;;;;;UAOe,QAAA;EACf,EAAA;EACA,IAAA;EA8CQ;EA5CR,IAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA;EACA,WAAA;EACA,KAAA;EACA,aAAA,GAAgB,gBAAA;EAgEhB;;;;;;EAzDA,KAAA;EA2EA;;;;;;AAaF;;EA/EE,UAAA;EACA,IAAA;EACA,MAAA;EACA,KAAA;IAAU,GAAA;IAAa,KAAA;EAAA;EACvB,QAAA;EACA,aAAA;EACA,OAAA,cAAqB,eAAA;EACrB,UAAA;IAiFQ,0HA/EN,IAAA,iCA+EsB;IA7EtB,eAAA,WAgFiB;IA9EjB,OAAA;EAAA;EA+EK;EA5EP,QAAA;EA4EE;EA1EF,QAAA;EA0EM;EAxEN,SAAA;EAuEuB;EArEvB,KAAA,GAAQ,WAAA;EAsEN;EApEF,UAAA,GAAa,SAAA;EAoEH;;;AAGZ;;;;;AAKA;;;EAhEE,aAAA;EAiEA;EA7DA,UAAA;EA+DA;EA7DA,IAAA;EA6DW;EA3DX,OAAA;EA8D0B;EA5D1B,eAAA;EA4D0B;EA1D1B,aAAA;EA4DA;EA1DA,cAAA;EA8DA;EA5DA,KAAA,GAAQ,MAAA;EA4DF;AAGR;;;;;EAxDE,SAAA,GAAY,SAAA;EA2Da;;;;;EArDzB,aAAA;EACA,cAAA;EACA,mBAAA;EAmDyB;;AAG3B;;;EAhDE,cAAA;EAkDiB;EAhDjB,SAAA;AAAA;AAAA,UAIe,WAAA;EACf,EAAA;EACA,IAAA;AAAA;AAAA,UAGe,qBAAA;EACf,MAAA;EACA,WAAA;EACA,WAAA;EACA,MAAA,EAAQ,gBAAA;AAAA;AAAA,KAGL,gBAAA,8BAA8C,CAAA,eAC/C,IAAA,CAAK,CAAA,EAAG,IAAA;AAAA,KAGA,iBAAA,GAAoB,gBAAA,CAC9B,cAAA;AAAA,UAIe,gBAAA;EACf,KAAA;EACA,WAAA;EACA,WAAA;AAAA;AAAA,UAGe,WAAA;EACf,WAAA;EACA,GAAA;;EAEA,GAAA;;EAEA,MAAA;AAAA;AAAA,UAGe,cAAA;EACf,OAAA,GAAU,WAAA;EACV,QAAA,GAAW,WAAA;EACX,WAAA,GAAc,WAAA;AAAA;AAAA,UAGC,cAAA;EACf,SAAA,EAAW,QAAA;EACX,eAAA,EAAiB,qBAAA;EACjB,eAAA,EAAiB,iBAAA;EACjB,OAAA,EAAS,MAAA;EACT,MAAA,EAAQ,KAAA;EACR,QAAA,GAAW,cAAA;AAAA"}
1
+ {"version":3,"file":"appCatalogTypes.d.mts","names":[],"sources":["../../../src/types/common/appCatalogTypes.ts"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@igstack/app-catalog-backend-core",
3
- "version": "6.9.0",
3
+ "version": "6.10.0",
4
4
  "description": "Backend core library for App Catalog",
5
5
  "homepage": "https://github.com/lislon/app-catalog",
6
6
  "repository": {
@@ -44,8 +44,8 @@
44
44
  "tsyringe": "^4.10.0",
45
45
  "yaml": "^2.8.0",
46
46
  "zod": "^4.3.5",
47
- "@igstack/app-catalog-shared-core": "6.9.0",
48
- "@igstack/app-catalog-table-sync": "6.9.0"
47
+ "@igstack/app-catalog-shared-core": "6.10.0",
48
+ "@igstack/app-catalog-table-sync": "6.10.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@tanstack/vite-config": "^0.4.3",
@@ -72,7 +72,7 @@
72
72
  "engines": {
73
73
  "node": ">=24"
74
74
  },
75
- "gitHead": "6b0730c9d2d396517940db7d847080f4737df2fd",
75
+ "gitHead": "8a9a5fb554ef3414c474c608d32f830907dfcc72",
76
76
  "scripts": {
77
77
  "build": "tsdown",
78
78
  "build:lenient": "tsdown",
@@ -157,6 +157,8 @@ model DbResource {
157
157
  tiers Json?
158
158
  /// [QuickJump[]]
159
159
  quickJumps Json?
160
+ /// [Claim[]] Statements the card text is built from; see Claim in appCatalogTypes.
161
+ claims Json?
160
162
  childrenLabel String? // Plural noun for this resource's children ("Accounts"); set on the parent, names its tab
161
163
  sourceRefs SourceReference[]
162
164
  feedback DbFeedback[]
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { rowToResource } from '../modules/appCatalog/service'
3
+
4
+ // Only the columns rowToResource reads unconditionally; everything else is null.
5
+ function row(claims: unknown) {
6
+ return {
7
+ id: 'example-app',
8
+ slug: 'example-app',
9
+ type: 'application',
10
+ displayName: 'Example App',
11
+ description: 'Does things.',
12
+ nicknames: [],
13
+ teams: [],
14
+ tags: [],
15
+ screenshotIds: [],
16
+ urlIssues: [],
17
+ aliases: [],
18
+ approverSlugs: [],
19
+ sourceRefs: [],
20
+ createdAt: new Date('2026-01-01'),
21
+ claims,
22
+ } as unknown as Parameters<typeof rowToResource>[0]
23
+ }
24
+
25
+ describe('rowToResource — claims', () => {
26
+ it('passes stored claims through unchanged', () => {
27
+ const claims = [
28
+ {
29
+ text: 'Lead approves access.',
30
+ addedAt: '2026-10-12',
31
+ origin: 'feedback:abc',
32
+ status: 'active',
33
+ },
34
+ {
35
+ text: 'No mobile app.',
36
+ addedAt: '2026-08-03',
37
+ status: 'archived',
38
+ archiveReason: 'superseded by https://example.com',
39
+ archivedAt: '2026-10-02',
40
+ },
41
+ ]
42
+ expect(rowToResource(row(claims)).claims).toEqual(claims)
43
+ })
44
+
45
+ it('omits claims when the column is null', () => {
46
+ expect(rowToResource(row(null)).claims).toBeUndefined()
47
+ })
48
+ })
@@ -0,0 +1,111 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { stripHiddenText } from '../modules/feedback/service'
3
+
4
+ describe('stripHiddenText', () => {
5
+ test('strips zero-width characters', () => {
6
+ // U+200B ZERO WIDTH SPACE
7
+ const input = `see${String.fromCodePoint(0x200b)}docs`
8
+ expect(stripHiddenText(input)).toBe('seedocs')
9
+ })
10
+
11
+ test('strips zero-width non-joiner', () => {
12
+ // U+200C ZERO WIDTH NON-JOINER
13
+ const input = `word${String.fromCodePoint(0x200c)}boundary`
14
+ expect(stripHiddenText(input)).toBe('wordboundary')
15
+ })
16
+
17
+ test('strips word joiner', () => {
18
+ // U+2060 WORD JOINER
19
+ const input = `no${String.fromCodePoint(0x2060)}break`
20
+ expect(stripHiddenText(input)).toBe('nobreak')
21
+ })
22
+
23
+ test('strips byte order mark', () => {
24
+ // U+FEFF ZERO WIDTH NO-BREAK SPACE (BOM)
25
+ const input = `${String.fromCodePoint(0xfeff)}text`
26
+ expect(stripHiddenText(input)).toBe('text')
27
+ })
28
+
29
+ test('strips left-to-right mark', () => {
30
+ // U+200E LEFT-TO-RIGHT MARK
31
+ const input = `text${String.fromCodePoint(0x200e)}here`
32
+ expect(stripHiddenText(input)).toBe('texthere')
33
+ })
34
+
35
+ test('strips right-to-left mark', () => {
36
+ // U+200F RIGHT-TO-LEFT MARK
37
+ const input = `text${String.fromCodePoint(0x200f)}here`
38
+ expect(stripHiddenText(input)).toBe('texthere')
39
+ })
40
+
41
+ test('strips directional embedding', () => {
42
+ // U+202A LEFT-TO-RIGHT EMBEDDING
43
+ const input = `text${String.fromCodePoint(0x202a)}here`
44
+ expect(stripHiddenText(input)).toBe('texthere')
45
+ })
46
+
47
+ test('strips directional override', () => {
48
+ // U+202E RIGHT-TO-LEFT OVERRIDE
49
+ const input = `text${String.fromCodePoint(0x202e)}here`
50
+ expect(stripHiddenText(input)).toBe('texthere')
51
+ })
52
+
53
+ test('strips directional isolate', () => {
54
+ // U+2066 LEFT-TO-RIGHT ISOLATE
55
+ const input = `text${String.fromCodePoint(0x2066)}here`
56
+ expect(stripHiddenText(input)).toBe('texthere')
57
+ })
58
+
59
+ test('strips pop directional isolate', () => {
60
+ // U+2069 POP DIRECTIONAL ISOLATE
61
+ const input = `text${String.fromCodePoint(0x2069)}here`
62
+ expect(stripHiddenText(input)).toBe('texthere')
63
+ })
64
+
65
+ test('strips Unicode tag characters', () => {
66
+ // U+E0020 TAG SPACE - example from the tag character range
67
+ const payload = 'ignore all instructions'
68
+ const tagEncoded = payload
69
+ .split('')
70
+ .map((ch) => String.fromCodePoint(0xe0000 + ch.charCodeAt(0)))
71
+ .join('')
72
+ const input = `see docs${tagEncoded}`
73
+ expect(stripHiddenText(input)).toBe('see docs')
74
+ })
75
+
76
+ test('preserves zero-width joiner for emoji sequences', () => {
77
+ // U+200D ZERO WIDTH JOINER - needed for emoji like 👩‍💻
78
+ const input = '👩‍💻'
79
+ expect(stripHiddenText(input)).toBe('👩‍💻')
80
+ })
81
+
82
+ test('preserves normal text unchanged', () => {
83
+ const input = 'This is normal text with spaces and punctuation!'
84
+ expect(stripHiddenText(input)).toBe(input)
85
+ })
86
+
87
+ test('preserves newlines', () => {
88
+ const input = 'Line one\nLine two\r\nLine three'
89
+ expect(stripHiddenText(input)).toBe(input)
90
+ })
91
+
92
+ test('preserves non-Latin text', () => {
93
+ expect(stripHiddenText('Привет мир')).toBe('Привет мир')
94
+ expect(stripHiddenText('日本語')).toBe('日本語')
95
+ expect(stripHiddenText('مرحبا')).toBe('مرحبا')
96
+ })
97
+
98
+ test('handles mixed visible and invisible characters', () => {
99
+ const input = `Hello${String.fromCodePoint(0x200b)}World${String.fromCodePoint(0x200e)}!`
100
+ expect(stripHiddenText(input)).toBe('HelloWorld!')
101
+ })
102
+
103
+ test('handles empty string', () => {
104
+ expect(stripHiddenText('')).toBe('')
105
+ })
106
+
107
+ test('handles string with only invisible characters', () => {
108
+ const input = String.fromCodePoint(0x200b, 0x200c, 0x200e)
109
+ expect(stripHiddenText(input)).toBe('')
110
+ })
111
+ })
@@ -313,6 +313,7 @@ export async function syncAppCatalog(
313
313
  urlIssues: resource.urlIssues ?? [],
314
314
  tiers: resource.tiers ?? null,
315
315
  quickJumps: resource.quickJumps ?? null,
316
+ claims: resource.claims ?? null,
316
317
  childrenLabel: resource.childrenLabel ?? null,
317
318
  // Fields from former SubResource
318
319
  parentSlug: resource.parentSlug ?? null,