@supabase/supabase-js 2.105.5-beta.8 → 2.106.0-canary.1

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/README.md CHANGED
@@ -110,6 +110,52 @@ const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishabl
110
110
  })
111
111
  ```
112
112
 
113
+ ### Distributed Tracing with OpenTelemetry
114
+
115
+ The Supabase JS SDK can attach W3C/OpenTelemetry trace context headers (`traceparent`, `tracestate`, `baggage`) to outgoing requests, enabling end-to-end request tracing from your client application through Supabase services.
116
+
117
+ Trace propagation is **opt-in** and disabled by default. When enabled, headers are only attached to requests targeting Supabase domains (`*.supabase.co`, `*.supabase.in`, `localhost`).
118
+
119
+ #### Enable trace propagation
120
+
121
+ ```js
122
+ import { createClient } from '@supabase/supabase-js'
123
+ import { trace } from '@opentelemetry/api'
124
+
125
+ const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key', {
126
+ tracePropagation: true,
127
+ })
128
+
129
+ const tracer = trace.getTracer('my-app')
130
+ await tracer.startActiveSpan('fetch-users', async (span) => {
131
+ // This request now includes the active trace context.
132
+ const { data, error } = await supabase.from('users').select('*')
133
+ span.end()
134
+ })
135
+ ```
136
+
137
+ If `@opentelemetry/api` is not installed or no active context exists, the SDK silently no-ops.
138
+
139
+ #### Advanced configuration
140
+
141
+ ```typescript
142
+ interface TracePropagationOptions {
143
+ // Enable trace propagation (default: false).
144
+ enabled?: boolean
145
+
146
+ // Respect upstream sampling decisions (default: true).
147
+ // When true, headers are skipped if the upstream trace is not sampled.
148
+ respectSamplingDecision?: boolean
149
+ }
150
+ ```
151
+
152
+ ```js
153
+ // Always propagate, even for non-sampled traces.
154
+ const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key', {
155
+ tracePropagation: { enabled: true, respectSamplingDecision: false },
156
+ })
157
+ ```
158
+
113
159
  ## Support Policy
114
160
 
115
161
  This section outlines the scope of support for various runtime environments in Supabase JavaScript client.
package/dist/index.cjs CHANGED
@@ -5,7 +5,7 @@ let _supabase_storage_js = require("@supabase/storage-js");
5
5
  let _supabase_auth_js = require("@supabase/auth-js");
6
6
 
7
7
  //#region src/lib/version.ts
8
- const version = "2.105.5-beta.8";
8
+ const version = "2.106.0-canary.1";
9
9
 
10
10
  //#endregion
11
11
  //#region src/lib/constants.ts
@@ -24,6 +24,225 @@ const DEFAULT_AUTH_OPTIONS = {
24
24
  flowType: "implicit"
25
25
  };
26
26
  const DEFAULT_REALTIME_OPTIONS = {};
27
+ const DEFAULT_TRACE_PROPAGATION_OPTIONS = {
28
+ enabled: false,
29
+ respectSamplingDecision: true
30
+ };
31
+
32
+ //#endregion
33
+ //#region ../../../node_modules/tslib/tslib.es6.mjs
34
+ function __awaiter(thisArg, _arguments, P, generator) {
35
+ function adopt(value) {
36
+ return value instanceof P ? value : new P(function(resolve) {
37
+ resolve(value);
38
+ });
39
+ }
40
+ return new (P || (P = Promise))(function(resolve, reject) {
41
+ function fulfilled(value) {
42
+ try {
43
+ step(generator.next(value));
44
+ } catch (e) {
45
+ reject(e);
46
+ }
47
+ }
48
+ function rejected(value) {
49
+ try {
50
+ step(generator["throw"](value));
51
+ } catch (e) {
52
+ reject(e);
53
+ }
54
+ }
55
+ function step(result) {
56
+ result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
57
+ }
58
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
59
+ });
60
+ }
61
+
62
+ //#endregion
63
+ //#region ../../shared/tracing/dist/module/extract.js
64
+ let otelModulePromise = null;
65
+ const OTEL_PKG = "@opentelemetry/api";
66
+ function loadOtel() {
67
+ if (otelModulePromise === null) otelModulePromise = import(
68
+ /* webpackIgnore: true */
69
+ /* @vite-ignore */
70
+ OTEL_PKG
71
+ ).catch(() => null);
72
+ return otelModulePromise;
73
+ }
74
+ /**
75
+ * Extract trace context from the OpenTelemetry API.
76
+ *
77
+ * Returns null if `@opentelemetry/api` is not installed or there is no active
78
+ * trace context. The dynamic import is cached after the first call.
79
+ *
80
+ * @returns Trace context with traceparent, tracestate, and baggage headers, or null if unavailable
81
+ */
82
+ function extractTraceContext() {
83
+ return __awaiter(this, void 0, void 0, function* () {
84
+ try {
85
+ const otel = yield loadOtel();
86
+ if (!otel || !otel.propagation || !otel.context) return null;
87
+ const carrier = {};
88
+ otel.propagation.inject(otel.context.active(), carrier);
89
+ const traceparent = carrier["traceparent"];
90
+ if (!traceparent) return null;
91
+ return {
92
+ traceparent,
93
+ tracestate: carrier["tracestate"],
94
+ baggage: carrier["baggage"]
95
+ };
96
+ } catch (_a) {
97
+ return null;
98
+ }
99
+ });
100
+ }
101
+
102
+ //#endregion
103
+ //#region ../../shared/tracing/dist/module/parse.js
104
+ /**
105
+ * Parse W3C traceparent header according to the specification.
106
+ *
107
+ * The traceparent header format is: version-traceid-parentid-traceflags
108
+ * - version: 2 hex digits (currently always "00")
109
+ * - traceid: 32 hex digits (128-bit trace identifier)
110
+ * - parentid: 16 hex digits (64-bit span/parent identifier)
111
+ * - traceflags: 2 hex digits (8-bit flags, bit 0 is sampled flag)
112
+ *
113
+ * @param traceparent - The traceparent header value
114
+ * @returns Parsed traceparent object, or null if invalid format
115
+ *
116
+ * @see https://www.w3.org/TR/trace-context/#traceparent-header
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * const parsed = parseTraceParent('00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01')
121
+ *
122
+ * console.log(parsed)
123
+ * // {
124
+ * // version: '00',
125
+ * // traceId: '0af7651916cd43dd8448eb211c80319c',
126
+ * // parentId: 'b7ad6b7169203331',
127
+ * // traceFlags: '01',
128
+ * // isSampled: true
129
+ * // }
130
+ * ```
131
+ */
132
+ function parseTraceParent(traceparent) {
133
+ if (!traceparent || typeof traceparent !== "string") return null;
134
+ const parts = traceparent.split("-");
135
+ if (parts.length !== 4) return null;
136
+ const [version$1, traceId, parentId, traceFlags] = parts;
137
+ if (version$1.length !== 2 || traceId.length !== 32 || parentId.length !== 16 || traceFlags.length !== 2) return null;
138
+ const hexRegex = /^[0-9a-f]+$/i;
139
+ if (!hexRegex.test(version$1) || !hexRegex.test(traceId) || !hexRegex.test(parentId) || !hexRegex.test(traceFlags)) return null;
140
+ if (traceId === "00000000000000000000000000000000" || parentId === "0000000000000000") return null;
141
+ return {
142
+ version: version$1,
143
+ traceId,
144
+ parentId,
145
+ traceFlags,
146
+ isSampled: (parseInt(traceFlags, 16) & 1) === 1
147
+ };
148
+ }
149
+
150
+ //#endregion
151
+ //#region ../../shared/tracing/dist/module/validate.js
152
+ /**
153
+ * Check if trace context should be propagated to the target URL.
154
+ *
155
+ * This function checks if the target URL matches any of the configured
156
+ * propagation targets. Targets can be:
157
+ * - String: Exact hostname match or wildcard domain (*.example.com)
158
+ * - RegExp: Pattern matching hostname
159
+ * - Function: Custom logic to determine if URL should receive trace context
160
+ *
161
+ * @param targetUrl - The URL to check
162
+ * @param targets - Array of propagation targets
163
+ * @returns True if trace context should be propagated, false otherwise
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * const targets = [
168
+ * 'myproject.supabase.co', // Exact match
169
+ * '*.supabase.co', // Wildcard domain
170
+ * /.*\.supabase\.co$/, // Regex pattern
171
+ * (url) => url.hostname === 'localhost' // Custom function
172
+ * ]
173
+ *
174
+ * shouldPropagateToTarget('https://myproject.supabase.co/rest/v1/table', targets)
175
+ * // true
176
+ *
177
+ * shouldPropagateToTarget('https://evil.com/api', targets)
178
+ * // false
179
+ * ```
180
+ */
181
+ function shouldPropagateToTarget(targetUrl, targets) {
182
+ if (!targetUrl || !targets || targets.length === 0) return false;
183
+ let url;
184
+ if (targetUrl instanceof URL) url = targetUrl;
185
+ else try {
186
+ url = new URL(targetUrl);
187
+ } catch (error) {
188
+ return false;
189
+ }
190
+ for (const target of targets) try {
191
+ if (typeof target === "string") {
192
+ if (matchStringTarget(url.hostname, target)) return true;
193
+ } else if (target instanceof RegExp) {
194
+ if (target.test(url.hostname)) return true;
195
+ } else if (typeof target === "function") {
196
+ if (target(url)) return true;
197
+ }
198
+ } catch (error) {
199
+ continue;
200
+ }
201
+ return false;
202
+ }
203
+ /**
204
+ * Match hostname against string target (exact match or wildcard)
205
+ *
206
+ * @param hostname - The hostname to check
207
+ * @param target - The target pattern (exact or wildcard)
208
+ * @returns True if hostname matches target
209
+ */
210
+ function matchStringTarget(hostname, target) {
211
+ if (target === hostname) return true;
212
+ if (target.startsWith("*.")) {
213
+ const domain = target.slice(2);
214
+ if (hostname.endsWith(domain)) {
215
+ if (hostname === domain || hostname.endsWith("." + domain)) return true;
216
+ }
217
+ }
218
+ return false;
219
+ }
220
+
221
+ //#endregion
222
+ //#region ../../shared/tracing/dist/module/defaults.js
223
+ /**
224
+ * Generate default propagation targets based on the Supabase project URL.
225
+ *
226
+ * By default, trace context is only propagated to Supabase domains for
227
+ * security. This prevents leaking trace context to potentially malicious
228
+ * third-party services.
229
+ *
230
+ * Wildcard strings (e.g. `*.supabase.co`) are matched with linear string
231
+ * operations rather than regex, avoiding ReDoS risk.
232
+ *
233
+ * @param supabaseUrl - The Supabase project URL
234
+ * @returns Array of default propagation targets
235
+ */
236
+ function getDefaultPropagationTargets(supabaseUrl) {
237
+ const targets = [];
238
+ try {
239
+ const url = new URL(supabaseUrl);
240
+ targets.push(url.hostname);
241
+ } catch (error) {}
242
+ targets.push("*.supabase.co", "*.supabase.in");
243
+ targets.push("localhost", "127.0.0.1", "[::1]");
244
+ return targets;
245
+ }
27
246
 
