@usearete/sdk 0.32.0 → 0.33.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/README.md CHANGED
@@ -25,6 +25,32 @@ for await (const item of session.stacks.myStack.views.MyEntity.list.use()) {
25
25
  }
26
26
  ```
27
27
 
28
+ ## Authentication
29
+
30
+ Pick the key by where the code runs:
31
+
32
+ ```ts
33
+ // Servers, agents and local scripts (Node, Bun, Deno, workers, SSR):
34
+ // an agent key (a4_ak_...) or secret key (a4_sk_...).
35
+ const server = await createSession(
36
+ { stacks: { myStack: MY_STACK } },
37
+ { auth: { secretKey: process.env.ARETE_API_KEY } },
38
+ );
39
+
40
+ // Anything shipped to a browser: an origin-bound publishable key (a4_pk_...).
41
+ // Create one with `a4 auth keys create-publishable --origin <scheme://host[:port]>`.
42
+ const browser = await createSession(
43
+ { stacks: { myStack: MY_STACK } },
44
+ { auth: { publishableKey: import.meta.env.VITE_ARETE_PUBLISHABLE_KEY } },
45
+ );
46
+ ```
47
+
48
+ Outside a browser, when no `auth` option is set, the SDK reads `ARETE_API_KEY`
49
+ itself, so `createSession({ stacks })` is enough on a server with that variable
50
+ set. `secretKey` throws in a browser, as does a secret or agent key passed as
51
+ `publishableKey`; a publishable key passed as `secretKey` is refused everywhere.
52
+ Keys are never included in error messages or logs.
53
+
28
54
  ## SDK Shapes
29
55
 
30
56
  ### Mental Model
package/dist/index.cjs CHANGED
@@ -1043,6 +1043,124 @@ const AUTH_ERROR_CODES_BY_WIRE = {
1043
1043
  'stack-version-unknown': 'STACK_VERSION_UNKNOWN',
1044
1044
  };
1045
1045
 
1046
+ /** Environment variable read for a secret-class key outside browsers. */
1047
+ const ARETE_API_KEY_ENV = 'ARETE_API_KEY';
1048
+ const CREATE_PUBLISHABLE_HINT = 'create one with `a4 auth keys create-publishable --origin <scheme://host[:port]>`';
1049
+ const PUBLISHABLE_PREFIXES = ['a4_pk_', 'hspk_'];
1050
+ const SECRET_PREFIXES = ['a4_sk_', 'a4_ak_', 'hsk_'];
1051
+ function classifyApiKey(key) {
1052
+ const trimmed = key.trim();
1053
+ if (PUBLISHABLE_PREFIXES.some((prefix) => trimmed.startsWith(prefix)))
1054
+ return 'publishable';
1055
+ if (SECRET_PREFIXES.some((prefix) => trimmed.startsWith(prefix)))
1056
+ return 'secret';
1057
+ return 'unknown';
1058
+ }
1059
+ /**
1060
+ * True in a browser page: both `window` and `document` exist. Web workers,
1061
+ * service workers, edge runtimes, SSR (Node/Bun) and Deno have no `document`
1062
+ * and count as server-side.
1063
+ */
1064
+ function isBrowserEnvironment() {
1065
+ const scope = globalThis;
1066
+ return typeof scope.window === 'object'
1067
+ && scope.window !== null
1068
+ && typeof scope.document === 'object'
1069
+ && scope.document !== null;
1070
+ }
1071
+ /**
1072
+ * Read an environment variable in Node, Bun or Deno without prompting for or
1073
+ * failing on missing permissions. Browsers have no environment.
1074
+ */
1075
+ function readEnvironmentVariable(name) {
1076
+ try {
1077
+ const deno = globalThis.Deno;
1078
+ if (deno) {
1079
+ // Reading without --allow-env throws or prompts; ask first.
1080
+ const state = deno.permissions?.querySync?.({ name: 'env', variable: name })?.state;
1081
+ if (state !== 'granted')
1082
+ return undefined;
1083
+ const value = deno.env?.get?.(name);
1084
+ return typeof value === 'string' ? value : undefined;
1085
+ }
1086
+ const value = globalThis
1087
+ .process?.env?.[name];
1088
+ return typeof value === 'string' ? value : undefined;
1089
+ }
1090
+ catch {
1091
+ return undefined;
1092
+ }
1093
+ }
1094
+ const warned$1 = new Set();
1095
+ function warnOnce$1(id, message) {
1096
+ if (warned$1.has(id))
1097
+ return;
1098
+ warned$1.add(id);
1099
+ console.warn(`[arete] ${message}`);
1100
+ }
1101
+ function hasExplicitAuth(auth) {
1102
+ return auth !== undefined && (auth.token !== undefined
1103
+ || auth.getToken !== undefined
1104
+ || auth.tokenEndpoint !== undefined
1105
+ || auth.publishableKey !== undefined
1106
+ || auth.secretKey !== undefined);
1107
+ }
1108
+ /**
1109
+ * Validate the configured API keys and apply the `ARETE_API_KEY` fallback.
1110
+ *
1111
+ * - `secretKey` is refused in browsers and refuses publishable keys.
1112
+ * - A secret-class key in `publishableKey` is refused in browsers and warned
1113
+ * about elsewhere (it still works server-side, as it always has).
1114
+ * - Outside browsers, when no auth option is set at all, `ARETE_API_KEY`
1115
+ * supplies `secretKey`.
1116
+ *
1117
+ * Error and warning text never includes key material.
1118
+ */
1119
+ function resolveAuthConfig(auth) {
1120
+ const browser = isBrowserEnvironment();
1121
+ if (auth?.secretKey !== undefined) {
1122
+ if (browser) {
1123
+ throw new AreteError('auth.secretKey cannot be used in a browser: anything shipped to a browser is public. '
1124
+ + 'Use auth.publishableKey with a publishable key (a4_pk_...) instead; '
1125
+ + `${CREATE_PUBLISHABLE_HINT}. Keep secret and agent keys on servers and in local scripts.`, 'INVALID_CONFIG');
1126
+ }
1127
+ if (auth.secretKey.trim() === '') {
1128
+ throw new AreteError('auth.secretKey is empty', 'INVALID_CONFIG');
1129
+ }
1130
+ if (classifyApiKey(auth.secretKey) === 'publishable') {
1131
+ throw new AreteError('auth.secretKey was given a publishable key (a4_pk_...). Pass it as auth.publishableKey '
1132
+ + 'instead; auth.secretKey takes an agent key (a4_ak_...) or secret key (a4_sk_...).', 'INVALID_CONFIG');
1133
+ }
1134
+ if (auth.publishableKey !== undefined) {
1135
+ throw new AreteError('Set either auth.secretKey (servers and scripts) or auth.publishableKey (browsers), not both.', 'INVALID_CONFIG');
1136
+ }
1137
+ }
1138
+ if (auth?.publishableKey !== undefined && classifyApiKey(auth.publishableKey) === 'secret') {
1139
+ if (browser) {
1140
+ throw new AreteError('auth.publishableKey was given a secret-class key (a4_sk_... or a4_ak_...). Never ship a '
1141
+ + 'secret or agent key to a browser. Use a publishable key (a4_pk_...) instead; '
1142
+ + `${CREATE_PUBLISHABLE_HINT}.`, 'INVALID_CONFIG');
1143
+ }
1144
+ warnOnce$1('secret-in-publishable', 'auth.publishableKey was given a secret-class key (a4_sk_... or a4_ak_...). '
1145
+ + 'Pass it as auth.secretKey instead (servers and scripts only), or set ARETE_API_KEY.');
1146
+ }
1147
+ if (browser || hasExplicitAuth(auth))
1148
+ return auth;
1149
+ const environmentKey = readEnvironmentVariable(ARETE_API_KEY_ENV)?.trim();
1150
+ if (!environmentKey)
1151
+ return auth;
1152
+ if (classifyApiKey(environmentKey) === 'publishable') {
1153
+ warnOnce$1('publishable-in-env', `${ARETE_API_KEY_ENV} holds a publishable key (a4_pk_...) and was ignored. Set it to an agent `
1154
+ + 'key (a4_ak_...) or secret key (a4_sk_...), or pass the publishable key as auth.publishableKey.');
1155
+ return auth;
1156
+ }
1157
+ return { ...auth, secretKey: environmentKey };
1158
+ }
1159
+ /** The key sent as the token endpoint bearer credential, if any. */
1160
+ function tokenEndpointApiKey(auth) {
1161
+ return auth?.secretKey ?? auth?.publishableKey;
1162
+ }
1163
+
1046
1164
  const GZIP_MAGIC_0 = 0x1f;
