diff --git a/.changeset/common-glasses-sit.md b/.changeset/common-glasses-sit.md deleted file mode 100644 index 93e1e76..0000000 --- a/.changeset/common-glasses-sit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@scrawn/analytics": patch ---- - -fix: allow mode filtering diff --git a/.changeset/ready-horses-punch.md b/.changeset/ready-horses-punch.md deleted file mode 100644 index 7e880cf..0000000 --- a/.changeset/ready-horses-punch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@scrawn/core": patch ---- - -feat: pass in httpurl and webhook public key to constructor diff --git a/.changeset/spotty-plums-enter.md b/.changeset/spotty-plums-enter.md new file mode 100644 index 0000000..6fd32b0 --- /dev/null +++ b/.changeset/spotty-plums-enter.md @@ -0,0 +1,5 @@ +--- +"@scrawn/core": patch +--- + +feat: reported timestamp diff --git a/packages/scrawn/src/core/scrawn.ts b/packages/scrawn/src/core/scrawn.ts index d1ca92c..12a75e4 100644 --- a/packages/scrawn/src/core/scrawn.ts +++ b/packages/scrawn/src/core/scrawn.ts @@ -475,6 +475,7 @@ export class Scrawn< userId: validationResult.data.userId, debit, metadata: validationResult.data.metadata, + reportedTimestamp: validationResult.data.reportedTimestamp, }; const attempt = () => @@ -636,6 +637,7 @@ export class Scrawn< userId: extractedPayload.userId, debit: extractedPayload.debit, metadata: extractedPayload.metadata, + reportedTimestamp: extractedPayload.reportedTimestamp, }; const validationResult = EventPayloadSchema.safeParse(rawPayload); if (!validationResult.success) { @@ -661,6 +663,7 @@ export class Scrawn< userId: validationResult.data.userId, debit, metadata: validationResult.data.metadata, + reportedTimestamp: validationResult.data.reportedTimestamp, }; this.consumeEvent( @@ -763,6 +766,7 @@ export class Scrawn< userId: string; debit: NormalizedDebit; metadata?: Record; + reportedTimestamp?: number; }, authMethodName: K, eventType: "RAW" | "MIDDLEWARE_CALL", @@ -792,6 +796,10 @@ export class Scrawn< // Build debit field — already normalized by caller const debitField = payload.debit; + // Resolve timestamp once — stable across retries + const resolvedTimestamp = + payload.reportedTimestamp ?? Math.floor(Date.now() / 1000); + // Retry loop for retryable failures for (let attempt = 0; ; attempt++) { try { @@ -811,7 +819,7 @@ export class Scrawn< const request = { type: EventType.BASIC_USAGE, userId: payload.userId, - reportedTimestamp: 0, + reportedTimestamp: resolvedTimestamp, eventId, idempotencyKey, basicUsage, @@ -1153,7 +1161,8 @@ export class Scrawn< const request = { type: EventType.AI_TOKEN_USAGE, userId: validated.userId, - reportedTimestamp: 0, + reportedTimestamp: + validated.reportedTimestamp ?? Math.floor(Date.now() / 1000), eventId, idempotencyKey, aiTokenUsage, diff --git a/packages/scrawn/src/core/types/event.ts b/packages/scrawn/src/core/types/event.ts index 64fca62..c32fc68 100644 --- a/packages/scrawn/src/core/types/event.ts +++ b/packages/scrawn/src/core/types/event.ts @@ -98,6 +98,7 @@ export const EventPayloadSchema = z.object({ userId: z.string().min(1, "userId must be a non-empty string"), debit: DebitSchemaNoTokens, metadata: z.record(z.string(), z.unknown()).optional(), + reportedTimestamp: z.number().int().nonnegative().optional(), }); /** @@ -160,6 +161,7 @@ export type EventPayload = { userId: string; debit: Debit; metadata?: Record; + reportedTimestamp?: number; }; /** @@ -348,6 +350,7 @@ export const AITokenUsagePayloadSchema = z.object({ .nonnegative("outputCacheTokens must be non-negative") .optional(), outputCacheDebit: DebitSchemaWithTokens.optional(), + reportedTimestamp: z.number().int().nonnegative().optional(), }); /** @@ -425,4 +428,6 @@ export type AITokenUsagePayload = { outputCacheTokens?: number; /** Debit pricing for cached output tokens. */ outputCacheDebit?: Debit; + /** Unix timestamp (seconds) when this event was created. Auto-set if omitted. */ + reportedTimestamp?: number; };