tracklane 0.6.0 → 0.7.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 +1 -1
- package/dist/{browser-_0LRgTpx.d.ts → browser-34-CHcxK.d.ts} +104 -10
- package/dist/browser-DhzO5qPS.js +432 -0
- package/dist/browser-DhzO5qPS.js.map +1 -0
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +2 -397
- package/dist/conformance.d.ts +7 -1
- package/dist/conformance.js +56 -10
- package/dist/conformance.js.map +1 -1
- package/dist/{dispatch-BLBoCn-b.js → dispatch-B32jGhlh.js} +6 -3
- package/dist/dispatch-B32jGhlh.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/server-DnP7RtEn.js +647 -0
- package/dist/server-DnP7RtEn.js.map +1 -0
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -531
- package/dist/{errors-DLRGPWMj.js → src-DLRGPWMj.js} +1 -1
- package/dist/src-DLRGPWMj.js.map +1 -0
- package/package.json +4 -3
- package/dist/browser.js.map +0 -1
- package/dist/dispatch-BLBoCn-b.js.map +0 -1
- package/dist/errors-DLRGPWMj.js.map +0 -1
- package/dist/server.js.map +0 -1
package/dist/conformance.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conformance.js","names":[],"sources":["../src/conformance.ts"],"sourcesContent":["import { expect, it } from 'vitest';\nimport type { BrowserProvider } from './browser.js';\nimport { isVendorResponseError } from './errors.js';\nimport type { ResolvedContext, ServerProvider } from './server.js';\nimport type { EventData, ProviderDefault } from './vocabulary.js';\n\n/**\n * The behaviour every provider owes, which the types cannot express.\n *\n * ADR-0004 requires this suite because a provider can satisfy\n * {@link BrowserProvider} or {@link ServerProvider} perfectly and still break\n * a rule the whole library rests on. The interface cannot say \"never invent an\n * identifier\" or \"never expand a consent signal\"; a test can.\n *\n * These are invariants, not vendor behaviour. What GA4 sends and how it spells\n * it belongs in the GA4 tests. What follows must hold for a provider written\n * by anyone, for a vendor nobody here has heard of.\n *\n * **Published as `tracklane/conformance`**, because the claim that a provider\n * written elsewhere uses the same contract as the ones shipped here is only\n * true if it can run the same checks. `vitest` is an optional peer: import this\n * from a test file and call one of the two functions below inside a `describe`.\n *\n * **A wrapper owes the same invariants as a provider.** Wrapping a shipped\n * provider and delegating (see the provider documentation) produces an object\n * that the dispatcher cannot tell apart from any other, so run it through here\n * too. On the server half that catches the expensive mistake: a wrapper whose\n * `track` does not `await` the provider it delegates to resolves before the\n * request settles, which a serverless runtime turns into a conversion that\n * never left. {@link conformsAsServerProvider} fails on it, because a vendor\n * refusal can no longer reach the caller.\n */\n\nconst DEFAULTS: ProviderDefault[] = ['passthrough', 'ignore'];\n\n/** A payload with nothing in it that could excuse a difference between runs. */\nconst STABLE_DATA: EventData = { transaction_id: 'T-1', value: 49.9, currency: 'BRL' };\n\nfunction assertIdentity(provider: { name: string; default: ProviderDefault }): void {\n it('names itself, once and stably', () => {\n // The name is what a duplicate registration is detected by, and what an\n // error report is attributed to.\n expect(provider.name).toBeTruthy();\n expect(provider.name.trim()).toBe(provider.name);\n });\n\n it('declares what happens to an event it has no binding for', () => {\n expect(DEFAULTS).toContain(provider.default);\n });\n}\n\n/**\n * Runs the shared conformance suite against a browser provider.\n *\n * @param create - Builds the provider under test. Called per assertion, so a\n * provider that accumulates state between runs fails rather than hides.\n * @param captured - Everything the provider handed the vendor since the last\n * reset, in order.\n * @param reset - Clears what `captured` returns.\n *\n * @example\n * ```ts\n * describe('ga4 browser conformance', () => {\n * conformsAsBrowserProvider({\n * create: () => ga4('G-ABC'),\n * captured: () => calls,\n * reset: () => { calls = []; },\n * });\n * });\n * ```\n */\nexport function conformsAsBrowserProvider(options: {\n create: () => BrowserProvider;\n captured: () => unknown[];\n reset: () => void;\n}): void {\n const { create, captured, reset } = options;\n\n assertIdentity(create());\n\n it('sends the same thing twice for the same event', () => {\n // The library never invents an identifier, and this is how that rule is\n // caught rather than trusted: a generated id, a random value or a clock\n // read makes two identical calls differ.\n reset();\n create().track('purchase', STABLE_DATA, {});\n const first = JSON.stringify(captured());\n\n reset();\n create().track('purchase', STABLE_DATA, {});\n const second = JSON.stringify(captured());\n\n expect(second).toBe(first);\n });\n\n it('forwards the deduplication id only when it was given one', () => {\n reset();\n create().track('purchase', STABLE_DATA, {});\n const without = JSON.stringify(captured());\n\n reset();\n create().track('purchase', STABLE_DATA, { dedupId: 'DEDUP-XYZ' });\n const with_ = JSON.stringify(captured());\n\n // Either the vendor has the concept, and the value appears where it did\n // not before, or it has none and nothing changes. Inventing one to fill\n // the slot is the failure this catches.\n if (with_ !== without) expect(with_).toContain('DEDUP-XYZ');\n expect(without).not.toContain('DEDUP-XYZ');\n });\n\n it('never expands a consent signal it was not given', () => {\n const provider = create();\n if (!provider.consent) return;\n\n reset();\n provider.consent('update', { analytics_storage: 'denied' });\n\n // One denial in must never become a grant out. Collapsing Google's\n // vocabulary for a coarser vendor is translation; widening it invents a\n // permission the visitor never gave.\n expect(JSON.stringify(captured())).not.toContain('granted');\n });\n}\n\n/**\n * Runs the shared conformance suite against a server provider.\n *\n * @param create - Builds the provider under test, per assertion.\n * @param captured - Everything the provider sent since the last reset.\n * @param reset - Clears what `captured` returns, and restores a successful\n * transport.\n * @param fail - Makes the next send fail at the transport, so the suite can\n * check that the provider is loud about it.\n * @param context - A resolved context with a fixed timestamp.\n *\n * @example\n * ```ts\n * describe('ga4 server conformance', () => {\n * conformsAsServerProvider({\n * create: () => ga4(credentials),\n * captured: () => fetchMock.mock.calls,\n * reset: () => { fetchMock.mockClear(); },\n * fail: () => { fetchMock.mockResolvedValueOnce(new Response(null, { status: 500 })); },\n * context,\n * });\n * });\n * ```\n */\nexport function conformsAsServerProvider(options: {\n create: () => ServerProvider;\n captured: () => unknown[];\n reset: () => void;\n fail: () => void;\n context: () => ResolvedContext;\n}): void {\n const { create, captured, reset, fail, context } = options;\n const noop = (): void => {};\n\n assertIdentity(create());\n\n it('sends the same thing twice for the same event', async () => {\n // The context carries a fixed timestamp, so any difference between these\n // two is something the provider produced on its own.\n reset();\n await create().track('purchase', STABLE_DATA, context(), noop);\n const first = JSON.stringify(captured());\n\n reset();\n await create().track('purchase', STABLE_DATA, context(), noop);\n\n expect(JSON.stringify(captured())).toBe(first);\n });\n\n it('forwards the deduplication id only when it was given one', async () => {\n reset();\n await create().track('purchase', STABLE_DATA, context(), noop);\n const without = JSON.stringify(captured());\n\n reset();\n await create().track('purchase', STABLE_DATA, { ...context(), dedupId: 'DEDUP-XYZ' }, noop);\n const with_ = JSON.stringify(captured());\n\n if (with_ !== without) expect(with_).toContain('DEDUP-XYZ');\n expect(without).not.toContain('DEDUP-XYZ');\n });\n\n it('throws a VendorResponseError when the vendor did not accept the send', async () => {\n // Adapters stay honest and loud. Swallowing a non-2xx is how a host ends\n // up believing it is measuring something, and the dispatcher is already\n // there to isolate the throw and report it. ADR-0013 adds the shape: what\n // reaches the host is recognised by `isVendorResponseError`, carrying the\n // status and the body, not just any throw. The body itself is\n // the caller's `fail()` hook to produce; a bodiless response is valid\n // (the documented example does exactly that), so it is not asserted here.\n reset();\n fail();\n\n let caught: unknown;\n try {\n await create().track('purchase', STABLE_DATA, context(), noop);\n } catch (error) {\n caught = error;\n }\n\n expect(isVendorResponseError(caught)).toBe(true);\n if (isVendorResponseError(caught)) {\n expect(typeof caught.status).toBe('number');\n expect(typeof caught.body).toBe('string');\n }\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"conformance.js","names":["createBrowserTracking","createServerTracking"],"sources":["../src/conformance.ts"],"sourcesContent":["import { expect, it } from 'vitest';\nimport { type BrowserProvider, createTracking as createBrowserTracking } from './browser.js';\nimport { isVendorResponseError } from './errors.js';\nimport {\n createTracking as createServerTracking,\n type ResolvedContext,\n type ServerProvider,\n} from './server.js';\nimport type { EventData, ProviderDefault } from './vocabulary.js';\n\n/**\n * The behaviour every provider owes, which the types cannot express.\n *\n * ADR-0004 requires this suite because a provider can satisfy\n * {@link BrowserProvider} or {@link ServerProvider} perfectly and still break\n * a rule the whole library rests on. The interface cannot say \"never invent an\n * identifier\" or \"never expand a consent signal\"; a test can.\n *\n * These are invariants, not vendor behaviour. What GA4 sends and how it spells\n * it belongs in the GA4 tests. What follows must hold for a provider written\n * by anyone, for a vendor nobody here has heard of.\n *\n * **Published as `tracklane/conformance`**, because the claim that a provider\n * written elsewhere uses the same contract as the ones shipped here is only\n * true if it can run the same checks. `vitest` is an optional peer: import this\n * from a test file and call one of the two functions below inside a `describe`.\n *\n * **A wrapper owes the same invariants as a provider.** Wrapping a shipped\n * provider and delegating (see the provider documentation) produces an object\n * that the dispatcher cannot tell apart from any other, so run it through here\n * too. On the server half that catches the expensive mistake: a wrapper whose\n * `track` does not `await` the provider it delegates to resolves before the\n * request settles, which a serverless runtime turns into a conversion that\n * never left. {@link conformsAsServerProvider} fails on it, because a vendor\n * refusal can no longer reach the caller.\n */\n\nconst DEFAULTS: ProviderDefault[] = ['passthrough', 'ignore'];\n\n/** A payload with nothing in it that could excuse a difference between runs. */\nconst STABLE_DATA: EventData = { transaction_id: 'T-1', value: 49.9, currency: 'BRL' };\n\nfunction assertIdentity(provider: { name: string; default: ProviderDefault }): void {\n it('names itself, once and stably', () => {\n // The name is what a duplicate registration is detected by, and what an\n // error report is attributed to.\n expect(provider.name).toBeTruthy();\n expect(provider.name.trim()).toBe(provider.name);\n });\n\n it('declares what happens to an event it has no binding for', () => {\n expect(DEFAULTS).toContain(provider.default);\n });\n}\n\n/**\n * Runs the shared conformance suite against a browser provider.\n *\n * @param create - Builds the provider under test. Called per assertion, so a\n * provider that accumulates state between runs fails rather than hides.\n * @param captured - Everything the provider handed the vendor since the last\n * reset, in order.\n * @param reset - Clears what `captured` returns.\n * @param target - An already-resolved vendor target. Defaults to `purchase`.\n *\n * @example\n * ```ts\n * describe('ga4 browser conformance', () => {\n * conformsAsBrowserProvider({\n * create: () => ga4('G-ABC'),\n * captured: () => calls,\n * reset: () => { calls = []; },\n * });\n * });\n * ```\n */\nexport function conformsAsBrowserProvider(options: {\n create: () => BrowserProvider;\n captured: () => unknown[];\n reset: () => void;\n /** Already-resolved vendor target; conversion-ID providers use their configured ID. */\n target?: string;\n}): void {\n const { create, captured, reset } = options;\n const target = options.target ?? 'purchase';\n\n assertIdentity(create());\n\n it('reports absent mappings on ignore providers and keeps explicit exclusions silent', () => {\n // Mapping assertions do not exercise the host's installation hook.\n const { install: _install, ...provider } = create();\n if (provider.default !== 'ignore') return;\n reset();\n const errors: { severity: string; event: string }[] = [];\n const tracking = createBrowserTracking({\n providers: [{ ...provider, events: { explicit: null } }],\n onError: (error) => errors.push(error),\n });\n tracking.track('missing');\n tracking.track('explicit');\n expect(errors).toMatchObject([{ severity: 'warning', event: 'missing' }]);\n expect(captured()).toEqual([]);\n });\n\n it('sends the same thing twice for the same event', () => {\n // The library never invents an identifier, and this is how that rule is\n // caught rather than trusted: a generated id, a random value or a clock\n // read makes two identical calls differ.\n reset();\n create().track(target, STABLE_DATA, {});\n const first = JSON.stringify(captured());\n\n reset();\n create().track(target, STABLE_DATA, {});\n const second = JSON.stringify(captured());\n\n expect(second).toBe(first);\n });\n\n it('forwards the deduplication id only when it was given one', () => {\n reset();\n create().track(target, STABLE_DATA, {});\n const without = JSON.stringify(captured());\n\n reset();\n create().track(target, STABLE_DATA, { dedupId: 'DEDUP-XYZ' });\n const with_ = JSON.stringify(captured());\n\n // Either the vendor has the concept, and the value appears where it did\n // not before, or it has none and nothing changes. Inventing one to fill\n // the slot is the failure this catches.\n if (with_ !== without) expect(with_).toContain('DEDUP-XYZ');\n expect(without).not.toContain('DEDUP-XYZ');\n });\n\n it('never expands a consent signal it was not given', () => {\n const provider = create();\n if (!provider.consent) return;\n\n reset();\n provider.consent('update', { analytics_storage: 'denied' });\n\n // One denial in must never become a grant out. Collapsing Google's\n // vocabulary for a coarser vendor is translation; widening it invents a\n // permission the visitor never gave.\n expect(JSON.stringify(captured())).not.toContain('granted');\n });\n}\n\n/**\n * Runs the shared conformance suite against a server provider.\n *\n * @param create - Builds the provider under test, per assertion.\n * @param captured - Everything the provider sent since the last reset.\n * @param reset - Clears what `captured` returns, and restores a successful\n * transport.\n * @param fail - Makes the next send fail at the transport, so the suite can\n * check that the provider is loud about it.\n * @param context - A resolved context with a fixed timestamp.\n * @param target - An already-resolved vendor target. Defaults to `purchase`.\n *\n * @example\n * ```ts\n * describe('ga4 server conformance', () => {\n * conformsAsServerProvider({\n * create: () => ga4(credentials),\n * captured: () => fetchMock.mock.calls,\n * reset: () => { fetchMock.mockClear(); },\n * fail: () => { fetchMock.mockResolvedValueOnce(new Response(null, { status: 500 })); },\n * context,\n * });\n * });\n * ```\n */\nexport function conformsAsServerProvider(options: {\n create: () => ServerProvider;\n captured: () => unknown[];\n reset: () => void;\n fail: () => void;\n context: () => ResolvedContext;\n /** Already-resolved vendor target; conversion-ID providers use their configured ID. */\n target?: string;\n}): void {\n const { create, captured, reset, fail, context } = options;\n const target = options.target ?? 'purchase';\n const noop = (): void => {};\n\n assertIdentity(create());\n\n it('reports absent mappings on ignore providers and keeps explicit exclusions silent', async () => {\n const provider = create();\n if (provider.default !== 'ignore') return;\n reset();\n const errors: { severity: string; event: string }[] = [];\n const tracking = createServerTracking({\n providers: [{ ...provider, events: { explicit: null } }],\n onError: (error) => errors.push(error),\n });\n await tracking.track('missing', {}, context());\n await tracking.track('explicit', {}, context());\n expect(errors).toMatchObject([{ severity: 'warning', event: 'missing' }]);\n expect(captured()).toEqual([]);\n });\n\n it('sends the same thing twice for the same event', async () => {\n // The context carries a fixed timestamp, so any difference between these\n // two is something the provider produced on its own.\n reset();\n await create().track(target, STABLE_DATA, context(), noop);\n const first = JSON.stringify(captured());\n\n reset();\n await create().track(target, STABLE_DATA, context(), noop);\n\n expect(JSON.stringify(captured())).toBe(first);\n });\n\n it('forwards the deduplication id only when it was given one', async () => {\n reset();\n await create().track(target, STABLE_DATA, context(), noop);\n const without = JSON.stringify(captured());\n\n reset();\n await create().track(target, STABLE_DATA, { ...context(), dedupId: 'DEDUP-XYZ' }, noop);\n const with_ = JSON.stringify(captured());\n\n if (with_ !== without) expect(with_).toContain('DEDUP-XYZ');\n expect(without).not.toContain('DEDUP-XYZ');\n });\n\n it('throws a VendorResponseError when the vendor did not accept the send', async () => {\n // Adapters stay honest and loud. Swallowing a non-2xx is how a host ends\n // up believing it is measuring something, and the dispatcher is already\n // there to isolate the throw and report it. ADR-0013 adds the shape: what\n // reaches the host is recognised by `isVendorResponseError`, carrying the\n // status and the body, not just any throw. The body itself is\n // the caller's `fail()` hook to produce; a bodiless response is valid\n // (the documented example does exactly that), so it is not asserted here.\n reset();\n fail();\n\n let caught: unknown;\n try {\n await create().track(target, STABLE_DATA, context(), noop);\n } catch (error) {\n caught = error;\n }\n\n expect(isVendorResponseError(caught)).toBe(true);\n if (isVendorResponseError(caught)) {\n expect(typeof caught.status).toBe('number');\n expect(typeof caught.body).toBe('string');\n }\n });\n}\n"],"mappings":"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"}
|
|
@@ -21,12 +21,15 @@ function assertUniqueProviders(providers) {
|
|
|
21
21
|
* event identity is minted per account in its own dashboard, so there is no
|
|
22
22
|
* name that could possibly be guessed.
|
|
23
23
|
*/
|
|
24
|
-
function resolveEvent(provider, name) {
|
|
24
|
+
function resolveEvent(provider, name, report) {
|
|
25
25
|
const events = provider.events;
|
|
26
26
|
const binding = events && Object.hasOwn(events, name) ? events[name] : void 0;
|
|
27
27
|
if (binding === null) return void 0;
|
|
28
28
|
if (binding !== void 0) return binding;
|
|
29
|
-
if (provider.default === "ignore")
|
|
29
|
+
if (provider.default === "ignore") {
|
|
30
|
+
report?.("event omitted: no conversion mapping; add a binding or null for an explicit exclusion");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
30
33
|
return name;
|
|
31
34
|
}
|
|
32
35
|
/**
|
|
@@ -48,4 +51,4 @@ function messageOf(cause) {
|
|
|
48
51
|
//#endregion
|
|
49
52
|
export { resolveEvent as i, makeReporter as n, messageOf as r, assertUniqueProviders as t };
|
|
50
53
|
|
|
51
|
-
//# sourceMappingURL=dispatch-
|
|
54
|
+
//# sourceMappingURL=dispatch-B32jGhlh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch-B32jGhlh.js","names":[],"sources":["../src/dispatch.ts"],"sourcesContent":["import type { EventBinding, ProviderDefault, TrackingError } from './vocabulary.js';\n\n/** The part of a provider both libraries resolve event names against. */\ninterface Resolvable<Target> {\n name: string;\n default: ProviderDefault;\n events?: Record<string, EventBinding<Target>>;\n}\n\n/**\n * Registering one vendor twice is a hard error rather than a merge: there is\n * nothing a second registration could say that the first cannot, and\n * accepting both would double every event to that vendor, a defect that\n * surfaces weeks later as inflated conversions.\n */\nexport function assertUniqueProviders(providers: readonly { name: string }[]): void {\n const seen = new Set<string>();\n\n for (const provider of providers) {\n if (seen.has(provider.name)) {\n throw new Error(\n `tracklane: provider \"${provider.name}\" is registered twice. One configuration per vendor.`,\n );\n }\n seen.add(provider.name);\n }\n}\n\n/**\n * Resolves how a vendor spells a canonical event, or `undefined` when\n * nothing should be sent.\n *\n * Not sending is a legitimate outcome rather than a failure: `null` is the\n * host saying \"never send this event here\", and `ignore` is a vendor whose\n * event identity is minted per account in its own dashboard, so there is no\n * name that could possibly be guessed.\n */\nexport function resolveEvent<Target>(\n provider: Resolvable<Target>,\n name: string,\n report?: (message: string) => void,\n): Target | undefined {\n const events = provider.events;\n // `hasOwn`, not a plain lookup: every object inherits `constructor` and\n // `toString`, so an event named either would otherwise resolve to a\n // function off the prototype chain.\n const binding = events && Object.hasOwn(events, name) ? events[name] : undefined;\n\n if (binding === null) return undefined;\n if (binding !== undefined) return binding;\n if (provider.default === 'ignore') {\n report?.(\n 'event omitted: no conversion mapping; add a binding or null for an explicit exclusion',\n );\n return undefined;\n }\n\n return name as Target;\n}\n\n/**\n * Builds the reporting channel. A host that passes no `onError` gets\n * silence, which is what a hand-written vendor call gives it.\n */\nexport function makeReporter(\n onError: ((error: TrackingError) => void) | undefined,\n): (error: TrackingError) => void {\n if (!onError) return () => {};\n\n return (error) => {\n // A throwing reporter must not take the dispatch down with it, or a\n // host's broken logger becomes a broken checkout.\n try {\n onError(error);\n } catch {\n /* the reporter is the last line; there is nowhere left to report to */\n }\n };\n}\n\n/** Normalises whatever a provider threw into a reportable message. */\nexport function messageOf(cause: unknown): string {\n return cause instanceof Error ? cause.message : String(cause);\n}\n"],"mappings":";;;;;;;AAeA,SAAgB,sBAAsB,WAA8C;CAClF,MAAM,uBAAO,IAAI,IAAY;CAE7B,KAAK,MAAM,YAAY,WAAW;EAChC,IAAI,KAAK,IAAI,SAAS,IAAI,GACxB,MAAM,IAAI,MACR,wBAAwB,SAAS,KAAK,qDACxC;EAEF,KAAK,IAAI,SAAS,IAAI;CACxB;AACF;;;;;;;;;;AAWA,SAAgB,aACd,UACA,MACA,QACoB;CACpB,MAAM,SAAS,SAAS;CAIxB,MAAM,UAAU,UAAU,OAAO,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ,KAAA;CAEvE,IAAI,YAAY,MAAM,OAAO,KAAA;CAC7B,IAAI,YAAY,KAAA,GAAW,OAAO;CAClC,IAAI,SAAS,YAAY,UAAU;EACjC,SACE,uFACF;EACA;CACF;CAEA,OAAO;AACT;;;;;AAMA,SAAgB,aACd,SACgC;CAChC,IAAI,CAAC,SAAS,aAAa,CAAC;CAE5B,QAAQ,UAAU;EAGhB,IAAI;GACF,QAAQ,KAAK;EACf,QAAQ,CAER;CACF;AACF;;AAGA,SAAgB,UAAU,OAAwB;CAChD,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { B as VendorResponseError, C as EventData, D as UserData, E as TrackingError, S as EventBinding, T as ProviderDefault, V as isVendorResponseError, _ as ServerTracking, b as ActionSource, g as ServerProvider, h as ResolvedContext, i as TrackOptions, m as EventContext, n as BrowserTracking, r as BrowserTrackingOptions, t as BrowserProvider, v as ServerTrackingOptions, w as EventItem, x as ConsentState } from "./browser-34-CHcxK.js";
|
|
2
2
|
export { type ActionSource, type BrowserProvider, type BrowserTracking, type BrowserTrackingOptions, type ConsentState, type EventBinding, type EventContext, type EventData, type EventItem, type ProviderDefault, type ResolvedContext, type ServerProvider, type ServerTracking, type ServerTrackingOptions, type TrackOptions, type TrackingError, type UserData, VendorResponseError, isVendorResponseError };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as isVendorResponseError, t as VendorResponseError } from "./
|
|
1
|
+
import { n as isVendorResponseError, t as VendorResponseError } from "./src-DLRGPWMj.js";
|
|
2
2
|
export { VendorResponseError, isVendorResponseError };
|