@absolutejs/voice 0.0.22-beta.372 → 0.0.22-beta.374

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 CHANGED
@@ -1519,6 +1519,32 @@ app.use(
1519
1519
  );
1520
1520
  ```
1521
1521
 
1522
+ For longer-running proof collection, add a job store and mark selected actions async. The POST returns a `jobId` with `jobStatus: "queued"`, and the app can poll `${path}/actions/:jobId`:
1523
+
1524
+ ```ts
1525
+ const recoveryJobs = createVoiceInMemoryRealCallProfileRecoveryJobStore();
1526
+
1527
+ app.use(
1528
+ createVoiceRealCallProfileRecoveryActionRoutes({
1529
+ asyncActionIds: ['collect-browser-proof', 'collect-phone-proof'],
1530
+ handlers: {
1531
+ 'collect-browser-proof': async () => runBrowserProfileProof(),
1532
+ 'collect-phone-proof': async () => runPhoneProfileProof()
1533
+ },
1534
+ jobStore: recoveryJobs,
1535
+ source: buildRealCallHistory
1536
+ })
1537
+ );
1538
+ ```
1539
+
1540
+ Use the SQLite job store when recovery jobs should survive restarts:
1541
+
1542
+ ```ts
1543
+ const recoveryJobs = createVoiceSQLiteRealCallProfileRecoveryJobStore({
1544
+ path: '.voice-runtime/real-call-recovery/jobs.sqlite'
1545
+ });
1546
+ ```
1547
+
1522
1548
  Use `createVoiceProfileTraceTagger(...)` when the app already has a trace store and needs every appended trace to carry a benchmark profile label. It wraps any `VoiceTraceEventStore`, preserves the underlying store behavior, and adds `profileId`/`benchmarkProfileId` metadata and payload fields that real-call profile history can ingest later.
1523
1549
 
1524
1550
  ```ts
@@ -3907,6 +3907,7 @@ var defineVoicePlatformCoverageElement = (tagName = "absolute-voice-platform-cov
3907
3907
  };
3908
3908
  // src/proofTrends.ts
3909
3909
  import { Elysia } from "elysia";
3910
+ import { Database } from "bun:sqlite";
3910
3911
  var DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS = 24 * 60 * 60 * 1000;
3911
3912
  var DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS = [
3912
3913
  {
@@ -4627,6 +4628,98 @@ var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
4627
4628
  }
4628
4629
  return uniqueRealCallProfileActions(actions);
4629
4630
  };
4631
+ var createVoiceInMemoryRealCallProfileRecoveryJobStore = (options = {}) => {
4632
+ const jobs = new Map;
4633
+ const now = () => (options.now ?? (() => new Date))().toISOString();
4634
+ const createId = () => `${options.idPrefix ?? "voice-recovery-job"}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
4635
+ return {
4636
+ create(input) {
4637
+ const createdAt = input.createdAt ?? now();
4638
+ const job = {
4639
+ actionId: input.actionId,
4640
+ createdAt,
4641
+ id: input.id ?? createId(),
4642
+ message: input.message,
4643
+ status: input.status ?? "queued",
4644
+ updatedAt: createdAt
4645
+ };
4646
+ jobs.set(job.id, job);
4647
+ return job;
4648
+ },
4649
+ get(id) {
4650
+ return jobs.get(id);
4651
+ },
4652
+ update(id, update) {
4653
+ const existing = jobs.get(id);
4654
+ if (!existing) {
4655
+ return;
4656
+ }
4657
+ const next = {
4658
+ ...existing,
4659
+ ...update,
4660
+ updatedAt: update.updatedAt ?? now()
4661
+ };
4662
+ jobs.set(id, next);
4663
+ return next;
4664
+ }
4665
+ };
4666
+ };
4667
+ var normalizeRealCallRecoveryJobTableName = (value) => value.trim().replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "voice_real_call_profile_recovery_jobs";
4668
+ var createVoiceSQLiteRealCallProfileRecoveryJobStore = (options = {}) => {
4669
+ const database = options.database ?? new Database(options.path ?? ":memory:", {
4670
+ create: true
4671
+ });
4672
+ const tableName = normalizeRealCallRecoveryJobTableName(options.tableName ?? "voice_real_call_profile_recovery_jobs");
4673
+ const now = () => (options.now ?? (() => new Date))().toISOString();
4674
+ const createId = () => `${options.idPrefix ?? "voice-recovery-job"}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
4675
+ database.exec("PRAGMA journal_mode = WAL;");
4676
+ database.exec("PRAGMA synchronous = NORMAL;");
4677
+ database.exec("PRAGMA busy_timeout = 5000;");
4678
+ database.exec(`CREATE TABLE IF NOT EXISTS "${tableName}" (
4679
+ id TEXT PRIMARY KEY,
4680
+ sort_at INTEGER NOT NULL,
4681
+ payload TEXT NOT NULL
4682
+ )`);
4683
+ const selectStatement = database.query(`SELECT payload FROM "${tableName}" WHERE id = ?1 LIMIT 1`);
4684
+ const upsertStatement = database.query(`INSERT INTO "${tableName}" (id, sort_at, payload)
4685
+ VALUES (?1, ?2, ?3)
4686
+ ON CONFLICT(id) DO UPDATE SET sort_at = excluded.sort_at, payload = excluded.payload`);
4687
+ const writeJob = (job) => {
4688
+ upsertStatement.run(job.id, Date.parse(job.updatedAt) || Date.now(), JSON.stringify(job));
4689
+ return job;
4690
+ };
4691
+ const readJob = (id) => {
4692
+ const row = selectStatement.get(id);
4693
+ return row ? JSON.parse(row.payload) : undefined;
4694
+ };
4695
+ return {
4696
+ create(input) {
4697
+ const createdAt = input.createdAt ?? now();
4698
+ return writeJob({
4699
+ actionId: input.actionId,
4700
+ createdAt,
4701
+ id: input.id ?? createId(),
4702
+ message: input.message,
4703
+ status: input.status ?? "queued",
4704
+ updatedAt: createdAt
4705
+ });
4706
+ },
4707
+ get(id) {
4708
+ return readJob(id);
4709
+ },
4710
+ update(id, update) {
4711
+ const existing = readJob(id);
4712
+ if (!existing) {
4713
+ return;
4714
+ }
4715
+ return writeJob({
4716
+ ...existing,
4717
+ ...update,
4718
+ updatedAt: update.updatedAt ?? now()
4719
+ });
4720
+ }
4721
+ };
4722
+ };
4630
4723
  var buildVoiceRealCallProfileReadinessCheck = (report, options = {}) => {
4631
4724
  const { issues, warnings } = buildRealCallProfileReadinessIssues(report, options);
4632
4725
  const status = issues.length > 0 ? "fail" : warnings.length > 0 && options.failOnWarnings === true ? "fail" : warnings.length > 0 ? "warn" : "pass";
@@ -5245,6 +5338,7 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
5245
5338
  });
5246
5339
  const actionPath = (actionId) => `${path}${realCallProfileActionPaths[actionId]}`;
5247
5340
  const loadReport = () => loadVoiceRealCallProfileHistoryRouteReport(options);
5341
+ const asyncActionIds = new Set(options.asyncActionIds ?? []);
5248
5342
  const listActions = async () => {
5249
5343
  const report = await loadReport();
5250
5344
  const actions = buildVoiceRealCallProfileRecoveryActions(report, {
@@ -5260,6 +5354,67 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
5260
5354
  }));
5261
5355
  return { actions, generatedAt: new Date().toISOString(), report };
5262
5356
  };
