@forgezero/providers 0.1.6 → 0.1.8

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/binance.js CHANGED
@@ -144,7 +144,7 @@ function createRegistry(options) {
144
144
  }
145
145
  return { call };
146
146
  }
147
- var VERSION = "0.1.6";
147
+ var VERSION = "0.1.8";
148
148
 
149
149
  // src/http.ts
150
150
  class BudgetExhausted extends ProviderError {
package/dist/chain.js CHANGED
@@ -144,7 +144,7 @@ function createRegistry(options) {
144
144
  }
145
145
  return { call };
146
146
  }
147
- var VERSION = "0.1.6";
147
+ var VERSION = "0.1.8";
148
148
 
149
149
  // src/chain.ts
150
150
  var hexToNumber = (value) => Number(BigInt(value));
@@ -43,6 +43,10 @@ export interface ArangoConfig {
43
43
  url?: string;
44
44
  /** Explicit authenticated private coordinators. Bounded to prevent config abuse. */
45
45
  urls?: readonly string[];
46
+ /** Optional subset of `urls` preferred only by explicitly safe reads. */
47
+ readPreferredUrls?: readonly string[];
48
+ /** Whether a preferred read may fall back to the balanced writable pool. */
49
+ readPreferredFallback?: 'balanced' | 'error';
46
50
  /**
47
51
  * Dynamic membership is accepted only as a signed/attested envelope verified
48
52
  * by the host. A Worker/KV node list by itself is discovery data, not routing
@@ -70,6 +74,8 @@ export interface Query {
70
74
  * caller assertions; this adapter never guesses AQL semantics from text.
71
75
  */
72
76
  retrySafety?: 'read' | 'idempotent' | 'never';
77
+ /** Routing hint valid only with `retrySafety: read`; it never changes write routing. */
78
+ readPreference?: 'balanced' | 'prefer-designated';
73
79
  /** Required when `retrySafety` is `idempotent`; retained for audit correlation. */
74
80
  idempotencyKey?: string;
75
81
  }
@@ -84,6 +90,8 @@ export interface ArangoCoordinatorMember {
84
90
  dirtyFollowerReads: false;
85
91
  };
86
92
  status: 'healthy' | 'degraded' | 'unhealthy';
93
+ /** Signed operator preference for safe reads; this is not an ArangoDB role. */
94
+ readPreferred?: boolean;
87
95
  /** Milliseconds since epoch, covered by the membership signature/attestation. */
88
96
  observedAt: number;
89
97
  /** Enrolled hybrid identity. The node key is its Ed25519 public half. */
package/dist/database.js CHANGED
@@ -144,7 +144,7 @@ function createRegistry(options) {
144
144
  }
145
145
  return { call };
146
146
  }
147
- var VERSION = "0.1.6";
147
+ var VERSION = "0.1.8";
148
148
 
149
149
  // src/database.ts
150
150
  var pools = new Map;