1047
1165
  const GZIP_MAGIC_1 = 0x8b;
1048
1166
  const FRAME_MODES = new Set(['state', 'append', 'list']);
@@ -1850,7 +1968,7 @@ class ConnectionManager {
1850
1968
  this.reconnectIntervals = config.reconnectIntervals ?? DEFAULT_CONFIG.reconnectIntervals;
1851
1969
  this.maxReconnectAttempts =
1852
1970
  config.maxReconnectAttempts ?? DEFAULT_CONFIG.maxReconnectAttempts;
1853
- this.authConfig = config.auth;
1971
+ this.authConfig = resolveAuthConfig(config.auth);
1854
1972
  this.release = normalizeStackRelease(config.release);
1855
1973
  this.authFetch = config.fetch ?? ((input, init) => {
1856
1974
  if (typeof globalThis.fetch !== 'function') {
@@ -2102,12 +2220,11 @@ class ConnectionManager {
2102
2220
  };
2103
2221
  }
2104
2222
  async fetchTokenFromEndpoint(tokenEndpoint, request) {
2223
+ const apiKey = tokenEndpointApiKey(this.authConfig);
2105
2224
  const response = await this.authFetch(tokenEndpoint, {
2106
2225
  method: 'POST',
2107
2226
  headers: {
2108
- ...(this.authConfig?.publishableKey
2109
- ? { Authorization: `Bearer ${this.authConfig.publishableKey}` }
2110
- : {}),
2227
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
2111
2228
  ...(this.authConfig?.tokenEndpointHeaders ?? {}),
2112
2229
  'Content-Type': 'application/json',
2113
2230
  },
@@ -5453,7 +5570,10 @@ function hasRuntimeAuthStrategy(auth) {
5453
5570
  || auth?.getToken !== undefined
5454
5571
  || auth?.tokenEndpoint !== undefined;
5455
5572
  }
5456
- function bindingAuthConfig(binding, runtimeAuth) {
5573
+ function bindingAuthConfig(binding, configuredAuth) {
5574
+ // Resolve ARETE_API_KEY before the binding endpoint is filled in, so an
5575
+ // otherwise unconfigured client still authenticates.
5576
+ const runtimeAuth = resolveAuthConfig(configuredAuth);
5457
5577
  if (hasRuntimeAuthStrategy(runtimeAuth))
5458
5578
  return runtimeAuth;
5459
5579
  if (!binding.auth.required)
@@ -7500,7 +7620,10 @@ function parseProgramReadResponse(response, path, body) {
7500
7620
  throw new AreteError(`Program read '${path}' returned invalid JSON`, 'INVALID_RESPONSE', error);
7501
7621
  }
7502
7622
  }
7503
- function hostedAuthConfig(binding, runtimeAuth) {
7623
+ function hostedAuthConfig(binding, configuredAuth) {
7624
+ // Resolve ARETE_API_KEY before the binding endpoint is filled in, so an
7625
+ // otherwise unconfigured client still authenticates.
7626
+ const runtimeAuth = resolveAuthConfig(configuredAuth);
7504
7627
  const metadata = binding.auth;
7505
7628
  const runtimeStrategyConfigured = runtimeAuth?.token !== undefined
7506
7629
  || runtimeAuth?.getToken !== undefined
@@ -8975,6 +9098,7 @@ function stringifyBigints(value) {
8975
9098
  const Arete = Object.assign(Arete$1, { session: createSession });
8976
9099
 
8977
9100
  exports.API_PROBLEM_SCHEMA_VERSION = API_PROBLEM_SCHEMA_VERSION;
9101
+ exports.ARETE_API_KEY_ENV = ARETE_API_KEY_ENV;
8978
9102
  exports.ASSOCIATED_TOKEN_PROGRAM_ADDRESS = ASSOCIATED_TOKEN_PROGRAM_ADDRESS;
8979
9103
  exports.Arete = Arete;
8980
9104
  exports.AreteError = AreteError;
@@ -9019,6 +9143,7 @@ exports.buildInstruction = buildInstruction;
9019
9143
  exports.bytes = bytes;
9020
9144
  exports.canonicalQueryKey = canonicalQueryKey;
9021
9145
  exports.chainAccountLoader = chainAccountLoader;
9146
+ exports.classifyApiKey = classifyApiKey;
9022
9147
  exports.compareProgramIdentity = compareProgramIdentity;
9023
9148
  exports.createChainClient = createChainClient;
9024
9149
  exports.createEntityStream = createEntityStream;