@cowliss/sdk 0.1.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/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +113 -0
- package/dist/types.d.ts +131 -0
- package/dist/types.js +27 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bogdan Radu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @cowliss/sdk
|
|
2
|
+
|
|
3
|
+
The TypeScript client for the Cowliss ingestion API. Send `identify` and
|
|
4
|
+
`track` from your app, and Cowliss keeps the profiles, the events, the
|
|
5
|
+
segments, and the journeys that follow from them.
|
|
6
|
+
|
|
7
|
+
Runs anywhere `fetch` does: Node 22 and up, Bun, Deno, edge runtimes and
|
|
8
|
+
browsers. Zero dependencies, ESM only, fully typed.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @cowliss/sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Use it
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { Cow } from "@cowliss/sdk";
|
|
20
|
+
|
|
21
|
+
const cow = new Cow({
|
|
22
|
+
apiKey: process.env.COW_API_KEY!,
|
|
23
|
+
sourceId: process.env.COW_SOURCE_ID!,
|
|
24
|
+
baseUrl: "https://api.cowliss.com",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await cow.identify({
|
|
28
|
+
identifiers: { userId: "u_1", email: "ada@example.com" },
|
|
29
|
+
traits: { plan: "pro" },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await cow.track({
|
|
33
|
+
identifiers: { userId: "u_1" },
|
|
34
|
+
event: "checkout.completed",
|
|
35
|
+
properties: { amount: 4900, currency: "usd" },
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Every call carries `identifiers`, a map from kind to value. Cowliss generates
|
|
40
|
+
the profile id and resolves the map to it, so a call that carries two
|
|
41
|
+
identifiers is what links two profiles. There is no alias call.
|
|
42
|
+
|
|
43
|
+
`identify` returns the profile, `track` returns the stored event, and
|
|
44
|
+
`batch` imports many of both in one request for backfills. Each call names
|
|
45
|
+
the source it arrives through: set `sourceId` once on the client, or pass one
|
|
46
|
+
per call when a single process writes into more than one app.
|
|
47
|
+
|
|
48
|
+
Retries are handled for you. A call that fails to reach Cowliss, or that
|
|
49
|
+
Cowliss could not complete, backs off and tries twice more; a generated
|
|
50
|
+
`messageId` travels with every track, so a retry after a lost response never
|
|
51
|
+
writes the event twice. A call Cowliss rejected is not retried: it throws a
|
|
52
|
+
`CowError` carrying the reason.
|
|
53
|
+
|
|
54
|
+
## Docs
|
|
55
|
+
|
|
56
|
+
[docs.cowliss.com](https://docs.cowliss.com)
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { BatchBody, BatchResultDto, ErrorCode, EventDto, IdentifyInput, ProfileDto, TrackInput } from "./types.js";
|
|
2
|
+
export type * from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* The Cowliss TS client SDK. Small typed surface over the ingestion API:
|
|
5
|
+
* cow.identify / .track / .batch. Every call carries `identifiers` (a map
|
|
6
|
+
* from kind to value; Cowliss generates the profile id and resolves them), so
|
|
7
|
+
* there is no alias call: linking happens whenever one call carries two
|
|
8
|
+
* identifiers. Every call also names the source it arrives through: give the
|
|
9
|
+
* constructor a `sourceId` and the calls inherit it, or name one per call
|
|
10
|
+
* when one process writes into more than one app. messageIds are generated
|
|
11
|
+
* automatically (and double as the Idempotency-Key, so a retry after a lost
|
|
12
|
+
* response dedupes instead of double-writing); network errors and 5xx retry with exponential backoff;
|
|
13
|
+
* 4xx surface as typed CowErrors with the API's error-code enum, never
|
|
14
|
+
* retried.
|
|
15
|
+
*/
|
|
16
|
+
export declare class CowError extends Error {
|
|
17
|
+
readonly code: ErrorCode | "network_error";
|
|
18
|
+
readonly status: number;
|
|
19
|
+
constructor(code: ErrorCode | "network_error", message: string, status: number, options?: {
|
|
20
|
+
cause?: unknown;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export type CowOptions = {
|
|
24
|
+
/** Org ingestion API key ("Bearer" credential on every request). */
|
|
25
|
+
apiKey: string;
|
|
26
|
+
/**
|
|
27
|
+
* The `src_` source every call arrives through, when the caller does not
|
|
28
|
+
* name one per call. An app's source is a deployment fact, not a per-call
|
|
29
|
+
* one, so the common case is one client per app configured once here; a
|
|
30
|
+
* process that writes into two apps passes `sourceId` per call (or holds
|
|
31
|
+
* two clients) and this default stays out of the way.
|
|
32
|
+
*/
|
|
33
|
+
sourceId?: string;
|
|
34
|
+
/** API origin, no trailing slash. Defaults to local dev. */
|
|
35
|
+
baseUrl?: string;
|
|
36
|
+
/** Retries after the initial attempt on network errors / 5xx. Default 2. */
|
|
37
|
+
maxRetries?: number;
|
|
38
|
+
/** Injectable for tests and non-fetch runtimes. */
|
|
39
|
+
fetch?: typeof globalThis.fetch;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The per-call shape: `sourceId` is optional here and required on the wire,
|
|
43
|
+
* so a client constructed with one takes calls without it. `#sourceFor`
|
|
44
|
+
* puts it back before the request, or fails naming both ways to supply it.
|
|
45
|
+
*/
|
|
46
|
+
type WithDefaultSource<T extends {
|
|
47
|
+
sourceId: string;
|
|
48
|
+
}> = Omit<T, "sourceId"> & {
|
|
49
|
+
sourceId?: string;
|
|
50
|
+
};
|
|
51
|
+
export declare class Cow {
|
|
52
|
+
#private;
|
|
53
|
+
constructor(options: CowOptions);
|
|
54
|
+
identify(input: WithDefaultSource<IdentifyInput>): Promise<ProfileDto>;
|
|
55
|
+
/**
|
|
56
|
+
* Tracks one event. A messageId is generated when the caller does not
|
|
57
|
+
* supply one, and sent both in the body and as the Idempotency-Key, so a
|
|
58
|
+
* retried request (network error, 5xx, or lost response) dedupes
|
|
59
|
+
* server-side. Historical timestamps pass through for backfill scripts.
|
|
60
|
+
*/
|
|
61
|
+
track(input: WithDefaultSource<TrackInput>): Promise<EventDto>;
|
|
62
|
+
/**
|
|
63
|
+
* Batch import: one source's identify and track calls in one request.
|
|
64
|
+
* Track items must carry an explicit messageId: the API requires it and
|
|
65
|
+
* the SDK does not generate one here, so re-running the same backfill
|
|
66
|
+
* (same messageIds) dedupes instead of double-writing.
|
|
67
|
+
*/
|
|
68
|
+
batch(input: WithDefaultSource<BatchBody["data"]>): Promise<BatchResultDto>;
|
|
69
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { CLIENT_HEADER, SDK_VERSION } from "./types.js";
|
|
2
|
+
/** What this client calls itself in {@link CLIENT_HEADER}. */
|
|
3
|
+
const CLIENT_ID = `@cowliss/sdk/${SDK_VERSION}`;
|
|
4
|
+
/**
|
|
5
|
+
* The Cowliss TS client SDK. Small typed surface over the ingestion API:
|
|
6
|
+
* cow.identify / .track / .batch. Every call carries `identifiers` (a map
|
|
7
|
+
* from kind to value; Cowliss generates the profile id and resolves them), so
|
|
8
|
+
* there is no alias call: linking happens whenever one call carries two
|
|
9
|
+
* identifiers. Every call also names the source it arrives through: give the
|
|
10
|
+
* constructor a `sourceId` and the calls inherit it, or name one per call
|
|
11
|
+
* when one process writes into more than one app. messageIds are generated
|
|
12
|
+
* automatically (and double as the Idempotency-Key, so a retry after a lost
|
|
13
|
+
* response dedupes instead of double-writing); network errors and 5xx retry with exponential backoff;
|
|
14
|
+
* 4xx surface as typed CowErrors with the API's error-code enum, never
|
|
15
|
+
* retried.
|
|
16
|
+
*/
|
|
17
|
+
export class CowError extends Error {
|
|
18
|
+
code;
|
|
19
|
+
status;
|
|
20
|
+
constructor(code, message, status, options) {
|
|
21
|
+
super(message, options);
|
|
22
|
+
this.name = "CowError";
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.status = status;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const RETRY_BASE_DELAY_MS = 100;
|
|
28
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
29
|
+
export class Cow {
|
|
30
|
+
#apiKey;
|
|
31
|
+
#sourceId;
|
|
32
|
+
#baseUrl;
|
|
33
|
+
#maxRetries;
|
|
34
|
+
#fetch;
|
|
35
|
+
constructor(options) {
|
|
36
|
+
this.#apiKey = options.apiKey;
|
|
37
|
+
this.#sourceId = options.sourceId;
|
|
38
|
+
this.#baseUrl = options.baseUrl ?? "http://localhost:3400";
|
|
39
|
+
this.#maxRetries = options.maxRetries ?? 2;
|
|
40
|
+
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The call's own `sourceId`, else the client's. Throws when neither, from
|
|
44
|
+
* inside an async method so the caller sees a rejected promise like every
|
|
45
|
+
* other failure rather than a synchronous throw a `.catch()` would miss.
|
|
46
|
+
*/
|
|
47
|
+
#sourceFor(input) {
|
|
48
|
+
const sourceId = input.sourceId ?? this.#sourceId;
|
|
49
|
+
if (!sourceId) {
|
|
50
|
+
throw new CowError("validation_failed", "No sourceId: pass one to the Cow constructor, or name one on the call.", 0);
|
|
51
|
+
}
|
|
52
|
+
return sourceId;
|
|
53
|
+
}
|
|
54
|
+
async identify(input) {
|
|
55
|
+
return this.#post("/v1/identify", {
|
|
56
|
+
data: { ...input, sourceId: this.#sourceFor(input) },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Tracks one event. A messageId is generated when the caller does not
|
|
61
|
+
* supply one, and sent both in the body and as the Idempotency-Key, so a
|
|
62
|
+
* retried request (network error, 5xx, or lost response) dedupes
|
|
63
|
+
* server-side. Historical timestamps pass through for backfill scripts.
|
|
64
|
+
*/
|
|
65
|
+
async track(input) {
|
|
66
|
+
const messageId = input.messageId ?? crypto.randomUUID();
|
|
67
|
+
return this.#post("/v1/track", { data: { ...input, sourceId: this.#sourceFor(input), messageId } }, messageId);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Batch import: one source's identify and track calls in one request.
|
|
71
|
+
* Track items must carry an explicit messageId: the API requires it and
|
|
72
|
+
* the SDK does not generate one here, so re-running the same backfill
|
|
73
|
+
* (same messageIds) dedupes instead of double-writing.
|
|
74
|
+
*/
|
|
75
|
+
async batch(input) {
|
|
76
|
+
return this.#post("/v1/batch", {
|
|
77
|
+
data: { ...input, sourceId: this.#sourceFor(input) },
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async #post(path, body, idempotencyKey) {
|
|
81
|
+
for (let attempt = 0;; attempt++) {
|
|
82
|
+
let res;
|
|
83
|
+
try {
|
|
84
|
+
res = await this.#fetch(`${this.#baseUrl}${path}`, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: {
|
|
87
|
+
Authorization: `Bearer ${this.#apiKey}`,
|
|
88
|
+
"Content-Type": "application/json",
|
|
89
|
+
[CLIENT_HEADER]: CLIENT_ID,
|
|
90
|
+
...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify(body),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (cause) {
|
|
96
|
+
if (attempt < this.#maxRetries) {
|
|
97
|
+
await sleep(RETRY_BASE_DELAY_MS * 2 ** attempt);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
throw new CowError("network_error", "Network error", 0, { cause });
|
|
101
|
+
}
|
|
102
|
+
if (res.ok) {
|
|
103
|
+
return (await res.json()).data;
|
|
104
|
+
}
|
|
105
|
+
if (res.status >= 500 && attempt < this.#maxRetries) {
|
|
106
|
+
await sleep(RETRY_BASE_DELAY_MS * 2 ** attempt);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const parsed = (await res.json().catch(() => null));
|
|
110
|
+
throw new CowError(parsed?.error?.code ?? "internal", parsed?.error?.message ?? `Request failed with status ${res.status}`, res.status);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SDK's public types, hand-declared.
|
|
3
|
+
*
|
|
4
|
+
* They mirror `@cowliss/shared`, and they do not import it. The shared ones
|
|
5
|
+
* are `z.infer` over drizzle-zod schemas, so a declaration that referenced
|
|
6
|
+
* them would make drizzle-orm a type dependency of an ingestion client and
|
|
7
|
+
* would name a package a customer has never installed (which `skipLibCheck`
|
|
8
|
+
* would degrade to `any` in silence rather than report). Plain object types
|
|
9
|
+
* keep the SDK's `dist` free of both.
|
|
10
|
+
*
|
|
11
|
+
* `types.check.ts` asserts each of these is mutually assignable with the
|
|
12
|
+
* shared type it mirrors, so a wire-shape change fails the monorepo
|
|
13
|
+
* typecheck here rather than at a customer's call site.
|
|
14
|
+
*/
|
|
15
|
+
/** The API's error-code enum, as the SDK surfaces it on `CowError.code`. */
|
|
16
|
+
export type ErrorCode = "malformed_request" | "invalid_key" | "forbidden" | "not_found" | "conflict" | "payload_too_large" | "validation_failed" | "trait_validation_failed" | "event_denied" | "trait_denied" | "over_quota" | "rate_limited" | "dependency_unavailable" | "internal" | "journey_fuel_exceeded" | "journey_timeout" | "journey_memory_exceeded" | "journey_error" | "journey_output_invalid" | "journey_nondeterministic" | "release_invalid" | "release_not_ready" | "project_exists" | "project_missing" | "artifact_too_large" | "deploy_key_scope";
|
|
17
|
+
export type Environment = "development" | "production";
|
|
18
|
+
export type IdentifierKind = "anonymousId" | "userId" | "clerkId" | "email";
|
|
19
|
+
/**
|
|
20
|
+
* The named identifiers a call carries, at least one. Cowliss generates the
|
|
21
|
+
* profile id and resolves the map to it; a call carrying two identifiers is
|
|
22
|
+
* what links two profiles, so there is no alias call.
|
|
23
|
+
*/
|
|
24
|
+
export type Identifiers = Partial<Record<IdentifierKind, string>>;
|
|
25
|
+
export type IdentifierDto = {
|
|
26
|
+
kind: IdentifierKind;
|
|
27
|
+
value: string;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
};
|
|
30
|
+
/** A person, as identify returns them. */
|
|
31
|
+
export type ProfileDto = {
|
|
32
|
+
id: string;
|
|
33
|
+
orgId: string;
|
|
34
|
+
environment: Environment;
|
|
35
|
+
/** The app that first created the profile. */
|
|
36
|
+
appId: string;
|
|
37
|
+
/** The source the creating write arrived through. */
|
|
38
|
+
sourceId: string;
|
|
39
|
+
traits: Record<string, unknown>;
|
|
40
|
+
consent: Record<string, boolean>;
|
|
41
|
+
/** Set on a profile that was merged away, naming the survivor. */
|
|
42
|
+
mergedInto: string | null;
|
|
43
|
+
createdAt: string;
|
|
44
|
+
updatedAt: string;
|
|
45
|
+
identifiers: IdentifierDto[];
|
|
46
|
+
/** True when every identifier is an anonymousId. */
|
|
47
|
+
anonymous: boolean;
|
|
48
|
+
};
|
|
49
|
+
/** One stored event, as track returns it. */
|
|
50
|
+
export type EventDto = {
|
|
51
|
+
id: string;
|
|
52
|
+
orgId: string;
|
|
53
|
+
environment: Environment;
|
|
54
|
+
appId: string;
|
|
55
|
+
sourceId: string;
|
|
56
|
+
profileId: string;
|
|
57
|
+
event: string;
|
|
58
|
+
properties: Record<string, unknown>;
|
|
59
|
+
timestamp: string;
|
|
60
|
+
receivedAt: string;
|
|
61
|
+
messageId: string | null;
|
|
62
|
+
};
|
|
63
|
+
export type IdentifyInput = {
|
|
64
|
+
sourceId: string;
|
|
65
|
+
identifiers: Identifiers;
|
|
66
|
+
traits?: Record<string, unknown>;
|
|
67
|
+
/** ISO 8601 UTC. A past timestamp backdates a first-write profile. */
|
|
68
|
+
timestamp?: string;
|
|
69
|
+
messageId?: string;
|
|
70
|
+
};
|
|
71
|
+
export type TrackInput = {
|
|
72
|
+
sourceId: string;
|
|
73
|
+
identifiers: Identifiers;
|
|
74
|
+
event: string;
|
|
75
|
+
properties?: Record<string, unknown>;
|
|
76
|
+
/** ISO 8601 UTC. A past timestamp is the event time, for backfill. */
|
|
77
|
+
timestamp?: string;
|
|
78
|
+
/** Generated by the SDK when omitted; also sent as the Idempotency-Key. */
|
|
79
|
+
messageId?: string;
|
|
80
|
+
};
|
|
81
|
+
/** One batch item: an identify or a track, minus the batch's own source. */
|
|
82
|
+
export type BatchItem = ({
|
|
83
|
+
type: "identify";
|
|
84
|
+
} & Omit<IdentifyInput, "sourceId">) | ({
|
|
85
|
+
type: "track";
|
|
86
|
+
} & Omit<TrackInput, "sourceId"> & {
|
|
87
|
+
messageId: string;
|
|
88
|
+
});
|
|
89
|
+
export type BatchBody = {
|
|
90
|
+
data: {
|
|
91
|
+
sourceId: string;
|
|
92
|
+
/**
|
|
93
|
+
* Validated per item server-side, so one bad row never fails the import:
|
|
94
|
+
* the wire schema accepts anything here and reports item problems in the
|
|
95
|
+
* response. `BatchItem` is what a caller should send.
|
|
96
|
+
*/
|
|
97
|
+
items: unknown[];
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
export type BatchItemResult = {
|
|
101
|
+
ok: true;
|
|
102
|
+
type: "identify";
|
|
103
|
+
id: string;
|
|
104
|
+
} | {
|
|
105
|
+
ok: true;
|
|
106
|
+
type: "track";
|
|
107
|
+
id: string;
|
|
108
|
+
deduped: boolean;
|
|
109
|
+
} | {
|
|
110
|
+
ok: false;
|
|
111
|
+
error: {
|
|
112
|
+
code: ErrorCode;
|
|
113
|
+
message: string;
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
export type BatchResultDto = {
|
|
117
|
+
results: BatchItemResult[];
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* The two runtime constants in this file, and the reason they live beside
|
|
121
|
+
* the types rather than in `@cowliss/shared`: this module is the SDK's one
|
|
122
|
+
* mirror of the shared vocabulary (`docs/standards/code.md`, "Central
|
|
123
|
+
* constants"), because a published package may not name an unpublished one.
|
|
124
|
+
*
|
|
125
|
+
* `CLIENT_HEADER` is pinned against the shared constant by `types.check.ts`.
|
|
126
|
+
* `SDK_VERSION` mirrors this package's own `version` instead, which no
|
|
127
|
+
* compiler can read from here: `apps/api/src/sdk.test.ts` is what fails when
|
|
128
|
+
* a release bumps one and not the other.
|
|
129
|
+
*/
|
|
130
|
+
export declare const CLIENT_HEADER = "X-Cow-Client";
|
|
131
|
+
export declare const SDK_VERSION = "0.1.0";
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SDK's public types, hand-declared.
|
|
3
|
+
*
|
|
4
|
+
* They mirror `@cowliss/shared`, and they do not import it. The shared ones
|
|
5
|
+
* are `z.infer` over drizzle-zod schemas, so a declaration that referenced
|
|
6
|
+
* them would make drizzle-orm a type dependency of an ingestion client and
|
|
7
|
+
* would name a package a customer has never installed (which `skipLibCheck`
|
|
8
|
+
* would degrade to `any` in silence rather than report). Plain object types
|
|
9
|
+
* keep the SDK's `dist` free of both.
|
|
10
|
+
*
|
|
11
|
+
* `types.check.ts` asserts each of these is mutually assignable with the
|
|
12
|
+
* shared type it mirrors, so a wire-shape change fails the monorepo
|
|
13
|
+
* typecheck here rather than at a customer's call site.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The two runtime constants in this file, and the reason they live beside
|
|
17
|
+
* the types rather than in `@cowliss/shared`: this module is the SDK's one
|
|
18
|
+
* mirror of the shared vocabulary (`docs/standards/code.md`, "Central
|
|
19
|
+
* constants"), because a published package may not name an unpublished one.
|
|
20
|
+
*
|
|
21
|
+
* `CLIENT_HEADER` is pinned against the shared constant by `types.check.ts`.
|
|
22
|
+
* `SDK_VERSION` mirrors this package's own `version` instead, which no
|
|
23
|
+
* compiler can read from here: `apps/api/src/sdk.test.ts` is what fails when
|
|
24
|
+
* a release bumps one and not the other.
|
|
25
|
+
*/
|
|
26
|
+
export const CLIENT_HEADER = "X-Cow-Client";
|
|
27
|
+
export const SDK_VERSION = "0.1.0";
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cowliss/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"default": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@cowliss/shared": "0.0.0",
|
|
13
|
+
"@types/node": "^26.2.0"
|
|
14
|
+
},
|
|
15
|
+
"description": "TypeScript client for the Cowliss ingestion API: identify, track and batch.",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/bogdanrn/cowliss.git",
|
|
20
|
+
"directory": "packages/sdk"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://docs.cowliss.com",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc -b tsconfig.json --force",
|
|
36
|
+
"typecheck": "tsc -b tsconfig.json tsconfig.check.json"
|
|
37
|
+
}
|
|
38
|
+
}
|