@volter/twin-twilio 0.1.1 → 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({
@@ -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.1",
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.1"
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.1",
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 }> {
@@ -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
  }