@seaverse/dataservice 1.8.3 → 1.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -471,6 +471,9 @@ collection.insert(data: T): Promise<DataRecord<T>>
471
471
 
472
472
  // Insert with custom UUID
473
473
  collection.insert(data: T, id: string): Promise<DataRecord<T>>
474
+
475
+ // Upsert with deterministic per-user UUID derived from user_id + app_id + collection name
476
+ collection.upsert(data: T): Promise<DataRecord<T>>
474
477
  ```
475
478
 
476
479
  #### Read
package/dist/index.d.mts CHANGED
@@ -183,6 +183,13 @@ interface QueryBuilder<T = any> {
183
183
  interface Collection<T = any> {
184
184
  /** Insert a new record (optionally provide UUID) */
185
185
  insert(data: T, id?: string): Promise<DataRecord<T>>;
186
+ /**
187
+ * Upsert a record using a stable per-user key.
188
+ *
189
+ * The SDK derives a deterministic UUID from the authenticated user_id,
190
+ * app_id, and collection name, then uses PostgREST on_conflict=id.
191
+ */
192
+ upsert(data: T): Promise<DataRecord<T>>;
186
193
  /** Get a single record by ID (alias for selectById) */
187
194
  get(id: string): Promise<DataRecord<T> | null>;
188
195
  /** Select records with optional filters */
@@ -239,6 +246,8 @@ interface DataTable {
239
246
  interface DataServiceClient {
240
247
  /** Automatically extracted application ID from current URL */
241
248
  readonly appId: string;
249
+ /** User ID decoded from the JWT token when available */
250
+ readonly userId: string | null;
242
251
  /** Access user private data table (user_data) */
243
252
  readonly userData: DataTable;
244
253
  /** Get user data statistics (RPC call) */
@@ -302,6 +311,53 @@ declare function debugSetToken(token: string): void;
302
311
  * ```
303
312
  */
304
313
  declare function setAppId(appId: string): void;
314
+ /**
315
+ * Create a new Data Service client
316
+ *
317
+ * The SDK automatically obtains the service host from the parent page via PostMessage (500ms timeout).
318
+ * If the fetch fails, it falls back to the default host: https://dataservice-api.seaverse.ai
319
+ *
320
+ * AI-friendly: Single entry point with clear configuration
321
+ *
322
+ * @param config - Client configuration (optional)
323
+ * @returns DataServiceClient instance
324
+ *
325
+ * @example
326
+ * ```typescript
327
+ * // Auto-fetch token and serviceHost from parent page (iframe)
328
+ * // ServiceHost fetch: 500ms timeout, falls back to https://dataservice-api.seaverse.ai
329
+ * const client = await createClient({});
330
+ *
331
+ * // For development/testing, use debugSetToken
332
+ * import { debugSetToken, createClient } from '@seaverse/dataservice';
333
+ * debugSetToken('your-test-token');
334
+ * const client = await createClient({});
335
+ *
336
+ * // appId is automatically extracted from current URL
337
+ * const order = await client.userData.collection('orders').insert({ ... });
338
+ * const orders = await client.userData.collection('orders').select().execute();
339
+ * ```
340
+ */
341
+ /**
342
+ * Notify the parent (SeaVerse App or iframe) of the current game score.
343
+ *
344
+ * Fire-and-forget — no response is expected.
345
+ *
346
+ * Protocol:
347
+ * - RN WebView: window.ReactNativeWebView.postMessage({ type: 'seaverse:game_score', score: number })
348
+ * - iframe: window.parent.postMessage({ type: 'seaverse:game_score', score: number }, '*')
349
+ *
350
+ * @param score Current game score (must be a non-negative number)
351
+ *
352
+ * @example
353
+ * ```typescript
354
+ * import { notifyGameScore } from '@seaverse/dataservice';
355
+ *
356
+ * // Call whenever the score updates
357
+ * notifyGameScore(1234);
358
+ * ```
359
+ */
360
+ declare function notifyGameScore(score: number): void;
305
361
  declare function createClient(config?: ClientConfig): Promise<DataServiceClient>;
306
362
 
307
363
  /**
@@ -350,6 +406,6 @@ declare function createClient(config?: ClientConfig): Promise<DataServiceClient>
350
406
  * ```
351
407
  */
352
408
 
353
- declare const VERSION = "1.8.0";
409
+ declare const VERSION = "1.8.4";
354
410
 
