@mostrom/service-catalog 0.1.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.
package/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # Service Catalog
2
+
3
+ Shared catalog data for platform services. This package provides the data contract and mock dataset used by the App Shell and other apps.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ import { getServiceCatalogSync, searchServices } from "@platform/service-catalog";
9
+
10
+ const catalog = getServiceCatalogSync();
11
+ const results = catalog ? searchServices(catalog, "crm") : [];
12
+ ```
13
+
14
+ ## Swap mock data for API
15
+
16
+ The default export uses the mock catalog in `src/mock/catalog.ts`.
17
+ To replace it with a real API later:
18
+
19
+ 1. Create a fetcher that returns `ServiceCatalog`.
20
+ 2. Replace `defaultSource` in `src/adapter.ts` with `createRemoteCatalogSource(fetcher)`.
21
+
22
+ The UI will continue to work without any changes because it only relies on the shared contract.
package/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "@mostrom/service-catalog",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/index.ts"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public"
11
+ }
12
+ }
package/src/adapter.ts ADDED
@@ -0,0 +1,103 @@
1
+ import { mockServiceCatalog } from "./mock/catalog";
2
+ import { createMockCatalogSource } from "./source";
3
+ import type { ServiceCatalog, ServiceGroup, ServiceItem } from "./types";
4
+ import type { ServiceCatalogSource } from "./source";
5
+
6
+ const defaultSource = createMockCatalogSource(mockServiceCatalog);
7
+
8
+ export function getServiceCatalog(): Promise<ServiceCatalog> {
9
+ return defaultSource.getCatalog();
10
+ }
11
+
12
+ export function getServiceCatalogSync(): ServiceCatalog | null {
13
+ return defaultSource.getCatalogSync?.() ?? null;
14
+ }
15
+
16
+ export function groupServicesByCategory(catalog: ServiceCatalog): ServiceGroup[] {
17
+ const servicesByCategory = new Map<string, ServiceItem[]>();
18
+
19
+ for (const service of catalog.services) {
20
+ const bucket = servicesByCategory.get(service.categoryId) ?? [];
21
+ bucket.push(service);
22
+ servicesByCategory.set(service.categoryId, bucket);
23
+ }
24
+
25
+ const categories = [...catalog.categories].sort((a, b) => a.order - b.order);
26
+
27
+ return categories.map((category) => ({
28
+ category,
29
+ services: (servicesByCategory.get(category.id) ?? []).sort((a, b) =>
30
+ a.name.localeCompare(b.name),
31
+ ),
32
+ }));
33
+ }
34
+
35
+ const normalize = (value: string) =>
36
+ value
37
+ .toLowerCase()
38
+ .replace(/[^a-z0-9\s]/g, " ")
39
+ .replace(/\s+/g, " ")
40
+ .trim();
41
+
42
+ const scoreMatch = (text: string, query: string) => {
43
+ if (!text) return 0;
44
+ if (text.startsWith(query)) return 6;
45
+ if (text.includes(query)) return 3;
46
+ return 0;
47
+ };
48
+
49
+ export function searchServices(
50
+ catalog: ServiceCatalog,
51
+ query: string,
52
+ limit = 12,
53
+ ): ServiceItem[] {
54
+ const normalizedQuery = normalize(query);
55
+ if (!normalizedQuery) return [];
56
+
57
+ const tokens = normalizedQuery.split(" ");
58
+
59
+ // Build category lookup map for including category names in search
60
+ const categoryMap = new Map(
61
+ catalog.categories.map((cat) => [cat.id, normalize(cat.label)])
62
+ );
63
+
64
+ const scored = catalog.services
65
+ .map((service) => {
66
+ const name = normalize(service.name);
67
+ const summary = normalize(service.summary);
68
+ const keywords = (service.keywords ?? []).map(normalize).join(" ");
69
+ const categoryName = categoryMap.get(service.categoryId) ?? "";
70
+
71
+ let score = 0;
72
+ score += scoreMatch(name, normalizedQuery) * 3;
73
+ score += scoreMatch(summary, normalizedQuery) * 2;
74
+ score += scoreMatch(keywords, normalizedQuery);
75
+ score += scoreMatch(categoryName, normalizedQuery);
76
+
77
+ for (const token of tokens) {
78
+ if (name.includes(token)) score += 2;
79
+ if (summary.includes(token)) score += 1;
80
+ if (keywords.includes(token)) score += 1;
81
+ if (categoryName.includes(token)) score += 1;
82
+ }
83
+
84
+ return { service, score };
85
+ })
86
+ .filter((item) => item.score > 0)
87
+ .sort((a, b) => {
88
+ if (a.score !== b.score) return b.score - a.score;
89
+ return a.service.name.localeCompare(b.service.name);
90
+ })
91
+ .map((item) => item.service);
92
+
93
+ return scored.slice(0, limit);
94
+ }
95
+
96
+ export function createServiceCatalogClient(source: ServiceCatalogSource = defaultSource) {
97
+ return {
98
+ getCatalog: () => source.getCatalog(),
99
+ getCatalogSync: () => source.getCatalogSync?.() ?? null,
100
+ };
101
+ }
102
+
103
+ export const serviceCatalogClient = createServiceCatalogClient(defaultSource);
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ export type { ServiceCatalog, ServiceCategory, ServiceGroup, ServiceItem } from "./types";
2
+ export type { ServiceCatalogSource } from "./source";
3
+ export {
4
+ createRemoteCatalogSource,
5
+ createMockCatalogSource,
6
+ } from "./source";
7
+ export {
8
+ getServiceCatalog,
9
+ getServiceCatalogSync,
10
+ groupServicesByCategory,
11
+ searchServices,
12
+ createServiceCatalogClient,
13
+ serviceCatalogClient,
14
+ } from "./adapter";
15
+ export { mockServiceCatalog } from "./mock/catalog";
@@ -0,0 +1,496 @@
1
+ import type { ServiceCatalog } from "../types";
2
+
3
+ export const mockServiceCatalog: ServiceCatalog = {
4
+ updatedAt: "2026-02-08T00:00:00.000Z",
5
+ categories: [
6
+ {
7
+ id: "client-management",
8
+ label: "Client Management",
9
+ description: "Manage clients, pipeline, and customer workstreams.",
10
+ order: 1,
11
+ },
12
+ {
13
+ id: "project-management",
14
+ label: "Project Management",
15
+ description: "Plan, track, and deliver projects and tasks.",
16
+ order: 2,
17
+ },
18
+ {
19
+ id: "finance-management",
20
+ label: "Finance Management",
21
+ description: "Bill, collect, and track financial performance.",
22
+ order: 3,
23
+ },
24
+ {
25
+ id: "knowledge-management",
26
+ label: "Knowledge Management",
27
+ description: "Wikis, knowledge bases, and internal documentation.",
28
+ order: 4,
29
+ },
30
+ {
31
+ id: "people-and-hr",
32
+ label: "People & HR",
33
+ description: "Hiring, payroll, and HR operations.",
34
+ order: 5,
35
+ },
36
+ {
37
+ id: "growth-and-marketing",
38
+ label: "Growth & Marketing",
39
+ description: "Campaigns, automation, and audience engagement.",
40
+ order: 6,
41
+ },
42
+ {
43
+ id: "operations-and-automation",
44
+ label: "Operations & Automation",
45
+ description: "Workflow automation and system integrations.",
46
+ order: 7,
47
+ },
48
+ {
49
+ id: "analytics",
50
+ label: "Analytics",
51
+ description: "Dashboards, reporting, and performance insights.",
52
+ order: 8,
53
+ },
54
+ {
55
+ id: "administration",
56
+ label: "Administration",
57
+ description: "Billing, spending governance, and access control.",
58
+ order: 9,
59
+ },
60
+ ],
61
+ services: [
62
+ // Client Management
63
+ {
64
+ id: "clients",
65
+ name: "Clients",
66
+ summary: "Track client accounts and relationship history.",
67
+ categoryId: "client-management",
68
+ href: "/client-management/clients",
69
+ keywords: ["crm", "accounts", "customers"],
70
+ },
71
+ {
72
+ id: "contacts",
73
+ name: "Contacts",
74
+ summary: "Manage people, roles, and contact details.",
75
+ categoryId: "client-management",
76
+ href: "/client-management/contacts",
77
+ keywords: ["people", "stakeholders", "directory"],
78
+ },
79
+ {
80
+ id: "deals",
81
+ name: "Deals",
82
+ summary: "Monitor pipeline stages, forecasts, and close dates.",
83
+ categoryId: "client-management",
84
+ href: "/client-management/deals",
85
+ keywords: ["pipeline", "sales", "opportunities"],
86
+ },
87
+ {
88
+ id: "estimates",
89
+ name: "Estimates",
90
+ summary: "Create estimates and quotes tied to client work.",
91
+ categoryId: "client-management",
92
+ href: "/client-management/estimates",
93
+ keywords: ["quotes", "pricing", "scope"],
94
+ },
95
+ {
96
+ id: "proposals",
97
+ name: "Proposals",
98
+ summary: "Build proposals with scope, pricing, and approvals.",
99
+ categoryId: "client-management",
100
+ href: "/client-management/proposals",
101
+ keywords: ["rfp", "scope", "approvals"],
102
+ },
103
+ {
104
+ id: "contracts",
105
+ name: "Contracts",
106
+ summary: "Generate agreements and track signatures.",
107
+ categoryId: "client-management",
108
+ href: "/client-management/contracts",
109
+ keywords: ["agreements", "signatures", "legal"],
110
+ },
111
+ {
112
+ id: "cm-forms",
113
+ name: "Forms",
114
+ summary: "Create contact forms and intake surveys.",
115
+ categoryId: "client-management",
116
+ href: "/client-management/forms",
117
+ keywords: ["surveys", "intake", "questionnaires"],
118
+ },
119
+ {
120
+ id: "scheduling",
121
+ name: "Scheduling",
122
+ summary: "Book meetings and coordinate availability.",
123
+ categoryId: "client-management",
124
+ href: "/client-management/scheduling",
125
+ keywords: ["calendar", "meetings", "availability"],
126
+ },
127
+ {
128
+ id: "services",
129
+ name: "Services",
130
+ summary: "Define service offerings and standard packages.",
131
+ categoryId: "client-management",
132
+ href: "/client-management/services",
133
+ keywords: ["offerings", "packages", "catalog"],
134
+ },
135
+ {
136
+ id: "client-portal",
137
+ name: "Client Portal",
138
+ summary: "Give clients a secure space to view work and files.",
139
+ categoryId: "client-management",
140
+ href: "/client-management/client-portal",
141
+ keywords: ["portal", "sharing", "client access"],
142
+ },
143
+
144
+ // Project Management
145
+ {
146
+ id: "projects",
147
+ name: "Projects",
148
+ summary: "Create and manage projects with tasks, milestones, and team assignments.",
149
+ categoryId: "project-management",
150
+ href: "/project-management/projects",
151
+ keywords: ["workstreams", "delivery", "planning"],
152
+ },
153
+ {
154
+ id: "tasks",
155
+ name: "Tasks",
156
+ summary: "Manage individual work items across all projects.",
157
+ categoryId: "project-management",
158
+ href: "/project-management/tasks",
159
+ keywords: ["kanban", "backlog", "workflow", "todo"],
160
+ },
161
+ {
162
+ id: "milestones",
163
+ name: "Milestones",
164
+ summary: "Define and track key project milestones and deliverables.",
165
+ categoryId: "project-management",
166
+ href: "/project-management/milestones",
167
+ keywords: ["deadlines", "deliverables", "checkpoints"],
168
+ },
169
+ {
170
+ id: "time-tracking",
171
+ name: "Time Tracking",
172
+ summary: "Log and track time spent on tasks and projects.",
173
+ categoryId: "project-management",
174
+ href: "/project-management/time-tracking",
175
+ keywords: ["hours", "utilization", "billing", "logging"],
176
+ },
177
+ {
178
+ id: "timesheets",
179
+ name: "Timesheets",
180
+ summary: "Review and approve weekly timesheets.",
181
+ categoryId: "project-management",
182
+ href: "/project-management/timesheets",
183
+ keywords: ["timesheets", "approvals", "payroll"],
184
+ },
185
+ {
186
+ id: "resources",
187
+ name: "Resources",
188
+ summary: "Manage team members and their availability.",
189
+ categoryId: "project-management",
190
+ href: "/project-management/resources",
191
+ keywords: ["capacity", "staffing", "allocation", "team"],
192
+ },
193
+ {
194
+ id: "pm-reports",
195
+ name: "Reports",
196
+ summary: "Generate and view project reports.",
197
+ categoryId: "project-management",
198
+ href: "/project-management/reports",
199
+ keywords: ["status", "insights", "performance", "analytics"],
200
+ },
201
+
202
+ // Finance Management
203
+ {
204
+ id: "invoicing",
205
+ name: "Invoicing",
206
+ summary: "Create and manage invoices and recurring billing.",
207
+ categoryId: "finance-management",
208
+ href: "/finance-management/invoicing",
209
+ keywords: ["billing", "invoices", "accounts receivable"],
210
+ },
211
+ {
212
+ id: "payments",
213
+ name: "Payments",
214
+ summary: "Track received payments and reconcile transactions.",
215
+ categoryId: "finance-management",
216
+ href: "/finance-management/payments",
217
+ keywords: ["collections", "payments", "reconciliation"],
218
+ },
219
+ {
220
+ id: "transactions",
221
+ name: "Transactions",
222
+ summary: "Track income and expenses in one place.",
223
+ categoryId: "finance-management",
224
+ href: "/finance-management/transactions",
225
+ keywords: ["income", "expenses", "money movement"],
226
+ },
227
+ {
228
+ id: "bookkeeping",
229
+ name: "Bookkeeping",
230
+ summary: "Maintain chart of accounts and journal entries.",
231
+ categoryId: "finance-management",
232
+ href: "/finance-management/bookkeeping",
233
+ keywords: ["general ledger", "accounts", "journals"],
234
+ },
235
+ {
236
+ id: "rate-cards",
237
+ name: "Rate Cards",
238
+ summary: "Standardize pricing by role, service, or region.",
239
+ categoryId: "finance-management",
240
+ href: "/finance-management/rate-cards",
241
+ keywords: ["pricing", "rates", "billing"],
242
+ },
243
+ {
244
+ id: "fm-reports",
245
+ name: "Reports",
246
+ summary: "Generate financial reports and track budgets.",
247
+ categoryId: "finance-management",
248
+ href: "/finance-management/reports",
249
+ keywords: ["p&l", "balance sheet", "budgets", "analytics"],
250
+ },
251
+ {
252
+ id: "fm-integrations",
253
+ name: "Integrations",
254
+ summary: "Connect to QuickBooks, Xero, Stripe, and more.",
255
+ categoryId: "finance-management",
256
+ href: "/finance-management/integrations",
257
+ keywords: ["quickbooks", "xero", "stripe", "sync"],
258
+ },
259
+
260
+ // Knowledge Management
261
+ {
262
+ id: "wikis-knowledge",
263
+ name: "Wiki",
264
+ summary: "Organize internal knowledge and documentation.",
265
+ categoryId: "knowledge-management",
266
+ href: "/knowledge-management/wiki",
267
+ keywords: ["confluence", "docs", "knowledge"],
268
+ },
269
+ {
270
+ id: "knowledge-base",
271
+ name: "Knowledge Base",
272
+ summary: "Publish help articles and FAQs for teams.",
273
+ categoryId: "knowledge-management",
274
+ href: "/knowledge-management/knowledge-base",
275
+ keywords: ["faq", "help center", "support"],
276
+ },
277
+ {
278
+ id: "policies-sops",
279
+ name: "Policies & SOPs",
280
+ summary: "Store policies, procedures, and playbooks.",
281
+ categoryId: "knowledge-management",
282
+ href: "/knowledge-management/policies-sops",
283
+ keywords: ["policies", "sops", "governance"],
284
+ },
285
+
286
+ // People & HR
287
+ {
288
+ id: "people",
289
+ name: "People",
290
+ summary: "Employee directory and lifecycle management.",
291
+ categoryId: "people-and-hr",
292
+ href: "/people-and-hr/people",
293
+ keywords: ["employees", "directory", "onboarding", "offboarding"],
294
+ },
295
+ {
296
+ id: "recruiting",
297
+ name: "Recruiting",
298
+ summary: "Job postings, candidate pipeline, and hiring workflow.",
299
+ categoryId: "people-and-hr",
300
+ href: "/people-and-hr/recruiting",
301
+ keywords: ["candidates", "jobs", "hiring", "ats"],
302
+ },
303
+ {
304
+ id: "payroll",
305
+ name: "Payroll",
306
+ summary: "Pay runs, compensation, and payslips.",
307
+ categoryId: "people-and-hr",
308
+ href: "/people-and-hr/payroll",
309
+ keywords: ["pay", "compensation", "salary"],
310
+ },
311
+ {
312
+ id: "hr-expenses",
313
+ name: "Expenses",
314
+ summary: "Employee expense tracking and reimbursements.",
315
+ categoryId: "people-and-hr",
316
+ href: "/people-and-hr/expenses",
317
+ keywords: ["reimbursements", "receipts", "expense reports"],
318
+ },
319
+ {
320
+ id: "time-off",
321
+ name: "Time Off",
322
+ summary: "PTO, leave requests, and balances.",
323
+ categoryId: "people-and-hr",
324
+ href: "/people-and-hr/time-off",
325
+ keywords: ["pto", "vacation", "leave", "attendance"],
326
+ },
327
+ {
328
+ id: "benefits",
329
+ name: "Benefits",
330
+ summary: "Health insurance, retirement, and perks administration.",
331
+ categoryId: "people-and-hr",
332
+ href: "/people-and-hr/benefits",
333
+ keywords: ["health", "insurance", "401k", "perks"],
334
+ },
335
+ {
336
+ id: "compliance",
337
+ name: "Compliance",
338
+ summary: "Tax documents and regulatory compliance.",
339
+ categoryId: "people-and-hr",
340
+ href: "/people-and-hr/compliance",
341
+ keywords: ["taxes", "w2", "1099", "audits"],
342
+ },
343
+ {
344
+ id: "hr-documents",
345
+ name: "Documents",
346
+ summary: "HR documents, offer letters, and policies.",
347
+ categoryId: "people-and-hr",
348
+ href: "/people-and-hr/documents",
349
+ keywords: ["templates", "offer letters", "policies", "handbook"],
350
+ },
351
+ {
352
+ id: "hr-reports",
353
+ name: "Reports",
354
+ summary: "HR analytics and dashboards.",
355
+ categoryId: "people-and-hr",
356
+ href: "/people-and-hr/reports",
357
+ keywords: ["headcount", "turnover", "compensation", "analytics"],
358
+ },
359
+
360
+ // Growth & Marketing
361
+ {
362
+ id: "campaigns",
363
+ name: "Campaigns",
364
+ summary: "Create and manage marketing campaigns across channels.",
365
+ categoryId: "growth-and-marketing",
366
+ href: "/growth-and-marketing/campaigns",
367
+ keywords: ["email", "social", "marketing", "outreach"],
368
+ },
369
+ {
370
+ id: "automations",
371
+ name: "Automations",
372
+ summary: "Build automated workflows triggered by user actions.",
373
+ categoryId: "growth-and-marketing",
374
+ href: "/growth-and-marketing/automations",
375
+ keywords: ["workflows", "triggers", "nurture", "drip"],
376
+ },
377
+ {
378
+ id: "sms",
379
+ name: "SMS",
380
+ summary: "Send targeted SMS messages to your audience.",
381
+ categoryId: "growth-and-marketing",
382
+ href: "/growth-and-marketing/sms",
383
+ keywords: ["text", "messaging", "mobile"],
384
+ },
385
+ {
386
+ id: "gm-forms",
387
+ name: "Forms",
388
+ summary: "Create lead capture and registration forms.",
389
+ categoryId: "growth-and-marketing",
390
+ href: "/growth-and-marketing/forms",
391
+ keywords: ["lead capture", "registration", "data collection"],
392
+ },
393
+ {
394
+ id: "polls",
395
+ name: "Polls",
396
+ summary: "Create quick polls to gather instant feedback.",
397
+ categoryId: "growth-and-marketing",
398
+ href: "/growth-and-marketing/polls",
399
+ keywords: ["voting", "feedback", "questions"],
400
+ },
401
+ {
402
+ id: "surveys",
403
+ name: "Surveys",
404
+ summary: "Create multi-question surveys for detailed feedback.",
405
+ categoryId: "growth-and-marketing",
406
+ href: "/growth-and-marketing/surveys",
407
+ keywords: ["questionnaires", "research", "feedback"],
408
+ },
409
+ {
410
+ id: "subscribe-forms",
411
+ name: "Subscribe Forms",
412
+ summary: "Create embeddable email subscribe forms for websites.",
413
+ categoryId: "growth-and-marketing",
414
+ href: "/growth-and-marketing/subscribe-forms",
415
+ keywords: ["opt-in", "newsletter", "email list", "embed"],
416
+ },
417
+ {
418
+ id: "gm-contacts",
419
+ name: "Contacts",
420
+ summary: "Manage your marketing contact list and segments.",
421
+ categoryId: "growth-and-marketing",
422
+ href: "/growth-and-marketing/contacts",
423
+ keywords: ["subscribers", "audience", "segments", "lists"],
424
+ },
425
+ {
426
+ id: "gm-analytics",
427
+ name: "Analytics",
428
+ summary: "Track and analyze marketing performance.",
429
+ categoryId: "growth-and-marketing",
430
+ href: "/growth-and-marketing/analytics",
431
+ keywords: ["metrics", "reports", "roi", "performance"],
432
+ },
433
+ {
434
+ id: "content",
435
+ name: "Content",
436
+ summary: "Manage marketing assets and content library.",
437
+ categoryId: "growth-and-marketing",
438
+ href: "/growth-and-marketing/content",
439
+ keywords: ["assets", "media", "storage", "files"],
440
+ },
441
+ {
442
+ id: "gm-integrations",
443
+ name: "Integrations",
444
+ summary: "Connect marketing tools and third-party platforms.",
445
+ categoryId: "growth-and-marketing",
446
+ href: "/growth-and-marketing/integrations",
447
+ keywords: ["mailchimp", "hubspot", "salesforce", "api"],
448
+ },
449
+
450
+ // Operations & Automation
451
+ {
452
+ id: "workflow-automation",
453
+ name: "Workflow Automation",
454
+ summary: "Automate workflows across teams and tools.",
455
+ categoryId: "operations-and-automation",
456
+ href: "/operations-and-automation/workflow-automation",
457
+ keywords: ["zapier", "n8n", "automation"],
458
+ },
459
+ {
460
+ id: "integrations",
461
+ name: "Integrations",
462
+ summary: "Connect services to share data seamlessly.",
463
+ categoryId: "operations-and-automation",
464
+ href: "/operations-and-automation/integrations",
465
+ keywords: ["apis", "connectors", "workato"],
466
+ },
467
+
468
+ // Analytics
469
+ {
470
+ id: "dashboards-reporting",
471
+ name: "Dashboards & Reporting",
472
+ summary: "Visualize metrics and track KPIs.",
473
+ categoryId: "analytics",
474
+ href: "/analytics/dashboards-reporting",
475
+ keywords: ["dashboards", "analytics", "insights"],
476
+ },
477
+
478
+ // Administration
479
+ {
480
+ id: "billing",
481
+ name: "Billing",
482
+ summary: "View and pay bills, analyze and govern your spending, and optimize your costs.",
483
+ categoryId: "administration",
484
+ href: "/billing",
485
+ keywords: ["cost", "spending", "invoices", "payments", "budgets"],
486
+ },
487
+ {
488
+ id: "iam",
489
+ name: "IAM",
490
+ summary: "Manage access.",
491
+ categoryId: "administration",
492
+ href: "/iam",
493
+ keywords: ["identity", "access", "permissions", "roles", "users"],
494
+ },
495
+ ],
496
+ };
package/src/source.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { ServiceCatalog } from "./types";
2
+
3
+ export type ServiceCatalogSource = {
4
+ getCatalog: () => Promise<ServiceCatalog>;
5
+ getCatalogSync?: () => ServiceCatalog | null;
6
+ };
7
+
8
+ export function createMockCatalogSource(catalog: ServiceCatalog): ServiceCatalogSource {
9
+ return {
10
+ getCatalog: async () => catalog,
11
+ getCatalogSync: () => catalog,
12
+ };
13
+ }
14
+
15
+ export function createRemoteCatalogSource(
16
+ fetchCatalog: () => Promise<ServiceCatalog>,
17
+ ): ServiceCatalogSource {
18
+ return {
19
+ getCatalog: fetchCatalog,
20
+ };
21
+ }
package/src/types.ts ADDED
@@ -0,0 +1,27 @@
1
+ export type ServiceCategory = {
2
+ id: string;
3
+ label: string;
4
+ description?: string;
5
+ order: number;
6
+ };
7
+
8
+ export type ServiceItem = {
9
+ id: string;
10
+ name: string;
11
+ summary: string;
12
+ categoryId: string;
13
+ href: string;
14
+ keywords?: string[];
15
+ tags?: string[];
16
+ };
17
+
18
+ export type ServiceCatalog = {
19
+ categories: ServiceCategory[];
20
+ services: ServiceItem[];
21
+ updatedAt: string;
22
+ };
23
+
24
+ export type ServiceGroup = {
25
+ category: ServiceCategory;
26
+ services: ServiceItem[];
27
+ };
@@ -0,0 +1,6 @@
1
+ import { searchServices } from "./dist/adapter.js";
2
+ import { mockServiceCatalog } from "./dist/mock/catalog.js";
3
+
4
+ const results = searchServices(mockServiceCatalog, "project");
5
+ console.log(`Search "project" returned ${results.length} results:`);
6
+ results.forEach(r => console.log(` - ${r.name} (${r.categoryId})`));
package/test-search.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { searchServices } from "./src/adapter.ts";
2
+ import { mockServiceCatalog } from "./src/mock/catalog.ts";
3
+
4
+ const results = searchServices(mockServiceCatalog, "project");
5
+ console.log(`Search "project" returned ${results.length} results:`);
6
+ results.forEach(r => console.log(` - ${r.name} (${r.categoryId})`));