@volter/twin-twilio 0.1.0 → 0.1.2

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.
@@ -24,12 +24,14 @@
24
24
  // a genuinely empty response. The server is one line of Bun.serve around that same closure.
25
25
  import { serveHttp } from '@volter/world-core';
26
26
  import { handleTwilioTwinRequest } from "./twilio-twin.js";
27
- import { createTwinFetchFromHandler, statefulTwinManifest } from '@volter/world-core';
27
+ import { createTwinFetchFromHandler, statefulTwinManifest, withRequestScopes } from '@volter/world-core';
28
28
  export function createTwilioTwinFetch(options = {}) {
29
- return createTwinFetchFromHandler(handleTwilioTwinRequest, {
29
+ // a read-only request (x-volter-read-only) is a request to a read-only twin: a virgin World is not
30
+ // seeded with the presented credentials, nothing is sent (withRequestScopes: the pack opts in)
31
+ return withRequestScopes(createTwinFetchFromHandler(handleTwilioTwinRequest, {
30
32
  ...options,
31
33
  manifest: statefulTwinManifest({ vendor: 'twilio', twinOf: 'the Twilio messaging API', stores: 'sent messages progressing queued→sending→sent→delivered, with signed status callbacks' }),
32
- });
34
+ }));
33
35
  }