@@ -187,18 +187,23 @@ function arangoCoordinatorUrls(config) {
187
187
  async function resolveMembership(config, signal) {
188
188
  if (!config.membership) {
189
189
  const urls = arangoCoordinatorUrls(config);
190
+ const preferred = config.readPreferredUrls?.length ? arangoCoordinatorUrls({ urls: config.readPreferredUrls }) : [];
191
+ if (preferred.some((url) => !urls.includes(url))) {
192
+ throw new ProviderError("ARANGO_READ_PREFERENCE_INVALID", "Read-preferred coordinators must be a subset of the verified writable coordinator list.");
193
+ }
190
194
  return {
191
195
  members: urls.map((url) => ({
192
196
  url,
193
197
  role: "coordinator",
194
198
  serverMode: "default",
195
199
  capabilities: { dirtyFollowerReads: false },
196
- status: "healthy"
200
+ status: "healthy",
201
+ readPreferred: preferred.includes(url)
197
202
  })),
198
- identity: `static:${urls.join(",")}`
203
+ identity: `static:${urls.join(",")}:preferred:${preferred.join(",")}`
199
204
  };
200
205
  }
201
- if (config.url || config.urls) {
206
+ if (config.url || config.urls || config.readPreferredUrls) {
202
207
  throw new ProviderError("ARANGO_MEMBERSHIP_AMBIGUOUS", "Configure static coordinator URLs or verified membership, not both.");
203
208
  }
204
209
  if (!config.clusterId || config.membership.clusterId !== config.clusterId) {
@@ -216,7 +221,7 @@ async function resolveMembership(config, signal) {
216
221
  throw new ProviderError("ARANGO_MEMBERSHIP_EXPIRED", "Dynamic ArangoDB membership is not currently valid.");
217
222
  }
218
223
  const validAuthority = snapshot.authority === "forgezero-platform-signed" || snapshot.authority === "node-attested";
219
- const validNodes = snapshot.coordinators.every((node) => Number.isFinite(node.observedAt) && node.nodeKey.length > 0 && node.nodeKey === node.publicKeys?.ed25519 && Boolean(node.publicKeys.mlDsa) && (node.status === "healthy" || node.status === "degraded" || node.status === "unhealthy") && node.role === "coordinator" && node.serverMode === "default" && node.capabilities?.dirtyFollowerReads === false);
224
+ const validNodes = snapshot.coordinators.every((node) => Number.isFinite(node.observedAt) && node.nodeKey.length > 0 && node.nodeKey === node.publicKeys?.ed25519 && Boolean(node.publicKeys.mlDsa) && (node.status === "healthy" || node.status === "degraded" || node.status === "unhealthy") && (node.readPreferred === undefined || typeof node.readPreferred === "boolean") && node.role === "coordinator" && node.serverMode === "default" && node.capabilities?.dirtyFollowerReads === false);
220
225
  if (!validAuthority || !snapshot.revision || !validNodes) {
221
226
  throw new ProviderError("ARANGO_MEMBERSHIP_INVALID", "Dynamic ArangoDB membership needs a revision and observed timestamps.");
222
227
  }
@@ -255,6 +260,12 @@ function boundedAttempts(config, available, safe) {
255
260
  return Math.min(requested, available);
256
261
  }
257
262
  function requestIsSafe(request) {
263
+ if (request.readPreference !== undefined && request.readPreference !== "balanced" && request.readPreference !== "prefer-designated") {
264
+ throw new ProviderError("ARANGO_READ_PREFERENCE_INVALID", "Unknown ArangoDB read preference.");
265
+ }
266
+ if (request.readPreference && request.retrySafety !== "read") {
267
+ throw new ProviderError("ARANGO_READ_PREFERENCE_UNSAFE", "ArangoDB read preference requires an explicit read retry-safety assertion.");
268
+ }
258
269
  if (request.retrySafety === "read")
259
270
  return true;
260
271
  if (request.retrySafety !== "idempotent")
@@ -279,7 +290,10 @@ function selectNode(pool, excluded, now, eligibleMember) {
279
290
  }
280
291
  return;
281
292
  }
282
- function nodeForRequest(pool, excluded, now) {
293
+ function nodeForRequest(pool, request, fallback, excluded, now) {
294
+ if (request.retrySafety === "read" && request.readPreference === "prefer-designated") {
295
+ return selectNode(pool, excluded, now, (node) => node.member.readPreferred === true) ?? (fallback === "error" ? undefined : selectNode(pool, excluded, now, () => true));
296
+ }
283
297
  return selectNode(pool, excluded, now, () => true);
284
298
  }
285
299
  var arangodb = defineProvider({
@@ -318,6 +332,20 @@ var arangodb = defineProvider({
318
332
  title: "Coordinator URLs",
319
333
  description: "Authenticated private coordinators in this one logical cluster."
320
334
  },
335
+ readPreferredUrls: {
336
+ type: "array",
337
+ minItems: 1,
338
+ maxItems: MAX_ARANGO_COORDINATORS,
339
+ uniqueItems: true,
340
+ items: { type: "string" },
341
+ title: "Read-preferred Coordinator URLs",
342
+ description: "Optional verified coordinator subset preferred by explicitly safe reads."
343
+ },
344
+ readPreferredFallback: {
345
+ type: "string",
346
+ enum: ["balanced", "error"],
347
+ title: "Read-preferred fallback"
348
+ },
321
349
  clusterId: {
322
350
  type: "string",
323
351
  title: "Cluster ID",
@@ -423,7 +451,7 @@ var arangodb = defineProvider({
423
451
  throw context.signal.reason ?? new ProviderError("ARANGO_ABORTED", "ArangoDB request was cancelled.");
424
452
  }
425
453
  const now = (config.now ?? Date.now)();
426
- const node = nodeForRequest(pool, tried, now);
454
+ const node = nodeForRequest(pool, request, config.readPreferredFallback, tried, now);
427
455
  if (!node)
428
456
  break;
429
457
  tried.add(node.url);
package/dist/email.js CHANGED
@@ -144,7 +144,7 @@ function createRegistry(options) {
144
144
  }
145
145
  return { call };
146
146
  }
147
- var VERSION = "0.1.6";
147
+ var VERSION = "0.1.8";
148
148
 
149
149
  // src/email.ts
150
150
  var recipients = (to) => Array.isArray(to) ? [...to] : [to];
package/dist/http.js CHANGED
@@ -144,7 +144,7 @@ function createRegistry(options) {
144
144
  }
145
145
  return { call };
146
146
  }
147
- var VERSION = "0.1.6";
147
+ var VERSION = "0.1.8";
148
148
 
149
149
  // src/http.ts
150
150
  class BudgetExhausted extends ProviderError {
package/dist/index.d.ts CHANGED
@@ -156,4 +156,4 @@ export interface EmailMessage {
156
156
  from?: string;
157
157
  replyTo?: string;
158
158
  }
159
- export declare const VERSION = "0.1.6";
159
+ export declare const VERSION = "0.1.8";
package/dist/index.js CHANGED
@@ -144,7 +144,7 @@ function createRegistry(options) {
144
144
  }
145
145
  return { call };
146
146
  }
147
- var VERSION = "0.1.6";
147
+ var VERSION = "0.1.8";
148
148
  export {
149
149
  staticConfig,
150
150
  nextHealth,
package/dist/pool.js CHANGED
@@ -144,7 +144,7 @@ function createRegistry(options) {
144
144
  }
145
145
  return { call };
146
146
  }
147
- var VERSION = "0.1.6";
147
+ var VERSION = "0.1.8";
148
148
 
149
149
  // src/http.ts
150
150
  class BudgetExhausted extends ProviderError {
package/dist/storage.js CHANGED
@@ -144,7 +144,7 @@ function createRegistry(options) {
144
144
  }
145
145
  return { call };
146
146
  }
147
- var VERSION = "0.1.6";
147
+ var VERSION = "0.1.8";
148
148
 
149
149
  // src/storage.ts
150
150
  var encoder = new TextEncoder;
@@ -144,7 +144,7 @@ function createRegistry(options) {
144
144
  }
145
145
  return { call };
146
146
  }
147
- var VERSION = "0.1.6";
147
+ var VERSION = "0.1.8";
148
148
 
149
149
  // src/translation.ts
150
150
  var GOOGLE_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/models";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/providers",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public",