@rpcbase/server 0.507.0 → 0.509.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.js CHANGED
@@ -4,7 +4,7 @@ import MongoStore from "connect-mongo";
4
4
  import { createClient } from "redis";
5
5
  import { MongoClient } from "mongodb";
6
6
  import env from "@rpcbase/env";
7
- import { initApiClient, SsrErrorFallback, SSR_ERROR_STATE_GLOBAL_KEY, serializeSsrErrorState } from "@rpcbase/client";
7
+ import { initApiClient, STATIC_RPCBASE_RTS_HYDRATION_DATA_KEY, RtsSsrRuntimeProvider, SsrErrorFallback, SSR_ERROR_STATE_GLOBAL_KEY, serializeSsrErrorState } from "@rpcbase/client";
8
8
  import { dirname, posix, sep } from "path";
9
9
  import fs, { createReadStream, readFileSync } from "node:fs";
10
10
  import { createInterface } from "node:readline";
@@ -16,11 +16,12 @@ import fsPromises from "node:fs/promises";
16
16
  import inspector from "node:inspector";
17
17
  import path from "node:path";
18
18
  import { fileURLToPath } from "node:url";
19
- import { Transform } from "node:stream";
19
+ import { Writable, Transform } from "node:stream";
20
20
  import { StrictMode, createElement } from "react";
21
21
  import { renderToPipeableStream, renderToStaticMarkup } from "react-dom/server";
22
22
  import { jsx } from "react/jsx-runtime";
23
23
  import { createPath, matchRoutes, parsePath, createStaticRouter, StaticRouterProvider } from "@rpcbase/router";
24
+ import { r as resolveRtsRequestTenantId, i as isRtsRequestAuthorized, b as buildRtsAbilityFromRequest, n as normalizeRtsQueryOptions, a as runRtsQuery } from "./queryExecutor-BZ0GSPM1.js";
24
25
  import { s } from "./email-DEw8keax.js";