5357
+ const runActionHandler = async (actionId, report) => {
5358
+ const handler = options.handlers?.[actionId];
5359
+ if (!handler) {
5360
+ return;
5361
+ }
5362
+ return await handler({ actionId, report });
5363
+ };
5364
+ const runActionAsJob = async (actionId, report) => {
5365
+ const job = await options.jobStore?.create({
5366
+ actionId,
5367
+ message: `Queued real-call profile recovery action: ${actionId}.`,
5368
+ status: "queued"
5369
+ });
5370
+ if (!job) {
5371
+ return;
5372
+ }
5373
+ queueMicrotask(() => {
5374
+ (async () => {
5375
+ const startedAt = new Date().toISOString();
5376
+ await options.jobStore?.update(job.id, {
5377
+ message: `Running real-call profile recovery action: ${actionId}.`,
5378
+ startedAt,
5379
+ status: "running",
5380
+ updatedAt: startedAt
5381
+ });
5382
+ try {
5383
+ const result = await runActionHandler(actionId, report);
5384
+ const completedAt = new Date().toISOString();
5385
+ const ok = result?.ok ?? true;
5386
+ const status = result?.jobStatus ?? (result?.status === "fail" || ok === false ? "fail" : "pass");
5387
+ await options.jobStore?.update(job.id, {
5388
+ completedAt,
5389
+ message: result?.message ?? `Completed real-call profile recovery action: ${actionId}.`,
5390
+ ok,
5391
+ report: result?.report,
5392
+ status,
5393
+ updatedAt: completedAt
5394
+ });
5395
+ } catch (error) {
5396
+ const completedAt = new Date().toISOString();
5397
+ await options.jobStore?.update(job.id, {
5398
+ completedAt,
5399
+ message: error instanceof Error ? error.message : `Failed real-call profile recovery action: ${actionId}.`,
5400
+ ok: false,
5401
+ status: "fail",
5402
+ updatedAt: completedAt
5403
+ });
5404
+ }
5405
+ })();
5406
+ });
5407
+ return {
5408
+ actionId,
5409
+ generatedAt: new Date().toISOString(),
5410
+ job,
5411
+ jobId: job.id,
5412
+ jobStatus: job.status,
5413
+ message: job.message,
5414
+ ok: true,
5415
+ status: "pass"
5416
+ };
5417
+ };
5263
5418
  const runAction = async (actionId) => {
5264
5419
  const report = await loadReport();
5265
5420
  const handler = options.handlers?.[actionId];
@@ -5272,10 +5427,19 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
5272
5427
  status: "fail"
5273
5428
  };
5274
5429
  }
5275
- const result = await handler({ actionId, report });
5430
+ if (options.jobStore && asyncActionIds.has(actionId)) {
5431
+ const queued = await runActionAsJob(actionId, report);
5432
+ if (queued) {
5433
+ return queued;
5434
+ }
5435
+ }
5436
+ const result = await runActionHandler(actionId, report);
5276
5437
  return {
5277
5438
  actionId,
5278
5439
  generatedAt: new Date().toISOString(),
5440
+ job: result?.job,
5441
+ jobId: result?.jobId,
5442
+ jobStatus: result?.jobStatus,
5279
5443
  message: result?.message,
5280
5444
  ok: result?.ok ?? true,
5281
5445
  report: result?.report,
@@ -5283,6 +5447,24 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
5283
5447
  };
5284
5448
  };
5285
5449
  routes.get(`${path}/actions`, async () => Response.json(await listActions(), { headers: options.headers }));
