@aranova/tracking-next 0.7.2 → 0.8.0

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
@@ -171,9 +171,55 @@ export default function Page() {
171
171
  }
172
172
  ```
173
173
 
174
+ ## Recording Sales
175
+
176
+ Record a sale / conversion (a first-class, mutable resource — not a fire-and-forget
177
+ event). Money is integer minor units (cents); `currency` is a required ISO-4217 enum.
178
+
179
+ One isomorphic `createSalesClient` (on the root entry) serves both sides — what a
180
+ key may do is enforced by the backend, not by hiding methods. Browser write with
181
+ your **public** key, from a client component:
182
+
183
+ ```tsx
184
+ 'use client';
185
+ import { createSalesClient, toMinor } from '@aranova/tracking-next';
186
+
187
+ const sales = createSalesClient({ apiKey: process.env.NEXT_PUBLIC_ARANOVA_TRACKING_API_KEY!, endpoint });
188
+ await sales.record({ currency: 'CAD', amount_total_cents: toMinor(250, 'CAD'), service: 'tires' });
189
+ ```
190
+
191
+ Full CRUD from a route handler / server action with your **secret** key (same
192
+ import — keep the secret key in server env, never in client code):
193
+
194
+ ```ts
195
+ import { createSalesClient } from '@aranova/tracking-next';
196
+ import type { AranovaService } from './aranova-services'; // generated, see below
197
+
198
+ const sales = createSalesClient<AranovaService>({
199
+ apiKey: process.env.ARANOVA_TRACKING_SECRET_KEY!,
200
+ endpoint: process.env.ARANOVA_TRACKING_ENDPOINT!,
201
+ });
202
+ const { items, next_cursor } = await sales.list({ limit: 50 });
203
+ ```
204
+
205
+ A public-key client calling `list`/`get`/`update`/`delete` gets a `403` telling it
206
+ to use a secret key server-side.
207
+
208
+ Generate the typed `AranovaService` union from your dashboard services with the CLI
209
+ (install it as a **devDependency**):
210
+
211
+ ```bash
212
+ npm install --save-dev @aranova/tracking-cli
213
+ npx @aranova/tracking-cli gen # reads ARANOVA_TRACKING_SECRET_KEY from .env
214
+ ```
215
+
216
+ See the full guide: [sales-tracking.md](https://github.com/AranovaIO/aranova_internal/blob/master/docs/tracking-package/sales-tracking.md)
217
+ and the CLI reference: [cli.md](https://github.com/AranovaIO/aranova_internal/blob/master/docs/tracking-package/cli.md).
218
+
174
219
  ## Exports
175
220
 
176
- - Root package: `createTracking`, `TrackingProvider`, `useTracking`, `GoogleAdsTracking`, `ConsentBanner`, hooks, and event types
221
+ - Root package: `createTracking`, `TrackingProvider`, `useTracking`, `GoogleAdsTracking`, `ConsentBanner`, hooks, event types, and `createSalesClient()` (isomorphic sales client — public key writes, secret key reads/CRUD) + money helpers (`toMinor`/`fromMinor`/`formatMoney`)
177
222
  - `@aranova/tracking-next/middleware`: `createTrackingMiddleware()`
178
223
  - `@aranova/tracking-next/server`: `getTrackingParamsServer()`
224
+ - Codegen: [`@aranova/tracking-cli`](https://www.npmjs.com/package/@aranova/tracking-cli) — `gen` typed service unions (devDependency)
179
225
 
package/dist/index.d.mts CHANGED
@@ -1232,6 +1232,307 @@ interface TypedTrackingClient<TRegistry extends TriggerRegistryConfig> {
1232
1232
  getVisitorId(): string;
1233
1233
  }
1234
1234
 
1235
+ /**
1236
+ * Sales / Conversions wire schemas — the client-side source of truth.
1237
+ *
1238
+ * `saleCreateSchema` is mirrored by `SaleCreateSchema` in
1239
+ * `apps/api/src/schemas/tracking_sales.py` and enforced by the backend drift
1240
+ * test (the `resources` section of `events.schema.json`). Keep them in lockstep.
1241
+ *
1242
+ * Money is **integer minor units (cents)**; `quantity` is a decimal string;
1243
+ * `currency` is the required `SupportedCurrency` enum.
1244
+ */
1245
+ declare const SUPPORTED_CURRENCIES: readonly ["USD", "CAD"];
1246
+ type SupportedCurrency = (typeof SUPPORTED_CURRENCIES)[number];
1247
+ declare const TRACKING_ENVIRONMENTS: readonly ["production", "development"];
1248
+ declare const saleItemSchema: z.ZodObject<{
1249
+ external_item_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1250
+ name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1251
+ category: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1252
+ quantity: z.ZodString;
1253
+ unit_price_cents: z.ZodNumber;
1254
+ unit_cost_cents: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
1255
+ }, "strict", z.ZodTypeAny, {
1256
+ quantity: string;
1257
+ unit_price_cents: number;
1258
+ name?: string | null | undefined;
1259
+ external_item_id?: string | null | undefined;
1260
+ category?: string | null | undefined;
1261
+ unit_cost_cents?: number | null | undefined;
1262
+ }, {
1263
+ quantity: string;
1264
+ unit_price_cents: number;
1265
+ name?: string | null | undefined;
1266
+ external_item_id?: string | null | undefined;
1267
+ category?: string | null | undefined;
1268
+ unit_cost_cents?: number | null | undefined;
1269
+ }>;
1270
+ declare const saleCreateSchema: z.ZodObject<{
1271
+ external_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1272
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1273
+ service: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1274
+ currency: z.ZodEnum<["USD", "CAD"]>;
1275
+ amount_total_cents: z.ZodNumber;
1276
+ occurred_at: z.ZodString;
1277
+ environment: z.ZodDefault<z.ZodEnum<["production", "development"]>>;
1278
+ items: z.ZodDefault<z.ZodArray<z.ZodObject<{
1279
+ external_item_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1280
+ name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1281
+ category: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1282
+ quantity: z.ZodString;
1283
+ unit_price_cents: z.ZodNumber;
1284
+ unit_cost_cents: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
1285
+ }, "strict", z.ZodTypeAny, {
1286
+ quantity: string;
1287
+ unit_price_cents: number;
1288
+ name?: string | null | undefined;
1289
+ external_item_id?: string | null | undefined;
1290
+ category?: string | null | undefined;
1291
+ unit_cost_cents?: number | null | undefined;
1292
+ }, {
1293
+ quantity: string;
1294
+ unit_price_cents: number;
1295
+ name?: string | null | undefined;
1296
+ external_item_id?: string | null | undefined;
1297
+ category?: string | null | undefined;
1298
+ unit_cost_cents?: number | null | undefined;
1299
+ }>, "many">>;
1300
+ metadata: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
1301
+ }, "strict", z.ZodTypeAny, {
1302
+ currency: "USD" | "CAD";
1303
+ amount_total_cents: number;
1304
+ occurred_at: string;
1305
+ environment: "production" | "development";
1306
+ items: {
1307
+ quantity: string;
1308
+ unit_price_cents: number;
1309
+ name?: string | null | undefined;
1310
+ external_item_id?: string | null | undefined;
1311
+ category?: string | null | undefined;
1312
+ unit_cost_cents?: number | null | undefined;
1313
+ }[];
1314
+ metadata?: Record<string, unknown> | null | undefined;
1315
+ external_id?: string | null | undefined;
1316
+ description?: string | null | undefined;
1317
+ service?: string | null | undefined;
1318
+ }, {
1319
+ currency: "USD" | "CAD";
1320
+ amount_total_cents: number;
1321
+ occurred_at: string;
1322
+ metadata?: Record<string, unknown> | null | undefined;
1323
+ external_id?: string | null | undefined;
1324
+ description?: string | null | undefined;
1325
+ service?: string | null | undefined;
1326
+ environment?: "production" | "development" | undefined;
1327
+ items?: {
1328
+ quantity: string;
1329
+ unit_price_cents: number;
1330
+ name?: string | null | undefined;
1331
+ external_item_id?: string | null | undefined;
1332
+ category?: string | null | undefined;
1333
+ unit_cost_cents?: number | null | undefined;
1334
+ }[] | undefined;
1335
+ }>;
1336
+ declare const saleUpdateSchema: z.ZodObject<{
1337
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1338
+ service: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1339
+ currency: z.ZodOptional<z.ZodEnum<["USD", "CAD"]>>;
1340
+ amount_total_cents: z.ZodOptional<z.ZodNumber>;
1341
+ occurred_at: z.ZodOptional<z.ZodString>;
1342
+ items: z.ZodOptional<z.ZodArray<z.ZodObject<{
1343
+ external_item_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1344
+ name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1345
+ category: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1346
+ quantity: z.ZodString;
1347
+ unit_price_cents: z.ZodNumber;
1348
+ unit_cost_cents: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
1349
+ }, "strict", z.ZodTypeAny, {
1350
+ quantity: string;
1351
+ unit_price_cents: number;
1352
+ name?: string | null | undefined;
1353
+ external_item_id?: string | null | undefined;
1354
+ category?: string | null | undefined;
1355
+ unit_cost_cents?: number | null | undefined;
1356
+ }, {
1357
+ quantity: string;
1358
+ unit_price_cents: number;
1359
+ name?: string | null | undefined;
1360
+ external_item_id?: string | null | undefined;
1361
+ category?: string | null | undefined;
1362
+ unit_cost_cents?: number | null | undefined;
1363
+ }>, "many">>;
1364
+ metadata: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
1365
+ }, "strict", z.ZodTypeAny, {
1366
+ metadata?: Record<string, unknown> | null | undefined;
1367
+ description?: string | null | undefined;
1368
+ service?: string | null | undefined;
1369
+ currency?: "USD" | "CAD" | undefined;
1370
+ amount_total_cents?: number | undefined;
1371
+ occurred_at?: string | undefined;
1372
+ items?: {
1373
+ quantity: string;
1374
+ unit_price_cents: number;
1375
+ name?: string | null | undefined;
1376
+ external_item_id?: string | null | undefined;
1377
+ category?: string | null | undefined;
1378
+ unit_cost_cents?: number | null | undefined;
1379
+ }[] | undefined;
1380
+ }, {
1381
+ metadata?: Record<string, unknown> | null | undefined;
1382
+ description?: string | null | undefined;
1383
+ service?: string | null | undefined;
1384
+ currency?: "USD" | "CAD" | undefined;
1385
+ amount_total_cents?: number | undefined;
1386
+ occurred_at?: string | undefined;
1387
+ items?: {
1388
+ quantity: string;
1389
+ unit_price_cents: number;
1390
+ name?: string | null | undefined;
1391
+ external_item_id?: string | null | undefined;
1392
+ category?: string | null | undefined;
1393
+ unit_cost_cents?: number | null | undefined;
1394
+ }[] | undefined;
1395
+ }>;
1396
+ type SaleItemInput = z.input<typeof saleItemSchema>;
1397
+ type SaleInput = z.input<typeof saleCreateSchema>;
1398
+ type SaleUpdateInput = z.input<typeof saleUpdateSchema>;
1399
+ interface SaleItem {
1400
+ id: string;
1401
+ external_item_id: string | null;
1402
+ name: string | null;
1403
+ category: string | null;
1404
+ quantity: string;
1405
+ unit_price_cents: number;
1406
+ unit_cost_cents: number | null;
1407
+ }
1408
+ interface Sale {
1409
+ id: string;
1410
+ business_id: string;
1411
+ business_name?: string | null;
1412
+ external_id: string | null;
1413
+ currency: SupportedCurrency;
1414
+ amount_total_cents: number;
1415
+ description: string | null;
1416
+ service_id: string | null;
1417
+ service_key?: string | null;
1418
+ service_label?: string | null;
1419
+ occurred_at: string;
1420
+ environment: (typeof TRACKING_ENVIRONMENTS)[number];
1421
+ metadata: Record<string, unknown> | null;
1422
+ created_at: string;
1423
+ updated_at: string;
1424
+ items: SaleItem[];
1425
+ }
1426
+ interface SaleListPage {
1427
+ items: Sale[];
1428
+ total: number;
1429
+ }
1430
+ interface SaleCursorPage {
1431
+ items: Sale[];
1432
+ next_cursor: string | null;
1433
+ }
1434
+ interface SaleListQuery {
1435
+ business_id?: string;
1436
+ external_id?: string;
1437
+ currency?: SupportedCurrency;
1438
+ environment?: (typeof TRACKING_ENVIRONMENTS)[number];
1439
+ since?: string;
1440
+ until?: string;
1441
+ limit?: number;
1442
+ cursor?: string | null;
1443
+ }
1444
+
1445
+ /** Shared config for every sales HTTP helper. */
1446
+ interface SalesTransportConfig {
1447
+ /** Public (`aranv_pk_…`) or secret (`aranv_sk_…`) API key. */
1448
+ apiKey: string;
1449
+ /**
1450
+ * Base tracking endpoint, e.g. `https://aranovainternal-production.up.railway.app/tracking`.
1451
+ * The `/sales` path is appended by the helpers.
1452
+ */
1453
+ endpoint: string;
1454
+ /** Optional SDK identity headers (mirrors the event ingest client). */
1455
+ sdkVersion?: string;
1456
+ packageName?: string;
1457
+ surface?: string;
1458
+ environment?: string;
1459
+ }
1460
+
1461
+ /** Config for {@link createSalesClient}. */
1462
+ interface SalesClientConfig extends SalesTransportConfig {
1463
+ /** Applied when an individual `record()` call omits `currency`. */
1464
+ defaultCurrency?: SupportedCurrency;
1465
+ }
1466
+ /**
1467
+ * One isomorphic sales client — what a key may *do* is enforced by the backend,
1468
+ * not by hiding methods. A **public** key (`aranv_pk_…`) may `record` (the
1469
+ * backend rejects reads/CRUD from it with a `403`); a **secret** key
1470
+ * (`aranv_sk_…`), used **server-side only**, gets full read/list/update/delete.
1471
+ * Never ship a secret key in a browser bundle.
1472
+ *
1473
+ * Generic over the service-key union `TService`: bind the type emitted by
1474
+ * `@aranova/tracking-cli gen` for compile-time-checked `service` values.
1475
+ */
1476
+ interface SalesClient<TService extends string = string> {
1477
+ record(input: Omit<SaleInput, 'currency' | 'occurred_at' | 'service'> & {
1478
+ service?: TService | null;
1479
+ currency?: SupportedCurrency;
1480
+ occurred_at?: string;
1481
+ }): Promise<Sale>;
1482
+ list(query?: SaleListQuery): Promise<SaleCursorPage>;
1483
+ get(id: string): Promise<Sale>;
1484
+ update(id: string, patch: Omit<SaleUpdateInput, 'service'> & {
1485
+ service?: TService | null;
1486
+ }): Promise<Sale>;
1487
+ delete(id: string): Promise<void>;
1488
+ }
1489
+ declare function createSalesClient<TService extends string = string>(config: SalesClientConfig): SalesClient<TService>;
1490
+
1491
+ /** A business's active service, as returned by `GET /tracking/services`. */
1492
+ interface PublicServiceItem {
1493
+ key: string;
1494
+ label: string;
1495
+ }
1496
+ /**
1497
+ * Fetch the caller's business's active service taxonomy. Accepts a public or
1498
+ * secret key (the taxonomy is low-sensitivity category names). Powers the
1499
+ * `@aranova/tracking-cli gen` codegen.
1500
+ */
1501
+ declare function fetchServices(config: SalesTransportConfig): Promise<PublicServiceItem[]>;
1502
+
1503
+ /**
1504
+ * Convert a major amount (dollars `250.5`) to integer minor units (`25050`).
1505
+ *
1506
+ * Convenience only — the wire is always integer cents. Uses float multiply +
1507
+ * `Math.round`, so values that aren't exactly representable in binary float
1508
+ * (e.g. `1.005`) can round to the neighbouring cent. If you already hold an
1509
+ * exact cents integer, pass it straight through and skip this helper.
1510
+ */
1511
+ declare function toMinor(amount: number, currency: SupportedCurrency): number;
1512
+ /** Convert integer minor units (`25050`) to a major amount (`250.5`). */
1513
+ declare function fromMinor(cents: number, currency: SupportedCurrency): number;
1514
+ /**
1515
+ * Format integer minor units as a localized currency string (e.g. `"$250.50"`).
1516
+ * Uses the built-in `Intl.NumberFormat` — no extra dependency.
1517
+ */
1518
+ declare function formatMoney(cents: number, currency: SupportedCurrency, locale?: string): string;
1519
+
1520
+ /**
1521
+ * Error thrown by the sales client (`createSalesClient`) on a
1522
+ * non-2xx response. Unlike the fire-and-forget event queue (which swallows
1523
+ * failures), a sale is a transaction the caller must be able to react to.
1524
+ */
1525
+ declare class AranovaApiError extends Error {
1526
+ readonly status: number;
1527
+ readonly code: string | undefined;
1528
+ readonly requestId: string | undefined;
1529
+ constructor(message: string, options: {
1530
+ status: number;
1531
+ code?: string;
1532
+ requestId?: string;
1533
+ });
1534
+ }
1535
+
1235
1536
  /**
1236
1537
  * Default non-blocking consent banner for Next.js installs.
1237
1538
  *
@@ -1345,4 +1646,4 @@ interface CreateTrackingResult<TRegistry extends TriggerRegistryConfig> {
1345
1646
  */
