@appmachina/node 0.0.1 → 2.2.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 +427 -0
- package/dist/express-CeETK4sI.js +123 -0
- package/dist/express-CeETK4sI.js.map +1 -0
- package/dist/express-D5w2Pl15.d.ts +377 -0
- package/dist/express-D5w2Pl15.d.ts.map +1 -0
- package/dist/express.d.ts +3 -0
- package/dist/express.js +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +550 -0
- package/dist/index.js.map +1 -0
- package/dist/nextjs.d.ts +59 -0
- package/dist/nextjs.d.ts.map +1 -0
- package/dist/nextjs.js +112 -0
- package/dist/nextjs.js.map +1 -0
- package/dist/persistence-ChBsyTkr.d.ts +22 -0
- package/dist/persistence-ChBsyTkr.d.ts.map +1 -0
- package/dist/persistence-D_zFNklE.js +47 -0
- package/dist/persistence-D_zFNklE.js.map +1 -0
- package/dist/persistence.d.ts +2 -0
- package/dist/persistence.js +3 -0
- package/package.json +52 -5
- package/index.js +0 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import { t as appMachinaDebugMiddleware } from "./express-CeETK4sI.js";
|
|
2
|
+
import { t as FilePersistence } from "./persistence-D_zFNklE.js";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { AppMachinaCore, AppMachinaError, FetchHttpClient } from "@appmachina/core-wasm";
|
|
7
|
+
|
|
8
|
+
//#region src/standard-events.ts
|
|
9
|
+
/**
|
|
10
|
+
* Predefined standard event name constants.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { StandardEvents } from '@appmachina/node';
|
|
15
|
+
* appmachina.track(distinctId, StandardEvents.PURCHASE, { amount: 9.99 });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
const StandardEvents = {
|
|
19
|
+
APP_INSTALL: "app_install",
|
|
20
|
+
APP_OPEN: "app_open",
|
|
21
|
+
LOGIN: "login",
|
|
22
|
+
SIGN_UP: "sign_up",
|
|
23
|
+
REGISTER: "register",
|
|
24
|
+
PURCHASE: "purchase_success",
|
|
25
|
+
ADD_TO_CART: "add_to_cart",
|
|
26
|
+
ADD_TO_WISHLIST: "add_to_wishlist",
|
|
27
|
+
INITIATE_CHECKOUT: "initiate_checkout",
|
|
28
|
+
BEGIN_CHECKOUT: "begin_checkout",
|
|
29
|
+
START_TRIAL: "start_trial",
|
|
30
|
+
SUBSCRIBE: "subscribe",
|
|
31
|
+
LEVEL_START: "level_start",
|
|
32
|
+
LEVEL_COMPLETE: "level_complete",
|
|
33
|
+
TUTORIAL_COMPLETE: "tutorial_complete",
|
|
34
|
+
SEARCH: "search",
|
|
35
|
+
VIEW_ITEM: "view_item",
|
|
36
|
+
VIEW_CONTENT: "view_content",
|
|
37
|
+
SHARE: "share",
|
|
38
|
+
DEEP_LINK: "deep_link_opened",
|
|
39
|
+
SCREEN_VIEW: "screen_view"
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/commerce.ts
|
|
44
|
+
/**
|
|
45
|
+
* Track a successful purchase for a specific user.
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { trackPurchase } from '@appmachina/node';
|
|
49
|
+
*
|
|
50
|
+
* trackPurchase(sdk, 'user_123', {
|
|
51
|
+
* productId: 'premium_monthly',
|
|
52
|
+
* price: 9.99,
|
|
53
|
+
* currency: 'USD',
|
|
54
|
+
* transactionId: 'txn_abc123',
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
function trackPurchase(sdk, distinctId, params) {
|
|
59
|
+
const quantity = params.quantity ?? 1;
|
|
60
|
+
const props = {
|
|
61
|
+
product_id: params.productId,
|
|
62
|
+
price: params.price,
|
|
63
|
+
currency: params.currency,
|
|
64
|
+
quantity,
|
|
65
|
+
revenue: params.price * quantity,
|
|
66
|
+
...params.properties
|
|
67
|
+
};
|
|
68
|
+
if (params.transactionId !== void 0) props.transaction_id = params.transactionId;
|
|
69
|
+
if (params.isRestored !== void 0) props.is_restored = params.isRestored;
|
|
70
|
+
if (params.store !== void 0) props.store = params.store;
|
|
71
|
+
sdk.track(distinctId, "purchase_success", props);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Track a failed purchase attempt for a specific user.
|
|
75
|
+
*/
|
|
76
|
+
function trackPurchaseFailed(sdk, distinctId, params) {
|
|
77
|
+
const props = {
|
|
78
|
+
product_id: params.productId,
|
|
79
|
+
currency: params.currency,
|
|
80
|
+
error_code: params.errorCode,
|
|
81
|
+
...params.properties
|
|
82
|
+
};
|
|
83
|
+
if (params.errorMessage !== void 0) props.error_message = params.errorMessage;
|
|
84
|
+
sdk.track(distinctId, "purchase_failed", props);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Track a subscription purchase or renewal for a specific user.
|
|
88
|
+
*
|
|
89
|
+
* ```ts
|
|
90
|
+
* import { trackSubscription } from '@appmachina/node';
|
|
91
|
+
*
|
|
92
|
+
* trackSubscription(sdk, 'user_123', {
|
|
93
|
+
* productId: 'pro_annual',
|
|
94
|
+
* price: 49.99,
|
|
95
|
+
* currency: 'USD',
|
|
96
|
+
* period: 'P1Y',
|
|
97
|
+
* });
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
function trackSubscription(sdk, distinctId, params) {
|
|
101
|
+
const props = {
|
|
102
|
+
product_id: params.productId,
|
|
103
|
+
price: params.price,
|
|
104
|
+
currency: params.currency,
|
|
105
|
+
quantity: 1,
|
|
106
|
+
revenue: params.price,
|
|
107
|
+
...params.properties
|
|
108
|
+
};
|
|
109
|
+
if (params.transactionId !== void 0) props.transaction_id = params.transactionId;
|
|
110
|
+
if (params.period !== void 0) props.period = params.period;
|
|
111
|
+
if (params.isRenewal !== void 0) props.is_renewal = params.isRenewal;
|
|
112
|
+
if (params.isTrial !== void 0) props.is_trial = params.isTrial;
|
|
113
|
+
if (params.subscriptionGroupId !== void 0) props.subscription_group_id = params.subscriptionGroupId;
|
|
114
|
+
if (params.originalTransactionId !== void 0) props.original_transaction_id = params.originalTransactionId;
|
|
115
|
+
sdk.track(distinctId, "subscribe", props);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Track a completed order with multiple line items for a specific user.
|
|
119
|
+
*/
|
|
120
|
+
function trackOrder(sdk, distinctId, params) {
|
|
121
|
+
const currency = params.currency ?? "USD";
|
|
122
|
+
let total = params.subtotal;
|
|
123
|
+
if (params.tax !== void 0) total += params.tax;
|
|
124
|
+
if (params.shipping !== void 0) total += params.shipping;
|
|
125
|
+
if (params.discount !== void 0) total -= params.discount;
|
|
126
|
+
const props = {
|
|
127
|
+
order_id: params.orderId,
|
|
128
|
+
subtotal: params.subtotal,
|
|
129
|
+
total,
|
|
130
|
+
currency,
|
|
131
|
+
item_count: params.items.length,
|
|
132
|
+
revenue: total,
|
|
133
|
+
product_ids: params.items.map((i) => i.productId).join(","),
|
|
134
|
+
...params.properties
|
|
135
|
+
};
|
|
136
|
+
if (params.tax !== void 0) props.tax = params.tax;
|
|
137
|
+
if (params.shipping !== void 0) props.shipping = params.shipping;
|
|
138
|
+
if (params.discount !== void 0) props.discount = params.discount;
|
|
139
|
+
if (params.couponCode !== void 0) props.coupon_code = params.couponCode;
|
|
140
|
+
sdk.track(distinctId, "purchase_success", props);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Track an item being added to the cart for a specific user.
|
|
144
|
+
*/
|
|
145
|
+
function trackAddToCart(sdk, distinctId, item, properties) {
|
|
146
|
+
const quantity = item.quantity ?? 1;
|
|
147
|
+
const props = {
|
|
148
|
+
product_id: item.productId,
|
|
149
|
+
product_name: item.name,
|
|
150
|
+
price: item.price,
|
|
151
|
+
quantity,
|
|
152
|
+
value: item.price * quantity,
|
|
153
|
+
...properties
|
|
154
|
+
};
|
|
155
|
+
if (item.category !== void 0) props.category = item.category;
|
|
156
|
+
sdk.track(distinctId, "add_to_cart", props);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Track an item being removed from the cart for a specific user.
|
|
160
|
+
*/
|
|
161
|
+
function trackRemoveFromCart(sdk, distinctId, item, properties) {
|
|
162
|
+
const quantity = item.quantity ?? 1;
|
|
163
|
+
const props = {
|
|
164
|
+
product_id: item.productId,
|
|
165
|
+
product_name: item.name,
|
|
166
|
+
price: item.price,
|
|
167
|
+
quantity,
|
|
168
|
+
...properties
|
|
169
|
+
};
|
|
170
|
+
if (item.category !== void 0) props.category = item.category;
|
|
171
|
+
sdk.track(distinctId, "remove_from_cart", props);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Track beginning the checkout flow for a specific user.
|
|
175
|
+
*/
|
|
176
|
+
function trackBeginCheckout(sdk, distinctId, items, currency = "USD", properties) {
|
|
177
|
+
const total = items.reduce((sum, item) => sum + item.price * (item.quantity ?? 1), 0);
|
|
178
|
+
const props = {
|
|
179
|
+
item_count: items.length,
|
|
180
|
+
value: total,
|
|
181
|
+
currency,
|
|
182
|
+
product_ids: items.map((i) => i.productId).join(","),
|
|
183
|
+
...properties
|
|
184
|
+
};
|
|
185
|
+
sdk.track(distinctId, "begin_checkout", props);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Track viewing a product detail page for a specific user.
|
|
189
|
+
*/
|
|
190
|
+
function trackViewProduct(sdk, distinctId, productId, name, price, currency = "USD", category, properties) {
|
|
191
|
+
const props = {
|
|
192
|
+
product_id: productId,
|
|
193
|
+
product_name: name,
|
|
194
|
+
price,
|
|
195
|
+
currency,
|
|
196
|
+
...properties
|
|
197
|
+
};
|
|
198
|
+
if (category !== void 0) props.category = category;
|
|
199
|
+
sdk.track(distinctId, "view_item", props);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Track a refund for a specific user.
|
|
203
|
+
*/
|
|
204
|
+
function trackRefund(sdk, distinctId, params) {
|
|
205
|
+
const props = {
|
|
206
|
+
transaction_id: params.transactionId,
|
|
207
|
+
amount: params.amount,
|
|
208
|
+
currency: params.currency,
|
|
209
|
+
...params.properties
|
|
210
|
+
};
|
|
211
|
+
if (params.reason !== void 0) props.reason = params.reason;
|
|
212
|
+
sdk.track(distinctId, "refund", props);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/index.ts
|
|
217
|
+
const SDK_VERSION = typeof __APPMACHINA_NODE_VERSION__ !== "undefined" ? __APPMACHINA_NODE_VERSION__ : "0.1.0";
|
|
218
|
+
var AppMachinaNode = class AppMachinaNode {
|
|
219
|
+
core;
|
|
220
|
+
config;
|
|
221
|
+
enableDebug;
|
|
222
|
+
baseUrl;
|
|
223
|
+
shutdownFlushTimeoutMs;
|
|
224
|
+
isShutDown = false;
|
|
225
|
+
errorListeners = [];
|
|
226
|
+
static globalSignalHandlersRegistered = false;
|
|
227
|
+
static instances = [];
|
|
228
|
+
_instanceId;
|
|
229
|
+
_initDurationMs;
|
|
230
|
+
constructor(config) {
|
|
231
|
+
const initStart = performance.now();
|
|
232
|
+
this.config = config;
|
|
233
|
+
this.enableDebug = config.enableDebug ?? false;
|
|
234
|
+
this.baseUrl = (config.baseUrl ?? "https://in.appmachina.com").replace(/\/$/, "");
|
|
235
|
+
this.shutdownFlushTimeoutMs = config.shutdownFlushTimeoutMs ?? 5e3;
|
|
236
|
+
this._instanceId = getOrCreateInstanceId(config.appId, config.persistenceDir);
|
|
237
|
+
const persistence = new FilePersistence(config.appId, config.persistenceDir);
|
|
238
|
+
const httpClient = new FetchHttpClient(3e4);
|
|
239
|
+
this.core = AppMachinaCore.init({
|
|
240
|
+
config: {
|
|
241
|
+
appId: config.appId,
|
|
242
|
+
environment: config.environment,
|
|
243
|
+
...config.baseUrl != null && { baseUrl: config.baseUrl },
|
|
244
|
+
...config.enableDebug != null && { enableDebug: config.enableDebug },
|
|
245
|
+
flushIntervalMs: config.flushIntervalMs ?? 1e4,
|
|
246
|
+
flushThreshold: config.flushThreshold ?? 20,
|
|
247
|
+
...config.maxQueueSize != null && { maxQueueSize: config.maxQueueSize },
|
|
248
|
+
...config.maxBatchSize != null && { maxBatchSize: config.maxBatchSize },
|
|
249
|
+
sdkVersion: `node/${SDK_VERSION}`
|
|
250
|
+
},
|
|
251
|
+
httpClient,
|
|
252
|
+
persistence
|
|
253
|
+
});
|
|
254
|
+
this.core.setDeviceContext({
|
|
255
|
+
platform: "node",
|
|
256
|
+
osVersion: `${process.platform} ${process.version}`,
|
|
257
|
+
appVersion: SDK_VERSION,
|
|
258
|
+
deviceModel: process.arch,
|
|
259
|
+
locale: Intl.DateTimeFormat().resolvedOptions().locale ?? "en-US",
|
|
260
|
+
installId: this._instanceId
|
|
261
|
+
});
|
|
262
|
+
if (config.handleSignals !== false) this.registerSignalHandlers();
|
|
263
|
+
this._initDurationMs = Math.round((performance.now() - initStart) * 100) / 100;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Complete asynchronous initialization: fetches remote config and tracks init timing.
|
|
267
|
+
*
|
|
268
|
+
* Call this after constructing the instance. The constructor handles synchronous
|
|
269
|
+
* setup; this method handles async work (remote config fetch) and fires the
|
|
270
|
+
* `appmachina_init_timing` event.
|
|
271
|
+
*/
|
|
272
|
+
async init() {
|
|
273
|
+
await this.core.fetchRemoteConfig().catch(() => {});
|
|
274
|
+
if (this._initDurationMs != null) try {
|
|
275
|
+
this.core.track("appmachina_init_timing", {
|
|
276
|
+
duration_ms: this._initDurationMs,
|
|
277
|
+
platform: "node"
|
|
278
|
+
}, "system");
|
|
279
|
+
} catch {}
|
|
280
|
+
try {
|
|
281
|
+
this.core.track("server_start", {
|
|
282
|
+
platform: "node",
|
|
283
|
+
node_version: process.version,
|
|
284
|
+
duration_ms: this._initDurationMs
|
|
285
|
+
}, "system");
|
|
286
|
+
} catch {}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Track an event for a specific user.
|
|
290
|
+
*
|
|
291
|
+
* Unlike @appmachina/client which stores the user ID via identify(),
|
|
292
|
+
* the Node.js SDK requires a distinctId on every call. This is the
|
|
293
|
+
* correct pattern for server-side SDKs where requests come from
|
|
294
|
+
* many users concurrently.
|
|
295
|
+
*/
|
|
296
|
+
track(distinctId, eventName, properties) {
|
|
297
|
+
if (this.isShutDown) return;
|
|
298
|
+
if (this.enableDebug) console.log(`[AppMachina] track("${eventName}", ${Object.keys(properties ?? {}).length} properties, distinctId="${distinctId}")`);
|
|
299
|
+
if (!distinctId || typeof distinctId !== "string") {
|
|
300
|
+
this.emitError(/* @__PURE__ */ new Error("distinctId must be a non-empty string"));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const depthBefore = this.core.queueDepth();
|
|
305
|
+
this.core.track(eventName, properties, distinctId);
|
|
306
|
+
const depthAfter = this.core.queueDepth();
|
|
307
|
+
if (depthAfter <= depthBefore && this.enableDebug) console.warn(`[AppMachina] Event "${eventName}" may not have been accepted (queue depth ${depthBefore} -> ${depthAfter})`);
|
|
308
|
+
} catch (e) {
|
|
309
|
+
this.emitError(e);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Track a screen view for a specific user.
|
|
314
|
+
*/
|
|
315
|
+
screen(distinctId, screenName, properties) {
|
|
316
|
+
if (this.isShutDown) return;
|
|
317
|
+
if (this.enableDebug) console.log(`[AppMachina] screen("${screenName}", ${Object.keys(properties ?? {}).length} properties, distinctId="${distinctId}")`);
|
|
318
|
+
if (!distinctId || typeof distinctId !== "string") {
|
|
319
|
+
this.emitError(/* @__PURE__ */ new Error("distinctId must be a non-empty string"));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
this.core.screen(screenName, properties, distinctId);
|
|
324
|
+
} catch (e) {
|
|
325
|
+
this.emitError(e);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Set user properties for a specific user.
|
|
330
|
+
*
|
|
331
|
+
* Calls identify(distinctId) first so the core associates
|
|
332
|
+
* these properties with the correct user.
|
|
333
|
+
*
|
|
334
|
+
* Note: identify(distinctId) and setUserProperties are two separate calls.
|
|
335
|
+
* In concurrent server environments, interleaving is possible. For atomic
|
|
336
|
+
* user property updates, the Rust core would need a single
|
|
337
|
+
* identify-and-set-properties API.
|
|
338
|
+
*/
|
|
339
|
+
setUserProperties(distinctId, properties) {
|
|
340
|
+
if (this.isShutDown) return;
|
|
341
|
+
if (this.enableDebug) console.log(`[AppMachina] setUserProperties(${Object.keys(properties ?? {}).length} properties, distinctId="${distinctId}")`);
|
|
342
|
+
if (!distinctId || typeof distinctId !== "string") {
|
|
343
|
+
this.emitError(/* @__PURE__ */ new Error("distinctId must be a non-empty string"));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
this.core.identify(distinctId);
|
|
348
|
+
this.core.setUserProperties(properties);
|
|
349
|
+
this.sendUserPropertiesAsync(distinctId, properties, false);
|
|
350
|
+
} catch (e) {
|
|
351
|
+
this.emitError(e);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Set user properties with "set once" semantics for a specific user.
|
|
356
|
+
*
|
|
357
|
+
* Only properties whose keys have not been previously set via this method
|
|
358
|
+
* are forwarded. Calls identify(distinctId) first so the core associates
|
|
359
|
+
* these properties with the correct user.
|
|
360
|
+
*/
|
|
361
|
+
setUserPropertiesOnce(distinctId, properties) {
|
|
362
|
+
if (this.isShutDown) return;
|
|
363
|
+
if (this.enableDebug) console.log(`[AppMachina] setUserPropertiesOnce(${Object.keys(properties ?? {}).length} properties, distinctId="${distinctId}")`);
|
|
364
|
+
if (!distinctId || typeof distinctId !== "string") {
|
|
365
|
+
this.emitError(/* @__PURE__ */ new Error("distinctId must be a non-empty string"));
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
this.core.identify(distinctId);
|
|
370
|
+
this.core.setUserPropertiesOnce(properties);
|
|
371
|
+
this.sendUserPropertiesAsync(distinctId, properties, true);
|
|
372
|
+
} catch (e) {
|
|
373
|
+
this.emitError(e);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Associate events for a specific user with a group (company, team, organization).
|
|
378
|
+
*
|
|
379
|
+
* Unlike @appmachina/client which stores the group ID, the Node.js SDK
|
|
380
|
+
* follows the server-side pattern where distinctId is required on every call.
|
|
381
|
+
* The group association is set on the core and a `group` event is tracked
|
|
382
|
+
* with the provided properties.
|
|
383
|
+
*/
|
|
384
|
+
group(distinctId, groupId, properties) {
|
|
385
|
+
if (this.isShutDown) return;
|
|
386
|
+
if (this.enableDebug) console.log(`[AppMachina] group("${groupId}", ${Object.keys(properties ?? {}).length} properties, distinctId="${distinctId}")`);
|
|
387
|
+
if (!distinctId || typeof distinctId !== "string") {
|
|
388
|
+
this.emitError(/* @__PURE__ */ new Error("distinctId must be a non-empty string"));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
this.core.identify(distinctId);
|
|
393
|
+
this.core.group(groupId, properties);
|
|
394
|
+
} catch (e) {
|
|
395
|
+
this.emitError(e);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Update consent state.
|
|
400
|
+
*
|
|
401
|
+
* Consent applies globally to this SDK instance, not per-user.
|
|
402
|
+
* All events from this instance will respect the configured consent state.
|
|
403
|
+
*/
|
|
404
|
+
setConsent(consent) {
|
|
405
|
+
if (this.isShutDown) return;
|
|
406
|
+
try {
|
|
407
|
+
this.core.setConsent(consent);
|
|
408
|
+
} catch (e) {
|
|
409
|
+
this.emitError(e);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Get the persistent instance ID for this server instance.
|
|
414
|
+
*
|
|
415
|
+
* The instance ID is a UUID generated on first init and persisted to the
|
|
416
|
+
* filesystem so it survives process restarts. Stored in
|
|
417
|
+
* `.appmachina-instance-id` inside the persistence directory.
|
|
418
|
+
*/
|
|
419
|
+
getInstanceId() {
|
|
420
|
+
return this._instanceId;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Get current session ID.
|
|
424
|
+
*/
|
|
425
|
+
getSessionId() {
|
|
426
|
+
return this.core.getSessionId();
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Get current consent state.
|
|
430
|
+
*/
|
|
431
|
+
getConsentState() {
|
|
432
|
+
return this.core.getConsentState();
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Get number of queued events.
|
|
436
|
+
*/
|
|
437
|
+
queueDepth() {
|
|
438
|
+
return this.core.queueDepth();
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Flush all queued events to the server.
|
|
442
|
+
*/
|
|
443
|
+
async flush() {
|
|
444
|
+
if (this.isShutDown) return;
|
|
445
|
+
try {
|
|
446
|
+
await this.core.flushAsync();
|
|
447
|
+
} catch (e) {
|
|
448
|
+
this.emitError(e);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Gracefully shut down: flush remaining events, then stop.
|
|
453
|
+
*/
|
|
454
|
+
async shutdown() {
|
|
455
|
+
if (this.isShutDown) return;
|
|
456
|
+
this.isShutDown = true;
|
|
457
|
+
const idx = AppMachinaNode.instances.indexOf(this);
|
|
458
|
+
if (idx !== -1) AppMachinaNode.instances.splice(idx, 1);
|
|
459
|
+
try {
|
|
460
|
+
await Promise.race([this.core.flushAsync(), new Promise((resolve) => setTimeout(resolve, this.shutdownFlushTimeoutMs))]);
|
|
461
|
+
} catch {}
|
|
462
|
+
this.core.shutdown();
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Register an error listener. Errors from track/screen/flush
|
|
466
|
+
* that would otherwise be silently dropped are forwarded here.
|
|
467
|
+
*/
|
|
468
|
+
on(event, listener) {
|
|
469
|
+
this.errorListeners.push(listener);
|
|
470
|
+
return this;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Remove a previously registered error listener.
|
|
474
|
+
*/
|
|
475
|
+
off(event, listener) {
|
|
476
|
+
const idx = this.errorListeners.indexOf(listener);
|
|
477
|
+
if (idx !== -1) this.errorListeners.splice(idx, 1);
|
|
478
|
+
return this;
|
|
479
|
+
}
|
|
480
|
+
emitError(e) {
|
|
481
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
482
|
+
for (const listener of this.errorListeners) try {
|
|
483
|
+
listener(error);
|
|
484
|
+
} catch {}
|
|
485
|
+
if (this.enableDebug && this.errorListeners.length === 0) console.warn("[AppMachina]", error.message);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Fire-and-forget POST to /users/properties.
|
|
489
|
+
* Best-effort: errors are silently swallowed.
|
|
490
|
+
*/
|
|
491
|
+
sendUserPropertiesAsync(distinctId, properties, setOnce) {
|
|
492
|
+
const payload = {
|
|
493
|
+
app_id: this.config.appId,
|
|
494
|
+
app_user_id: distinctId,
|
|
495
|
+
properties,
|
|
496
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
497
|
+
};
|
|
498
|
+
if (setOnce) payload.set_once = true;
|
|
499
|
+
fetch(`${this.baseUrl}/users/properties`, {
|
|
500
|
+
method: "POST",
|
|
501
|
+
headers: {
|
|
502
|
+
"Content-Type": "application/json",
|
|
503
|
+
"X-App-Id": this.config.appId,
|
|
504
|
+
"X-SDK-Version": `node/${SDK_VERSION}`
|
|
505
|
+
},
|
|
506
|
+
body: JSON.stringify(payload)
|
|
507
|
+
}).catch(() => {});
|
|
508
|
+
}
|
|
509
|
+
registerSignalHandlers() {
|
|
510
|
+
AppMachinaNode.instances.push(this);
|
|
511
|
+
if (AppMachinaNode.globalSignalHandlersRegistered) return;
|
|
512
|
+
AppMachinaNode.globalSignalHandlersRegistered = true;
|
|
513
|
+
const handler = (signal) => {
|
|
514
|
+
for (const instance of AppMachinaNode.instances) try {
|
|
515
|
+
instance.core.track("server_shutdown", { reason: signal }, "system");
|
|
516
|
+
} catch {}
|
|
517
|
+
const shutdowns = AppMachinaNode.instances.map((inst) => inst.shutdown());
|
|
518
|
+
Promise.allSettled(shutdowns).then(() => {
|
|
519
|
+
process.kill(process.pid, signal);
|
|
520
|
+
});
|
|
521
|
+
};
|
|
522
|
+
process.once("SIGTERM", () => handler("SIGTERM"));
|
|
523
|
+
process.once("SIGINT", () => handler("SIGINT"));
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
const INSTANCE_ID_FILENAME = ".appmachina-instance-id";
|
|
527
|
+
/**
|
|
528
|
+
* Read or create a persistent instance ID on the filesystem.
|
|
529
|
+
* Uses `crypto.randomUUID()` for generation.
|
|
530
|
+
*/
|
|
531
|
+
function getOrCreateInstanceId(appId, baseDir) {
|
|
532
|
+
const dir = join(baseDir ?? tmpdir(), "appmachina-sdk", appId);
|
|
533
|
+
const filePath = join(dir, INSTANCE_ID_FILENAME);
|
|
534
|
+
try {
|
|
535
|
+
if (existsSync(filePath)) {
|
|
536
|
+
const existing = readFileSync(filePath, "utf-8").trim();
|
|
537
|
+
if (existing) return existing;
|
|
538
|
+
}
|
|
539
|
+
} catch {}
|
|
540
|
+
const newId = crypto.randomUUID();
|
|
541
|
+
try {
|
|
542
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
543
|
+
writeFileSync(filePath, newId, "utf-8");
|
|
544
|
+
} catch {}
|
|
545
|
+
return newId;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
//#endregion
|
|
549
|
+
export { AppMachinaError, AppMachinaNode, FilePersistence, StandardEvents, appMachinaDebugMiddleware, trackAddToCart, trackBeginCheckout, trackOrder, trackPurchase, trackPurchaseFailed, trackRefund, trackRemoveFromCart, trackSubscription, trackViewProduct };
|
|
550
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["props: EventProperties","SDK_VERSION: string","payload: Record<string, unknown>"],"sources":["../src/standard-events.ts","../src/commerce.ts","../src/index.ts"],"sourcesContent":["// Standard event types for AppMachina Analytics.\n// These 18 events match the canonical AppMachina event taxonomy\n// and are consistent across all SDK platforms.\n\n/**\n * Predefined standard event name constants.\n *\n * Usage:\n * ```ts\n * import { StandardEvents } from '@appmachina/node';\n * appmachina.track(distinctId, StandardEvents.PURCHASE, { amount: 9.99 });\n * ```\n */\nexport const StandardEvents = {\n APP_INSTALL: 'app_install',\n APP_OPEN: 'app_open',\n LOGIN: 'login',\n SIGN_UP: 'sign_up',\n REGISTER: 'register',\n PURCHASE: 'purchase_success',\n ADD_TO_CART: 'add_to_cart',\n ADD_TO_WISHLIST: 'add_to_wishlist',\n INITIATE_CHECKOUT: 'initiate_checkout',\n BEGIN_CHECKOUT: 'begin_checkout',\n START_TRIAL: 'start_trial',\n SUBSCRIBE: 'subscribe',\n LEVEL_START: 'level_start',\n LEVEL_COMPLETE: 'level_complete',\n TUTORIAL_COMPLETE: 'tutorial_complete',\n SEARCH: 'search',\n VIEW_ITEM: 'view_item',\n VIEW_CONTENT: 'view_content',\n SHARE: 'share',\n DEEP_LINK: 'deep_link_opened',\n SCREEN_VIEW: 'screen_view'\n} as const;\n\n/** Union type of all standard event name strings. */\nexport type StandardEventName = (typeof StandardEvents)[keyof typeof StandardEvents];\n","// Commerce module for @appmachina/node (Server-side Node.js SDK).\n// Cross-platform purchase, subscription, cart, and refund tracking helpers.\n// Each function accepts a NodeCommerceTracker instance, a distinctId, and\n// tracks the appropriate event with standardized properties consistent with\n// the iOS CommerceModule, Android CommerceModule, and RN commerce module.\n//\n// Unlike the Web and RN commerce modules, the Node commerce module follows\n// the server-side distinctId-per-call pattern. Every function requires a\n// distinctId to identify which user the commerce event belongs to.\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Event properties bag accepted by commerce functions. */\nexport type EventProperties = Record<string, unknown>;\n\n/** Minimal AppMachina SDK interface required by the Node commerce module. */\nexport interface NodeCommerceTracker {\n track(distinctId: string, event: string, properties?: Record<string, any>): void;\n}\n\n/** Purchase details for trackPurchase. */\nexport interface PurchaseParams {\n productId: string;\n /** Unit price of the item. Revenue is computed as `price * quantity`. */\n price: number;\n currency: string;\n transactionId?: string;\n quantity?: number;\n isRestored?: boolean;\n store?: string;\n properties?: EventProperties;\n}\n\n/** Subscription details for trackSubscription. */\nexport interface SubscriptionParams {\n productId: string;\n /** Unit price of the subscription. */\n price: number;\n currency: string;\n period?: string;\n transactionId?: string;\n isRenewal?: boolean;\n isTrial?: boolean;\n subscriptionGroupId?: string;\n originalTransactionId?: string;\n properties?: EventProperties;\n}\n\n/** Cart item for order and checkout tracking. */\nexport interface CartItem {\n productId: string;\n name: string;\n price: number;\n quantity?: number;\n category?: string;\n}\n\n/** Order details for trackOrder. */\nexport interface OrderParams {\n orderId: string;\n items: CartItem[];\n subtotal: number;\n currency?: string;\n tax?: number;\n shipping?: number;\n discount?: number;\n couponCode?: string;\n properties?: EventProperties;\n}\n\n/** Refund details for trackRefund. */\nexport interface RefundParams {\n transactionId: string;\n amount: number;\n currency: string;\n reason?: string;\n properties?: EventProperties;\n}\n\n/** Purchase failure details for trackPurchaseFailed. */\nexport interface PurchaseFailedParams {\n productId: string;\n currency: string;\n errorCode: string | number;\n errorMessage?: string;\n properties?: EventProperties;\n}\n\n// ---------------------------------------------------------------------------\n// Purchase tracking\n// ---------------------------------------------------------------------------\n\n/**\n * Track a successful purchase for a specific user.\n *\n * ```ts\n * import { trackPurchase } from '@appmachina/node';\n *\n * trackPurchase(sdk, 'user_123', {\n * productId: 'premium_monthly',\n * price: 9.99,\n * currency: 'USD',\n * transactionId: 'txn_abc123',\n * });\n * ```\n */\nexport function trackPurchase(\n sdk: NodeCommerceTracker,\n distinctId: string,\n params: PurchaseParams\n): void {\n const quantity = params.quantity ?? 1;\n const props: EventProperties = {\n product_id: params.productId,\n price: params.price,\n currency: params.currency,\n quantity,\n revenue: params.price * quantity,\n ...params.properties\n };\n if (params.transactionId !== undefined) props.transaction_id = params.transactionId;\n if (params.isRestored !== undefined) props.is_restored = params.isRestored;\n if (params.store !== undefined) props.store = params.store;\n\n sdk.track(distinctId, 'purchase_success', props);\n}\n\n/**\n * Track a failed purchase attempt for a specific user.\n */\nexport function trackPurchaseFailed(\n sdk: NodeCommerceTracker,\n distinctId: string,\n params: PurchaseFailedParams\n): void {\n const props: EventProperties = {\n product_id: params.productId,\n currency: params.currency,\n error_code: params.errorCode,\n ...params.properties\n };\n if (params.errorMessage !== undefined) props.error_message = params.errorMessage;\n\n sdk.track(distinctId, 'purchase_failed', props);\n}\n\n// ---------------------------------------------------------------------------\n// Subscription tracking\n// ---------------------------------------------------------------------------\n\n/**\n * Track a subscription purchase or renewal for a specific user.\n *\n * ```ts\n * import { trackSubscription } from '@appmachina/node';\n *\n * trackSubscription(sdk, 'user_123', {\n * productId: 'pro_annual',\n * price: 49.99,\n * currency: 'USD',\n * period: 'P1Y',\n * });\n * ```\n */\nexport function trackSubscription(\n sdk: NodeCommerceTracker,\n distinctId: string,\n params: SubscriptionParams\n): void {\n const props: EventProperties = {\n product_id: params.productId,\n price: params.price,\n currency: params.currency,\n quantity: 1,\n revenue: params.price,\n ...params.properties\n };\n if (params.transactionId !== undefined) props.transaction_id = params.transactionId;\n if (params.period !== undefined) props.period = params.period;\n if (params.isRenewal !== undefined) props.is_renewal = params.isRenewal;\n if (params.isTrial !== undefined) props.is_trial = params.isTrial;\n if (params.subscriptionGroupId !== undefined)\n props.subscription_group_id = params.subscriptionGroupId;\n if (params.originalTransactionId !== undefined)\n props.original_transaction_id = params.originalTransactionId;\n\n sdk.track(distinctId, 'subscribe', props);\n}\n\n// ---------------------------------------------------------------------------\n// Order / cart tracking\n// ---------------------------------------------------------------------------\n\n/**\n * Track a completed order with multiple line items for a specific user.\n */\nexport function trackOrder(\n sdk: NodeCommerceTracker,\n distinctId: string,\n params: OrderParams\n): void {\n const currency = params.currency ?? 'USD';\n let total = params.subtotal;\n if (params.tax !== undefined) total += params.tax;\n if (params.shipping !== undefined) total += params.shipping;\n if (params.discount !== undefined) total -= params.discount;\n\n const props: EventProperties = {\n order_id: params.orderId,\n subtotal: params.subtotal,\n total,\n currency,\n item_count: params.items.length,\n revenue: total,\n product_ids: params.items.map((i) => i.productId).join(','),\n ...params.properties\n };\n if (params.tax !== undefined) props.tax = params.tax;\n if (params.shipping !== undefined) props.shipping = params.shipping;\n if (params.discount !== undefined) props.discount = params.discount;\n if (params.couponCode !== undefined) props.coupon_code = params.couponCode;\n\n sdk.track(distinctId, 'purchase_success', props);\n}\n\n/**\n * Track an item being added to the cart for a specific user.\n */\nexport function trackAddToCart(\n sdk: NodeCommerceTracker,\n distinctId: string,\n item: CartItem,\n properties?: EventProperties\n): void {\n const quantity = item.quantity ?? 1;\n const props: EventProperties = {\n product_id: item.productId,\n product_name: item.name,\n price: item.price,\n quantity,\n value: item.price * quantity,\n ...properties\n };\n if (item.category !== undefined) props.category = item.category;\n\n sdk.track(distinctId, 'add_to_cart', props);\n}\n\n/**\n * Track an item being removed from the cart for a specific user.\n */\nexport function trackRemoveFromCart(\n sdk: NodeCommerceTracker,\n distinctId: string,\n item: CartItem,\n properties?: EventProperties\n): void {\n const quantity = item.quantity ?? 1;\n const props: EventProperties = {\n product_id: item.productId,\n product_name: item.name,\n price: item.price,\n quantity,\n ...properties\n };\n if (item.category !== undefined) props.category = item.category;\n\n sdk.track(distinctId, 'remove_from_cart', props);\n}\n\n/**\n * Track beginning the checkout flow for a specific user.\n */\nexport function trackBeginCheckout(\n sdk: NodeCommerceTracker,\n distinctId: string,\n items: CartItem[],\n currency = 'USD',\n properties?: EventProperties\n): void {\n const total = items.reduce((sum, item) => sum + item.price * (item.quantity ?? 1), 0);\n const props: EventProperties = {\n item_count: items.length,\n value: total,\n currency,\n product_ids: items.map((i) => i.productId).join(','),\n ...properties\n };\n\n sdk.track(distinctId, 'begin_checkout', props);\n}\n\n// ---------------------------------------------------------------------------\n// Product view tracking\n// ---------------------------------------------------------------------------\n\n/**\n * Track viewing a product detail page for a specific user.\n */\nexport function trackViewProduct(\n sdk: NodeCommerceTracker,\n distinctId: string,\n productId: string,\n name: string,\n price: number,\n currency = 'USD',\n category?: string,\n properties?: EventProperties\n): void {\n const props: EventProperties = {\n product_id: productId,\n product_name: name,\n price,\n currency,\n ...properties\n };\n if (category !== undefined) props.category = category;\n\n sdk.track(distinctId, 'view_item', props);\n}\n\n// ---------------------------------------------------------------------------\n// Refund tracking\n// ---------------------------------------------------------------------------\n\n/**\n * Track a refund for a specific user.\n */\nexport function trackRefund(\n sdk: NodeCommerceTracker,\n distinctId: string,\n params: RefundParams\n): void {\n const props: EventProperties = {\n transaction_id: params.transactionId,\n amount: params.amount,\n currency: params.currency,\n ...params.properties\n };\n if (params.reason !== undefined) props.reason = params.reason;\n\n sdk.track(distinctId, 'refund', props);\n}\n","// @appmachina/node — Server-side Node.js SDK.\n// Thin wrapper over @appmachina/core-wasm. Owns only:\n// - distinctId-per-call API pattern (no stored user state)\n// - SIGTERM/SIGINT graceful shutdown\n// - File-system persistence via Node.js fs\n// - process.env access for configuration\n// - Instance ID persistence (filesystem)\n// - Init timing measurement\n// - Remote config fetch on init\n// - Queue depth gating\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { AppMachinaCore, AppMachinaError, FetchHttpClient } from '@appmachina/core-wasm';\nimport type {\n ConsentState,\n Environment,\n EventProperties,\n UserProperties\n} from '@appmachina/core-wasm';\n\nimport { FilePersistence } from './persistence.js';\n\nexport type {\n ConsentState,\n Environment,\n EventProperties,\n UserProperties\n} from '@appmachina/core-wasm';\nexport { AppMachinaError } from '@appmachina/core-wasm';\nexport { FilePersistence } from './persistence.js';\nexport { StandardEvents } from './standard-events.js';\nexport type { StandardEventName } from './standard-events.js';\nexport { appMachinaDebugMiddleware } from './express.js';\n\nexport type {\n NodeCommerceTracker,\n PurchaseParams,\n SubscriptionParams,\n CartItem,\n OrderParams,\n RefundParams,\n PurchaseFailedParams\n} from './commerce.js';\nexport {\n trackPurchase,\n trackPurchaseFailed,\n trackSubscription,\n trackOrder,\n trackAddToCart,\n trackRemoveFromCart,\n trackBeginCheckout,\n trackViewProduct,\n trackRefund\n} from './commerce.js';\n\nexport type ErrorListener = (error: Error) => void;\n\nexport interface AppMachinaNodeConfig {\n /** Application identifier that scopes events to a specific app. */\n appId: string;\n /** Deployment environment (e.g. \"production\", \"development\"). */\n environment: Environment;\n /** Enable verbose debug logging. */\n enableDebug?: boolean;\n /** Override the default AppMachina ingestion endpoint URL. */\n baseUrl?: string;\n /**\n * How often (in milliseconds) the SDK automatically flushes queued events.\n * @default 10000\n */\n flushIntervalMs?: number;\n /**\n * Number of queued events that triggers an automatic flush.\n * @default 20\n */\n flushThreshold?: number;\n /** Maximum number of events to hold in the in-memory queue. Events beyond this limit are dropped. */\n maxQueueSize?: number;\n /** Maximum number of events sent in a single HTTP request batch. */\n maxBatchSize?: number;\n /**\n * Maximum time (in milliseconds) to wait for a final flush during shutdown before giving up.\n * @default 5000\n */\n shutdownFlushTimeoutMs?: number;\n /** Whether to register SIGTERM/SIGINT handlers for graceful shutdown. Default: true */\n handleSignals?: boolean;\n /**\n * Directory where persisted event data is stored.\n * Defaults to `os.tmpdir()` (e.g. `/tmp/appmachina-sdk/<appId>`).\n */\n persistenceDir?: string;\n}\n\n// SDK version injected at build time\ndeclare const __APPMACHINA_NODE_VERSION__: string;\nconst SDK_VERSION: string =\n typeof __APPMACHINA_NODE_VERSION__ !== 'undefined' ? __APPMACHINA_NODE_VERSION__ : '0.1.0';\n\nexport class AppMachinaNode {\n private core: AppMachinaCore;\n private readonly config: AppMachinaNodeConfig;\n private readonly enableDebug: boolean;\n private readonly baseUrl: string;\n private readonly shutdownFlushTimeoutMs: number;\n private isShutDown = false;\n private readonly errorListeners: ErrorListener[] = [];\n private static globalSignalHandlersRegistered = false;\n private static instances: AppMachinaNode[] = [];\n private readonly _instanceId: string;\n private _initDurationMs: number | undefined;\n\n constructor(config: AppMachinaNodeConfig) {\n const initStart = performance.now();\n\n this.config = config;\n this.enableDebug = config.enableDebug ?? false;\n this.baseUrl = (config.baseUrl ?? 'https://in.appmachina.com').replace(/\\/$/, '');\n this.shutdownFlushTimeoutMs = config.shutdownFlushTimeoutMs ?? 5_000;\n\n // Instance ID: persist a UUID to the filesystem so it survives restarts\n this._instanceId = getOrCreateInstanceId(config.appId, config.persistenceDir);\n\n const persistence = new FilePersistence(config.appId, config.persistenceDir);\n const httpClient = new FetchHttpClient(30_000);\n\n this.core = AppMachinaCore.init({\n config: {\n appId: config.appId,\n environment: config.environment,\n ...(config.baseUrl != null && { baseUrl: config.baseUrl }),\n ...(config.enableDebug != null && { enableDebug: config.enableDebug }),\n flushIntervalMs: config.flushIntervalMs ?? 10_000,\n flushThreshold: config.flushThreshold ?? 20,\n ...(config.maxQueueSize != null && { maxQueueSize: config.maxQueueSize }),\n ...(config.maxBatchSize != null && { maxBatchSize: config.maxBatchSize }),\n sdkVersion: `node/${SDK_VERSION}`\n },\n httpClient,\n persistence\n });\n\n this.core.setDeviceContext({\n platform: 'node',\n osVersion: `${process.platform} ${process.version}`,\n appVersion: SDK_VERSION,\n deviceModel: process.arch,\n locale: Intl.DateTimeFormat().resolvedOptions().locale ?? 'en-US',\n installId: this._instanceId\n });\n\n if (config.handleSignals !== false) {\n this.registerSignalHandlers();\n }\n\n // Record init timing\n this._initDurationMs = Math.round((performance.now() - initStart) * 100) / 100;\n }\n\n /**\n * Complete asynchronous initialization: fetches remote config and tracks init timing.\n *\n * Call this after constructing the instance. The constructor handles synchronous\n * setup; this method handles async work (remote config fetch) and fires the\n * `appmachina_init_timing` event.\n */\n async init(): Promise<void> {\n // Fetch remote config (best-effort, like Web/RN SDKs)\n await this.core.fetchRemoteConfig().catch(() => {\n // Remote config fetch is best-effort\n });\n\n // Track init timing\n if (this._initDurationMs != null) {\n try {\n this.core.track(\n 'appmachina_init_timing',\n { duration_ms: this._initDurationMs, platform: 'node' },\n 'system'\n );\n } catch {\n // Init timing is best-effort\n }\n }\n\n // Track server_start lifecycle event\n try {\n this.core.track(\n 'server_start',\n {\n platform: 'node',\n node_version: process.version,\n duration_ms: this._initDurationMs\n },\n 'system'\n );\n } catch {\n // Lifecycle events are best-effort\n }\n }\n\n /**\n * Track an event for a specific user.\n *\n * Unlike @appmachina/client which stores the user ID via identify(),\n * the Node.js SDK requires a distinctId on every call. This is the\n * correct pattern for server-side SDKs where requests come from\n * many users concurrently.\n */\n track(distinctId: string, eventName: string, properties?: EventProperties): void {\n if (this.isShutDown) return;\n if (this.enableDebug) {\n console.log(\n `[AppMachina] track(\"${eventName}\", ${Object.keys(properties ?? {}).length} properties, distinctId=\"${distinctId}\")`\n );\n }\n if (!distinctId || typeof distinctId !== 'string') {\n this.emitError(new Error('distinctId must be a non-empty string'));\n return;\n }\n try {\n const depthBefore = this.core.queueDepth();\n this.core.track(eventName, properties, distinctId);\n const depthAfter = this.core.queueDepth();\n\n // Queue depth gating: verify the event was accepted by the core\n if (depthAfter <= depthBefore && this.enableDebug) {\n console.warn(\n `[AppMachina] Event \"${eventName}\" may not have been accepted (queue depth ${depthBefore} -> ${depthAfter})`\n );\n }\n } catch (e) {\n this.emitError(e);\n }\n }\n\n /**\n * Track a screen view for a specific user.\n */\n screen(distinctId: string, screenName: string, properties?: EventProperties): void {\n if (this.isShutDown) return;\n if (this.enableDebug) {\n console.log(\n `[AppMachina] screen(\"${screenName}\", ${Object.keys(properties ?? {}).length} properties, distinctId=\"${distinctId}\")`\n );\n }\n if (!distinctId || typeof distinctId !== 'string') {\n this.emitError(new Error('distinctId must be a non-empty string'));\n return;\n }\n try {\n this.core.screen(screenName, properties, distinctId);\n } catch (e) {\n this.emitError(e);\n }\n }\n\n /**\n * Set user properties for a specific user.\n *\n * Calls identify(distinctId) first so the core associates\n * these properties with the correct user.\n *\n * Note: identify(distinctId) and setUserProperties are two separate calls.\n * In concurrent server environments, interleaving is possible. For atomic\n * user property updates, the Rust core would need a single\n * identify-and-set-properties API.\n */\n setUserProperties(distinctId: string, properties: UserProperties): void {\n if (this.isShutDown) return;\n if (this.enableDebug) {\n console.log(\n `[AppMachina] setUserProperties(${Object.keys(properties ?? {}).length} properties, distinctId=\"${distinctId}\")`\n );\n }\n if (!distinctId || typeof distinctId !== 'string') {\n this.emitError(new Error('distinctId must be a non-empty string'));\n return;\n }\n try {\n this.core.identify(distinctId);\n this.core.setUserProperties(properties as Record<string, unknown>);\n this.sendUserPropertiesAsync(distinctId, properties as Record<string, unknown>, false);\n } catch (e) {\n this.emitError(e);\n }\n }\n\n /**\n * Set user properties with \"set once\" semantics for a specific user.\n *\n * Only properties whose keys have not been previously set via this method\n * are forwarded. Calls identify(distinctId) first so the core associates\n * these properties with the correct user.\n */\n setUserPropertiesOnce(distinctId: string, properties: UserProperties): void {\n if (this.isShutDown) return;\n if (this.enableDebug) {\n console.log(\n `[AppMachina] setUserPropertiesOnce(${Object.keys(properties ?? {}).length} properties, distinctId=\"${distinctId}\")`\n );\n }\n if (!distinctId || typeof distinctId !== 'string') {\n this.emitError(new Error('distinctId must be a non-empty string'));\n return;\n }\n try {\n this.core.identify(distinctId);\n this.core.setUserPropertiesOnce(properties as Record<string, unknown>);\n this.sendUserPropertiesAsync(distinctId, properties as Record<string, unknown>, true);\n } catch (e) {\n this.emitError(e);\n }\n }\n\n /**\n * Associate events for a specific user with a group (company, team, organization).\n *\n * Unlike @appmachina/client which stores the group ID, the Node.js SDK\n * follows the server-side pattern where distinctId is required on every call.\n * The group association is set on the core and a `group` event is tracked\n * with the provided properties.\n */\n group(distinctId: string, groupId: string, properties?: EventProperties): void {\n if (this.isShutDown) return;\n if (this.enableDebug) {\n console.log(\n `[AppMachina] group(\"${groupId}\", ${Object.keys(properties ?? {}).length} properties, distinctId=\"${distinctId}\")`\n );\n }\n if (!distinctId || typeof distinctId !== 'string') {\n this.emitError(new Error('distinctId must be a non-empty string'));\n return;\n }\n try {\n this.core.identify(distinctId);\n this.core.group(groupId, properties);\n } catch (e) {\n this.emitError(e);\n }\n }\n\n /**\n * Update consent state.\n *\n * Consent applies globally to this SDK instance, not per-user.\n * All events from this instance will respect the configured consent state.\n */\n setConsent(consent: ConsentState): void {\n if (this.isShutDown) return;\n try {\n this.core.setConsent(consent);\n } catch (e) {\n this.emitError(e);\n }\n }\n\n /**\n * Get the persistent instance ID for this server instance.\n *\n * The instance ID is a UUID generated on first init and persisted to the\n * filesystem so it survives process restarts. Stored in\n * `.appmachina-instance-id` inside the persistence directory.\n */\n getInstanceId(): string {\n return this._instanceId;\n }\n\n /**\n * Get current session ID.\n */\n getSessionId(): string {\n return this.core.getSessionId();\n }\n\n /**\n * Get current consent state.\n */\n getConsentState(): ConsentState {\n return this.core.getConsentState();\n }\n\n /**\n * Get number of queued events.\n */\n queueDepth(): number {\n return this.core.queueDepth();\n }\n\n /**\n * Flush all queued events to the server.\n */\n async flush(): Promise<void> {\n if (this.isShutDown) return;\n try {\n await this.core.flushAsync();\n } catch (e) {\n this.emitError(e);\n }\n }\n\n /**\n * Gracefully shut down: flush remaining events, then stop.\n */\n async shutdown(): Promise<void> {\n if (this.isShutDown) return;\n this.isShutDown = true;\n\n // Remove this instance from the static list to prevent memory leaks\n const idx = AppMachinaNode.instances.indexOf(this);\n if (idx !== -1) AppMachinaNode.instances.splice(idx, 1);\n\n // Try to flush remaining events with a timeout\n try {\n await Promise.race([\n this.core.flushAsync(),\n new Promise<void>((resolve) => setTimeout(resolve, this.shutdownFlushTimeoutMs))\n ]);\n } catch {\n // Best effort\n }\n\n this.core.shutdown();\n }\n\n /**\n * Register an error listener. Errors from track/screen/flush\n * that would otherwise be silently dropped are forwarded here.\n */\n on(event: 'error', listener: ErrorListener): this {\n this.errorListeners.push(listener);\n return this;\n }\n\n /**\n * Remove a previously registered error listener.\n */\n off(event: 'error', listener: ErrorListener): this {\n const idx = this.errorListeners.indexOf(listener);\n if (idx !== -1) this.errorListeners.splice(idx, 1);\n return this;\n }\n\n private emitError(e: unknown): void {\n const error = e instanceof Error ? e : new Error(String(e));\n for (const listener of this.errorListeners) {\n try {\n listener(error);\n } catch {\n // Listener errors must not propagate\n }\n }\n if (this.enableDebug && this.errorListeners.length === 0) {\n console.warn('[AppMachina]', error.message);\n }\n }\n\n // --- User properties HTTP POST ---\n\n /**\n * Fire-and-forget POST to /users/properties.\n * Best-effort: errors are silently swallowed.\n */\n private sendUserPropertiesAsync(\n distinctId: string,\n properties: Record<string, unknown>,\n setOnce: boolean\n ): void {\n const payload: Record<string, unknown> = {\n app_id: this.config.appId,\n app_user_id: distinctId,\n properties,\n timestamp: new Date().toISOString()\n };\n if (setOnce) {\n payload.set_once = true;\n }\n\n fetch(`${this.baseUrl}/users/properties`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-App-Id': this.config.appId,\n 'X-SDK-Version': `node/${SDK_VERSION}`\n },\n body: JSON.stringify(payload)\n }).catch(() => {\n // Best-effort — don't throw on network errors\n });\n }\n\n // --- Signal handlers ---\n\n private registerSignalHandlers(): void {\n AppMachinaNode.instances.push(this);\n\n if (AppMachinaNode.globalSignalHandlersRegistered) return;\n AppMachinaNode.globalSignalHandlersRegistered = true;\n\n const handler = (signal: string) => {\n // Track server_shutdown lifecycle event on all instances before shutting down\n for (const instance of AppMachinaNode.instances) {\n try {\n instance.core.track('server_shutdown', { reason: signal }, 'system');\n } catch {\n // Lifecycle events are best-effort\n }\n }\n const shutdowns = AppMachinaNode.instances.map((inst) => inst.shutdown());\n void Promise.allSettled(shutdowns).then(() => {\n process.kill(process.pid, signal);\n });\n };\n\n process.once('SIGTERM', () => handler('SIGTERM'));\n process.once('SIGINT', () => handler('SIGINT'));\n }\n}\n\n// --- Instance ID persistence ---\n\nconst INSTANCE_ID_FILENAME = '.appmachina-instance-id';\n\n/**\n * Read or create a persistent instance ID on the filesystem.\n * Uses `crypto.randomUUID()` for generation.\n */\nfunction getOrCreateInstanceId(appId: string, baseDir?: string): string {\n const dir = join(baseDir ?? tmpdir(), 'appmachina-sdk', appId);\n const filePath = join(dir, INSTANCE_ID_FILENAME);\n\n try {\n if (existsSync(filePath)) {\n const existing = readFileSync(filePath, 'utf-8').trim();\n if (existing) return existing;\n }\n } catch {\n // Fall through to create a new one\n }\n\n const newId = crypto.randomUUID();\n\n try {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(filePath, newId, 'utf-8');\n } catch {\n // If we can't persist, still return the generated ID for this session\n }\n\n return newId;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAaA,MAAa,iBAAiB;CAC5B,aAAa;CACb,UAAU;CACV,OAAO;CACP,SAAS;CACT,UAAU;CACV,UAAU;CACV,aAAa;CACb,iBAAiB;CACjB,mBAAmB;CACnB,gBAAgB;CAChB,aAAa;CACb,WAAW;CACX,aAAa;CACb,gBAAgB;CAChB,mBAAmB;CACnB,QAAQ;CACR,WAAW;CACX,cAAc;CACd,OAAO;CACP,WAAW;CACX,aAAa;CACd;;;;;;;;;;;;;;;;;;ACyED,SAAgB,cACd,KACA,YACA,QACM;CACN,MAAM,WAAW,OAAO,YAAY;CACpC,MAAMA,QAAyB;EAC7B,YAAY,OAAO;EACnB,OAAO,OAAO;EACd,UAAU,OAAO;EACjB;EACA,SAAS,OAAO,QAAQ;EACxB,GAAG,OAAO;EACX;AACD,KAAI,OAAO,kBAAkB,OAAW,OAAM,iBAAiB,OAAO;AACtE,KAAI,OAAO,eAAe,OAAW,OAAM,cAAc,OAAO;AAChE,KAAI,OAAO,UAAU,OAAW,OAAM,QAAQ,OAAO;AAErD,KAAI,MAAM,YAAY,oBAAoB,MAAM;;;;;AAMlD,SAAgB,oBACd,KACA,YACA,QACM;CACN,MAAMA,QAAyB;EAC7B,YAAY,OAAO;EACnB,UAAU,OAAO;EACjB,YAAY,OAAO;EACnB,GAAG,OAAO;EACX;AACD,KAAI,OAAO,iBAAiB,OAAW,OAAM,gBAAgB,OAAO;AAEpE,KAAI,MAAM,YAAY,mBAAmB,MAAM;;;;;;;;;;;;;;;;AAqBjD,SAAgB,kBACd,KACA,YACA,QACM;CACN,MAAMA,QAAyB;EAC7B,YAAY,OAAO;EACnB,OAAO,OAAO;EACd,UAAU,OAAO;EACjB,UAAU;EACV,SAAS,OAAO;EAChB,GAAG,OAAO;EACX;AACD,KAAI,OAAO,kBAAkB,OAAW,OAAM,iBAAiB,OAAO;AACtE,KAAI,OAAO,WAAW,OAAW,OAAM,SAAS,OAAO;AACvD,KAAI,OAAO,cAAc,OAAW,OAAM,aAAa,OAAO;AAC9D,KAAI,OAAO,YAAY,OAAW,OAAM,WAAW,OAAO;AAC1D,KAAI,OAAO,wBAAwB,OACjC,OAAM,wBAAwB,OAAO;AACvC,KAAI,OAAO,0BAA0B,OACnC,OAAM,0BAA0B,OAAO;AAEzC,KAAI,MAAM,YAAY,aAAa,MAAM;;;;;AAU3C,SAAgB,WACd,KACA,YACA,QACM;CACN,MAAM,WAAW,OAAO,YAAY;CACpC,IAAI,QAAQ,OAAO;AACnB,KAAI,OAAO,QAAQ,OAAW,UAAS,OAAO;AAC9C,KAAI,OAAO,aAAa,OAAW,UAAS,OAAO;AACnD,KAAI,OAAO,aAAa,OAAW,UAAS,OAAO;CAEnD,MAAMA,QAAyB;EAC7B,UAAU,OAAO;EACjB,UAAU,OAAO;EACjB;EACA;EACA,YAAY,OAAO,MAAM;EACzB,SAAS;EACT,aAAa,OAAO,MAAM,KAAK,MAAM,EAAE,UAAU,CAAC,KAAK,IAAI;EAC3D,GAAG,OAAO;EACX;AACD,KAAI,OAAO,QAAQ,OAAW,OAAM,MAAM,OAAO;AACjD,KAAI,OAAO,aAAa,OAAW,OAAM,WAAW,OAAO;AAC3D,KAAI,OAAO,aAAa,OAAW,OAAM,WAAW,OAAO;AAC3D,KAAI,OAAO,eAAe,OAAW,OAAM,cAAc,OAAO;AAEhE,KAAI,MAAM,YAAY,oBAAoB,MAAM;;;;;AAMlD,SAAgB,eACd,KACA,YACA,MACA,YACM;CACN,MAAM,WAAW,KAAK,YAAY;CAClC,MAAMA,QAAyB;EAC7B,YAAY,KAAK;EACjB,cAAc,KAAK;EACnB,OAAO,KAAK;EACZ;EACA,OAAO,KAAK,QAAQ;EACpB,GAAG;EACJ;AACD,KAAI,KAAK,aAAa,OAAW,OAAM,WAAW,KAAK;AAEvD,KAAI,MAAM,YAAY,eAAe,MAAM;;;;;AAM7C,SAAgB,oBACd,KACA,YACA,MACA,YACM;CACN,MAAM,WAAW,KAAK,YAAY;CAClC,MAAMA,QAAyB;EAC7B,YAAY,KAAK;EACjB,cAAc,KAAK;EACnB,OAAO,KAAK;EACZ;EACA,GAAG;EACJ;AACD,KAAI,KAAK,aAAa,OAAW,OAAM,WAAW,KAAK;AAEvD,KAAI,MAAM,YAAY,oBAAoB,MAAM;;;;;AAMlD,SAAgB,mBACd,KACA,YACA,OACA,WAAW,OACX,YACM;CACN,MAAM,QAAQ,MAAM,QAAQ,KAAK,SAAS,MAAM,KAAK,SAAS,KAAK,YAAY,IAAI,EAAE;CACrF,MAAMA,QAAyB;EAC7B,YAAY,MAAM;EAClB,OAAO;EACP;EACA,aAAa,MAAM,KAAK,MAAM,EAAE,UAAU,CAAC,KAAK,IAAI;EACpD,GAAG;EACJ;AAED,KAAI,MAAM,YAAY,kBAAkB,MAAM;;;;;AAUhD,SAAgB,iBACd,KACA,YACA,WACA,MACA,OACA,WAAW,OACX,UACA,YACM;CACN,MAAMA,QAAyB;EAC7B,YAAY;EACZ,cAAc;EACd;EACA;EACA,GAAG;EACJ;AACD,KAAI,aAAa,OAAW,OAAM,WAAW;AAE7C,KAAI,MAAM,YAAY,aAAa,MAAM;;;;;AAU3C,SAAgB,YACd,KACA,YACA,QACM;CACN,MAAMA,QAAyB;EAC7B,gBAAgB,OAAO;EACvB,QAAQ,OAAO;EACf,UAAU,OAAO;EACjB,GAAG,OAAO;EACX;AACD,KAAI,OAAO,WAAW,OAAW,OAAM,SAAS,OAAO;AAEvD,KAAI,MAAM,YAAY,UAAU,MAAM;;;;;ACrPxC,MAAMC,cACJ,OAAO,gCAAgC,cAAc,8BAA8B;AAErF,IAAa,iBAAb,MAAa,eAAe;CAC1B,AAAQ;CACR,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAQ,aAAa;CACrB,AAAiB,iBAAkC,EAAE;CACrD,OAAe,iCAAiC;CAChD,OAAe,YAA8B,EAAE;CAC/C,AAAiB;CACjB,AAAQ;CAER,YAAY,QAA8B;EACxC,MAAM,YAAY,YAAY,KAAK;AAEnC,OAAK,SAAS;AACd,OAAK,cAAc,OAAO,eAAe;AACzC,OAAK,WAAW,OAAO,WAAW,6BAA6B,QAAQ,OAAO,GAAG;AACjF,OAAK,yBAAyB,OAAO,0BAA0B;AAG/D,OAAK,cAAc,sBAAsB,OAAO,OAAO,OAAO,eAAe;EAE7E,MAAM,cAAc,IAAI,gBAAgB,OAAO,OAAO,OAAO,eAAe;EAC5E,MAAM,aAAa,IAAI,gBAAgB,IAAO;AAE9C,OAAK,OAAO,eAAe,KAAK;GAC9B,QAAQ;IACN,OAAO,OAAO;IACd,aAAa,OAAO;IACpB,GAAI,OAAO,WAAW,QAAQ,EAAE,SAAS,OAAO,SAAS;IACzD,GAAI,OAAO,eAAe,QAAQ,EAAE,aAAa,OAAO,aAAa;IACrE,iBAAiB,OAAO,mBAAmB;IAC3C,gBAAgB,OAAO,kBAAkB;IACzC,GAAI,OAAO,gBAAgB,QAAQ,EAAE,cAAc,OAAO,cAAc;IACxE,GAAI,OAAO,gBAAgB,QAAQ,EAAE,cAAc,OAAO,cAAc;IACxE,YAAY,QAAQ;IACrB;GACD;GACA;GACD,CAAC;AAEF,OAAK,KAAK,iBAAiB;GACzB,UAAU;GACV,WAAW,GAAG,QAAQ,SAAS,GAAG,QAAQ;GAC1C,YAAY;GACZ,aAAa,QAAQ;GACrB,QAAQ,KAAK,gBAAgB,CAAC,iBAAiB,CAAC,UAAU;GAC1D,WAAW,KAAK;GACjB,CAAC;AAEF,MAAI,OAAO,kBAAkB,MAC3B,MAAK,wBAAwB;AAI/B,OAAK,kBAAkB,KAAK,OAAO,YAAY,KAAK,GAAG,aAAa,IAAI,GAAG;;;;;;;;;CAU7E,MAAM,OAAsB;AAE1B,QAAM,KAAK,KAAK,mBAAmB,CAAC,YAAY,GAE9C;AAGF,MAAI,KAAK,mBAAmB,KAC1B,KAAI;AACF,QAAK,KAAK,MACR,0BACA;IAAE,aAAa,KAAK;IAAiB,UAAU;IAAQ,EACvD,SACD;UACK;AAMV,MAAI;AACF,QAAK,KAAK,MACR,gBACA;IACE,UAAU;IACV,cAAc,QAAQ;IACtB,aAAa,KAAK;IACnB,EACD,SACD;UACK;;;;;;;;;;CAaV,MAAM,YAAoB,WAAmB,YAAoC;AAC/E,MAAI,KAAK,WAAY;AACrB,MAAI,KAAK,YACP,SAAQ,IACN,uBAAuB,UAAU,KAAK,OAAO,KAAK,cAAc,EAAE,CAAC,CAAC,OAAO,2BAA2B,WAAW,IAClH;AAEH,MAAI,CAAC,cAAc,OAAO,eAAe,UAAU;AACjD,QAAK,0BAAU,IAAI,MAAM,wCAAwC,CAAC;AAClE;;AAEF,MAAI;GACF,MAAM,cAAc,KAAK,KAAK,YAAY;AAC1C,QAAK,KAAK,MAAM,WAAW,YAAY,WAAW;GAClD,MAAM,aAAa,KAAK,KAAK,YAAY;AAGzC,OAAI,cAAc,eAAe,KAAK,YACpC,SAAQ,KACN,uBAAuB,UAAU,4CAA4C,YAAY,MAAM,WAAW,GAC3G;WAEI,GAAG;AACV,QAAK,UAAU,EAAE;;;;;;CAOrB,OAAO,YAAoB,YAAoB,YAAoC;AACjF,MAAI,KAAK,WAAY;AACrB,MAAI,KAAK,YACP,SAAQ,IACN,wBAAwB,WAAW,KAAK,OAAO,KAAK,cAAc,EAAE,CAAC,CAAC,OAAO,2BAA2B,WAAW,IACpH;AAEH,MAAI,CAAC,cAAc,OAAO,eAAe,UAAU;AACjD,QAAK,0BAAU,IAAI,MAAM,wCAAwC,CAAC;AAClE;;AAEF,MAAI;AACF,QAAK,KAAK,OAAO,YAAY,YAAY,WAAW;WAC7C,GAAG;AACV,QAAK,UAAU,EAAE;;;;;;;;;;;;;;CAerB,kBAAkB,YAAoB,YAAkC;AACtE,MAAI,KAAK,WAAY;AACrB,MAAI,KAAK,YACP,SAAQ,IACN,kCAAkC,OAAO,KAAK,cAAc,EAAE,CAAC,CAAC,OAAO,2BAA2B,WAAW,IAC9G;AAEH,MAAI,CAAC,cAAc,OAAO,eAAe,UAAU;AACjD,QAAK,0BAAU,IAAI,MAAM,wCAAwC,CAAC;AAClE;;AAEF,MAAI;AACF,QAAK,KAAK,SAAS,WAAW;AAC9B,QAAK,KAAK,kBAAkB,WAAsC;AAClE,QAAK,wBAAwB,YAAY,YAAuC,MAAM;WAC/E,GAAG;AACV,QAAK,UAAU,EAAE;;;;;;;;;;CAWrB,sBAAsB,YAAoB,YAAkC;AAC1E,MAAI,KAAK,WAAY;AACrB,MAAI,KAAK,YACP,SAAQ,IACN,sCAAsC,OAAO,KAAK,cAAc,EAAE,CAAC,CAAC,OAAO,2BAA2B,WAAW,IAClH;AAEH,MAAI,CAAC,cAAc,OAAO,eAAe,UAAU;AACjD,QAAK,0BAAU,IAAI,MAAM,wCAAwC,CAAC;AAClE;;AAEF,MAAI;AACF,QAAK,KAAK,SAAS,WAAW;AAC9B,QAAK,KAAK,sBAAsB,WAAsC;AACtE,QAAK,wBAAwB,YAAY,YAAuC,KAAK;WAC9E,GAAG;AACV,QAAK,UAAU,EAAE;;;;;;;;;;;CAYrB,MAAM,YAAoB,SAAiB,YAAoC;AAC7E,MAAI,KAAK,WAAY;AACrB,MAAI,KAAK,YACP,SAAQ,IACN,uBAAuB,QAAQ,KAAK,OAAO,KAAK,cAAc,EAAE,CAAC,CAAC,OAAO,2BAA2B,WAAW,IAChH;AAEH,MAAI,CAAC,cAAc,OAAO,eAAe,UAAU;AACjD,QAAK,0BAAU,IAAI,MAAM,wCAAwC,CAAC;AAClE;;AAEF,MAAI;AACF,QAAK,KAAK,SAAS,WAAW;AAC9B,QAAK,KAAK,MAAM,SAAS,WAAW;WAC7B,GAAG;AACV,QAAK,UAAU,EAAE;;;;;;;;;CAUrB,WAAW,SAA6B;AACtC,MAAI,KAAK,WAAY;AACrB,MAAI;AACF,QAAK,KAAK,WAAW,QAAQ;WACtB,GAAG;AACV,QAAK,UAAU,EAAE;;;;;;;;;;CAWrB,gBAAwB;AACtB,SAAO,KAAK;;;;;CAMd,eAAuB;AACrB,SAAO,KAAK,KAAK,cAAc;;;;;CAMjC,kBAAgC;AAC9B,SAAO,KAAK,KAAK,iBAAiB;;;;;CAMpC,aAAqB;AACnB,SAAO,KAAK,KAAK,YAAY;;;;;CAM/B,MAAM,QAAuB;AAC3B,MAAI,KAAK,WAAY;AACrB,MAAI;AACF,SAAM,KAAK,KAAK,YAAY;WACrB,GAAG;AACV,QAAK,UAAU,EAAE;;;;;;CAOrB,MAAM,WAA0B;AAC9B,MAAI,KAAK,WAAY;AACrB,OAAK,aAAa;EAGlB,MAAM,MAAM,eAAe,UAAU,QAAQ,KAAK;AAClD,MAAI,QAAQ,GAAI,gBAAe,UAAU,OAAO,KAAK,EAAE;AAGvD,MAAI;AACF,SAAM,QAAQ,KAAK,CACjB,KAAK,KAAK,YAAY,EACtB,IAAI,SAAe,YAAY,WAAW,SAAS,KAAK,uBAAuB,CAAC,CACjF,CAAC;UACI;AAIR,OAAK,KAAK,UAAU;;;;;;CAOtB,GAAG,OAAgB,UAA+B;AAChD,OAAK,eAAe,KAAK,SAAS;AAClC,SAAO;;;;;CAMT,IAAI,OAAgB,UAA+B;EACjD,MAAM,MAAM,KAAK,eAAe,QAAQ,SAAS;AACjD,MAAI,QAAQ,GAAI,MAAK,eAAe,OAAO,KAAK,EAAE;AAClD,SAAO;;CAGT,AAAQ,UAAU,GAAkB;EAClC,MAAM,QAAQ,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,EAAE,CAAC;AAC3D,OAAK,MAAM,YAAY,KAAK,eAC1B,KAAI;AACF,YAAS,MAAM;UACT;AAIV,MAAI,KAAK,eAAe,KAAK,eAAe,WAAW,EACrD,SAAQ,KAAK,gBAAgB,MAAM,QAAQ;;;;;;CAU/C,AAAQ,wBACN,YACA,YACA,SACM;EACN,MAAMC,UAAmC;GACvC,QAAQ,KAAK,OAAO;GACpB,aAAa;GACb;GACA,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC;AACD,MAAI,QACF,SAAQ,WAAW;AAGrB,QAAM,GAAG,KAAK,QAAQ,oBAAoB;GACxC,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,YAAY,KAAK,OAAO;IACxB,iBAAiB,QAAQ;IAC1B;GACD,MAAM,KAAK,UAAU,QAAQ;GAC9B,CAAC,CAAC,YAAY,GAEb;;CAKJ,AAAQ,yBAA+B;AACrC,iBAAe,UAAU,KAAK,KAAK;AAEnC,MAAI,eAAe,+BAAgC;AACnD,iBAAe,iCAAiC;EAEhD,MAAM,WAAW,WAAmB;AAElC,QAAK,MAAM,YAAY,eAAe,UACpC,KAAI;AACF,aAAS,KAAK,MAAM,mBAAmB,EAAE,QAAQ,QAAQ,EAAE,SAAS;WAC9D;GAIV,MAAM,YAAY,eAAe,UAAU,KAAK,SAAS,KAAK,UAAU,CAAC;AACzE,GAAK,QAAQ,WAAW,UAAU,CAAC,WAAW;AAC5C,YAAQ,KAAK,QAAQ,KAAK,OAAO;KACjC;;AAGJ,UAAQ,KAAK,iBAAiB,QAAQ,UAAU,CAAC;AACjD,UAAQ,KAAK,gBAAgB,QAAQ,SAAS,CAAC;;;AAMnD,MAAM,uBAAuB;;;;;AAM7B,SAAS,sBAAsB,OAAe,SAA0B;CACtE,MAAM,MAAM,KAAK,WAAW,QAAQ,EAAE,kBAAkB,MAAM;CAC9D,MAAM,WAAW,KAAK,KAAK,qBAAqB;AAEhD,KAAI;AACF,MAAI,WAAW,SAAS,EAAE;GACxB,MAAM,WAAW,aAAa,UAAU,QAAQ,CAAC,MAAM;AACvD,OAAI,SAAU,QAAO;;SAEjB;CAIR,MAAM,QAAQ,OAAO,YAAY;AAEjC,KAAI;AACF,MAAI,CAAC,WAAW,IAAI,CAClB,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;AAErC,gBAAc,UAAU,OAAO,QAAQ;SACjC;AAIR,QAAO"}
|