5450
+ routes.get(`${path}/actions/:jobId`, async ({
5451
+ params,
5452
+ set
5453
+ }) => {
5454
+ const jobId = params.jobId ?? "";
5455
+ const job = await options.jobStore?.get(jobId);
5456
+ if (!job) {
5457
+ set.status = 404;
5458
+ return Response.json({
5459
+ jobId,
5460
+ message: `No real-call profile recovery job found for id: ${jobId}.`,
5461
+ ok: false
5462
+ }, { headers: options.headers });
5463
+ }
5464
+ return Response.json({ job, ok: job.ok ?? job.status !== "fail" }, {
5465
+ headers: options.headers
5466
+ });
5467
+ });
5286
5468
  for (const actionId of Object.keys(realCallProfileActionPaths)) {
5287
5469
  routes.post(actionPath(actionId), async ({ set }) => {
5288
5470
  const result = await runAction(actionId);
package/dist/index.d.ts CHANGED
@@ -30,12 +30,12 @@ export { assertVoicePlatformCoverage, buildVoicePlatformCoverageSummary, createV
30
30
  export { assertVoiceCompetitiveCoverage, buildVoiceCompetitiveCoverageReport, createVoiceCompetitiveCoverageRoutes, evaluateVoiceCompetitiveCoverage, renderVoiceCompetitiveCoverageHTML, renderVoiceCompetitiveCoverageMarkdown } from './competitiveCoverage';
31
31
  export type { VoiceCompetitiveCoverageAssertionInput, VoiceCompetitiveCoverageAssertionReport, VoiceCompetitiveCoverageIssue, VoiceCompetitiveCoverageLevel, VoiceCompetitiveCoverageReport, VoiceCompetitiveCoverageReportInput, VoiceCompetitiveCoverageRoutesOptions, VoiceCompetitiveCoverageStatus, VoiceCompetitiveCoverageSummary, VoiceCompetitiveDepthLevel, VoiceCompetitiveEvidence, VoiceCompetitiveSurface } from './competitiveCoverage';
32
32
  export type { VoicePlatformCoverageAssertionInput, VoicePlatformCoverageAssertionReport, VoicePlatformCoverageEvidence, VoicePlatformCoverageRoutesOptions, VoicePlatformCoverageStatus, VoicePlatformCoverageSummary, VoicePlatformCoverageSummaryInput, VoicePlatformCoverageSurface } from './platformCoverage';
33
- export { assertVoiceProofTrendEvidence, buildEmptyVoiceProofTrendReport, buildVoiceProofTrendProfileSummaries, buildVoiceProofTrendRecommendationReport, buildVoiceProofTrendReportFromRealCallProfiles, buildVoiceProofTrendReport, buildVoiceRealCallProfileEvidenceFromTraceEvents, buildVoiceRealCallProfileDefaults, buildVoiceRealCallProfileHistoryReport, buildVoiceRealCallProfileReadinessCheck, buildVoiceRealCallProfileRecoveryActions, createVoiceProofTrendRecommendationRoutes, createVoiceProofTrendRoutes, createVoiceRealCallProfileHistoryRoutes, createVoiceRealCallProfileRecoveryActionRoutes, DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS, DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS, evaluateVoiceProofTrendEvidence, formatVoiceProofTrendAge, loadVoiceRealCallProfileEvidenceFromTraceStore, normalizeVoiceProofTrendReport, readVoiceProofTrendReportFile, renderVoiceProofTrendRecommendationHTML, renderVoiceProofTrendRecommendationMarkdown, renderVoiceRealCallProfileHistoryHTML, renderVoiceRealCallProfileHistoryMarkdown, resolveVoiceRealCallProfileProviderRoute } from './proofTrends';
33
+ export { assertVoiceProofTrendEvidence, buildEmptyVoiceProofTrendReport, buildVoiceProofTrendProfileSummaries, buildVoiceProofTrendRecommendationReport, buildVoiceProofTrendReportFromRealCallProfiles, buildVoiceProofTrendReport, buildVoiceRealCallProfileEvidenceFromTraceEvents, buildVoiceRealCallProfileDefaults, buildVoiceRealCallProfileHistoryReport, buildVoiceRealCallProfileReadinessCheck, buildVoiceRealCallProfileRecoveryActions, createVoiceInMemoryRealCallProfileRecoveryJobStore, createVoiceSQLiteRealCallProfileRecoveryJobStore, createVoiceProofTrendRecommendationRoutes, createVoiceProofTrendRoutes, createVoiceRealCallProfileHistoryRoutes, createVoiceRealCallProfileRecoveryActionRoutes, DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS, DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS, evaluateVoiceProofTrendEvidence, formatVoiceProofTrendAge, loadVoiceRealCallProfileEvidenceFromTraceStore, normalizeVoiceProofTrendReport, readVoiceProofTrendReportFile, renderVoiceProofTrendRecommendationHTML, renderVoiceProofTrendRecommendationMarkdown, renderVoiceRealCallProfileHistoryHTML, renderVoiceRealCallProfileHistoryMarkdown, resolveVoiceRealCallProfileProviderRoute } from './proofTrends';
34
34
  export { applyVoiceProfileSwitchGuard, buildVoiceProfileSwitchReadinessReport, buildVoiceProfileSwitchLiveDecisionReport, createVoiceProfileSwitchLiveDecisionRoutes, createVoiceProfileSwitchPolicyProofRoutes, createVoiceProfileSwitchReadinessRoutes, recommendVoiceProfileSwitch, renderVoiceProfileSwitchLiveDecisionHTML, renderVoiceProfileSwitchPolicyProofHTML, renderVoiceProfileSwitchReadinessHTML, runVoiceProfileSwitchPolicyProof } from './profileSwitchRecommendation';
35
35
  export type { VoiceProfileSwitchGuardAction, VoiceProfileSwitchGuardDecision, VoiceProfileSwitchGuardMode, VoiceProfileSwitchGuardOptions, VoiceProfileSwitchObservedSignals, VoiceProfileSwitchLiveDecisionEvidence, VoiceProfileSwitchLiveDecisionReport, VoiceProfileSwitchLiveDecisionReportOptions, VoiceProfileSwitchLiveDecisionRoutesOptions, VoiceProfileSwitchLiveDecisionSession, VoiceProfileSwitchPolicyProofCase, VoiceProfileSwitchPolicyProofCaseResult, VoiceProfileSwitchPolicyProofOptions, VoiceProfileSwitchPolicyProofReport, VoiceProfileSwitchPolicyProofRoutesOptions, VoiceProfileSwitchReadinessIssue, VoiceProfileSwitchReadinessOptions, VoiceProfileSwitchReadinessReport, VoiceProfileSwitchReadinessRoutesOptions, VoiceProfileSwitchReadinessStatus, VoiceProfileSwitchRecommendation, VoiceProfileSwitchRecommendationOptions } from './profileSwitchRecommendation';
36
36
  export { buildVoiceProviderDecisionTraceReport, createVoiceProviderDecisionTraceEvent, createVoiceProviderDecisionTraceRoutes, listVoiceProviderDecisionTraces, renderVoiceProviderDecisionTraceHTML, renderVoiceProviderDecisionTraceMarkdown } from './providerDecisionTraces';
37
37
  export type { VoiceProviderDecisionStatus, VoiceProviderDecisionSurfaceReport, VoiceProviderDecisionTrace, VoiceProviderDecisionTraceInput, VoiceProviderDecisionTraceIssue, VoiceProviderDecisionTraceReport, VoiceProviderDecisionTraceReportOptions, VoiceProviderDecisionTraceRoutesOptions } from './providerDecisionTraces';
38
- export type { VoiceProofTrendAssertionInput, VoiceProofTrendAssertionReport, VoiceProofTrendCycle, VoiceProofTrendProfileDefinition, VoiceProofTrendProfileRecommendation, VoiceProofTrendProfileSummaryOptions, VoiceProofTrendProfileSummary, VoiceProofTrendProviderRecommendation, VoiceProofTrendProviderSummary, VoiceProofTrendRecommendation, VoiceProofTrendRecommendationOptions, VoiceProofTrendRecommendationReport, VoiceProofTrendRecommendationRoutesOptions, VoiceProofTrendRecommendationStatus, VoiceProofTrendRecommendationSurface, VoiceProofTrendRealCallProfileEvidence, VoiceProofTrendRealCallProfileReportOptions, VoiceProofTrendReport, VoiceProofTrendReportInput, VoiceProofTrendRoutesOptions, VoiceProofTrendRuntimeChannelSummary, VoiceProofTrendStatus, VoiceProofTrendSummary, VoiceRealCallProfileDefault, VoiceRealCallProfileDefaultsOptions, VoiceRealCallProfileDefaultsReport, VoiceRealCallProfileHistoryOptions, VoiceRealCallProfileHistoryReport, VoiceRealCallProfileHistoryRoutesOptions, VoiceRealCallProfileProviderRouteOptions, VoiceRealCallProfileReadinessCheckOptions, VoiceRealCallProfileRecoveryActionOptions, VoiceRealCallProfileRecoveryAction, VoiceRealCallProfileRecoveryActionHandler, VoiceRealCallProfileRecoveryActionHandlerInput, VoiceRealCallProfileRecoveryActionId, VoiceRealCallProfileRecoveryActionResult, VoiceRealCallProfileRecoveryActionRoutesOptions, VoiceRealCallProfileTraceEvidenceOptions, VoiceRealCallProfileTraceStoreEvidenceOptions } from './proofTrends';
38
+ export type { VoiceProofTrendAssertionInput, VoiceProofTrendAssertionReport, VoiceProofTrendCycle, VoiceProofTrendProfileDefinition, VoiceProofTrendProfileRecommendation, VoiceProofTrendProfileSummaryOptions, VoiceProofTrendProfileSummary, VoiceProofTrendProviderRecommendation, VoiceProofTrendProviderSummary, VoiceProofTrendRecommendation, VoiceProofTrendRecommendationOptions, VoiceProofTrendRecommendationReport, VoiceProofTrendRecommendationRoutesOptions, VoiceProofTrendRecommendationStatus, VoiceProofTrendRecommendationSurface, VoiceProofTrendRealCallProfileEvidence, VoiceProofTrendRealCallProfileReportOptions, VoiceProofTrendReport, VoiceProofTrendReportInput, VoiceProofTrendRoutesOptions, VoiceProofTrendRuntimeChannelSummary, VoiceProofTrendStatus, VoiceProofTrendSummary, VoiceRealCallProfileDefault, VoiceRealCallProfileDefaultsOptions, VoiceRealCallProfileDefaultsReport, VoiceRealCallProfileHistoryOptions, VoiceRealCallProfileHistoryReport, VoiceRealCallProfileHistoryRoutesOptions, VoiceRealCallProfileProviderRouteOptions, VoiceRealCallProfileReadinessCheckOptions, VoiceRealCallProfileRecoveryActionOptions, VoiceRealCallProfileRecoveryAction, VoiceRealCallProfileRecoveryActionHandler, VoiceRealCallProfileRecoveryActionHandlerInput, VoiceRealCallProfileRecoveryActionId, VoiceRealCallProfileRecoveryActionResult, VoiceRealCallProfileRecoveryActionRoutesOptions, VoiceRealCallProfileRecoveryJob, VoiceRealCallProfileRecoveryJobCreateInput, VoiceRealCallProfileRecoveryJobStatus, VoiceRealCallProfileRecoveryJobStore, VoiceRealCallProfileRecoveryJobUpdate, VoiceSQLiteRealCallProfileRecoveryJobStoreOptions, VoiceRealCallProfileTraceEvidenceOptions, VoiceRealCallProfileTraceStoreEvidenceOptions } from './proofTrends';
39
39
  export { assertVoiceSloCalibration, buildVoiceSloCalibrationReport, buildVoiceSloReadinessThresholdReport, createVoiceSloReadinessThresholdOptions, createVoiceSloReadinessThresholdRoutes, createVoiceSloThresholdProfile, createVoiceSloCalibrationRoutes, renderVoiceSloCalibrationMarkdown, renderVoiceSloReadinessThresholdHTML, renderVoiceSloReadinessThresholdMarkdown } from './sloCalibration';
40
40
  export type { VoiceSloCalibrationMetricKey, VoiceSloCalibrationOptions, VoiceSloCalibrationReport, VoiceSloCalibrationRoutesOptions, VoiceSloCalibrationSample, VoiceSloCalibrationStatus, VoiceSloCalibrationThreshold, VoiceSloCalibrationThresholds, VoiceSloReadinessThresholdReport, VoiceSloReadinessThresholdReportOptions, VoiceSloReadinessThresholdOptions, VoiceSloReadinessThresholdRoutesOptions, VoiceSloThresholdProfile } from './sloCalibration';
41
41
  export { assertVoiceLiveOpsControlEvidence, assertVoiceLiveOpsEvidence, buildVoiceLiveOpsControlState, createVoiceLiveOpsController, createVoiceLiveOpsRoutes, createVoiceMemoryLiveOpsControlStore, evaluateVoiceLiveOpsControlEvidence, evaluateVoiceLiveOpsEvidence, getVoiceLiveOpsControlStatus, VOICE_LIVE_OPS_ACTIONS } from './liveOps';
package/dist/index.js CHANGED
@@ -15376,6 +15376,7 @@ var createVoiceCompetitiveCoverageRoutes = (options) => {
15376
15376
  };
15377
15377
  // src/proofTrends.ts
15378
15378
  import { Elysia as Elysia22 } from "elysia";
15379
+ import { Database } from "bun:sqlite";
15379
15380
  var DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS = 24 * 60 * 60 * 1000;
15380
15381
  var DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS = [
15381
15382
  {
@@ -16096,6 +16097,98 @@ var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
16096
16097
  }
16097
16098
  return uniqueRealCallProfileActions(actions);
16098
16099
  };
16100
+ var createVoiceInMemoryRealCallProfileRecoveryJobStore = (options = {}) => {
16101
+ const jobs = new Map;
16102
+ const now = () => (options.now ?? (() => new Date))().toISOString();
16103
+ const createId3 = () => `${options.idPrefix ?? "voice-recovery-job"}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
16104
+ return {
16105
+ create(input) {
16106
+ const createdAt = input.createdAt ?? now();
16107
+ const job = {
16108
+ actionId: input.actionId,
16109
+ createdAt,
16110
+ id: input.id ?? createId3(),
16111
+ message: input.message,
16112
+ status: input.status ?? "queued",
16113
+ updatedAt: createdAt
16114
+ };
16115
+ jobs.set(job.id, job);
16116
+ return job;
16117
+ },
16118
+ get(id) {
16119
+ return jobs.get(id);
16120
+ },
16121
+ update(id, update) {
16122
+ const existing = jobs.get(id);
16123
+ if (!existing) {
16124
+ return;
16125
+ }
16126
+ const next = {
16127
+ ...existing,
16128
+ ...update,
16129
+ updatedAt: update.updatedAt ?? now()
16130
+ };
16131
+ jobs.set(id, next);
16132
+ return next;
16133
+ }
16134
+ };
16135
+ };
16136
+ var normalizeRealCallRecoveryJobTableName = (value) => value.trim().replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "voice_real_call_profile_recovery_jobs";
16137
+ var createVoiceSQLiteRealCallProfileRecoveryJobStore = (options = {}) => {
16138
+ const database = options.database ?? new Database(options.path ?? ":memory:", {
16139
+ create: true
16140
+ });
16141
+ const tableName = normalizeRealCallRecoveryJobTableName(options.tableName ?? "voice_real_call_profile_recovery_jobs");
16142
+ const now = () => (options.now ?? (() => new Date))().toISOString();
16143
+ const createId3 = () => `${options.idPrefix ?? "voice-recovery-job"}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
16144
+ database.exec("PRAGMA journal_mode = WAL;");
16145
+ database.exec("PRAGMA synchronous = NORMAL;");
16146
+ database.exec("PRAGMA busy_timeout = 5000;");
16147
+ database.exec(`CREATE TABLE IF NOT EXISTS "${tableName}" (
16148
+ id TEXT PRIMARY KEY,
16149
+ sort_at INTEGER NOT NULL,
16150
+ payload TEXT NOT NULL
16151
+ )`);
16152
+ const selectStatement = database.query(`SELECT payload FROM "${tableName}" WHERE id = ?1 LIMIT 1`);
16153
+ const upsertStatement = database.query(`INSERT INTO "${tableName}" (id, sort_at, payload)
16154
+ VALUES (?1, ?2, ?3)
16155
+ ON CONFLICT(id) DO UPDATE SET sort_at = excluded.sort_at, payload = excluded.payload`);
16156
+ const writeJob = (job) => {
16157
+ upsertStatement.run(job.id, Date.parse(job.updatedAt) || Date.now(), JSON.stringify(job));
16158
+ return job;
16159
+ };
16160
+ const readJob = (id) => {
16161
+ const row = selectStatement.get(id);
16162
+ return row ? JSON.parse(row.payload) : undefined;
16163
+ };
16164
+ return {
16165
+ create(input) {
16166
+ const createdAt = input.createdAt ?? now();
16167
+ return writeJob({
16168
+ actionId: input.actionId,
16169
+ createdAt,
16170
+ id: input.id ?? createId3(),
16171
+ message: input.message,
16172
+ status: input.status ?? "queued",
16173
+ updatedAt: createdAt
16174
+ });
16175
+ },
16176
+ get(id) {
16177
+ return readJob(id);
16178
+ },
16179
+ update(id, update) {
16180
+ const existing = readJob(id);
16181
+ if (!existing) {
16182
+ return;
16183
+ }
16184
+ return writeJob({
16185
+ ...existing,
16186
+ ...update,
16187
+ updatedAt: update.updatedAt ?? now()
16188
+ });
16189
+ }
16190
+ };
16191
+ };
16099
16192
  var buildVoiceRealCallProfileReadinessCheck = (report, options = {}) => {
16100
16193
  const { issues, warnings } = buildRealCallProfileReadinessIssues(report, options);
16101
16194
  const status = issues.length > 0 ? "fail" : warnings.length > 0 && options.failOnWarnings === true ? "fail" : warnings.length > 0 ? "warn" : "pass";
@@ -16714,6 +16807,7 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
16714
16807
  });
16715
16808
  const actionPath = (actionId) => `${path}${realCallProfileActionPaths[actionId]}`;
16716
16809
  const loadReport = () => loadVoiceRealCallProfileHistoryRouteReport(options);
16810
+ const asyncActionIds = new Set(options.asyncActionIds ?? []);
16717
16811
  const listActions = async () => {
16718
16812
  const report = await loadReport();
16719
16813
  const actions = buildVoiceRealCallProfileRecoveryActions(report, {
@@ -16729,6 +16823,67 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
16729
16823
  }));
16730
16824
  return { actions, generatedAt: new Date().toISOString(), report };
16731
16825
  };
16826
+ const runActionHandler = async (actionId, report) => {
16827
+ const handler = options.handlers?.[actionId];
16828
+ if (!handler) {
16829
+ return;
16830
+ }
16831
+ return await handler({ actionId, report });
16832
+ };
16833
+ const runActionAsJob = async (actionId, report) => {
16834
+ const job = await options.jobStore?.create({
16835
+ actionId,
16836
+ message: `Queued real-call profile recovery action: ${actionId}.`,
16837
+ status: "queued"
16838
+ });
16839
+ if (!job) {
16840
+ return;
16841
+ }
16842
+ queueMicrotask(() => {
16843
+ (async () => {
16844
+ const startedAt = new Date().toISOString();
16845
+ await options.jobStore?.update(job.id, {
16846
+ message: `Running real-call profile recovery action: ${actionId}.`,
16847
+ startedAt,
16848
+ status: "running",
16849
+ updatedAt: startedAt
16850
+ });
16851
+ try {
16852
+ const result = await runActionHandler(actionId, report);
16853
+ const completedAt = new Date().toISOString();
16854
+ const ok = result?.ok ?? true;
16855
+ const status = result?.jobStatus ?? (result?.status === "fail" || ok === false ? "fail" : "pass");
16856
+ await options.jobStore?.update(job.id, {
16857
+ completedAt,
16858
+ message: result?.message ?? `Completed real-call profile recovery action: ${actionId}.`,
16859
+ ok,
16860
+ report: result?.report,
16861
+ status,
16862
+ updatedAt: completedAt
16863
+ });
16864
+ } catch (error) {
16865
+ const completedAt = new Date().toISOString();
16866
+ await options.jobStore?.update(job.id, {
16867
+ completedAt,
16868
+ message: error instanceof Error ? error.message : `Failed real-call profile recovery action: ${actionId}.`,
16869
+ ok: false,
16870
+ status: "fail",
16871
+ updatedAt: completedAt
16872
+ });
16873
+ }
16874
+ })();
16875
+ });
16876
+ return {
16877
+ actionId,
16878
+ generatedAt: new Date().toISOString(),
16879
+ job,
16880
+ jobId: job.id,
16881
+ jobStatus: job.status,
16882
+ message: job.message,
16883
+ ok: true,
16884
+ status: "pass"
16885
+ };
16886
+ };
16732
16887
  const runAction = async (actionId) => {
16733
16888
  const report = await loadReport();
16734
16889
  const handler = options.handlers?.[actionId];
@@ -16741,10 +16896,19 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
16741
16896
  status: "fail"
16742
16897
  };
16743
16898
  }
16744
- const result = await handler({ actionId, report });
16899
+ if (options.jobStore && asyncActionIds.has(actionId)) {
16900
+ const queued = await runActionAsJob(actionId, report);
16901
+ if (queued) {
16902
+ return queued;
16903
+ }
16904
+ }
16905
+ const result = await runActionHandler(actionId, report);
16745
16906
  return {
16746
16907
  actionId,
16747
16908
  generatedAt: new Date().toISOString(),
16909
+ job: result?.job,
16910
+ jobId: result?.jobId,
16911
+ jobStatus: result?.jobStatus,
16748
16912
  message: result?.message,
16749
16913
  ok: result?.ok ?? true,
16750
16914
  report: result?.report,
@@ -16752,6 +16916,24 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
16752
16916
  };
16753
16917
  };
