@drawbridge/drawbridge-utils 0.0.42 → 0.0.44

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/ai.cjs CHANGED
@@ -16,7 +16,7 @@ var __copyProps = (to, from, except, desc) => {
16
16
  };
17
17
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
18
 
19
- // ai.js
19
+ // lib/ai.js
20
20
  var ai_exports = {};
21
21
  __export(ai_exports, {
22
22
  MARKUP: () => MARKUP,
@@ -31,7 +31,7 @@ __export(ai_exports, {
31
31
  module.exports = __toCommonJS(ai_exports);
32
32
  var import_genai = require("@google/genai");
33
33
 
34
- // circuit.js
34
+ // lib/circuit.js
35
35
  var CLOSED = "CLOSED";
36
36
  var OPEN = "OPEN";
37
37
  var HALF_OPEN = "HALF_OPEN";
@@ -82,7 +82,7 @@ var circuit = ({
82
82
  };
83
83
  };
84
84
 
85
- // transactions.js
85
+ // lib/transactions.js
86
86
  var import_drawbridge_telemetry = require("@drawbridge/drawbridge-telemetry");
87
87
  var insertTransaction = async ({
88
88
  db,
@@ -155,7 +155,7 @@ var debit = async ({
155
155
  });
156
156
  };
157
157
 
158
- // ai.js
158
+ // lib/ai.js
159
159
  var google_client = new import_genai.GoogleGenAI({
160
160
  apiKey: process.env.GOOGLE_API_KEY
161
161
  });
package/dist/ai.js CHANGED
@@ -1,12 +1,131 @@
1
- import {
2
- debit
3
- } from "./chunk-JT2PAZAZ.js";
4
- import {
5
- circuit
6
- } from "./chunk-6HH36OK6.js";
7
-
8
- // ai.js
1
+ // lib/ai.js
9
2
  import { GoogleGenAI } from "@google/genai";
3
+
4
+ // lib/circuit.js
5
+ var CLOSED = "CLOSED";
6
+ var OPEN = "OPEN";
7
+ var HALF_OPEN = "HALF_OPEN";
8
+ var circuit = ({
9
+ name,
10
+ threshold = 5,
11
+ timeout = 3e4
12
+ }) => {
13
+ let state = CLOSED;
14
+ let failures = 0;
15
+ let openedAt = null;
16
+ const trip = () => {
17
+ state = OPEN;
18
+ openedAt = Date.now();
19
+ console.error(`Circuit breaker OPEN: ${name}`);
20
+ };
21
+ const reset = () => {
22
+ state = CLOSED;
23
+ failures = 0;
24
+ openedAt = null;
25
+ console.log(`Circuit breaker CLOSED: ${name}`);
26
+ };
27
+ return async (fn) => {
28
+ if (state === OPEN) {
29
+ if (Date.now() - openedAt >= timeout) {
30
+ state = HALF_OPEN;
31
+ } else {
32
+ const error = new Error(`${name} is temporarily unavailable`);
33
+ error.status = 503;
34
+ throw error;
35
+ }
36
+ }
37
+ try {
38
+ const result = await fn();
39
+ if (state === HALF_OPEN) {
40
+ reset();
41
+ } else {
42
+ failures = 0;
43
+ }
44
+ return result;
45
+ } catch (error) {
46
+ failures++;
47
+ if (state === HALF_OPEN || failures >= threshold) {
48
+ trip();
49
+ }
50
+ throw error;
51
+ }
52
+ };
53
+ };
54
+
55
+ // lib/transactions.js
56
+ import { currentTraceId } from "@drawbridge/drawbridge-telemetry";
57
+ var insertTransaction = async ({
58
+ db,
59
+ user,
60
+ type,
61
+ category,
62
+ source,
63
+ amount,
64
+ _id,
65
+ stripeInvoiceId,
66
+ stripeEventId,
67
+ session,
68
+ ...rest
69
+ }) => {
70
+ var _a;
71
+ const beforeRaw = (_a = user == null ? void 0 : user.balance) == null ? void 0 : _a[type];
72
+ const balance = typeof beforeRaw === "number" ? { before: beforeRaw, after: beforeRaw + amount } : void 0;
73
+ const trace = currentTraceId();
74
+ await db.create({
75
+ authenticated: user,
76
+ collection: "transaction",
77
+ data: {
78
+ ..._id && { _id },
79
+ user: user == null ? void 0 : user.id,
80
+ type,
81
+ category,
82
+ source,
83
+ amount,
84
+ ...balance && { balance },
85
+ ...trace && { trace },
86
+ ...rest,
87
+ ...stripeInvoiceId && { stripeInvoiceId },
88
+ ...stripeEventId && { stripeEventId }
89
+ },
90
+ ...session && { options: { session } }
91
+ });
92
+ };
93
+ var debit = async ({
94
+ db,
95
+ user,
96
+ amount,
97
+ type,
98
+ category,
99
+ source,
100
+ stripeInvoiceId,
101
+ stripeEventId,
102
+ session,
103
+ ...rest
104
+ }) => {
105
+ if (!Number.isInteger(amount) || amount <= 0) {
106
+ throw new Error(`debit() requires a positive integer amount, got ${amount}`);
107
+ }
108
+ if (!type) {
109
+ throw new Error('debit() requires a type (e.g., "ai")');
110
+ }
111
+ if (!source) {
112
+ throw new Error('debit() requires a source ("system" | "admin" | "user")');
113
+ }
114
+ await insertTransaction({
115
+ db,
116
+ user,
117
+ type,
118
+ category,
119
+ source,
120
+ amount: -amount,
121
+ stripeInvoiceId,
122
+ stripeEventId,
123
+ session,
124
+ ...rest
125
+ });
126
+ };
127
+
128
+ // lib/ai.js
10
129
  var google_client = new GoogleGenAI({
11
130
  apiKey: process.env.GOOGLE_API_KEY
12
131
  });
package/dist/axios.cjs CHANGED
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  ));
27
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
28
 
29
- // axios.js
29
+ // lib/axios.js
30
30
  var axios_exports = {};
31
31
  __export(axios_exports, {
32
32
  axios: () => axios,
package/dist/axios.js CHANGED
@@ -1,4 +1,4 @@
1
- // axios.js
1
+ // lib/axios.js
2
2
  import axiosLib from "axios";
3
3
  import dns from "dns";
4
4
  import https from "https";
package/dist/cdn.cjs CHANGED
@@ -16,7 +16,7 @@ var __copyProps = (to, from, except, desc) => {
16
16
  };
17
17
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
18
 
19
- // cdn.js
19
+ // lib/cdn.js
20
20
  var cdn_exports = {};
21
21
  __export(cdn_exports, {
22
22
  cdnShopify: () => cdnShopify,
package/dist/cdn.js CHANGED
@@ -1,4 +1,4 @@
1
- // cdn.js
1
+ // lib/cdn.js
2
2
  var cdnSrc = (asset, size, timestamp) => {
3
3
  var _a;
4
4
  const src = (_a = asset == null ? void 0 : asset.sizes) == null ? void 0 : _a[size];
package/dist/circuit.cjs CHANGED
@@ -16,7 +16,7 @@ var __copyProps = (to, from, except, desc) => {
16
16
  };
17
17
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
18
 
19
- // circuit.js
19
+ // lib/circuit.js
20
20
  var circuit_exports = {};
21
21
  __export(circuit_exports, {
22
22
  circuit: () => circuit
package/dist/circuit.js CHANGED
@@ -1,6 +1,53 @@
1
- import {
2
- circuit
3
- } from "./chunk-6HH36OK6.js";
1
+ // lib/circuit.js
2
+ var CLOSED = "CLOSED";
3
+ var OPEN = "OPEN";
4
+ var HALF_OPEN = "HALF_OPEN";
5
+ var circuit = ({
6
+ name,
7
+ threshold = 5,
8
+ timeout = 3e4
9
+ }) => {
10
+ let state = CLOSED;
11
+ let failures = 0;
12
+ let openedAt = null;
13
+ const trip = () => {
14
+ state = OPEN;
15
+ openedAt = Date.now();
16
+ console.error(`Circuit breaker OPEN: ${name}`);
17
+ };
18
+ const reset = () => {
19
+ state = CLOSED;
20
+ failures = 0;
21
+ openedAt = null;
22
+ console.log(`Circuit breaker CLOSED: ${name}`);
23
+ };
24
+ return async (fn) => {
25
+ if (state === OPEN) {
26
+ if (Date.now() - openedAt >= timeout) {
27
+ state = HALF_OPEN;
28
+ } else {
29
+ const error = new Error(`${name} is temporarily unavailable`);
30
+ error.status = 503;
31
+ throw error;
32
+ }
33
+ }
34
+ try {
35
+ const result = await fn();
36
+ if (state === HALF_OPEN) {
37
+ reset();
38
+ } else {
39
+ failures = 0;
40
+ }
41
+ return result;
42
+ } catch (error) {
43
+ failures++;
44
+ if (state === HALF_OPEN || failures >= threshold) {
45
+ trip();
46
+ }
47
+ throw error;
48
+ }
49
+ };
50
+ };
4
51
  export {
5
52
  circuit
6
53
  };
package/dist/encrypt.cjs CHANGED
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  ));
27
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
28
 
29
- // encrypt.js
29
+ // lib/encrypt.js
30
30
  var encrypt_exports = {};
31
31
  __export(encrypt_exports, {
32
32
  decrypt: () => decrypt,
@@ -35,14 +35,14 @@ __export(encrypt_exports, {
35
35
  module.exports = __toCommonJS(encrypt_exports);
36
36
  var import_crypto2 = __toESM(require("crypto"), 1);
37
37
 
38
- // token.js
38
+ // lib/token.js
39
39
  var import_crypto = __toESM(require("crypto"), 1);
40
40
  var generate = (bytes = 32, encoding = "base64url") => {
41
41
  const buf = import_crypto.default.randomBytes(bytes);
42
42
  return encoding ? buf.toString(encoding) : buf;
43
43
  };
44
44
 
45
- // encrypt.js
45
+ // lib/encrypt.js
46
46
  var ALGORITHM = "aes-256-gcm";
47
47
  var getKey = () => Buffer.from(process.env.ENCRYPT_CONNECTION_SECRET, "hex");
48
48
  var encrypt = (value) => {
package/dist/encrypt.js CHANGED
@@ -1,8 +1,41 @@
1
- import {
2
- decrypt,
3
- encrypt
4
- } from "./chunk-2DRAIKGZ.js";
5
- import "./chunk-OS2AHUWX.js";
1
+ // lib/encrypt.js
2
+ import crypto2 from "crypto";
3
+
4
+ // lib/token.js
5
+ import crypto from "crypto";
6
+ var generate = (bytes = 32, encoding = "base64url") => {
7
+ const buf = crypto.randomBytes(bytes);
8
+ return encoding ? buf.toString(encoding) : buf;
9
+ };
10
+
11
+ // lib/encrypt.js
12
+ var ALGORITHM = "aes-256-gcm";
13
+ var getKey = () => Buffer.from(process.env.ENCRYPT_CONNECTION_SECRET, "hex");
14
+ var encrypt = (value) => {
15
+ const iv = generate(12, null);
16
+ const cipher = crypto2.createCipheriv(ALGORITHM, getKey(), iv);
17
+ const data = Buffer.concat([
18
+ cipher.update(JSON.stringify(value), "utf8"),
19
+ cipher.final()
20
+ ]);
21
+ const tag = cipher.getAuthTag();
22
+ return [iv, tag, data].map((b) => b.toString("hex")).join(":");
23
+ };
24
+ var decrypt = (value) => {
25
+ if (typeof value !== "string") return value;
26
+ const [ivHex, tagHex, dataHex] = value.split(":");
27
+ const decipher = crypto2.createDecipheriv(
28
+ ALGORITHM,
29
+ getKey(),
30
+ Buffer.from(ivHex, "hex")
31
+ );
32
+ decipher.setAuthTag(Buffer.from(tagHex, "hex"));
33
+ const result = Buffer.concat([
34
+ decipher.update(Buffer.from(dataHex, "hex")),
35
+ decipher.final()
36
+ ]);
37
+ return JSON.parse(result.toString("utf8"));
38
+ };
6
39
  export {
7
40
  decrypt,
8
41
  encrypt
package/dist/fetch.cjs CHANGED
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  ));
27
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
28
 
29
- // fetch.js
29
+ // lib/fetch.js
30
30
  var fetch_exports = {};
31
31
  __export(fetch_exports, {
32
32
  queryString: () => queryString,
package/dist/fetch.js CHANGED
@@ -1,4 +1,4 @@
1
- // fetch.js
1
+ // lib/fetch.js
2
2
  import qs from "qs";
3
3
  var queryString = qs;
4
4
  var memoryToken = null;
package/dist/http.cjs CHANGED
@@ -16,7 +16,7 @@ var __copyProps = (to, from, except, desc) => {
16
16
  };
17
17
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
18
 
19
- // http.js
19
+ // lib/http.js
20
20
  var http_exports = {};
21
21
  __export(http_exports, {
22
22
  request: () => request
package/dist/http.js CHANGED
@@ -1,4 +1,4 @@
1
- // http.js
1
+ // lib/http.js
2
2
  var DEFAULT_TIMEOUT_MS = 15e3;
3
3
  var request = async ({
4
4
  body,
package/dist/nanoid.cjs CHANGED
@@ -16,7 +16,7 @@ var __copyProps = (to, from, except, desc) => {
16
16
  };
17
17
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
18
 
19
- // nanoid.js
19
+ // lib/nanoid.js
20
20
  var nanoid_exports = {};
21
21
  __export(nanoid_exports, {
22
22
  nanoid: () => nanoid,
package/dist/nanoid.js CHANGED
@@ -1,4 +1,4 @@
1
- // nanoid.js
1
+ // lib/nanoid.js
2
2
  import { customAlphabet } from "nanoid";
3
3
  var nanoid = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 8);
4
4
  var DEFAULT_MAX_RETRIES = 5;
@@ -26,23 +26,26 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  ));
27
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
28
 
29
- // oauth.js
29
+ // lib/oauth/index.js
30
30
  var oauth_exports = {};
31
31
  __export(oauth_exports, {
32
- DEVELOPER_ALLOWED_SCOPES: () => DEVELOPER_ALLOWED_SCOPES,
33
- SCOPES: () => SCOPES,
34
32
  compare: () => compare,
35
33
  createOAuthClient: () => createOAuthClient,
34
+ developer: () => developer,
35
+ developerOptions: () => developerOptions,
36
36
  generateToken: () => generateToken,
37
37
  hashToken: () => hashToken,
38
38
  intersect: () => intersect,
39
39
  isDeveloperAllowed: () => isDeveloperAllowed,
40
40
  parse: () => parse,
41
+ scopeLabels: () => scopeLabels,
42
+ scopeOptions: () => scopeOptions,
43
+ scopes: () => scopes,
41
44
  validate: () => validate
42
45
  });
43
46
  module.exports = __toCommonJS(oauth_exports);
44
47
 
45
- // token.js
48
+ // lib/token.js
46
49
  var import_crypto = __toESM(require("crypto"), 1);
47
50
  var generate = (bytes = 32, encoding = "base64url") => {
48
51
  const buf = import_crypto.default.randomBytes(bytes);
@@ -64,8 +67,8 @@ var compare = (a, b) => {
64
67
  );
65
68
  };
66
69
 
67
- // oauth.js
68
- var SCOPES = [
70
+ // lib/oauth/index.js
71
+ var scopes = [
69
72
  "profile:read",
70
73
  "profile:write",
71
74
  "organization:read",
@@ -82,7 +85,7 @@ var SCOPES = [
82
85
  "billing:write",
83
86
  "admin"
84
87
  ];
85
- var DEVELOPER_ALLOWED_SCOPES = [
88
+ var developer = [
86
89
  "profile:read",
87
90
  "organization:read",
88
91
  "campaigns:read",
@@ -99,9 +102,9 @@ var parse = (raw) => {
99
102
  if (Array.isArray(raw)) return raw;
100
103
  return String(raw).split(/\s+/).filter(Boolean);
101
104
  };
102
- var validate = (scopes) => {
103
- const parsed = parse(scopes);
104
- const invalid = parsed.filter((scope) => !SCOPES.includes(scope));
105
+ var validate = (scopes2) => {
106
+ const parsed = parse(scopes2);
107
+ const invalid = parsed.filter((scope) => !scopes2.includes(scope));
105
108
  return {
106
109
  invalid,
107
110
  valid: invalid.length === 0
@@ -112,14 +115,37 @@ var intersect = (requested, allowed) => {
112
115
  const a = parse(allowed);
113
116
  return r.filter((scope) => a.includes(scope));
114
117
  };
115
- var isDeveloperAllowed = (scope) => DEVELOPER_ALLOWED_SCOPES.includes(scope);
118
+ var isDeveloperAllowed = (scope) => developer.includes(scope);
119
+ var scopeLabels = {
120
+ "profile:read": "Read own profile",
121
+ "profile:write": "Update own profile",
122
+ "organization:read": "Read organization info",
123
+ "organization:write": "Manage organization",
124
+ "campaigns:read": "Read campaigns",
125
+ "campaigns:write": "Manage campaigns",
126
+ "contacts:read": "Read contacts",
127
+ "contacts:write": "Manage contacts",
128
+ "workflows:read": "Read workflows",
129
+ "workflows:write": "Manage workflows",
130
+ "connections:read": "Read integrations",
131
+ "connections:write": "Manage integrations",
132
+ "billing:read": "Read billing",
133
+ "billing:write": "Manage billing",
134
+ admin: "Admin access"
135
+ };
136
+ var toOption = (scope) => ({
137
+ key: scopeLabels[scope] || scope,
138
+ value: scope
139
+ });
140
+ var developerOptions = developer.map(toOption);
141
+ var scopeOptions = scopes.map(toOption);
116
142
  var hashToken = (raw) => hash(raw, process.env.OAUTH_TOKEN_HMAC_KEY);
117
143
  var generateToken = () => generate(32, "base64url");
118
144
  var REFRESH_LEAD_SECONDS = 60;
119
145
  var createOAuthClient = ({
120
146
  clientId,
121
147
  clientSecret,
122
- scopes = [],
148
+ scopes: scopes2 = [],
123
149
  tokenUri
124
150
  }) => {
125
151
  if (!clientId) throw new Error("createOAuthClient: clientId required");
@@ -135,8 +161,8 @@ var createOAuthClient = ({
135
161
  const fetchToken = async () => {
136
162
  const params = new URLSearchParams();
137
163
  params.set("grant_type", "client_credentials");
138
- if (scopes.length > 0) {
139
- params.set("scope", scopes.join(" "));
164
+ if (scopes2.length > 0) {
165
+ params.set("scope", scopes2.join(" "));
140
166
  }
141
167
  const basic = Buffer.from(clientId + ":" + clientSecret).toString("base64");
142
168
  const response = await fetch(
@@ -183,14 +209,17 @@ var createOAuthClient = ({
183
209
  };
184
210
  // Annotate the CommonJS export names for ESM import in node:
185
211
  0 && (module.exports = {
186
- DEVELOPER_ALLOWED_SCOPES,
187
- SCOPES,
188
212
  compare,
189
213
  createOAuthClient,
214
+ developer,
215
+ developerOptions,
190
216
  generateToken,
191
217
  hashToken,
192
218
  intersect,
193
219
  isDeveloperAllowed,
194
220
  parse,
221
+ scopeLabels,
222
+ scopeOptions,
223
+ scopes,
195
224
  validate
196
225
  });
@@ -1,12 +1,12 @@
1
- import { generate, hash } from './token.js';
2
- export { compare } from './token.js';
1
+ import { generate, hash } from '../token.cjs';
2
+ export { compare } from '../token.cjs';
3
3
  import 'crypto';
4
4
 
5
5
  // Shared OAuth helpers used across drawbridge-api, drawbridge-sync,
6
6
  // drawbridge-app-web, and any first-party app that needs to participate
7
7
  // in the OAuth 2.1 provider:
8
8
  //
9
- // - SCOPES, DEVELOPER_ALLOWED_SCOPES: the locked scope vocabulary
9
+ // - scopes, developer: the locked scope vocabulary
10
10
  // - parse, validate, intersect, isDeveloperAllowed: scope set operations
11
11
  // - hashToken, generateToken, compare: opaque-token primitives
12
12
  // - createOAuthClient: cached client_credentials access token getter
@@ -16,7 +16,7 @@ import 'crypto';
16
16
  // Scope vocabulary (locked — 15 scopes, 10 in the developer-allowed subset)
17
17
  // ─────────────────────────────────────────────────────────────────────────
18
18
 
19
- const SCOPES = [
19
+ const scopes = [
20
20
  'profile:read',
21
21
  'profile:write',
22
22
  'organization:read',
@@ -37,7 +37,7 @@ const SCOPES = [
37
37
  // Subset granted to third-party developer apps via /oauth/clients self-service
38
38
  // registration. Excludes profile:write (account-level), organization:write
39
39
  // (org admin only), billing:* (financial), admin (privileged).
40
- const DEVELOPER_ALLOWED_SCOPES = [
40
+ const developer = [
41
41
  'profile:read',
42
42
  'organization:read',
43
43
  'campaigns:read',
@@ -63,7 +63,7 @@ const parse = ( raw ) => {
63
63
  const validate = ( scopes ) => {
64
64
 
65
65
  const parsed = parse( scopes );
66
- const invalid = parsed.filter( ( scope ) => ! SCOPES.includes( scope ) );
66
+ const invalid = parsed.filter( ( scope ) => ! scopes.includes( scope ) );
67
67
 
68
68
  return {
69
69
  invalid,
@@ -81,7 +81,39 @@ const intersect = ( requested, allowed ) => {
81
81
 
82
82
  };
83
83
 
84
- const isDeveloperAllowed = ( scope ) => DEVELOPER_ALLOWED_SCOPES.includes( scope );
84
+ const isDeveloperAllowed = ( scope ) => developer.includes( scope );
85
+
86
+ // User-facing labels for each scope — used by UI components rendering
87
+ // scope pickers. Centralized here so the dashboard, future admin tools,
88
+ // and any other consumer surface the same strings.
89
+ const scopeLabels = {
90
+ 'profile:read' : 'Read own profile',
91
+ 'profile:write' : 'Update own profile',
92
+ 'organization:read' : 'Read organization info',
93
+ 'organization:write' : 'Manage organization',
94
+ 'campaigns:read' : 'Read campaigns',
95
+ 'campaigns:write' : 'Manage campaigns',
96
+ 'contacts:read' : 'Read contacts',
97
+ 'contacts:write' : 'Manage contacts',
98
+ 'workflows:read' : 'Read workflows',
99
+ 'workflows:write' : 'Manage workflows',
100
+ 'connections:read' : 'Read integrations',
101
+ 'connections:write' : 'Manage integrations',
102
+ 'billing:read' : 'Read billing',
103
+ 'billing:write' : 'Manage billing',
104
+ admin : 'Admin access'
105
+ };
106
+
107
+ const toOption = ( scope ) => ({
108
+ key : scopeLabels[ scope ] || scope,
109
+ value : scope
110
+ });
111
+
112
+ // `{ key, value }` option arrays ready to feed any standard checkbox-list
113
+ // component. Derived from the canonical scope arrays so vocabulary
114
+ // changes auto-propagate to every consumer on the next utils bump.
115
+ const developerOptions = developer.map( toOption );
116
+ const scopeOptions = scopes.map( toOption );
85
117
 
86
118
  // ─────────────────────────────────────────────────────────────────────────
87
119
  // Token primitives — convenience wrappers over drawbridge-utils/token that
@@ -228,4 +260,4 @@ const createOAuthClient = ({
228
260
 
229
261
  };
230
262
 
231
- export { DEVELOPER_ALLOWED_SCOPES, SCOPES, createOAuthClient, generateToken, hashToken, intersect, isDeveloperAllowed, parse, validate };
263
+ export { createOAuthClient, developer, developerOptions, generateToken, hashToken, intersect, isDeveloperAllowed, parse, scopeLabels, scopeOptions, scopes, validate };