34
36
  export async function createTwilioTwinServer(options = {}) {
35
37
  const server = await serveHttp({
@@ -88,7 +88,7 @@
88
88
  // `phone_number:<sid>`) — the PUBLIC sid emitted to clients is the bare Twilio-shaped sid
89
89
  // (SM…/VE…/PN…). `kid()` builds the subject id; `rows()` strips the prefix back off.
90
90
  import { createHash } from 'node:crypto';
91
- import { applyTwinWrite, projectResources } from '@volter/world-core';
91
+ import { applyTwinWrite, projectResources, worldNow } from '@volter/world-core';
92
92
  import { presentedTwilioCredentials, storedTwilioCredentials, twilioCredentials } from "./twilio-credentials.js";
93
93
  const SERVICE = 'twilio';
94
94
  const API_VERSION = '2010-04-01';
@@ -150,7 +150,7 @@ function parseFormBody(body) {
150
150
  return { ok: true, value };
151
151
  }
152
152
  function nowIso(occurredAt) {
153
- return occurredAt ?? new Date().toISOString();
153
+ return occurredAt ?? worldNow();
154
154
  }
155
155
  /** Twilio's own message/account timestamp wire format (RFC 2822-ish, e.g. "Fri, 24 May 2019
156
156
  * 17:44:46 +0000") — grounded from the twilio_api_v2010.json Messages-list example AND the
@@ -307,7 +307,10 @@ async function handleMessageGet(accountSid, sid, req) {
307
307
  const existing = getRow('message', sid, req.root);
308
308
  if (!existing)
309
309
  return notFound();
310
- await progressMessageOnce(sid, req);
310
+ // ADVANCE ON POLL — a read-only twin (a World's read token) answers the message's current status and
311
+ // never advances it: the app's next poll sees exactly what it would have without the viewer.
312
+ if (!req.readOnly)
313
+ await progressMessageOnce(sid, req);
311
314
  const row = getRow('message', sid, req.root);
312
315
  return { status: 200, body: { ...sidView(row), ...messageUrls(accountSid, sid) } };
313
316
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-twilio",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Local Twilio twin for the messaging/telephony surface (Messages send + delivery-status lifecycle, Verify start/check, Lookup, IncomingPhoneNumbers, X-Twilio-Signature-signed webhooks) built on @volter/world-core.",
5
5
  "author": "Volter (https://github.com/volter-ai)",
6
6
  "license": "Apache-2.0",
@@ -35,12 +35,12 @@
35
35
  "postpack": "node ../../../scripts/publish/prepare-publish.mjs postpack"
36
36
  },
37
37
  "peerDependencies": {
38
- "@volter/world-core": "2.0.0"
38
+ "@volter/world-core": "2.0.2"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/bun": "^1.2.20",
42
42
  "@types/node": "^24.0.0",
43
- "@volter/world-core": "2.0.0",
43
+ "@volter/world-core": "2.0.2",
44
44
  "@volter/world-tooling": "0.1.0",
45
45
  "twilio": "6.0.2",
46
46
  "typescript": "^5.9.0"
@@ -24,7 +24,7 @@
24
24
  // a genuinely empty response. The server is one line of Bun.serve around that same closure.
25
25
  import { serveHttp } from '@volter/world-core';
26
26
  import { handleTwilioTwinRequest } from './twilio-twin.ts';
27
- import { createTwinFetchFromHandler, statefulTwinManifest } from '@volter/world-core';
27
+ import { createTwinFetchFromHandler, statefulTwinManifest, withRequestScopes } from '@volter/world-core';
28
28
 
29
29
  /** Options every Twilio-twin HTTP surface needs, independent of who owns the socket. */
30
30
  export interface TwilioTwinFetchOptions {
@@ -33,10 +33,12 @@ export interface TwilioTwinFetchOptions {
33
33
  }
34
34
 
35
35
  export function createTwilioTwinFetch(options: TwilioTwinFetchOptions = {}): (request: Request) => Promise<Response> {
36
- return createTwinFetchFromHandler(handleTwilioTwinRequest, {
36
+ // a read-only request (x-volter-read-only) is a request to a read-only twin: a virgin World is not
37
+ // seeded with the presented credentials, nothing is sent (withRequestScopes: the pack opts in)
38
+ return withRequestScopes(createTwinFetchFromHandler(handleTwilioTwinRequest, {
37
39
  ...options,
38
40
  manifest: statefulTwinManifest({ vendor: 'twilio', twinOf: 'the Twilio messaging API', stores: 'sent messages progressing queued→sending→sent→delivered, with signed status callbacks' }),
39
- });
41
+ }));
40
42
  }
41
43
 
42
44
  export async function createTwilioTwinServer(options: { root?: string; port?: number; readOnly?: boolean } = {}): Promise<{ port: number; stop: () => void }> {
@@ -88,7 +88,7 @@
88
88
  // `phone_number:<sid>`) — the PUBLIC sid emitted to clients is the bare Twilio-shaped sid
89
89
  // (SM…/VE…/PN…). `kid()` builds the subject id; `rows()` strips the prefix back off.
90
90
  import { createHash } from 'node:crypto';
91
- import { applyTwinWrite, projectResources } from '@volter/world-core';
91
+ import { applyTwinWrite, projectResources, worldNow } from '@volter/world-core';
92
92
  import { presentedTwilioCredentials, storedTwilioCredentials, twilioCredentials } from './twilio-credentials.ts';
93
93
 
94
94
  const SERVICE = 'twilio';
@@ -175,7 +175,7 @@ function parseFormBody(body?: string): { ok: true; value: Record<string, string>
175
175
  }
176
176
 
177
177
  function nowIso(occurredAt?: string): string {
178
- return occurredAt ?? new Date().toISOString();
178
+ return occurredAt ?? worldNow();
179
179
  }
180
180
  /** Twilio's own message/account timestamp wire format (RFC 2822-ish, e.g. "Fri, 24 May 2019
181
181
  * 17:44:46 +0000") — grounded from the twilio_api_v2010.json Messages-list example AND the
@@ -345,7 +345,9 @@ async function progressMessageOnce(sid: string, req: TwilioRequest): Promise<voi
345
345
  async function handleMessageGet(accountSid: string, sid: string, req: TwilioRequest): Promise<TwilioResponse> {
346
346
  const existing = getRow('message', sid, req.root);
347
347
  if (!existing) return notFound();
348
- await progressMessageOnce(sid, req);
348
+ // ADVANCE ON POLL — a read-only twin (a World's read token) answers the message's current status and
349
+ // never advances it: the app's next poll sees exactly what it would have without the viewer.
350
+ if (!req.readOnly) await progressMessageOnce(sid, req);
349
351
  const row = getRow('message', sid, req.root)!;
350
352
  return { status: 200, body: { ...sidView(row), ...messageUrls(accountSid, sid) } };
351
353
  }