Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ bun add @scrawn/core
## Quick Example

```typescript
import { Scrawn } from "@scrawn/core";
import { scrawn } from "@scrawn/core";
import * as grpc from "@grpc/grpc-js";

const scrawn = new Scrawn({
const biller = scrawn({
apiKey: process.env.SCRAWN_KEY as `scrn_${string}`,
baseURL: process.env.SCRAWN_BASE_URL || "http://localhost:8069",
// secure: false, // optional: allow insecure connections for local dev
// credentials: grpc.credentials.createSsl(customCa), // optional: custom CA
});

// Track a billable event
await scrawn.sdkCallEventConsumer({
await biller.sdkCallEventConsumer({
userId: "user-123",
debitAmount: 100,
});
Expand Down
4 changes: 2 additions & 2 deletions examples/scrawn/biller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createScrawn } from "@scrawn/core";
import { scrawn } from "@scrawn/core";
import { TAGS, EXPRESSIONS } from "./pricerefs.ts";

export const biller = createScrawn({
export const biller = scrawn({
apiKey: process.env.SCRAWN_KEY as string,
baseURL: process.env.SCRAWN_BASE_URL as string,
secure: process.env.SCRAWN_BASE_URL?.startsWith("https") ?? false,
Expand Down
6 changes: 3 additions & 3 deletions packages/scrawn/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ bun add @scrawn/core
## Quick Example

```typescript
import { Scrawn } from "@scrawn/core";
import { scrawn } from "@scrawn/core";