28
247
  //#endregion
29
248
  //#region \0@oxc-project+runtime@0.103.0/helpers/typeof.js
@@ -100,34 +319,64 @@ const resolveFetch = (customFetch) => {
100
319
  const resolveHeadersConstructor = () => {
101
320
  return Headers;
102
321
  };
103
- const fetchWithAuth = (supabaseKey, getAccessToken, customFetch) => {
322
+ const fetchWithAuth = (supabaseKey, supabaseUrl, getAccessToken, customFetch, tracePropagationOptions) => {
104
323
  const fetch$1 = resolveFetch(customFetch);
105
324
  const HeadersConstructor = resolveHeadersConstructor();
325
+ const traceEnabled = (tracePropagationOptions === null || tracePropagationOptions === void 0 ? void 0 : tracePropagationOptions.enabled) === true;
326
+ const respectSampling = (tracePropagationOptions === null || tracePropagationOptions === void 0 ? void 0 : tracePropagationOptions.respectSamplingDecision) !== false;
327
+ const traceTargets = traceEnabled ? getDefaultPropagationTargets(supabaseUrl) : null;
106
328
  return async (input, init) => {
107
329
  var _await$getAccessToken;
108
330
  const accessToken = (_await$getAccessToken = await getAccessToken()) !== null && _await$getAccessToken !== void 0 ? _await$getAccessToken : supabaseKey;
109
331
  let headers = new HeadersConstructor(init === null || init === void 0 ? void 0 : init.headers);
110
332
  if (!headers.has("apikey")) headers.set("apikey", supabaseKey);
111
333
  if (!headers.has("Authorization")) headers.set("Authorization", `Bearer ${accessToken}`);
334
+ if (traceTargets) {
335
+ const traceHeaders = await getTraceHeaders(input, traceTargets, respectSampling);
336
+ if (traceHeaders) {
337
+ if (traceHeaders.traceparent && !headers.has("traceparent")) headers.set("traceparent", traceHeaders.traceparent);
338
+ if (traceHeaders.tracestate && !headers.has("tracestate")) headers.set("tracestate", traceHeaders.tracestate);
339
+ if (traceHeaders.baggage && !headers.has("baggage")) headers.set("baggage", traceHeaders.baggage);
340
+ }
341
+ }
112
342
  return fetch$1(input, _objectSpread2(_objectSpread2({}, init), {}, { headers }));
113
343
  };
114
344
  };
345
+ async function getTraceHeaders(input, targets, respectSampling) {
346
+ if (!shouldPropagateToTarget(typeof input === "string" ? input : input instanceof URL ? input : input.url, targets)) return null;
347
+ const traceContext = await extractTraceContext();
348
+ if (!traceContext || !traceContext.traceparent) return null;
349
+ if (respectSampling) {
350
+ const parsed = parseTraceParent(traceContext.traceparent);
351
+ if (parsed && !parsed.isSampled) return null;
352
+ }
353
+ return traceContext;
354
+ }
115
355
 
116
356
  //#endregion
117
357
  //#region src/lib/helpers.ts
358
+ function normalizeTracePropagation(value) {
359
+ return typeof value === "boolean" ? { enabled: value } : value;
360
+ }
118
361
  function ensureTrailingSlash(url) {
119
362
  return url.endsWith("/") ? url : url + "/";
120
363
  }
121
364
  function applySettingDefaults(options, defaults) {
122
- var _DEFAULT_GLOBAL_OPTIO, _globalOptions$header;
365
+ var _DEFAULT_GLOBAL_OPTIO, _globalOptions$header, _ref, _tracePropagationOpti, _ref2, _tracePropagationOpti2;
123
366
  const { db: dbOptions, auth: authOptions, realtime: realtimeOptions, global: globalOptions } = options;
124
367
  const { db: DEFAULT_DB_OPTIONS$1, auth: DEFAULT_AUTH_OPTIONS$1, realtime: DEFAULT_REALTIME_OPTIONS$1, global: DEFAULT_GLOBAL_OPTIONS$1 } = defaults;
368
+ const tracePropagationOptions = normalizeTracePropagation(options.tracePropagation);
369
+ const DEFAULT_TRACE_PROPAGATION_OPTIONS$1 = normalizeTracePropagation(defaults.tracePropagation);
125
370
  const result = {
126
371
  db: _objectSpread2(_objectSpread2({}, DEFAULT_DB_OPTIONS$1), dbOptions),
127
372
  auth: _objectSpread2(_objectSpread2({}, DEFAULT_AUTH_OPTIONS$1), authOptions),
128
373
  realtime: _objectSpread2(_objectSpread2({}, DEFAULT_REALTIME_OPTIONS$1), realtimeOptions),
129
374
  storage: {},
130
375
  global: _objectSpread2(_objectSpread2(_objectSpread2({}, DEFAULT_GLOBAL_OPTIONS$1), globalOptions), {}, { headers: _objectSpread2(_objectSpread2({}, (_DEFAULT_GLOBAL_OPTIO = DEFAULT_GLOBAL_OPTIONS$1 === null || DEFAULT_GLOBAL_OPTIONS$1 === void 0 ? void 0 : DEFAULT_GLOBAL_OPTIONS$1.headers) !== null && _DEFAULT_GLOBAL_OPTIO !== void 0 ? _DEFAULT_GLOBAL_OPTIO : {}), (_globalOptions$header = globalOptions === null || globalOptions === void 0 ? void 0 : globalOptions.headers) !== null && _globalOptions$header !== void 0 ? _globalOptions$header : {}) }),
376
+ tracePropagation: {
377
+ enabled: (_ref = (_tracePropagationOpti = tracePropagationOptions === null || tracePropagationOptions === void 0 ? void 0 : tracePropagationOptions.enabled) !== null && _tracePropagationOpti !== void 0 ? _tracePropagationOpti : DEFAULT_TRACE_PROPAGATION_OPTIONS$1 === null || DEFAULT_TRACE_PROPAGATION_OPTIONS$1 === void 0 ? void 0 : DEFAULT_TRACE_PROPAGATION_OPTIONS$1.enabled) !== null && _ref !== void 0 ? _ref : false,
378
+ respectSamplingDecision: (_ref2 = (_tracePropagationOpti2 = tracePropagationOptions === null || tracePropagationOptions === void 0 ? void 0 : tracePropagationOptions.respectSamplingDecision) !== null && _tracePropagationOpti2 !== void 0 ? _tracePropagationOpti2 : DEFAULT_TRACE_PROPAGATION_OPTIONS$1 === null || DEFAULT_TRACE_PROPAGATION_OPTIONS$1 === void 0 ? void 0 : DEFAULT_TRACE_PROPAGATION_OPTIONS$1.respectSamplingDecision) !== null && _ref2 !== void 0 ? _ref2 : true
379
+ },
131
380
  accessToken: async () => ""
132
381
  };
133
382
  if (options.accessToken) result.accessToken = options.accessToken;
@@ -371,9 +620,11 @@ var SupabaseClient = class {
371
620
  db: DEFAULT_DB_OPTIONS,
372
621
  realtime: DEFAULT_REALTIME_OPTIONS,
373
622
  auth: _objectSpread2(_objectSpread2({}, DEFAULT_AUTH_OPTIONS), {}, { storageKey: defaultStorageKey }),
374
- global: DEFAULT_GLOBAL_OPTIONS
623
+ global: DEFAULT_GLOBAL_OPTIONS,
624
+ tracePropagation: DEFAULT_TRACE_PROPAGATION_OPTIONS
375
625
  };
376
626
  const settings = applySettingDefaults(options !== null && options !== void 0 ? options : {}, DEFAULTS);
627
+ this.settings = settings;
377
628
  this.storageKey = (_settings$auth$storag = settings.auth.storageKey) !== null && _settings$auth$storag !== void 0 ? _settings$auth$storag : "";
378
629
  this.headers = (_settings$global$head = settings.global.headers) !== null && _settings$global$head !== void 0 ? _settings$global$head : {};
379
630
  if (!settings.accessToken) {
@@ -385,7 +636,7 @@ var SupabaseClient = class {
385
636
  throw new Error(`@supabase/supabase-js: Supabase Client is configured with the accessToken option, accessing supabase.auth.${String(prop)} is not possible`);
386
637
  } });
387
638
  }
388
- this.fetch = fetchWithAuth(supabaseKey, this._getAccessToken.bind(this), settings.global.fetch);
639
+ this.fetch = fetchWithAuth(supabaseKey, supabaseUrl, this._getAccessToken.bind(this), settings.global.fetch, settings.tracePropagation);
389
640
  this.realtime = this._initRealtimeClient(_objectSpread2({
390
641
  headers: this.headers,
391
642
  accessToken: this._getAccessToken.bind(this),