@adityanair98/api-oracle 0.5.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/LICENSE +21 -0
- package/README.md +216 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +74 -0
- package/dist/dashboard/public/app.js +1004 -0
- package/dist/dashboard/public/index.html +142 -0
- package/dist/dashboard/public/public/app.js +1004 -0
- package/dist/dashboard/public/public/index.html +142 -0
- package/dist/dashboard/public/public/styles.css +1464 -0
- package/dist/dashboard/public/styles.css +1464 -0
- package/dist/dashboard/routes/api.d.ts +7 -0
- package/dist/dashboard/routes/api.js +245 -0
- package/dist/dashboard/server.d.ts +9 -0
- package/dist/dashboard/server.js +45 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +23 -0
- package/dist/knowledge/db.d.ts +22 -0
- package/dist/knowledge/db.js +182 -0
- package/dist/knowledge/schema.d.ts +275 -0
- package/dist/knowledge/schema.js +135 -0
- package/dist/knowledge/scorer.d.ts +63 -0
- package/dist/knowledge/scorer.js +314 -0
- package/dist/knowledge/search.d.ts +37 -0
- package/dist/knowledge/search.js +111 -0
- package/dist/knowledge/synonyms.d.ts +36 -0
- package/dist/knowledge/synonyms.js +523 -0
- package/dist/knowledge/tfidf.d.ts +42 -0
- package/dist/knowledge/tfidf.js +138 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +40 -0
- package/dist/tools/check-freshness.d.ts +9 -0
- package/dist/tools/check-freshness.js +95 -0
- package/dist/tools/compare-apis.d.ts +8 -0
- package/dist/tools/compare-apis.js +149 -0
- package/dist/tools/find-api.d.ts +9 -0
- package/dist/tools/find-api.js +120 -0
- package/dist/tools/get-setup-guide.d.ts +8 -0
- package/dist/tools/get-setup-guide.js +127 -0
- package/dist/updater/linter.d.ts +31 -0
- package/dist/updater/linter.js +219 -0
- package/dist/updater/report.d.ts +29 -0
- package/dist/updater/report.js +96 -0
- package/dist/updater/staleness.d.ts +39 -0
- package/dist/updater/staleness.js +66 -0
- package/dist/updater/version-tracker.d.ts +28 -0
- package/dist/updater/version-tracker.js +50 -0
- package/dist/utils/config.d.ts +11 -0
- package/dist/utils/config.js +13 -0
- package/dist/utils/logger.d.ts +20 -0
- package/dist/utils/logger.js +32 -0
- package/package.json +56 -0
- package/src/entries/ai/anthropic.json +95 -0
- package/src/entries/ai/eleven-labs.json +90 -0
- package/src/entries/ai/openai.json +95 -0
- package/src/entries/ai/replicate.json +87 -0
- package/src/entries/ai/resemble-ai.json +88 -0
- package/src/entries/ai/stability-ai.json +89 -0
- package/src/entries/analytics/posthog.json +88 -0
- package/src/entries/analytics/sentry.json +84 -0
- package/src/entries/auth/auth0.json +90 -0
- package/src/entries/auth/clerk.json +95 -0
- package/src/entries/cms/contentful.json +92 -0
- package/src/entries/cms/sanity.json +92 -0
- package/src/entries/cms/strapi.json +93 -0
- package/src/entries/commerce/medusa.json +91 -0
- package/src/entries/commerce/shopify-api.json +91 -0
- package/src/entries/communication/sendbird.json +85 -0
- package/src/entries/communication/stream-chat.json +94 -0
- package/src/entries/database/firebase.json +88 -0
- package/src/entries/database/neon.json +94 -0
- package/src/entries/database/planetscale.json +95 -0
- package/src/entries/database/supabase.json +94 -0
- package/src/entries/database/upstash.json +94 -0
- package/src/entries/devops/fly-io.json +90 -0
- package/src/entries/devops/netlify.json +90 -0
- package/src/entries/devops/railway.json +90 -0
- package/src/entries/devops/vercel.json +90 -0
- package/src/entries/email/mailgun.json +91 -0
- package/src/entries/email/postmark.json +91 -0
- package/src/entries/email/resend.json +89 -0
- package/src/entries/email/sendgrid.json +90 -0
- package/src/entries/forms/formspark.json +85 -0
- package/src/entries/forms/typeform.json +98 -0
- package/src/entries/infrastructure/aws-s3.json +104 -0
- package/src/entries/infrastructure/cloudflare-r2.json +92 -0
- package/src/entries/infrastructure/cloudflare-workers.json +92 -0
- package/src/entries/infrastructure/digital-ocean-spaces.json +87 -0
- package/src/entries/integration/nango.json +90 -0
- package/src/entries/integration/zapier.json +92 -0
- package/src/entries/maps/google-maps.json +89 -0
- package/src/entries/maps/mapbox.json +87 -0
- package/src/entries/media/deepgram.json +84 -0
- package/src/entries/media/imgix.json +84 -0
- package/src/entries/media/mux.json +94 -0
- package/src/entries/messaging/ably.json +94 -0
- package/src/entries/messaging/pusher.json +94 -0
- package/src/entries/messaging/twilio.json +94 -0
- package/src/entries/messaging/vonage.json +89 -0
- package/src/entries/notifications/knock.json +84 -0
- package/src/entries/notifications/novu.json +84 -0
- package/src/entries/notifications/onesignal.json +84 -0
- package/src/entries/payments/lemonsqueezy.json +91 -0
- package/src/entries/payments/paddle.json +90 -0
- package/src/entries/payments/paypal.json +91 -0
- package/src/entries/payments/razorpay.json +85 -0
- package/src/entries/payments/square.json +91 -0
- package/src/entries/payments/stripe.json +96 -0
- package/src/entries/scheduling/cal-com.json +90 -0
- package/src/entries/scheduling/calendly.json +90 -0
- package/src/entries/search/algolia.json +96 -0
- package/src/entries/security/arcjet.json +89 -0
- package/src/entries/security/snyk.json +90 -0
- package/src/entries/storage/cloudinary.json +93 -0
- package/src/entries/storage/uploadthing.json +90 -0
- package/src/entries/testing/browserstack.json +86 -0
- package/src/entries/testing/checkly.json +89 -0
- package/src/entries/workflow/inngest.json +88 -0
- package/src/entries/workflow/temporal.json +90 -0
- package/src/entries/workflow/trigger-dev.json +89 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synonym expansion, phrase detection, and category detection for query processing.
|
|
3
|
+
*
|
|
4
|
+
* Exports:
|
|
5
|
+
* SYNONYM_GROUPS — raw synonym groups for testing
|
|
6
|
+
* expandQuery — expands a query with synonyms
|
|
7
|
+
* detectPhrases — extracts modifier intent from a query
|
|
8
|
+
* detectCategory — maps a query to a known category with confidence
|
|
9
|
+
*/
|
|
10
|
+
// ─── Synonym Groups ───────────────────────────────────────────────────────────
|
|
11
|
+
// Each group is a list of terms that share meaning in the developer context.
|
|
12
|
+
// If any term from a group appears in the query, all terms are added.
|
|
13
|
+
export const SYNONYM_GROUPS = [
|
|
14
|
+
// Auth / Identity
|
|
15
|
+
[
|
|
16
|
+
"auth", "authentication", "authenticate", "login", "signin", "signup",
|
|
17
|
+
"register", "sso", "oauth", "saml", "identity", "credential", "session",
|
|
18
|
+
"jwt", "token", "passwordless", "social", "provider",
|
|
19
|
+
"log in", "sign in", "sign up", "sign on", "log on",
|
|
20
|
+
],
|
|
21
|
+
// Payments / Billing
|
|
22
|
+
[
|
|
23
|
+
"payment", "pay", "billing", "checkout", "charge", "subscription",
|
|
24
|
+
"invoice", "revenue", "commerce", "merchant", "card", "stripe",
|
|
25
|
+
"transaction", "pricing", "monetize", "monetization",
|
|
26
|
+
],
|
|
27
|
+
// Email
|
|
28
|
+
[
|
|
29
|
+
"email", "mail", "smtp", "newsletter", "inbox", "transactional",
|
|
30
|
+
"mailer", "sendgrid", "resend", "postmark", "bounce", "delivery",
|
|
31
|
+
],
|
|
32
|
+
// Database / Backend storage
|
|
33
|
+
[
|
|
34
|
+
"database", "db", "sql", "nosql", "data", "record", "table",
|
|
35
|
+
"query", "orm", "schema", "postgres", "mysql", "backend", "persist",
|
|
36
|
+
"persistence", "datastore",
|
|
37
|
+
],
|
|
38
|
+
// Real-time / WebSockets
|
|
39
|
+
[
|
|
40
|
+
"realtime", "real-time", "real time", "websocket", "socket",
|
|
41
|
+
"live", "streaming", "push", "broadcast", "pub-sub", "pubsub",
|
|
42
|
+
"presence", "channel",
|
|
43
|
+
],
|
|
44
|
+
// SMS / Voice / Messaging
|
|
45
|
+
[
|
|
46
|
+
"sms", "text", "message", "messaging", "twilio", "vonage",
|
|
47
|
+
"phone", "call", "voice", "telephony", "otp", "verify", "verification",
|
|
48
|
+
],
|
|
49
|
+
// Chat / Conversation
|
|
50
|
+
[
|
|
51
|
+
"chat", "conversation", "messaging", "inbox", "channel",
|
|
52
|
+
"thread", "room", "dm", "direct message",
|
|
53
|
+
],
|
|
54
|
+
// File / Image / Media Storage
|
|
55
|
+
[
|
|
56
|
+
"upload", "file", "image", "photo", "video", "media", "storage",
|
|
57
|
+
"cdn", "asset", "bucket", "s3", "object", "blob", "picture",
|
|
58
|
+
"avatar", "attachment",
|
|
59
|
+
],
|
|
60
|
+
// Search / Discovery
|
|
61
|
+
[
|
|
62
|
+
"search", "query", "index", "algolia", "full-text", "fulltext",
|
|
63
|
+
"autocomplete", "facet", "filter", "typeahead", "elasticsearch",
|
|
64
|
+
"discovery",
|
|
65
|
+
],
|
|
66
|
+
// AI / ML
|
|
67
|
+
[
|
|
68
|
+
"ai", "ml", "llm", "gpt", "claude", "openai", "anthropic",
|
|
69
|
+
"model", "completion", "embedding", "chatbot", "generate", "inference",
|
|
70
|
+
"generative", "machine learning", "artificial intelligence",
|
|
71
|
+
"neural", "natural language", "nlp",
|
|
72
|
+
],
|
|
73
|
+
// Error Tracking / Monitoring / Observability
|
|
74
|
+
[
|
|
75
|
+
"error", "crash", "crashes", "crashed", "crashing", "exception", "bug",
|
|
76
|
+
"monitoring", "observability", "error tracking", "crash report",
|
|
77
|
+
"sentry", "alerting", "incident", "apm", "logging", "trace",
|
|
78
|
+
"diagnose", "debug", "production issue", "alert",
|
|
79
|
+
],
|
|
80
|
+
// Analytics / Product Analytics
|
|
81
|
+
[
|
|
82
|
+
"analytics", "tracking", "event", "funnel", "session", "user behavior",
|
|
83
|
+
"product analytics", "posthog", "mixpanel", "amplitude", "heatmap",
|
|
84
|
+
"conversion", "retention", "cohort", "metric",
|
|
85
|
+
],
|
|
86
|
+
// Push Notifications
|
|
87
|
+
[
|
|
88
|
+
"notification", "push", "alert", "bell", "notify", "in-app",
|
|
89
|
+
"onesignal", "knock", "novu", "apns", "fcm", "mobile notification",
|
|
90
|
+
],
|
|
91
|
+
// Maps / Location / Geocoding
|
|
92
|
+
[
|
|
93
|
+
"map", "maps", "geo", "geolocation", "location", "address",
|
|
94
|
+
"geocod", "navigate", "navigation", "direction", "coordinate",
|
|
95
|
+
"latitude", "longitude", "place", "places",
|
|
96
|
+
],
|
|
97
|
+
// Video / Audio / Media Processing
|
|
98
|
+
[
|
|
99
|
+
"video", "stream", "streaming", "transcode", "encode", "playback",
|
|
100
|
+
"mux", "player", "hls", "dash", "vod", "audio", "speech",
|
|
101
|
+
"transcribe", "transcription", "voice synthesis",
|
|
102
|
+
],
|
|
103
|
+
// Image Processing / CDN
|
|
104
|
+
[
|
|
105
|
+
"image", "resize", "crop", "optimize", "transform", "cdn", "imgix",
|
|
106
|
+
"thumbnail", "watermark", "compress",
|
|
107
|
+
],
|
|
108
|
+
// DevOps / Deployment / Hosting
|
|
109
|
+
[
|
|
110
|
+
"deploy", "deployment", "host", "hosting", "server", "cloud",
|
|
111
|
+
"devops", "ci", "cd", "pipeline", "container", "docker", "kubernetes",
|
|
112
|
+
"vercel", "netlify", "railway", "heroku", "vps", "platform",
|
|
113
|
+
"ship", "launch", "publish", "paas", "infrastructure",
|
|
114
|
+
],
|
|
115
|
+
// Edge / Serverless Compute
|
|
116
|
+
[
|
|
117
|
+
"edge", "serverless", "function", "lambda", "worker", "cloudflare",
|
|
118
|
+
"edge compute", "edge function", "faas", "compute",
|
|
119
|
+
],
|
|
120
|
+
// Object Storage / S3
|
|
121
|
+
[
|
|
122
|
+
"s3", "bucket", "object storage", "blob storage", "r2",
|
|
123
|
+
"file storage", "static files", "presigned", "presigned url",
|
|
124
|
+
],
|
|
125
|
+
// Commerce / eCommerce
|
|
126
|
+
[
|
|
127
|
+
"ecommerce", "e-commerce", "shop", "store", "storefront", "product",
|
|
128
|
+
"cart", "order", "checkout", "inventory", "catalog", "shopify",
|
|
129
|
+
"merchant", "sell", "digital products",
|
|
130
|
+
],
|
|
131
|
+
// CMS / Content Management
|
|
132
|
+
[
|
|
133
|
+
"cms", "content", "content management", "headless cms", "blog",
|
|
134
|
+
"article", "page", "rich text", "editor", "contentful", "sanity",
|
|
135
|
+
"strapi", "markdown", "wysiwyg", "editorial",
|
|
136
|
+
],
|
|
137
|
+
// Forms / Surveys
|
|
138
|
+
[
|
|
139
|
+
"form", "survey", "questionnaire", "typeform", "lead capture",
|
|
140
|
+
"quiz", "feedback", "response", "submission", "field", "input",
|
|
141
|
+
"contact form", "onboarding form",
|
|
142
|
+
],
|
|
143
|
+
// Scheduling / Calendar / Booking
|
|
144
|
+
[
|
|
145
|
+
"schedule", "scheduling", "calendar", "booking", "appointment",
|
|
146
|
+
"meeting", "calendly", "cal.com", "availability", "timeslot",
|
|
147
|
+
"slot", "book a call", "event scheduling",
|
|
148
|
+
],
|
|
149
|
+
// Security / Vulnerability / Protection
|
|
150
|
+
[
|
|
151
|
+
"security", "vulnerability", "cve", "sast", "dast", "scan",
|
|
152
|
+
"firewall", "waf", "rate limit", "ddos", "bot", "abuse", "snyk",
|
|
153
|
+
"arcjet", "pen test", "pentest", "owasp", "secret scanning",
|
|
154
|
+
],
|
|
155
|
+
// Background Jobs / Queues / Workflows
|
|
156
|
+
[
|
|
157
|
+
"background job", "job queue", "queue", "worker", "cron", "scheduled job",
|
|
158
|
+
"task", "async", "workflow", "trigger", "inngest", "temporal",
|
|
159
|
+
"job processing", "event-driven", "fan-out", "batch",
|
|
160
|
+
],
|
|
161
|
+
// Integration / Automation / OAuth connectors
|
|
162
|
+
[
|
|
163
|
+
"integration", "connect", "connector", "automation", "zapier",
|
|
164
|
+
"oauth", "third-party", "saas integration", "webhook",
|
|
165
|
+
"no-code", "low-code", "nango", "token refresh",
|
|
166
|
+
],
|
|
167
|
+
// Testing / QA / Monitoring
|
|
168
|
+
[
|
|
169
|
+
"test", "testing", "qa", "quality assurance", "browser test",
|
|
170
|
+
"e2e", "end-to-end", "selenium", "playwright", "puppeteer",
|
|
171
|
+
"synthetic", "uptime monitor", "checkly", "browserstack",
|
|
172
|
+
"cross-browser", "regression",
|
|
173
|
+
],
|
|
174
|
+
];
|
|
175
|
+
// Build a lookup: term → all terms in its group (for fast expansion)
|
|
176
|
+
const TERM_TO_GROUP = new Map();
|
|
177
|
+
for (const group of SYNONYM_GROUPS) {
|
|
178
|
+
for (const term of group) {
|
|
179
|
+
TERM_TO_GROUP.set(term.toLowerCase(), group);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// ─── Query Expansion ──────────────────────────────────────────────────────────
|
|
183
|
+
/**
|
|
184
|
+
* Build a regex to match a synonym term in a query.
|
|
185
|
+
* - Multi-word phrases ("log in"): exact phrase with word boundaries
|
|
186
|
+
* - Single words: left word boundary only (so "crash" matches "crashes")
|
|
187
|
+
*/
|
|
188
|
+
function buildTermRegex(term) {
|
|
189
|
+
const escaped = term.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
190
|
+
if (term.includes(" ")) {
|
|
191
|
+
// Multi-word: exact phrase match (word boundary on left, space/end on right)
|
|
192
|
+
return new RegExp(`(?<![a-z])${escaped}(?![a-z])`, "i");
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// Single word: match at word start; allow suffixes (crash → crashes)
|
|
196
|
+
return new RegExp(`\\b${escaped}`, "i");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Sort terms longest-first so multi-word phrases are checked before their parts
|
|
200
|
+
const SORTED_TERM_ENTRIES = [...TERM_TO_GROUP.entries()].sort(([a], [b]) => b.length - a.length);
|
|
201
|
+
/**
|
|
202
|
+
* Expand a query by adding synonyms for any terms it contains.
|
|
203
|
+
* Example: "log in with Google" → "log in with Google auth authentication
|
|
204
|
+
* login signin signup register sso oauth identity..."
|
|
205
|
+
*/
|
|
206
|
+
export function expandQuery(query) {
|
|
207
|
+
if (!query)
|
|
208
|
+
return query;
|
|
209
|
+
const lowerQuery = query.toLowerCase();
|
|
210
|
+
const addedTerms = new Set();
|
|
211
|
+
// Track which positions have been matched to avoid double-matching
|
|
212
|
+
const matchedGroups = new Set();
|
|
213
|
+
for (const [term, group] of SORTED_TERM_ENTRIES) {
|
|
214
|
+
if (matchedGroups.has(group))
|
|
215
|
+
continue; // Already expanding this group
|
|
216
|
+
const regex = buildTermRegex(term);
|
|
217
|
+
if (regex.test(lowerQuery)) {
|
|
218
|
+
matchedGroups.add(group);
|
|
219
|
+
for (const synonym of group) {
|
|
220
|
+
const synLower = synonym.toLowerCase();
|
|
221
|
+
// Only add if not already present (substring check)
|
|
222
|
+
if (!lowerQuery.includes(synLower)) {
|
|
223
|
+
addedTerms.add(synonym);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (addedTerms.size === 0)
|
|
229
|
+
return query;
|
|
230
|
+
return `${query} ${[...addedTerms].join(" ")}`;
|
|
231
|
+
}
|
|
232
|
+
const FREE_PATTERNS = [
|
|
233
|
+
/\bfree\b/i, /\bcheap/i, /\bno cost\b/i, /\bbudget\b/i, /\baffordable\b/i,
|
|
234
|
+
];
|
|
235
|
+
const OPEN_SOURCE_PATTERNS = [
|
|
236
|
+
/\bopen.?source\b/i, /\bself.?host/i, /\bself host/i, /\bown server\b/i,
|
|
237
|
+
];
|
|
238
|
+
const RELIABLE_PATTERNS = [
|
|
239
|
+
/\breliable\b/i, /\benterprise\b/i, /\bproduction.grade\b/i,
|
|
240
|
+
/\bmost reliable\b/i, /\bhigh.?availab/i, /\buptime\b/i,
|
|
241
|
+
];
|
|
242
|
+
const SERVERLESS_PATTERNS = [
|
|
243
|
+
/\bserverless\b/i, /\bedge\b/i, /\bvercel\b/i, /\bnextjs?\b/i,
|
|
244
|
+
/\bnext\.js\b/i, /\bcloudflare\b/i,
|
|
245
|
+
];
|
|
246
|
+
// Known API names to slugs for reference extraction
|
|
247
|
+
const API_NAME_TO_SLUG = {
|
|
248
|
+
// Original entries
|
|
249
|
+
firebase: "firebase", supabase: "supabase", stripe: "stripe",
|
|
250
|
+
sendgrid: "sendgrid", twilio: "twilio", algolia: "algolia",
|
|
251
|
+
auth0: "auth0", clerk: "clerk", cloudinary: "cloudinary",
|
|
252
|
+
openai: "openai", anthropic: "anthropic", sentry: "sentry",
|
|
253
|
+
pusher: "pusher", mapbox: "mapbox", "google maps": "google-maps",
|
|
254
|
+
// Session 4 additions
|
|
255
|
+
vercel: "vercel", netlify: "netlify", railway: "railway",
|
|
256
|
+
"fly.io": "fly-io", "aws s3": "aws-s3", "cloudflare r2": "cloudflare-r2",
|
|
257
|
+
"cloudflare workers": "cloudflare-workers", paddle: "paddle",
|
|
258
|
+
lemonsqueezy: "lemonsqueezy", paypal: "paypal", shopify: "shopify-api",
|
|
259
|
+
medusa: "medusa", contentful: "contentful", sanity: "sanity",
|
|
260
|
+
strapi: "strapi", typeform: "typeform", calendly: "calendly",
|
|
261
|
+
"cal.com": "cal-com", browserstack: "browserstack", checkly: "checkly",
|
|
262
|
+
snyk: "snyk", arcjet: "arcjet", "stability ai": "stability-ai",
|
|
263
|
+
elevenlabs: "eleven-labs", "eleven labs": "eleven-labs",
|
|
264
|
+
mailgun: "mailgun", "trigger.dev": "trigger-dev", inngest: "inngest",
|
|
265
|
+
temporal: "temporal", zapier: "zapier", nango: "nango",
|
|
266
|
+
};
|
|
267
|
+
/** Extract modifier intent from a query */
|
|
268
|
+
export function detectPhrases(query) {
|
|
269
|
+
const preferFree = FREE_PATTERNS.some((p) => p.test(query));
|
|
270
|
+
const preferOpenSource = OPEN_SOURCE_PATTERNS.some((p) => p.test(query));
|
|
271
|
+
const preferReliable = RELIABLE_PATTERNS.some((p) => p.test(query));
|
|
272
|
+
const preferServerless = SERVERLESS_PATTERNS.some((p) => p.test(query));
|
|
273
|
+
// Detect "like X but..." reference pattern
|
|
274
|
+
let referenceSlug = null;
|
|
275
|
+
const likePattern = /like\s+([a-z][a-z0-9\s-]*?)(?:\s+but|\s+except|\s+without|,|\.|$)/i;
|
|
276
|
+
const match = likePattern.exec(query);
|
|
277
|
+
if (match) {
|
|
278
|
+
const refName = match[1].trim().toLowerCase();
|
|
279
|
+
referenceSlug = API_NAME_TO_SLUG[refName] ?? null;
|
|
280
|
+
}
|
|
281
|
+
return { preferFree, preferOpenSource, preferReliable, preferServerless, referenceSlug };
|
|
282
|
+
}
|
|
283
|
+
const CATEGORY_DETECTORS = [
|
|
284
|
+
{
|
|
285
|
+
category: "email",
|
|
286
|
+
patterns: [
|
|
287
|
+
/\bemail\b/i, /\bmail\b/i, /\bsmtp\b/i, /\bnewsletter\b/i,
|
|
288
|
+
/\binbox\b/i, /\btransactional\b/i, /\bsend.*mail\b/i,
|
|
289
|
+
],
|
|
290
|
+
weight: 0.4,
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
category: "payments",
|
|
294
|
+
patterns: [
|
|
295
|
+
/\bpay(ment)?s?\b/i, /\bbilling\b/i, /\bcheckout\b/i,
|
|
296
|
+
/\bcharge\b/i, /\bsubscription\b/i, /\binvoice\b/i,
|
|
297
|
+
/\bsaas.*billing\b/i, /\baccept.*payment\b/i, /\bcredit card\b/i,
|
|
298
|
+
],
|
|
299
|
+
weight: 0.4,
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
category: "auth",
|
|
303
|
+
patterns: [
|
|
304
|
+
/\bauth(entication)?\b/i, /\blogin\b/i, /\bsign.?in\b/i,
|
|
305
|
+
/\bsign.?up\b/i, /\boauth\b/i, /\bsso\b/i, /\bidentity\b/i,
|
|
306
|
+
/\blog.*in\b/i, /\buser.*account\b/i, /\bcredential\b/i,
|
|
307
|
+
],
|
|
308
|
+
weight: 0.4,
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
category: "database",
|
|
312
|
+
patterns: [
|
|
313
|
+
/\bdatabase\b/i, /\b(postgres|mysql|sqlite|mongodb)\b/i,
|
|
314
|
+
/\bdata store\b/i, /\bsql\b/i, /\bbackend\b/i,
|
|
315
|
+
/\bstore.*data\b/i, /\bpersist\b/i,
|
|
316
|
+
],
|
|
317
|
+
weight: 0.35,
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
category: "storage",
|
|
321
|
+
patterns: [
|
|
322
|
+
/\bupload\b/i, /\bfile.*upload\b/i, /\bimage.*upload\b/i,
|
|
323
|
+
/\bstore.*file\b/i, /\bcdn\b/i, /\bmedia.*storage\b/i,
|
|
324
|
+
],
|
|
325
|
+
weight: 0.4,
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
category: "ai",
|
|
329
|
+
patterns: [
|
|
330
|
+
/\bai\b/i, /\bllm\b/i, /\bgpt\b/i, /\bchatbot\b/i,
|
|
331
|
+
/\bml\b/i, /\bmachine learning\b/i, /\bartificial intelligence\b/i,
|
|
332
|
+
/\bgenerat(e|ive)\b/i, /\bcompletion\b/i, /\bembedding\b/i,
|
|
333
|
+
],
|
|
334
|
+
weight: 0.4,
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
category: "analytics",
|
|
338
|
+
patterns: [
|
|
339
|
+
/\banalytics\b/i, /\bmonitoring\b/i, /\berror track/i,
|
|
340
|
+
/\bcrash(es|ing)?\b/i, /\bexception\b/i, /\buser.*behav/i,
|
|
341
|
+
/\bproduct.*analytics\b/i, /\btrack.*user\b/i, /\bhow user/i,
|
|
342
|
+
/\bobservabilit/i, /\bsentry\b/i, /\bposthog\b/i,
|
|
343
|
+
],
|
|
344
|
+
weight: 0.35,
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
category: "messaging",
|
|
348
|
+
patterns: [
|
|
349
|
+
/\bsms\b/i, /\btext message\b/i, /\bwhatsapp\b/i, /\bvoice call\b/i,
|
|
350
|
+
/\bphone.*notif/i, /\btwilio\b/i,
|
|
351
|
+
],
|
|
352
|
+
weight: 0.4,
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
category: "notifications",
|
|
356
|
+
patterns: [
|
|
357
|
+
/\bpush.*notif/i, /\bnotif(y|ication)/i, /\bin.?app.*notif/i,
|
|
358
|
+
/\balert.*user\b/i, /\bemail.*notif/i,
|
|
359
|
+
],
|
|
360
|
+
weight: 0.35,
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
category: "communication",
|
|
364
|
+
patterns: [
|
|
365
|
+
/\bchat\b/i, /\bmessag(e|ing)\b/i, /\bconversation\b/i,
|
|
366
|
+
/\breal.?time.*chat\b/i, /\bin.?app.*chat\b/i,
|
|
367
|
+
],
|
|
368
|
+
weight: 0.35,
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
category: "maps",
|
|
372
|
+
patterns: [
|
|
373
|
+
/\bmap(s)?\b/i, /\bgeocode\b/i, /\blocation\b/i,
|
|
374
|
+
/\bnavigation\b/i, /\baddress.*look/i, /\bnearby\b/i,
|
|
375
|
+
],
|
|
376
|
+
weight: 0.4,
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
category: "media",
|
|
380
|
+
patterns: [
|
|
381
|
+
/\bvideo.*stream/i, /\bvideo.*host/i, /\baudio\b/i,
|
|
382
|
+
/\btranscribe\b/i, /\bspeech.?to.?text\b/i, /\bvideo.*encod/i,
|
|
383
|
+
/\bmux\b/i, /\bdeepgram\b/i,
|
|
384
|
+
],
|
|
385
|
+
weight: 0.4,
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
category: "search",
|
|
389
|
+
patterns: [
|
|
390
|
+
/\bsearch\b/i, /\bfull.?text\b/i, /\bautocomplete\b/i,
|
|
391
|
+
/\bsearch engine\b/i, /\balgolia\b/i, /\bindex.*search\b/i,
|
|
392
|
+
],
|
|
393
|
+
weight: 0.4,
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
category: "devops",
|
|
397
|
+
patterns: [
|
|
398
|
+
/\bdeploy(ment)?\b/i, /\bhost(ing)?\b/i, /\bvps\b/i,
|
|
399
|
+
/\bpaas\b/i, /\bcontainer\b/i, /\bdocker\b/i,
|
|
400
|
+
/\bci\/cd\b/i, /\bpipeline\b/i, /\bvercel\b/i, /\bnetlify\b/i,
|
|
401
|
+
/\brailway\b/i, /\bfly\.io\b/i, /\bheroku\b/i,
|
|
402
|
+
],
|
|
403
|
+
weight: 0.4,
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
category: "infrastructure",
|
|
407
|
+
patterns: [
|
|
408
|
+
/\bobject storage\b/i, /\bfile storage\b/i, /\bs3\b/i,
|
|
409
|
+
/\bbucket\b/i, /\bedge compute\b/i, /\bcloudflare workers\b/i,
|
|
410
|
+
/\bcdn\b/i, /\bblob\b/i, /\bpresigned url\b/i,
|
|
411
|
+
],
|
|
412
|
+
weight: 0.4,
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
category: "commerce",
|
|
416
|
+
patterns: [
|
|
417
|
+
/\becommerce\b/i, /\be-commerce\b/i, /\bheadless.*shop\b/i,
|
|
418
|
+
/\bstorefront\b/i, /\bproduct catalog\b/i, /\bcart\b/i,
|
|
419
|
+
/\bdigital.*product\b/i, /\bsell.*online\b/i, /\bshopify\b/i,
|
|
420
|
+
],
|
|
421
|
+
weight: 0.4,
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
category: "cms",
|
|
425
|
+
patterns: [
|
|
426
|
+
/\bcms\b/i, /\bcontent management\b/i, /\bheadless cms\b/i,
|
|
427
|
+
/\bblog\b/i, /\beditorial\b/i, /\brich text\b/i,
|
|
428
|
+
/\bsanity\b/i, /\bcontentful\b/i, /\bstrapi\b/i,
|
|
429
|
+
],
|
|
430
|
+
weight: 0.4,
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
category: "forms",
|
|
434
|
+
patterns: [
|
|
435
|
+
/\bform\b/i, /\bsurvey\b/i, /\bquestionnaire\b/i,
|
|
436
|
+
/\blead capture\b/i, /\bfeedback form\b/i, /\btypeform\b/i,
|
|
437
|
+
/\bform submission\b/i, /\bcontact form\b/i,
|
|
438
|
+
],
|
|
439
|
+
weight: 0.4,
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
category: "scheduling",
|
|
443
|
+
patterns: [
|
|
444
|
+
/\bschedul(e|ing)\b/i, /\bbooking\b/i, /\bappointment\b/i,
|
|
445
|
+
/\bcalendar\b/i, /\bmeeting.*book\b/i, /\bcalendly\b/i,
|
|
446
|
+
/\bbook a call\b/i, /\btime.*slot\b/i, /\bavailability\b/i,
|
|
447
|
+
],
|
|
448
|
+
weight: 0.4,
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
category: "security",
|
|
452
|
+
patterns: [
|
|
453
|
+
/\bsecurity\b/i, /\bvulnerabilit/i, /\bwaf\b/i, /\bfirewall\b/i,
|
|
454
|
+
/\brate.?limit\b/i, /\bddos\b/i, /\bbot protection\b/i,
|
|
455
|
+
/\bsast\b/i, /\bsnyk\b/i, /\barcjet\b/i, /\bscan.*dep/i,
|
|
456
|
+
],
|
|
457
|
+
weight: 0.4,
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
category: "workflow",
|
|
461
|
+
patterns: [
|
|
462
|
+
/\bbackground job\b/i, /\bjob queue\b/i, /\bqueue\b/i,
|
|
463
|
+
/\bcron\b/i, /\bscheduled task\b/i, /\bworkflow engine\b/i,
|
|
464
|
+
/\btrigger\.dev\b/i, /\binngest\b/i, /\btemporal\b/i,
|
|
465
|
+
/\bfan.?out\b/i, /\bworker.*task\b/i, /\basync.*process\b/i,
|
|
466
|
+
],
|
|
467
|
+
weight: 0.4,
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
category: "integration",
|
|
471
|
+
patterns: [
|
|
472
|
+
/\bintegration\b/i, /\bno.?code.*automat/i, /\bautomat.*no.?code/i,
|
|
473
|
+
/\bzapier\b/i, /\bnango\b/i, /\boauth.*connect\b/i,
|
|
474
|
+
/\bsaas.*connect\b/i, /\bconnect.*third.?party\b/i,
|
|
475
|
+
/\bwebhook.*forward\b/i, /\bconnect.*apps?\b/i,
|
|
476
|
+
],
|
|
477
|
+
weight: 0.4,
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
category: "testing",
|
|
481
|
+
patterns: [
|
|
482
|
+
/\bcross.?browser.*test\b/i, /\be2e.*test\b/i, /\bend.?to.?end.*test\b/i,
|
|
483
|
+
/\bqa\b/i, /\bsynth(etic)?\b/i, /\buptime.*monitor\b/i,
|
|
484
|
+
/\bbrowserstack\b/i, /\bcheckly\b/i, /\bplaywright\b/i,
|
|
485
|
+
],
|
|
486
|
+
weight: 0.4,
|
|
487
|
+
},
|
|
488
|
+
];
|
|
489
|
+
/** Detect the most likely category for a query, with a confidence score */
|
|
490
|
+
export function detectCategory(query) {
|
|
491
|
+
const scores = new Map();
|
|
492
|
+
for (const detector of CATEGORY_DETECTORS) {
|
|
493
|
+
let categoryScore = 0;
|
|
494
|
+
for (const pattern of detector.patterns) {
|
|
495
|
+
if (pattern.test(query)) {
|
|
496
|
+
categoryScore += detector.weight;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (categoryScore > 0) {
|
|
500
|
+
scores.set(detector.category, categoryScore);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (scores.size === 0)
|
|
504
|
+
return { category: null, confidence: 0 };
|
|
505
|
+
// Find highest-scoring category
|
|
506
|
+
let bestCategory = "";
|
|
507
|
+
let bestScore = 0;
|
|
508
|
+
let totalScore = 0;
|
|
509
|
+
for (const [cat, score] of scores) {
|
|
510
|
+
totalScore += score;
|
|
511
|
+
if (score > bestScore) {
|
|
512
|
+
bestScore = score;
|
|
513
|
+
bestCategory = cat;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
// Confidence: how dominant is the top category vs others
|
|
517
|
+
// If one category is clearly dominant, confidence is high
|
|
518
|
+
const dominance = bestScore / totalScore;
|
|
519
|
+
// Clamp raw score: a single strong signal (0.4+) → confidence 0.6+
|
|
520
|
+
const rawConfidence = Math.min(bestScore / 0.8, 1.0);
|
|
521
|
+
const confidence = rawConfidence * (0.5 + dominance * 0.5);
|
|
522
|
+
return { category: bestCategory, confidence: Math.min(confidence, 1.0) };
|
|
523
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF-IDF text search engine for API entries.
|
|
3
|
+
*
|
|
4
|
+
* Builds an in-memory index from entries and provides relevance-ranked search
|
|
5
|
+
* without requiring any external API calls or embedding models. Uses weighted
|
|
6
|
+
* multi-field document construction to boost precision:
|
|
7
|
+
* name (x4) > bestFor/subcategory (x3) > useCases (x2) > description (x1)
|
|
8
|
+
*
|
|
9
|
+
* TF-IDF formula:
|
|
10
|
+
* score(doc, query) = sum_t( TF(t, doc) * IDF(t) ) for each query term t
|
|
11
|
+
* IDF(t) = log((N + 1) / (df(t) + 1)) + 1 (Laplace smoothing)
|
|
12
|
+
*
|
|
13
|
+
* Exports: TfIdfEngine, getTfIdfEngine, resetTfIdfEngine
|
|
14
|
+
*/
|
|
15
|
+
import type { ApiEntry } from "./schema.js";
|
|
16
|
+
export declare class TfIdfEngine {
|
|
17
|
+
private vectors;
|
|
18
|
+
private idf;
|
|
19
|
+
private entryBySlug;
|
|
20
|
+
private _built;
|
|
21
|
+
/**
|
|
22
|
+
* Build the TF-IDF index from a list of entries.
|
|
23
|
+
* Can be called again to rebuild with updated entries.
|
|
24
|
+
*/
|
|
25
|
+
build(entries: ApiEntry[]): void;
|
|
26
|
+
/**
|
|
27
|
+
* Search the index for entries most relevant to the query.
|
|
28
|
+
* Returns up to topN entries sorted by TF-IDF score descending.
|
|
29
|
+
* Entries with zero score are excluded.
|
|
30
|
+
*/
|
|
31
|
+
search(query: string, topN?: number): ApiEntry[];
|
|
32
|
+
get isBuilt(): boolean;
|
|
33
|
+
/** Number of indexed documents */
|
|
34
|
+
get size(): number;
|
|
35
|
+
}
|
|
36
|
+
/** Get the shared engine instance (lazily created) */
|
|
37
|
+
export declare function getTfIdfEngine(): TfIdfEngine;
|
|
38
|
+
/**
|
|
39
|
+
* Reset the shared engine (forces rebuild on next search).
|
|
40
|
+
* Must be called in test afterEach when the DB is replaced.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resetTfIdfEngine(): void;
|