@drawbridge/drawbridge-utils 0.0.78 → 0.0.80
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 +31 -4
- package/dist/ai.d.cts +35 -4
- package/dist/ai.d.ts +35 -4
- package/dist/ai.js +21 -4
- package/dist/email.cjs +42 -0
- package/dist/email.d.cts +36 -0
- package/dist/email.d.ts +36 -0
- package/dist/email.js +18 -0
- package/dist/fetch.cjs +6 -0
- package/dist/fetch.d.cts +16 -1
- package/dist/fetch.d.ts +16 -1
- package/dist/fetch.js +5 -0
- package/package.json +7 -2
package/dist/ai.cjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
1
2
|
var __defProp = Object.defineProperty;
|
|
2
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
7
|
var __export = (target, all) => {
|
|
6
8
|
for (var name in all)
|
|
@@ -14,6 +16,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
14
16
|
}
|
|
15
17
|
return to;
|
|
16
18
|
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
17
27
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
28
|
|
|
19
29
|
// lib/ai.js
|
|
@@ -251,6 +261,13 @@ var ai = {
|
|
|
251
261
|
bill: ({ db, user, model, usage, tools }) => (user == null ? void 0 : user.id) ? () => billRequest({ db, user, model, usage, tools }) : () => Promise.resolve()
|
|
252
262
|
};
|
|
253
263
|
|
|
264
|
+
// lib/fetch.js
|
|
265
|
+
var import_qs = __toESM(require("qs"), 1);
|
|
266
|
+
var isTransientError = (error) => {
|
|
267
|
+
const status = error == null ? void 0 : error.status;
|
|
268
|
+
return !status || status === 429 || status >= 500;
|
|
269
|
+
};
|
|
270
|
+
|
|
254
271
|
// lib/ai.js
|
|
255
272
|
var google_client = new import_genai.GoogleGenAI({
|
|
256
273
|
apiKey: process.env.GOOGLE_GEMINI_API_KEY
|
|
@@ -260,6 +277,16 @@ var googleCircuit = circuit({
|
|
|
260
277
|
threshold: 3,
|
|
261
278
|
timeout: 6e4
|
|
262
279
|
});
|
|
280
|
+
var withRetries = async (fn, { attempts = 3, delay = 500 } = {}) => {
|
|
281
|
+
for (let attempt = 1; ; attempt++) {
|
|
282
|
+
try {
|
|
283
|
+
return await fn();
|
|
284
|
+
} catch (error) {
|
|
285
|
+
if (attempt >= attempts || !isTransientError(error)) throw error;
|
|
286
|
+
await new Promise((resolve) => setTimeout(resolve, delay * attempt));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
263
290
|
var models = {
|
|
264
291
|
image: "gemini-2.5-flash-image",
|
|
265
292
|
text: "gemini-3.5-flash"
|
|
@@ -293,14 +320,14 @@ var google = {
|
|
|
293
320
|
...refs.map((ref) => ({ inlineData: { data: ref.data, mimeType: ref.mimeType } })),
|
|
294
321
|
{ text: prompt.join("\n") }
|
|
295
322
|
];
|
|
296
|
-
const response = await googleCircuit(() => google_client.models.generateContent({
|
|
323
|
+
const response = await googleCircuit(() => withRetries(() => google_client.models.generateContent({
|
|
297
324
|
model,
|
|
298
325
|
contents: { parts },
|
|
299
326
|
config: {
|
|
300
327
|
...config,
|
|
301
328
|
responseModalities: ["Image"]
|
|
302
329
|
}
|
|
303
|
-
}));
|
|
330
|
+
})));
|
|
304
331
|
const content = (_f = (_e = (_d = (_c = (_b = (_a = response == null ? void 0 : response.candidates) == null ? void 0 : _a[0]) == null ? void 0 : _b.content) == null ? void 0 : _c.parts) == null ? void 0 : _d[0]) == null ? void 0 : _e.inlineData) == null ? void 0 : _f.data;
|
|
305
332
|
if (!content) {
|
|
306
333
|
throw new Error("Could not generate from prompt. Please try again with a different prompt.");
|
|
@@ -321,14 +348,14 @@ var google = {
|
|
|
321
348
|
}) => {
|
|
322
349
|
const model = models.text;
|
|
323
350
|
const contents = parts ? { parts } : images.length ? { parts: [...images.map((image) => ({ inlineData: { data: image.data, mimeType: image.mimeType } })), { text: prompt.join("\n") }] } : prompt.join("\n");
|
|
324
|
-
const response = await googleCircuit(() => google_client.models.generateContent({
|
|
351
|
+
const response = await googleCircuit(() => withRetries(() => google_client.models.generateContent({
|
|
325
352
|
config: {
|
|
326
353
|
...config,
|
|
327
354
|
responseModalities: ["Text"]
|
|
328
355
|
},
|
|
329
356
|
contents,
|
|
330
357
|
model
|
|
331
|
-
}));
|
|
358
|
+
})));
|
|
332
359
|
const content = response == null ? void 0 : response.text;
|
|
333
360
|
if (!content) {
|
|
334
361
|
throw new Error("Could not generate from prompt. Please try again with a different prompt.");
|
package/dist/ai.d.cts
CHANGED
|
@@ -2,9 +2,11 @@ import { GoogleGenAI } from '@google/genai';
|
|
|
2
2
|
import { circuit } from './circuit.cjs';
|
|
3
3
|
import { ai } from './billing.cjs';
|
|
4
4
|
export { MARKUP } from './billing.cjs';
|
|
5
|
+
import { isTransientError } from './fetch.cjs';
|
|
5
6
|
import './transactions.cjs';
|
|
6
7
|
import '@drawbridge/drawbridge-telemetry';
|
|
7
8
|
import './usage.cjs';
|
|
9
|
+
import 'qs';
|
|
8
10
|
|
|
9
11
|
// AI request entry point. Wraps the Google GenAI client with a per-process
|
|
10
12
|
// circuit breaker, owns the per-model pricing table, and atomically debits
|
|
@@ -35,6 +37,35 @@ const googleCircuit = circuit({
|
|
|
35
37
|
timeout : 60000
|
|
36
38
|
});
|
|
37
39
|
|
|
40
|
+
// Bounded retry for the raw model call. A single transient failure — undici
|
|
41
|
+
// "fetch failed" on a resource-starved pod, a 429, a 5xx — must not lose the
|
|
42
|
+
// generation step OR count toward the breaker: three of those trip it and
|
|
43
|
+
// hard-fail every AI call for the next 60s. Runs INSIDE the googleCircuit
|
|
44
|
+
// callback so the breaker only counts calls that failed after retries were
|
|
45
|
+
// exhausted, and an OPEN circuit still fails fast without retrying.
|
|
46
|
+
// isTransientError treats status-less errors as transient, which is correct
|
|
47
|
+
// here: the wrapped fn is exactly one SDK call, and the GenAI SDK reports
|
|
48
|
+
// network-level failures as plain Errors with no `.status`.
|
|
49
|
+
const withRetries = async ( fn, { attempts = 3, delay = 500 } = {} ) => {
|
|
50
|
+
|
|
51
|
+
for( let attempt = 1; ; attempt++ ){
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
|
|
55
|
+
return await fn();
|
|
56
|
+
|
|
57
|
+
} catch ( error ) {
|
|
58
|
+
|
|
59
|
+
if( attempt >= attempts || ! isTransientError( error ) ) throw error;
|
|
60
|
+
|
|
61
|
+
await new Promise( ( resolve ) => setTimeout( resolve, delay * attempt ) );
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
};
|
|
68
|
+
|
|
38
69
|
// gemini-2.5-flash was retired by Google (generateContent → 404 "no longer
|
|
39
70
|
// available"), taking down every generation. gemini-3.5-flash is the current
|
|
40
71
|
// stable flash successor. The image model is a distinct model and still live,
|
|
@@ -89,14 +120,14 @@ const google = {
|
|
|
89
120
|
{ text : prompt.join( '\n' ) }
|
|
90
121
|
];
|
|
91
122
|
|
|
92
|
-
const response = await googleCircuit( () => google_client.models.generateContent({
|
|
123
|
+
const response = await googleCircuit( () => withRetries( () => google_client.models.generateContent({
|
|
93
124
|
model,
|
|
94
125
|
contents : { parts },
|
|
95
126
|
config : {
|
|
96
127
|
...config,
|
|
97
128
|
responseModalities : [ 'Image' ]
|
|
98
129
|
}
|
|
99
|
-
}) );
|
|
130
|
+
}) ) );
|
|
100
131
|
|
|
101
132
|
const content = response?.candidates?.[ 0 ]?.content?.parts?.[ 0 ]?.inlineData?.data;
|
|
102
133
|
|
|
@@ -143,14 +174,14 @@ const google = {
|
|
|
143
174
|
? { parts : [ ...images.map( ( image ) => ({ inlineData : { data : image.data, mimeType : image.mimeType } }) ), { text : prompt.join( '\n' ) } ] }
|
|
144
175
|
: prompt.join( '\n' );
|
|
145
176
|
|
|
146
|
-
const response = await googleCircuit( () => google_client.models.generateContent({
|
|
177
|
+
const response = await googleCircuit( () => withRetries( () => google_client.models.generateContent({
|
|
147
178
|
config : {
|
|
148
179
|
...config,
|
|
149
180
|
responseModalities : [ 'Text' ]
|
|
150
181
|
},
|
|
151
182
|
contents,
|
|
152
183
|
model
|
|
153
|
-
}) );
|
|
184
|
+
}) ) );
|
|
154
185
|
|
|
155
186
|
const content = response?.text;
|
|
156
187
|
|
package/dist/ai.d.ts
CHANGED
|
@@ -2,9 +2,11 @@ import { GoogleGenAI } from '@google/genai';
|
|
|
2
2
|
import { circuit } from './circuit.js';
|
|
3
3
|
import { ai } from './billing.js';
|
|
4
4
|
export { MARKUP } from './billing.js';
|
|
5
|
+
import { isTransientError } from './fetch.js';
|
|
5
6
|
import './transactions.js';
|
|
6
7
|
import '@drawbridge/drawbridge-telemetry';
|
|
7
8
|
import './usage.js';
|
|
9
|
+
import 'qs';
|
|
8
10
|
|
|
9
11
|
// AI request entry point. Wraps the Google GenAI client with a per-process
|
|
10
12
|
// circuit breaker, owns the per-model pricing table, and atomically debits
|
|
@@ -35,6 +37,35 @@ const googleCircuit = circuit({
|
|
|
35
37
|
timeout : 60000
|
|
36
38
|
});
|
|
37
39
|
|
|
40
|
+
// Bounded retry for the raw model call. A single transient failure — undici
|
|
41
|
+
// "fetch failed" on a resource-starved pod, a 429, a 5xx — must not lose the
|
|
42
|
+
// generation step OR count toward the breaker: three of those trip it and
|
|
43
|
+
// hard-fail every AI call for the next 60s. Runs INSIDE the googleCircuit
|
|
44
|
+
// callback so the breaker only counts calls that failed after retries were
|
|
45
|
+
// exhausted, and an OPEN circuit still fails fast without retrying.
|
|
46
|
+
// isTransientError treats status-less errors as transient, which is correct
|
|
47
|
+
// here: the wrapped fn is exactly one SDK call, and the GenAI SDK reports
|
|
48
|
+
// network-level failures as plain Errors with no `.status`.
|
|
49
|
+
const withRetries = async ( fn, { attempts = 3, delay = 500 } = {} ) => {
|
|
50
|
+
|
|
51
|
+
for( let attempt = 1; ; attempt++ ){
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
|
|
55
|
+
return await fn();
|
|
56
|
+
|
|
57
|
+
} catch ( error ) {
|
|
58
|
+
|
|
59
|
+
if( attempt >= attempts || ! isTransientError( error ) ) throw error;
|
|
60
|
+
|
|
61
|
+
await new Promise( ( resolve ) => setTimeout( resolve, delay * attempt ) );
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
};
|
|
68
|
+
|
|
38
69
|
// gemini-2.5-flash was retired by Google (generateContent → 404 "no longer
|
|
39
70
|
// available"), taking down every generation. gemini-3.5-flash is the current
|
|
40
71
|
// stable flash successor. The image model is a distinct model and still live,
|
|
@@ -89,14 +120,14 @@ const google = {
|
|
|
89
120
|
{ text : prompt.join( '\n' ) }
|
|
90
121
|
];
|
|
91
122
|
|
|
92
|
-
const response = await googleCircuit( () => google_client.models.generateContent({
|
|
123
|
+
const response = await googleCircuit( () => withRetries( () => google_client.models.generateContent({
|
|
93
124
|
model,
|
|
94
125
|
contents : { parts },
|
|
95
126
|
config : {
|
|
96
127
|
...config,
|
|
97
128
|
responseModalities : [ 'Image' ]
|
|
98
129
|
}
|
|
99
|
-
}) );
|
|
130
|
+
}) ) );
|
|
100
131
|
|
|
101
132
|
const content = response?.candidates?.[ 0 ]?.content?.parts?.[ 0 ]?.inlineData?.data;
|
|
102
133
|
|
|
@@ -143,14 +174,14 @@ const google = {
|
|
|
143
174
|
? { parts : [ ...images.map( ( image ) => ({ inlineData : { data : image.data, mimeType : image.mimeType } }) ), { text : prompt.join( '\n' ) } ] }
|
|
144
175
|
: prompt.join( '\n' );
|
|
145
176
|
|
|
146
|
-
const response = await googleCircuit( () => google_client.models.generateContent({
|
|
177
|
+
const response = await googleCircuit( () => withRetries( () => google_client.models.generateContent({
|
|
147
178
|
config : {
|
|
148
179
|
...config,
|
|
149
180
|
responseModalities : [ 'Text' ]
|
|
150
181
|
},
|
|
151
182
|
contents,
|
|
152
183
|
model
|
|
153
|
-
}) );
|
|
184
|
+
}) ) );
|
|
154
185
|
|
|
155
186
|
const content = response?.text;
|
|
156
187
|
|
package/dist/ai.js
CHANGED
|
@@ -227,6 +227,13 @@ var ai = {
|
|
|
227
227
|
bill: ({ db, user, model, usage, tools }) => (user == null ? void 0 : user.id) ? () => billRequest({ db, user, model, usage, tools }) : () => Promise.resolve()
|
|
228
228
|
};
|
|
229
229
|
|
|
230
|
+
// lib/fetch.js
|
|
231
|
+
import qs from "qs";
|
|
232
|
+
var isTransientError = (error) => {
|
|
233
|
+
const status = error == null ? void 0 : error.status;
|
|
234
|
+
return !status || status === 429 || status >= 500;
|
|
235
|
+
};
|
|
236
|
+
|
|
230
237
|
// lib/ai.js
|
|
231
238
|
var google_client = new GoogleGenAI({
|
|
232
239
|
apiKey: process.env.GOOGLE_GEMINI_API_KEY
|
|
@@ -236,6 +243,16 @@ var googleCircuit = circuit({
|
|
|
236
243
|
threshold: 3,
|
|
237
244
|
timeout: 6e4
|
|
238
245
|
});
|
|
246
|
+
var withRetries = async (fn, { attempts = 3, delay = 500 } = {}) => {
|
|
247
|
+
for (let attempt = 1; ; attempt++) {
|
|
248
|
+
try {
|
|
249
|
+
return await fn();
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (attempt >= attempts || !isTransientError(error)) throw error;
|
|
252
|
+
await new Promise((resolve) => setTimeout(resolve, delay * attempt));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
239
256
|
var models = {
|
|
240
257
|
image: "gemini-2.5-flash-image",
|
|
241
258
|
text: "gemini-3.5-flash"
|
|
@@ -269,14 +286,14 @@ var google = {
|
|
|
269
286
|
...refs.map((ref) => ({ inlineData: { data: ref.data, mimeType: ref.mimeType } })),
|
|
270
287
|
{ text: prompt.join("\n") }
|
|
271
288
|
];
|
|
272
|
-
const response = await googleCircuit(() => google_client.models.generateContent({
|
|
289
|
+
const response = await googleCircuit(() => withRetries(() => google_client.models.generateContent({
|
|
273
290
|
model,
|
|
274
291
|
contents: { parts },
|
|
275
292
|
config: {
|
|
276
293
|
...config,
|
|
277
294
|
responseModalities: ["Image"]
|
|
278
295
|
}
|
|
279
|
-
}));
|
|
296
|
+
})));
|
|
280
297
|
const content = (_f = (_e = (_d = (_c = (_b = (_a = response == null ? void 0 : response.candidates) == null ? void 0 : _a[0]) == null ? void 0 : _b.content) == null ? void 0 : _c.parts) == null ? void 0 : _d[0]) == null ? void 0 : _e.inlineData) == null ? void 0 : _f.data;
|
|
281
298
|
if (!content) {
|
|
282
299
|
throw new Error("Could not generate from prompt. Please try again with a different prompt.");
|
|
@@ -297,14 +314,14 @@ var google = {
|
|
|
297
314
|
}) => {
|
|
298
315
|
const model = models.text;
|
|
299
316
|
const contents = parts ? { parts } : images.length ? { parts: [...images.map((image) => ({ inlineData: { data: image.data, mimeType: image.mimeType } })), { text: prompt.join("\n") }] } : prompt.join("\n");
|
|
300
|
-
const response = await googleCircuit(() => google_client.models.generateContent({
|
|
317
|
+
const response = await googleCircuit(() => withRetries(() => google_client.models.generateContent({
|
|
301
318
|
config: {
|
|
302
319
|
...config,
|
|
303
320
|
responseModalities: ["Text"]
|
|
304
321
|
},
|
|
305
322
|
contents,
|
|
306
323
|
model
|
|
307
|
-
}));
|
|
324
|
+
})));
|
|
308
325
|
const content = response == null ? void 0 : response.text;
|
|
309
326
|
if (!content) {
|
|
310
327
|
throw new Error("Could not generate from prompt. Please try again with a different prompt.");
|
package/dist/email.cjs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// lib/email.js
|
|
20
|
+
var email_exports = {};
|
|
21
|
+
__export(email_exports, {
|
|
22
|
+
toCanonicalEmail: () => toCanonicalEmail
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(email_exports);
|
|
25
|
+
var GMAIL_DOMAINS = /* @__PURE__ */ new Set(["gmail.com", "googlemail.com"]);
|
|
26
|
+
var toCanonicalEmail = (value) => {
|
|
27
|
+
if (!value || typeof value !== "string") return null;
|
|
28
|
+
const email = value.trim().toLowerCase();
|
|
29
|
+
const at = email.lastIndexOf("@");
|
|
30
|
+
if (at < 1 || at === email.length - 1) return null;
|
|
31
|
+
let local = email.slice(0, at);
|
|
32
|
+
const domain = email.slice(at + 1);
|
|
33
|
+
const plus = local.indexOf("+");
|
|
34
|
+
if (plus > 0) local = local.slice(0, plus);
|
|
35
|
+
if (GMAIL_DOMAINS.has(domain)) local = local.replaceAll(".", "");
|
|
36
|
+
if (!local) return null;
|
|
37
|
+
return local + "@" + domain;
|
|
38
|
+
};
|
|
39
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
40
|
+
0 && (module.exports = {
|
|
41
|
+
toCanonicalEmail
|
|
42
|
+
});
|
package/dist/email.d.cts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Gmail treats dots in the local part as insignificant; these are the only
|
|
2
|
+
// domains where dot-stripping is safe.
|
|
3
|
+
const GMAIL_DOMAINS = new Set( [ 'gmail.com', 'googlemail.com' ] );
|
|
4
|
+
|
|
5
|
+
// Canonicalize an email for identity matching (NOT for delivery — always send
|
|
6
|
+
// to the raw address the user typed): trim, lowercase, strip the +tag
|
|
7
|
+
// subaddress, and strip local-part dots on Gmail-family domains. Heuristic by
|
|
8
|
+
// design — a canonical collision only ever merges identities, never loses a
|
|
9
|
+
// deliverable address, so lenient is the right bias. Unparseable input → null.
|
|
10
|
+
const toCanonicalEmail = ( value ) => {
|
|
11
|
+
|
|
12
|
+
if( ! value || typeof value !== 'string' ) return null;
|
|
13
|
+
|
|
14
|
+
const email = value.trim().toLowerCase();
|
|
15
|
+
|
|
16
|
+
const at = email.lastIndexOf( '@' );
|
|
17
|
+
|
|
18
|
+
if( at < 1 || at === email.length - 1 ) return null;
|
|
19
|
+
|
|
20
|
+
let local = email.slice( 0, at );
|
|
21
|
+
const domain = email.slice( at + 1 );
|
|
22
|
+
|
|
23
|
+
// index 0 would empty the local part — leave a leading plus alone
|
|
24
|
+
const plus = local.indexOf( '+' );
|
|
25
|
+
|
|
26
|
+
if( plus > 0 ) local = local.slice( 0, plus );
|
|
27
|
+
|
|
28
|
+
if( GMAIL_DOMAINS.has( domain ) ) local = local.replaceAll( '.', '' );
|
|
29
|
+
|
|
30
|
+
if( ! local ) return null;
|
|
31
|
+
|
|
32
|
+
return local + '@' + domain;
|
|
33
|
+
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export { toCanonicalEmail };
|
package/dist/email.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Gmail treats dots in the local part as insignificant; these are the only
|
|
2
|
+
// domains where dot-stripping is safe.
|
|
3
|
+
const GMAIL_DOMAINS = new Set( [ 'gmail.com', 'googlemail.com' ] );
|
|
4
|
+
|
|
5
|
+
// Canonicalize an email for identity matching (NOT for delivery — always send
|
|
6
|
+
// to the raw address the user typed): trim, lowercase, strip the +tag
|
|
7
|
+
// subaddress, and strip local-part dots on Gmail-family domains. Heuristic by
|
|
8
|
+
// design — a canonical collision only ever merges identities, never loses a
|
|
9
|
+
// deliverable address, so lenient is the right bias. Unparseable input → null.
|
|
10
|
+
const toCanonicalEmail = ( value ) => {
|
|
11
|
+
|
|
12
|
+
if( ! value || typeof value !== 'string' ) return null;
|
|
13
|
+
|
|
14
|
+
const email = value.trim().toLowerCase();
|
|
15
|
+
|
|
16
|
+
const at = email.lastIndexOf( '@' );
|
|
17
|
+
|
|
18
|
+
if( at < 1 || at === email.length - 1 ) return null;
|
|
19
|
+
|
|
20
|
+
let local = email.slice( 0, at );
|
|
21
|
+
const domain = email.slice( at + 1 );
|
|
22
|
+
|
|
23
|
+
// index 0 would empty the local part — leave a leading plus alone
|
|
24
|
+
const plus = local.indexOf( '+' );
|
|
25
|
+
|
|
26
|
+
if( plus > 0 ) local = local.slice( 0, plus );
|
|
27
|
+
|
|
28
|
+
if( GMAIL_DOMAINS.has( domain ) ) local = local.replaceAll( '.', '' );
|
|
29
|
+
|
|
30
|
+
if( ! local ) return null;
|
|
31
|
+
|
|
32
|
+
return local + '@' + domain;
|
|
33
|
+
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export { toCanonicalEmail };
|
package/dist/email.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// lib/email.js
|
|
2
|
+
var GMAIL_DOMAINS = /* @__PURE__ */ new Set(["gmail.com", "googlemail.com"]);
|
|
3
|
+
var toCanonicalEmail = (value) => {
|
|
4
|
+
if (!value || typeof value !== "string") return null;
|
|
5
|
+
const email = value.trim().toLowerCase();
|
|
6
|
+
const at = email.lastIndexOf("@");
|
|
7
|
+
if (at < 1 || at === email.length - 1) return null;
|
|
8
|
+
let local = email.slice(0, at);
|
|
9
|
+
const domain = email.slice(at + 1);
|
|
10
|
+
const plus = local.indexOf("+");
|
|
11
|
+
if (plus > 0) local = local.slice(0, plus);
|
|
12
|
+
if (GMAIL_DOMAINS.has(domain)) local = local.replaceAll(".", "");
|
|
13
|
+
if (!local) return null;
|
|
14
|
+
return local + "@" + domain;
|
|
15
|
+
};
|
|
16
|
+
export {
|
|
17
|
+
toCanonicalEmail
|
|
18
|
+
};
|
package/dist/fetch.cjs
CHANGED
|
@@ -29,6 +29,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
29
29
|
// lib/fetch.js
|
|
30
30
|
var fetch_exports = {};
|
|
31
31
|
__export(fetch_exports, {
|
|
32
|
+
isTransientError: () => isTransientError,
|
|
32
33
|
queryString: () => queryString,
|
|
33
34
|
request: () => request,
|
|
34
35
|
setMemoryToken: () => setMemoryToken
|
|
@@ -109,8 +110,13 @@ var request = async ({
|
|
|
109
110
|
}
|
|
110
111
|
return res;
|
|
111
112
|
};
|
|
113
|
+
var isTransientError = (error) => {
|
|
114
|
+
const status = error == null ? void 0 : error.status;
|
|
115
|
+
return !status || status === 429 || status >= 500;
|
|
116
|
+
};
|
|
112
117
|
// Annotate the CommonJS export names for ESM import in node:
|
|
113
118
|
0 && (module.exports = {
|
|
119
|
+
isTransientError,
|
|
114
120
|
queryString,
|
|
115
121
|
request,
|
|
116
122
|
setMemoryToken
|
package/dist/fetch.d.cts
CHANGED
|
@@ -112,4 +112,19 @@ const request = async ({
|
|
|
112
112
|
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
// Classifies an error thrown by request() — which carries `.status` from the
|
|
116
|
+
// HTTP response, or no status for a network / parse failure. Transient errors
|
|
117
|
+
// ( rate limits, server errors, connection blips ) may succeed on retry, so
|
|
118
|
+
// callers should offer a reload rather than treating them as an auth failure
|
|
119
|
+
// ( → sign-in / 401 ) or a missing resource ( → 404 ). Reserving those hard
|
|
120
|
+
// outcomes for genuine 4xx keeps a rate-limited request from bouncing between
|
|
121
|
+
// error screens in a redirect loop.
|
|
122
|
+
const isTransientError = ( error ) => {
|
|
123
|
+
|
|
124
|
+
const status = error?.status;
|
|
125
|
+
|
|
126
|
+
return ! status || status === 429 || status >= 500;
|
|
127
|
+
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export { isTransientError, queryString, request, setMemoryToken };
|
package/dist/fetch.d.ts
CHANGED
|
@@ -112,4 +112,19 @@ const request = async ({
|
|
|
112
112
|
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
// Classifies an error thrown by request() — which carries `.status` from the
|
|
116
|
+
// HTTP response, or no status for a network / parse failure. Transient errors
|
|
117
|
+
// ( rate limits, server errors, connection blips ) may succeed on retry, so
|
|
118
|
+
// callers should offer a reload rather than treating them as an auth failure
|
|
119
|
+
// ( → sign-in / 401 ) or a missing resource ( → 404 ). Reserving those hard
|
|
120
|
+
// outcomes for genuine 4xx keeps a rate-limited request from bouncing between
|
|
121
|
+
// error screens in a redirect loop.
|
|
122
|
+
const isTransientError = ( error ) => {
|
|
123
|
+
|
|
124
|
+
const status = error?.status;
|
|
125
|
+
|
|
126
|
+
return ! status || status === 429 || status >= 500;
|
|
127
|
+
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export { isTransientError, queryString, request, setMemoryToken };
|
package/dist/fetch.js
CHANGED
|
@@ -74,7 +74,12 @@ var request = async ({
|
|
|
74
74
|
}
|
|
75
75
|
return res;
|
|
76
76
|
};
|
|
77
|
+
var isTransientError = (error) => {
|
|
78
|
+
const status = error == null ? void 0 : error.status;
|
|
79
|
+
return !status || status === 429 || status >= 500;
|
|
80
|
+
};
|
|
77
81
|
export {
|
|
82
|
+
isTransientError,
|
|
78
83
|
queryString,
|
|
79
84
|
request,
|
|
80
85
|
setMemoryToken
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"type": "module",
|
|
3
3
|
"dependencies": {
|
|
4
4
|
"@drawbridge/drawbridge-agents": "0.0.10",
|
|
5
|
-
"@drawbridge/drawbridge-telemetry": "0.0.
|
|
5
|
+
"@drawbridge/drawbridge-telemetry": "0.0.15",
|
|
6
6
|
"@google/genai": "1.30.0",
|
|
7
7
|
"axios": "1.16.0",
|
|
8
8
|
"currency-codes": "2.2.0",
|
|
@@ -47,6 +47,11 @@
|
|
|
47
47
|
"import": "./dist/color.js",
|
|
48
48
|
"require": "./dist/color.cjs"
|
|
49
49
|
},
|
|
50
|
+
"./email": {
|
|
51
|
+
"types": "./dist/email.d.ts",
|
|
52
|
+
"import": "./dist/email.js",
|
|
53
|
+
"require": "./dist/email.cjs"
|
|
54
|
+
},
|
|
50
55
|
"./encrypt": {
|
|
51
56
|
"types": "./dist/encrypt.d.ts",
|
|
52
57
|
"import": "./dist/encrypt.js",
|
|
@@ -159,5 +164,5 @@
|
|
|
159
164
|
"test": "node --test test/"
|
|
160
165
|
},
|
|
161
166
|
"types": "dist/index.d.ts",
|
|
162
|
-
"version": "0.0.
|
|
167
|
+
"version": "0.0.80"
|
|
163
168
|
}
|