@ai-billing/polar 0.0.3 → 0.0.5

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/dist/index.cjs CHANGED
@@ -27,47 +27,6 @@ module.exports = __toCommonJS(index_exports);
27
27
  // src/destination/polar-destination.ts
28
28
  var import_sdk = require("@polar-sh/sdk");
29
29
  var import_core = require("@ai-billing/core");
30
- function mapEventToPolarMetadata(event) {
31
- const metadata = {
32
- generation_id: event.generationId,
33
- model_id: event.modelId,
34
- provider: event.provider
35
- };
36
- const usageFields = {
37
- usage_input_tokens: "inputTokens",
38
- usage_output_tokens: "outputTokens",
39
- usage_total_tokens: "totalTokens",
40
- usage_reasoning_tokens: "reasoningTokens",
41
- usage_cache_read_tokens: "cacheReadTokens",
42
- usage_cache_write_tokens: "cacheWriteTokens",
43
- usage_request_count: "requestCount",
44
- usage_raw_provider_cost: "rawProviderCost"
45
- };
46
- for (const [polarKey, internalKey] of Object.entries(usageFields)) {
47
- const value = event.usage?.[internalKey];
48
- if (value !== void 0) {
49
- metadata[polarKey] = value;
50
- }
51
- }
52
- if (event.cost) {
53
- metadata.cost_amount_base = (0, import_core.costToNumber)(event.cost, "base");
54
- metadata.cost_amount_cents = (0, import_core.costToNumber)(event.cost, "cents");
55
- metadata.cost_amount_micros = (0, import_core.costToNumber)(event.cost, "micros");
56
- metadata.cost_amount_nanos = (0, import_core.costToNumber)(event.cost, "nanos");
57
- metadata.cost_currency = event.cost.currency;
58
- }
59
- if (!event.tags) return metadata;
60
- for (const [key, value] of Object.entries(event.tags)) {
61
- if (value == null) continue;
62
- const metadataKey = `ai-billing-tag_${key}`;
63
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
64
- metadata[metadataKey] = value;
65
- } else {
66
- metadata[metadataKey] = JSON.stringify(value);
67
- }
68
- }
69
- return metadata;
70
- }
71
30
  function createPolarDestination(options) {
72
31
  const polar = options.client ?? new import_sdk.Polar({
73
32
  accessToken: options.accessToken,
@@ -75,25 +34,40 @@ function createPolarDestination(options) {
75
34
  });
76
35
  return (0, import_core.createDestination)("polar", async (event) => {
77
36
  const tags = event.tags ?? {};
78
- const internalId = tags[options.customerIdKey] ?? tags.customerId;
79
- const externalId = tags[options.externalCustomerIdKey] ?? tags.userId;
37
+ const internalId = options.customerIdKey ? tags[options.customerIdKey] : tags.customerId ?? tags.polarCustomerId ?? tags.customer_id;
38
+ const externalId = options.externalCustomerIdKey ? tags[options.externalCustomerIdKey] : tags.userId ?? tags.externalId ?? tags.user_id;
80
39
  if (!internalId && !externalId) {
81
40
  console.warn(
82
41
  "[ai-billing] Polar: No identity found in tags. Skipping event."
83
42
  );
84
43
  }
85
- const meterName = typeof options.meterName === "function" ? options.meterName(event) : options.meterName;
86
- const metadata = options.mapMetadata ? options.mapMetadata(event) : mapEventToPolarMetadata(event);
87
- await polar.events.ingest({
88
- events: [
89
- {
90
- name: meterName,
91
- // Priority: Internal Polar ID always wins if both are present
92
- ...internalId ? { customerId: String(internalId) } : { externalCustomerId: String(externalId) },
93
- metadata
94
- }
95
- ]
96
- });
44
+ const eventName = typeof options.eventName === "function" ? options.eventName(event) : options.eventName;
45
+ let metadata;
46
+ if (options.mapMetadata) {
47
+ metadata = options.mapMetadata(event);
48
+ } else {
49
+ metadata = {
50
+ ...(0, import_core.buildMeterMetadata)(event),
51
+ ...event.cost ? {
52
+ cost_nanos: (0, import_core.costToNumber)(event.cost, "nanos"),
53
+ cost_currency: event.cost.currency
54
+ } : {}
55
+ };
56
+ }
57
+ try {
58
+ await polar.events.ingest({
59
+ events: [
60
+ {
61
+ name: eventName,
62
+ customerId: String(internalId),
63
+ ...externalId ? { externalId: String(externalId) } : {},
64
+ metadata
65
+ }
66
+ ]
67
+ });
68
+ } catch (error) {
69
+ console.error("[ai-billing] Failed to ingest event to Polar:", error);
70
+ }
97
71
  });
98
72
  }
99
73
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/destination/polar-destination.ts"],"sourcesContent":["export * from './destination/index.js';\n","import { Polar } from '@polar-sh/sdk';\nimport { createDestination, costToNumber } from '@ai-billing/core';\nimport type { BillingEvent, DefaultTags, Destination } from '@ai-billing/core';\n\nfunction mapEventToPolarMetadata<TTags extends DefaultTags = DefaultTags>(\n event: BillingEvent<TTags>,\n): Record<string, string | number | boolean> {\n const metadata: Record<string, string | number | boolean> = {\n generation_id: event.generationId,\n model_id: event.modelId,\n provider: event.provider,\n };\n\n const usageFields: Record<string, keyof typeof event.usage> = {\n usage_input_tokens: 'inputTokens',\n usage_output_tokens: 'outputTokens',\n usage_total_tokens: 'totalTokens',\n usage_reasoning_tokens: 'reasoningTokens',\n usage_cache_read_tokens: 'cacheReadTokens',\n usage_cache_write_tokens: 'cacheWriteTokens',\n usage_request_count: 'requestCount',\n usage_raw_provider_cost: 'rawProviderCost',\n };\n\n for (const [polarKey, internalKey] of Object.entries(usageFields)) {\n const value = event.usage?.[internalKey];\n if (value !== undefined) {\n metadata[polarKey] = value;\n }\n }\n\n if (event.cost) {\n metadata.cost_amount_base = costToNumber(event.cost, 'base');\n metadata.cost_amount_cents = costToNumber(event.cost, 'cents');\n metadata.cost_amount_micros = costToNumber(event.cost, 'micros');\n metadata.cost_amount_nanos = costToNumber(event.cost, 'nanos');\n metadata.cost_currency = event.cost.currency;\n }\n\n if (!event.tags) return metadata;\n\n for (const [key, value] of Object.entries(event.tags)) {\n if (value == null) continue; // Skip null/undefined immediately\n\n const metadataKey = `ai-billing-tag_${key}`;\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n metadata[metadataKey] = value;\n } else {\n // If it's an object/array, stringify it\n metadata[metadataKey] = JSON.stringify(value);\n }\n }\n\n return metadata;\n}\n\nexport interface PolarDestinationOptions<\n TTags extends DefaultTags = DefaultTags,\n> {\n client?: Polar;\n accessToken?: string;\n server?: 'sandbox' | 'production';\n meterName: string | ((event: BillingEvent<TTags>) => string);\n\n /** * Custom key to look for in tags for Polar's internal customer ID (cus_...).\n * Defaults to: 'customerId' | 'polarCustomerId'\n */\n customerIdKey?: keyof TTags;\n\n /** * Custom key to look for in tags for your system's ID.\n * Defaults to: 'userId' | 'externalId'\n */\n externalCustomerIdKey?: keyof TTags;\n\n mapMetadata?: (\n event: BillingEvent<TTags>,\n ) => Record<string, string | number | boolean>;\n}\n\nexport function createPolarDestination<TTags extends DefaultTags = DefaultTags>(\n options: PolarDestinationOptions<TTags>,\n): Destination<TTags> {\n const polar =\n options.client ??\n new Polar({\n accessToken: options.accessToken,\n server: options.server,\n });\n\n return createDestination<TTags>('polar', async event => {\n const tags = (event.tags ?? {}) as Record<\n string,\n string | number | boolean\n >;\n\n const internalId = tags[options.customerIdKey as string] ?? tags.customerId;\n const externalId =\n tags[options.externalCustomerIdKey as string] ?? tags.userId;\n\n if (!internalId && !externalId) {\n console.warn(\n '[ai-billing] Polar: No identity found in tags. Skipping event.',\n );\n }\n\n const meterName =\n typeof options.meterName === 'function'\n ? options.meterName(event)\n : options.meterName;\n\n const metadata = options.mapMetadata\n ? options.mapMetadata(event)\n : mapEventToPolarMetadata(event);\n\n await polar.events.ingest({\n events: [\n {\n name: meterName,\n // Priority: Internal Polar ID always wins if both are present\n ...(internalId\n ? { customerId: String(internalId) }\n : { externalCustomerId: String(externalId) }),\n metadata,\n },\n ],\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,iBAAsB;AACtB,kBAAgD;AAGhD,SAAS,wBACP,OAC2C;AAC3C,QAAM,WAAsD;AAAA,IAC1D,eAAe,MAAM;AAAA,IACrB,UAAU,MAAM;AAAA,IAChB,UAAU,MAAM;AAAA,EAClB;AAEA,QAAM,cAAwD;AAAA,IAC5D,oBAAoB;AAAA,IACpB,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB,wBAAwB;AAAA,IACxB,yBAAyB;AAAA,IACzB,0BAA0B;AAAA,IAC1B,qBAAqB;AAAA,IACrB,yBAAyB;AAAA,EAC3B;AAEA,aAAW,CAAC,UAAU,WAAW,KAAK,OAAO,QAAQ,WAAW,GAAG;AACjE,UAAM,QAAQ,MAAM,QAAQ,WAAW;AACvC,QAAI,UAAU,QAAW;AACvB,eAAS,QAAQ,IAAI;AAAA,IACvB;AAAA,EACF;AAEA,MAAI,MAAM,MAAM;AACd,aAAS,uBAAmB,0BAAa,MAAM,MAAM,MAAM;AAC3D,aAAS,wBAAoB,0BAAa,MAAM,MAAM,OAAO;AAC7D,aAAS,yBAAqB,0BAAa,MAAM,MAAM,QAAQ;AAC/D,aAAS,wBAAoB,0BAAa,MAAM,MAAM,OAAO;AAC7D,aAAS,gBAAgB,MAAM,KAAK;AAAA,EACtC;AAEA,MAAI,CAAC,MAAM,KAAM,QAAO;AAExB,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,IAAI,GAAG;AACrD,QAAI,SAAS,KAAM;AAEnB,UAAM,cAAc,kBAAkB,GAAG;AACzC,QACE,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,WACjB;AACA,eAAS,WAAW,IAAI;AAAA,IAC1B,OAAO;AAEL,eAAS,WAAW,IAAI,KAAK,UAAU,KAAK;AAAA,IAC9C;AAAA,EACF;AAEA,SAAO;AACT;AAyBO,SAAS,uBACd,SACoB;AACpB,QAAM,QACJ,QAAQ,UACR,IAAI,iBAAM;AAAA,IACR,aAAa,QAAQ;AAAA,IACrB,QAAQ,QAAQ;AAAA,EAClB,CAAC;AAEH,aAAO,+BAAyB,SAAS,OAAM,UAAS;AACtD,UAAM,OAAQ,MAAM,QAAQ,CAAC;AAK7B,UAAM,aAAa,KAAK,QAAQ,aAAuB,KAAK,KAAK;AACjE,UAAM,aACJ,KAAK,QAAQ,qBAA+B,KAAK,KAAK;AAExD,QAAI,CAAC,cAAc,CAAC,YAAY;AAC9B,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAEA,UAAM,YACJ,OAAO,QAAQ,cAAc,aACzB,QAAQ,UAAU,KAAK,IACvB,QAAQ;AAEd,UAAM,WAAW,QAAQ,cACrB,QAAQ,YAAY,KAAK,IACzB,wBAAwB,KAAK;AAEjC,UAAM,MAAM,OAAO,OAAO;AAAA,MACxB,QAAQ;AAAA,QACN;AAAA,UACE,MAAM;AAAA;AAAA,UAEN,GAAI,aACA,EAAE,YAAY,OAAO,UAAU,EAAE,IACjC,EAAE,oBAAoB,OAAO,UAAU,EAAE;AAAA,UAC7C;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/destination/polar-destination.ts"],"sourcesContent":["export * from './destination/index.js';\n","import { Polar } from '@polar-sh/sdk';\nimport {\n createDestination,\n costToNumber,\n buildMeterMetadata,\n} from '@ai-billing/core';\nimport type { BillingEvent, DefaultTags, Destination } from '@ai-billing/core';\nimport { EventMetadataInput } from '@polar-sh/sdk/models/components/eventmetadatainput.js';\n\nexport interface PolarDestinationOptions<\n TTags extends DefaultTags = DefaultTags,\n> {\n client?: Polar;\n accessToken?: string;\n server?: 'sandbox' | 'production';\n eventName: string | ((event: BillingEvent<TTags>) => string);\n customerIdKey?: keyof TTags;\n externalCustomerIdKey?: keyof TTags;\n\n mapMetadata?: (\n event: BillingEvent<TTags>,\n ) => Record<string, string | number | boolean>;\n}\n\nexport function createPolarDestination<TTags extends DefaultTags = DefaultTags>(\n options: PolarDestinationOptions<TTags>,\n): Destination<TTags> {\n const polar =\n options.client ??\n new Polar({\n accessToken: options.accessToken,\n server: options.server,\n });\n\n return createDestination<TTags>('polar', async event => {\n const tags = (event.tags ?? {}) as Record<\n string,\n string | number | boolean\n >;\n\n const internalId = options.customerIdKey\n ? tags[options.customerIdKey as string]\n : (tags.customerId ?? tags.polarCustomerId ?? tags.customer_id);\n\n const externalId = options.externalCustomerIdKey\n ? tags[options.externalCustomerIdKey as string]\n : (tags.userId ?? tags.externalId ?? tags.user_id);\n\n if (!internalId && !externalId) {\n console.warn(\n '[ai-billing] Polar: No identity found in tags. Skipping event.',\n );\n }\n\n const eventName =\n typeof options.eventName === 'function'\n ? options.eventName(event)\n : options.eventName;\n\n let metadata: Record<string, EventMetadataInput>;\n\n if (options.mapMetadata) {\n metadata = options.mapMetadata(event);\n } else {\n metadata = {\n ...(buildMeterMetadata(event) as Record<\n string,\n string | number | boolean\n >),\n ...(event.cost\n ? {\n cost_nanos: costToNumber(event.cost, 'nanos'),\n cost_currency: event.cost.currency,\n }\n : {}),\n };\n }\n\n try {\n await polar.events.ingest({\n events: [\n {\n name: eventName,\n customerId: String(internalId),\n ...(externalId ? { externalId: String(externalId) } : {}),\n metadata,\n },\n ],\n });\n } catch (error) {\n console.error('[ai-billing] Failed to ingest event to Polar:', error);\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,iBAAsB;AACtB,kBAIO;AAmBA,SAAS,uBACd,SACoB;AACpB,QAAM,QACJ,QAAQ,UACR,IAAI,iBAAM;AAAA,IACR,aAAa,QAAQ;AAAA,IACrB,QAAQ,QAAQ;AAAA,EAClB,CAAC;AAEH,aAAO,+BAAyB,SAAS,OAAM,UAAS;AACtD,UAAM,OAAQ,MAAM,QAAQ,CAAC;AAK7B,UAAM,aAAa,QAAQ,gBACvB,KAAK,QAAQ,aAAuB,IACnC,KAAK,cAAc,KAAK,mBAAmB,KAAK;AAErD,UAAM,aAAa,QAAQ,wBACvB,KAAK,QAAQ,qBAA+B,IAC3C,KAAK,UAAU,KAAK,cAAc,KAAK;AAE5C,QAAI,CAAC,cAAc,CAAC,YAAY;AAC9B,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAEA,UAAM,YACJ,OAAO,QAAQ,cAAc,aACzB,QAAQ,UAAU,KAAK,IACvB,QAAQ;AAEd,QAAI;AAEJ,QAAI,QAAQ,aAAa;AACvB,iBAAW,QAAQ,YAAY,KAAK;AAAA,IACtC,OAAO;AACL,iBAAW;AAAA,QACT,OAAI,gCAAmB,KAAK;AAAA,QAI5B,GAAI,MAAM,OACN;AAAA,UACE,gBAAY,0BAAa,MAAM,MAAM,OAAO;AAAA,UAC5C,eAAe,MAAM,KAAK;AAAA,QAC5B,IACA,CAAC;AAAA,MACP;AAAA,IACF;AAEA,QAAI;AACF,YAAM,MAAM,OAAO,OAAO;AAAA,QACxB,QAAQ;AAAA,UACN;AAAA,YACE,MAAM;AAAA,YACN,YAAY,OAAO,UAAU;AAAA,YAC7B,GAAI,aAAa,EAAE,YAAY,OAAO,UAAU,EAAE,IAAI,CAAC;AAAA,YACvD;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,MAAM,iDAAiD,KAAK;AAAA,IACtE;AAAA,EACF,CAAC;AACH;","names":[]}
package/dist/index.d.cts CHANGED
@@ -5,14 +5,8 @@ interface PolarDestinationOptions<TTags extends DefaultTags = DefaultTags> {
5
5
  client?: Polar;
6
6
  accessToken?: string;
7
7
  server?: 'sandbox' | 'production';
8
- meterName: string | ((event: BillingEvent<TTags>) => string);
9
- /** * Custom key to look for in tags for Polar's internal customer ID (cus_...).
10
- * Defaults to: 'customerId' | 'polarCustomerId'
11
- */
8
+ eventName: string | ((event: BillingEvent<TTags>) => string);
12
9
  customerIdKey?: keyof TTags;
13
- /** * Custom key to look for in tags for your system's ID.
14
- * Defaults to: 'userId' | 'externalId'
15
- */
16
10
  externalCustomerIdKey?: keyof TTags;
17
11
  mapMetadata?: (event: BillingEvent<TTags>) => Record<string, string | number | boolean>;
18
12
  }
package/dist/index.d.ts CHANGED
@@ -5,14 +5,8 @@ interface PolarDestinationOptions<TTags extends DefaultTags = DefaultTags> {
5
5
  client?: Polar;
6
6
  accessToken?: string;
7
7
  server?: 'sandbox' | 'production';
8
- meterName: string | ((event: BillingEvent<TTags>) => string);
9
- /** * Custom key to look for in tags for Polar's internal customer ID (cus_...).
10
- * Defaults to: 'customerId' | 'polarCustomerId'
11
- */
8
+ eventName: string | ((event: BillingEvent<TTags>) => string);
12
9
  customerIdKey?: keyof TTags;
13
- /** * Custom key to look for in tags for your system's ID.
14
- * Defaults to: 'userId' | 'externalId'
15
- */
16
10
  externalCustomerIdKey?: keyof TTags;
17
11
  mapMetadata?: (event: BillingEvent<TTags>) => Record<string, string | number | boolean>;
18
12
  }
package/dist/index.js CHANGED
@@ -1,47 +1,10 @@
1
1
  // src/destination/polar-destination.ts
2
2
  import { Polar } from "@polar-sh/sdk";
3
- import { createDestination, costToNumber } from "@ai-billing/core";
4
- function mapEventToPolarMetadata(event) {
5
- const metadata = {
6
- generation_id: event.generationId,
7
- model_id: event.modelId,
8
- provider: event.provider
9
- };
10
- const usageFields = {
11
- usage_input_tokens: "inputTokens",
12
- usage_output_tokens: "outputTokens",
13
- usage_total_tokens: "totalTokens",
14
- usage_reasoning_tokens: "reasoningTokens",
15
- usage_cache_read_tokens: "cacheReadTokens",
16
- usage_cache_write_tokens: "cacheWriteTokens",
17
- usage_request_count: "requestCount",
18
- usage_raw_provider_cost: "rawProviderCost"
19
- };
20
- for (const [polarKey, internalKey] of Object.entries(usageFields)) {
21
- const value = event.usage?.[internalKey];
22
- if (value !== void 0) {
23
- metadata[polarKey] = value;
24
- }
25
- }
26
- if (event.cost) {
27
- metadata.cost_amount_base = costToNumber(event.cost, "base");
28
- metadata.cost_amount_cents = costToNumber(event.cost, "cents");
29
- metadata.cost_amount_micros = costToNumber(event.cost, "micros");
30
- metadata.cost_amount_nanos = costToNumber(event.cost, "nanos");
31
- metadata.cost_currency = event.cost.currency;
32
- }
33
- if (!event.tags) return metadata;
34
- for (const [key, value] of Object.entries(event.tags)) {
35
- if (value == null) continue;
36
- const metadataKey = `ai-billing-tag_${key}`;
37
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
38
- metadata[metadataKey] = value;
39
- } else {
40
- metadata[metadataKey] = JSON.stringify(value);
41
- }
42
- }
43
- return metadata;
44
- }
3
+ import {
4
+ createDestination,
5
+ costToNumber,
6
+ buildMeterMetadata
7
+ } from "@ai-billing/core";
45
8
  function createPolarDestination(options) {
46
9
  const polar = options.client ?? new Polar({
47
10
  accessToken: options.accessToken,
@@ -49,25 +12,40 @@ function createPolarDestination(options) {
49
12
  });
50
13
  return createDestination("polar", async (event) => {
51
14
  const tags = event.tags ?? {};
52
- const internalId = tags[options.customerIdKey] ?? tags.customerId;
53
- const externalId = tags[options.externalCustomerIdKey] ?? tags.userId;
15
+ const internalId = options.customerIdKey ? tags[options.customerIdKey] : tags.customerId ?? tags.polarCustomerId ?? tags.customer_id;
16
+ const externalId = options.externalCustomerIdKey ? tags[options.externalCustomerIdKey] : tags.userId ?? tags.externalId ?? tags.user_id;
54
17
  if (!internalId && !externalId) {
55
18
  console.warn(
56
19
  "[ai-billing] Polar: No identity found in tags. Skipping event."
57
20
  );
58
21
  }
59
- const meterName = typeof options.meterName === "function" ? options.meterName(event) : options.meterName;
60
- const metadata = options.mapMetadata ? options.mapMetadata(event) : mapEventToPolarMetadata(event);
61
- await polar.events.ingest({
62
- events: [
63
- {
64
- name: meterName,
65
- // Priority: Internal Polar ID always wins if both are present
66
- ...internalId ? { customerId: String(internalId) } : { externalCustomerId: String(externalId) },
67
- metadata
68
- }
69
- ]
70
- });
22
+ const eventName = typeof options.eventName === "function" ? options.eventName(event) : options.eventName;
23
+ let metadata;
24
+ if (options.mapMetadata) {
25
+ metadata = options.mapMetadata(event);
26
+ } else {
27
+ metadata = {
28
+ ...buildMeterMetadata(event),
29
+ ...event.cost ? {
30
+ cost_nanos: costToNumber(event.cost, "nanos"),
31
+ cost_currency: event.cost.currency
32
+ } : {}
33
+ };
34
+ }
35
+ try {
36
+ await polar.events.ingest({
37
+ events: [
38
+ {
39
+ name: eventName,
40
+ customerId: String(internalId),
41
+ ...externalId ? { externalId: String(externalId) } : {},
42
+ metadata
43
+ }
44
+ ]
45
+ });
46
+ } catch (error) {
47
+ console.error("[ai-billing] Failed to ingest event to Polar:", error);
48
+ }
71
49
  });
72
50
  }
73
51
  export {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/destination/polar-destination.ts"],"sourcesContent":["import { Polar } from '@polar-sh/sdk';\nimport { createDestination, costToNumber } from '@ai-billing/core';\nimport type { BillingEvent, DefaultTags, Destination } from '@ai-billing/core';\n\nfunction mapEventToPolarMetadata<TTags extends DefaultTags = DefaultTags>(\n event: BillingEvent<TTags>,\n): Record<string, string | number | boolean> {\n const metadata: Record<string, string | number | boolean> = {\n generation_id: event.generationId,\n model_id: event.modelId,\n provider: event.provider,\n };\n\n const usageFields: Record<string, keyof typeof event.usage> = {\n usage_input_tokens: 'inputTokens',\n usage_output_tokens: 'outputTokens',\n usage_total_tokens: 'totalTokens',\n usage_reasoning_tokens: 'reasoningTokens',\n usage_cache_read_tokens: 'cacheReadTokens',\n usage_cache_write_tokens: 'cacheWriteTokens',\n usage_request_count: 'requestCount',\n usage_raw_provider_cost: 'rawProviderCost',\n };\n\n for (const [polarKey, internalKey] of Object.entries(usageFields)) {\n const value = event.usage?.[internalKey];\n if (value !== undefined) {\n metadata[polarKey] = value;\n }\n }\n\n if (event.cost) {\n metadata.cost_amount_base = costToNumber(event.cost, 'base');\n metadata.cost_amount_cents = costToNumber(event.cost, 'cents');\n metadata.cost_amount_micros = costToNumber(event.cost, 'micros');\n metadata.cost_amount_nanos = costToNumber(event.cost, 'nanos');\n metadata.cost_currency = event.cost.currency;\n }\n\n if (!event.tags) return metadata;\n\n for (const [key, value] of Object.entries(event.tags)) {\n if (value == null) continue; // Skip null/undefined immediately\n\n const metadataKey = `ai-billing-tag_${key}`;\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n metadata[metadataKey] = value;\n } else {\n // If it's an object/array, stringify it\n metadata[metadataKey] = JSON.stringify(value);\n }\n }\n\n return metadata;\n}\n\nexport interface PolarDestinationOptions<\n TTags extends DefaultTags = DefaultTags,\n> {\n client?: Polar;\n accessToken?: string;\n server?: 'sandbox' | 'production';\n meterName: string | ((event: BillingEvent<TTags>) => string);\n\n /** * Custom key to look for in tags for Polar's internal customer ID (cus_...).\n * Defaults to: 'customerId' | 'polarCustomerId'\n */\n customerIdKey?: keyof TTags;\n\n /** * Custom key to look for in tags for your system's ID.\n * Defaults to: 'userId' | 'externalId'\n */\n externalCustomerIdKey?: keyof TTags;\n\n mapMetadata?: (\n event: BillingEvent<TTags>,\n ) => Record<string, string | number | boolean>;\n}\n\nexport function createPolarDestination<TTags extends DefaultTags = DefaultTags>(\n options: PolarDestinationOptions<TTags>,\n): Destination<TTags> {\n const polar =\n options.client ??\n new Polar({\n accessToken: options.accessToken,\n server: options.server,\n });\n\n return createDestination<TTags>('polar', async event => {\n const tags = (event.tags ?? {}) as Record<\n string,\n string | number | boolean\n >;\n\n const internalId = tags[options.customerIdKey as string] ?? tags.customerId;\n const externalId =\n tags[options.externalCustomerIdKey as string] ?? tags.userId;\n\n if (!internalId && !externalId) {\n console.warn(\n '[ai-billing] Polar: No identity found in tags. Skipping event.',\n );\n }\n\n const meterName =\n typeof options.meterName === 'function'\n ? options.meterName(event)\n : options.meterName;\n\n const metadata = options.mapMetadata\n ? options.mapMetadata(event)\n : mapEventToPolarMetadata(event);\n\n await polar.events.ingest({\n events: [\n {\n name: meterName,\n // Priority: Internal Polar ID always wins if both are present\n ...(internalId\n ? { customerId: String(internalId) }\n : { externalCustomerId: String(externalId) }),\n metadata,\n },\n ],\n });\n });\n}\n"],"mappings":";AAAA,SAAS,aAAa;AACtB,SAAS,mBAAmB,oBAAoB;AAGhD,SAAS,wBACP,OAC2C;AAC3C,QAAM,WAAsD;AAAA,IAC1D,eAAe,MAAM;AAAA,IACrB,UAAU,MAAM;AAAA,IAChB,UAAU,MAAM;AAAA,EAClB;AAEA,QAAM,cAAwD;AAAA,IAC5D,oBAAoB;AAAA,IACpB,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB,wBAAwB;AAAA,IACxB,yBAAyB;AAAA,IACzB,0BAA0B;AAAA,IAC1B,qBAAqB;AAAA,IACrB,yBAAyB;AAAA,EAC3B;AAEA,aAAW,CAAC,UAAU,WAAW,KAAK,OAAO,QAAQ,WAAW,GAAG;AACjE,UAAM,QAAQ,MAAM,QAAQ,WAAW;AACvC,QAAI,UAAU,QAAW;AACvB,eAAS,QAAQ,IAAI;AAAA,IACvB;AAAA,EACF;AAEA,MAAI,MAAM,MAAM;AACd,aAAS,mBAAmB,aAAa,MAAM,MAAM,MAAM;AAC3D,aAAS,oBAAoB,aAAa,MAAM,MAAM,OAAO;AAC7D,aAAS,qBAAqB,aAAa,MAAM,MAAM,QAAQ;AAC/D,aAAS,oBAAoB,aAAa,MAAM,MAAM,OAAO;AAC7D,aAAS,gBAAgB,MAAM,KAAK;AAAA,EACtC;AAEA,MAAI,CAAC,MAAM,KAAM,QAAO;AAExB,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,IAAI,GAAG;AACrD,QAAI,SAAS,KAAM;AAEnB,UAAM,cAAc,kBAAkB,GAAG;AACzC,QACE,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,WACjB;AACA,eAAS,WAAW,IAAI;AAAA,IAC1B,OAAO;AAEL,eAAS,WAAW,IAAI,KAAK,UAAU,KAAK;AAAA,IAC9C;AAAA,EACF;AAEA,SAAO;AACT;AAyBO,SAAS,uBACd,SACoB;AACpB,QAAM,QACJ,QAAQ,UACR,IAAI,MAAM;AAAA,IACR,aAAa,QAAQ;AAAA,IACrB,QAAQ,QAAQ;AAAA,EAClB,CAAC;AAEH,SAAO,kBAAyB,SAAS,OAAM,UAAS;AACtD,UAAM,OAAQ,MAAM,QAAQ,CAAC;AAK7B,UAAM,aAAa,KAAK,QAAQ,aAAuB,KAAK,KAAK;AACjE,UAAM,aACJ,KAAK,QAAQ,qBAA+B,KAAK,KAAK;AAExD,QAAI,CAAC,cAAc,CAAC,YAAY;AAC9B,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAEA,UAAM,YACJ,OAAO,QAAQ,cAAc,aACzB,QAAQ,UAAU,KAAK,IACvB,QAAQ;AAEd,UAAM,WAAW,QAAQ,cACrB,QAAQ,YAAY,KAAK,IACzB,wBAAwB,KAAK;AAEjC,UAAM,MAAM,OAAO,OAAO;AAAA,MACxB,QAAQ;AAAA,QACN;AAAA,UACE,MAAM;AAAA;AAAA,UAEN,GAAI,aACA,EAAE,YAAY,OAAO,UAAU,EAAE,IACjC,EAAE,oBAAoB,OAAO,UAAU,EAAE;AAAA,UAC7C;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/destination/polar-destination.ts"],"sourcesContent":["import { Polar } from '@polar-sh/sdk';\nimport {\n createDestination,\n costToNumber,\n buildMeterMetadata,\n} from '@ai-billing/core';\nimport type { BillingEvent, DefaultTags, Destination } from '@ai-billing/core';\nimport { EventMetadataInput } from '@polar-sh/sdk/models/components/eventmetadatainput.js';\n\nexport interface PolarDestinationOptions<\n TTags extends DefaultTags = DefaultTags,\n> {\n client?: Polar;\n accessToken?: string;\n server?: 'sandbox' | 'production';\n eventName: string | ((event: BillingEvent<TTags>) => string);\n customerIdKey?: keyof TTags;\n externalCustomerIdKey?: keyof TTags;\n\n mapMetadata?: (\n event: BillingEvent<TTags>,\n ) => Record<string, string | number | boolean>;\n}\n\nexport function createPolarDestination<TTags extends DefaultTags = DefaultTags>(\n options: PolarDestinationOptions<TTags>,\n): Destination<TTags> {\n const polar =\n options.client ??\n new Polar({\n accessToken: options.accessToken,\n server: options.server,\n });\n\n return createDestination<TTags>('polar', async event => {\n const tags = (event.tags ?? {}) as Record<\n string,\n string | number | boolean\n >;\n\n const internalId = options.customerIdKey\n ? tags[options.customerIdKey as string]\n : (tags.customerId ?? tags.polarCustomerId ?? tags.customer_id);\n\n const externalId = options.externalCustomerIdKey\n ? tags[options.externalCustomerIdKey as string]\n : (tags.userId ?? tags.externalId ?? tags.user_id);\n\n if (!internalId && !externalId) {\n console.warn(\n '[ai-billing] Polar: No identity found in tags. Skipping event.',\n );\n }\n\n const eventName =\n typeof options.eventName === 'function'\n ? options.eventName(event)\n : options.eventName;\n\n let metadata: Record<string, EventMetadataInput>;\n\n if (options.mapMetadata) {\n metadata = options.mapMetadata(event);\n } else {\n metadata = {\n ...(buildMeterMetadata(event) as Record<\n string,\n string | number | boolean\n >),\n ...(event.cost\n ? {\n cost_nanos: costToNumber(event.cost, 'nanos'),\n cost_currency: event.cost.currency,\n }\n : {}),\n };\n }\n\n try {\n await polar.events.ingest({\n events: [\n {\n name: eventName,\n customerId: String(internalId),\n ...(externalId ? { externalId: String(externalId) } : {}),\n metadata,\n },\n ],\n });\n } catch (error) {\n console.error('[ai-billing] Failed to ingest event to Polar:', error);\n }\n });\n}\n"],"mappings":";AAAA,SAAS,aAAa;AACtB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAmBA,SAAS,uBACd,SACoB;AACpB,QAAM,QACJ,QAAQ,UACR,IAAI,MAAM;AAAA,IACR,aAAa,QAAQ;AAAA,IACrB,QAAQ,QAAQ;AAAA,EAClB,CAAC;AAEH,SAAO,kBAAyB,SAAS,OAAM,UAAS;AACtD,UAAM,OAAQ,MAAM,QAAQ,CAAC;AAK7B,UAAM,aAAa,QAAQ,gBACvB,KAAK,QAAQ,aAAuB,IACnC,KAAK,cAAc,KAAK,mBAAmB,KAAK;AAErD,UAAM,aAAa,QAAQ,wBACvB,KAAK,QAAQ,qBAA+B,IAC3C,KAAK,UAAU,KAAK,cAAc,KAAK;AAE5C,QAAI,CAAC,cAAc,CAAC,YAAY;AAC9B,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAEA,UAAM,YACJ,OAAO,QAAQ,cAAc,aACzB,QAAQ,UAAU,KAAK,IACvB,QAAQ;AAEd,QAAI;AAEJ,QAAI,QAAQ,aAAa;AACvB,iBAAW,QAAQ,YAAY,KAAK;AAAA,IACtC,OAAO;AACL,iBAAW;AAAA,QACT,GAAI,mBAAmB,KAAK;AAAA,QAI5B,GAAI,MAAM,OACN;AAAA,UACE,YAAY,aAAa,MAAM,MAAM,OAAO;AAAA,UAC5C,eAAe,MAAM,KAAK;AAAA,QAC5B,IACA,CAAC;AAAA,MACP;AAAA,IACF;AAEA,QAAI;AACF,YAAM,MAAM,OAAO,OAAO;AAAA,QACxB,QAAQ;AAAA,UACN;AAAA,YACE,MAAM;AAAA,YACN,YAAY,OAAO,UAAU;AAAA,YAC7B,GAAI,aAAa,EAAE,YAAY,OAAO,UAAU,EAAE,IAAI,CAAC;AAAA,YACvD;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,MAAM,iDAAiD,KAAK;AAAA,IACtE;AAAA,EACF,CAAC;AACH;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-billing/polar",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "repository": {
@@ -40,7 +40,7 @@
40
40
  "@ai-billing/testing": "0.0.2"
41
41
  },
42
42
  "peerDependencies": {
43
- "@ai-billing/core": "0.0.3",
43
+ "@ai-billing/core": "0.0.4",
44
44
  "@polar-sh/sdk": "^0.46.7"
45
45
  },
46
46
  "engines": {