16754
16918
  routes.get(`${path}/actions`, async () => Response.json(await listActions(), { headers: options.headers }));
16919
+ routes.get(`${path}/actions/:jobId`, async ({
16920
+ params,
16921
+ set
16922
+ }) => {
16923
+ const jobId = params.jobId ?? "";
16924
+ const job = await options.jobStore?.get(jobId);
16925
+ if (!job) {
16926
+ set.status = 404;
16927
+ return Response.json({
16928
+ jobId,
16929
+ message: `No real-call profile recovery job found for id: ${jobId}.`,
16930
+ ok: false
16931
+ }, { headers: options.headers });
16932
+ }
16933
+ return Response.json({ job, ok: job.ok ?? job.status !== "fail" }, {
16934
+ headers: options.headers
16935
+ });
16936
+ });
16755
16937
  for (const actionId of Object.keys(realCallProfileActionPaths)) {
16756
16938
  routes.post(actionPath(actionId), async ({ set }) => {
16757
16939
  const result = await runAction(actionId);
@@ -23437,7 +23619,7 @@ import { Elysia as Elysia45 } from "elysia";
23437
23619
 
23438
23620
  // src/telephony/plivo.ts
23439
23621
  import { Buffer as Buffer5 } from "buffer";
23440
- import { Database } from "bun:sqlite";
23622
+ import { Database as Database2 } from "bun:sqlite";
23441
23623
  import { Elysia as Elysia41 } from "elysia";
23442
23624
 
23443
23625
  // src/telephony/contract.ts
@@ -24564,7 +24746,7 @@ var resolvePlivoNonceTableName = (input) => {
24564
24746
  };
24565
24747
  var getPlivoNonceExpiresAt = (ttlSeconds) => typeof ttlSeconds === "number" && ttlSeconds > 0 ? Date.now() + Math.ceil(ttlSeconds * 1000) : null;
24566
24748
  var createVoiceSQLitePlivoWebhookNonceStore = (options) => {
24567
- const database = options.database ?? new Database(options.path ?? ":memory:", {
24749
+ const database = options.database ?? new Database2(options.path ?? ":memory:", {
24568
24750
  create: true
24569
24751
  });
24570
24752
  const tableName = resolvePlivoNonceTableName({
@@ -24964,7 +25146,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
24964
25146
 
24965
25147
  // src/telephony/telnyx.ts
24966
25148
  import { Buffer as Buffer6 } from "buffer";
24967
- import { Database as Database2 } from "bun:sqlite";
25149
+ import { Database as Database3 } from "bun:sqlite";
24968
25150
  import { Elysia as Elysia42 } from "elysia";
24969
25151
  var escapeXml4 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
24970
25152
  var escapeHtml42 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
@@ -25164,7 +25346,7 @@ var resolveTelnyxEventTableName = (input) => {
25164
25346
  };
25165
25347
  var getTelnyxEventExpiresAt = (ttlSeconds) => typeof ttlSeconds === "number" && ttlSeconds > 0 ? Date.now() + Math.ceil(ttlSeconds * 1000) : null;
25166
25348
  var createVoiceSQLiteTelnyxWebhookEventStore = (options) => {
25167
- const database = options.database ?? new Database2(options.path ?? ":memory:", {
25349
+ const database = options.database ?? new Database3(options.path ?? ":memory:", {
25168
25350
  create: true
25169
25351
  });
25170
25352
  const tableName = resolveTelnyxEventTableName({
@@ -28895,7 +29077,7 @@ var createVoicePostgresRuntimeStorage = (options) => {
28895
29077
  };
28896
29078
 
28897
29079
  // src/sqliteStore.ts
28898
- import { Database as Database3 } from "bun:sqlite";
29080
+ import { Database as Database4 } from "bun:sqlite";
28899
29081
  var normalizeTableNameSegment = (value) => value.trim().replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "voice";
28900
29082
  var resolveTableName = (input) => {
28901
29083
  if (input.options.tableName) {
@@ -28906,7 +29088,7 @@ var resolveTableName = (input) => {
28906
29088
  return `${prefix}_${fallback}`;
28907
29089
  };
28908
29090
  var openVoiceSQLiteDatabase = (path) => {
28909
- const database = new Database3(path, {
29091
+ const database = new Database4(path, {
28910
29092
  create: true
28911
29093
  });
28912
29094
  database.exec("PRAGMA journal_mode = WAL;");
@@ -29685,7 +29867,7 @@ var createVoiceOpsRecoveryRoutes = (options = {}) => {
29685
29867
 
29686
29868
  // src/observabilityExport.ts
29687
29869
  import { Elysia as Elysia53 } from "elysia";
29688
- import { Database as Database4 } from "bun:sqlite";
29870
+ import { Database as Database5 } from "bun:sqlite";
29689
29871
  import { createHash } from "crypto";
29690
29872
  import { mkdir as mkdir4, readFile as readFile2, stat, unlink } from "fs/promises";
29691
29873
  import { join as join3 } from "path";
@@ -31304,7 +31486,7 @@ var loadVoiceObservabilityExportReplaySource = async (source) => {
31304
31486
  if (!source.database && !source.path) {
31305
31487
  throw new Error("SQLite observability export replay requires source.database or source.path.");
31306
31488
  }
31307
- const database = source.database ?? new Database4(source.path, { create: false });
31489
+ const database = source.database ?? new Database5(source.path, { create: false });
31308
31490
  const table2 = quoteObservabilityIdentifier(normalizeObservabilityIdentifier(source.tableName));
31309
31491
  const row2 = database.query(`SELECT manifest_json, artifact_index_json, payload_json FROM ${table2} WHERE run_id = $runId`).get({ $runId: source.runId });
31310
31492
  if (!row2) {
@@ -31381,7 +31563,7 @@ var deliverObservabilityExportToSQLite = async (input) => {
31381
31563
  if (!input.destination.database && !input.destination.path) {
31382
31564
  throw new Error("SQLite observability export delivery requires destination.database or destination.path.");
31383
31565
  }
31384
- const database = input.destination.database ?? new Database4(input.destination.path, { create: true });
31566
+ const database = input.destination.database ?? new Database5(input.destination.path, { create: true });
31385
31567
  const table = quoteObservabilityIdentifier(normalizeObservabilityIdentifier(input.destination.tableName));
31386
31568
  const record = buildObservabilityExportDatabaseRecord(input);
31387
31569
  database.exec(`CREATE TABLE IF NOT EXISTS ${table} (
@@ -38813,6 +38995,7 @@ export {
38813
38995
  createVoiceSQLiteSessionStore,
38814
38996
  createVoiceSQLiteRuntimeStorage,
38815
38997
  createVoiceSQLiteReviewStore,
38998
+ createVoiceSQLiteRealCallProfileRecoveryJobStore,
38816
38999
  createVoiceSQLitePlivoWebhookNonceStore,
38817
39000
  createVoiceSQLiteIntegrationEventStore,
38818
39001
  createVoiceSQLiteExternalObjectMapStore,
@@ -38932,6 +39115,7 @@ export {
38932
39115
  createVoiceIntegrationHTTPSink,
38933
39116
  createVoiceIntegrationEvent,
38934
39117
  createVoiceIncidentBundleRoutes,
39118
+ createVoiceInMemoryRealCallProfileRecoveryJobStore,
38935
39119
  createVoiceHubSpotTaskUpdateSink,
38936
39120
  createVoiceHubSpotTaskSyncSinks,
38937
39121
  createVoiceHubSpotTaskSink,
@@ -1,4 +1,5 @@
1
1
  import { Elysia } from 'elysia';
2
+ import { Database } from 'bun:sqlite';
2
3
  import type { VoiceProductionReadinessAction, VoiceProductionReadinessCheck } from './productionReadiness';
3
4
  import type { StoredVoiceTraceEvent, VoiceTraceEventStore } from './trace';
4
5
  export type VoiceProofTrendStatus = 'empty' | 'fail' | 'pass' | 'stale';
@@ -384,14 +385,52 @@ export type VoiceRealCallProfileRecoveryActionHandlerInput = {
384
385
  export type VoiceRealCallProfileRecoveryActionResult = {
385
386
  actionId: VoiceRealCallProfileRecoveryActionId;
386
387
  generatedAt: string;
388
+ job?: VoiceRealCallProfileRecoveryJob;
389
+ jobId?: string;
390
+ jobStatus?: VoiceRealCallProfileRecoveryJobStatus;
387
391
  message?: string;
388
392
  ok: boolean;
389
393
  report?: VoiceRealCallProfileHistoryReport;
390
394
  status: VoiceProofTrendStatus;
391
395
  };
396
+ export type VoiceRealCallProfileRecoveryJobStatus = 'fail' | 'pass' | 'queued' | 'running';
397
+ export type VoiceRealCallProfileRecoveryJob = {
398
+ actionId: VoiceRealCallProfileRecoveryActionId;
399
+ completedAt?: string;
400
+ createdAt: string;
401
+ id: string;
402
+ message?: string;
403
+ ok?: boolean;
404
+ report?: VoiceRealCallProfileHistoryReport;
405
+ startedAt?: string;
406
+ status: VoiceRealCallProfileRecoveryJobStatus;
407
+ updatedAt: string;
408
+ };
409
+ export type VoiceRealCallProfileRecoveryJobCreateInput = {
410
+ actionId: VoiceRealCallProfileRecoveryActionId;
411
+ createdAt?: string;
412
+ id?: string;
413
+ message?: string;
414
+ status?: VoiceRealCallProfileRecoveryJobStatus;
415
+ };
416
+ export type VoiceRealCallProfileRecoveryJobUpdate = Partial<Pick<VoiceRealCallProfileRecoveryJob, 'completedAt' | 'message' | 'ok' | 'report' | 'startedAt' | 'status' | 'updatedAt'>>;
417
+ export type VoiceRealCallProfileRecoveryJobStore = {
418
+ create(input: VoiceRealCallProfileRecoveryJobCreateInput): Promise<VoiceRealCallProfileRecoveryJob> | VoiceRealCallProfileRecoveryJob;
419
+ get(id: string): Promise<VoiceRealCallProfileRecoveryJob | undefined> | VoiceRealCallProfileRecoveryJob | undefined;
420
+ update(id: string, update: VoiceRealCallProfileRecoveryJobUpdate): Promise<VoiceRealCallProfileRecoveryJob | undefined> | VoiceRealCallProfileRecoveryJob | undefined;
421
+ };
422
+ export type VoiceSQLiteRealCallProfileRecoveryJobStoreOptions = {
423
+ database?: Database;
424
+ idPrefix?: string;
425
+ now?: () => Date;
426
+ path?: string;
427
+ tableName?: string;
428
+ };
392
429
  export type VoiceRealCallProfileRecoveryActionHandler = (input: VoiceRealCallProfileRecoveryActionHandlerInput) => Promise<Partial<VoiceRealCallProfileRecoveryActionResult> | void> | Partial<VoiceRealCallProfileRecoveryActionResult> | void;
393
430
  export type VoiceRealCallProfileRecoveryActionRoutesOptions = VoiceRealCallProfileRecoveryActionOptions & Omit<VoiceRealCallProfileHistoryRoutesOptions, 'htmlPath' | 'markdownPath'> & {
431
+ asyncActionIds?: readonly VoiceRealCallProfileRecoveryActionId[];
394
432
  handlers?: Partial<Record<VoiceRealCallProfileRecoveryActionId, VoiceRealCallProfileRecoveryActionHandler>>;
433
+ jobStore?: VoiceRealCallProfileRecoveryJobStore;
395
434
  };
396
435
  export declare const DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS: number;
397
436
  export declare const DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS: ({
@@ -427,6 +466,11 @@ export declare const buildVoiceProofTrendProfileSummaries: (input: VoiceProofTre
427
466
  export declare const buildVoiceProofTrendReportFromRealCallProfiles: (options: VoiceProofTrendRealCallProfileReportOptions) => VoiceProofTrendReport;
428
467
  export declare const buildVoiceRealCallProfileDefaults: (input: VoiceRealCallProfileHistoryReport | VoiceProofTrendReport, options?: VoiceRealCallProfileDefaultsOptions) => VoiceRealCallProfileDefaultsReport;
429
468
  export declare const buildVoiceRealCallProfileRecoveryActions: (report: VoiceRealCallProfileHistoryReport, options?: VoiceRealCallProfileRecoveryActionOptions) => VoiceRealCallProfileRecoveryAction[];
469
+ export declare const createVoiceInMemoryRealCallProfileRecoveryJobStore: (options?: {
470
+ idPrefix?: string;
471
+ now?: () => Date;
472
+ }) => VoiceRealCallProfileRecoveryJobStore;
473
+ export declare const createVoiceSQLiteRealCallProfileRecoveryJobStore: (options?: VoiceSQLiteRealCallProfileRecoveryJobStoreOptions) => VoiceRealCallProfileRecoveryJobStore;
430
474
  export declare const buildVoiceRealCallProfileReadinessCheck: (report: VoiceRealCallProfileHistoryReport, options?: VoiceRealCallProfileReadinessCheckOptions) => VoiceProductionReadinessCheck;
431
475
  export declare const resolveVoiceRealCallProfileProviderRoute: <TProvider extends string = string>(options: VoiceRealCallProfileProviderRouteOptions<TProvider>) => TProvider | undefined;
432
476
  export declare const buildVoiceRealCallProfileHistoryReport: (options?: VoiceRealCallProfileHistoryOptions) => VoiceRealCallProfileHistoryReport;
@@ -1494,6 +1494,7 @@ var useVoiceProofTrends = (path = "/api/voice/proof-trends", options = {}) => {
1494
1494
 
1495
1495
  // src/proofTrends.ts
1496
1496
  import { Elysia } from "elysia";
1497
+ import { Database } from "bun:sqlite";
1497
1498
  var DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS = 24 * 60 * 60 * 1000;
1498
1499
  var DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS = [
1499
1500
  {
@@ -2214,6 +2215,98 @@ var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
2214
2215
  }
2215
2216
  return uniqueRealCallProfileActions(actions);
2216
2217
  };
2218
+ var createVoiceInMemoryRealCallProfileRecoveryJobStore = (options = {}) => {
2219
+ const jobs = new Map;
2220
+ const now = () => (options.now ?? (() => new Date))().toISOString();
2221
+ const createId = () => `${options.idPrefix ?? "voice-recovery-job"}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
2222
+ return {
2223
+ create(input) {
2224
+ const createdAt = input.createdAt ?? now();
2225
+ const job = {
2226
+ actionId: input.actionId,
2227
+ createdAt,
2228
+ id: input.id ?? createId(),
2229
+ message: input.message,
2230
+ status: input.status ?? "queued",
2231
+ updatedAt: createdAt
2232
+ };
2233
+ jobs.set(job.id, job);
2234
+ return job;
2235
+ },
2236
+ get(id) {
2237
+ return jobs.get(id);
2238
+ },
2239
+ update(id, update) {
2240
+ const existing = jobs.get(id);
2241
+ if (!existing) {
2242
+ return;
2243
+ }
2244
+ const next = {
2245
+ ...existing,
2246
+ ...update,
2247
+ updatedAt: update.updatedAt ?? now()
2248
+ };
2249
+ jobs.set(id, next);
2250
+ return next;
2251
+ }
2252
+ };
2253
+ };
2254
+ var normalizeRealCallRecoveryJobTableName = (value) => value.trim().replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "voice_real_call_profile_recovery_jobs";
2255
+ var createVoiceSQLiteRealCallProfileRecoveryJobStore = (options = {}) => {
2256
+ const database = options.database ?? new Database(options.path ?? ":memory:", {
2257
+ create: true
2258
+ });
2259
+ const tableName = normalizeRealCallRecoveryJobTableName(options.tableName ?? "voice_real_call_profile_recovery_jobs");
2260
+ const now = () => (options.now ?? (() => new Date))().toISOString();
2261
+ const createId = () => `${options.idPrefix ?? "voice-recovery-job"}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
2262
+ database.exec("PRAGMA journal_mode = WAL;");
2263
+ database.exec("PRAGMA synchronous = NORMAL;");
2264
+ database.exec("PRAGMA busy_timeout = 5000;");
2265
+ database.exec(`CREATE TABLE IF NOT EXISTS "${tableName}" (
2266
+ id TEXT PRIMARY KEY,
2267
+ sort_at INTEGER NOT NULL,
2268
+ payload TEXT NOT NULL
2269
+ )`);
2270
+ const selectStatement = database.query(`SELECT payload FROM "${tableName}" WHERE id = ?1 LIMIT 1`);
2271
+ const upsertStatement = database.query(`INSERT INTO "${tableName}" (id, sort_at, payload)
2272
+ VALUES (?1, ?2, ?3)
2273
+ ON CONFLICT(id) DO UPDATE SET sort_at = excluded.sort_at, payload = excluded.payload`);
2274
+ const writeJob = (job) => {
2275
+ upsertStatement.run(job.id, Date.parse(job.updatedAt) || Date.now(), JSON.stringify(job));
2276
+ return job;
2277
+ };
2278
+ const readJob = (id) => {
2279
+ const row = selectStatement.get(id);
2280
+ return row ? JSON.parse(row.payload) : undefined;
2281
+ };
2282
+ return {
2283
+ create(input) {
2284
+ const createdAt = input.createdAt ?? now();
2285
+ return writeJob({
2286
+ actionId: input.actionId,
2287
+ createdAt,
2288
+ id: input.id ?? createId(),
2289
+ message: input.message,
2290
+ status: input.status ?? "queued",
2291
+ updatedAt: createdAt
2292
+ });
2293
+ },
2294
+ get(id) {
2295
+ return readJob(id);
2296
+ },
2297
+ update(id, update) {
2298
+ const existing = readJob(id);
2299
+ if (!existing) {
2300
+ return;
2301
+ }
2302
+ return writeJob({
2303
+ ...existing,
2304
+ ...update,
2305
+ updatedAt: update.updatedAt ?? now()
2306
+ });
2307
+ }
2308
+ };
2309
+ };
2217
2310
  var buildVoiceRealCallProfileReadinessCheck = (report, options = {}) => {
2218
2311
  const { issues, warnings } = buildRealCallProfileReadinessIssues(report, options);
2219
2312
  const status = issues.length > 0 ? "fail" : warnings.length > 0 && options.failOnWarnings === true ? "fail" : warnings.length > 0 ? "warn" : "pass";
@@ -2832,6 +2925,7 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
2832
2925
  });
2833
2926
  const actionPath = (actionId) => `${path}${realCallProfileActionPaths[actionId]}`;
2834
2927
  const loadReport = () => loadVoiceRealCallProfileHistoryRouteReport(options);
2928
+ const asyncActionIds = new Set(options.asyncActionIds ?? []);
2835
2929
  const listActions = async () => {
2836
2930
  const report = await loadReport();
2837
2931
  const actions = buildVoiceRealCallProfileRecoveryActions(report, {
@@ -2847,6 +2941,67 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
2847
2941
  }));
2848
2942
  return { actions, generatedAt: new Date().toISOString(), report };
2849
2943
  };
2944
+ const runActionHandler = async (actionId, report) => {
2945
+ const handler = options.handlers?.[actionId];
2946
+ if (!handler) {
2947
+ return;
2948
+ }
2949
+ return await handler({ actionId, report });
2950
+ };
2951
+ const runActionAsJob = async (actionId, report) => {
2952
+ const job = await options.jobStore?.create({
2953
+ actionId,
2954
+ message: `Queued real-call profile recovery action: ${actionId}.`,
2955
+ status: "queued"
2956
+ });
2957
+ if (!job) {
2958
+ return;
2959
+ }
2960
+ queueMicrotask(() => {
2961
+ (async () => {
2962
+ const startedAt = new Date().toISOString();
2963
+ await options.jobStore?.update(job.id, {
2964
+ message: `Running real-call profile recovery action: ${actionId}.`,
2965
+ startedAt,
2966
+ status: "running",
2967
+ updatedAt: startedAt
2968
+ });
2969
+ try {
2970
+ const result = await runActionHandler(actionId, report);
2971
+ const completedAt = new Date().toISOString();
2972
+ const ok = result?.ok ?? true;
2973
+ const status = result?.jobStatus ?? (result?.status === "fail" || ok === false ? "fail" : "pass");
2974
+ await options.jobStore?.update(job.id, {
2975
+ completedAt,
2976
+ message: result?.message ?? `Completed real-call profile recovery action: ${actionId}.`,
2977
+ ok,
2978
+ report: result?.report,
2979
+ status,
2980
+ updatedAt: completedAt
2981
+ });
2982
+ } catch (error) {
2983
+ const completedAt = new Date().toISOString();
2984
+ await options.jobStore?.update(job.id, {
2985
+ completedAt,
2986
+ message: error instanceof Error ? error.message : `Failed real-call profile recovery action: ${actionId}.`,
2987
+ ok: false,
2988
+ status: "fail",
2989
+ updatedAt: completedAt
2990
+ });
2991
+ }
2992
+ })();
2993
+ });
2994
+ return {
2995
+ actionId,
2996
+ generatedAt: new Date().toISOString(),
2997
+ job,
2998
+ jobId: job.id,
2999
+ jobStatus: job.status,
3000
+ message: job.message,
3001
+ ok: true,
3002
+ status: "pass"
3003
+ };
3004
+ };
2850
3005
  const runAction = async (actionId) => {
2851
3006
  const report = await loadReport();
2852
3007
  const handler = options.handlers?.[actionId];
@@ -2859,10 +3014,19 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
2859
3014
  status: "fail"
2860
3015
  };
2861
3016
  }
2862
- const result = await handler({ actionId, report });
3017
+ if (options.jobStore && asyncActionIds.has(actionId)) {
3018
+ const queued = await runActionAsJob(actionId, report);
3019
+ if (queued) {
3020
+ return queued;
3021
+ }
3022
+ }
3023
+ const result = await runActionHandler(actionId, report);
2863
3024
  return {
2864
3025
  actionId,
2865
3026
  generatedAt: new Date().toISOString(),
3027
+ job: result?.job,
3028
+ jobId: result?.jobId,
3029
+ jobStatus: result?.jobStatus,
2866
3030
  message: result?.message,
2867
3031
  ok: result?.ok ?? true,
2868
3032
  report: result?.report,
@@ -2870,6 +3034,24 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
2870
3034
  };
2871
3035
  };
2872
3036
  routes.get(`${path}/actions`, async () => Response.json(await listActions(), { headers: options.headers }));
3037
+ routes.get(`${path}/actions/:jobId`, async ({
3038
+ params,
3039
+ set
3040
+ }) => {
3041
+ const jobId = params.jobId ?? "";
3042
+ const job = await options.jobStore?.get(jobId);
3043
+ if (!job) {
3044
+ set.status = 404;
3045
+ return Response.json({
3046
+ jobId,
3047
+ message: `No real-call profile recovery job found for id: ${jobId}.`,
3048
+ ok: false
3049
+ }, { headers: options.headers });
3050
+ }
3051
+ return Response.json({ job, ok: job.ok ?? job.status !== "fail" }, {
3052
+ headers: options.headers
3053
+ });
3054
+ });
2873
3055
  for (const actionId of Object.keys(realCallProfileActionPaths)) {
2874
3056
  routes.post(actionPath(actionId), async ({ set }) => {
2875
3057
  const result = await runAction(actionId);
package/dist/vue/index.js CHANGED
@@ -1415,6 +1415,7 @@ import { defineComponent as defineComponent5, h as h5 } from "vue";
1415
1415
 
1416
1416
  // src/proofTrends.ts
1417
1417
  import { Elysia } from "elysia";
1418
+ import { Database } from "bun:sqlite";
1418
1419
  var DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS = 24 * 60 * 60 * 1000;
1419
1420
  var DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS = [
1420
1421
  {
@@ -2135,6 +2136,98 @@ var buildVoiceRealCallProfileRecoveryActions = (report, options = {}) => {
2135
2136
  }
2136
2137
  return uniqueRealCallProfileActions(actions);
2137
2138
  };
2139
+ var createVoiceInMemoryRealCallProfileRecoveryJobStore = (options = {}) => {
2140
+ const jobs = new Map;
2141
+ const now = () => (options.now ?? (() => new Date))().toISOString();
2142
+ const createId = () => `${options.idPrefix ?? "voice-recovery-job"}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
2143
+ return {
2144
+ create(input) {
2145
+ const createdAt = input.createdAt ?? now();
2146
+ const job = {
2147
+ actionId: input.actionId,
2148
+ createdAt,
2149
+ id: input.id ?? createId(),
2150
+ message: input.message,
2151
+ status: input.status ?? "queued",
2152
+ updatedAt: createdAt
2153
+ };
2154
+ jobs.set(job.id, job);
2155
+ return job;
2156
+ },
2157
+ get(id) {
2158
+ return jobs.get(id);
2159
+ },
2160
+ update(id, update) {
2161
+ const existing = jobs.get(id);
2162
+ if (!existing) {
2163
+ return;
2164
+ }
2165
+ const next = {
2166
+ ...existing,
2167
+ ...update,
2168
+ updatedAt: update.updatedAt ?? now()
2169
+ };
2170
+ jobs.set(id, next);
2171
+ return next;
2172
+ }
2173
+ };
2174
+ };
2175
+ var normalizeRealCallRecoveryJobTableName = (value) => value.trim().replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "voice_real_call_profile_recovery_jobs";
2176
+ var createVoiceSQLiteRealCallProfileRecoveryJobStore = (options = {}) => {
2177
+ const database = options.database ?? new Database(options.path ?? ":memory:", {
2178
+ create: true
2179
+ });
2180
+ const tableName = normalizeRealCallRecoveryJobTableName(options.tableName ?? "voice_real_call_profile_recovery_jobs");
2181
+ const now = () => (options.now ?? (() => new Date))().toISOString();
2182
+ const createId = () => `${options.idPrefix ?? "voice-recovery-job"}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
2183
+ database.exec("PRAGMA journal_mode = WAL;");
2184
+ database.exec("PRAGMA synchronous = NORMAL;");
2185
+ database.exec("PRAGMA busy_timeout = 5000;");
2186
+ database.exec(`CREATE TABLE IF NOT EXISTS "${tableName}" (
2187
+ id TEXT PRIMARY KEY,
2188
+ sort_at INTEGER NOT NULL,
2189
+ payload TEXT NOT NULL
2190
+ )`);
2191
+ const selectStatement = database.query(`SELECT payload FROM "${tableName}" WHERE id = ?1 LIMIT 1`);
2192
+ const upsertStatement = database.query(`INSERT INTO "${tableName}" (id, sort_at, payload)
2193
+ VALUES (?1, ?2, ?3)
2194
+ ON CONFLICT(id) DO UPDATE SET sort_at = excluded.sort_at, payload = excluded.payload`);
2195
+ const writeJob = (job) => {
2196
+ upsertStatement.run(job.id, Date.parse(job.updatedAt) || Date.now(), JSON.stringify(job));
2197
+ return job;
2198
+ };
2199
+ const readJob = (id) => {
2200
+ const row = selectStatement.get(id);
2201
+ return row ? JSON.parse(row.payload) : undefined;
2202
+ };
2203
+ return {
2204
+ create(input) {
2205
+ const createdAt = input.createdAt ?? now();
2206
+ return writeJob({
2207
+ actionId: input.actionId,
2208
+ createdAt,
2209
+ id: input.id ?? createId(),
2210
+ message: input.message,
2211
+ status: input.status ?? "queued",
2212
+ updatedAt: createdAt
2213
+ });
2214
+ },
2215
+ get(id) {
2216
+ return readJob(id);
2217
+ },
2218
+ update(id, update) {
2219
+ const existing = readJob(id);
2220
+ if (!existing) {
2221
+ return;
2222
+ }
2223
+ return writeJob({
2224
+ ...existing,
2225
+ ...update,
2226
+ updatedAt: update.updatedAt ?? now()
2227
+ });
2228
+ }
2229
+ };
2230
+ };
2138
2231
  var buildVoiceRealCallProfileReadinessCheck = (report, options = {}) => {
2139
2232
  const { issues, warnings } = buildRealCallProfileReadinessIssues(report, options);
2140
2233
  const status = issues.length > 0 ? "fail" : warnings.length > 0 && options.failOnWarnings === true ? "fail" : warnings.length > 0 ? "warn" : "pass";
@@ -2753,6 +2846,7 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
2753
2846
  });
2754
2847
  const actionPath = (actionId) => `${path}${realCallProfileActionPaths[actionId]}`;
2755
2848
  const loadReport = () => loadVoiceRealCallProfileHistoryRouteReport(options);
2849
+ const asyncActionIds = new Set(options.asyncActionIds ?? []);
2756
2850
  const listActions = async () => {
2757
2851
  const report = await loadReport();
2758
2852
  const actions = buildVoiceRealCallProfileRecoveryActions(report, {
@@ -2768,6 +2862,67 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
2768
2862
  }));
2769
2863
  return { actions, generatedAt: new Date().toISOString(), report };
2770
2864
  };
2865
+ const runActionHandler = async (actionId, report) => {
2866
+ const handler = options.handlers?.[actionId];
2867
+ if (!handler) {
2868
+ return;
2869
+ }
2870
+ return await handler({ actionId, report });
2871
+ };
2872
+ const runActionAsJob = async (actionId, report) => {
2873
+ const job = await options.jobStore?.create({
2874
+ actionId,
2875
+ message: `Queued real-call profile recovery action: ${actionId}.`,
2876
+ status: "queued"
2877
+ });
2878
+ if (!job) {
2879
+ return;
2880
+ }
2881
+ queueMicrotask(() => {
2882
+ (async () => {
2883
+ const startedAt = new Date().toISOString();
2884
+ await options.jobStore?.update(job.id, {
2885
+ message: `Running real-call profile recovery action: ${actionId}.`,
2886
+ startedAt,
2887
+ status: "running",
2888
+ updatedAt: startedAt
2889
+ });
2890
+ try {
2891
+ const result = await runActionHandler(actionId, report);
2892
+ const completedAt = new Date().toISOString();
2893
+ const ok = result?.ok ?? true;
2894
+ const status = result?.jobStatus ?? (result?.status === "fail" || ok === false ? "fail" : "pass");
2895
+ await options.jobStore?.update(job.id, {
2896
+ completedAt,
2897
+ message: result?.message ?? `Completed real-call profile recovery action: ${actionId}.`,
2898
+ ok,
2899
+ report: result?.report,
2900
+ status,
2901
+ updatedAt: completedAt
2902
+ });
2903
+ } catch (error) {
2904
+ const completedAt = new Date().toISOString();
2905
+ await options.jobStore?.update(job.id, {
2906
+ completedAt,
2907
+ message: error instanceof Error ? error.message : `Failed real-call profile recovery action: ${actionId}.`,
2908
+ ok: false,
2909
+ status: "fail",
2910
+ updatedAt: completedAt
2911
+ });
2912
+ }
2913
+ })();
2914
+ });
2915
+ return {
2916
+ actionId,
2917
+ generatedAt: new Date().toISOString(),
2918
+ job,
2919
+ jobId: job.id,
2920
+ jobStatus: job.status,
2921
+ message: job.message,
2922
+ ok: true,
2923
+ status: "pass"
2924
+ };
2925
+ };
2771
2926
  const runAction = async (actionId) => {
2772
2927
  const report = await loadReport();
2773
2928
  const handler = options.handlers?.[actionId];
@@ -2780,10 +2935,19 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
2780
2935
  status: "fail"
2781
2936
  };
2782
2937
  }
2783
- const result = await handler({ actionId, report });
2938
+ if (options.jobStore && asyncActionIds.has(actionId)) {
2939
+ const queued = await runActionAsJob(actionId, report);
2940
+ if (queued) {
2941
+ return queued;
2942
+ }
2943
+ }
2944
+ const result = await runActionHandler(actionId, report);
2784
2945
  return {
2785
2946
  actionId,
2786
2947
  generatedAt: new Date().toISOString(),
2948
+ job: result?.job,
2949
+ jobId: result?.jobId,
2950
+ jobStatus: result?.jobStatus,
2787
2951
  message: result?.message,
2788
2952
  ok: result?.ok ?? true,
2789
2953
  report: result?.report,
@@ -2791,6 +2955,24 @@ var createVoiceRealCallProfileRecoveryActionRoutes = (options = {}) => {
2791
2955
  };
2792
2956
  };
2793
2957
  routes.get(`${path}/actions`, async () => Response.json(await listActions(), { headers: options.headers }));
2958
+ routes.get(`${path}/actions/:jobId`, async ({
2959
+ params,
2960
+ set
2961
+ }) => {
2962
+ const jobId = params.jobId ?? "";
2963
+ const job = await options.jobStore?.get(jobId);
2964
+ if (!job) {
2965
+ set.status = 404;
2966
+ return Response.json({
2967
+ jobId,
2968
+ message: `No real-call profile recovery job found for id: ${jobId}.`,
2969
+ ok: false
2970
+ }, { headers: options.headers });
2971
+ }
2972
+ return Response.json({ job, ok: job.ok ?? job.status !== "fail" }, {
2973
+ headers: options.headers
2974
+ });
2975
+ });
2794
2976
  for (const actionId of Object.keys(realCallProfileActionPaths)) {
2795
2977
  routes.post(actionPath(actionId), async ({ set }) => {
2796
2978
  const result = await runAction(actionId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.372",
3
+ "version": "0.0.22-beta.374",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",