@mitway/sdk 0.2.4 → 0.4.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.d.cts CHANGED
@@ -70,6 +70,7 @@ declare class TokenManager {
70
70
  */
71
71
 
72
72
  type PostgresChangesEventSelector = 'INSERT' | 'UPDATE' | 'DELETE' | '*';
73
+ /** Filter for any event (including the `*` catch-all). */
73
74
  interface PostgresChangesFilter {
74
75
  event: PostgresChangesEventSelector;
75
76
  schema?: string;
@@ -77,8 +78,8 @@ interface PostgresChangesFilter {
77
78
  /** PostgREST-style filter: `column=op.value` or `column=in.(a,b,c)`. */
78
79
  filter?: string;
79
80
  }
80
- interface PostgresChangesPayload<T = Record<string, unknown>> {
81
- type: 'INSERT' | 'UPDATE' | 'DELETE';
81
+ /** Common shape shared by every postgres_changes payload variant. */
82
+ interface PostgresChangesPayloadBase {
82
83
  schema: string;
83
84
  table: string;
84
85
  commit_timestamp: string;
@@ -86,10 +87,33 @@ interface PostgresChangesPayload<T = Record<string, unknown>> {
86
87
  name: string;
87
88
  type: string;
88
89
  }>;
89
- record?: T;
90
- old_record?: T;
91
90
  errors?: string[];
92
91
  }
92
+ /** INSERT: the new row is in `new`. `old` is kept as an empty object so
93
+ * code that accesses both fields never needs optional chaining. */
94
+ interface PostgresChangesInsertPayload<T = Record<string, unknown>> extends PostgresChangesPayloadBase {
95
+ eventType: 'INSERT';
96
+ new: T;
97
+ old: Record<string, never>;
98
+ }
99
+ /** UPDATE: both `new` and `old` are populated. `old` is `Partial<T>`
100
+ * because RLS / REPLICA IDENTITY may strip columns the subscriber can't
101
+ * see. */
102
+ interface PostgresChangesUpdatePayload<T = Record<string, unknown>> extends PostgresChangesPayloadBase {
103
+ eventType: 'UPDATE';
104
+ new: T;
105
+ old: Partial<T>;
106
+ }
107
+ /** DELETE: the deleted row is in `old`. On RLS-enabled tables only
108
+ * primary-key columns are populated. `new` is always an empty object. */
109
+ interface PostgresChangesDeletePayload<T = Record<string, unknown>> extends PostgresChangesPayloadBase {
110
+ eventType: 'DELETE';
111
+ new: Record<string, never>;
112
+ old: Partial<T>;
113
+ }
114
+ /** Discriminated union — what the `'*'` overload of `on('postgres_changes',
115
+ * ...)` passes to the callback. */
116
+ type PostgresChangesPayload<T = Record<string, unknown>> = PostgresChangesInsertPayload<T> | PostgresChangesUpdatePayload<T> | PostgresChangesDeletePayload<T>;
93
117
  interface BroadcastFilter {
94
118
  event: string;
95
119
  }
@@ -140,11 +164,6 @@ interface ChannelOptions {
140
164
  * requested on a private channel. Default: `false` (open topic). */
141
165
  private?: boolean;
142
166
  broadcast?: {
143
- /** When true, `channel.send(...)` resolves only after the server
144
- * ack'd the publish — useful if the app wants to know the
145
- * message_id assigned by the server. Default: `false`
146
- * (fire-and-forget). */
147
- ack?: boolean;
148
167
  /** When `false`, the sending socket is excluded from the fan-out.
149
168
  * Default: `true` (sender also receives its own broadcasts). */
150
169
  self?: boolean;
@@ -158,9 +177,13 @@ interface ChannelOptions {
158
177
  };
159
178
  }
160
179
  type ChannelStatus = 'SUBSCRIBED' | 'CHANNEL_ERROR' | 'TIMED_OUT' | 'CLOSED';
161
- type ChannelStatusCallback = (status: ChannelStatus, error?: {
162
- code: string;
163
- message: string;
180
+ /** Callback invoked for every channel status transition. The second
181
+ * argument is a standard `Error` (so consumers can annotate it as
182
+ * `err: Error` out of the box) with a non-standard `.code` string
183
+ * attached for programmatic discrimination (`SUBSCRIBE_FAILED`,
184
+ * `CONNECT_FAILED`, `REJOIN_FAILED`, …). */
185
+ type ChannelStatusCallback = (status: ChannelStatus, error?: Error & {
186
+ code?: string;
164
187
  }) => void;
165
188
  interface RealtimeOptions {
166
189
  path?: string;
@@ -168,9 +191,10 @@ interface RealtimeOptions {
168
191
  timeoutMs?: number;
169
192
  extraAuth?: Record<string, string>;
170
193
  }
171
- type PostgresChangesCallback<T = Record<string, unknown>> = (payload: PostgresChangesPayload<T>) => void;
172
194
  type BroadcastCallback<T = Record<string, unknown>> = (payload: BroadcastPayload<T>) => void;
173
- type PresenceCallback<T = Record<string, unknown>> = (payload: PresencePayload<T>) => void;
195
+ type PresenceSyncCallback = () => void;
196
+ type PresenceJoinCallback<T = Record<string, unknown>> = (payload: PresenceJoinPayload<T>) => void;
197
+ type PresenceLeaveCallback<T = Record<string, unknown>> = (payload: PresenceLeavePayload<T>) => void;
174
198
  declare class RealtimeChannel {
175
199
  readonly topic: string;
176
200
  private readonly realtime;
@@ -204,9 +228,40 @@ declare class RealtimeChannel {
204
228
  * statusCallback (from the original subscribe()) fires again with
205
229
  * 'SUBSCRIBED' or 'CHANNEL_ERROR' so the app can reflect state. */
206
230
  _rejoinAfterReconnect(): Promise<void>;
207
- on<T = Record<string, unknown>>(type: 'postgres_changes', filter: PostgresChangesFilter, callback: PostgresChangesCallback<T>): this;
231
+ on<T = Record<string, unknown>>(type: 'postgres_changes', filter: {
232
+ event: 'INSERT';
233
+ schema?: string;
234
+ table: string;
235
+ filter?: string;
236
+ }, callback: (payload: PostgresChangesInsertPayload<T>) => void): this;
237
+ on<T = Record<string, unknown>>(type: 'postgres_changes', filter: {
238
+ event: 'UPDATE';
239
+ schema?: string;
240
+ table: string;
241
+ filter?: string;
242
+ }, callback: (payload: PostgresChangesUpdatePayload<T>) => void): this;
243
+ on<T = Record<string, unknown>>(type: 'postgres_changes', filter: {
244
+ event: 'DELETE';
245
+ schema?: string;
246
+ table: string;
247
+ filter?: string;
248
+ }, callback: (payload: PostgresChangesDeletePayload<T>) => void): this;
249
+ on<T = Record<string, unknown>>(type: 'postgres_changes', filter: {
250
+ event: '*';
251
+ schema?: string;
252
+ table: string;
253
+ filter?: string;
254
+ }, callback: (payload: PostgresChangesPayload<T>) => void): this;
208
255
  on<T = Record<string, unknown>>(type: 'broadcast', filter: BroadcastFilter, callback: BroadcastCallback<T>): this;
209
- on<T = Record<string, unknown>>(type: 'presence', filter: PresenceFilter, callback: PresenceCallback<T>): this;
256
+ on(type: 'presence', filter: {
257
+ event: 'sync';
258
+ }, callback: PresenceSyncCallback): this;
259
+ on<T = Record<string, unknown>>(type: 'presence', filter: {
260
+ event: 'join';
261
+ }, callback: PresenceJoinCallback<T>): this;
262
+ on<T = Record<string, unknown>>(type: 'presence', filter: {
263
+ event: 'leave';
264
+ }, callback: PresenceLeaveCallback<T>): this;
210
265
  /**
211
266
  * Register or refresh this client's presence entry on the channel. Safe
212
267
  * to call many times — state replaces (not merges). Starts a heartbeat
@@ -248,21 +303,27 @@ declare class RealtimeChannel {
248
303
  * events, write to your own application table and enable
249
304
  * `postgres_changes` on it; the SDK will surface the INSERT as a
250
305
  * `postgres_changes` event without a separate channel.send call.
306
+ *
307
+ * The returned promise always resolves with the server ack, so callers
308
+ * can `await channel.send(...)` to confirm delivery + get the server-
309
+ * assigned `message_id`. There's no performance cost — Socket.IO piggy-
310
+ * backs the ack on the same frame. Callers that don't need it just
311
+ * don't await.
251
312
  */
252
313
  send<T extends Record<string, unknown>>(args: {
253
314
  type: 'broadcast';
254
315
  event: string;
255
316
  payload: T;
256
317
  }): Promise<{
257
- ok: true;
318
+ status: 'ok';
258
319
  message_id: string;
259
320
  } | {
260
- ok: false;
321
+ status: 'error';
261
322
  error: {
262
323
  code: string;
263
324
  message: string;
264
325
  };
265
- } | void>;
326
+ }>;
266
327
  /**
267
328
  * Replay SQL-originated broadcasts on this topic since the given
268
329
  * timestamp. Delivered only to this socket (same envelope format as
@@ -301,13 +362,14 @@ declare class Realtime {
301
362
  * The optional `opts` argument lets the caller configure the channel:
302
363
  * * `config.private` — enable subscribe-side authorization against
303
364
  * `realtime.authorize_subscribe(...)` on the tenant DB.
304
- * * `config.broadcast.ack` — `channel.send()` resolves with the
305
- * server's ack (message_id) instead of fire-and-forget.
306
365
  * * `config.broadcast.self` — `false` excludes the sender from the
307
366
  * fan-out (defaults to `true`).
308
367
  * * `config.presence.key` — stable presence key to group multiple
309
368
  * tabs of the same user under one entry.
310
369
  *
370
+ * `channel.send()` always resolves with the server ack (see its own
371
+ * docstring); there is no separate opt-in needed.
372
+ *
311
373
  * Options are locked in when the channel is first created; subsequent
312
374
  * `.channel('same')` calls with different opts are ignored. Pass a
313
375
  * different topic to get a different-configured channel.
@@ -486,6 +548,18 @@ declare class HttpClient {
486
548
  private computeRetryDelay;
487
549
  private handleRequest;
488
550
  request<T>(method: string, path: string, options?: RequestOptions): Promise<T>;
551
+ /**
552
+ * Low-level fetch helper for binary bodies (uploads) and streamed responses
553
+ * (downloads). Applies the current Bearer token (user session → anon key
554
+ * fallback) plus any configured default headers, resolves `path` against
555
+ * `baseUrl`, and returns the raw `Response` — it does NOT unwrap the
556
+ * `{ data, error }` envelope, so the caller is responsible for status
557
+ * checking and parsing.
558
+ *
559
+ * Used by the storage module for object upload/download paths where the
560
+ * body or response is not JSON.
561
+ */
562
+ rawFetch(path: string, init?: RequestInit): Promise<Response>;
489
563
  get<T>(path: string, options?: RequestOptions): Promise<T>;
490
564
  post<T>(path: string, body?: any, options?: RequestOptions): Promise<T>;
491
565
  put<T>(path: string, body?: any, options?: RequestOptions): Promise<T>;
@@ -656,6 +730,136 @@ declare class Database {
656
730
  getUrl(): string;
657
731
  }
658
732
 
733
+ /**
734
+ * Storage types — kept in sync manually with the MITWAY-BaaS shared-schemas
735
+ * definitions at
736
+ * `MITWAY-BaaS/packages/shared-schemas/src/storage.schema.ts`.
737
+ *
738
+ * All SDK-facing fields are camelCase. The wire format is snake_case; the
739
+ * storage module maps between the two.
740
+ */
741
+ interface StorageBucket {
742
+ id: string;
743
+ name: string;
744
+ public: boolean;
745
+ fileSizeLimitBytes: number | null;
746
+ allowedMimeTypes: string[] | null;
747
+ createdAt: string;
748
+ updatedAt: string;
749
+ }
750
+ interface StorageObject {
751
+ id: string;
752
+ bucket: string;
753
+ key: string;
754
+ size: number;
755
+ mimeType: string | null;
756
+ etag: string;
757
+ cacheControl: string | null;
758
+ contentDisposition: string | null;
759
+ uploadedBy: string | null;
760
+ uploadedAt: string;
761
+ updatedAt: string;
762
+ }
763
+ interface StorageConfig {
764
+ defaultFileSizeLimitBytes: number;
765
+ maxFileSizeLimitBytes: number;
766
+ tenantStorageQuotaBytes: number;
767
+ reservedSpaceBytes: number;
768
+ signedUrlDefaultTtlSec: number;
769
+ signedUrlMaxTtlSec: number;
770
+ }
771
+ interface CreateBucketOptions {
772
+ public?: boolean;
773
+ fileSizeLimitBytes?: number | null;
774
+ allowedMimeTypes?: string[] | null;
775
+ }
776
+ interface UpdateBucketOptions {
777
+ public?: boolean;
778
+ fileSizeLimitBytes?: number | null;
779
+ allowedMimeTypes?: string[] | null;
780
+ }
781
+ interface UploadOptions {
782
+ contentType?: string;
783
+ cacheControl?: string;
784
+ contentDisposition?: string;
785
+ /** Defaults to false (POST, fails on duplicate). If true, uses PUT (upsert). */
786
+ upsert?: boolean;
787
+ abortSignal?: AbortSignal;
788
+ }
789
+ interface DownloadOptions {
790
+ range?: {
791
+ start: number;
792
+ end: number;
793
+ };
794
+ abortSignal?: AbortSignal;
795
+ }
796
+ interface ListOptions {
797
+ prefix?: string;
798
+ limit?: number;
799
+ startAfter?: string;
800
+ }
801
+ interface SignedUrlOptions {
802
+ expiresIn?: number;
803
+ }
804
+ interface SignedUrlResult {
805
+ url: string;
806
+ token: string;
807
+ expiresAt: string;
808
+ }
809
+ type UploadBody = Blob | ArrayBuffer | ArrayBufferView | ReadableStream<Uint8Array> | string;
810
+
811
+ /**
812
+ * Storage module — thin wrapper over the /api/storage/* REST endpoints
813
+ * exposed by MITWAY-BaaS.
814
+ *
815
+ * Surface mirrors what coding agents expect from a Supabase-style storage
816
+ * SDK but routes through the Mitway backend (no direct S3 calls). Binary
817
+ * upload/download uses `HttpClient.rawFetch`; JSON operations use the
818
+ * standard typed `request<T>` path.
819
+ */
820
+
821
+ type StorageResult<T> = {
822
+ data: T | null;
823
+ error: MitwayBaasError | null;
824
+ };
825
+ declare class StorageBucketClient {
826
+ private readonly http;
827
+ private readonly bucketName;
828
+ constructor(http: HttpClient, bucketName: string);
829
+ private bucketBase;
830
+ private objectPath;
831
+ upload(key: string, body: UploadBody, opts?: UploadOptions): Promise<StorageResult<StorageObject>>;
832
+ download(key: string, opts?: DownloadOptions): Promise<StorageResult<Blob>>;
833
+ getStream(key: string, opts?: DownloadOptions): Promise<StorageResult<ReadableStream<Uint8Array>>>;
834
+ remove(keys: string[]): Promise<StorageResult<{
835
+ removed: string[];
836
+ }>>;
837
+ list(opts?: ListOptions): Promise<StorageResult<StorageObject[]>>;
838
+ copy(fromKey: string, toKey: string, toBucket?: string): Promise<StorageResult<StorageObject>>;
839
+ move(fromKey: string, toKey: string, toBucket?: string): Promise<StorageResult<StorageObject>>;
840
+ createSignedUrl(key: string, opts?: SignedUrlOptions): Promise<StorageResult<SignedUrlResult>>;
841
+ getPublicUrl(key: string): {
842
+ data: {
843
+ url: string;
844
+ };
845
+ };
846
+ }
847
+ declare class Storage {
848
+ private readonly http;
849
+ constructor(http: HttpClient);
850
+ /** Scope subsequent operations to a single bucket. */
851
+ from(bucketName: string): StorageBucketClient;
852
+ listBuckets(): Promise<StorageResult<StorageBucket[]>>;
853
+ getBucket(name: string): Promise<StorageResult<StorageBucket>>;
854
+ createBucket(name: string, opts?: CreateBucketOptions): Promise<StorageResult<StorageBucket>>;
855
+ updateBucket(name: string, opts: UpdateBucketOptions): Promise<StorageResult<StorageBucket>>;
856
+ deleteBucket(name: string): Promise<StorageResult<null>>;
857
+ emptyBucket(name: string): Promise<StorageResult<{
858
+ removed: number;
859
+ }>>;
860
+ getConfig(): Promise<StorageResult<StorageConfig>>;
861
+ }
862
+
659
863
  /**
660
864
  * MITWAY-BaaS SDK client.
661
865
  *
@@ -689,6 +893,7 @@ declare class MitwayBaasClient {
689
893
  readonly auth: Auth;
690
894
  readonly database: Database;
691
895
  readonly realtime: Realtime;
896
+ readonly storage: Storage;
692
897
  constructor(config?: MitwayBaasConfig);
693
898
  /**
694
899
  * Escape hatch for callers that need to make custom requests against the
@@ -729,4 +934,4 @@ declare class MitwayBaasClient {
729
934
  */
730
935
  declare function createClient(config: MitwayBaasConfig): MitwayBaasClient;
731
936
 
732
- export { type ApiError, Auth, type AuthRefreshResponse, type AuthResponse, type AuthResult, type AuthSession, type BroadcastFilter, type BroadcastPayload, type ChannelOptions, type ChannelStatus, type ChannelStatusCallback, Database, HttpClient, Logger, MitwayBaasClient, type MitwayBaasConfig, MitwayBaasError, type PostgresChangesEventSelector, type PostgresChangesFilter, type PostgresChangesPayload, type PresenceEventSelector, type PresenceFilter, type PresenceJoinPayload, type PresenceLeavePayload, type PresencePayload, type PresenceState, type PresenceSyncPayload, Realtime, RealtimeChannel, type RealtimeMessageMeta, type RealtimeOptions, type SignInRequest, type SignUpRequest, TokenManager, type User, createClient, MitwayBaasClient as default };
937
+ export { type ApiError, Auth, type AuthRefreshResponse, type AuthResponse, type AuthResult, type AuthSession, type BroadcastFilter, type BroadcastPayload, type ChannelOptions, type ChannelStatus, type ChannelStatusCallback, type CreateBucketOptions, Database, type DownloadOptions, HttpClient, type ListOptions, Logger, MitwayBaasClient, type MitwayBaasConfig, MitwayBaasError, type PostgresChangesDeletePayload, type PostgresChangesEventSelector, type PostgresChangesFilter, type PostgresChangesInsertPayload, type PostgresChangesPayload, type PostgresChangesUpdatePayload, type PresenceEventSelector, type PresenceFilter, type PresenceJoinPayload, type PresenceLeavePayload, type PresencePayload, type PresenceState, type PresenceSyncPayload, Realtime, RealtimeChannel, type RealtimeMessageMeta, type RealtimeOptions, type SignInRequest, type SignUpRequest, type SignedUrlOptions, type SignedUrlResult, Storage, type StorageBucket, StorageBucketClient, type StorageConfig, type StorageObject, type StorageResult, TokenManager, type UpdateBucketOptions, type UploadBody, type UploadOptions, type User, createClient, MitwayBaasClient as default };