order3000 0.7.1 → 0.8.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/client.cjs CHANGED
@@ -1,18 +1,20 @@
1
1
  'use strict';
2
2
 
3
- var __defProp$1 = Object.defineProperty;
4
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
- var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
3
+ var supabaseJs = require('@supabase/supabase-js');
4
+
5
+ var __defProp$3 = Object.defineProperty;
6
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
+ var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
6
8
  class TokenManager {
7
9
  constructor(clientId, clientSecret, tokenEndpoint, userVenueId) {
8
10
  this.clientId = clientId;
9
11
  this.clientSecret = clientSecret;
10
12
  this.tokenEndpoint = tokenEndpoint;
11
13
  this.userVenueId = userVenueId;
12
- __publicField$1(this, "accessToken", null);
13
- __publicField$1(this, "tokenExpiresAt", 0);
14
- __publicField$1(this, "refreshPromise", null);
15
- __publicField$1(this, "defaultVenueId");
14
+ __publicField$3(this, "accessToken", null);
15
+ __publicField$3(this, "tokenExpiresAt", 0);
16
+ __publicField$3(this, "refreshPromise", null);
17
+ __publicField$3(this, "defaultVenueId");
16
18
  }
17
19
  async initialize() {
18
20
  await this.refreshToken();
@@ -172,6 +174,36 @@ class EventsResource extends BaseResource {
172
174
  }
173
175
  }
174
176
 
177
+ class MenusResource extends BaseResource {
178
+ constructor(tokenManager, baseUrl) {
179
+ super(tokenManager, baseUrl, "/menus");
180
+ }
181
+ /**
182
+ * Get a menu by ID
183
+ * @param id - The menu UUID
184
+ * @param options - Optional expand options
185
+ * @returns The menu with optional items and assets
186
+ */
187
+ async get(id, options) {
188
+ const queryString = this.buildQueryString(options);
189
+ return this.request("GET", `/${id}${queryString}`);
190
+ }
191
+ /**
192
+ * Get a menu by accessor
193
+ * @param accessor - The menu accessor (slug)
194
+ * @param options - Optional expand options
195
+ * @returns The menu with optional items and assets
196
+ */
197
+ async getByAccessor(accessor, options) {
198
+ const queryString = this.buildQueryString(options);
199
+ return this.request("GET", `/${accessor}${queryString}`);
200
+ }
201
+ buildQueryString(options) {
202
+ if (!options?.expand?.length) return "";
203
+ return `?expand=${options.expand.join(",")}`;
204
+ }
205
+ }
206
+
175
207
  class OrdersResource extends BaseResource {
176
208
  constructor(tokenManager, baseUrl) {
177
209
  super(tokenManager, baseUrl, "/orders");
@@ -206,14 +238,221 @@ class ReservationsResource extends BaseResource {
206
238
  }
207
239
  }
208
240
 
241
+ var __defProp$2 = Object.defineProperty;
242
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
243
+ var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
244
+ class BaseRealtimeChannel {
245
+ constructor(manager, tableName) {
246
+ this.manager = manager;
247
+ this.tableName = tableName;
248
+ __publicField$2(this, "channels", /* @__PURE__ */ new Map());
249
+ __publicField$2(this, "supabaseClient", null);
250
+ }
251
+ async subscribe(options = {}) {
252
+ const client = await this.manager.getSupabaseClient();
253
+ this.supabaseClient = client;
254
+ const venueId = this.manager.getVenueId();
255
+ const channelName = `${this.tableName}:${venueId}:${Date.now()}`;
256
+ const filter = this.buildFilter(venueId, options.filter);
257
+ const requestedEvent = options.event || "*";
258
+ const channel = client.channel(channelName).on(
259
+ "postgres_changes",
260
+ {
261
+ event: "*",
262
+ schema: "public",
263
+ table: this.tableName,
264
+ filter: filter || void 0
265
+ },
266
+ (payload) => {
267
+ if (requestedEvent !== "*" && payload.eventType !== requestedEvent) {
268
+ return;
269
+ }
270
+ this.handlePayload(payload, options);
271
+ }
272
+ ).subscribe((status) => {
273
+ options.onStatusChange?.(status);
274
+ if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
275
+ options.onError?.(new Error(`Channel ${status}: ${channelName}`));
276
+ }
277
+ });
278
+ this.channels.set(channelName, channel);
279
+ return {
280
+ channelName,
281
+ unsubscribe: async () => {
282
+ await client.removeChannel(channel);
283
+ this.channels.delete(channelName);
284
+ }
285
+ };
286
+ }
287
+ buildFilter(venueId, additionalFilter) {
288
+ const baseFilter = `venue_id=eq.${venueId}`;
289
+ return additionalFilter ? `${baseFilter},${additionalFilter}` : baseFilter;
290
+ }
291
+ handlePayload(payload, options) {
292
+ options.onChange?.(payload);
293
+ switch (payload.eventType) {
294
+ case "INSERT":
295
+ options.onInsert?.(payload.new);
296
+ break;
297
+ case "UPDATE":
298
+ options.onUpdate?.(payload.new, payload.old);
299
+ break;
300
+ case "DELETE":
301
+ options.onDelete?.(payload.old);
302
+ break;
303
+ }
304
+ }
305
+ async unsubscribeAll() {
306
+ if (!this.supabaseClient) return;
307
+ for (const [, channel] of this.channels) {
308
+ await this.supabaseClient.removeChannel(channel);
309
+ }
310
+ this.channels.clear();
311
+ }
312
+ }
313
+
314
+ class MenuItemsChannel extends BaseRealtimeChannel {
315
+ constructor(manager) {
316
+ super(manager, "menu_items");
317
+ }
318
+ // menu_items doesn't have venue_id directly, so we filter by menu_id
319
+ // RLS policy handles security, client filters by menu_id
320
+ buildFilter(_venueId, additionalFilter) {
321
+ return additionalFilter || "";
322
+ }
323
+ /**
324
+ * Subscribe to changes for a specific menu's items
325
+ */
326
+ async subscribeToMenu(menuId, options = {}) {
327
+ return this.subscribe({
328
+ ...options,
329
+ filter: `menu_id=eq.${menuId}`
330
+ });
331
+ }
332
+ }
333
+
334
+ class EventsChannel extends BaseRealtimeChannel {
335
+ constructor(manager) {
336
+ super(manager, "events");
337
+ }
338
+ /**
339
+ * Subscribe to changes for a specific event
340
+ */
341
+ async subscribeToEvent(eventId, options = {}) {
342
+ return this.subscribe({
343
+ ...options,
344
+ filter: `id=eq.${eventId}`
345
+ });
346
+ }
347
+ }
348
+
349
+ class ScheduleOverridesChannel extends BaseRealtimeChannel {
350
+ constructor(manager) {
351
+ super(manager, "schedule_overrides");
352
+ }
353
+ // schedule_overrides doesn't have venue_id
354
+ buildFilter(_venueId, additionalFilter) {
355
+ return additionalFilter || "";
356
+ }
357
+ /**
358
+ * Subscribe to changes for a specific schedule's overrides
359
+ */
360
+ async subscribeToSchedule(scheduleId, options = {}) {
361
+ return this.subscribe({
362
+ ...options,
363
+ filter: `schedule_id=eq.${scheduleId}`
364
+ });
365
+ }
366
+ }
367
+
368
+ var __defProp$1 = Object.defineProperty;
369
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
370
+ var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
371
+ class RealtimeManager {
372
+ constructor(tokenManager, baseUrl) {
373
+ this.tokenManager = tokenManager;
374
+ this.baseUrl = baseUrl;
375
+ __publicField$1(this, "supabaseClient", null);
376
+ __publicField$1(this, "configPromise", null);
377
+ __publicField$1(this, "menuItems");
378
+ __publicField$1(this, "events");
379
+ __publicField$1(this, "scheduleOverrides");
380
+ this.menuItems = new MenuItemsChannel(this);
381
+ this.events = new EventsChannel(this);
382
+ this.scheduleOverrides = new ScheduleOverridesChannel(this);
383
+ }
384
+ /**
385
+ * Fetch realtime config from the order3000 backend.
386
+ * This keeps Supabase as an internal implementation detail.
387
+ */
388
+ async fetchRealtimeConfig() {
389
+ if (!this.configPromise) {
390
+ this.configPromise = (async () => {
391
+ const token = await this.tokenManager.getToken();
392
+ const response = await fetch(
393
+ `${this.baseUrl}/m2m/realtime-config`,
394
+ {
395
+ headers: { Authorization: `Bearer ${token}` }
396
+ }
397
+ );
398
+ if (!response.ok) {
399
+ throw new Error(`Failed to fetch realtime config: ${response.status}`);
400
+ }
401
+ return response.json();
402
+ })();
403
+ }
404
+ return this.configPromise;
405
+ }
406
+ async getSupabaseClient() {
407
+ if (!this.supabaseClient) {
408
+ const config = await this.fetchRealtimeConfig();
409
+ this.supabaseClient = supabaseJs.createClient(config.url, config.anonKey, {
410
+ auth: {
411
+ persistSession: false,
412
+ autoRefreshToken: false
413
+ },
414
+ realtime: {
415
+ params: {
416
+ eventsPerSecond: 10
417
+ }
418
+ }
419
+ });
420
+ }
421
+ return this.supabaseClient;
422
+ }
423
+ getVenueId() {
424
+ return this.tokenManager.getVenueId();
425
+ }
426
+ /**
427
+ * Get the realtime configuration (Supabase URL and anon key).
428
+ * Use this to pass the config to client-side code for establishing
429
+ * realtime connections from the browser.
430
+ */
431
+ async getConfig() {
432
+ return this.fetchRealtimeConfig();
433
+ }
434
+ async disconnectAll() {
435
+ await Promise.all([
436
+ this.menuItems.unsubscribeAll(),
437
+ this.events.unsubscribeAll(),
438
+ this.scheduleOverrides.unsubscribeAll()
439
+ ]);
440
+ if (this.supabaseClient) {
441
+ await this.supabaseClient.removeAllChannels();
442
+ }
443
+ }
444
+ }
445
+
209
446
  var __defProp = Object.defineProperty;
210
447
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
211
448
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
212
449
  class Order3000Client {
213
450
  constructor(config) {
214
451
  __publicField(this, "events");
452
+ __publicField(this, "menus");
215
453
  __publicField(this, "orders");
216
454
  __publicField(this, "reservations");
455
+ __publicField(this, "realtime");
217
456
  __publicField(this, "baseUrl");
218
457
  __publicField(this, "tokenManager");
219
458
  this.baseUrl = config.baseUrl || "https://order3000.com/api";
@@ -225,11 +464,13 @@ class Order3000Client {
225
464
  config.venueId
226
465
  );
227
466
  this.events = new EventsResource(this.tokenManager, this.baseUrl);
467
+ this.menus = new MenusResource(this.tokenManager, this.baseUrl);
228
468
  this.orders = new OrdersResource(this.tokenManager, this.baseUrl);
229
469
  this.reservations = new ReservationsResource(
230
470
  this.tokenManager,
231
471
  this.baseUrl
232
472
  );
473
+ this.realtime = new RealtimeManager(this.tokenManager, this.baseUrl);
233
474
  }
234
475
  }
235
476
 
package/dist/client.d.cts CHANGED
@@ -1,3 +1,5 @@
1
+ import { RealtimePostgresChangesPayload, RealtimeChannel, SupabaseClient } from '@supabase/supabase-js';
2
+
1
3
  declare class TokenManager {
2
4
  private readonly clientId;
3
5
  private readonly clientSecret;
@@ -34,13 +36,14 @@ interface components {
34
36
  venueId: string;
35
37
  basketId: string;
36
38
  /** @enum {string} */
37
- status?: "DRAFT" | "PENDING" | "PAID" | "FULFILLED" | "CANCELLED" | "REFUNDED";
39
+ status?: "draft" | "pending" | "paid" | "fulfilled" | "cancelled" | "refunded";
38
40
  paymentIntentId?: string | null;
39
41
  /** @enum {string|null} */
40
- paymentMethod?: "CARD" | "CASH" | "VOUCHER" | null;
41
- taxAmount: string;
42
- totalAmount: string;
43
- currency?: string;
42
+ paymentMethod?: "card" | "cash" | "voucher" | null;
43
+ taxAmount: number;
44
+ totalAmount: number;
45
+ /** @enum {string} */
46
+ currency?: "EUR" | "CHF";
44
47
  orderedAt?: unknown;
45
48
  orderedBy: string;
46
49
  updatedAt?: unknown;
@@ -52,13 +55,14 @@ interface components {
52
55
  venueId: string;
53
56
  basketId: string;
54
57
  /** @enum {string} */
55
- status: "DRAFT" | "PENDING" | "PAID" | "FULFILLED" | "CANCELLED" | "REFUNDED";
58
+ status: "draft" | "pending" | "paid" | "fulfilled" | "cancelled" | "refunded";
56
59
  paymentIntentId: string | null;
57
60
  /** @enum {string|null} */
58
- paymentMethod: "CARD" | "CASH" | "VOUCHER" | null;
59
- taxAmount: string;
60
- totalAmount: string;
61
- currency: string;
61
+ paymentMethod: "card" | "cash" | "voucher" | null;
62
+ taxAmount: number;
63
+ totalAmount: number;
64
+ /** @enum {string} */
65
+ currency: "EUR" | "CHF";
62
66
  orderedAt: unknown;
63
67
  orderedBy: string;
64
68
  updatedAt: unknown;
@@ -206,6 +210,7 @@ interface components {
206
210
  * @description Request payload for creating a new event
207
211
  */
208
212
  CreateEventRequest: {
213
+ accessor?: string | null;
209
214
  name: (string | number | boolean | (null)) | {
210
215
  [key: string]: unknown;
211
216
  } | unknown[];
@@ -223,16 +228,18 @@ interface components {
223
228
  bookingOpensAtDays?: number | null;
224
229
  bookingClosesAtHours?: number | null;
225
230
  isActive?: boolean;
226
- isClosed?: boolean;
231
+ isFeatured?: boolean;
227
232
  priority?: number;
228
233
  schedule?: {
229
234
  rrule?: string | null;
230
235
  timezone?: string | null;
231
236
  validFrom?: unknown;
232
237
  validTo?: unknown;
233
- startTime?: string | null;
234
- endTime?: string | null;
235
- lastSeatingTime?: string | null;
238
+ timeRanges: {
239
+ startTime: string;
240
+ endTime: string;
241
+ lastSeatingTime?: string | null;
242
+ }[];
236
243
  /** @enum {string|null} */
237
244
  slotMode?: "interval" | "duration" | null;
238
245
  intervalMinutes?: number | null;
@@ -246,6 +253,7 @@ interface components {
246
253
  CreateEventResponse: {
247
254
  id: string;
248
255
  venueId: string;
256
+ accessor: string | null;
249
257
  name: (string | number | boolean | (null)) | {
250
258
  [key: string]: unknown;
251
259
  } | unknown[];
@@ -263,7 +271,7 @@ interface components {
263
271
  bookingOpensAtDays: number | null;
264
272
  bookingClosesAtHours: number | null;
265
273
  isActive: boolean;
266
- isClosed: boolean;
274
+ isFeatured: boolean;
267
275
  priority: number;
268
276
  createdBy: string | null;
269
277
  updatedBy: string | null;
@@ -290,6 +298,7 @@ interface TimeSlot {
290
298
  startTime: string;
291
299
  endTime: string | null;
292
300
  dayOfWeek?: 'MON' | 'TUE' | 'WED' | 'THU' | 'FRI' | 'SAT' | 'SUN' | null;
301
+ isSchedulable: boolean | null;
293
302
  }
294
303
  interface ScheduleOverride {
295
304
  id: string;
@@ -316,8 +325,6 @@ interface Schedule {
316
325
  slotMode: 'interval' | 'duration';
317
326
  intervalMinutes: number | null;
318
327
  durationMinutes: number | null;
319
- startTime: string | null;
320
- lastSeatingTime: string | null;
321
328
  }
322
329
  interface EventPoster {
323
330
  id: string;
@@ -369,6 +376,8 @@ interface Event {
369
376
  bookingOpensAtDays: number | null;
370
377
  bookingClosesAtHours: number | null;
371
378
  isActive: boolean;
379
+ /** Whether this event should be displayed prominently in public-facing areas */
380
+ isFeatured: boolean;
372
381
  /** Priority for conflict resolution (higher = takes precedence) */
373
382
  priority: number;
374
383
  createdBy: string | null;
@@ -398,6 +407,60 @@ interface ListEventsOptions {
398
407
  upcomingWithinDays?: number;
399
408
  }
400
409
  type ListEventsResponse = Event[];
410
+ type MenuItemType = 'dish' | 'beverage';
411
+ interface MenuAsset {
412
+ id: string;
413
+ type: 'image' | 'video' | 'document';
414
+ tags: string[] | null;
415
+ filename: string;
416
+ contentType: string;
417
+ metadata: {
418
+ width?: number;
419
+ height?: number;
420
+ duration?: number;
421
+ } | null;
422
+ publicUrl: string;
423
+ }
424
+ interface MenuDish {
425
+ id: string;
426
+ accessor: string;
427
+ name: Record<string, string>;
428
+ description: Record<string, string> | null;
429
+ isVegetarian: boolean | null;
430
+ isVegan: boolean | null;
431
+ isGlutenFree: boolean | null;
432
+ isDairyFree: boolean | null;
433
+ assets?: MenuAsset[];
434
+ }
435
+ interface MenuBeverage {
436
+ id: string;
437
+ accessor: string;
438
+ name: Record<string, string>;
439
+ description: Record<string, string> | null;
440
+ }
441
+ interface MenuItem {
442
+ id: string;
443
+ type: MenuItemType;
444
+ tags: string[] | null;
445
+ price: number;
446
+ currency: string;
447
+ isActive: boolean;
448
+ sortIndex: string | null;
449
+ dish: MenuDish | null;
450
+ beverage: MenuBeverage | null;
451
+ }
452
+ interface Menu {
453
+ id: string;
454
+ venueId: string;
455
+ accessor: string;
456
+ name: Record<string, string>;
457
+ description: Record<string, string> | null;
458
+ items?: MenuItem[];
459
+ }
460
+ interface GetMenuOptions {
461
+ /** Expand related resources (items, assets) */
462
+ expand?: ('items' | 'assets')[];
463
+ }
401
464
 
402
465
  declare class EventsResource extends BaseResource {
403
466
  constructor(tokenManager: TokenManager, baseUrl: string);
@@ -421,6 +484,25 @@ declare class EventsResource extends BaseResource {
421
484
  get(eventId: string): Promise<Event>;
422
485
  }
423
486
 
487
+ declare class MenusResource extends BaseResource {
488
+ constructor(tokenManager: TokenManager, baseUrl: string);
489
+ /**
490
+ * Get a menu by ID
491
+ * @param id - The menu UUID
492
+ * @param options - Optional expand options
493
+ * @returns The menu with optional items and assets
494
+ */
495
+ get(id: string, options?: GetMenuOptions): Promise<Menu>;
496
+ /**
497
+ * Get a menu by accessor
498
+ * @param accessor - The menu accessor (slug)
499
+ * @param options - Optional expand options
500
+ * @returns The menu with optional items and assets
501
+ */
502
+ getByAccessor(accessor: string, options?: GetMenuOptions): Promise<Menu>;
503
+ private buildQueryString;
504
+ }
505
+
424
506
  declare class OrdersResource extends BaseResource {
425
507
  constructor(tokenManager: TokenManager, baseUrl: string);
426
508
  place(order: PlaceOrderRequest): Promise<PlaceOrderResponse>;
@@ -443,6 +525,127 @@ declare class ReservationsResource extends BaseResource {
443
525
  get(reservationId: string): Promise<MakeReservationResponse>;
444
526
  }
445
527
 
528
+ type RealtimeEvent = 'INSERT' | 'UPDATE' | 'DELETE' | '*';
529
+ type RealtimeRow = {
530
+ [key: string]: any;
531
+ };
532
+ interface SubscriptionOptions<T extends RealtimeRow> {
533
+ event?: RealtimeEvent;
534
+ filter?: string;
535
+ onInsert?: (record: T) => void;
536
+ onUpdate?: (newRecord: T, oldRecord: T) => void;
537
+ onDelete?: (oldRecord: T) => void;
538
+ onChange?: (payload: RealtimePostgresChangesPayload<T>) => void;
539
+ onError?: (error: Error) => void;
540
+ onStatusChange?: (status: 'SUBSCRIBED' | 'CLOSED' | 'CHANNEL_ERROR' | 'TIMED_OUT') => void;
541
+ }
542
+ interface RealtimeSubscription {
543
+ unsubscribe: () => Promise<void>;
544
+ channelName: string;
545
+ }
546
+ interface MenuItemRow {
547
+ id: string;
548
+ menu_id: string;
549
+ type: 'dish' | 'beverage';
550
+ dish_id: string | null;
551
+ beverage_id: string | null;
552
+ price: number;
553
+ currency: string;
554
+ is_active: boolean;
555
+ tags: string[] | null;
556
+ sort_index: string | null;
557
+ }
558
+ interface EventRow {
559
+ id: string;
560
+ venue_id: string;
561
+ accessor: string;
562
+ name: Record<string, string>;
563
+ is_public: boolean;
564
+ is_active: boolean;
565
+ schedule_id: string | null;
566
+ }
567
+ interface ScheduleRow {
568
+ id: string;
569
+ name: Record<string, string>;
570
+ is_active: boolean;
571
+ valid_from: string | null;
572
+ valid_to: string | null;
573
+ }
574
+ interface ScheduleOverrideRow {
575
+ id: string;
576
+ schedule_id: string;
577
+ date: string;
578
+ reason: Record<string, string> | null;
579
+ is_active: boolean;
580
+ }
581
+
582
+ declare abstract class BaseRealtimeChannel<T extends RealtimeRow> {
583
+ protected readonly manager: RealtimeManager;
584
+ protected readonly tableName: string;
585
+ protected channels: Map<string, RealtimeChannel>;
586
+ private supabaseClient;
587
+ constructor(manager: RealtimeManager, tableName: string);
588
+ subscribe(options?: SubscriptionOptions<T>): Promise<RealtimeSubscription>;
589
+ protected buildFilter(venueId: string, additionalFilter?: string): string;
590
+ private handlePayload;
591
+ unsubscribeAll(): Promise<void>;
592
+ }
593
+
594
+ declare class MenuItemsChannel extends BaseRealtimeChannel<MenuItemRow> {
595
+ constructor(manager: RealtimeManager);
596
+ protected buildFilter(_venueId: string, additionalFilter?: string): string;
597
+ /**
598
+ * Subscribe to changes for a specific menu's items
599
+ */
600
+ subscribeToMenu(menuId: string, options?: Omit<SubscriptionOptions<MenuItemRow>, 'filter'>): Promise<RealtimeSubscription>;
601
+ }
602
+
603
+ declare class EventsChannel extends BaseRealtimeChannel<EventRow> {
604
+ constructor(manager: RealtimeManager);
605
+ /**
606
+ * Subscribe to changes for a specific event
607
+ */
608
+ subscribeToEvent(eventId: string, options?: Omit<SubscriptionOptions<EventRow>, 'filter'>): Promise<RealtimeSubscription>;
609
+ }
610
+
611
+ declare class ScheduleOverridesChannel extends BaseRealtimeChannel<ScheduleOverrideRow> {
612
+ constructor(manager: RealtimeManager);
613
+ protected buildFilter(_venueId: string, additionalFilter?: string): string;
614
+ /**
615
+ * Subscribe to changes for a specific schedule's overrides
616
+ */
617
+ subscribeToSchedule(scheduleId: string, options?: Omit<SubscriptionOptions<ScheduleOverrideRow>, 'filter'>): Promise<RealtimeSubscription>;
618
+ }
619
+
620
+ interface RealtimeConfig {
621
+ url: string;
622
+ anonKey: string;
623
+ }
624
+ declare class RealtimeManager {
625
+ private readonly tokenManager;
626
+ private readonly baseUrl;
627
+ private supabaseClient;
628
+ private configPromise;
629
+ menuItems: MenuItemsChannel;
630
+ events: EventsChannel;
631
+ scheduleOverrides: ScheduleOverridesChannel;
632
+ constructor(tokenManager: TokenManager, baseUrl: string);
633
+ /**
634
+ * Fetch realtime config from the order3000 backend.
635
+ * This keeps Supabase as an internal implementation detail.
636
+ */
637
+ private fetchRealtimeConfig;
638
+ getSupabaseClient(): Promise<SupabaseClient>;
639
+ getVenueId(): string;
640
+ /**
641
+ * Get the realtime configuration (Supabase URL and anon key).
642
+ * Use this to pass the config to client-side code for establishing
643
+ * realtime connections from the browser.
644
+ */
645
+ getConfig(): Promise<RealtimeConfig>;
646
+ disconnectAll(): Promise<void>;
647
+ }
648
+
446
649
  interface Order3000ClientConfig {
447
650
  clientId: string;
448
651
  clientSecret: string;
@@ -460,11 +663,13 @@ interface Order3000ClientConfig {
460
663
  }
461
664
  declare class Order3000Client {
462
665
  events: EventsResource;
666
+ menus: MenusResource;
463
667
  orders: OrdersResource;
464
668
  reservations: ReservationsResource;
669
+ realtime: RealtimeManager;
465
670
  private readonly baseUrl;
466
671
  private readonly tokenManager;
467
672
  constructor(config: Order3000ClientConfig);
468
673
  }
469
674
 
470
- export { type Event, type EventPoster, type ListEventsOptions, type ListEventsResponse, Order3000Client, type Order3000ClientConfig, type Promotion, type Schedule, type ScheduleOverride, type TimeSlot };
675
+ export { type Event, type EventPoster, type EventRow, type GetMenuOptions, type ListEventsOptions, type ListEventsResponse, type Menu, type MenuAsset, type MenuBeverage, type MenuDish, type MenuItem, type MenuItemRow, type MenuItemType, Order3000Client, type Order3000ClientConfig, type Promotion, type RealtimeConfig, type RealtimeEvent, type RealtimeSubscription, type Schedule, type ScheduleOverride, type ScheduleOverrideRow, type ScheduleRow, type SubscriptionOptions, type TimeSlot };
package/dist/client.d.mts CHANGED
@@ -1,3 +1,5 @@
1
+ import { RealtimePostgresChangesPayload, RealtimeChannel, SupabaseClient } from '@supabase/supabase-js';
2
+
1
3
  declare class TokenManager {
2
4
  private readonly clientId;
3
5
  private readonly clientSecret;
@@ -34,13 +36,14 @@ interface components {
34
36
  venueId: string;
35
37
  basketId: string;
36
38
  /** @enum {string} */
37
- status?: "DRAFT" | "PENDING" | "PAID" | "FULFILLED" | "CANCELLED" | "REFUNDED";
39
+ status?: "draft" | "pending" | "paid" | "fulfilled" | "cancelled" | "refunded";
38
40
  paymentIntentId?: string | null;
39
41
  /** @enum {string|null} */
40
- paymentMethod?: "CARD" | "CASH" | "VOUCHER" | null;
41
- taxAmount: string;
42
- totalAmount: string;
43
- currency?: string;
42
+ paymentMethod?: "card" | "cash" | "voucher" | null;
43
+ taxAmount: number;
44
+ totalAmount: number;
45
+ /** @enum {string} */
46
+ currency?: "EUR" | "CHF";
44
47
  orderedAt?: unknown;
45
48
  orderedBy: string;
46
49
  updatedAt?: unknown;
@@ -52,13 +55,14 @@ interface components {
52
55
  venueId: string;
53
56
  basketId: string;
54
57
  /** @enum {string} */
55
- status: "DRAFT" | "PENDING" | "PAID" | "FULFILLED" | "CANCELLED" | "REFUNDED";
58
+ status: "draft" | "pending" | "paid" | "fulfilled" | "cancelled" | "refunded";
56
59
  paymentIntentId: string | null;
57
60
  /** @enum {string|null} */
58
- paymentMethod: "CARD" | "CASH" | "VOUCHER" | null;
59
- taxAmount: string;
60
- totalAmount: string;
61
- currency: string;
61
+ paymentMethod: "card" | "cash" | "voucher" | null;
62
+ taxAmount: number;
63
+ totalAmount: number;
64
+ /** @enum {string} */
65
+ currency: "EUR" | "CHF";
62
66
  orderedAt: unknown;
63
67
  orderedBy: string;
64
68
  updatedAt: unknown;
@@ -206,6 +210,7 @@ interface components {
206
210
  * @description Request payload for creating a new event
207
211
  */
208
212
  CreateEventRequest: {
213
+ accessor?: string | null;
209
214
  name: (string | number | boolean | (null)) | {
210
215
  [key: string]: unknown;
211
216
  } | unknown[];
@@ -223,16 +228,18 @@ interface components {
223
228
  bookingOpensAtDays?: number | null;
224
229
  bookingClosesAtHours?: number | null;
225
230
  isActive?: boolean;
226
- isClosed?: boolean;
231
+ isFeatured?: boolean;
227
232
  priority?: number;
228
233
  schedule?: {
229
234
  rrule?: string | null;
230
235
  timezone?: string | null;
231
236
  validFrom?: unknown;
232
237
  validTo?: unknown;
233
- startTime?: string | null;
234
- endTime?: string | null;
235
- lastSeatingTime?: string | null;
238
+ timeRanges: {
239
+ startTime: string;
240
+ endTime: string;
241
+ lastSeatingTime?: string | null;
242
+ }[];
236
243
  /** @enum {string|null} */
237
244
  slotMode?: "interval" | "duration" | null;
238
245
  intervalMinutes?: number | null;
@@ -246,6 +253,7 @@ interface components {
246
253
  CreateEventResponse: {
247
254
  id: string;
248
255
  venueId: string;
256
+ accessor: string | null;
249
257
  name: (string | number | boolean | (null)) | {
250
258
  [key: string]: unknown;
251
259
  } | unknown[];
@@ -263,7 +271,7 @@ interface components {
263
271
  bookingOpensAtDays: number | null;
264
272
  bookingClosesAtHours: number | null;
265
273
  isActive: boolean;
266
- isClosed: boolean;
274
+ isFeatured: boolean;
267
275
  priority: number;
268
276
  createdBy: string | null;
269
277
  updatedBy: string | null;
@@ -290,6 +298,7 @@ interface TimeSlot {
290
298
  startTime: string;
291
299
  endTime: string | null;
292
300
  dayOfWeek?: 'MON' | 'TUE' | 'WED' | 'THU' | 'FRI' | 'SAT' | 'SUN' | null;
301
+ isSchedulable: boolean | null;
293
302
  }
294
303
  interface ScheduleOverride {
295
304
  id: string;
@@ -316,8 +325,6 @@ interface Schedule {
316
325
  slotMode: 'interval' | 'duration';
317
326
  intervalMinutes: number | null;
318
327
  durationMinutes: number | null;
319
- startTime: string | null;
320
- lastSeatingTime: string | null;
321
328
  }
322
329
  interface EventPoster {
323
330
  id: string;
@@ -369,6 +376,8 @@ interface Event {
369
376
  bookingOpensAtDays: number | null;
370
377
  bookingClosesAtHours: number | null;
371
378
  isActive: boolean;
379
+ /** Whether this event should be displayed prominently in public-facing areas */
380
+ isFeatured: boolean;
372
381
  /** Priority for conflict resolution (higher = takes precedence) */
373
382
  priority: number;
374
383
  createdBy: string | null;
@@ -398,6 +407,60 @@ interface ListEventsOptions {
398
407
  upcomingWithinDays?: number;
399
408
  }
400
409
  type ListEventsResponse = Event[];
410
+ type MenuItemType = 'dish' | 'beverage';
411
+ interface MenuAsset {
412
+ id: string;
413
+ type: 'image' | 'video' | 'document';
414
+ tags: string[] | null;
415
+ filename: string;
416
+ contentType: string;
417
+ metadata: {
418
+ width?: number;
419
+ height?: number;
420
+ duration?: number;
421
+ } | null;
422
+ publicUrl: string;
423
+ }
424
+ interface MenuDish {
425
+ id: string;
426
+ accessor: string;
427
+ name: Record<string, string>;
428
+ description: Record<string, string> | null;
429
+ isVegetarian: boolean | null;
430
+ isVegan: boolean | null;
431
+ isGlutenFree: boolean | null;
432
+ isDairyFree: boolean | null;
433
+ assets?: MenuAsset[];
434
+ }
435
+ interface MenuBeverage {
436
+ id: string;
437
+ accessor: string;
438
+ name: Record<string, string>;
439
+ description: Record<string, string> | null;
440
+ }
441
+ interface MenuItem {
442
+ id: string;
443
+ type: MenuItemType;
444
+ tags: string[] | null;
445
+ price: number;
446
+ currency: string;
447
+ isActive: boolean;
448
+ sortIndex: string | null;
449
+ dish: MenuDish | null;
450
+ beverage: MenuBeverage | null;
451
+ }
452
+ interface Menu {
453
+ id: string;
454
+ venueId: string;
455
+ accessor: string;
456
+ name: Record<string, string>;
457
+ description: Record<string, string> | null;
458
+ items?: MenuItem[];
459
+ }
460
+ interface GetMenuOptions {
461
+ /** Expand related resources (items, assets) */
462
+ expand?: ('items' | 'assets')[];
463
+ }
401
464
 
402
465
  declare class EventsResource extends BaseResource {
403
466
  constructor(tokenManager: TokenManager, baseUrl: string);
@@ -421,6 +484,25 @@ declare class EventsResource extends BaseResource {
421
484
  get(eventId: string): Promise<Event>;
422
485
  }
423
486
 
487
+ declare class MenusResource extends BaseResource {
488
+ constructor(tokenManager: TokenManager, baseUrl: string);
489
+ /**
490
+ * Get a menu by ID
491
+ * @param id - The menu UUID
492
+ * @param options - Optional expand options
493
+ * @returns The menu with optional items and assets
494
+ */
495
+ get(id: string, options?: GetMenuOptions): Promise<Menu>;
496
+ /**
497
+ * Get a menu by accessor
498
+ * @param accessor - The menu accessor (slug)
499
+ * @param options - Optional expand options
500
+ * @returns The menu with optional items and assets
501
+ */
502
+ getByAccessor(accessor: string, options?: GetMenuOptions): Promise<Menu>;
503
+ private buildQueryString;
504
+ }
505
+
424
506
  declare class OrdersResource extends BaseResource {
425
507
  constructor(tokenManager: TokenManager, baseUrl: string);
426
508
  place(order: PlaceOrderRequest): Promise<PlaceOrderResponse>;
@@ -443,6 +525,127 @@ declare class ReservationsResource extends BaseResource {
443
525
  get(reservationId: string): Promise<MakeReservationResponse>;
444
526
  }
445
527
 
528
+ type RealtimeEvent = 'INSERT' | 'UPDATE' | 'DELETE' | '*';
529
+ type RealtimeRow = {
530
+ [key: string]: any;
531
+ };
532
+ interface SubscriptionOptions<T extends RealtimeRow> {
533
+ event?: RealtimeEvent;
534
+ filter?: string;
535
+ onInsert?: (record: T) => void;
536
+ onUpdate?: (newRecord: T, oldRecord: T) => void;
537
+ onDelete?: (oldRecord: T) => void;
538
+ onChange?: (payload: RealtimePostgresChangesPayload<T>) => void;
539
+ onError?: (error: Error) => void;
540
+ onStatusChange?: (status: 'SUBSCRIBED' | 'CLOSED' | 'CHANNEL_ERROR' | 'TIMED_OUT') => void;
541
+ }
542
+ interface RealtimeSubscription {
543
+ unsubscribe: () => Promise<void>;
544
+ channelName: string;
545
+ }
546
+ interface MenuItemRow {
547
+ id: string;
548
+ menu_id: string;
549
+ type: 'dish' | 'beverage';
550
+ dish_id: string | null;
551
+ beverage_id: string | null;
552
+ price: number;
553
+ currency: string;
554
+ is_active: boolean;
555
+ tags: string[] | null;
556
+ sort_index: string | null;
557
+ }
558
+ interface EventRow {
559
+ id: string;
560
+ venue_id: string;
561
+ accessor: string;
562
+ name: Record<string, string>;
563
+ is_public: boolean;
564
+ is_active: boolean;
565
+ schedule_id: string | null;
566
+ }
567
+ interface ScheduleRow {
568
+ id: string;
569
+ name: Record<string, string>;
570
+ is_active: boolean;
571
+ valid_from: string | null;
572
+ valid_to: string | null;
573
+ }
574
+ interface ScheduleOverrideRow {
575
+ id: string;
576
+ schedule_id: string;
577
+ date: string;
578
+ reason: Record<string, string> | null;
579
+ is_active: boolean;
580
+ }
581
+
582
+ declare abstract class BaseRealtimeChannel<T extends RealtimeRow> {
583
+ protected readonly manager: RealtimeManager;
584
+ protected readonly tableName: string;
585
+ protected channels: Map<string, RealtimeChannel>;
586
+ private supabaseClient;
587
+ constructor(manager: RealtimeManager, tableName: string);
588
+ subscribe(options?: SubscriptionOptions<T>): Promise<RealtimeSubscription>;
589
+ protected buildFilter(venueId: string, additionalFilter?: string): string;
590
+ private handlePayload;
591
+ unsubscribeAll(): Promise<void>;
592
+ }
593
+
594
+ declare class MenuItemsChannel extends BaseRealtimeChannel<MenuItemRow> {
595
+ constructor(manager: RealtimeManager);
596
+ protected buildFilter(_venueId: string, additionalFilter?: string): string;
597
+ /**
598
+ * Subscribe to changes for a specific menu's items
599
+ */
600
+ subscribeToMenu(menuId: string, options?: Omit<SubscriptionOptions<MenuItemRow>, 'filter'>): Promise<RealtimeSubscription>;
601
+ }
602
+
603
+ declare class EventsChannel extends BaseRealtimeChannel<EventRow> {
604
+ constructor(manager: RealtimeManager);
605
+ /**
606
+ * Subscribe to changes for a specific event
607
+ */
608
+ subscribeToEvent(eventId: string, options?: Omit<SubscriptionOptions<EventRow>, 'filter'>): Promise<RealtimeSubscription>;
609
+ }
610
+
611
+ declare class ScheduleOverridesChannel extends BaseRealtimeChannel<ScheduleOverrideRow> {
612
+ constructor(manager: RealtimeManager);
613
+ protected buildFilter(_venueId: string, additionalFilter?: string): string;
614
+ /**
615
+ * Subscribe to changes for a specific schedule's overrides
616
+ */
617
+ subscribeToSchedule(scheduleId: string, options?: Omit<SubscriptionOptions<ScheduleOverrideRow>, 'filter'>): Promise<RealtimeSubscription>;
618
+ }
619
+
620
+ interface RealtimeConfig {
621
+ url: string;
622
+ anonKey: string;
623
+ }
624
+ declare class RealtimeManager {
625
+ private readonly tokenManager;
626
+ private readonly baseUrl;
627
+ private supabaseClient;
628
+ private configPromise;
629
+ menuItems: MenuItemsChannel;
630
+ events: EventsChannel;
631
+ scheduleOverrides: ScheduleOverridesChannel;
632
+ constructor(tokenManager: TokenManager, baseUrl: string);
633
+ /**
634
+ * Fetch realtime config from the order3000 backend.
635
+ * This keeps Supabase as an internal implementation detail.
636
+ */
637
+ private fetchRealtimeConfig;
638
+ getSupabaseClient(): Promise<SupabaseClient>;
639
+ getVenueId(): string;
640
+ /**
641
+ * Get the realtime configuration (Supabase URL and anon key).
642
+ * Use this to pass the config to client-side code for establishing
643
+ * realtime connections from the browser.
644
+ */
645
+ getConfig(): Promise<RealtimeConfig>;
646
+ disconnectAll(): Promise<void>;
647
+ }
648
+
446
649
  interface Order3000ClientConfig {
447
650
  clientId: string;
448
651
  clientSecret: string;
@@ -460,11 +663,13 @@ interface Order3000ClientConfig {
460
663
  }
461
664
  declare class Order3000Client {
462
665
  events: EventsResource;
666
+ menus: MenusResource;
463
667
  orders: OrdersResource;
464
668
  reservations: ReservationsResource;
669
+ realtime: RealtimeManager;
465
670
  private readonly baseUrl;
466
671
  private readonly tokenManager;
467
672
  constructor(config: Order3000ClientConfig);
468
673
  }
469
674
 
470
- export { type Event, type EventPoster, type ListEventsOptions, type ListEventsResponse, Order3000Client, type Order3000ClientConfig, type Promotion, type Schedule, type ScheduleOverride, type TimeSlot };
675
+ export { type Event, type EventPoster, type EventRow, type GetMenuOptions, type ListEventsOptions, type ListEventsResponse, type Menu, type MenuAsset, type MenuBeverage, type MenuDish, type MenuItem, type MenuItemRow, type MenuItemType, Order3000Client, type Order3000ClientConfig, type Promotion, type RealtimeConfig, type RealtimeEvent, type RealtimeSubscription, type Schedule, type ScheduleOverride, type ScheduleOverrideRow, type ScheduleRow, type SubscriptionOptions, type TimeSlot };
package/dist/client.mjs CHANGED
@@ -1,16 +1,18 @@
1
- var __defProp$1 = Object.defineProperty;
2
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1
+ import { createClient } from '@supabase/supabase-js';
2
+
3
+ var __defProp$3 = Object.defineProperty;
4
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
+ var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
4
6
  class TokenManager {
5
7
  constructor(clientId, clientSecret, tokenEndpoint, userVenueId) {
6
8
  this.clientId = clientId;
7
9
  this.clientSecret = clientSecret;
8
10
  this.tokenEndpoint = tokenEndpoint;
9
11
  this.userVenueId = userVenueId;
10
- __publicField$1(this, "accessToken", null);
11
- __publicField$1(this, "tokenExpiresAt", 0);
12
- __publicField$1(this, "refreshPromise", null);
13
- __publicField$1(this, "defaultVenueId");
12
+ __publicField$3(this, "accessToken", null);
13
+ __publicField$3(this, "tokenExpiresAt", 0);
14
+ __publicField$3(this, "refreshPromise", null);
15
+ __publicField$3(this, "defaultVenueId");
14
16
  }
15
17
  async initialize() {
16
18
  await this.refreshToken();
@@ -170,6 +172,36 @@ class EventsResource extends BaseResource {
170
172
  }
171
173
  }
172
174
 
175
+ class MenusResource extends BaseResource {
176
+ constructor(tokenManager, baseUrl) {
177
+ super(tokenManager, baseUrl, "/menus");
178
+ }
179
+ /**
180
+ * Get a menu by ID
181
+ * @param id - The menu UUID
182
+ * @param options - Optional expand options
183
+ * @returns The menu with optional items and assets
184
+ */
185
+ async get(id, options) {
186
+ const queryString = this.buildQueryString(options);
187
+ return this.request("GET", `/${id}${queryString}`);
188
+ }
189
+ /**
190
+ * Get a menu by accessor
191
+ * @param accessor - The menu accessor (slug)
192
+ * @param options - Optional expand options
193
+ * @returns The menu with optional items and assets
194
+ */
195
+ async getByAccessor(accessor, options) {
196
+ const queryString = this.buildQueryString(options);
197
+ return this.request("GET", `/${accessor}${queryString}`);
198
+ }
199
+ buildQueryString(options) {
200
+ if (!options?.expand?.length) return "";
201
+ return `?expand=${options.expand.join(",")}`;
202
+ }
203
+ }
204
+
173
205
  class OrdersResource extends BaseResource {
174
206
  constructor(tokenManager, baseUrl) {
175
207
  super(tokenManager, baseUrl, "/orders");
@@ -204,14 +236,221 @@ class ReservationsResource extends BaseResource {
204
236
  }
205
237
  }
206
238
 
239
+ var __defProp$2 = Object.defineProperty;
240
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
241
+ var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
242
+ class BaseRealtimeChannel {
243
+ constructor(manager, tableName) {
244
+ this.manager = manager;
245
+ this.tableName = tableName;
246
+ __publicField$2(this, "channels", /* @__PURE__ */ new Map());
247
+ __publicField$2(this, "supabaseClient", null);
248
+ }
249
+ async subscribe(options = {}) {
250
+ const client = await this.manager.getSupabaseClient();
251
+ this.supabaseClient = client;
252
+ const venueId = this.manager.getVenueId();
253
+ const channelName = `${this.tableName}:${venueId}:${Date.now()}`;
254
+ const filter = this.buildFilter(venueId, options.filter);
255
+ const requestedEvent = options.event || "*";
256
+ const channel = client.channel(channelName).on(
257
+ "postgres_changes",
258
+ {
259
+ event: "*",
260
+ schema: "public",
261
+ table: this.tableName,
262
+ filter: filter || void 0
263
+ },
264
+ (payload) => {
265
+ if (requestedEvent !== "*" && payload.eventType !== requestedEvent) {
266
+ return;
267
+ }
268
+ this.handlePayload(payload, options);
269
+ }
270
+ ).subscribe((status) => {
271
+ options.onStatusChange?.(status);
272
+ if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
273
+ options.onError?.(new Error(`Channel ${status}: ${channelName}`));
274
+ }
275
+ });
276
+ this.channels.set(channelName, channel);
277
+ return {
278
+ channelName,
279
+ unsubscribe: async () => {
280
+ await client.removeChannel(channel);
281
+ this.channels.delete(channelName);
282
+ }
283
+ };
284
+ }
285
+ buildFilter(venueId, additionalFilter) {
286
+ const baseFilter = `venue_id=eq.${venueId}`;
287
+ return additionalFilter ? `${baseFilter},${additionalFilter}` : baseFilter;
288
+ }
289
+ handlePayload(payload, options) {
290
+ options.onChange?.(payload);
291
+ switch (payload.eventType) {
292
+ case "INSERT":
293
+ options.onInsert?.(payload.new);
294
+ break;
295
+ case "UPDATE":
296
+ options.onUpdate?.(payload.new, payload.old);
297
+ break;
298
+ case "DELETE":
299
+ options.onDelete?.(payload.old);
300
+ break;
301
+ }
302
+ }
303
+ async unsubscribeAll() {
304
+ if (!this.supabaseClient) return;
305
+ for (const [, channel] of this.channels) {
306
+ await this.supabaseClient.removeChannel(channel);
307
+ }
308
+ this.channels.clear();
309
+ }
310
+ }
311
+
312
+ class MenuItemsChannel extends BaseRealtimeChannel {
313
+ constructor(manager) {
314
+ super(manager, "menu_items");
315
+ }
316
+ // menu_items doesn't have venue_id directly, so we filter by menu_id
317
+ // RLS policy handles security, client filters by menu_id
318
+ buildFilter(_venueId, additionalFilter) {
319
+ return additionalFilter || "";
320
+ }
321
+ /**
322
+ * Subscribe to changes for a specific menu's items
323
+ */
324
+ async subscribeToMenu(menuId, options = {}) {
325
+ return this.subscribe({
326
+ ...options,
327
+ filter: `menu_id=eq.${menuId}`
328
+ });
329
+ }
330
+ }
331
+
332
+ class EventsChannel extends BaseRealtimeChannel {
333
+ constructor(manager) {
334
+ super(manager, "events");
335
+ }
336
+ /**
337
+ * Subscribe to changes for a specific event
338
+ */
339
+ async subscribeToEvent(eventId, options = {}) {
340
+ return this.subscribe({
341
+ ...options,
342
+ filter: `id=eq.${eventId}`
343
+ });
344
+ }
345
+ }
346
+
347
+ class ScheduleOverridesChannel extends BaseRealtimeChannel {
348
+ constructor(manager) {
349
+ super(manager, "schedule_overrides");
350
+ }
351
+ // schedule_overrides doesn't have venue_id
352
+ buildFilter(_venueId, additionalFilter) {
353
+ return additionalFilter || "";
354
+ }
355
+ /**
356
+ * Subscribe to changes for a specific schedule's overrides
357
+ */
358
+ async subscribeToSchedule(scheduleId, options = {}) {
359
+ return this.subscribe({
360
+ ...options,
361
+ filter: `schedule_id=eq.${scheduleId}`
362
+ });
363
+ }
364
+ }
365
+
366
+ var __defProp$1 = Object.defineProperty;
367
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
368
+ var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
369
+ class RealtimeManager {
370
+ constructor(tokenManager, baseUrl) {
371
+ this.tokenManager = tokenManager;
372
+ this.baseUrl = baseUrl;
373
+ __publicField$1(this, "supabaseClient", null);
374
+ __publicField$1(this, "configPromise", null);
375
+ __publicField$1(this, "menuItems");
376
+ __publicField$1(this, "events");
377
+ __publicField$1(this, "scheduleOverrides");
378
+ this.menuItems = new MenuItemsChannel(this);
379
+ this.events = new EventsChannel(this);
380
+ this.scheduleOverrides = new ScheduleOverridesChannel(this);
381
+ }
382
+ /**
383
+ * Fetch realtime config from the order3000 backend.
384
+ * This keeps Supabase as an internal implementation detail.
385
+ */
386
+ async fetchRealtimeConfig() {
387
+ if (!this.configPromise) {
388
+ this.configPromise = (async () => {
389
+ const token = await this.tokenManager.getToken();
390
+ const response = await fetch(
391
+ `${this.baseUrl}/m2m/realtime-config`,
392
+ {
393
+ headers: { Authorization: `Bearer ${token}` }
394
+ }
395
+ );
396
+ if (!response.ok) {
397
+ throw new Error(`Failed to fetch realtime config: ${response.status}`);
398
+ }
399
+ return response.json();
400
+ })();
401
+ }
402
+ return this.configPromise;
403
+ }
404
+ async getSupabaseClient() {
405
+ if (!this.supabaseClient) {
406
+ const config = await this.fetchRealtimeConfig();
407
+ this.supabaseClient = createClient(config.url, config.anonKey, {
408
+ auth: {
409
+ persistSession: false,
410
+ autoRefreshToken: false
411
+ },
412
+ realtime: {
413
+ params: {
414
+ eventsPerSecond: 10
415
+ }
416
+ }
417
+ });
418
+ }
419
+ return this.supabaseClient;
420
+ }
421
+ getVenueId() {
422
+ return this.tokenManager.getVenueId();
423
+ }
424
+ /**
425
+ * Get the realtime configuration (Supabase URL and anon key).
426
+ * Use this to pass the config to client-side code for establishing
427
+ * realtime connections from the browser.
428
+ */
429
+ async getConfig() {
430
+ return this.fetchRealtimeConfig();
431
+ }
432
+ async disconnectAll() {
433
+ await Promise.all([
434
+ this.menuItems.unsubscribeAll(),
435
+ this.events.unsubscribeAll(),
436
+ this.scheduleOverrides.unsubscribeAll()
437
+ ]);
438
+ if (this.supabaseClient) {
439
+ await this.supabaseClient.removeAllChannels();
440
+ }
441
+ }
442
+ }
443
+
207
444
  var __defProp = Object.defineProperty;
208
445
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
209
446
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
210
447
  class Order3000Client {
211
448
  constructor(config) {
212
449
  __publicField(this, "events");
450
+ __publicField(this, "menus");
213
451
  __publicField(this, "orders");
214
452
  __publicField(this, "reservations");
453
+ __publicField(this, "realtime");
215
454
  __publicField(this, "baseUrl");
216
455
  __publicField(this, "tokenManager");
217
456
  this.baseUrl = config.baseUrl || "https://order3000.com/api";
@@ -223,11 +462,13 @@ class Order3000Client {
223
462
  config.venueId
224
463
  );
225
464
  this.events = new EventsResource(this.tokenManager, this.baseUrl);
465
+ this.menus = new MenusResource(this.tokenManager, this.baseUrl);
226
466
  this.orders = new OrdersResource(this.tokenManager, this.baseUrl);
227
467
  this.reservations = new ReservationsResource(
228
468
  this.tokenManager,
229
469
  this.baseUrl
230
470
  );
471
+ this.realtime = new RealtimeManager(this.tokenManager, this.baseUrl);
231
472
  }
232
473
  }
233
474
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "order3000",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "order3000 node.js sdk",
5
5
  "main": "./dist/client.cjs",
6
6
  "module": "./dist/client.mjs",
@@ -30,8 +30,8 @@
30
30
  "tsx": "^4.19.2",
31
31
  "typescript": "^5.9.3",
32
32
  "@kolaveri/eslint-config": "1.0.9",
33
- "@kolaveri/prettier-config": "1.0.6",
34
- "@kolaveri/tsconfig": "1.0.1"
33
+ "@kolaveri/tsconfig": "1.0.1",
34
+ "@kolaveri/prettier-config": "1.0.6"
35
35
  },
36
36
  "files": [
37
37
  "dist"