verify-phone-sms 0.9.4

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 (56) hide show
  1. package/.env.example +20 -0
  2. package/CHANGELOG.md +71 -0
  3. package/DEPLOYMENT.md +151 -0
  4. package/README.md +475 -0
  5. package/bun.lock +1110 -0
  6. package/docs/.source/index.ts +11 -0
  7. package/docs/.source/source.config.mjs +9 -0
  8. package/docs/app/(home)/layout.tsx +7 -0
  9. package/docs/app/(home)/page.tsx +38 -0
  10. package/docs/app/docs/[[...slug]]/page.tsx +59 -0
  11. package/docs/app/docs/layout.tsx +12 -0
  12. package/docs/app/docs-og/[...slug]/route.ts +24 -0
  13. package/docs/app/globals.css +587 -0
  14. package/docs/app/layout.config.tsx +13 -0
  15. package/docs/app/layout.tsx +27 -0
  16. package/docs/app/logo.tsx +35 -0
  17. package/docs/bun.lock +923 -0
  18. package/docs/content/docs/API_AUTHENTICATION.md +91 -0
  19. package/docs/content/docs/DEPLOYMENT.md +181 -0
  20. package/docs/content/docs/api/post.mdx +35 -0
  21. package/docs/content/docs/api/verify.mdx +34 -0
  22. package/docs/content/docs/meta.json +8 -0
  23. package/docs/content/docs/verify-legal-name.md +339 -0
  24. package/docs/lib/source.ts +14 -0
  25. package/docs/mdx-components.tsx +12 -0
  26. package/docs/next.config.mjs +51 -0
  27. package/docs/openapi.json +329 -0
  28. package/docs/package.json +37 -0
  29. package/docs/postcss.config.mjs +5 -0
  30. package/docs/scripts/generate-docs.mjs +23 -0
  31. package/docs/source.config.ts +5 -0
  32. package/docs/tsconfig.json +29 -0
  33. package/docs/worker.js +35 -0
  34. package/docs/wrangler.toml +26 -0
  35. package/examples/client.ts +105 -0
  36. package/examples/demo.html +325 -0
  37. package/examples/libphonenumber-example.ts +120 -0
  38. package/openapi.json +329 -0
  39. package/package.json +75 -0
  40. package/scripts/deploy.sh +63 -0
  41. package/src/identity-verification-server.ts +677 -0
  42. package/src/index.ts +8 -0
  43. package/src/sns.ts +265 -0
  44. package/src/verify-phone-server.ts +503 -0
  45. package/src/verify-phone.ts +577 -0
  46. package/test/api.test.ts +205 -0
  47. package/test/integration.test.ts +152 -0
  48. package/test/metadata-test.ts +73 -0
  49. package/test/server.test.ts +143 -0
  50. package/test/setup.ts +32 -0
  51. package/test/utils.test.ts +186 -0
  52. package/test/verify.test.ts +20 -0
  53. package/test/voip.test.ts +113 -0
  54. package/tsconfig.json +24 -0
  55. package/vitest.config.ts +10 -0
  56. package/wrangler.toml +24 -0