1346
1647
  declare function createTracking<TRegistry extends TriggerRegistryConfig>(options: CreateTrackingOptions<TRegistry>): CreateTrackingResult<TRegistry>;
1347
1648
 
1348
- export { type AutomaticEventName, ConsentBanner, ConsentState, type CreateTrackingOptions, type CreateTrackingResult, type CtaClickConfig, type CtaClickMetadata, type EventConfig, type EventMetadata, type EventName, type FormStartConfig, type FormStartMetadata, type FormSubmitConfig, type FormSubmitMetadata, GoogleAdsTracking, type GoogleAdsTrackingProps, type JsonValue, type ManualEventName, type MultiPageSessionConfig, type MultiPageSessionMetadata, type PageViewConfig, type PageViewMetadata, type PhoneClickConfig, type PhoneClickMetadata, type RegisteredAutomaticEvents, type RegisteredManualEvents, type ScrollDepthConfig, type ScrollDepthMetadata, type SpecificPageName, type SpecificPageVisitConfig, type SpecificPageVisitMetadata, TRACKING_PARAM_KEYS, type TimeOnSiteConfig, type TimeOnSiteMetadata, type TrackingClient, TrackingClientContext, TrackingEventCreatePayload, TrackingInstallSurface, TrackingParams, type TrackingProviderProps, TrackingSessionUpsertPayload, type TriggerRegistryConfig, type TypedTrackEventOptions, type TypedTrackingClient, captureTrackingParamsFromLocation, createTracking, createTrackingClientContext, createTrackingEventCreatePayload, createTrackingSessionUpsertPayload, getConsentState, setConsentState, useConsentState, useGclid, useTrackingParams };
1649
+ export { AranovaApiError, type AutomaticEventName, ConsentBanner, ConsentState, type CreateTrackingOptions, type CreateTrackingResult, type CtaClickConfig, type CtaClickMetadata, type EventConfig, type EventMetadata, type EventName, type FormStartConfig, type FormStartMetadata, type FormSubmitConfig, type FormSubmitMetadata, GoogleAdsTracking, type GoogleAdsTrackingProps, type JsonValue, type ManualEventName, type MultiPageSessionConfig, type MultiPageSessionMetadata, type PageViewConfig, type PageViewMetadata, type PhoneClickConfig, type PhoneClickMetadata, type PublicServiceItem, type RegisteredAutomaticEvents, type RegisteredManualEvents, type Sale, type SaleCursorPage, type SaleInput, type SaleItem, type SaleItemInput, type SaleListPage, type SaleListQuery, type SaleUpdateInput, type SalesClient, type SalesClientConfig, type SalesTransportConfig, type ScrollDepthConfig, type ScrollDepthMetadata, type SpecificPageName, type SpecificPageVisitConfig, type SpecificPageVisitMetadata, type SupportedCurrency, TRACKING_PARAM_KEYS, type TimeOnSiteConfig, type TimeOnSiteMetadata, type TrackingClient, TrackingClientContext, TrackingEventCreatePayload, TrackingInstallSurface, TrackingParams, type TrackingProviderProps, TrackingSessionUpsertPayload, type TriggerRegistryConfig, type TypedTrackEventOptions, type TypedTrackingClient, captureTrackingParamsFromLocation, createSalesClient, createTracking, createTrackingClientContext, createTrackingEventCreatePayload, createTrackingSessionUpsertPayload, fetchServices, formatMoney, fromMinor, getConsentState, setConsentState, toMinor, useConsentState, useGclid, useTrackingParams };