euredact 0.5.1 → 0.7.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/NOTICE +11 -0
- package/README.md +658 -45
- package/dist/cjs/cache.js +2 -8
- package/dist/cjs/cloud/account.js +180 -0
- package/dist/cjs/cloud/batches.js +469 -0
- package/dist/cjs/cloud/client.js +131 -18
- package/dist/cjs/cloud/config.js +28 -2
- package/dist/cjs/cloud/errors.js +39 -2
- package/dist/cjs/cloud/index.js +16 -1
- package/dist/cjs/index.js +9 -1
- package/dist/cjs/platform.browser.js +98 -0
- package/dist/cjs/platform.js +140 -0
- package/dist/cjs/rules/bicGleif.js +57 -0
- package/dist/cjs/rules/bicRegistry.js +37 -11
- package/dist/cjs/rules/countries/index.js +138 -48
- package/dist/cjs/rules/cues.js +29 -9
- package/dist/cjs/rules/engine.js +14 -6
- package/dist/cjs/rules/suppressors.js +418 -23
- package/dist/cjs/rules/validators.js +103 -0
- package/dist/cjs/sdk.js +150 -44
- package/dist/cjs/types.js +4 -1
- package/dist/esm/cache.js +2 -8
- package/dist/esm/cloud/account.d.ts +155 -0
- package/dist/esm/cloud/account.js +175 -0
- package/dist/esm/cloud/batches.d.ts +166 -0
- package/dist/esm/cloud/batches.js +464 -0
- package/dist/esm/cloud/client.d.ts +90 -5
- package/dist/esm/cloud/client.js +126 -26
- package/dist/esm/cloud/config.d.ts +6 -0
- package/dist/esm/cloud/config.js +27 -2
- package/dist/esm/cloud/errors.d.ts +26 -1
- package/dist/esm/cloud/errors.js +35 -1
- package/dist/esm/cloud/index.d.ts +4 -2
- package/dist/esm/cloud/index.js +4 -2
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/platform.browser.d.ts +27 -0
- package/dist/esm/platform.browser.js +92 -0
- package/dist/esm/platform.d.ts +20 -0
- package/dist/esm/platform.js +101 -0
- package/dist/esm/rules/bicGleif.d.ts +4 -0
- package/dist/esm/rules/bicGleif.js +53 -0
- package/dist/esm/rules/bicRegistry.d.ts +25 -11
- package/dist/esm/rules/bicRegistry.js +36 -11
- package/dist/esm/rules/countries/index.js +138 -48
- package/dist/esm/rules/cues.d.ts +1 -1
- package/dist/esm/rules/cues.js +29 -9
- package/dist/esm/rules/engine.js +14 -6
- package/dist/esm/rules/suppressors.d.ts +1 -0
- package/dist/esm/rules/suppressors.js +417 -22
- package/dist/esm/rules/validators.d.ts +14 -0
- package/dist/esm/rules/validators.js +100 -0
- package/dist/esm/sdk.d.ts +63 -19
- package/dist/esm/sdk.js +148 -44
- package/dist/esm/types.d.ts +58 -2
- package/dist/esm/types.js +4 -1
- package/package.json +12 -4
package/dist/cjs/cloud/client.js
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* [CLOUD EXTENSION] The cloud client.
|
|
4
4
|
*
|
|
5
|
-
* Speaks to the euRedact inference service: a
|
|
6
|
-
*
|
|
7
|
-
* service returns both the redacted document and located spans, so this
|
|
5
|
+
* Speaks to the euRedact inference service: a fine-tuned model asked only
|
|
6
|
+
* *what did the rules miss?* The service returns located spans, so this
|
|
8
7
|
* module's job is transport and translation, not detection.
|
|
9
8
|
*
|
|
9
|
+
* It sends the text it is given, as given. `redactAsync(text, { mode: "cloud" })`
|
|
10
|
+
* is what runs the rules engine first and hands this client only the masked
|
|
11
|
+
* text; call the client directly and whatever you pass is what leaves the
|
|
12
|
+
* machine. The spans that come back index the text that was sent.
|
|
13
|
+
*
|
|
10
14
|
* Three things it hides from the caller:
|
|
11
15
|
*
|
|
12
16
|
* - **Whether the document finished inside the service's sync window.** A short
|
|
@@ -22,12 +26,27 @@
|
|
|
22
26
|
* own `fetch`.
|
|
23
27
|
*/
|
|
24
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.CloudClient = void 0;
|
|
29
|
+
exports.CloudClient = exports.RETRY_STATUS = void 0;
|
|
30
|
+
exports.requireFetch = requireFetch;
|
|
31
|
+
exports.sleep = sleep;
|
|
32
|
+
exports.retryAfterMs = retryAfterMs;
|
|
33
|
+
exports.backoffMs = backoffMs;
|
|
34
|
+
exports.uuid = uuid;
|
|
35
|
+
exports.codePointOffsets = codePointOffsets;
|
|
36
|
+
exports.isInteger = isInteger;
|
|
37
|
+
exports.toUsage = toUsage;
|
|
38
|
+
exports.toCloudInfo = toCloudInfo;
|
|
39
|
+
exports.isQuota = isQuota;
|
|
40
|
+
exports.cloudErrorFor = cloudErrorFor;
|
|
41
|
+
exports.toResult = toResult;
|
|
42
|
+
exports.readJson = readJson;
|
|
26
43
|
const config_js_1 = require("./config.js");
|
|
27
44
|
const errors_js_1 = require("./errors.js");
|
|
28
45
|
const types_js_1 = require("../types.js");
|
|
29
|
-
|
|
46
|
+
/** @internal Shared with cloud/account.ts. */
|
|
47
|
+
exports.RETRY_STATUS = new Set([429, 500, 502, 503, 504]);
|
|
30
48
|
const MAX_BACKOFF_MS = 30_000;
|
|
49
|
+
/** @internal */
|
|
31
50
|
function requireFetch(supplied) {
|
|
32
51
|
const impl = supplied ?? globalThis.fetch;
|
|
33
52
|
if (typeof impl !== "function") {
|
|
@@ -36,9 +55,11 @@ function requireFetch(supplied) {
|
|
|
36
55
|
}
|
|
37
56
|
return impl;
|
|
38
57
|
}
|
|
58
|
+
/** @internal */
|
|
39
59
|
function sleep(ms) {
|
|
40
60
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
41
61
|
}
|
|
62
|
+
/** @internal */
|
|
42
63
|
function retryAfterMs(headers) {
|
|
43
64
|
const raw = headers?.get("Retry-After");
|
|
44
65
|
if (!raw)
|
|
@@ -51,11 +72,13 @@ function retryAfterMs(headers) {
|
|
|
51
72
|
* Full jitter. A synchronised retry storm from many clients is how a
|
|
52
73
|
* recovering service is knocked back over.
|
|
53
74
|
*/
|
|
75
|
+
/** @internal */
|
|
54
76
|
function backoffMs(attempt, retryAfter) {
|
|
55
77
|
if (retryAfter !== null)
|
|
56
78
|
return Math.min(retryAfter, MAX_BACKOFF_MS);
|
|
57
79
|
return Math.min(MAX_BACKOFF_MS, Math.random() * 2 ** attempt * 1000);
|
|
58
80
|
}
|
|
81
|
+
/** @internal */
|
|
59
82
|
function uuid() {
|
|
60
83
|
const c = globalThis.crypto;
|
|
61
84
|
if (c && typeof c.randomUUID === "function")
|
|
@@ -83,6 +106,7 @@ function toEntityType(raw) {
|
|
|
83
106
|
* Returns the identity when the text has none, which is the common case and
|
|
84
107
|
* costs one regex test.
|
|
85
108
|
*/
|
|
109
|
+
/** @internal Shared with cloud/batches.ts. */
|
|
86
110
|
function codePointOffsets(text) {
|
|
87
111
|
if (!/[\uD800-\uDBFF]/.test(text))
|
|
88
112
|
return cp => cp;
|
|
@@ -95,6 +119,97 @@ function codePointOffsets(text) {
|
|
|
95
119
|
units.push(i);
|
|
96
120
|
return cp => units[Math.min(cp, units.length - 1)];
|
|
97
121
|
}
|
|
122
|
+
/** @internal */
|
|
123
|
+
function isInteger(value) {
|
|
124
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The `usage` block, or undefined when absent or malformed. Shared rules with
|
|
128
|
+
* the Python SDK (`conformance/cloud_usage.json`): `tokens` and `credits` are
|
|
129
|
+
* integers, `billing_rate` a number, `factors` a list whose entries carry a
|
|
130
|
+
* string `code`. Anything else there drops the whole block rather than
|
|
131
|
+
* reporting a cost nobody can trust; unknown keys are ignored.
|
|
132
|
+
* @internal Exported for the conformance test.
|
|
133
|
+
*/
|
|
134
|
+
function toUsage(raw) {
|
|
135
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
136
|
+
return undefined;
|
|
137
|
+
const u = raw;
|
|
138
|
+
if (!isInteger(u.tokens) || !isInteger(u.credits) || typeof u.billing_rate !== "number"
|
|
139
|
+
|| !Array.isArray(u.factors))
|
|
140
|
+
return undefined;
|
|
141
|
+
const factors = [];
|
|
142
|
+
for (const f of u.factors) {
|
|
143
|
+
if (f === null || typeof f !== "object" || Array.isArray(f))
|
|
144
|
+
return undefined;
|
|
145
|
+
const r = f;
|
|
146
|
+
if (typeof r.code !== "string")
|
|
147
|
+
return undefined;
|
|
148
|
+
const factor = { code: r.code, detail: typeof r.detail === "string" ? r.detail : "" };
|
|
149
|
+
if (Array.isArray(r.types))
|
|
150
|
+
factor.types = r.types.filter((t) => typeof t === "string");
|
|
151
|
+
factors.push(factor);
|
|
152
|
+
}
|
|
153
|
+
return { tokens: u.tokens, billingRate: u.billing_rate, credits: u.credits, factors };
|
|
154
|
+
}
|
|
155
|
+
function str(value) {
|
|
156
|
+
return typeof value === "string" ? value : null;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The service's account of a request beside its spans. Shared rules with the
|
|
160
|
+
* Python SDK (`conformance/cloud_result.json`): a field of the wrong type reads
|
|
161
|
+
* as absent, and an unlocated entry without string `text` is skipped.
|
|
162
|
+
* @internal
|
|
163
|
+
*/
|
|
164
|
+
function toCloudInfo(payload) {
|
|
165
|
+
const unlocated = [];
|
|
166
|
+
for (const item of Array.isArray(payload.unlocated) ? payload.unlocated : []) {
|
|
167
|
+
if (item && typeof item === "object" && typeof item.text === "string") {
|
|
168
|
+
const t = item.type;
|
|
169
|
+
unlocated.push({
|
|
170
|
+
text: item.text,
|
|
171
|
+
entityType: toEntityType(typeof t === "string" ? t : "OTHER"),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const usage = toUsage(payload.usage);
|
|
176
|
+
return {
|
|
177
|
+
jobId: str(payload.job_id),
|
|
178
|
+
modelVersion: str(payload.model_version),
|
|
179
|
+
...(usage ? { usage } : {}),
|
|
180
|
+
unlocated,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* A 429 from the gateway's daily quota rather than the edge's rate limit: the
|
|
185
|
+
* gateway answers in JSON with `detail.used` and `detail.limit`; nginx in HTML.
|
|
186
|
+
* @internal
|
|
187
|
+
*/
|
|
188
|
+
function isQuota(payload) {
|
|
189
|
+
const detail = payload.detail;
|
|
190
|
+
return !!detail && typeof detail === "object" && "limit" in detail && "used" in detail;
|
|
191
|
+
}
|
|
192
|
+
/** The error for a non-success answer. @internal Shared with batches and account. */
|
|
193
|
+
function cloudErrorFor(status, payload) {
|
|
194
|
+
const message = typeof payload.error === "string" ? payload.error : `HTTP ${status}`;
|
|
195
|
+
const detail = (payload.detail && typeof payload.detail === "object"
|
|
196
|
+
? payload.detail : {});
|
|
197
|
+
if (status === 401)
|
|
198
|
+
return new errors_js_1.CloudError(`authentication failed: ${message}`, status);
|
|
199
|
+
if (status === 404)
|
|
200
|
+
return new errors_js_1.NotFoundError(message, status, detail);
|
|
201
|
+
if (status === 410)
|
|
202
|
+
return new errors_js_1.ResultExpiredError(message, status, detail);
|
|
203
|
+
if (status === 413)
|
|
204
|
+
return new errors_js_1.TooLargeError(message, status, detail);
|
|
205
|
+
if (status === 429) {
|
|
206
|
+
return isQuota(payload)
|
|
207
|
+
? new errors_js_1.QuotaExceededError(message, status, detail)
|
|
208
|
+
: new errors_js_1.RateLimitedError("rate limited by the service (HTTP 429); try again shortly", status, detail);
|
|
209
|
+
}
|
|
210
|
+
return new errors_js_1.CloudError(message, status, detail);
|
|
211
|
+
}
|
|
212
|
+
/** @internal Shared with cloud/batches.ts. */
|
|
98
213
|
function toResult(payload, text) {
|
|
99
214
|
const offset = codePointOffsets(text);
|
|
100
215
|
const detections = (payload.entities ?? []).map(span => ({
|
|
@@ -104,7 +219,9 @@ function toResult(payload, text) {
|
|
|
104
219
|
text: span.text,
|
|
105
220
|
source: span.source === "model" ? types_js_1.DetectionSource.CLOUD : types_js_1.DetectionSource.RULES,
|
|
106
221
|
country: null,
|
|
107
|
-
|
|
222
|
+
// A model span carries no evidence of its own: the service's confidence
|
|
223
|
+
// when it sends one, never an invented "high" (rules-engine#108).
|
|
224
|
+
confidence: span.source === "model" ? (span.confidence ?? null) : (span.confidence ?? "high"),
|
|
108
225
|
}));
|
|
109
226
|
detections.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
110
227
|
return {
|
|
@@ -117,6 +234,7 @@ function toResult(payload, text) {
|
|
|
117
234
|
detectionMode: "declared",
|
|
118
235
|
tokens: {},
|
|
119
236
|
exempted: [],
|
|
237
|
+
cloud: toCloudInfo(payload),
|
|
120
238
|
};
|
|
121
239
|
}
|
|
122
240
|
class CloudClient {
|
|
@@ -125,6 +243,8 @@ class CloudClient {
|
|
|
125
243
|
const resolved = config ?? (0, config_js_1.getConfig)();
|
|
126
244
|
if (resolved === null)
|
|
127
245
|
throw new errors_js_1.NotConfiguredError();
|
|
246
|
+
// A config built by hand bypasses configure(), so check it here too.
|
|
247
|
+
(0, config_js_1.requireSecureBaseUrl)(resolved.baseUrl);
|
|
128
248
|
this.config = resolved;
|
|
129
249
|
}
|
|
130
250
|
headers(idempotencyKey) {
|
|
@@ -138,15 +258,7 @@ class CloudClient {
|
|
|
138
258
|
};
|
|
139
259
|
}
|
|
140
260
|
raiseFor(status, payload) {
|
|
141
|
-
|
|
142
|
-
const detail = payload.detail ?? {};
|
|
143
|
-
if (status === 401)
|
|
144
|
-
throw new errors_js_1.CloudError(`authentication failed: ${message}`, status);
|
|
145
|
-
if (status === 413)
|
|
146
|
-
throw new errors_js_1.TooLargeError(message, status, detail);
|
|
147
|
-
if (status === 429)
|
|
148
|
-
throw new errors_js_1.QuotaExceededError(message, status, detail);
|
|
149
|
-
throw new errors_js_1.CloudError(message, status, detail);
|
|
261
|
+
throw cloudErrorFor(status, payload);
|
|
150
262
|
}
|
|
151
263
|
async redact(text, options) {
|
|
152
264
|
const doFetch = requireFetch(options.fetchImpl);
|
|
@@ -156,7 +268,6 @@ class CloudClient {
|
|
|
156
268
|
country: options.country.toUpperCase(),
|
|
157
269
|
language: options.language ?? "",
|
|
158
270
|
priority: options.priority ?? "interactive",
|
|
159
|
-
rules_only: options.rulesOnly ?? false,
|
|
160
271
|
});
|
|
161
272
|
let attempt = 0;
|
|
162
273
|
for (;;) {
|
|
@@ -186,7 +297,8 @@ class CloudClient {
|
|
|
186
297
|
}
|
|
187
298
|
if (response.status === 200)
|
|
188
299
|
return toResult(payload, text);
|
|
189
|
-
if (!RETRY_STATUS.has(response.status) || attempt >= this.config.maxRetries
|
|
300
|
+
if (!exports.RETRY_STATUS.has(response.status) || attempt >= this.config.maxRetries
|
|
301
|
+
|| (response.status === 429 && isQuota(payload))) {
|
|
190
302
|
this.raiseFor(response.status, payload);
|
|
191
303
|
}
|
|
192
304
|
}
|
|
@@ -209,7 +321,7 @@ class CloudClient {
|
|
|
209
321
|
if (response.status === 200 && payload.redacted_text !== undefined) {
|
|
210
322
|
return payload;
|
|
211
323
|
}
|
|
212
|
-
if (response.status !== 200 && !RETRY_STATUS.has(response.status)) {
|
|
324
|
+
if (response.status !== 200 && !exports.RETRY_STATUS.has(response.status)) {
|
|
213
325
|
this.raiseFor(response.status, payload);
|
|
214
326
|
}
|
|
215
327
|
if (Date.now() >= deadline) {
|
|
@@ -232,6 +344,7 @@ class CloudClient {
|
|
|
232
344
|
}
|
|
233
345
|
}
|
|
234
346
|
exports.CloudClient = CloudClient;
|
|
347
|
+
/** @internal */
|
|
235
348
|
async function readJson(response) {
|
|
236
349
|
try {
|
|
237
350
|
const data = await response.json();
|
package/dist/cjs/cloud/config.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/** [CLOUD EXTENSION] Cloud tier configuration. */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.DEFAULT_MAX_RETRIES = exports.DEFAULT_POLL_TIMEOUT_MS = exports.DEFAULT_TIMEOUT_MS = exports.DEFAULT_BASE_URL = void 0;
|
|
5
|
+
exports.requireSecureBaseUrl = requireSecureBaseUrl;
|
|
5
6
|
exports.configure = configure;
|
|
6
7
|
exports.getConfig = getConfig;
|
|
7
8
|
exports.reset = reset;
|
|
@@ -19,6 +20,29 @@ exports.DEFAULT_TIMEOUT_MS = 90_000;
|
|
|
19
20
|
*/
|
|
20
21
|
exports.DEFAULT_POLL_TIMEOUT_MS = 900_000;
|
|
21
22
|
exports.DEFAULT_MAX_RETRIES = 3;
|
|
23
|
+
/** Hosts a plain-HTTP base URL may name: a gateway or TLS proxy on this machine. */
|
|
24
|
+
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
25
|
+
/**
|
|
26
|
+
* Refuse a cloud address that would not use TLS. The API key and the text sent
|
|
27
|
+
* must travel encrypted; plain `http` is allowed only to a loopback host, for a
|
|
28
|
+
* local gateway or a TLS-terminating proxy on the same machine (rules-engine#86).
|
|
29
|
+
*/
|
|
30
|
+
function requireSecureBaseUrl(baseUrl) {
|
|
31
|
+
// Parsed by hand rather than with `URL`: a runtime without that global would
|
|
32
|
+
// otherwise refuse every address, https included, with a misleading message.
|
|
33
|
+
const m = /^([a-z][a-z0-9+.-]*):\/\/(?:[^@/?#]*@)?(\[[^\]]*\]|[^:/?#]*)/i.exec(baseUrl);
|
|
34
|
+
if (m !== null) {
|
|
35
|
+
const scheme = m[1].toLowerCase();
|
|
36
|
+
const host = m[2].toLowerCase();
|
|
37
|
+
if (scheme === "https" && host)
|
|
38
|
+
return;
|
|
39
|
+
if (scheme === "http" && LOOPBACK_HOSTS.has(host))
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`refusing cloud base URL ${JSON.stringify(baseUrl)}: it must start with https:// ` +
|
|
43
|
+
"(plain http:// is allowed only to localhost, 127.0.0.1 or ::1), because the " +
|
|
44
|
+
"API key and document text would otherwise travel unencrypted");
|
|
45
|
+
}
|
|
22
46
|
let config = null;
|
|
23
47
|
/** Read an env var where there is an environment to read. Browsers have none. */
|
|
24
48
|
function env(name) {
|
|
@@ -36,10 +60,12 @@ function configure(options = {}) {
|
|
|
36
60
|
if (!apiKey) {
|
|
37
61
|
throw new Error("no API key: pass configure({ apiKey }) or set EUREDACT_API_KEY");
|
|
38
62
|
}
|
|
63
|
+
const baseUrl = (options.baseUrl ?? env("EUREDACT_BASE_URL") ?? exports.DEFAULT_BASE_URL)
|
|
64
|
+
.replace(/\/+$/, "");
|
|
65
|
+
requireSecureBaseUrl(baseUrl);
|
|
39
66
|
config = {
|
|
40
67
|
apiKey,
|
|
41
|
-
baseUrl
|
|
42
|
-
.replace(/\/+$/, ""),
|
|
68
|
+
baseUrl,
|
|
43
69
|
timeoutMs: options.timeoutMs ?? exports.DEFAULT_TIMEOUT_MS,
|
|
44
70
|
pollTimeoutMs: options.pollTimeoutMs ?? exports.DEFAULT_POLL_TIMEOUT_MS,
|
|
45
71
|
maxRetries: options.maxRetries ?? exports.DEFAULT_MAX_RETRIES,
|
package/dist/cjs/cloud/errors.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/** [CLOUD EXTENSION] Errors the cloud tier can raise. */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.TooLargeError = exports.QuotaExceededError = exports.NotConfiguredError = exports.CloudError = void 0;
|
|
4
|
+
exports.TooLargeError = exports.ResultExpiredError = exports.NotFoundError = exports.RateLimitedError = exports.QuotaExceededError = exports.NotConfiguredError = exports.CloudError = void 0;
|
|
5
5
|
class CloudError extends Error {
|
|
6
6
|
status;
|
|
7
7
|
detail;
|
|
@@ -31,7 +31,11 @@ class NotConfiguredError extends CloudError {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
exports.NotConfiguredError = NotConfiguredError;
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* 429: the account's daily document quota is used up. Thrown at once, not
|
|
36
|
+
* after retries: the gateway's quota answer (JSON with `detail.used` and
|
|
37
|
+
* `detail.limit`) will not change before the day does (rules-engine#89).
|
|
38
|
+
*/
|
|
35
39
|
class QuotaExceededError extends CloudError {
|
|
36
40
|
constructor(message, status, detail) {
|
|
37
41
|
super(message, status, detail);
|
|
@@ -39,6 +43,39 @@ class QuotaExceededError extends CloudError {
|
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
exports.QuotaExceededError = QuotaExceededError;
|
|
46
|
+
/**
|
|
47
|
+
* 429 from the edge's rate limit, still there after every retry. It clears
|
|
48
|
+
* within seconds, so it is retried with backoff first. A subclass of
|
|
49
|
+
* `QuotaExceededError`, which is what every 429 threw before the two were told apart.
|
|
50
|
+
*/
|
|
51
|
+
class RateLimitedError extends QuotaExceededError {
|
|
52
|
+
constructor(message, status, detail) {
|
|
53
|
+
super(message, status, detail);
|
|
54
|
+
this.name = "RateLimitedError";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.RateLimitedError = RateLimitedError;
|
|
58
|
+
/** 404: no such job, batch or key -- or another account's, which the service
|
|
59
|
+
* deliberately does not distinguish. */
|
|
60
|
+
class NotFoundError extends CloudError {
|
|
61
|
+
constructor(message, status, detail) {
|
|
62
|
+
super(message, status, detail);
|
|
63
|
+
this.name = "NotFoundError";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.NotFoundError = NotFoundError;
|
|
67
|
+
/**
|
|
68
|
+
* 410: the job succeeded, but its result is no longer retained -- swept by the
|
|
69
|
+
* account's retention period, or discarded on delivery under no-retention. An
|
|
70
|
+
* empty result here would read like a document with no personal data in it.
|
|
71
|
+
*/
|
|
72
|
+
class ResultExpiredError extends CloudError {
|
|
73
|
+
constructor(message, status, detail) {
|
|
74
|
+
super(message, status, detail);
|
|
75
|
+
this.name = "ResultExpiredError";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.ResultExpiredError = ResultExpiredError;
|
|
42
79
|
/**
|
|
43
80
|
* 413. The document is over the service's input cap.
|
|
44
81
|
*
|
package/dist/cjs/cloud/index.js
CHANGED
|
@@ -16,16 +16,31 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.TooLargeError = exports.QuotaExceededError = exports.NotConfiguredError = exports.CloudError = exports.DEFAULT_BASE_URL = exports.reset = exports.getConfig = exports.configure = exports.CloudClient = void 0;
|
|
19
|
+
exports.TooLargeError = exports.ResultExpiredError = exports.RateLimitedError = exports.QuotaExceededError = exports.NotFoundError = exports.NotConfiguredError = exports.CloudError = exports.DEFAULT_BASE_URL = exports.requireSecureBaseUrl = exports.reset = exports.getConfig = exports.configure = exports.MAX_DOCUMENT_TOKENS = exports.MAX_BODY_BYTES = exports.CUSTOM_ID = exports.MAX_CUSTOM_ID = exports.MAX_DOCUMENTS = exports.BatchError = exports.Batches = exports.Jobs = exports.Account = exports.CloudClient = void 0;
|
|
20
20
|
var client_js_1 = require("./client.js");
|
|
21
21
|
Object.defineProperty(exports, "CloudClient", { enumerable: true, get: function () { return client_js_1.CloudClient; } });
|
|
22
|
+
var account_js_1 = require("./account.js");
|
|
23
|
+
Object.defineProperty(exports, "Account", { enumerable: true, get: function () { return account_js_1.Account; } });
|
|
24
|
+
Object.defineProperty(exports, "Jobs", { enumerable: true, get: function () { return account_js_1.Jobs; } });
|
|
25
|
+
var batches_js_1 = require("./batches.js");
|
|
26
|
+
Object.defineProperty(exports, "Batches", { enumerable: true, get: function () { return batches_js_1.Batches; } });
|
|
27
|
+
Object.defineProperty(exports, "BatchError", { enumerable: true, get: function () { return batches_js_1.BatchError; } });
|
|
28
|
+
Object.defineProperty(exports, "MAX_DOCUMENTS", { enumerable: true, get: function () { return batches_js_1.MAX_DOCUMENTS; } });
|
|
29
|
+
Object.defineProperty(exports, "MAX_CUSTOM_ID", { enumerable: true, get: function () { return batches_js_1.MAX_CUSTOM_ID; } });
|
|
30
|
+
Object.defineProperty(exports, "CUSTOM_ID", { enumerable: true, get: function () { return batches_js_1.CUSTOM_ID; } });
|
|
31
|
+
Object.defineProperty(exports, "MAX_BODY_BYTES", { enumerable: true, get: function () { return batches_js_1.MAX_BODY_BYTES; } });
|
|
32
|
+
Object.defineProperty(exports, "MAX_DOCUMENT_TOKENS", { enumerable: true, get: function () { return batches_js_1.MAX_DOCUMENT_TOKENS; } });
|
|
22
33
|
var config_js_1 = require("./config.js");
|
|
23
34
|
Object.defineProperty(exports, "configure", { enumerable: true, get: function () { return config_js_1.configure; } });
|
|
24
35
|
Object.defineProperty(exports, "getConfig", { enumerable: true, get: function () { return config_js_1.getConfig; } });
|
|
25
36
|
Object.defineProperty(exports, "reset", { enumerable: true, get: function () { return config_js_1.reset; } });
|
|
37
|
+
Object.defineProperty(exports, "requireSecureBaseUrl", { enumerable: true, get: function () { return config_js_1.requireSecureBaseUrl; } });
|
|
26
38
|
Object.defineProperty(exports, "DEFAULT_BASE_URL", { enumerable: true, get: function () { return config_js_1.DEFAULT_BASE_URL; } });
|
|
27
39
|
var errors_js_1 = require("./errors.js");
|
|
28
40
|
Object.defineProperty(exports, "CloudError", { enumerable: true, get: function () { return errors_js_1.CloudError; } });
|
|
29
41
|
Object.defineProperty(exports, "NotConfiguredError", { enumerable: true, get: function () { return errors_js_1.NotConfiguredError; } });
|
|
42
|
+
Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: function () { return errors_js_1.NotFoundError; } });
|
|
30
43
|
Object.defineProperty(exports, "QuotaExceededError", { enumerable: true, get: function () { return errors_js_1.QuotaExceededError; } });
|
|
44
|
+
Object.defineProperty(exports, "RateLimitedError", { enumerable: true, get: function () { return errors_js_1.RateLimitedError; } });
|
|
45
|
+
Object.defineProperty(exports, "ResultExpiredError", { enumerable: true, get: function () { return errors_js_1.ResultExpiredError; } });
|
|
31
46
|
Object.defineProperty(exports, "TooLargeError", { enumerable: true, get: function () { return errors_js_1.TooLargeError; } });
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TooLargeError = exports.QuotaExceededError = exports.NotConfiguredError = exports.CloudError = exports.CloudClient = exports.getConfig = exports.configure = exports.SEED_BIC6_PREFIXES = exports.getBicRegistry = exports.setBicRegistry = exports.COUNTRY_CONFIGS = exports.restore = exports.EuRedact = exports.DocumentContext = exports.canonicalType = exports.LEGACY_TYPE_ALIASES = exports.DetectionSource = exports.EntityType = void 0;
|
|
3
|
+
exports.TooLargeError = exports.ResultExpiredError = exports.RateLimitedError = exports.QuotaExceededError = exports.NotFoundError = exports.NotConfiguredError = exports.Jobs = exports.CUSTOM_ID = exports.BatchError = exports.Batches = exports.CloudError = exports.CloudClient = exports.Account = exports.getConfig = exports.configure = exports.SEED_BIC6_PREFIXES = exports.getBicRegistry = exports.setBicRegistry = exports.COUNTRY_CONFIGS = exports.restore = exports.EuRedact = exports.DocumentContext = exports.canonicalType = exports.LEGACY_TYPE_ALIASES = exports.DetectionSource = exports.EntityType = void 0;
|
|
4
4
|
exports.availableCountries = availableCountries;
|
|
5
5
|
exports.redact = redact;
|
|
6
6
|
exports.redactAsync = redactAsync;
|
|
@@ -26,10 +26,18 @@ Object.defineProperty(exports, "SEED_BIC6_PREFIXES", { enumerable: true, get: fu
|
|
|
26
26
|
var index_js_2 = require("./cloud/index.js");
|
|
27
27
|
Object.defineProperty(exports, "configure", { enumerable: true, get: function () { return index_js_2.configure; } });
|
|
28
28
|
Object.defineProperty(exports, "getConfig", { enumerable: true, get: function () { return index_js_2.getConfig; } });
|
|
29
|
+
Object.defineProperty(exports, "Account", { enumerable: true, get: function () { return index_js_2.Account; } });
|
|
29
30
|
Object.defineProperty(exports, "CloudClient", { enumerable: true, get: function () { return index_js_2.CloudClient; } });
|
|
30
31
|
Object.defineProperty(exports, "CloudError", { enumerable: true, get: function () { return index_js_2.CloudError; } });
|
|
32
|
+
Object.defineProperty(exports, "Batches", { enumerable: true, get: function () { return index_js_2.Batches; } });
|
|
33
|
+
Object.defineProperty(exports, "BatchError", { enumerable: true, get: function () { return index_js_2.BatchError; } });
|
|
34
|
+
Object.defineProperty(exports, "CUSTOM_ID", { enumerable: true, get: function () { return index_js_2.CUSTOM_ID; } });
|
|
35
|
+
Object.defineProperty(exports, "Jobs", { enumerable: true, get: function () { return index_js_2.Jobs; } });
|
|
31
36
|
Object.defineProperty(exports, "NotConfiguredError", { enumerable: true, get: function () { return index_js_2.NotConfiguredError; } });
|
|
37
|
+
Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: function () { return index_js_2.NotFoundError; } });
|
|
32
38
|
Object.defineProperty(exports, "QuotaExceededError", { enumerable: true, get: function () { return index_js_2.QuotaExceededError; } });
|
|
39
|
+
Object.defineProperty(exports, "RateLimitedError", { enumerable: true, get: function () { return index_js_2.RateLimitedError; } });
|
|
40
|
+
Object.defineProperty(exports, "ResultExpiredError", { enumerable: true, get: function () { return index_js_2.ResultExpiredError; } });
|
|
33
41
|
Object.defineProperty(exports, "TooLargeError", { enumerable: true, get: function () { return index_js_2.TooLargeError; } });
|
|
34
42
|
const sdk_js_2 = require("./sdk.js");
|
|
35
43
|
const index_js_3 = require("./rules/countries/index.js");
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `platform.ts` for browsers: no `node:` imports, so the package bundles for a
|
|
4
|
+
* page. Selected by the `browser` field in package.json (rules-engine#56).
|
|
5
|
+
*
|
|
6
|
+
* The cache key is computed synchronously and must stay collision-resistant
|
|
7
|
+
* (see `ResultCache.key`), and Web Crypto's `subtle.digest` is asynchronous, so
|
|
8
|
+
* SHA-256 is implemented here. A test checks it against `node:crypto` byte for
|
|
9
|
+
* byte, astral characters and lone surrogates included.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.sha256Hex = sha256Hex;
|
|
13
|
+
exports.randomIndex = randomIndex;
|
|
14
|
+
exports.defaultBatchStore = defaultBatchStore;
|
|
15
|
+
exports.gzip = gzip;
|
|
16
|
+
const K = new Uint32Array([
|
|
17
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
18
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
19
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
20
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
21
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
22
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
23
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
24
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
25
|
+
]);
|
|
26
|
+
/** Hex SHA-256 of the UTF-8 encoding of `parts`, joined with nothing between. */
|
|
27
|
+
function sha256Hex(parts) {
|
|
28
|
+
// TextEncoder replaces a lone surrogate with U+FFFD, as Node's hash.update does.
|
|
29
|
+
const bytes = new TextEncoder().encode(parts.join(""));
|
|
30
|
+
const bitLength = bytes.length * 8;
|
|
31
|
+
const padded = new Uint8Array(((bytes.length + 9 + 63) >> 6) << 6);
|
|
32
|
+
padded.set(bytes);
|
|
33
|
+
padded[bytes.length] = 0x80;
|
|
34
|
+
const view = new DataView(padded.buffer);
|
|
35
|
+
view.setUint32(padded.length - 8, Math.floor(bitLength / 0x100000000));
|
|
36
|
+
view.setUint32(padded.length - 4, bitLength >>> 0);
|
|
37
|
+
const h = new Uint32Array([
|
|
38
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
39
|
+
]);
|
|
40
|
+
const w = new Uint32Array(64);
|
|
41
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
42
|
+
for (let i = 0; i < 16; i++)
|
|
43
|
+
w[i] = view.getUint32(offset + i * 4);
|
|
44
|
+
for (let i = 16; i < 64; i++) {
|
|
45
|
+
const a = w[i - 15], b = w[i - 2];
|
|
46
|
+
const s0 = ((a >>> 7) | (a << 25)) ^ ((a >>> 18) | (a << 14)) ^ (a >>> 3);
|
|
47
|
+
const s1 = ((b >>> 17) | (b << 15)) ^ ((b >>> 19) | (b << 13)) ^ (b >>> 10);
|
|
48
|
+
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
|
|
49
|
+
}
|
|
50
|
+
let [a, b, c, d, e, f, g, hh] = h;
|
|
51
|
+
for (let i = 0; i < 64; i++) {
|
|
52
|
+
const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
|
|
53
|
+
const ch = (e & f) ^ (~e & g);
|
|
54
|
+
const t1 = (hh + S1 + ch + K[i] + w[i]) >>> 0;
|
|
55
|
+
const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
|
|
56
|
+
const maj = (a & b) ^ (a & c) ^ (b & c);
|
|
57
|
+
const t2 = (S0 + maj) >>> 0;
|
|
58
|
+
hh = g;
|
|
59
|
+
g = f;
|
|
60
|
+
f = e;
|
|
61
|
+
e = (d + t1) >>> 0;
|
|
62
|
+
d = c;
|
|
63
|
+
c = b;
|
|
64
|
+
b = a;
|
|
65
|
+
a = (t1 + t2) >>> 0;
|
|
66
|
+
}
|
|
67
|
+
h[0] = (h[0] + a) >>> 0;
|
|
68
|
+
h[1] = (h[1] + b) >>> 0;
|
|
69
|
+
h[2] = (h[2] + c) >>> 0;
|
|
70
|
+
h[3] = (h[3] + d) >>> 0;
|
|
71
|
+
h[4] = (h[4] + e) >>> 0;
|
|
72
|
+
h[5] = (h[5] + f) >>> 0;
|
|
73
|
+
h[6] = (h[6] + g) >>> 0;
|
|
74
|
+
h[7] = (h[7] + hh) >>> 0;
|
|
75
|
+
}
|
|
76
|
+
return Array.from(h, x => x.toString(16).padStart(8, "0")).join("");
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A uniformly random integer in `[0, n)`, from `crypto.getRandomValues`.
|
|
80
|
+
* Rejection sampling: a plain modulo would favour the low residues.
|
|
81
|
+
*/
|
|
82
|
+
function randomIndex(n) {
|
|
83
|
+
const limit = Math.floor(0x100000000 / n) * n;
|
|
84
|
+
const buf = new Uint32Array(1);
|
|
85
|
+
for (;;) {
|
|
86
|
+
globalThis.crypto.getRandomValues(buf);
|
|
87
|
+
if (buf[0] < limit)
|
|
88
|
+
return buf[0] % n;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** A page has no filesystem: the caller passes `store` to `Batches`. */
|
|
92
|
+
function defaultBatchStore(_directory) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
/** No synchronous gzip in a page; the batch is uploaded uncompressed. */
|
|
96
|
+
function gzip(_data) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.sha256Hex = sha256Hex;
|
|
37
|
+
exports.randomIndex = randomIndex;
|
|
38
|
+
exports.defaultBatchStore = defaultBatchStore;
|
|
39
|
+
exports.gzip = gzip;
|
|
40
|
+
/**
|
|
41
|
+
* The two things the SDK needs from a crypto library: a SHA-256 digest for the
|
|
42
|
+
* result-cache key and a uniform random index for token suffixes.
|
|
43
|
+
*
|
|
44
|
+
* This is the Node implementation, on `node:crypto`. A browser bundle gets
|
|
45
|
+
* `platform.browser.ts` instead, through the `browser` field in package.json,
|
|
46
|
+
* because `node:crypto` does not exist there and importing it from `cache.ts`
|
|
47
|
+
* and `sdk.ts` made the package unbundleable for a page (rules-engine#56).
|
|
48
|
+
* Every other module imports these two functions from here and nothing from
|
|
49
|
+
* `node:`; a test holds the source tree to that.
|
|
50
|
+
*/
|
|
51
|
+
const node_crypto_1 = require("node:crypto");
|
|
52
|
+
const fs = __importStar(require("node:fs"));
|
|
53
|
+
const os = __importStar(require("node:os"));
|
|
54
|
+
const path = __importStar(require("node:path"));
|
|
55
|
+
const node_zlib_1 = require("node:zlib");
|
|
56
|
+
/** Hex SHA-256 of the UTF-8 encoding of `parts`, joined with nothing between. */
|
|
57
|
+
function sha256Hex(parts) {
|
|
58
|
+
const hash = (0, node_crypto_1.createHash)("sha256");
|
|
59
|
+
for (const part of parts)
|
|
60
|
+
hash.update(part);
|
|
61
|
+
return hash.digest("hex");
|
|
62
|
+
}
|
|
63
|
+
/** A uniformly random integer in `[0, n)`. */
|
|
64
|
+
function randomIndex(n) {
|
|
65
|
+
return (0, node_crypto_1.randomInt)(n);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* `<dir>/<batchId>.json`, directory `0700`, files `0600`, the default local
|
|
69
|
+
* batch store. Writes go to a temporary file in the same directory, created
|
|
70
|
+
* `0600` before any byte is written, then renamed over the old one.
|
|
71
|
+
*/
|
|
72
|
+
function defaultBatchStore(directory) {
|
|
73
|
+
const dir = directory ?? path.join(os.homedir(), ".euredact", "batches");
|
|
74
|
+
const fileFor = (batchId) => {
|
|
75
|
+
if (!batchId || /[/\\]/.test(batchId) || batchId === "." || batchId === "..") {
|
|
76
|
+
throw new Error(`not a batch id: ${JSON.stringify(batchId)}`);
|
|
77
|
+
}
|
|
78
|
+
return path.join(dir, `${batchId}.json`);
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
async read(batchId) {
|
|
82
|
+
try {
|
|
83
|
+
return new Uint8Array(fs.readFileSync(fileFor(batchId)));
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
if (err.code === "ENOENT")
|
|
87
|
+
return null;
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
async write(batchId, data) {
|
|
92
|
+
const target = fileFor(batchId);
|
|
93
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
94
|
+
try {
|
|
95
|
+
fs.chmodSync(dir, 0o700);
|
|
96
|
+
}
|
|
97
|
+
catch { /* not ours to change */ }
|
|
98
|
+
const tmp = path.join(dir, `.${batchId}.${process.pid}.${Date.now()}.tmp`);
|
|
99
|
+
const fd = fs.openSync(tmp, "wx", 0o600);
|
|
100
|
+
try {
|
|
101
|
+
fs.writeSync(fd, data);
|
|
102
|
+
fs.fsyncSync(fd);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
fs.closeSync(fd);
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
fs.renameSync(tmp, target);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
try {
|
|
112
|
+
fs.unlinkSync(tmp);
|
|
113
|
+
}
|
|
114
|
+
catch { /* already gone */ }
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
async delete(batchId) {
|
|
119
|
+
try {
|
|
120
|
+
fs.unlinkSync(fileFor(batchId));
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
if (err.code !== "ENOENT")
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
async list() {
|
|
128
|
+
if (!fs.existsSync(dir))
|
|
129
|
+
return [];
|
|
130
|
+
return fs.readdirSync(dir)
|
|
131
|
+
.filter(name => name.endsWith(".json") && !name.startsWith("."))
|
|
132
|
+
.map(name => name.slice(0, -".json".length))
|
|
133
|
+
.sort();
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** Gzip, for the batch upload. */
|
|
138
|
+
function gzip(data) {
|
|
139
|
+
return new Uint8Array((0, node_zlib_1.gzipSync)(data));
|
|
140
|
+
}
|