@cross-deck/node 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/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@cross-deck/node` will be documented here. The
4
+ format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0] — 2026-05-12
8
+
9
+ Initial server SDK release.
10
+
11
+ ### Added
12
+
13
+ - Separate `@cross-deck/node` package with no browser assumptions.
14
+ - `CrossdeckServer` constructor with secret-key validation.
15
+ - Secret-key HTTP transport with typed `CrossdeckError` handling.
16
+ - Web-parity sanitisation for traits and event properties, plus a transport
17
+ backstop that converts serialization failures into stable `CrossdeckError`s.
18
+ - `identify()` / `aliasIdentity()` for server-side identity linking.
19
+ - `forget()` for server-side GDPR/CCPA deletion requests.
20
+ - `getEntitlements()` by `customerId`, `userId`, or `anonymousId`.
21
+ - `getCustomerEntitlements(customerId)` server-only direct lookup route.
22
+ - `track()` and `ingest()` for explicit server-side event ingest.
23
+ - `syncPurchases()` for Apple signed purchase forwarding.
24
+ - `grantEntitlement()` and `revokeEntitlement()` server-side manual overrides.
25
+ - `getAuditEntry()` for server-side audit-log reads.
26
+ - Dual ESM/CJS build.
27
+ - Strict TypeScript + Vitest coverage for transport and public method routing.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Crossdeck
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,238 @@
1
+ # @cross-deck/node
2
+
3
+ The Crossdeck server SDK for Node.js.
4
+
5
+ This is the **secret-key** SDK: server-only, no browser assumptions, no
6
+ auto-tracking, no local state. It wraps the real HTTP surface for
7
+ entitlements, identity aliasing, event ingest, purchase forwarding, manual
8
+ entitlement overrides, and audit reads.
9
+
10
+ ```bash
11
+ npm install @cross-deck/node
12
+ ```
13
+
14
+ ## Quick start
15
+
16
+ ```ts
17
+ import { CrossdeckServer } from "@cross-deck/node";
18
+
19
+ const crossdeck = new CrossdeckServer({
20
+ secretKey: process.env.CROSSDECK_SECRET_KEY!,
21
+ });
22
+
23
+ const entitlements = await crossdeck.getEntitlements({ userId: "user_847" });
24
+
25
+ await crossdeck.track({
26
+ name: "invoice.retry_started",
27
+ developerUserId: "user_847",
28
+ properties: { invoiceId: "inv_123" },
29
+ });
30
+ ```
31
+
32
+ ## What this SDK is for
33
+
34
+ - **Server-side entitlement reads.** Query by `customerId`, `userId`, or `anonymousId`.
35
+ - **Identity graph writes.** Alias a known `anonymousId` to your stable `userId`.
36
+ - **Telemetry from jobs and backends.** Send events from cron jobs, webhooks, workers, and admin tools.
37
+ - **Fast purchase forwarding.** Push signed Apple purchase evidence directly.
38
+ - **Manual entitlement controls.** Grant or revoke an entitlement with a reason.
39
+ - **Audit lookup.** Read one audit-log entry by event ID.
40
+
41
+ ## What this SDK is not
42
+
43
+ - Not for browsers.
44
+ - Not a wrapper around the web SDK.
45
+ - Not a stateful singleton that keeps identity or a queue in memory.
46
+ - Not an auto-tracker. Every server call is explicit.
47
+
48
+ ## Constructing the client
49
+
50
+ ```ts
51
+ import { CrossdeckServer } from "@cross-deck/node";
52
+
53
+ const crossdeck = new CrossdeckServer({
54
+ secretKey: process.env.CROSSDECK_SECRET_KEY!,
55
+ baseUrl: "https://api.cross-deck.com/v1", // optional
56
+ timeoutMs: 15_000, // optional
57
+ appId: "app_web_xxx", // optional informational event envelope field
58
+ });
59
+ ```
60
+
61
+ `secretKey` must start with `cd_sk_`. The constructor throws immediately on
62
+ an invalid key prefix so a server misconfiguration fails at boot, not under
63
+ load.
64
+
65
+ ## Identity
66
+
67
+ ### `await crossdeck.identify(userId, anonymousId, options?)`
68
+
69
+ Alias a pre-login `anonymousId` to your stable user ID. This is the same
70
+ identity graph the web SDK uses, just called explicitly from your backend.
71
+
72
+ ```ts
73
+ await crossdeck.identify("user_847", "anon_123", {
74
+ email: "wes@example.com",
75
+ traits: { plan: "pro", region: "za" },
76
+ });
77
+ ```
78
+
79
+ `identify()` is a convenience alias for `aliasIdentity(...)`.
80
+
81
+ Traits are sanitised before send with the same rules as `@cross-deck/web`:
82
+ `BigInt` becomes a string, circular refs become `"[circular]"`, `Map`/`Set`
83
+ normalise to JSON-friendly shapes, and functions/symbols/`undefined` are dropped.
84
+
85
+ ### `await crossdeck.forget(hints)`
86
+
87
+ Queue GDPR/CCPA deletion by `customerId`, `userId`, or `anonymousId`.
88
+
89
+ ```ts
90
+ await crossdeck.forget({ customerId: "cdcust_123" });
91
+ ```
92
+
93
+ ## Entitlements
94
+
95
+ ### `await crossdeck.getEntitlements(hints)`
96
+
97
+ Read entitlements by any supported identity hint.
98
+
99
+ ```ts
100
+ const result = await crossdeck.getEntitlements({ userId: "user_847" });
101
+ console.log(result.data.map((e) => e.key));
102
+ ```
103
+
104
+ ### `await crossdeck.getCustomerEntitlements(customerId)`
105
+
106
+ Server-only direct lookup by canonical Crossdeck customer ID.
107
+
108
+ ```ts
109
+ const result = await crossdeck.getCustomerEntitlements("cdcust_123");
110
+ ```
111
+
112
+ ### `await crossdeck.grantEntitlement(input)`
113
+
114
+ Manually grant an entitlement.
115
+
116
+ ```ts
117
+ await crossdeck.grantEntitlement({
118
+ customerId: "cdcust_123",
119
+ entitlementKey: "pro",
120
+ duration: "P30D",
121
+ reason: "Support recovery after billing incident",
122
+ });
123
+ ```
124
+
125
+ ### `await crossdeck.revokeEntitlement(input)`
126
+
127
+ Manually revoke an entitlement.
128
+
129
+ ```ts
130
+ await crossdeck.revokeEntitlement({
131
+ customerId: "cdcust_123",
132
+ entitlementKey: "pro",
133
+ reason: "Chargeback",
134
+ });
135
+ ```
136
+
137
+ ## Events
138
+
139
+ ### `await crossdeck.track(event)`
140
+
141
+ Send one event immediately.
142
+
143
+ ```ts
144
+ await crossdeck.track({
145
+ name: "support.refund_issued",
146
+ crossdeckCustomerId: "cdcust_123",
147
+ properties: { ticketId: "ticket_987" },
148
+ });
149
+ ```
150
+
151
+ Identity is required on every event. Provide at least one of:
152
+
153
+ - `developerUserId`
154
+ - `anonymousId`
155
+ - `crossdeckCustomerId`
156
+
157
+ ### `await crossdeck.ingest(events)`
158
+
159
+ Send a batch in one call.
160
+
161
+ ```ts
162
+ await crossdeck.ingest([
163
+ {
164
+ name: "job.started",
165
+ developerUserId: "user_847",
166
+ properties: { job: "daily-mrr-reconcile" },
167
+ },
168
+ {
169
+ name: "job.completed",
170
+ developerUserId: "user_847",
171
+ properties: { job: "daily-mrr-reconcile", durationMs: 842 },
172
+ },
173
+ ]);
174
+ ```
175
+
176
+ The SDK auto-mints `eventId` and `timestamp` if you omit them.
177
+
178
+ Event `properties` are sanitised with the same contract as the web SDK before
179
+ they hit the wire, so one bad backend-shaped object cannot crash request
180
+ serialization.
181
+
182
+ ## Purchases
183
+
184
+ ### `await crossdeck.syncPurchases(input)`
185
+
186
+ Forward Apple signed purchase evidence to Crossdeck.
187
+
188
+ ```ts
189
+ await crossdeck.syncPurchases({
190
+ signedTransactionInfo: transactionJws,
191
+ signedRenewalInfo: renewalJws,
192
+ });
193
+ ```
194
+
195
+ ## Audit
196
+
197
+ ### `await crossdeck.getAuditEntry(eventId)`
198
+
199
+ Read one audit row by event ID.
200
+
201
+ ```ts
202
+ const audit = await crossdeck.getAuditEntry("srv_grant_123");
203
+ console.log(audit.decision, audit.reason);
204
+ ```
205
+
206
+ ## Errors
207
+
208
+ Every non-2xx response is normalised to `CrossdeckError`:
209
+
210
+ ```ts
211
+ import { CrossdeckError } from "@cross-deck/node";
212
+
213
+ try {
214
+ await crossdeck.getEntitlements({ userId: "user_847" });
215
+ } catch (err) {
216
+ if (err instanceof CrossdeckError) {
217
+ console.error(err.type, err.code, err.requestId);
218
+ }
219
+ }
220
+ ```
221
+
222
+ The error fields mirror the backend envelope:
223
+
224
+ - `type`
225
+ - `code`
226
+ - `message`
227
+ - `requestId`
228
+ - `status`
229
+ - `retryAfterMs`
230
+
231
+ ## Node version
232
+
233
+ Node 18+ is required. The SDK uses the platform `fetch` implementation and
234
+ does not ship an HTTP dependency.
235
+
236
+ ## License
237
+
238
+ MIT