@koolbase/core 10.0.1 → 10.1.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.
@@ -29,6 +29,11 @@ class KoolbaseAnalytics {
29
29
  (0, platform_js_1.getPlatform)().lifecycle.onBackground(() => { this.flush(); });
30
30
  // Periodic flush
31
31
  this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
32
+ // A browser keeps the page alive regardless; Node counts this timer as
33
+ // work and will not exit while it is pending. Without unref, any script
34
+ // that initializes the SDK — a CLI, a test runner, an SSR build step —
35
+ // hangs after its last line. unref exists only on Node's timer object.
36
+ this.flushTimer.unref?.();
32
37
  // Auto track app_open
33
38
  this.track('app_open');
34
39
  this.initialized = true;
@@ -176,10 +176,13 @@ class KoolbaseRealtime {
176
176
  return;
177
177
  const delay = Math.min(3000 * Math.pow(2, this.reconnectAttempts), 60000);
178
178
  this.reconnectAttempts += 1;
179
+ // Same reason as the analytics flush: a pending reconnect must not be
180
+ // the thing that keeps a Node process alive.
179
181
  this.reconnectTimer = setTimeout(() => {
180
182
  this.reconnectTimer = null;
181
183
  void this.connect();
182
184
  }, delay);
185
+ this.reconnectTimer.unref?.();
183
186
  }
184
187
  disconnect() {
185
188
  if (this.reconnectTimer) {
@@ -88,7 +88,14 @@ class KoolbaseStorage {
88
88
  */
89
89
  async upload(options) {
90
90
  const overwrite = options.overwrite ?? false;
91
- const contentType = options.file.type;
91
+ // A Blob (browser File included) knows its own type; the React Native
92
+ // URI form carries it alongside. Falling back to octet-stream keeps a
93
+ // typeless Blob from presigning with an empty content type, which R2
94
+ // then refuses at PUT.
95
+ const isBlob = typeof Blob !== 'undefined' && options.file instanceof Blob;
96
+ const contentType = isBlob
97
+ ? options.file.type || 'application/octet-stream'
98
+ : options.file.type;
92
99
  // ─── Step 1: Get presigned upload URL ───
93
100
  const urlRes = await fetch(`${this.config.baseUrl}/v1/sdk/storage/upload-url`, {
94
101
  method: 'POST',
@@ -108,10 +115,15 @@ class KoolbaseStorage {
108
115
  }
109
116
  const { upload_url } = (await urlRes.json());
110
117
  // ─── Step 2: Upload directly to R2 ───
111
- // RN's fetch resolves local file URIs and Blob bodies on a raw PUT.
112
118
  // R2 presigned URLs expect raw binary, NOT multipart/form-data.
113
- const fileResp = await fetch(options.file.uri);
114
- const fileBlob = await fileResp.blob();
119
+ //
120
+ // A browser hands us the bytes already: a File from an input is a Blob,
121
+ // and fetching its .uri would be fetching undefined. React Native hands
122
+ // us a local URI, and its fetch resolves that into a Blob — which is why
123
+ // the two hosts differ here and nowhere else in this method.
124
+ const fileBlob = isBlob
125
+ ? options.file
126
+ : await (await fetch(options.file.uri)).blob();
115
127
  const fileSize = fileBlob.size;
116
128
  const uploadRes = await fetch(upload_url, {
117
129
  method: 'PUT',
@@ -214,7 +214,16 @@ export interface SemanticSearchResult {
214
214
  export interface UploadOptions {
215
215
  bucket: string;
216
216
  path: string;
217
- file: {
217
+ /**
218
+ * The bytes to upload, in whichever form the host produces them.
219
+ *
220
+ * A browser gives you a `File` from an `<input type="file">` or a `Blob`
221
+ * you built; pass it directly. React Native gives you a local URI from an
222
+ * image picker or the file system; pass `{ uri, name, type }` and the SDK
223
+ * resolves it. A Blob already carries its own content type, so `type` is
224
+ * only needed for the URI form.
225
+ */
226
+ file: Blob | {
218
227
  uri: string;
219
228
  name: string;
220
229
  type: string;
@@ -26,6 +26,11 @@ export class KoolbaseAnalytics {
26
26
  getPlatform().lifecycle.onBackground(() => { this.flush(); });
27
27
  // Periodic flush
28
28
  this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
29
+ // A browser keeps the page alive regardless; Node counts this timer as
30
+ // work and will not exit while it is pending. Without unref, any script
31
+ // that initializes the SDK — a CLI, a test runner, an SSR build step —
32
+ // hangs after its last line. unref exists only on Node's timer object.
33
+ this.flushTimer.unref?.();
29
34
  // Auto track app_open
30
35
  this.track('app_open');
31
36
  this.initialized = true;
@@ -173,10 +173,13 @@ export class KoolbaseRealtime {
173
173
  return;
174
174
  const delay = Math.min(3000 * Math.pow(2, this.reconnectAttempts), 60000);
175
175
  this.reconnectAttempts += 1;
176
+ // Same reason as the analytics flush: a pending reconnect must not be
177
+ // the thing that keeps a Node process alive.
176
178
  this.reconnectTimer = setTimeout(() => {
177
179
  this.reconnectTimer = null;
178
180
  void this.connect();
179
181
  }, delay);
182
+ this.reconnectTimer.unref?.();
180
183
  }
181
184
  disconnect() {
182
185
  if (this.reconnectTimer) {
@@ -85,7 +85,14 @@ export class KoolbaseStorage {
85
85
  */
86
86
  async upload(options) {
87
87
  const overwrite = options.overwrite ?? false;
88
- const contentType = options.file.type;
88
+ // A Blob (browser File included) knows its own type; the React Native
89
+ // URI form carries it alongside. Falling back to octet-stream keeps a
90
+ // typeless Blob from presigning with an empty content type, which R2
91
+ // then refuses at PUT.
92
+ const isBlob = typeof Blob !== 'undefined' && options.file instanceof Blob;
93
+ const contentType = isBlob
94
+ ? options.file.type || 'application/octet-stream'
95
+ : options.file.type;
89
96
  // ─── Step 1: Get presigned upload URL ───
90
97
  const urlRes = await fetch(`${this.config.baseUrl}/v1/sdk/storage/upload-url`, {
91
98
  method: 'POST',
@@ -105,10 +112,15 @@ export class KoolbaseStorage {
105
112
  }
106
113
  const { upload_url } = (await urlRes.json());
107
114
  // ─── Step 2: Upload directly to R2 ───
108
- // RN's fetch resolves local file URIs and Blob bodies on a raw PUT.
109
115
  // R2 presigned URLs expect raw binary, NOT multipart/form-data.
110
- const fileResp = await fetch(options.file.uri);
111
- const fileBlob = await fileResp.blob();
116
+ //
117
+ // A browser hands us the bytes already: a File from an input is a Blob,
118
+ // and fetching its .uri would be fetching undefined. React Native hands
119
+ // us a local URI, and its fetch resolves that into a Blob — which is why
120
+ // the two hosts differ here and nowhere else in this method.
121
+ const fileBlob = isBlob
122
+ ? options.file
123
+ : await (await fetch(options.file.uri)).blob();
112
124
  const fileSize = fileBlob.size;
113
125
  const uploadRes = await fetch(upload_url, {
114
126
  method: 'PUT',
@@ -214,7 +214,16 @@ export interface SemanticSearchResult {
214
214
  export interface UploadOptions {
215
215
  bucket: string;
216
216
  path: string;
217
- file: {
217
+ /**
218
+ * The bytes to upload, in whichever form the host produces them.
219
+ *
220
+ * A browser gives you a `File` from an `<input type="file">` or a `Blob`
221
+ * you built; pass it directly. React Native gives you a local URI from an
222
+ * image picker or the file system; pass `{ uri, name, type }` and the SDK
223
+ * resolves it. A Blob already carries its own content type, so `type` is
224
+ * only needed for the URI form.
225
+ */
226
+ file: Blob | {
218
227
  uri: string;
219
228
  name: string;
220
229
  type: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koolbase/core",
3
- "version": "10.0.1",
3
+ "version": "10.1.0",
4
4
  "description": "Koolbase SDK core \u2014 shared behaviour behind @koolbase/react-native and @koolbase/js. Install one of those, not this.",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "types": "./dist/esm/index.d.ts",