25
26
  function getDefaultExportFromCjs(x) {
26
27
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
@@ -539,6 +540,8 @@ var types_PostHogPersistedProperty = /* @__PURE__ */ (function(PostHogPersistedP
539
540
  PostHogPersistedProperty["AnonymousId"] = "anonymous_id";
540
541
  PostHogPersistedProperty["DistinctId"] = "distinct_id";
541
542
  PostHogPersistedProperty["Props"] = "props";
543
+ PostHogPersistedProperty["EnablePersonProcessing"] = "enable_person_processing";
544
+ PostHogPersistedProperty["PersonMode"] = "person_mode";
542
545
  PostHogPersistedProperty["FeatureFlagDetails"] = "feature_flag_details";
543
546
  PostHogPersistedProperty["FeatureFlags"] = "feature_flags";
544
547
  PostHogPersistedProperty["FeatureFlagPayloads"] = "feature_flag_payloads";
@@ -573,7 +576,7 @@ const isObject = (x) => x === Object(x) && !isArray(x);
573
576
  const isUndefined = (x) => void 0 === x;
574
577
  const isString = (x) => "[object String]" == type_utils_toString.call(x);
575
578
  const isEmptyString = (x) => isString(x) && 0 === x.trim().length;
576
- const isNumber = (x) => "[object Number]" == type_utils_toString.call(x);
579
+ const isNumber = (x) => "[object Number]" == type_utils_toString.call(x) && x === x;
577
580
  const isPlainError = (x) => x instanceof Error;
578
581
  function isPrimitive(value) {
579
582
  return null === value || "object" != typeof value;
@@ -855,10 +858,11 @@ class PostHogCoreStateless {
855
858
  this.disableGeoip = options.disableGeoip ?? true;
856
859
  this.disabled = options.disabled ?? false;
857
860
  this.historicalMigration = options?.historicalMigration ?? false;
858
- this.evaluationEnvironments = options?.evaluationEnvironments;
859
861
  this._initPromise = Promise.resolve();
860
862
  this._isInitialized = true;
861
863
  this._logger = createLogger("[PostHog]", this.logMsgIfDebug.bind(this));
864
+ this.evaluationContexts = options?.evaluationContexts ?? options?.evaluationEnvironments;
865
+ if (options?.evaluationEnvironments && !options?.evaluationContexts) this._logger.warn("evaluationEnvironments is deprecated. Use evaluationContexts instead. This property will be removed in a future version.");
862
866
  this.disableCompression = !isGzipSupported() || (options?.disableCompression ?? false);
863
867
  }
864
868
  logMsgIfDebug(fn) {
@@ -1033,7 +1037,7 @@ class PostHogCoreStateless {
1033
1037
  group_properties: groupProperties,
1034
1038
  ...extraPayload
1035
1039
  };
1036
- if (this.evaluationEnvironments && this.evaluationEnvironments.length > 0) requestData.evaluation_environments = this.evaluationEnvironments;
1040
+ if (this.evaluationContexts && this.evaluationContexts.length > 0) requestData.evaluation_contexts = this.evaluationContexts;
1037
1041
  const fetchOptions = {
1038
1042
  method: "POST",
1039
1043
  headers: {
@@ -1045,10 +1049,35 @@ class PostHogCoreStateless {
1045
1049
  this._logger.info("Flags URL", url);
1046
1050
  return this.fetchWithRetry(url, fetchOptions, {
1047
1051
  retryCount: 0
1048
- }, this.featureFlagsRequestTimeoutMs).then((response) => response.json()).then((response) => normalizeFlagsResponse(response)).catch((error) => {
1052
+ }, this.featureFlagsRequestTimeoutMs).then((response) => response.json()).then((response) => ({
1053
+ success: true,
1054
+ response: normalizeFlagsResponse(response)
1055
+ })).catch((error) => {
1049
1056
  this._events.emit("error", error);
1057
+ return {
1058
+ success: false,
1059
+ error: this.categorizeRequestError(error)
1060
+ };
1050
1061
  });
1051
1062
  }
1063
+ categorizeRequestError(error) {
1064
+ if (error instanceof PostHogFetchHttpError) return {
1065
+ type: "api_error",
1066
+ statusCode: error.status
1067
+ };
1068
+ if (error instanceof PostHogFetchNetworkError) {
1069
+ const cause = error.error;
1070
+ if (cause instanceof Error && ("AbortError" === cause.name || "TimeoutError" === cause.name)) return {
1071
+ type: "timeout"
1072
+ };
1073
+ return {
1074
+ type: "connection_error"
1075
+ };
1076
+ }
1077
+ return {
1078
+ type: "unknown_error"
1079
+ };
1080
+ }
1052
1081
  async getFeatureFlagStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1053
1082
  await this._initPromise;
1054
1083
  const flagDetailResponse = await this.getFeatureFlagDetailStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
@@ -1115,8 +1144,9 @@ class PostHogCoreStateless {
1115
1144
  const extraPayload = {};
1116
1145
  if (disableGeoip ?? this.disableGeoip) extraPayload["geoip_disable"] = true;
1117
1146
  if (flagKeysToEvaluate) extraPayload["flag_keys_to_evaluate"] = flagKeysToEvaluate;
1118
- const flagsResponse = await this.getFlags(distinctId, groups, personProperties, groupProperties, extraPayload);
1119
- if (void 0 === flagsResponse) return;
1147
+ const result = await this.getFlags(distinctId, groups, personProperties, groupProperties, extraPayload);
1148
+ if (!result.success) return;
1149
+ const flagsResponse = result.response;
1120
1150
  if (flagsResponse.errorsWhileComputingFlags) console.error("[FEATURE FLAGS] Error while computing feature flags, some flags may be missing or incorrect. Learn more at https://posthog.com/docs/feature-flags/best-practices");
1121
1151
  if (flagsResponse.quotaLimited?.includes("feature_flags")) {
1122
1152
  console.warn("[FEATURE FLAGS] Feature flags quota limit exceeded - feature flags unavailable. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts");
@@ -1183,10 +1213,17 @@ class PostHogCoreStateless {
1183
1213
  this.setPersistedProperty(types_PostHogPersistedProperty.Props, this.props);
1184
1214
  });
1185
1215
  }
1216
+ processBeforeEnqueue(message) {
1217
+ return message;
1218
+ }
1219
+ async flushStorage() {
1220
+ }
1186
1221
  enqueue(type, _message, options) {
1187
1222
  this.wrap(() => {
1188
1223
  if (this.optedOut) return void this._events.emit(type, "Library is disabled. Not sending event. To re-enable, call posthog.optIn()");
1189
- const message = this.prepareMessage(type, _message, options);
1224
+ let message = this.prepareMessage(type, _message, options);
1225
+ message = this.processBeforeEnqueue(message);
1226
+ if (null === message) return;
1190
1227
  const queue = this.getPersistedProperty(types_PostHogPersistedProperty.Queue) || [];
1191
1228
  if (queue.length >= this.maxQueueSize) {
1192
1229
  queue.shift();
@@ -1205,10 +1242,13 @@ class PostHogCoreStateless {
1205
1242
  if (this.disabled) return void this._logger.warn("The client is disabled");
1206
1243
  if (!this._isInitialized) await this._initPromise;
1207
1244
  if (this.optedOut) return void this._events.emit(type, "Library is disabled. Not sending event. To re-enable, call posthog.optIn()");
1245
+ let message = this.prepareMessage(type, _message, options);
1246
+ message = this.processBeforeEnqueue(message);
1247
+ if (null === message) return;
1208
1248
  const data = {
1209
1249
  api_key: this.apiKey,
1210
1250
  batch: [
1211
- this.prepareMessage(type, _message, options)
1251
+ message
1212
1252
  ],
1213
1253
  sent_at: currentISOTime()
1214
1254
  };
@@ -1293,11 +1333,12 @@ class PostHogCoreStateless {
1293
1333
  while (queue.length > 0 && sentMessages.length < originalQueueLength) {
1294
1334
  const batchItems = queue.slice(0, this.maxBatchSize);
1295
1335
  const batchMessages = batchItems.map((item) => item.message);
1296
- const persistQueueChange = () => {
1336
+ const persistQueueChange = async () => {
1297
1337
  const refreshedQueue = this.getPersistedProperty(types_PostHogPersistedProperty.Queue) || [];
1298
1338
  const newQueue = refreshedQueue.slice(batchItems.length);
1299
1339
  this.setPersistedProperty(types_PostHogPersistedProperty.Queue, newQueue);
1300
1340
  queue = newQueue;
1341
+ await this.flushStorage();
1301
1342
  };
1302
1343
  const data = {
1303
1344
  api_key: this.apiKey,
@@ -1333,11 +1374,11 @@ class PostHogCoreStateless {
1333
1374
  this._logger.warn(`Received 413 when sending batch of size ${batchMessages.length}, reducing batch size to ${this.maxBatchSize}`);
1334
1375
  continue;
1335
1376
  }
1336
- if (!(err instanceof PostHogFetchNetworkError)) persistQueueChange();
1377
+ if (!(err instanceof PostHogFetchNetworkError)) await persistQueueChange();
1337
1378
  this._events.emit("error", err);
1338
1379
  throw err;
1339
1380
  }
1340
- persistQueueChange();
1381
+ await persistQueueChange();
1341
1382
  sentMessages.push(...batchMessages);
1342
1383
  }
1343
1384
  this._events.emit("flush", sentMessages);
@@ -1462,7 +1503,7 @@ class ErrorPropertiesBuilder {
1462
1503
  };
1463
1504
  const coercingContext = this.buildCoercingContext(mechanism, hint, 0);
1464
1505
  const exceptionWithCause = coercingContext.apply(input);
1465
- const parsingContext = this.buildParsingContext();
1506
+ const parsingContext = this.buildParsingContext(hint);
1466
1507
  const exceptionWithStack = this.parseStacktrace(exceptionWithCause, parsingContext);
1467
1508
  const exceptionList = this.convertToExceptionList(exceptionWithStack, mechanism);
1468
1509
  return {
@@ -1486,7 +1527,7 @@ class ErrorPropertiesBuilder {
1486
1527
  let cause;
1487
1528
  if (null != err.cause) cause = this.parseStacktrace(err.cause, ctx);
1488
1529
  let stack;
1489
- if ("" != err.stack && null != err.stack) stack = this.applyChunkIds(this.stackParser(err.stack, err.synthetic ? 1 : 0), ctx.chunkIdMap);
1530
+ if ("" != err.stack && null != err.stack) stack = this.applyChunkIds(this.stackParser(err.stack, err.synthetic ? ctx.skipFirstLines : 0), ctx.chunkIdMap);
1490
1531
  return {
1491
1532
  ...err,
1492
1533
  cause,
@@ -1531,9 +1572,10 @@ class ErrorPropertiesBuilder {
1531
1572
  }));
1532
1573
  return exceptionList;
1533
1574
  }
1534
- buildParsingContext() {
1575
+ buildParsingContext(hint) {
1535
1576
  const context = {
1536
- chunkIdMap: getFilenameToChunkIdMap(this.stackParser)
1577
+ chunkIdMap: getFilenameToChunkIdMap(this.stackParser),
1578
+ skipFirstLines: hint.skipFirstLines ?? 1
1537
1579
  };
1538
1580
  return context;
1539
1581
  }
@@ -2105,7 +2147,7 @@ class ErrorTracking {
2105
2147
  this._rateLimiter.stop();
2106
2148
  }
2107
2149
  }
2108
- const version = "5.20.0";
2150
+ const version = "5.24.11";
2109
2151
  const FeatureFlagError = {
2110
2152
  ERRORS_WHILE_COMPUTING: "errors_while_computing_flags",
2111
2153
  FLAG_MISSING: "flag_missing",
@@ -2170,6 +2212,7 @@ class FeatureFlagsPoller {
2170
2212
  this.customHeaders = customHeaders;
2171
2213
  this.onLoad = options.onLoad;
2172
2214
  this.cacheProvider = options.cacheProvider;
2215
+ this.strictLocalEvaluation = options.strictLocalEvaluation ?? false;
2173
2216
  this.loadFeatureFlags();
2174
2217
  }
2175
2218
  debug(enabled = true) {
@@ -2365,6 +2408,11 @@ class FeatureFlagsPoller {
2365
2408
  this.cohorts = flagData.cohorts;
2366
2409
  this.loadedSuccessfullyOnce = true;
2367
2410
  }
2411
+ warnAboutExperienceContinuityFlags(flags) {
2412
+ if (this.strictLocalEvaluation) return;
2413
+ const experienceContinuityFlags = flags.filter((f) => f.ensure_experience_continuity);
2414
+ if (experienceContinuityFlags.length > 0) console.warn(`[PostHog] You are using local evaluation but ${experienceContinuityFlags.length} flag(s) have experience continuity enabled: ${experienceContinuityFlags.map((f) => f.key).join(", ")}. Experience continuity is incompatible with local evaluation and will cause a server request on every flag evaluation, negating local evaluation cost savings. To avoid server requests and unexpected costs, either disable experience continuity on these flags in PostHog, use strictLocalEvaluation: true in client init, or pass onlyEvaluateLocally: true per flag call (flags that cannot be evaluated locally will return undefined).`);
2415
+ }
2368
2416
  async loadFromCache(debugMessage) {
2369
2417
  if (!this.cacheProvider) return false;
2370
2418
  try {
@@ -2373,6 +2421,7 @@ class FeatureFlagsPoller {
2373
2421
  this.updateFlagState(cached);
2374
2422
  this.logMsgIfDebug(() => console.debug(`[FEATURE FLAGS] ${debugMessage} (${cached.flags.length} flags)`));
2375
2423
  this.onLoad?.(this.featureFlags.length);
2424
+ this.warnAboutExperienceContinuityFlags(cached.flags);
2376
2425
  return true;
2377
2426
  }
2378
2427
  return false;
@@ -2466,6 +2515,7 @@ class FeatureFlagsPoller {
2466
2515
  this.onError?.(new Error(`Failed to store in cache: ${err}`));
2467
2516
  }
2468
2517
  this.onLoad?.(this.featureFlags.length);
2518
+ this.warnAboutExperienceContinuityFlags(flagData.flags);
2469
2519
  break;
2470
2520
  }
2471
2521
  default:
@@ -2720,7 +2770,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
2720
2770
  this._events.emit("localEvaluationFlagsLoaded", count);
2721
2771
  },
2722
2772
  customHeaders: this.getCustomHeaders(),
2723
- cacheProvider: options.flagDefinitionCacheProvider
2773
+ cacheProvider: options.flagDefinitionCacheProvider,
2774
+ strictLocalEvaluation: options.strictLocalEvaluation
2724
2775
  });
2725
2776
  }
2726
2777
  this.errorTracking = new ErrorTracking(this, options, this._logger);
@@ -2826,21 +2877,56 @@ class PostHogBackendClient extends PostHogCoreStateless {
2826
2877
  });
2827
2878
  });
2828
2879
  }
2829
- async getFeatureFlag(key, distinctId, options) {
2830
- if (void 0 !== this._flagOverrides && key in this._flagOverrides) return this._flagOverrides[key];
2831
- const { groups, disableGeoip } = options || {};
2832
- let { onlyEvaluateLocally, sendFeatureFlagEvents, personProperties, groupProperties } = options || {};
2880
+ async _getFeatureFlagResult(key, distinctId, options = {}, matchValue) {
2881
+ const sendFeatureFlagEvents = options.sendFeatureFlagEvents ?? true;
2882
+ if (void 0 !== this._flagOverrides && key in this._flagOverrides) {
2883
+ const overrideValue = this._flagOverrides[key];
2884
+ if (void 0 === overrideValue) return;
2885
+ const overridePayload = this._payloadOverrides?.[key];
2886
+ return {
2887
+ key,
2888
+ enabled: false !== overrideValue,
2889
+ variant: "string" == typeof overrideValue ? overrideValue : void 0,
2890
+ payload: overridePayload
2891
+ };
2892
+ }
2893
+ const { groups, disableGeoip } = options;
2894
+ let { onlyEvaluateLocally, personProperties, groupProperties } = options;
2833
2895
  const adjustedProperties = this.addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties);
2834
2896
  personProperties = adjustedProperties.allPersonProperties;
2835
2897
  groupProperties = adjustedProperties.allGroupProperties;
2836
- if (void 0 == onlyEvaluateLocally) onlyEvaluateLocally = false;
2837
- if (void 0 == sendFeatureFlagEvents) sendFeatureFlagEvents = this.options.sendFeatureFlagEvent ?? true;
2838
- let response = await this.featureFlagsPoller?.getFeatureFlag(key, distinctId, groups, personProperties, groupProperties);
2839
- const flagWasLocallyEvaluated = void 0 !== response;
2898
+ if (void 0 == onlyEvaluateLocally) onlyEvaluateLocally = this.options.strictLocalEvaluation ?? false;
2899
+ let result;
2900
+ let flagWasLocallyEvaluated = false;
2840
2901
  let requestId;
2841
2902
  let evaluatedAt;
2842
- let flagDetail;
2843
2903
  let featureFlagError;
2904
+ let flagId;
2905
+ let flagVersion;
2906
+ let flagReason;
2907
+ const localEvaluationEnabled = void 0 !== this.featureFlagsPoller;
2908
+ if (localEvaluationEnabled) {
2909
+ await this.featureFlagsPoller?.loadFeatureFlags();
2910
+ const flag = this.featureFlagsPoller?.featureFlagsByKey[key];
2911
+ if (flag) try {
2912
+ const localResult = await this.featureFlagsPoller?.computeFlagAndPayloadLocally(flag, distinctId, groups, personProperties, groupProperties, matchValue);
2913
+ if (localResult) {
2914
+ flagWasLocallyEvaluated = true;
2915
+ const value = localResult.value;
2916
+ flagId = flag.id;
2917
+ flagReason = "Evaluated locally";
2918
+ result = {
2919
+ key,
2920
+ enabled: false !== value,
2921
+ variant: "string" == typeof value ? value : void 0,
2922
+ payload: localResult.payload ?? void 0
2923
+ };
2924
+ }
2925
+ } catch (e) {
2926
+ if (e instanceof RequiresServerEvaluation || e instanceof InconclusiveMatchError) this._logger?.info(`${e.name} when computing flag locally: ${key}: ${e.message}`);
2927
+ else throw e;
2928
+ }
2929
+ }
2844
2930
  if (!flagWasLocallyEvaluated && !onlyEvaluateLocally) {
2845
2931
  const flagsResponse = await super.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [
2846
2932
  key
@@ -2852,68 +2938,87 @@ class PostHogBackendClient extends PostHogCoreStateless {
2852
2938
  const errors = [];
2853
2939
  if (flagsResponse.errorsWhileComputingFlags) errors.push(FeatureFlagError.ERRORS_WHILE_COMPUTING);
2854
2940
  if (flagsResponse.quotaLimited?.includes("feature_flags")) errors.push(FeatureFlagError.QUOTA_LIMITED);
2855
- flagDetail = flagsResponse.flags[key];
2941
+ const flagDetail = flagsResponse.flags[key];
2856
2942
  if (void 0 === flagDetail) errors.push(FeatureFlagError.FLAG_MISSING);
2943
+ else {
2944
+ flagId = flagDetail.metadata?.id;
2945
+ flagVersion = flagDetail.metadata?.version;
2946
+ flagReason = flagDetail.reason?.description ?? flagDetail.reason?.code;
2947
+ let parsedPayload;
2948
+ if (flagDetail.metadata?.payload !== void 0) try {
2949
+ parsedPayload = JSON.parse(flagDetail.metadata.payload);
2950
+ } catch {
2951
+ parsedPayload = flagDetail.metadata.payload;
2952
+ }
2953
+ result = {
2954
+ key,
2955
+ enabled: flagDetail.enabled,
2956
+ variant: flagDetail.variant,
2957
+ payload: parsedPayload
2958
+ };
2959
+ }
2857
2960
  if (errors.length > 0) featureFlagError = errors.join(",");
2858
- response = getFeatureFlagValue(flagDetail);
2859
2961
  }
2860
2962
  }
2861
- const featureFlagReportedKey = `${key}_${response}`;
2862
- if (sendFeatureFlagEvents && (!(distinctId in this.distinctIdHasSentFlagCalls) || !this.distinctIdHasSentFlagCalls[distinctId].includes(featureFlagReportedKey))) {
2863
- if (Object.keys(this.distinctIdHasSentFlagCalls).length >= this.maxCacheSize) this.distinctIdHasSentFlagCalls = {};
2864
- if (Array.isArray(this.distinctIdHasSentFlagCalls[distinctId])) this.distinctIdHasSentFlagCalls[distinctId].push(featureFlagReportedKey);
2865
- else this.distinctIdHasSentFlagCalls[distinctId] = [
2866
- featureFlagReportedKey
2867
- ];
2868
- const properties = {
2869
- $feature_flag: key,
2870
- $feature_flag_response: response,
2871
- $feature_flag_id: flagDetail?.metadata?.id,
2872
- $feature_flag_version: flagDetail?.metadata?.version,
2873
- $feature_flag_reason: flagDetail?.reason?.description ?? flagDetail?.reason?.code,
2874
- locally_evaluated: flagWasLocallyEvaluated,
2875
- [`$feature/${key}`]: response,
2876
- $feature_flag_request_id: requestId,
2877
- $feature_flag_evaluated_at: evaluatedAt
2878
- };
2879
- if (featureFlagError) properties.$feature_flag_error = featureFlagError;
2880
- this.capture({
2881
- distinctId,
2882
- event: "$feature_flag_called",
2883
- properties,
2884
- groups,
2885
- disableGeoip
2886
- });
2963
+ if (sendFeatureFlagEvents) {
2964
+ const response = void 0 === result ? void 0 : false === result.enabled ? false : result.variant ?? true;
2965
+ const featureFlagReportedKey = `${key}_${response}`;
2966
+ if (!(distinctId in this.distinctIdHasSentFlagCalls) || !this.distinctIdHasSentFlagCalls[distinctId].includes(featureFlagReportedKey)) {
2967
+ if (Object.keys(this.distinctIdHasSentFlagCalls).length >= this.maxCacheSize) this.distinctIdHasSentFlagCalls = {};
2968
+ if (Array.isArray(this.distinctIdHasSentFlagCalls[distinctId])) this.distinctIdHasSentFlagCalls[distinctId].push(featureFlagReportedKey);
2969
+ else this.distinctIdHasSentFlagCalls[distinctId] = [
2970
+ featureFlagReportedKey
2971
+ ];
2972
+ const properties = {
2973
+ $feature_flag: key,
2974
+ $feature_flag_response: response,
2975
+ $feature_flag_id: flagId,
2976
+ $feature_flag_version: flagVersion,
2977
+ $feature_flag_reason: flagReason,
2978
+ locally_evaluated: flagWasLocallyEvaluated,
2979
+ [`$feature/${key}`]: response,
2980
+ $feature_flag_request_id: requestId,
2981
+ $feature_flag_evaluated_at: evaluatedAt
2982
+ };
2983
+ if (featureFlagError) properties.$feature_flag_error = featureFlagError;
2984
+ this.capture({
2985
+ distinctId,
2986
+ event: "$feature_flag_called",
2987
+ properties,
2988
+ groups,
2989
+ disableGeoip
2990
+ });
2991
+ }
2887
2992
  }
2888
- return response;
2993
+ if (void 0 !== result && void 0 !== this._payloadOverrides && key in this._payloadOverrides) result = {
2994
+ ...result,
2995
+ payload: this._payloadOverrides[key]
2996
+ };
2997
+ return result;
2998
+ }
2999
+ async getFeatureFlag(key, distinctId, options) {
3000
+ const result = await this._getFeatureFlagResult(key, distinctId, {
3001
+ ...options,
3002
+ sendFeatureFlagEvents: options?.sendFeatureFlagEvents ?? this.options.sendFeatureFlagEvent ?? true
3003
+ });
3004
+ if (void 0 === result) return;
3005
+ if (false === result.enabled) return false;
3006
+ return result.variant ?? true;
2889
3007
  }
2890
3008
  async getFeatureFlagPayload(key, distinctId, matchValue, options) {
2891
3009
  if (void 0 !== this._payloadOverrides && key in this._payloadOverrides) return this._payloadOverrides[key];
2892
- const { groups, disableGeoip } = options || {};
2893
- let { onlyEvaluateLocally, personProperties, groupProperties } = options || {};
2894
- const adjustedProperties = this.addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties);
2895
- personProperties = adjustedProperties.allPersonProperties;
2896
- groupProperties = adjustedProperties.allGroupProperties;
2897
- let response;
2898
- const localEvaluationEnabled = void 0 !== this.featureFlagsPoller;
2899
- if (localEvaluationEnabled) {
2900
- await this.featureFlagsPoller?.loadFeatureFlags();
2901
- const flag = this.featureFlagsPoller?.featureFlagsByKey[key];
2902
- if (flag) try {
2903
- const result = await this.featureFlagsPoller?.computeFlagAndPayloadLocally(flag, distinctId, groups, personProperties, groupProperties, matchValue);
2904
- if (result) {
2905
- matchValue = result.value;
2906
- response = result.payload;
2907
- }
2908
- } catch (e) {
2909
- if (e instanceof RequiresServerEvaluation || e instanceof InconclusiveMatchError) this._logger?.info(`${e.name} when computing flag locally: ${flag.key}: ${e.message}`);
2910
- else throw e;
2911
- }
2912
- }
2913
- if (void 0 == onlyEvaluateLocally) onlyEvaluateLocally = false;
2914
- const payloadWasLocallyEvaluated = void 0 !== response;
2915
- if (!payloadWasLocallyEvaluated && !onlyEvaluateLocally) response = await super.getFeatureFlagPayloadStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
2916
- return response;
3010
+ const result = await this._getFeatureFlagResult(key, distinctId, {
3011
+ ...options,
3012
+ sendFeatureFlagEvents: false
3013
+ }, matchValue);
3014
+ if (void 0 === result) return;
3015
+ return result.payload ?? null;
3016
+ }
3017
+ async getFeatureFlagResult(key, distinctId, options) {
3018
+ return this._getFeatureFlagResult(key, distinctId, {
3019
+ ...options,
3020
+ sendFeatureFlagEvents: options?.sendFeatureFlagEvents ?? this.options.sendFeatureFlagEvent ?? true
3021
+ });
2917
3022
  }
2918
3023
  async getRemoteConfigPayload(flagKey) {
2919
3024
  if (!this.options.personalApiKey) throw new Error("Personal API key is required for remote config payload decryption");
@@ -2941,7 +3046,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
2941
3046
  const adjustedProperties = this.addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties);
2942
3047
  personProperties = adjustedProperties.allPersonProperties;
2943
3048
  groupProperties = adjustedProperties.allGroupProperties;
2944
- if (void 0 == onlyEvaluateLocally) onlyEvaluateLocally = false;
3049
+ if (void 0 == onlyEvaluateLocally) onlyEvaluateLocally = this.options.strictLocalEvaluation ?? false;
2945
3050
  const localEvaluationResult = await this.featureFlagsPoller?.getAllFlagsAndPayloads(distinctId, groups, personProperties, groupProperties, flagKeys);
2946
3051
  let featureFlags = {};
2947
3052
  let featureFlagPayloads = {};
@@ -3091,7 +3196,7 @@ class PostHogBackendClient extends PostHogCoreStateless {
3091
3196
  const finalPersonProperties = sendFeatureFlagsOptions?.personProperties || {};
3092
3197
  const finalGroupProperties = sendFeatureFlagsOptions?.groupProperties || {};
3093
3198
  const flagKeys = sendFeatureFlagsOptions?.flagKeys;
3094
- const onlyEvaluateLocally = sendFeatureFlagsOptions?.onlyEvaluateLocally ?? false;
3199
+ const onlyEvaluateLocally = sendFeatureFlagsOptions?.onlyEvaluateLocally ?? this.options.strictLocalEvaluation ?? false;
3095
3200
  if (onlyEvaluateLocally) if (!((this.featureFlagsPoller?.featureFlags?.length || 0) > 0)) return {};
3096
3201
  else {
3097
3202
  const groupsWithStringValues = {};
@@ -4319,6 +4424,142 @@ async function applyRouteLoaders(req, dataRoutes) {
4319
4424
  statusCode
4320
4425
  };
4321
4426
  }
4427
+ const DEFAULT_MAX_QUERIES = 32;
4428
+ const DEFAULT_MAX_DOCS_PER_QUERY = 500;
4429
+ const DEFAULT_MAX_SERIALIZED_BYTES = 200 * 1024;
4430
+ const makeRegistrationKey = (modelName, queryKey) => `${modelName}.${queryKey}`;
4431
+ const hasSessionUser = (req) => {
4432
+ const id = req.session?.user?.id;
4433
+ return typeof id === "string" && id.trim().length > 0;
4434
+ };
4435
+ const escapeForInlineScript = (value) => {
4436
+ return value.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
4437
+ };
4438
+ const createRtsSsrCollector = (req, opts) => {
4439
+ const maxQueries = DEFAULT_MAX_QUERIES;
4440
+ const maxDocsPerQuery = DEFAULT_MAX_DOCS_PER_QUERY;
4441
+ const maxSerializedBytes = DEFAULT_MAX_SERIALIZED_BYTES;
4442
+ const registrations = /* @__PURE__ */ new Map();
4443
+ const resolved = /* @__PURE__ */ new Map();
4444
+ const runtime = {
4445
+ registerQuery(query) {
4446
+ const modelName = typeof query.modelName === "string" ? query.modelName.trim() : "";
4447
+ const queryKey = typeof query.queryKey === "string" ? query.queryKey.trim() : "";
4448
+ if (!modelName || !queryKey) return;
4449
+ const key = makeRegistrationKey(modelName, queryKey);
4450
+ if (registrations.has(key)) return;
4451
+ if (registrations.size >= maxQueries) return;
4452
+ registrations.set(key, {
4453
+ modelName,
4454
+ queryKey,
4455
+ query: query.query ?? {},
4456
+ options: query.options ?? {}
4457
+ });
4458
+ },
4459
+ getQueryData(modelName, queryKey) {
4460
+ return resolved.get(makeRegistrationKey(modelName, queryKey));
4461
+ }
4462
+ };
4463
+ const resolve = async () => {
4464
+ if (!registrations.size) return null;
4465
+ const tenantId = resolveRtsRequestTenantId(req);
4466
+ if (!tenantId) return null;
4467
+ if (hasSessionUser(req) && !isRtsRequestAuthorized(req, tenantId)) {
4468
+ return null;
4469
+ }
4470
+ const { ability, userId } = await buildRtsAbilityFromRequest(req, tenantId);
4471
+ const queryEntries = [];
4472
+ for (const registration of registrations.values()) {
4473
+ const options = normalizeRtsQueryOptions(registration.options);
4474
+ try {
4475
+ const data = await runRtsQuery({
4476
+ tenantId,
4477
+ ability,
4478
+ modelName: registration.modelName,
4479
+ query: registration.query,
4480
+ options
4481
+ });
4482
+ const boundedData = maxDocsPerQuery > 0 ? data.slice(0, maxDocsPerQuery) : data;
4483
+ queryEntries.push({
4484
+ modelName: registration.modelName,
4485
+ queryKey: registration.queryKey,
4486
+ data: boundedData
4487
+ });
4488
+ } catch {
4489
+ continue;
4490
+ }
4491
+ }
4492
+ if (!queryEntries.length) return null;
4493
+ const payload = {
4494
+ v: 1,
4495
+ tenantId,
4496
+ uid: userId,
4497
+ queries: []
4498
+ };
4499
+ for (const entry of queryEntries) {
4500
+ payload.queries.push(entry);
4501
+ const bytes = Buffer.byteLength(JSON.stringify(payload), "utf8");
4502
+ if (bytes > maxSerializedBytes) {
4503
+ payload.queries.pop();
4504
+ break;
4505
+ }
4506
+ }
4507
+ resolved.clear();
4508
+ for (const entry of payload.queries) {
4509
+ resolved.set(makeRegistrationKey(entry.modelName, entry.queryKey), entry.data);
4510
+ }
4511
+ return payload.queries.length ? payload : null;
4512
+ };
4513
+ const hasRegistrations = () => registrations.size > 0;
4514
+ return { runtime, hasRegistrations, resolve };
4515
+ };
4516
+ const renderRtsHydrationScript = (data) => {
4517
+ if (!data) return "";
4518
+ const serialized = escapeForInlineScript(JSON.stringify(data));
4519
+ return `<script>window.${STATIC_RPCBASE_RTS_HYDRATION_DATA_KEY}=${serialized};<\/script>`;
4520
+ };
4521
+ const RTS_SSR_PREPASS_TIMEOUT_MS = 4e3;
4522
+ const runRtsPrepass = async (element) => {
4523
+ return await new Promise((resolve) => {
4524
+ let isDone = false;
4525
+ let timeoutId = null;
4526
+ const finish = (ok) => {
4527
+ if (isDone) return;
4528
+ isDone = true;
4529
+ if (timeoutId) {
4530
+ clearTimeout(timeoutId);
4531
+ }
4532
+ resolve(ok);
4533
+ };
4534
+ const sink = new Writable({
4535
+ write(_chunk, _encoding, callback) {
4536
+ callback();
4537
+ }
4538
+ });
4539
+ sink.on("finish", () => {
4540
+ finish(true);
4541
+ });
4542
+ sink.on("error", () => {
4543
+ finish(false);
4544
+ });
4545
+ const { pipe, abort } = renderToPipeableStream(element, {
4546
+ onAllReady() {
4547
+ if (isDone) return;
4548
+ pipe(sink);
4549
+ },
4550
+ onShellError() {
4551
+ finish(false);
4552
+ abort();
4553
+ },
4554
+ onError() {
4555
+ }
4556
+ });
4557
+ timeoutId = setTimeout(() => {
4558
+ abort();
4559
+ finish(false);
4560
+ }, RTS_SSR_PREPASS_TIMEOUT_MS);
4561
+ });
4562
+ };
4322
4563
  async function renderSSR(req, dataRoutes) {
4323
4564
  const routerContext = await applyRouteLoaders(req, dataRoutes);
4324
4565
  const isMatched = routerContext.matches.length > 0;
@@ -4329,7 +4570,8 @@ async function renderSSR(req, dataRoutes) {
4329
4570
  statusCode: routerContext.statusCode ?? routerContext.redirectResponse.status ?? 302,
4330
4571
  redirectResponse: routerContext.redirectResponse,
4331
4572
  redirectRouteId: routerContext.redirectRouteId,
4332
- redirectRoutePath: routerContext.redirectRoutePath
4573
+ redirectRoutePath: routerContext.redirectRoutePath,
4574
+ rtsHydrationData: null
4333
4575
  };
4334
4576
  }
4335
4577
  if (routerContext.errors) {
@@ -4364,18 +4606,34 @@ async function renderSSR(req, dataRoutes) {
4364
4606
  throw error;
4365
4607
  }
4366
4608
  }
4367
- const router = createStaticRouter(dataRoutes, routerContext);
4368
- const element = /* @__PURE__ */ jsx(StrictMode, { children: /* @__PURE__ */ jsx(
4369
- StaticRouterProvider,
4370
- {
4371
- router,
4372
- context: routerContext
4609
+ const buildElement = (runtime2) => {
4610
+ const router = createStaticRouter(dataRoutes, routerContext);
4611
+ const app = /* @__PURE__ */ jsx(
4612
+ StaticRouterProvider,
4613
+ {
4614
+ router,
4615
+ context: routerContext
4616
+ }
4617
+ );
4618
+ return /* @__PURE__ */ jsx(StrictMode, { children: runtime2 ? /* @__PURE__ */ jsx(RtsSsrRuntimeProvider, { value: runtime2, children: app }) : app });
4619
+ };
4620
+ const tenantId = resolveRtsRequestTenantId(req);
4621
+ let rtsHydrationData = null;
4622
+ let runtime = null;
4623
+ if (tenantId) {
4624
+ const collector = createRtsSsrCollector(req);
4625
+ const prepassOk = await runRtsPrepass(buildElement(collector.runtime));
4626
+ if (prepassOk && collector.hasRegistrations()) {
4627
+ rtsHydrationData = await collector.resolve();
4373
4628
  }
4374
- ) });
4629
+ runtime = collector.runtime;
4630
+ }
4631
+ const element = buildElement(runtime);
4375
4632
  return {
4376
4633
  element,
4377
4634
  isMatched,
4378
- statusCode: routerContext.statusCode ?? (routerContext.errors ? 500 : 200)
4635
+ statusCode: routerContext.statusCode ?? (routerContext.errors ? 500 : 200),
4636
+ rtsHydrationData
4379
4637
  };
4380
4638
  }
4381
4639
  const ABORT_DELAY_MS = 1e4;
@@ -4526,6 +4784,7 @@ const ssrMiddleware = ({
4526
4784
  element,
4527
4785
  isMatched,
4528
4786
  statusCode,
4787
+ rtsHydrationData,
4529
4788
  redirectResponse,
4530
4789
  redirectRouteId,
4531
4790
  redirectRoutePath
@@ -4593,9 +4852,10 @@ const ssrMiddleware = ({
4593
4852
  });
4594
4853
  const start = htmlStart;
4595
4854
  const end = htmlEnd;
4855
+ const rtsHydrationScript = renderRtsHydrationScript(rtsHydrationData);
4596
4856
  res.write(start);
4597
4857
  transformStream.on("finish", () => {
4598
- res.end(end);
4858
+ res.end(`${rtsHydrationScript}${end}`);
4599
4859
  });
4600
4860
  pipe(transformStream);
4601
4861
  }