@forgezero/providers 0.1.5 → 0.1.7

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.5";
147
+ var VERSION = "0.1.6";
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.5";
147
+ var VERSION = "0.1.6";
148
148
 
149
149
  // src/chain.ts
150
150
  var hexToNumber = (value) => Number(BigInt(value));
@@ -43,11 +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
- /**
47
- * Optional bootstrap Coordinators already proven to be in Arango `readonly`
48
- * server mode. Prefer signed, probed membership for production.
49
- */
50
- readOnlyUrls?: 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';
51
50
  /**
52
51
  * Dynamic membership is accepted only as a signed/attested envelope verified
53
52
  * by the host. A Worker/KV node list by itself is discovery data, not routing
@@ -65,8 +64,6 @@ export interface ArangoConfig {
65
64
  maxCoordinatorAttempts?: number;
66
65
  /** Local transport-failure quarantine. Primarily injectable for tests. */
67
66
  unhealthyForMs?: number;
68
- /** What an explicit read-only preference does when no read-only coordinator can serve it. */
69
- readOnlyFallback?: 'authoritative' | 'error';
70
67
  now?: () => number;
71
68
  }
72
69
  export interface Query {
@@ -77,29 +74,24 @@ export interface Query {
77
74
  * caller assertions; this adapter never guesses AQL semantics from text.
78
75
  */
79
76
  retrySafety?: 'read' | 'idempotent' | 'never';
80
- /**
81
- * `prefer-readonly` targets Coordinators whose actual Arango server mode is
82
- * `readonly`. It is valid only with `retrySafety: 'read'`; ForgeZero does not
83
- * infer AQL semantics. Community 3.11.14 dirty follower reads are not exposed:
84
- * that server feature is Enterprise-only.
85
- */
86
- readPreference?: 'authoritative' | 'prefer-readonly';
77
+ /** Routing hint valid only with `retrySafety: read`; it never changes write routing. */
78
+ readPreference?: 'balanced' | 'prefer-designated';
87
79
  /** Required when `retrySafety` is `idempotent`; retained for audit correlation. */
88
80
  idempotencyKey?: string;
89
81
  }
90
82
  export interface ArangoCoordinatorMember {
91
83
  url: string;
92
- /** ArangoDB has no read-only DBServer/client role. Clients address Coordinators. */
84
+ /** Clients address Coordinators; Community 3.11.14 has no read-only joiner role. */
93
85
  role: 'coordinator';
94
86
  /** Actual `/_admin/server/mode` state for this Coordinator. */
95
- serverMode: 'default' | 'readonly';
96
- /** ForgeZero request-routing capability, required to agree with `serverMode`. */
97
- access: 'read-write' | 'read-only';
87
+ serverMode: 'default';
98
88
  capabilities: {
99
89
  /** Always false for the exact Community 3.11.14 contract. */
100
90
  dirtyFollowerReads: false;
101
91
  };
102
92
  status: 'healthy' | 'degraded' | 'unhealthy';
93
+ /** Signed operator preference for safe reads; this is not an ArangoDB role. */
94
+ readPreferred?: boolean;
103
95
  /** Milliseconds since epoch, covered by the membership signature/attestation. */
104
96
  observedAt: number;
105
97
  /** Enrolled hybrid identity. The node key is its Ed25519 public half. */
@@ -122,7 +114,7 @@ export interface ArangoMembershipSnapshot {
122
114
  export type ArangoMembershipVerifier = (snapshot: ArangoMembershipSnapshot, signal?: AbortSignal) => Promise<boolean>;
123
115
  export declare const MAX_ARANGO_COORDINATORS = 16;
124
116
  export declare const ARANGO_COMMUNITY_VERSION: "3.11.14";
125
- /** Protected per-server API used to attest/set `default` or `readonly` mode. */
117
+ /** Protected API used to attest cluster-wide `default`/maintenance mode. */
126
118
  export declare const ARANGO_SERVER_MODE_PATH: "/_admin/server/mode";
127
119
  /**
128
120
  * Accept only an explicit, bounded set of private authenticated endpoints.
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.5";
147
+ var VERSION = "0.1.6";
148
148
 
149
149
  // src/database.ts
150
150
  var pools = new Map;
@@ -187,30 +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 readOnlyUrls = config.readOnlyUrls?.length ? arangoCoordinatorUrls({ urls: config.readOnlyUrls }) : [];
191
- if (urls.length + readOnlyUrls.length > MAX_ARANGO_COORDINATORS || urls.some((url) => readOnlyUrls.includes(url))) {
192
- throw new ProviderError("ARANGO_COORDINATORS_INVALID", `Writable and read-only ArangoDB coordinator URLs must be disjoint and total at most ${MAX_ARANGO_COORDINATORS}.`);
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
193
  }
194
194
  return {
195
- members: [...urls.map((url) => ({
195
+ members: urls.map((url) => ({
196
196
  url,
197
197
  role: "coordinator",
198
198
  serverMode: "default",
199
- access: "read-write",
200
199
  capabilities: { dirtyFollowerReads: false },
201
- status: "healthy"
202
- })), ...readOnlyUrls.map((url) => ({
203
- url,
204
- role: "coordinator",
205
- serverMode: "readonly",
206
- access: "read-only",
207
- capabilities: { dirtyFollowerReads: false },
208
- status: "healthy"
209
- }))],
210
- identity: `static:${urls.join(",")}:readonly:${readOnlyUrls.join(",")}`
200
+ status: "healthy",
201
+ readPreferred: preferred.includes(url)
202
+ })),
203
+ identity: `static:${urls.join(",")}:preferred:${preferred.join(",")}`
211
204
  };
212
205
  }
213
- if (config.url || config.urls || config.readOnlyUrls) {
206
+ if (config.url || config.urls || config.readPreferredUrls) {
214
207
  throw new ProviderError("ARANGO_MEMBERSHIP_AMBIGUOUS", "Configure static coordinator URLs or verified membership, not both.");
215
208
  }
216
209
  if (!config.clusterId || config.membership.clusterId !== config.clusterId) {
@@ -228,7 +221,7 @@ async function resolveMembership(config, signal) {
228
221
  throw new ProviderError("ARANGO_MEMBERSHIP_EXPIRED", "Dynamic ArangoDB membership is not currently valid.");
229
222
  }
230
223
  const validAuthority = snapshot.authority === "forgezero-platform-signed" || snapshot.authority === "node-attested";
231
- 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.serverMode === "readonly") && node.access === (node.serverMode === "readonly" ? "read-only" : "read-write") && 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);
232
225
  if (!validAuthority || !snapshot.revision || !validNodes) {
233
226
  throw new ProviderError("ARANGO_MEMBERSHIP_INVALID", "Dynamic ArangoDB membership needs a revision and observed timestamps.");
234
227
  }
@@ -267,6 +260,12 @@ function boundedAttempts(config, available, safe) {
267
260
  return Math.min(requested, available);
268
261
  }
269
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
+ }
270
269
  if (request.retrySafety === "read")
271
270
  return true;
272
271
  if (request.retrySafety !== "idempotent")
@@ -291,20 +290,11 @@ function selectNode(pool, excluded, now, eligibleMember) {
291
290
  }
292
291
  return;
293
292
  }
294
- function assertReadPolicy(request) {
295
- if (request.readPreference !== undefined && request.readPreference !== "authoritative" && request.readPreference !== "prefer-readonly") {
296
- throw new ProviderError("ARANGO_READ_PREFERENCE_INVALID", "Unknown ArangoDB read preference.");
297
- }
298
- if (request.readPreference && request.retrySafety !== "read") {
299
- throw new ProviderError("ARANGO_READ_PREFERENCE_UNSAFE", "ArangoDB read preference requires an explicit read retry-safety assertion.");
300
- }
301
- }
302
293
  function nodeForRequest(pool, request, fallback, excluded, now) {
303
- const writable = (node) => node.member.access === "read-write";
304
- if (request.retrySafety === "read" && request.readPreference === "prefer-readonly") {
305
- return selectNode(pool, excluded, now, (node) => node.member.access === "read-only") ?? (fallback === "error" ? undefined : selectNode(pool, excluded, now, writable));
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));
306
296
  }
307
- return selectNode(pool, excluded, now, writable);
297
+ return selectNode(pool, excluded, now, () => true);
308
298
  }
309
299
  var arangodb = defineProvider({
310
300
  id: "arangodb",
@@ -342,14 +332,19 @@ var arangodb = defineProvider({
342
332
  title: "Coordinator URLs",
343
333
  description: "Authenticated private coordinators in this one logical cluster."
344
334
  },
345
- readOnlyUrls: {
335
+ readPreferredUrls: {
346
336
  type: "array",
347
337
  minItems: 1,
348
- maxItems: MAX_ARANGO_COORDINATORS - 1,
338
+ maxItems: MAX_ARANGO_COORDINATORS,
349
339
  uniqueItems: true,
350
340
  items: { type: "string" },
351
- title: "Read-only Coordinator URLs",
352
- description: "Optional bootstrap endpoints whose protected Arango server mode was probed as readonly."
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"
353
348
  },
354
349
  clusterId: {
355
350
  type: "string",
@@ -369,11 +364,6 @@ var arangodb = defineProvider({
369
364
  minimum: 1000,
370
365
  maximum: 300000,
371
366
  title: "Local coordinator quarantine (ms)"
372
- },
373
- readOnlyFallback: {
374
- type: "string",
375
- enum: ["authoritative", "error"],
376
- title: "Read-only coordinator fallback"
377
367
  }
378
368
  }
379
369
  },
@@ -385,11 +375,10 @@ var arangodb = defineProvider({
385
375
  const membership = await resolveMembership(config, context.signal);
386
376
  const members = membership.members;
387
377
  const urls = members.map(({ url }) => url);
388
- assertReadPolicy(request);
389
378
  const safe = requestIsSafe(request);
390
- const available = request.retrySafety === "read" && request.readPreference === "prefer-readonly" && config.readOnlyFallback === "error" ? members.filter(({ serverMode }) => serverMode === "readonly").length : members.filter(({ serverMode }) => request.retrySafety === "read" && request.readPreference === "prefer-readonly" ? true : serverMode === "default").length;
379
+ const available = members.length;
391
380
  if (available === 0) {
392
- throw new ProviderError("ARANGO_COORDINATORS_UNAVAILABLE", request.readPreference === "prefer-readonly" ? "No verified read-only ArangoDB coordinator is available under the configured fallback policy." : "No verified authoritative ArangoDB coordinator is available.");
381
+ throw new ProviderError("ARANGO_COORDINATORS_UNAVAILABLE", "No verified authoritative ArangoDB coordinator is available.");
393
382
  }
394
383
  const attempts = boundedAttempts(config, available, safe);
395
384
  const key = `${membership.identity}/${config.database}/${config.username}`;
@@ -462,7 +451,7 @@ var arangodb = defineProvider({
462
451
  throw context.signal.reason ?? new ProviderError("ARANGO_ABORTED", "ArangoDB request was cancelled.");
463
452
  }
464
453
  const now = (config.now ?? Date.now)();
465
- const node = nodeForRequest(pool, request, config.readOnlyFallback, tried, now);
454
+ const node = nodeForRequest(pool, request, config.readPreferredFallback, tried, now);
466
455
  if (!node)
467
456
  break;
468
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.5";
147
+ var VERSION = "0.1.6";
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.5";
147
+ var VERSION = "0.1.6";
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.5";
159
+ export declare const VERSION = "0.1.6";
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.5";
147
+ var VERSION = "0.1.6";
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.5";
147
+ var VERSION = "0.1.6";
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.5";
147
+ var VERSION = "0.1.6";
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.5";
147
+ var VERSION = "0.1.6";
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.5",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public",