@@ -0,0 +1,677 @@
1
+ import { cors } from "hono/cors";
2
+ import { logger } from "hono/logger";
3
+ import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
4
+
5
+ // Input/Output Schemas
6
+ const PersonInputSchema = z.object({
7
+ phone_number: z
8
+ .string()
9
+ .min(10)
10
+ .max(15)
11
+ .describe("Phone number in E.164 or local format"),
12
+ legal_name: z
13
+ .string()
14
+ .min(1)
15
+ .max(100)
16
+ .describe("Full legal name of the person"),
17
+ current_address: z
18
+ .object({
19
+ street_line_1: z.string().min(1).max(1000),
20
+ street_line_2: z.string().max(1000).optional(),
21
+ city: z.string().min(1).max(500),
22
+ state_code: z.string().length(2),
23
+ postal_code: z.string().min(5).max(10),
24
+ country_code: z.string().length(2).default("US"),
25
+ })
26
+ .describe("Current address information"),
27
+ });
28
+
29
+ const VerificationResponseSchema = z.object({
30
+ verification_score: z
31
+ .number()
32
+ .min(0)
33
+ .max(100)
34
+ .describe("Confidence score 0-100"),
35
+ name_match_found: z.boolean(),
36
+ phone_validated: z.boolean(),
37
+ address_validated: z.boolean(),
38
+ questions: z.array(
39
+ z.object({
40
+ id: z.string(),
41
+ question: z.string(),
42
+ type: z.enum(["address_history", "phone_history", "name_verification"]),
43
+ options: z.array(z.string()).optional(),
44
+ }),
45
+ ),
46
+ historical_data: z.object({
47
+ previous_addresses: z.array(z.string()),
48
+ previous_phones: z.array(z.string()),
49
+ associated_names: z.array(z.string()),
50
+ }),
51
+ recommendations: z
52
+ .array(z.string())
53
+ .describe("Suggestions for additional verification"),
54
+ });
55
+
56
+ // TrestleIQ API Functions
57
+ interface AddressInput {
58
+ street_line_1: string;
59
+ street_line_2?: string;
60
+ city: string;
61
+ state_code: string;
62
+ postal_code: string;
63
+ country_code?: string;
64
+ }
65
+
66
+ interface PhoneHints {
67
+ name?: string;
68
+ postalCode?: string;
69
+ }
70
+
71
+ interface PersonInput {
72
+ phone_number: string;
73
+ legal_name: string;
74
+ current_address: AddressInput;
75
+ }
76
+
77
+ async function makeRequest(
78
+ endpoint: string,
79
+ params: Record<string, string | undefined>,
80
+ apiKey: string,
81
+ baseUrl = "https://api.trestleiq.com",
82
+ ): Promise<any> {
83
+ const url = new URL(endpoint, baseUrl);
84
+ Object.entries(params).forEach(([key, value]) => {
85
+ if (value) url.searchParams.set(key, value as string);
86
+ });
87
+
88
+ const response = await fetch(url.toString(), {
89
+ headers: {
90
+ "x-api-key": apiKey,
91
+ Accept: "application/json",
92
+ },
93
+ });
94
+
95
+ if (!response.ok) {
96
+ throw new Error(
97
+ `TrestleIQ API error: ${response.status} ${response.statusText}`,
98
+ );
99
+ }
100
+
101
+ return response.json();
102
+ }
103
+
104
+ function reversePhone(phoneNumber: string, apiKey: string, hints: PhoneHints = {}) {
105
+ return makeRequest(
106
+ "/3.2/phone",
107
+ {
108
+ phone: phoneNumber,
109
+ "phone.country_hint": "US",
110
+ ...(hints.name && { "phone.name_hint": hints.name }),
111
+ ...(hints.postalCode && { "phone.postal_code_hint": hints.postalCode }),
112
+ },
113
+ apiKey,
114
+ );
115
+ }
116
+
117
+ function findPerson(name: string, address: AddressInput, apiKey: string) {
118
+ return makeRequest(
119
+ "/3.1/person",
120
+ {
121
+ name,
122
+ "address.street_line_1": address.street_line_1,
123
+ "address.city": address.city,
124
+ "address.state_code": address.state_code,
125
+ "address.postal_code": address.postal_code,
126
+ "address.country_code": address.country_code || "US",
127
+ },
128
+ apiKey,
129
+ );
130
+ }
131
+
132
+ function reverseAddress(address: AddressInput, apiKey: string) {
133
+ return makeRequest(
134
+ "/3.1/location",
135
+ {
136
+ street_line_1: address.street_line_1,
137
+ ...(address.street_line_2 && { street_line_2: address.street_line_2 }),
138
+ city: address.city,
139
+ state_code: address.state_code,
140
+ postal_code: address.postal_code,
141
+ country_code: address.country_code || "US",
142
+ },
143
+ apiKey,
144
+ );
145
+ }
146
+
147
+ // Utility Functions
148
+ function normalizeName(name: string): string {
149
+ return name
150
+ .toLowerCase()
151
+ .replace(/[^a-z\s]/g, "")
152
+ .trim();
153
+ }
154
+
155
+ function extractHistoricalData(phoneResult: any, personResult: any, addressResult: any) {
156
+ const previousAddresses: string[] = [];
157
+ const previousPhones: string[] = [];
158
+ const associatedNames: string[] = [];
159
+
160
+ // Extract from phone result
161
+ if (phoneResult?.owners) {
162
+ phoneResult.owners.forEach((owner: any) => {
163
+ if (owner.name) associatedNames.push(owner.name);
164
+ if (owner.addresses) {
165
+ owner.addresses.forEach((addr: any) => {
166
+ if (addr.street_line_1 && addr.city && addr.state_code) {
167
+ previousAddresses.push(
168
+ `${addr.street_line_1}, ${addr.city}, ${addr.state_code}`,
169
+ );
170
+ }
171
+ });
172
+ }
173
+ if (owner.phones) {
174
+ owner.phones.forEach((phone: any) => {
175
+ if (phone.phone_number) previousPhones.push(phone.phone_number);
176
+ });
177
+ }
178
+ });
179
+ }
180
+
181
+ // Extract from person result
182
+ if (personResult?.person) {
183
+ personResult.person.forEach((person: any) => {
184
+ if (person.addresses) {
185
+ person.addresses.forEach((addr: any) => {
186
+ if (addr.street_line_1 && addr.city && addr.state_code) {
187
+ previousAddresses.push(
188
+ `${addr.street_line_1}, ${addr.city}, ${addr.state_code}`,
189
+ );
190
+ }
191
+ });
192
+ }
193
+ if (person.phones) {
194
+ person.phones.forEach((phone: any) => {
195
+ if (phone.phone_number) previousPhones.push(phone.phone_number);
196
+ });
197
+ }
198
+ });
199
+ }
200
+
201
+ // Extract from address result
202
+ if (addressResult?.current_residents) {
203
+ addressResult.current_residents.forEach((resident: any) => {
204
+ if (resident.name) associatedNames.push(resident.name);
205
+ });
206
+ }
207
+
208
+ return {
209
+ previous_addresses: [...new Set(previousAddresses)].slice(0, 10),
210
+ previous_phones: [...new Set(previousPhones)].slice(0, 5),
211
+ associated_names: [...new Set(associatedNames)].slice(0, 10),
212
+ };
213
+ }
214
+
215
+ function checkNameMatch(personResult: any, phoneResult: any, inputName: string): boolean {
216
+ const normalizedInputName = normalizeName(inputName);
217
+
218
+ // Check person result
219
+ if (personResult?.person) {
220
+ for (const person of personResult.person) {
221
+ if (person.name && normalizeName(person.name) === normalizedInputName) {
222
+ return true;
223
+ }
224
+ }
225
+ }
226
+
227
+ // Check phone owners
228
+ if (phoneResult?.owners) {
229
+ for (const owner of phoneResult.owners) {
230
+ if (owner.name && normalizeName(owner.name) === normalizedInputName) {
231
+ return true;
232
+ }
233
+ }
234
+ }
235
+
236
+ return false;
237
+ }
238
+
239
+ function checkPartialNameMatch(personResult: any, phoneResult: any, inputName: string): boolean {
240
+ const inputNameParts = normalizeName(inputName).split(" ");
241
+
242
+ const checkNameParts = (name: string): boolean => {
243
+ const nameParts = normalizeName(name).split(" ");
244
+ return inputNameParts.some(
245
+ (part) => nameParts.includes(part) && part.length > 2,
246
+ );
247
+ };
248
+
249
+ // Check person result
250
+ if (personResult?.person) {
251
+ for (const person of personResult.person) {
252
+ if (person.name && checkNameParts(person.name)) {
253
+ return true;
254
+ }
255
+ }
256
+ }
257
+
258
+ // Check phone owners
259
+ if (phoneResult?.owners) {
260
+ for (const owner of phoneResult.owners) {
261
+ if (owner.name && checkNameParts(owner.name)) {
262
+ return true;
263
+ }
264
+ }
265
+ }
266
+
267
+ return false;
268
+ }
269
+
270
+ function calculateVerificationScore(
271
+ phoneResult: any,
272
+ personResult: any,
273
+ addressResult: any,
274
+ input: PersonInput,
275
+ ): number {
276
+ let score = 0;
277
+
278
+ // Phone validation (30 points max)
279
+ if (phoneResult?.is_valid) {
280
+ score += 15;
281
+ if (
282
+ phoneResult.line_type === "Mobile" ||
283
+ phoneResult.line_type === "Landline"
284
+ ) {
285
+ score += 10;
286
+ }
287
+ if (!phoneResult.is_commercial) {
288
+ score += 5;
289
+ }
290
+ }
291
+
292
+ // Name matching (40 points max)
293
+ if (checkNameMatch(personResult, phoneResult, input.legal_name)) {
294
+ score += 40;
295
+ } else if (
296
+ checkPartialNameMatch(personResult, phoneResult, input.legal_name)
297
+ ) {
298
+ score += 20;
299
+ }
300
+
301
+ // Address validation (30 points max)
302
+ if (addressResult?.is_valid) {
303
+ score += 15;
304
+ if (addressResult.is_active) {
305
+ score += 10;
306
+ }
307
+ if (!addressResult.is_commercial) {
308
+ score += 5;
309
+ }
310
+ }
311
+
312
+ return Math.min(score, 100);
313
+ }
314
+
315
+ function generateFakeAddresses(count: number): string[] {
316
+ const fakeAddresses = [
317
+ "123 Fake St, Nowhere, CA",
318
+ "456 Made Up Ave, Fictional, TX",
319
+ "789 Pretend Dr, Imaginary, FL",
320
+ "101 False Blvd, Bogus, NY",
321
+ "202 Phony Way, Unreal, WA",
322
+ ];
323
+ return fakeAddresses.sort(() => Math.random() - 0.5).slice(0, count);
324
+ }
325
+
326
+ function generateFakePhones(count: number): string[] {
327
+ const fakePhones: string[] = [];
328
+ for (let i = 0; i < count; i++) {
329
+ const areaCode = Math.floor(Math.random() * 900) + 100;
330
+ const exchange = Math.floor(Math.random() * 900) + 100;
331
+ const number = Math.floor(Math.random() * 9000) + 1000;
332
+ fakePhones.push(`${areaCode}${exchange}${number}`);
333
+ }
334
+ return fakePhones;
335
+ }
336
+
337
+ interface HistoricalData {
338
+ previous_addresses: string[];
339
+ previous_phones: string[];
340
+ associated_names: string[];
341
+ }
342
+
343
+ interface VerificationQuestion {
344
+ id: string;
345
+ question: string;
346
+ type: "address_history" | "phone_history" | "name_verification";
347
+ options?: string[];
348
+ }
349
+
350
+ function generateVerificationQuestions(historicalData: HistoricalData, inputName: string): VerificationQuestion[] {
351
+ const questions: VerificationQuestion[] = [];
352
+
353
+ // Address history questions
354
+ if (historicalData.previous_addresses.length > 0) {
355
+ const shuffledAddresses = [...historicalData.previous_addresses].sort(
356
+ () => Math.random() - 0.5,
357
+ );
358
+ const realAddresses = shuffledAddresses.slice(0, 3);
359
+ const fakeAddresses = generateFakeAddresses(2);
360
+ const allOptions = [...realAddresses, ...fakeAddresses].sort(
361
+ () => Math.random() - 0.5,
362
+ );
363
+
364
+ questions.push({
365
+ id: `addr_${Date.now()}`,
366
+ question: `Which of the following addresses have you lived at in the past?`,
367
+ type: "address_history",
368
+ options: allOptions,
369
+ });
370
+ }
371
+
372
+ // Phone history questions
373
+ if (historicalData.previous_phones.length > 1) {
374
+ const shuffledPhones = [...historicalData.previous_phones].sort(
375
+ () => Math.random() - 0.5,
376
+ );
377
+ const realPhones = shuffledPhones.slice(0, 2);
378
+ const fakePhones = generateFakePhones(2);
379
+ const allOptions = [...realPhones, ...fakePhones, "None of the above"].sort(
380
+ () => Math.random() - 0.5,
381
+ );
382
+
383
+ questions.push({
384
+ id: `phone_${Date.now()}`,
385
+ question: `Which of the following phone numbers have you previously used?`,
386
+ type: "phone_history",
387
+ options: allOptions,
388
+ });
389
+ }
390
+
391
+ // Name verification questions
392
+ if (historicalData.associated_names.length > 0) {
393
+ const otherNames = historicalData.associated_names.filter(
394
+ (name: string) => normalizeName(name) !== normalizeName(inputName),
395
+ );
396
+
397
+ if (otherNames.length > 0) {
398
+ questions.push({
399
+ id: `name_${Date.now()}`,
400
+ question: `Are any of the following names associated with you (maiden name, nickname, etc.)?`,
401
+ type: "name_verification",
402
+ options: [...otherNames.slice(0, 4), "None of the above"],
403
+ });
404
+ }
405
+ }
406
+
407
+ return questions;
408
+ }
409
+
410
+ function generateRecommendations(
411
+ score: number,
412
+ phoneResult: any,
413
+ personResult: any,
414
+ addressResult: any,
415
+ ): string[] {
416
+ const recommendations: string[] = [];
417
+
418
+ if (score < 30) {
419
+ recommendations.push(
420
+ "Consider requesting additional identification documents",
421
+ );
422
+ recommendations.push(
423
+ "Verify identity through alternative methods (government ID, utility bills)",
424
+ );
425
+ } else if (score < 60) {
426
+ recommendations.push("Request additional verification questions");
427
+ recommendations.push("Consider manual review of provided information");
428
+ } else if (score < 80) {
429
+ recommendations.push("Proceed with standard verification process");
430
+ } else {
431
+ recommendations.push(
432
+ "High confidence verification - proceed with confidence",
433
+ );
434
+ }
435
+
436
+ if (!phoneResult?.is_valid) {
437
+ recommendations.push("Request alternative contact phone number");
438
+ }
439
+
440
+ if (!addressResult?.is_valid) {
441
+ recommendations.push(
442
+ "Verify current address with utility bill or bank statement",
443
+ );
444
+ }
445
+
446
+ if (phoneResult?.line_type === "NonFixedVOIP") {
447
+ recommendations.push(
448
+ "VOIP number detected - consider additional verification",
449
+ );
450
+ }
451
+
452
+ return recommendations;
453
+ }
454
+
455
+ // Main Verification Function
456
+ async function verifyIdentity(input: PersonInput, apiKey: string) {
457
+ const { phone_number, legal_name, current_address } = input;
458
+
459
+ // Run all API calls in parallel
460
+ const [phoneData, personData, addressData] = await Promise.allSettled([
461
+ reversePhone(phone_number, apiKey, {
462
+ name: legal_name,
463
+ postalCode: current_address.postal_code,
464
+ }),
465
+ findPerson(legal_name, current_address, apiKey),
466
+ reverseAddress(current_address, apiKey),
467
+ ]);
468
+
469
+ // Process results
470
+ const phoneResult = phoneData.status === "fulfilled" ? phoneData.value : null;
471
+ const personResult =
472
+ personData.status === "fulfilled" ? personData.value : null;
473
+ const addressResult =
474
+ addressData.status === "fulfilled" ? addressData.value : null;
475
+
476
+ // Extract historical data
477
+ const historicalData = extractHistoricalData(
478
+ phoneResult,
479
+ personResult,
480
+ addressResult,
481
+ );
482
+
483
+ // Calculate verification score
484
+ const verificationScore = calculateVerificationScore(
485
+ phoneResult,
486
+ personResult,
487
+ addressResult,
488
+ input,
489
+ );
490
+
491
+ // Generate verification questions
492
+ const questions = generateVerificationQuestions(historicalData, legal_name);
493
+
494
+ // Generate recommendations
495
+ const recommendations = generateRecommendations(
496
+ verificationScore,
497
+ phoneResult,
498
+ personResult,
499
+ addressResult,
500
+ );
501
+
502
+ return {
503
+ verification_score: verificationScore,
504
+ name_match_found: checkNameMatch(personResult, phoneResult, legal_name),
505
+ phone_validated: phoneResult?.is_valid || false,
506
+ address_validated: addressResult?.is_valid || false,
507
+ questions,
508
+ historical_data: historicalData,
509
+ recommendations,
510
+ };
511
+ }
512
+
513
+ // Create Hono app
514
+ const app = new OpenAPIHono();
515
+
516
+ // Middleware
517
+ app.use("*", cors());
518
+ app.use("*", logger());
519
+
520
+ // Get API key from environment
521
+ const getApiKey = () => {
522
+ const apiKey = process.env.TRESTLE_API_KEY;
523
+ if (!apiKey) {
524
+ throw new Error("TRESTLE_API_KEY environment variable is required");
525
+ }
526
+ return apiKey;
527
+ };
528
+
529
+ // Main API route
530
+ const verifyIdentityRoute = createRoute({
531
+ method: "post",
532
+ path: "/verify-identity",
533
+ tags: ["Identity Verification"],
534
+ summary: "Verify person identity",
535
+ description:
536
+ "Verify a person's identity using phone number, legal name, and current address",
537
+ request: {
538
+ body: {
539
+ content: {
540
+ "application/json": {
541
+ schema: PersonInputSchema,
542
+ },
543
+ },
544
+ },
545
+ },
546
+ responses: {
547
+ 200: {
548
+ content: {
549
+ "application/json": {
550
+ schema: VerificationResponseSchema,
551
+ },
552
+ },
553
+ description: "Identity verification completed successfully",
554
+ },
555
+ 400: {
556
+ description: "Bad request - invalid input data",
557
+ },
558
+ 500: {
559
+ description: "Internal server error",
560
+ },
561
+ },
562
+ });
563
+
564
+ app.openapi(verifyIdentityRoute, async (c) => {
565
+ try {
566
+ const body = c.req.valid("json") as PersonInput;
567
+ const apiKey = getApiKey();
568
+ const result = await verifyIdentity(body, apiKey);
569
+
570
+ return c.json(result, 200);
571
+ } catch (error) {
572
+ console.error("Identity verification error:", error);
573
+ return c.json(
574
+ {
575
+ error: "Failed to verify identity",
576
+ message: error instanceof Error ? error.message : "Unknown error",
577
+ },
578
+ 500,
579
+ );
580
+ }
581
+ });
582
+
583
+ // Health check endpoint
584
+ app.get("/health", (c) => {
585
+ return c.json({ status: "healthy", timestamp: new Date().toISOString() });
586
+ });
587
+
588
+ // OpenAPI documentation
589
+ app.doc("/doc", {
590
+ openapi: "3.0.0",
591
+ info: {
592
+ version: "1.0.0",
593
+ title: "Identity Verification API",
594
+ description:
595
+ "API for verifying person identity using TrestleIQ data services",
596
+ },
597
+ servers: [
598
+ {
599
+ url: "http://localhost:3000",
600
+ description: "Development server",
601
+ },
602
+ ],
603
+ });
604
+
605
+ // Serve Swagger UI
606
+ app.get("/swagger", async (c) => {
607
+ return c.html(`
608
+ <!DOCTYPE html>
609
+ <html>
610
+ <head>
611
+ <title>Identity Verification API</title>
612
+ <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@3.52.5/swagger-ui.css" />
613
+ </head>
614
+ <body>
615
+ <div id="swagger-ui"></div>
616
+ <script src="https://unpkg.com/swagger-ui-dist@3.52.5/swagger-ui-bundle.js"></script>
617
+ <script>
618
+ SwaggerUIBundle({
619
+ url: '/doc',
620
+ dom_id: '#swagger-ui',
621
+ presets: [SwaggerUIBundle.presets.standalone]
622
+ })
623
+ </script>
624
+ </body>
625
+ </html>
626
+ `);
627
+ });
628
+
629
+ // Demo endpoint with sample data
630
+ app.get("/demo", async (c) => {
631
+ const sampleRequest = {
632
+ phone_number: "2069735100",
633
+ legal_name: "John Smith",
634
+ current_address: {
635
+ street_line_1: "123 Main St",
636
+ city: "Seattle",
637
+ state_code: "WA",
638
+ postal_code: "98101",
639
+ country_code: "US",
640
+ },
641
+ };
642
+
643
+ try {
644
+ const apiKey = getApiKey();
645
+ const result = await verifyIdentity(sampleRequest, apiKey);
646
+
647
+ return c.json({
648
+ demo_request: sampleRequest,
649
+ demo_response: result,
650
+ note: "This is a demo using sample data. Use POST /verify-identity for real verification.",
651
+ });
652
+ } catch (error) {
653
+ return c.json({
654
+ demo_request: sampleRequest,
655
+ error: "Demo failed - check your TRESTLE_API_KEY",
656
+ note: "This demo requires a valid TrestleIQ API key",
657
+ });
658
+ }
659
+ });
660
+
661
+ export default app;
662
+
663
+ declare const Bun: { serve: (options: { port: number; fetch: any }) => unknown };
664
+
665
+ // Start server if not imported as module
666
+ if ((import.meta as any).main) {
667
+ const port = parseInt(process.env.PORT || "3000");
668
+ console.log(`🚀 Server running at http://localhost:${port}`);
669
+ console.log(`📚 API Documentation: http://localhost:${port}/swagger`);
670
+ console.log(`🔍 OpenAPI Spec: http://localhost:${port}/doc`);
671
+ console.log(`🎮 Demo Endpoint: http://localhost:${port}/demo`);
672
+
673
+ Bun.serve({
674
+ port,
675
+ fetch: app.fetch,
676
+ });
677
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Main entry point for the SMS Verification API server.
3
+ * This file is used by Cloudflare Workers and Wrangler.
4
+ */
5
+
6
+ import app from "./verify-phone-server";
7
+
8
+ export default app;