@koolbase/core 12.8.0 → 12.9.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.
@@ -3,8 +3,21 @@ export declare class KoolbaseFlags {
3
3
  private config;
4
4
  private payload;
5
5
  private deviceId;
6
+ private lastArgs;
7
+ private inFlight;
6
8
  constructor(config: KoolbaseConfig, deviceId: string);
7
9
  koolbaseFetch(appVersion: string, platform: string): Promise<void>;
10
+ /**
11
+ * The project id learned from the bootstrap payload, or '' while the first
12
+ * bootstrap has not completed (storage's publicUrlFor reads it).
13
+ */
14
+ projectId(): string;
15
+ /**
16
+ * Fetch the bootstrap payload again with the last arguments -- a nudge from
17
+ * storage when identity is missing, so a startup failure heals within the
18
+ * session. One fetch at a time; never throws.
19
+ */
20
+ refresh(): Promise<void>;
8
21
  isEnabled(key: string): boolean;
9
22
  getString(key: string, fallback?: string): string;
10
23
  getNumber(key: string, fallback?: number): number;
package/dist/cjs/flags.js CHANGED
@@ -5,10 +5,15 @@ const network_js_1 = require("./network.js");
5
5
  class KoolbaseFlags {
6
6
  constructor(config, deviceId) {
7
7
  this.payload = null;
8
+ // The last bootstrap's arguments, so a nudge can refresh with the same ones;
9
+ // and the fetch in flight, so nudges never stack.
10
+ this.lastArgs = null;
11
+ this.inFlight = null;
8
12
  this.config = config;
9
13
  this.deviceId = deviceId;
10
14
  }
11
15
  async koolbaseFetch(appVersion, platform) {
16
+ this.lastArgs = { appVersion, platform };
12
17
  try {
13
18
  const res = await (0, network_js_1.koolbaseFetch)(`${this.config.baseUrl}/v1/bootstrap?public_key=${this.config.publicKey}&device_id=${this.deviceId}&app_version=${appVersion}&platform=${platform}`);
14
19
  if (res.ok) {
@@ -17,6 +22,25 @@ class KoolbaseFlags {
17
22
  }
18
23
  catch (_) { }
19
24
  }
25
+ /**
26
+ * The project id learned from the bootstrap payload, or '' while the first
27
+ * bootstrap has not completed (storage's publicUrlFor reads it).
28
+ */
29
+ projectId() {
30
+ return this.payload?.project_id ?? '';
31
+ }
32
+ /**
33
+ * Fetch the bootstrap payload again with the last arguments -- a nudge from
34
+ * storage when identity is missing, so a startup failure heals within the
35
+ * session. One fetch at a time; never throws.
36
+ */
37
+ refresh() {
38
+ if (this.inFlight)
39
+ return this.inFlight;
40
+ const args = this.lastArgs ?? { appVersion: this.config.appVersion ?? '0.0.0', platform: 'unknown' };
41
+ this.inFlight = this.koolbaseFetch(args.appVersion, args.platform).finally(() => { this.inFlight = null; });
42
+ return this.inFlight;
43
+ }
20
44
  isEnabled(key) {
21
45
  const flag = this.payload?.flags[key];
22
46
  if (!flag || !flag.enabled || flag.kill_switch)
@@ -7,6 +7,22 @@ import { KoolbaseError } from './errors.js';
7
7
  export declare class KoolbaseStorageError extends KoolbaseError {
8
8
  constructor(message: string, code?: string);
9
9
  }
10
+ /**
11
+ * Thrown by {@link KoolbaseStorage.publicUrlFor} when the SDK has not yet
12
+ * learned which project it belongs to. Project identity arrives with the
13
+ * bootstrap payload; it is unavailable until the first bootstrap completes
14
+ * (an app starting offline) or when the server predates identity metadata.
15
+ *
16
+ * A DIFFERENT state from a `null` public URL: null means the project is
17
+ * known but the object cannot have a public URL (private bucket). This one is
18
+ * transient: it heals on the next successful bootstrap, which calling
19
+ * publicUrlFor also nudges.
20
+ *
21
+ * Stable `code`: `project_identity_unavailable`.
22
+ */
23
+ export declare class KoolbaseStorageProjectIdentityError extends KoolbaseStorageError {
24
+ constructor();
25
+ }
10
26
  /**
11
27
  * Thrown when an upload is rejected because an object already exists at
12
28
  * the requested path — the server responds with 409 Conflict and code
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.KoolbaseStorageMetadataInvalidError = exports.KoolbaseStorageMimeTypeError = exports.KoolbaseStorageFileTooLargeError = exports.KoolbaseStorageQuotaError = exports.KoolbaseCapBelowUsageError = exports.KoolbaseUploadExpiredError = exports.KoolbaseUploadURLFailedError = exports.KoolbaseStoragePermissionError = exports.KoolbaseStorageValidationError = exports.KoolbaseStorageNotFoundError = exports.KoolbaseStorageConflictError = exports.KoolbaseStorageError = void 0;
3
+ exports.KoolbaseStorageMetadataInvalidError = exports.KoolbaseStorageMimeTypeError = exports.KoolbaseStorageFileTooLargeError = exports.KoolbaseStorageQuotaError = exports.KoolbaseCapBelowUsageError = exports.KoolbaseUploadExpiredError = exports.KoolbaseUploadURLFailedError = exports.KoolbaseStoragePermissionError = exports.KoolbaseStorageValidationError = exports.KoolbaseStorageNotFoundError = exports.KoolbaseStorageConflictError = exports.KoolbaseStorageProjectIdentityError = exports.KoolbaseStorageError = void 0;
4
4
  exports.koolbaseStorageError = koolbaseStorageError;
5
5
  exports.koolbaseStorageErrorFromResponse = koolbaseStorageErrorFromResponse;
6
6
  const errors_js_1 = require("./errors.js");
@@ -17,6 +17,27 @@ class KoolbaseStorageError extends errors_js_1.KoolbaseError {
17
17
  }
18
18
  }
19
19
  exports.KoolbaseStorageError = KoolbaseStorageError;
20
+ /**
21
+ * Thrown by {@link KoolbaseStorage.publicUrlFor} when the SDK has not yet
22
+ * learned which project it belongs to. Project identity arrives with the
23
+ * bootstrap payload; it is unavailable until the first bootstrap completes
24
+ * (an app starting offline) or when the server predates identity metadata.
25
+ *
26
+ * A DIFFERENT state from a `null` public URL: null means the project is
27
+ * known but the object cannot have a public URL (private bucket). This one is
28
+ * transient: it heals on the next successful bootstrap, which calling
29
+ * publicUrlFor also nudges.
30
+ *
31
+ * Stable `code`: `project_identity_unavailable`.
32
+ */
33
+ class KoolbaseStorageProjectIdentityError extends KoolbaseStorageError {
34
+ constructor() {
35
+ super('The SDK has not yet learned its project identity -- it arrives with the first successful bootstrap. Retry after connectivity is available.', 'project_identity_unavailable');
36
+ this.name = 'KoolbaseStorageProjectIdentityError';
37
+ Object.setPrototypeOf(this, KoolbaseStorageProjectIdentityError.prototype);
38
+ }
39
+ }
40
+ exports.KoolbaseStorageProjectIdentityError = KoolbaseStorageProjectIdentityError;
20
41
  /**
21
42
  * Thrown when an upload is rejected because an object already exists at
22
43
  * the requested path — the server responds with 409 Conflict and code
@@ -19,7 +19,11 @@ export declare class KoolbaseStorage {
19
19
  * keeps believing it is signed in.
20
20
  */
21
21
  private onSessionExpired?;
22
- constructor(config: KoolbaseConfig, getToken: () => Promise<string | null>, onSessionExpired?: () => Promise<void>);
22
+ /** The project id from the bootstrap payload, '' until it arrives. */
23
+ private projectIdProvider?;
24
+ /** A background bootstrap refresh, nudged when identity is missing. */
25
+ private nudgeBootstrap?;
26
+ constructor(config: KoolbaseConfig, getToken: () => Promise<string | null>, onSessionExpired?: () => Promise<void>, projectIdProvider?: () => string, nudgeBootstrap?: () => void);
23
27
  /**
24
28
  * Builds the error for a failed response and clears the session when the
25
29
  * credentials were refused, before the error reaches the caller.
@@ -87,6 +91,20 @@ export declare class KoolbaseStorage {
87
91
  * Get a signed download URL for a file.
88
92
  */
89
93
  getDownloadUrl(bucket: string, path: string, versionId?: string): Promise<string>;
94
+ /**
95
+ * The stable public CDN URL for a file in a public bucket, using the project
96
+ * identity the SDK learned at bootstrap -- the runtime form of
97
+ * {@link KoolbaseStorage.publicUrl}, needing no projectId (as Flutter's).
98
+ *
99
+ * Throws {@link KoolbaseStorageProjectIdentityError} while identity is
100
+ * unavailable, and nudges a background bootstrap refresh so a later call in
101
+ * the same session succeeds once connectivity returns.
102
+ */
103
+ publicUrlFor(args: {
104
+ bucket: string;
105
+ path: string;
106
+ transform?: KoolbaseImageTransform;
107
+ }): string;
90
108
  /**
91
109
  * Build the stable public CDN URL for a file in a public bucket.
92
110
  *
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KoolbaseStorage = void 0;
4
+ const storage_errors_js_1 = require("./storage-errors.js");
4
5
  const network_js_1 = require("./network.js");
5
6
  const errors_js_1 = require("./errors.js");
6
- const storage_errors_js_1 = require("./storage-errors.js");
7
+ const storage_errors_js_2 = require("./storage-errors.js");
7
8
  // --- Cloudflare image-transform URL helpers -------------------------------
8
9
  // Module-private — callers use KoolbaseStorage.publicUrl / publicUrlForObject.
9
10
  function clampInt(v, min, max) {
@@ -42,17 +43,19 @@ function serializeTransform(t) {
42
43
  * `overwrite: true` is passed.
43
44
  */
44
45
  class KoolbaseStorage {
45
- constructor(config, getToken, onSessionExpired) {
46
+ constructor(config, getToken, onSessionExpired, projectIdProvider, nudgeBootstrap) {
46
47
  this.config = config;
47
48
  this.getToken = getToken;
48
49
  this.onSessionExpired = onSessionExpired;
50
+ this.projectIdProvider = projectIdProvider;
51
+ this.nudgeBootstrap = nudgeBootstrap;
49
52
  }
50
53
  /**
51
54
  * Builds the error for a failed response and clears the session when the
52
55
  * credentials were refused, before the error reaches the caller.
53
56
  */
54
57
  async error(res, fallback) {
55
- const err = await (0, storage_errors_js_1.koolbaseStorageErrorFromResponse)(res, fallback);
58
+ const err = await (0, storage_errors_js_2.koolbaseStorageErrorFromResponse)(res, fallback);
56
59
  if (err instanceof errors_js_1.KoolbaseUnauthenticatedError) {
57
60
  await this.onSessionExpired?.();
58
61
  }
@@ -134,7 +137,7 @@ class KoolbaseStorage {
134
137
  if (!uploadRes.ok) {
135
138
  // R2 PUT errors don't follow the Koolbase error shape — surface as a
136
139
  // generic storage error rather than trying to decode a Koolbase body.
137
- throw new storage_errors_js_1.KoolbaseStorageError(`Upload to storage failed: ${uploadRes.status}`);
140
+ throw new storage_errors_js_2.KoolbaseStorageError(`Upload to storage failed: ${uploadRes.status}`);
138
141
  }
139
142
  const etag = uploadRes.headers.get('etag') ?? '';
140
143
  // ─── Step 3: Confirm upload ───
@@ -234,6 +237,23 @@ class KoolbaseStorage {
234
237
  const data = (await res.json());
235
238
  return data.url;
236
239
  }
240
+ /**
241
+ * The stable public CDN URL for a file in a public bucket, using the project
242
+ * identity the SDK learned at bootstrap -- the runtime form of
243
+ * {@link KoolbaseStorage.publicUrl}, needing no projectId (as Flutter's).
244
+ *
245
+ * Throws {@link KoolbaseStorageProjectIdentityError} while identity is
246
+ * unavailable, and nudges a background bootstrap refresh so a later call in
247
+ * the same session succeeds once connectivity returns.
248
+ */
249
+ publicUrlFor(args) {
250
+ const pid = this.projectIdProvider?.() ?? '';
251
+ if (pid === '') {
252
+ this.nudgeBootstrap?.();
253
+ throw new storage_errors_js_1.KoolbaseStorageProjectIdentityError();
254
+ }
255
+ return KoolbaseStorage.publicUrl({ projectId: pid, bucket: args.bucket, path: args.path, transform: args.transform });
256
+ }
237
257
  /**
238
258
  * Build the stable public CDN URL for a file in a public bucket.
239
259
  *
@@ -457,6 +457,9 @@ export interface RealtimeEvent {
457
457
  export type RealtimeCallback = (event: RealtimeEvent) => void;
458
458
  export interface BootstrapPayload {
459
459
  payload_version: string;
460
+ /** The project this public key belongs to: storage's publicUrlFor builds CDN
461
+ * URLs with it. Absent from servers that predate identity metadata. */
462
+ project_id?: string;
460
463
  flags: Record<string, {
461
464
  enabled: boolean;
462
465
  rollout_percentage: number;
@@ -3,8 +3,21 @@ export declare class KoolbaseFlags {
3
3
  private config;
4
4
  private payload;
5
5
  private deviceId;
6
+ private lastArgs;
7
+ private inFlight;
6
8
  constructor(config: KoolbaseConfig, deviceId: string);
7
9
  koolbaseFetch(appVersion: string, platform: string): Promise<void>;
10
+ /**
11
+ * The project id learned from the bootstrap payload, or '' while the first
12
+ * bootstrap has not completed (storage's publicUrlFor reads it).
13
+ */
14
+ projectId(): string;
15
+ /**
16
+ * Fetch the bootstrap payload again with the last arguments -- a nudge from
17
+ * storage when identity is missing, so a startup failure heals within the
18
+ * session. One fetch at a time; never throws.
19
+ */
20
+ refresh(): Promise<void>;
8
21
  isEnabled(key: string): boolean;
9
22
  getString(key: string, fallback?: string): string;
10
23
  getNumber(key: string, fallback?: number): number;
package/dist/esm/flags.js CHANGED
@@ -2,10 +2,15 @@ import { koolbaseFetch } from './network.js';
2
2
  export class KoolbaseFlags {
3
3
  constructor(config, deviceId) {
4
4
  this.payload = null;
5
+ // The last bootstrap's arguments, so a nudge can refresh with the same ones;
6
+ // and the fetch in flight, so nudges never stack.
7
+ this.lastArgs = null;
8
+ this.inFlight = null;
5
9
  this.config = config;
6
10
  this.deviceId = deviceId;
7
11
  }
8
12
  async koolbaseFetch(appVersion, platform) {
13
+ this.lastArgs = { appVersion, platform };
9
14
  try {
10
15
  const res = await koolbaseFetch(`${this.config.baseUrl}/v1/bootstrap?public_key=${this.config.publicKey}&device_id=${this.deviceId}&app_version=${appVersion}&platform=${platform}`);
11
16
  if (res.ok) {
@@ -14,6 +19,25 @@ export class KoolbaseFlags {
14
19
  }
15
20
  catch (_) { }
16
21
  }
22
+ /**
23
+ * The project id learned from the bootstrap payload, or '' while the first
24
+ * bootstrap has not completed (storage's publicUrlFor reads it).
25
+ */
26
+ projectId() {
27
+ return this.payload?.project_id ?? '';
28
+ }
29
+ /**
30
+ * Fetch the bootstrap payload again with the last arguments -- a nudge from
31
+ * storage when identity is missing, so a startup failure heals within the
32
+ * session. One fetch at a time; never throws.
33
+ */
34
+ refresh() {
35
+ if (this.inFlight)
36
+ return this.inFlight;
37
+ const args = this.lastArgs ?? { appVersion: this.config.appVersion ?? '0.0.0', platform: 'unknown' };
38
+ this.inFlight = this.koolbaseFetch(args.appVersion, args.platform).finally(() => { this.inFlight = null; });
39
+ return this.inFlight;
40
+ }
17
41
  isEnabled(key) {
18
42
  const flag = this.payload?.flags[key];
19
43
  if (!flag || !flag.enabled || flag.kill_switch)
@@ -7,6 +7,22 @@ import { KoolbaseError } from './errors.js';
7
7
  export declare class KoolbaseStorageError extends KoolbaseError {
8
8
  constructor(message: string, code?: string);
9
9
  }
10
+ /**
11
+ * Thrown by {@link KoolbaseStorage.publicUrlFor} when the SDK has not yet
12
+ * learned which project it belongs to. Project identity arrives with the
13
+ * bootstrap payload; it is unavailable until the first bootstrap completes
14
+ * (an app starting offline) or when the server predates identity metadata.
15
+ *
16
+ * A DIFFERENT state from a `null` public URL: null means the project is
17
+ * known but the object cannot have a public URL (private bucket). This one is
18
+ * transient: it heals on the next successful bootstrap, which calling
19
+ * publicUrlFor also nudges.
20
+ *
21
+ * Stable `code`: `project_identity_unavailable`.
22
+ */
23
+ export declare class KoolbaseStorageProjectIdentityError extends KoolbaseStorageError {
24
+ constructor();
25
+ }
10
26
  /**
11
27
  * Thrown when an upload is rejected because an object already exists at
12
28
  * the requested path — the server responds with 409 Conflict and code
@@ -11,6 +11,26 @@ export class KoolbaseStorageError extends KoolbaseError {
11
11
  Object.setPrototypeOf(this, KoolbaseStorageError.prototype);
12
12
  }
13
13
  }
14
+ /**
15
+ * Thrown by {@link KoolbaseStorage.publicUrlFor} when the SDK has not yet
16
+ * learned which project it belongs to. Project identity arrives with the
17
+ * bootstrap payload; it is unavailable until the first bootstrap completes
18
+ * (an app starting offline) or when the server predates identity metadata.
19
+ *
20
+ * A DIFFERENT state from a `null` public URL: null means the project is
21
+ * known but the object cannot have a public URL (private bucket). This one is
22
+ * transient: it heals on the next successful bootstrap, which calling
23
+ * publicUrlFor also nudges.
24
+ *
25
+ * Stable `code`: `project_identity_unavailable`.
26
+ */
27
+ export class KoolbaseStorageProjectIdentityError extends KoolbaseStorageError {
28
+ constructor() {
29
+ super('The SDK has not yet learned its project identity -- it arrives with the first successful bootstrap. Retry after connectivity is available.', 'project_identity_unavailable');
30
+ this.name = 'KoolbaseStorageProjectIdentityError';
31
+ Object.setPrototypeOf(this, KoolbaseStorageProjectIdentityError.prototype);
32
+ }
33
+ }
14
34
  /**
15
35
  * Thrown when an upload is rejected because an object already exists at
16
36
  * the requested path — the server responds with 409 Conflict and code
@@ -19,7 +19,11 @@ export declare class KoolbaseStorage {
19
19
  * keeps believing it is signed in.
20
20
  */
21
21
  private onSessionExpired?;
22
- constructor(config: KoolbaseConfig, getToken: () => Promise<string | null>, onSessionExpired?: () => Promise<void>);
22
+ /** The project id from the bootstrap payload, '' until it arrives. */
23
+ private projectIdProvider?;
24
+ /** A background bootstrap refresh, nudged when identity is missing. */
25
+ private nudgeBootstrap?;
26
+ constructor(config: KoolbaseConfig, getToken: () => Promise<string | null>, onSessionExpired?: () => Promise<void>, projectIdProvider?: () => string, nudgeBootstrap?: () => void);
23
27
  /**
24
28
  * Builds the error for a failed response and clears the session when the
25
29
  * credentials were refused, before the error reaches the caller.
@@ -87,6 +91,20 @@ export declare class KoolbaseStorage {
87
91
  * Get a signed download URL for a file.
88
92
  */
89
93
  getDownloadUrl(bucket: string, path: string, versionId?: string): Promise<string>;
94
+ /**
95
+ * The stable public CDN URL for a file in a public bucket, using the project
96
+ * identity the SDK learned at bootstrap -- the runtime form of
97
+ * {@link KoolbaseStorage.publicUrl}, needing no projectId (as Flutter's).
98
+ *
99
+ * Throws {@link KoolbaseStorageProjectIdentityError} while identity is
100
+ * unavailable, and nudges a background bootstrap refresh so a later call in
101
+ * the same session succeeds once connectivity returns.
102
+ */
103
+ publicUrlFor(args: {
104
+ bucket: string;
105
+ path: string;
106
+ transform?: KoolbaseImageTransform;
107
+ }): string;
90
108
  /**
91
109
  * Build the stable public CDN URL for a file in a public bucket.
92
110
  *
@@ -1,3 +1,4 @@
1
+ import { KoolbaseStorageProjectIdentityError } from './storage-errors.js';
1
2
  import { koolbaseFetch } from './network.js';
2
3
  import { KoolbaseUnauthenticatedError } from './errors.js';
3
4
  import { KoolbaseStorageError, koolbaseStorageErrorFromResponse, } from './storage-errors.js';
@@ -39,10 +40,12 @@ function serializeTransform(t) {
39
40
  * `overwrite: true` is passed.
40
41
  */
41
42
  export class KoolbaseStorage {
42
- constructor(config, getToken, onSessionExpired) {
43
+ constructor(config, getToken, onSessionExpired, projectIdProvider, nudgeBootstrap) {
43
44
  this.config = config;
44
45
  this.getToken = getToken;
45
46
  this.onSessionExpired = onSessionExpired;
47
+ this.projectIdProvider = projectIdProvider;
48
+ this.nudgeBootstrap = nudgeBootstrap;
46
49
  }
47
50
  /**
48
51
  * Builds the error for a failed response and clears the session when the
@@ -231,6 +234,23 @@ export class KoolbaseStorage {
231
234
  const data = (await res.json());
232
235
  return data.url;
233
236
  }
237
+ /**
238
+ * The stable public CDN URL for a file in a public bucket, using the project
239
+ * identity the SDK learned at bootstrap -- the runtime form of
240
+ * {@link KoolbaseStorage.publicUrl}, needing no projectId (as Flutter's).
241
+ *
242
+ * Throws {@link KoolbaseStorageProjectIdentityError} while identity is
243
+ * unavailable, and nudges a background bootstrap refresh so a later call in
244
+ * the same session succeeds once connectivity returns.
245
+ */
246
+ publicUrlFor(args) {
247
+ const pid = this.projectIdProvider?.() ?? '';
248
+ if (pid === '') {
249
+ this.nudgeBootstrap?.();
250
+ throw new KoolbaseStorageProjectIdentityError();
251
+ }
252
+ return KoolbaseStorage.publicUrl({ projectId: pid, bucket: args.bucket, path: args.path, transform: args.transform });
253
+ }
234
254
  /**
235
255
  * Build the stable public CDN URL for a file in a public bucket.
236
256
  *
@@ -457,6 +457,9 @@ export interface RealtimeEvent {
457
457
  export type RealtimeCallback = (event: RealtimeEvent) => void;
458
458
  export interface BootstrapPayload {
459
459
  payload_version: string;
460
+ /** The project this public key belongs to: storage's publicUrlFor builds CDN
461
+ * URLs with it. Absent from servers that predate identity metadata. */
462
+ project_id?: string;
460
463
  flags: Record<string, {
461
464
  enabled: boolean;
462
465
  rollout_percentage: number;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@koolbase/core",
3
- "version": "12.8.0",
4
- "description": "Koolbase SDK core — shared behaviour behind @koolbase/react-native and @koolbase/js. Install one of those, not this.",
3
+ "version": "12.9.0",
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",
7
7
  "files": [