@omen.dog/sdk 1.0.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/dist/index.js ADDED
@@ -0,0 +1,542 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ OmenAuthError: () => OmenAuthError,
24
+ OmenClient: () => OmenClient,
25
+ OmenError: () => OmenError,
26
+ OmenNotFoundError: () => OmenNotFoundError,
27
+ OmenRateLimitError: () => OmenRateLimitError,
28
+ OmenValidationError: () => OmenValidationError
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/errors.ts
33
+ var OmenError = class extends Error {
34
+ /** HTTP status code from the API. */
35
+ status;
36
+ /** Machine-readable error code (e.g. "RATE_LIMITED", "NOT_FOUND"). */
37
+ code;
38
+ constructor(message, status, code) {
39
+ super(message);
40
+ this.name = "OmenError";
41
+ this.status = status;
42
+ this.code = code;
43
+ }
44
+ };
45
+ var OmenNotFoundError = class extends OmenError {
46
+ constructor(message = "Resource not found") {
47
+ super(message, 404, "NOT_FOUND");
48
+ this.name = "OmenNotFoundError";
49
+ }
50
+ };
51
+ var OmenAuthError = class extends OmenError {
52
+ constructor(message = "Authentication failed") {
53
+ super(message, 401, "AUTH_FAILED");
54
+ this.name = "OmenAuthError";
55
+ }
56
+ };
57
+ var OmenValidationError = class extends OmenError {
58
+ /** Field-level validation errors, if available. */
59
+ errors;
60
+ constructor(message, errors) {
61
+ super(message, 422, "VALIDATION_ERROR");
62
+ this.name = "OmenValidationError";
63
+ this.errors = errors;
64
+ }
65
+ };
66
+ var OmenRateLimitError = class extends OmenError {
67
+ /** Seconds until the rate limit resets. */
68
+ retryAfter;
69
+ /** Remaining requests in the current window. */
70
+ remaining;
71
+ constructor(message = "Rate limit exceeded", retryAfter, remaining) {
72
+ super(message, 429, "RATE_LIMITED");
73
+ this.name = "OmenRateLimitError";
74
+ this.retryAfter = retryAfter;
75
+ this.remaining = remaining;
76
+ }
77
+ };
78
+
79
+ // src/client.ts
80
+ var HttpClient = class {
81
+ baseUrl;
82
+ token;
83
+ constructor(token, baseUrl) {
84
+ this.token = token;
85
+ this.baseUrl = baseUrl.replace(/\/$/, "");
86
+ }
87
+ async request(path, opts = {}) {
88
+ const url = new URL(path, this.baseUrl);
89
+ if (opts.query) {
90
+ for (const [k, v] of Object.entries(opts.query)) {
91
+ if (v !== void 0) url.searchParams.set(k, String(v));
92
+ }
93
+ }
94
+ const headers = {
95
+ "Authorization": `Bearer ${this.token}`,
96
+ "Accept": "application/json",
97
+ ...opts.headers
98
+ };
99
+ if (opts.body !== void 0) {
100
+ headers["Content-Type"] = "application/json";
101
+ }
102
+ const res = await fetch(url.toString(), {
103
+ method: opts.method ?? "GET",
104
+ headers,
105
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
106
+ });
107
+ if (!res.ok) {
108
+ await this.handleError(res);
109
+ }
110
+ const text = await res.text();
111
+ return text ? JSON.parse(text) : {};
112
+ }
113
+ async handleError(res) {
114
+ let body = {};
115
+ try {
116
+ body = await res.json();
117
+ } catch {
118
+ }
119
+ const message = body.error || body.message || res.statusText;
120
+ switch (res.status) {
121
+ case 401:
122
+ case 403:
123
+ throw new OmenAuthError(message);
124
+ case 404:
125
+ throw new OmenNotFoundError(message);
126
+ case 422:
127
+ throw new OmenValidationError(message, body.errors);
128
+ case 429: {
129
+ const retryAfter = res.headers.get("Retry-After");
130
+ const remaining = res.headers.get("X-RateLimit-Remaining");
131
+ throw new OmenRateLimitError(
132
+ message,
133
+ retryAfter ? Number(retryAfter) : void 0,
134
+ remaining ? Number(remaining) : void 0
135
+ );
136
+ }
137
+ default:
138
+ throw new OmenError(message, res.status);
139
+ }
140
+ }
141
+ };
142
+
143
+ // src/namespaces/users.ts
144
+ var UsersNamespace = class {
145
+ constructor(http) {
146
+ this.http = http;
147
+ }
148
+ /**
149
+ * Get a user's profile by ID or username.
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * const profile = await omen.users.get('pistolphoenix');
154
+ * console.log(profile.user.username, profile.stats.creationsCount);
155
+ * ```
156
+ */
157
+ async get(userId) {
158
+ return this.http.request(`/api/v1/users/${encodeURIComponent(userId)}/profile`);
159
+ }
160
+ /**
161
+ * Get a user's friend list with optional filtering and pagination.
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * const { friends, onlineCount } = await omen.users.friends('pistolphoenix', {
166
+ * status: 'online',
167
+ * limit: 10,
168
+ * });
169
+ * ```
170
+ */
171
+ async friends(userId, options = {}) {
172
+ return this.http.request(`/api/v1/users/${encodeURIComponent(userId)}/friends`, {
173
+ query: {
174
+ status: options.status,
175
+ sort: options.sort,
176
+ search: options.search,
177
+ cursor: options.cursor,
178
+ limit: options.limit
179
+ }
180
+ });
181
+ }
182
+ };
183
+
184
+ // src/namespaces/storage.ts
185
+ var StorageNamespace = class {
186
+ constructor(http) {
187
+ this.http = http;
188
+ }
189
+ /**
190
+ * Get the current user's stored data for your app.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * const { data } = await omen.storage.get();
195
+ * console.log(data.highScore);
196
+ * ```
197
+ */
198
+ async get() {
199
+ return this.http.request("/api/appdata");
200
+ }
201
+ /**
202
+ * Replace the current user's stored data entirely.
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * await omen.storage.set({ highScore: 100, level: 5 });
207
+ * ```
208
+ */
209
+ async set(data) {
210
+ return this.http.request("/api/appdata", {
211
+ method: "PUT",
212
+ body: { data }
213
+ });
214
+ }
215
+ /**
216
+ * Shallow-merge fields into the current user's stored data.
217
+ * Existing fields not in the payload are preserved.
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * await omen.storage.merge({ highScore: 200 }); // keeps 'level' intact
222
+ * ```
223
+ */
224
+ async merge(data) {
225
+ return this.http.request("/api/appdata", {
226
+ method: "PATCH",
227
+ body: { data }
228
+ });
229
+ }
230
+ };
231
+
232
+ // src/namespaces/items.ts
233
+ var ItemsNamespace = class {
234
+ constructor(http, appId) {
235
+ this.http = http;
236
+ this.appId = appId;
237
+ }
238
+ /**
239
+ * Issue a single item to a user.
240
+ *
241
+ * @example
242
+ * ```ts
243
+ * const item = await omen.items.issue({
244
+ * userId: 'cmm0tzco8...',
245
+ * name: 'Dragon Slayer Badge',
246
+ * type: 'achievement',
247
+ * rarity: 'legendary',
248
+ * });
249
+ * ```
250
+ */
251
+ async issue(options) {
252
+ return this.http.request(`/api/v1/apps/${this.appId}/items/issue`, {
253
+ method: "POST",
254
+ body: options
255
+ });
256
+ }
257
+ /**
258
+ * Issue multiple items in one request. Maximum 100 items per batch.
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * const { items, count } = await omen.items.issueBatch([
263
+ * { userId: 'user1', name: 'Gold Medal', type: 'achievement' },
264
+ * { userId: 'user2', name: 'Silver Medal', type: 'achievement' },
265
+ * ]);
266
+ * ```
267
+ */
268
+ async issueBatch(items) {
269
+ return this.http.request(`/api/v1/apps/${this.appId}/items/issue-batch`, {
270
+ method: "POST",
271
+ body: { items }
272
+ });
273
+ }
274
+ /**
275
+ * Revoke an item. Idempotent — re-revoking a revoked item returns 200.
276
+ *
277
+ * @example
278
+ * ```ts
279
+ * await omen.items.revoke('item_abc123', 'Violated terms of service');
280
+ * ```
281
+ */
282
+ async revoke(itemId, reason) {
283
+ return this.http.request(`/api/v1/apps/${this.appId}/items/${encodeURIComponent(itemId)}/revoke`, {
284
+ method: "POST",
285
+ body: { reason }
286
+ });
287
+ }
288
+ };
289
+
290
+ // src/namespaces/collections.ts
291
+ var CollectionsNamespace = class {
292
+ constructor(http, appId) {
293
+ this.http = http;
294
+ this.appId = appId;
295
+ }
296
+ /**
297
+ * Create a new collection.
298
+ *
299
+ * @example
300
+ * ```ts
301
+ * const col = await omen.collections.create({
302
+ * name: 'player_stats',
303
+ * schema: { score: 'number', level: 'number', name: 'string' },
304
+ * indexedFields: ['score'],
305
+ * });
306
+ * ```
307
+ */
308
+ async create(options) {
309
+ return this.http.request(`/api/v1/apps/${this.appId}/collections`, {
310
+ method: "POST",
311
+ body: options
312
+ });
313
+ }
314
+ /** List all collections for your app. */
315
+ async list() {
316
+ return this.http.request(`/api/v1/apps/${this.appId}/collections`);
317
+ }
318
+ /** Get a single collection by name. */
319
+ async get(name) {
320
+ return this.http.request(`/api/v1/apps/${this.appId}/collections/${encodeURIComponent(name)}`);
321
+ }
322
+ /**
323
+ * Delete a collection and all its documents. This is irreversible.
324
+ */
325
+ async delete(name) {
326
+ return this.http.request(`/api/v1/apps/${this.appId}/collections/${encodeURIComponent(name)}`, {
327
+ method: "DELETE"
328
+ });
329
+ }
330
+ // ── Documents ──────────────────────────────
331
+ /**
332
+ * Insert a single document into a collection.
333
+ *
334
+ * @example
335
+ * ```ts
336
+ * const doc = await omen.collections.insert('player_stats', {
337
+ * score: 1500,
338
+ * level: 12,
339
+ * name: 'PistolPhoenix',
340
+ * });
341
+ * ```
342
+ */
343
+ async insert(collection, data) {
344
+ return this.http.request(this.docsPath(collection), {
345
+ method: "POST",
346
+ body: { data }
347
+ });
348
+ }
349
+ /**
350
+ * Insert multiple documents at once. Maximum 100 per batch.
351
+ */
352
+ async insertBatch(collection, documents) {
353
+ return this.http.request(this.docsPath(collection), {
354
+ method: "POST",
355
+ body: { documents }
356
+ });
357
+ }
358
+ /**
359
+ * Query documents with optional filtering, sorting, and pagination.
360
+ *
361
+ * @example
362
+ * ```ts
363
+ * const { documents } = await omen.collections.query('player_stats', {
364
+ * where: { level: { $gte: 10 } },
365
+ * sort: { score: -1 },
366
+ * limit: 20,
367
+ * });
368
+ * ```
369
+ */
370
+ async query(collection, options = {}) {
371
+ return this.http.request(this.docsPath(collection), {
372
+ query: {
373
+ where: options.where ? JSON.stringify(options.where) : void 0,
374
+ sort: options.sort ? JSON.stringify(options.sort) : void 0,
375
+ limit: options.limit,
376
+ offset: options.offset,
377
+ count: options.count ? "true" : void 0
378
+ }
379
+ });
380
+ }
381
+ /** Get a single document by ID. */
382
+ async getDocument(collection, documentId) {
383
+ return this.http.request(`${this.docsPath(collection)}/${encodeURIComponent(documentId)}`);
384
+ }
385
+ /** Update a document's data (shallow merge). */
386
+ async updateDocument(collection, documentId, data) {
387
+ return this.http.request(`${this.docsPath(collection)}/${encodeURIComponent(documentId)}`, {
388
+ method: "PATCH",
389
+ body: { data }
390
+ });
391
+ }
392
+ /** Delete a single document. */
393
+ async deleteDocument(collection, documentId) {
394
+ return this.http.request(`${this.docsPath(collection)}/${encodeURIComponent(documentId)}`, {
395
+ method: "DELETE"
396
+ });
397
+ }
398
+ /** Get the document count for a collection, with optional filter. */
399
+ async count(collection, where) {
400
+ return this.http.request(`/api/v1/apps/${this.appId}/collections/${encodeURIComponent(collection)}/count`, {
401
+ query: { where: where ? JSON.stringify(where) : void 0 }
402
+ });
403
+ }
404
+ /**
405
+ * Execute up to 5 operations atomically (Pro+ tier).
406
+ *
407
+ * @example
408
+ * ```ts
409
+ * const { results } = await omen.collections.transaction('game_state', [
410
+ * { type: 'get', id: 'player_1' },
411
+ * { type: 'update', id: 'player_1', data: { health: 50 } },
412
+ * ]);
413
+ * ```
414
+ */
415
+ async transaction(collection, operations) {
416
+ return this.http.request(
417
+ `/api/v1/apps/${this.appId}/collections/${encodeURIComponent(collection)}/transaction`,
418
+ { method: "POST", body: { operations } }
419
+ );
420
+ }
421
+ docsPath(collection) {
422
+ return `/api/v1/apps/${this.appId}/collections/${encodeURIComponent(collection)}/documents`;
423
+ }
424
+ };
425
+
426
+ // src/namespaces/webhooks.ts
427
+ var WebhooksNamespace = class {
428
+ constructor(http, appId) {
429
+ this.http = http;
430
+ this.appId = appId;
431
+ }
432
+ /**
433
+ * Register a new webhook endpoint.
434
+ * The `secret` field is only returned on creation — store it securely.
435
+ *
436
+ * @example
437
+ * ```ts
438
+ * const webhook = await omen.webhooks.create({
439
+ * url: 'https://myapp.com/webhooks/omen',
440
+ * events: ['item.issued', 'friend.added'],
441
+ * });
442
+ * console.log(webhook.secret); // save this!
443
+ * ```
444
+ */
445
+ async create(options) {
446
+ return this.http.request(`/api/v1/apps/${this.appId}/webhooks`, {
447
+ method: "POST",
448
+ body: options
449
+ });
450
+ }
451
+ /** List all webhook endpoints for your app. Secrets are not included. */
452
+ async list() {
453
+ return this.http.request(`/api/v1/apps/${this.appId}/webhooks`);
454
+ }
455
+ /** Get a single webhook endpoint. Secret is not included. */
456
+ async get(endpointId) {
457
+ return this.http.request(`/api/v1/apps/${this.appId}/webhooks/${encodeURIComponent(endpointId)}`);
458
+ }
459
+ /** Delete a webhook endpoint. */
460
+ async delete(endpointId) {
461
+ return this.http.request(`/api/v1/apps/${this.appId}/webhooks/${encodeURIComponent(endpointId)}`, {
462
+ method: "DELETE"
463
+ });
464
+ }
465
+ /**
466
+ * Verify an incoming webhook signature. Use this in your webhook handler
467
+ * to confirm the request came from Omen.
468
+ *
469
+ * @param payload - The raw request body string.
470
+ * @param signature - The `X-Omen-Signature` header value.
471
+ * @param secret - The webhook secret from `create()`.
472
+ * @returns `true` if the signature is valid.
473
+ *
474
+ * @example
475
+ * ```ts
476
+ * app.post('/webhooks/omen', (req, res) => {
477
+ * const valid = omen.webhooks.verify(
478
+ * req.body, // raw string
479
+ * req.headers['x-omen-signature'],
480
+ * process.env.OMEN_WEBHOOK_SECRET,
481
+ * );
482
+ * if (!valid) return res.status(401).send('Bad signature');
483
+ * // handle event...
484
+ * });
485
+ * ```
486
+ */
487
+ async verify(payload, signature, secret) {
488
+ const encoder = new TextEncoder();
489
+ const key = await crypto.subtle.importKey(
490
+ "raw",
491
+ encoder.encode(secret),
492
+ { name: "HMAC", hash: "SHA-256" },
493
+ false,
494
+ ["sign"]
495
+ );
496
+ const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
497
+ const expected = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
498
+ return timingSafeEqual(expected, signature);
499
+ }
500
+ };
501
+ function timingSafeEqual(a, b) {
502
+ if (a.length !== b.length) return false;
503
+ let result = 0;
504
+ for (let i = 0; i < a.length; i++) {
505
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
506
+ }
507
+ return result === 0;
508
+ }
509
+
510
+ // src/index.ts
511
+ var OmenClient = class {
512
+ /** User profiles and friend lists. */
513
+ users;
514
+ /** Per-user key-value storage. */
515
+ storage;
516
+ /** Issue, revoke, and batch-manage items. */
517
+ items;
518
+ /** Structured data collections (document database). */
519
+ collections;
520
+ /** Webhook endpoint management and signature verification. */
521
+ webhooks;
522
+ constructor(options) {
523
+ if (!options.token) throw new Error("OmenClient: token is required");
524
+ if (!options.appId) throw new Error("OmenClient: appId is required");
525
+ const baseUrl = options.baseUrl ?? "https://omen.dog";
526
+ const http = new HttpClient(options.token, baseUrl);
527
+ this.users = new UsersNamespace(http);
528
+ this.storage = new StorageNamespace(http);
529
+ this.items = new ItemsNamespace(http, options.appId);
530
+ this.collections = new CollectionsNamespace(http, options.appId);
531
+ this.webhooks = new WebhooksNamespace(http, options.appId);
532
+ }
533
+ };
534
+ // Annotate the CommonJS export names for ESM import in node:
535
+ 0 && (module.exports = {
536
+ OmenAuthError,
537
+ OmenClient,
538
+ OmenError,
539
+ OmenNotFoundError,
540
+ OmenRateLimitError,
541
+ OmenValidationError
542
+ });