355
- export { type APIError, type ClientConfig, type Collection, type DataRecord, type DataServiceClient, DataServiceError, type DataTable, type OrderOptions, type QueryBuilder, type QueryFilter, type UserDataStats, VERSION, createClient, debugSetToken, setAppId };
411
+ export { type APIError, type ClientConfig, type Collection, type DataRecord, type DataServiceClient, DataServiceError, type DataTable, type OrderOptions, type QueryBuilder, type QueryFilter, type UserDataStats, VERSION, createClient, debugSetToken, notifyGameScore, setAppId };
package/dist/index.d.ts CHANGED
@@ -183,6 +183,13 @@ interface QueryBuilder<T = any> {
183
183
  interface Collection<T = any> {
184
184
  /** Insert a new record (optionally provide UUID) */
185
185
  insert(data: T, id?: string): Promise<DataRecord<T>>;
186
+ /**
187
+ * Upsert a record using a stable per-user key.
188
+ *
189
+ * The SDK derives a deterministic UUID from the authenticated user_id,
190
+ * app_id, and collection name, then uses PostgREST on_conflict=id.
191
+ */
192
+ upsert(data: T): Promise<DataRecord<T>>;
186
193
  /** Get a single record by ID (alias for selectById) */
187
194
  get(id: string): Promise<DataRecord<T> | null>;
188
195
  /** Select records with optional filters */
@@ -239,6 +246,8 @@ interface DataTable {
239
246
  interface DataServiceClient {
240
247
  /** Automatically extracted application ID from current URL */
241
248
  readonly appId: string;
249
+ /** User ID decoded from the JWT token when available */
250
+ readonly userId: string | null;
242
251
  /** Access user private data table (user_data) */
243
252
  readonly userData: DataTable;
244
253
  /** Get user data statistics (RPC call) */
@@ -302,6 +311,53 @@ declare function debugSetToken(token: string): void;
302
311
  * ```
303
312
  */
304
313
  declare function setAppId(appId: string): void;
314
+ /**
315
+ * Create a new Data Service client
316
+ *
317
+ * The SDK automatically obtains the service host from the parent page via PostMessage (500ms timeout).
318
+ * If the fetch fails, it falls back to the default host: https://dataservice-api.seaverse.ai
319
+ *
320
+ * AI-friendly: Single entry point with clear configuration
321
+ *
322
+ * @param config - Client configuration (optional)
323
+ * @returns DataServiceClient instance
324
+ *
325
+ * @example
326
+ * ```typescript
327
+ * // Auto-fetch token and serviceHost from parent page (iframe)
328
+ * // ServiceHost fetch: 500ms timeout, falls back to https://dataservice-api.seaverse.ai
329
+ * const client = await createClient({});
330
+ *
331
+ * // For development/testing, use debugSetToken
332
+ * import { debugSetToken, createClient } from '@seaverse/dataservice';
333
+ * debugSetToken('your-test-token');
334
+ * const client = await createClient({});
335
+ *
336
+ * // appId is automatically extracted from current URL
337
+ * const order = await client.userData.collection('orders').insert({ ... });
338
+ * const orders = await client.userData.collection('orders').select().execute();
339
+ * ```
340
+ */
341
+ /**
342
+ * Notify the parent (SeaVerse App or iframe) of the current game score.
343
+ *
344
+ * Fire-and-forget — no response is expected.
345
+ *
346
+ * Protocol:
347
+ * - RN WebView: window.ReactNativeWebView.postMessage({ type: 'seaverse:game_score', score: number })
348
+ * - iframe: window.parent.postMessage({ type: 'seaverse:game_score', score: number }, '*')
349
+ *
350
+ * @param score Current game score (must be a non-negative number)
351
+ *
352
+ * @example
353
+ * ```typescript
354
+ * import { notifyGameScore } from '@seaverse/dataservice';
355
+ *
356
+ * // Call whenever the score updates
357
+ * notifyGameScore(1234);
358
+ * ```
359
+ */
360
+ declare function notifyGameScore(score: number): void;
305
361
  declare function createClient(config?: ClientConfig): Promise<DataServiceClient>;
306
362
 
307
363
  /**
@@ -350,6 +406,6 @@ declare function createClient(config?: ClientConfig): Promise<DataServiceClient>
350
406
  * ```
351
407
  */
352
408
 
353
- declare const VERSION = "1.8.0";
409
+ declare const VERSION = "1.8.4";
354
410
 
355
- export { type APIError, type ClientConfig, type Collection, type DataRecord, type DataServiceClient, DataServiceError, type DataTable, type OrderOptions, type QueryBuilder, type QueryFilter, type UserDataStats, VERSION, createClient, debugSetToken, setAppId };
411
+ export { type APIError, type ClientConfig, type Collection, type DataRecord, type DataServiceClient, DataServiceError, type DataTable, type OrderOptions, type QueryBuilder, type QueryFilter, type UserDataStats, VERSION, createClient, debugSetToken, notifyGameScore, setAppId };
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ __export(index_exports, {
24
24
  VERSION: () => VERSION,
25
25
  createClient: () => createClient,
26
26
  debugSetToken: () => debugSetToken,
27
+ notifyGameScore: () => notifyGameScore,
27
28
  setAppId: () => setAppId
28
29
  });
29
30
  module.exports = __toCommonJS(index_exports);
@@ -43,6 +44,64 @@ var DataServiceError = class extends Error {
43
44
  // src/client.ts
44
45
  var debugToken = null;
45
46
  var manualAppId = null;
47
+ function decodeBase64Url(value) {
48
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
49
+ const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
50
+ const buffer = globalThis.Buffer;
51
+ if (buffer) {
52
+ return buffer.from(padded, "base64").toString("utf8");
53
+ }
54
+ const atobFn = globalThis.atob;
55
+ if (typeof atobFn === "function") {
56
+ const binary = atobFn(padded);
57
+ const bytes = Uint8Array.from(Array.from(binary, (char) => char.charCodeAt(0)));
58
+ const textDecoder = globalThis.TextDecoder;
59
+ if (typeof textDecoder === "function") {
60
+ return new textDecoder().decode(bytes);
61
+ }
62
+ return binary;
63
+ }
64
+ throw new DataServiceError("Unable to decode JWT payload", "JWT_DECODE_UNSUPPORTED");
65
+ }
66
+ function extractUserIdFromToken(token) {
67
+ if (!token) return null;
68
+ const rawToken = token.startsWith("Bearer ") ? token.slice("Bearer ".length) : token;
69
+ const parts = rawToken.split(".");
70
+ if (parts.length < 2) return null;
71
+ try {
72
+ const payload = JSON.parse(decodeBase64Url(parts[1]));
73
+ return payload?.payload?.user_id || payload?.user_id || payload?.sub || null;
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+ function stableUuidFromParts(...parts) {
79
+ const input = parts.join("");
80
+ let h1 = 3735928559;
81
+ let h2 = 1103547991;
82
+ let h3 = 3235826430;
83
+ let h4 = 305419896;
84
+ for (let i = 0; i < input.length; i++) {
85
+ const ch = input.charCodeAt(i);
86
+ h1 = Math.imul(h1 ^ ch, 2654435761);
87
+ h2 = Math.imul(h2 ^ ch, 1597334677);
88
+ h3 = Math.imul(h3 ^ ch, 2246822507);
89
+ h4 = Math.imul(h4 ^ ch, 3266489909);
90
+ }
91
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
92
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h3 ^ h3 >>> 13, 3266489909);
93
+ h3 = Math.imul(h3 ^ h3 >>> 16, 2246822507) ^ Math.imul(h4 ^ h4 >>> 13, 3266489909);
94
+ h4 = Math.imul(h4 ^ h4 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
95
+ const bytes = new Uint8Array(16);
96
+ new DataView(bytes.buffer).setUint32(0, h1 >>> 0);
97
+ new DataView(bytes.buffer).setUint32(4, h2 >>> 0);
98
+ new DataView(bytes.buffer).setUint32(8, h3 >>> 0);
99
+ new DataView(bytes.buffer).setUint32(12, h4 >>> 0);
100
+ bytes[6] = bytes[6] & 15 | 80;
101
+ bytes[8] = bytes[8] & 63 | 128;
102
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
103
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
104
+ }
46
105
  function debugSetToken(token) {
47
106
  debugToken = token;
48
107
  }
@@ -399,11 +458,12 @@ var QueryBuilderImpl = class {
399
458
  }
400
459
  };
401
460
  var CollectionImpl = class {
402
- constructor(client, tablePath, appId, collectionName) {
461
+ constructor(client, tablePath, appId, collectionName, userId) {
403
462
  this.client = client;
404
463
  this.tablePath = tablePath;
405
464
  this.appId = appId;
406
465
  this.collectionName = collectionName;
466
+ this.userId = userId;
407
467
  }
408
468
  async insert(data, id) {
409
469
  const payload = {
@@ -417,6 +477,27 @@ var CollectionImpl = class {
417
477
  const response = await this.client.post(this.tablePath, payload);
418
478
  return Array.isArray(response) ? response[0] : response;
419
479
  }
480
+ async upsert(data) {
481
+ if (!this.userId) {
482
+ throw new DataServiceError(
483
+ "Unable to derive user_id from token for deterministic upsert",
484
+ "MISSING_USER_ID"
485
+ );
486
+ }
487
+ const id = stableUuidFromParts(this.userId, this.appId, this.collectionName);
488
+ const payload = {
489
+ id,
490
+ app_id: this.appId,
491
+ collection_name: this.collectionName,
492
+ data
493
+ };
494
+ const response = await this.client.post(
495
+ `${this.tablePath}?on_conflict=id`,
496
+ payload,
497
+ { Prefer: "resolution=merge-duplicates,return=representation" }
498
+ );
499
+ return Array.isArray(response) ? response[0] : response;
500
+ }
420
501
  async get(id) {
421
502
  return this.selectById(id);
422
503
  }
@@ -472,13 +553,14 @@ var CollectionImpl = class {
472
553
  }
473
554
  };
474
555
  var DataTableImpl = class {
475
- constructor(client, tablePath, appId) {
556
+ constructor(client, tablePath, appId, userId) {
476
557
  this.client = client;
477
558
  this.tablePath = tablePath;
478
559
  this.appId = appId;
560
+ this.userId = userId;
479
561
  }
480
562
  collection(name) {
481
- return new CollectionImpl(this.client, this.tablePath, this.appId, name);
563
+ return new CollectionImpl(this.client, this.tablePath, this.appId, name, this.userId);
482
564
  }
483
565
  async batchInsert(baseCollectionName, records) {
484
566
  const results = [];
@@ -488,7 +570,8 @@ var DataTableImpl = class {
488
570
  this.client,
489
571
  this.tablePath,
490
572
  this.appId,
491
- uniqueCollectionName
573
+ uniqueCollectionName,
574
+ this.userId
492
575
  );
493
576
  const result = await collection.insert(records[i]);
494
577
  results.push(result);
@@ -503,10 +586,12 @@ var DataServiceClientImpl = class {
503
586
  client;
504
587
  userData;
505
588
  appId;
506
- constructor(client, appId) {
589
+ userId;
590
+ constructor(client, appId, userId) {
507
591
  this.client = client;
508
592
  this.appId = appId;
509
- this.userData = new DataTableImpl(this.client, "/user_data", this.appId);
593
+ this.userId = userId;
594
+ this.userData = new DataTableImpl(this.client, "/user_data", this.appId, userId);
510
595
  }
511
596
  async getStats() {
512
597
  const response = await this.client.post("/rpc/get_user_data_stats");
@@ -516,6 +601,19 @@ var DataServiceClientImpl = class {
516
601
  return this.client.post("/rpc/health");
517
602
  }
518
603
  };
604
+ function notifyGameScore(score) {
605
+ if (!isInIframe() && !isInSeaVerseApp()) return;
606
+ const payload = JSON.stringify({ type: "seaverse:game_score", score });
607
+ try {
608
+ if (isInSeaVerseApp()) {
609
+ globalThis.window.ReactNativeWebView.postMessage(payload);
610
+ } else {
611
+ globalThis.window.parent.postMessage({ type: "seaverse:game_score", score }, "*");
612
+ }
613
+ } catch (e) {
614
+ console.warn("[SeaVerse DataService SDK] Failed to notify game score:", e);
615
+ }
616
+ }
519
617
  async function createClient(config = {}) {
520
618
  let token = null;
521
619
  let serviceHost = null;
@@ -547,16 +645,18 @@ async function createClient(config = {}) {
547
645
  }
548
646
  }
549
647
  const appId = manualAppId || parentAppId || extractAppId();
550
- return new DataServiceClientImpl(httpClient, appId);
648
+ const userId = extractUserIdFromToken(token);
649
+ return new DataServiceClientImpl(httpClient, appId, userId);
551
650
  }
552
651
 
553
652
  // src/index.ts
554
- var VERSION = "1.8.0";
653
+ var VERSION = "1.8.4";
555
654
  // Annotate the CommonJS export names for ESM import in node:
556
655
  0 && (module.exports = {
557
656
  DataServiceError,
558
657
  VERSION,
559
658
  createClient,
560
659
  debugSetToken,
660
+ notifyGameScore,
561
661
  setAppId
562
662
  });
package/dist/index.mjs CHANGED
@@ -13,6 +13,64 @@ var DataServiceError = class extends Error {
13
13
  // src/client.ts
14
14
  var debugToken = null;
15
15
  var manualAppId = null;
16
+ function decodeBase64Url(value) {
17
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
18
+ const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
19
+ const buffer = globalThis.Buffer;
20
+ if (buffer) {
21
+ return buffer.from(padded, "base64").toString("utf8");
22
+ }
23
+ const atobFn = globalThis.atob;
24
+ if (typeof atobFn === "function") {
25
+ const binary = atobFn(padded);
26
+ const bytes = Uint8Array.from(Array.from(binary, (char) => char.charCodeAt(0)));
27
+ const textDecoder = globalThis.TextDecoder;
28
+ if (typeof textDecoder === "function") {
29
+ return new textDecoder().decode(bytes);
30
+ }
31
+ return binary;
32
+ }
33
+ throw new DataServiceError("Unable to decode JWT payload", "JWT_DECODE_UNSUPPORTED");
34
+ }
35
+ function extractUserIdFromToken(token) {
36
+ if (!token) return null;
37
+ const rawToken = token.startsWith("Bearer ") ? token.slice("Bearer ".length) : token;
38
+ const parts = rawToken.split(".");
39
+ if (parts.length < 2) return null;
40
+ try {
41
+ const payload = JSON.parse(decodeBase64Url(parts[1]));
42
+ return payload?.payload?.user_id || payload?.user_id || payload?.sub || null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ function stableUuidFromParts(...parts) {
48
+ const input = parts.join("");
49
+ let h1 = 3735928559;
50
+ let h2 = 1103547991;
51
+ let h3 = 3235826430;
52
+ let h4 = 305419896;
53
+ for (let i = 0; i < input.length; i++) {
54
+ const ch = input.charCodeAt(i);
55
+ h1 = Math.imul(h1 ^ ch, 2654435761);
56
+ h2 = Math.imul(h2 ^ ch, 1597334677);
57
+ h3 = Math.imul(h3 ^ ch, 2246822507);
58
+ h4 = Math.imul(h4 ^ ch, 3266489909);
59
+ }
60
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
61
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h3 ^ h3 >>> 13, 3266489909);
62
+ h3 = Math.imul(h3 ^ h3 >>> 16, 2246822507) ^ Math.imul(h4 ^ h4 >>> 13, 3266489909);
63
+ h4 = Math.imul(h4 ^ h4 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
64
+ const bytes = new Uint8Array(16);
65
+ new DataView(bytes.buffer).setUint32(0, h1 >>> 0);
66
+ new DataView(bytes.buffer).setUint32(4, h2 >>> 0);
67
+ new DataView(bytes.buffer).setUint32(8, h3 >>> 0);
68
+ new DataView(bytes.buffer).setUint32(12, h4 >>> 0);
69
+ bytes[6] = bytes[6] & 15 | 80;
70
+ bytes[8] = bytes[8] & 63 | 128;
71
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
72
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
73
+ }
16
74
  function debugSetToken(token) {
17
75
  debugToken = token;
18
76
  }
@@ -369,11 +427,12 @@ var QueryBuilderImpl = class {
369
427
  }
370
428
  };
371
429
  var CollectionImpl = class {
372
- constructor(client, tablePath, appId, collectionName) {
430
+ constructor(client, tablePath, appId, collectionName, userId) {
373
431
  this.client = client;
374
432
  this.tablePath = tablePath;
375
433
  this.appId = appId;
376
434
  this.collectionName = collectionName;
435
+ this.userId = userId;
377
436
  }
378
437
  async insert(data, id) {
379
438
  const payload = {
@@ -387,6 +446,27 @@ var CollectionImpl = class {
387
446
  const response = await this.client.post(this.tablePath, payload);
388
447
  return Array.isArray(response) ? response[0] : response;
389
448
  }
449
+ async upsert(data) {
450
+ if (!this.userId) {
451
+ throw new DataServiceError(
452
+ "Unable to derive user_id from token for deterministic upsert",
453
+ "MISSING_USER_ID"
454
+ );
455
+ }
456
+ const id = stableUuidFromParts(this.userId, this.appId, this.collectionName);
457
+ const payload = {
458
+ id,
459
+ app_id: this.appId,
460
+ collection_name: this.collectionName,
461
+ data
462
+ };
463
+ const response = await this.client.post(
464
+ `${this.tablePath}?on_conflict=id`,
465
+ payload,
466
+ { Prefer: "resolution=merge-duplicates,return=representation" }
467
+ );
468
+ return Array.isArray(response) ? response[0] : response;
469
+ }
390
470
  async get(id) {
391
471
  return this.selectById(id);
392
472
  }
@@ -442,13 +522,14 @@ var CollectionImpl = class {
442
522
  }
443
523
  };
444
524
  var DataTableImpl = class {
445
- constructor(client, tablePath, appId) {
525
+ constructor(client, tablePath, appId, userId) {
446
526
  this.client = client;
447
527
  this.tablePath = tablePath;
448
528
  this.appId = appId;
529
+ this.userId = userId;
449
530
  }
450
531
  collection(name) {
451
- return new CollectionImpl(this.client, this.tablePath, this.appId, name);
532
+ return new CollectionImpl(this.client, this.tablePath, this.appId, name, this.userId);
452
533
  }
453
534
  async batchInsert(baseCollectionName, records) {
454
535
  const results = [];
@@ -458,7 +539,8 @@ var DataTableImpl = class {
458
539
  this.client,
459
540
  this.tablePath,
460
541
  this.appId,
461
- uniqueCollectionName
542
+ uniqueCollectionName,
543
+ this.userId
462
544
  );
463
545
  const result = await collection.insert(records[i]);
464
546
  results.push(result);
@@ -473,10 +555,12 @@ var DataServiceClientImpl = class {
473
555
  client;
474
556
  userData;
475
557
  appId;
476
- constructor(client, appId) {
558
+ userId;
559
+ constructor(client, appId, userId) {
477
560
  this.client = client;
478
561
  this.appId = appId;
479
- this.userData = new DataTableImpl(this.client, "/user_data", this.appId);
562
+ this.userId = userId;
563
+ this.userData = new DataTableImpl(this.client, "/user_data", this.appId, userId);
480
564
  }
481
565
  async getStats() {
482
566
  const response = await this.client.post("/rpc/get_user_data_stats");
@@ -486,6 +570,19 @@ var DataServiceClientImpl = class {
486
570
  return this.client.post("/rpc/health");
487
571
  }
488
572
  };
573
+ function notifyGameScore(score) {
574
+ if (!isInIframe() && !isInSeaVerseApp()) return;
575
+ const payload = JSON.stringify({ type: "seaverse:game_score", score });
576
+ try {
577
+ if (isInSeaVerseApp()) {
578
+ globalThis.window.ReactNativeWebView.postMessage(payload);
579
+ } else {
580
+ globalThis.window.parent.postMessage({ type: "seaverse:game_score", score }, "*");
581
+ }
582
+ } catch (e) {
583
+ console.warn("[SeaVerse DataService SDK] Failed to notify game score:", e);
584
+ }
585
+ }
489
586
  async function createClient(config = {}) {
490
587
  let token = null;
491
588
  let serviceHost = null;
@@ -517,15 +614,17 @@ async function createClient(config = {}) {
517
614
  }
518
615
  }
519
616
  const appId = manualAppId || parentAppId || extractAppId();
520
- return new DataServiceClientImpl(httpClient, appId);
617
+ const userId = extractUserIdFromToken(token);
618
+ return new DataServiceClientImpl(httpClient, appId, userId);
521
619
  }
522
620
 
523
621
  // src/index.ts
524
- var VERSION = "1.8.0";
622
+ var VERSION = "1.8.4";
525
623
  export {
526
624
  DataServiceError,
527
625
  VERSION,
528
626
  createClient,
529
627
  debugSetToken,
628
+ notifyGameScore,
530
629
  setAppId
531
630
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seaverse/dataservice",
3
- "version": "1.8.3",
3
+ "version": "1.8.5",
4
4
  "description": "AI-Friendly Universal Data Storage SDK for TypeScript/JavaScript",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",