const scrawn = new Scrawn({
const biller = scrawn({
apiKey: process.env.SCRAWN_KEY as `scrn_${string}`,
baseURL: process.env.SCRAWN_BASE_URL || "http://localhost:8069",
});

// Track a billable event
await scrawn.sdkCallEventConsumer({
await biller.sdkCallEventConsumer({
userId: "user-123",
debitAmount: 100,
});
Expand Down
16 changes: 8 additions & 8 deletions packages/scrawn/src/core/scrawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ const log = new ScrawnLogger("Scrawn");
*
* @example
* ```typescript
* import { createScrawn } from '@scrawn/core';
* import { scrawn } from '@scrawn/core';
*
* const biller = createScrawn({
* const biller = scrawn({
* apiKey: process.env.SCRAWN_KEY,
* baseURL: 'http://localhost:8069',
* tags: ["PREMIUM_CALL", "EXTRA_FEE"] as const,
Expand Down Expand Up @@ -1280,7 +1280,7 @@ export class Scrawn<
}

/**
* Configuration for creating a Scrawn instance via {@link createScrawn}.
* Configuration for creating a Scrawn instance via {@link scrawn}.
*/
export interface ScrawnInitConfig {
apiKey: string;
Expand All @@ -1306,9 +1306,9 @@ export interface ScrawnInitConfig {
*
* @example
* ```typescript
* import { createScrawn, mul, inputTokens } from '@scrawn/core';
* import { scrawn, mul, inputTokens } from '@scrawn/core';
*
* const biller = createScrawn({
* const biller = scrawn({
* apiKey: process.env.SCRAWN_KEY,
* baseURL: process.env.SCRAWN_BASE_URL,
* tags: ["PREMIUM_CALL", "EXTRA_FEE"] as const,
Expand All @@ -1325,14 +1325,14 @@ export interface ScrawnInitConfig {
* });
* ```
*/
export function createScrawn<
export function scrawn<
const TTags extends readonly string[],
const TExprs extends readonly string[]
>(
config: ScrawnInitConfig & { tags: TTags; expressions: TExprs }
): Scrawn<TTags[number], TExprs[number]>;
export function createScrawn(config: ScrawnInitConfig): Scrawn;
export function createScrawn(
export function scrawn(config: ScrawnInitConfig): Scrawn;
export function scrawn(
config: ScrawnInitConfig & {
tags?: readonly string[];
expressions?: readonly string[];
Expand Down
6 changes: 4 additions & 2 deletions packages/scrawn/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from "./core/scrawn.js";
export { scrawn } from "./core/scrawn.js";
export type { Scrawn } from "./core/scrawn.js";
export type { ScrawnInitConfig } from "./core/scrawn.js";
export * from "./core/types/event.js";
export * from "./core/types/auth.js";

Expand Down Expand Up @@ -69,4 +71,4 @@ export type {
LanguageModelUsage,
ModelInfo,
WithUserId,
} from "./core/ai/types.ts";
} from "./core/ai/types.js";
37 changes: 19 additions & 18 deletions packages/scrawn/tests/unit/scrawn/middleware.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { Scrawn } from "../../../src/core/scrawn.js";
import { scrawn } from "../../../src/core/scrawn.js";
import type { Scrawn } from "../../../src/core/scrawn.js";
import {
ScrawnError,
ScrawnValidationError,
Expand All @@ -24,8 +25,8 @@ const addMetadataMock = vi.fn(function (
});
let requestError: Error | null = null;

function attachMockClient(scrawn: Scrawn): void {
(scrawn as unknown as { grpcClient: unknown }).grpcClient = {
function attachMockClient(s: Scrawn): void {
(s as unknown as { grpcClient: unknown }).grpcClient = {
newCall: () => ({
addMetadata: addMetadataMock,
addPayload: addPayloadMock,
Expand All @@ -50,12 +51,12 @@ describe("middlewareEventConsumer", () => {
});

it("tracks events for matching paths", async () => {
const scrawn = new Scrawn({
const biller = scrawn({
apiKey: validKey,
baseURL: "https://api.example",
});
attachMockClient(scrawn);
const middleware = scrawn.middlewareEventConsumer({
attachMockClient(biller);
const middleware = biller.middlewareEventConsumer({
extractor: () => ({ userId: "user_1", debitAmount: 2 }),
whitelist: ["/api/**"],
});
Expand All @@ -69,12 +70,12 @@ describe("middlewareEventConsumer", () => {
});

it("skips events for non-whitelisted paths", async () => {
const scrawn = new Scrawn({
const biller = scrawn({
apiKey: validKey,
baseURL: "https://api.example",
});
attachMockClient(scrawn);
const middleware = scrawn.middlewareEventConsumer({
attachMockClient(biller);
const middleware = biller.middlewareEventConsumer({
extractor: () => ({ userId: "user_1", debitAmount: 2 }),
whitelist: ["/billing/**"],
});
Expand All @@ -88,12 +89,12 @@ describe("middlewareEventConsumer", () => {
});

it("skips events when extractor returns null", async () => {
const scrawn = new Scrawn({
const biller = scrawn({
apiKey: validKey,
baseURL: "https://api.example",
});
attachMockClient(scrawn);
const middleware = scrawn.middlewareEventConsumer({
attachMockClient(biller);
const middleware = biller.middlewareEventConsumer({
extractor: () => null,
});

Expand All @@ -106,16 +107,16 @@ describe("middlewareEventConsumer", () => {
});

it("calls onError when middleware tracking fails", async () => {
const scrawn = new Scrawn({
const biller = scrawn({
apiKey: validKey,
baseURL: "https://api.example",
retryCount: 0,
});
attachMockClient(scrawn);
attachMockClient(biller);
const onError = vi.fn();
requestError = new Error("grpc down");

const middleware = scrawn.middlewareEventConsumer({
const middleware = biller.middlewareEventConsumer({
extractor: () => ({ userId: "user_1", debitAmount: 2 }),
onError,
});
Expand All @@ -131,14 +132,14 @@ describe("middlewareEventConsumer", () => {
});

it("calls onError when extracted payload is invalid", async () => {
const scrawn = new Scrawn({
const biller = scrawn({
apiKey: validKey,
baseURL: "https://api.example",
});
attachMockClient(scrawn);
attachMockClient(biller);
const onError = vi.fn();

const middleware = scrawn.middlewareEventConsumer({
const middleware = biller.middlewareEventConsumer({
extractor: () => ({ userId: "", debitAmount: 2 }),
onError,
});
Expand Down
39 changes: 20 additions & 19 deletions packages/scrawn/tests/unit/scrawn/scrawn.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { Scrawn } from "../../../src/core/scrawn.js";
import { scrawn } from "../../../src/core/scrawn.js";
import type { Scrawn } from "../../../src/core/scrawn.js";
import { BasicUsageType } from "../../../src/gen/event/v1/event.js";
import {
ScrawnConfigError,
Expand All @@ -23,8 +24,8 @@ const addMetadataMock = vi.fn(function (
const unaryResponseMock = vi.fn();
let requestError: Error | null = null;

function attachMockClient(scrawn: Scrawn): void {
(scrawn as unknown as { grpcClient: unknown }).grpcClient = {
function attachMockClient(s: Scrawn): void {
(s as unknown as { grpcClient: unknown }).grpcClient = {
newCall: (_client: unknown, method: string) => ({
addMetadata: addMetadataMock,
addPayload: addPayloadMock,
Expand Down Expand Up @@ -55,13 +56,13 @@ describe("Scrawn", () => {
});

it("tracks basic usage events", async () => {
const scrawn = new Scrawn({
const biller = scrawn({
apiKey: validKey,
baseURL: "https://api.example",
});
attachMockClient(scrawn);
attachMockClient(biller);

await scrawn.basicUsageEventConsumer({ userId: "user_1", debitAmount: 5 });
await biller.basicUsageEventConsumer({ userId: "user_1", debitAmount: 5 });

const request = requestMock.mock.calls[0][0] as any;
expect(request.userId).toBe("user_1");
Expand All @@ -73,15 +74,15 @@ describe("Scrawn", () => {
});

it("rejects invalid event payloads", async () => {
const scrawn = new Scrawn({
const biller = scrawn({
apiKey: validKey,
baseURL: "https://api.example",
});
attachMockClient(scrawn);
attachMockClient(biller);

const onError = vi.fn();

await scrawn.basicUsageEventConsumer(
await biller.basicUsageEventConsumer(
{ userId: "", debitAmount: 5 },
{ onError }
);
Expand All @@ -92,47 +93,47 @@ describe("Scrawn", () => {
});

it("collects payment links", async () => {
const scrawn = new Scrawn({
const biller = scrawn({
apiKey: validKey,
baseURL: "https://api.example",
});
attachMockClient(scrawn);
const link = await scrawn.collectPayment("user_1");
attachMockClient(biller);
const link = await biller.collectPayment("user_1");

const request = requestMock.mock.calls[0][0] as any;
expect(request.userId).toBe("user_1");
expect(link).toBe("https://checkout.example");
});

it("validates constructor config", () => {
expect(() => new Scrawn({ apiKey: "", baseURL: "" })).toThrow(
expect(() => scrawn({ apiKey: "", baseURL: "" })).toThrow(
ScrawnConfigError
);
});

it("validates collectPayment input", async () => {
const scrawn = new Scrawn({
const biller = scrawn({
apiKey: validKey,
baseURL: "https://api.example",
});
attachMockClient(scrawn);
attachMockClient(biller);

await expect(scrawn.collectPayment("")).rejects.toBeInstanceOf(
await expect(biller.collectPayment("")).rejects.toBeInstanceOf(
ScrawnValidationError
);
});

it("calls onError with retry context when basicUsageEventConsumer fails", async () => {
const scrawn = new Scrawn({
const biller = scrawn({
apiKey: validKey,
baseURL: "https://api.example",
retryCount: 0,
});
const onError = vi.fn();
requestError = new Error("grpc down");
attachMockClient(scrawn);
attachMockClient(biller);

await scrawn.basicUsageEventConsumer(
await biller.basicUsageEventConsumer(
{ userId: "user_1", debitAmount: 5 },
{ onError }
);
Expand Down
Loading