@notionhq/workers 0.8.0 → 0.8.1
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/alpha/workflow.d.ts +30 -2
- package/dist/alpha/workflow.d.ts.map +1 -1
- package/dist/alpha/workflow.js +51 -1
- package/dist/credential.d.ts +26 -0
- package/dist/credential.d.ts.map +1 -0
- package/dist/credential.js +176 -0
- package/dist/credential.test.d.ts +2 -0
- package/dist/credential.test.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/worker.d.ts +7 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +28 -1
- package/package.json +1 -1
- package/src/alpha/workflow.test.ts +124 -2
- package/src/alpha/workflow.ts +92 -3
- package/src/credential.test.ts +324 -0
- package/src/credential.ts +238 -0
- package/src/index.ts +8 -0
- package/src/worker.test.ts +153 -2
- package/src/worker.ts +43 -1
package/src/worker.test.ts
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
|
+
import { CREDENTIAL_VALUE } from "./credential.js";
|
|
3
4
|
import * as Schema from "./schema.js";
|
|
4
5
|
import { Worker } from "./worker.js";
|
|
5
6
|
|
|
6
7
|
describe("Worker", () => {
|
|
7
|
-
it("includes sdkVersion, databases, and capabilities in manifest", () => {
|
|
8
|
+
it("includes sdkVersion, credentials, databases, and capabilities in manifest", () => {
|
|
8
9
|
const worker = new Worker();
|
|
10
|
+
worker.credential("LINEAR_API_TOKEN", {
|
|
11
|
+
network: [
|
|
12
|
+
{
|
|
13
|
+
domain: "api.linear.com",
|
|
14
|
+
transform: [
|
|
15
|
+
{
|
|
16
|
+
headers: {
|
|
17
|
+
Authorization: `Bearer ${CREDENTIAL_VALUE}`,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
});
|
|
9
24
|
const tasks = worker.database("tasks", {
|
|
10
25
|
type: "managed",
|
|
11
26
|
initialTitle: "Tasks",
|
|
@@ -26,6 +41,23 @@ describe("Worker", () => {
|
|
|
26
41
|
});
|
|
27
42
|
|
|
28
43
|
expect(worker.manifest.sdkVersion).toMatch(/^\d+\.\d+\.\d+/);
|
|
44
|
+
expect(worker.manifest.credentials).toEqual([
|
|
45
|
+
{
|
|
46
|
+
key: "LINEAR_API_TOKEN",
|
|
47
|
+
network: [
|
|
48
|
+
{
|
|
49
|
+
domain: "api.linear.com",
|
|
50
|
+
transform: [
|
|
51
|
+
{
|
|
52
|
+
headers: {
|
|
53
|
+
Authorization: "Bearer {{credential}}",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
]);
|
|
29
61
|
expect(worker.manifest.databases).toEqual([
|
|
30
62
|
{
|
|
31
63
|
key: "tasks",
|
|
@@ -288,7 +320,7 @@ describe("Worker", () => {
|
|
|
288
320
|
);
|
|
289
321
|
});
|
|
290
322
|
|
|
291
|
-
it("rejects duplicate keys across databases, pacers, and capabilities", () => {
|
|
323
|
+
it("rejects duplicate keys across credentials, databases, pacers, and capabilities", () => {
|
|
292
324
|
const worker = new Worker();
|
|
293
325
|
const db = worker.database("shared-key", {
|
|
294
326
|
type: "managed",
|
|
@@ -301,6 +333,23 @@ describe("Worker", () => {
|
|
|
301
333
|
},
|
|
302
334
|
});
|
|
303
335
|
|
|
336
|
+
expect(() =>
|
|
337
|
+
worker.credential("shared-key", {
|
|
338
|
+
network: [
|
|
339
|
+
{
|
|
340
|
+
domain: "api.example.com",
|
|
341
|
+
transform: [
|
|
342
|
+
{
|
|
343
|
+
headers: {
|
|
344
|
+
Authorization: CREDENTIAL_VALUE,
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
}),
|
|
351
|
+
).toThrow('Worker item with key "shared-key" already registered');
|
|
352
|
+
|
|
304
353
|
expect(() =>
|
|
305
354
|
worker.sync("shared-key", {
|
|
306
355
|
database: db,
|
|
@@ -311,4 +360,106 @@ describe("Worker", () => {
|
|
|
311
360
|
}),
|
|
312
361
|
).toThrow('Worker item with key "shared-key" already registered');
|
|
313
362
|
});
|
|
363
|
+
|
|
364
|
+
it("does not reserve a key when registration fails", () => {
|
|
365
|
+
const worker = new Worker();
|
|
366
|
+
|
|
367
|
+
expect(() =>
|
|
368
|
+
worker.credential("REUSABLE_KEY", {
|
|
369
|
+
network: [],
|
|
370
|
+
}),
|
|
371
|
+
).toThrow("must declare at least one network rule");
|
|
372
|
+
|
|
373
|
+
expect(() =>
|
|
374
|
+
worker.pacer("REUSABLE_KEY", {
|
|
375
|
+
allowedRequests: 10,
|
|
376
|
+
intervalMs: 1_000,
|
|
377
|
+
}),
|
|
378
|
+
).not.toThrow();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("rejects credentials that target the same domain and header", () => {
|
|
382
|
+
const worker = new Worker();
|
|
383
|
+
worker.credential("FIRST_TOKEN", {
|
|
384
|
+
network: [
|
|
385
|
+
{
|
|
386
|
+
domain: "api.example.com",
|
|
387
|
+
transform: [{ headers: { Authorization: `Bearer ${CREDENTIAL_VALUE}` } }],
|
|
388
|
+
},
|
|
389
|
+
],
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
expect(() =>
|
|
393
|
+
worker.credential("SECOND_TOKEN", {
|
|
394
|
+
network: [
|
|
395
|
+
{
|
|
396
|
+
domain: "API.EXAMPLE.COM",
|
|
397
|
+
transform: [{ headers: { authorization: CREDENTIAL_VALUE } }],
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
}),
|
|
401
|
+
).toThrow('conflicts with credential "FIRST_TOKEN"');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it("limits the number of credential declarations", () => {
|
|
405
|
+
const worker = new Worker();
|
|
406
|
+
for (let index = 0; index < 100; index += 1) {
|
|
407
|
+
worker.credential(`TOKEN_${index}`, {
|
|
408
|
+
network: [
|
|
409
|
+
{
|
|
410
|
+
domain: `api${index}.example.com`,
|
|
411
|
+
transform: [{ headers: { Authorization: CREDENTIAL_VALUE } }],
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
expect(() =>
|
|
418
|
+
worker.credential("ONE_TOO_MANY", {
|
|
419
|
+
network: [
|
|
420
|
+
{
|
|
421
|
+
domain: "overflow.example.com",
|
|
422
|
+
transform: [{ headers: { Authorization: CREDENTIAL_VALUE } }],
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
}),
|
|
426
|
+
).toThrow("cannot declare more than 100 credentials");
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it("matches the service limit for the credential declaration payload", () => {
|
|
430
|
+
const worker = new Worker();
|
|
431
|
+
for (let index = 0; index < 32; index += 1) {
|
|
432
|
+
worker.credential(`TOKEN_${index}`, {
|
|
433
|
+
network: [
|
|
434
|
+
{
|
|
435
|
+
domain: `api${index}.example.com`,
|
|
436
|
+
transform: [
|
|
437
|
+
{
|
|
438
|
+
headers: {
|
|
439
|
+
Authorization: `${"x".repeat(8_000)}${CREDENTIAL_VALUE}`,
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
},
|
|
444
|
+
],
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
expect(() =>
|
|
449
|
+
worker.credential("TOKEN_32", {
|
|
450
|
+
network: [
|
|
451
|
+
{
|
|
452
|
+
domain: "api32.example.com",
|
|
453
|
+
transform: [
|
|
454
|
+
{
|
|
455
|
+
headers: {
|
|
456
|
+
Authorization: `${"x".repeat(8_000)}${CREDENTIAL_VALUE}`,
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
],
|
|
460
|
+
},
|
|
461
|
+
],
|
|
462
|
+
}),
|
|
463
|
+
).toThrow("credential declarations must be at most 262144 bytes");
|
|
464
|
+
});
|
|
314
465
|
});
|
package/src/worker.ts
CHANGED
|
@@ -39,6 +39,17 @@ import type {
|
|
|
39
39
|
WorkflowEvent,
|
|
40
40
|
} from "./capabilities/workflow.js";
|
|
41
41
|
import { createWorkflowCapability } from "./capabilities/workflow.js";
|
|
42
|
+
import type {
|
|
43
|
+
CredentialConfiguration,
|
|
44
|
+
CredentialDeclaration,
|
|
45
|
+
CredentialKey,
|
|
46
|
+
} from "./credential.js";
|
|
47
|
+
import {
|
|
48
|
+
createCredentialDeclaration,
|
|
49
|
+
MAX_CREDENTIAL_DECLARATIONS_BYTES,
|
|
50
|
+
MAX_CREDENTIALS,
|
|
51
|
+
validateCredentialDeclarationConflicts,
|
|
52
|
+
} from "./credential.js";
|
|
42
53
|
import type { PacerDeclaration } from "./pacer_internal.js";
|
|
43
54
|
import { pacerWait } from "./pacer_internal.js";
|
|
44
55
|
import type { Schema } from "./schema.js";
|
|
@@ -212,6 +223,7 @@ type CapabilityManifestEntry = Pick<Capability, "_tag" | "key" | "config">;
|
|
|
212
223
|
*/
|
|
213
224
|
export type WorkerManifest = {
|
|
214
225
|
readonly sdkVersion: string;
|
|
226
|
+
readonly credentials: readonly CredentialDeclaration[];
|
|
215
227
|
readonly databases: readonly RegisteredDatabase[];
|
|
216
228
|
readonly pacers: readonly PacerDeclaration[];
|
|
217
229
|
readonly capabilities: readonly CapabilityManifestEntry[];
|
|
@@ -243,9 +255,33 @@ const SDK_VERSION = (() => {
|
|
|
243
255
|
|
|
244
256
|
export class Worker {
|
|
245
257
|
#capabilities: Map<string, StoredCapability> = new Map();
|
|
258
|
+
#credentials: Map<string, CredentialDeclaration> = new Map();
|
|
246
259
|
#databases: Map<string, RegisteredDatabase> = new Map();
|
|
247
260
|
#pacers: Map<string, PacerDeclaration> = new Map();
|
|
248
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Declare a credential that is injected into matching outbound HTTPS
|
|
264
|
+
* requests without exposing its value to Worker code.
|
|
265
|
+
*/
|
|
266
|
+
credential(key: CredentialKey, config: CredentialConfiguration): void {
|
|
267
|
+
this.#validateUniqueKey(key);
|
|
268
|
+
if (this.#credentials.size >= MAX_CREDENTIALS) {
|
|
269
|
+
throw new Error(`Worker cannot declare more than ${MAX_CREDENTIALS} credentials`);
|
|
270
|
+
}
|
|
271
|
+
const declaration = createCredentialDeclaration(key, config);
|
|
272
|
+
validateCredentialDeclarationConflicts(this.#credentials.values(), declaration);
|
|
273
|
+
const declarations = [...this.#credentials.values(), declaration];
|
|
274
|
+
if (
|
|
275
|
+
new TextEncoder().encode(JSON.stringify(declarations)).byteLength >
|
|
276
|
+
MAX_CREDENTIAL_DECLARATIONS_BYTES
|
|
277
|
+
) {
|
|
278
|
+
throw new Error(
|
|
279
|
+
`Worker credential declarations must be at most ${MAX_CREDENTIAL_DECLARATIONS_BYTES} bytes`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
this.#credentials.set(key, declaration);
|
|
283
|
+
}
|
|
284
|
+
|
|
249
285
|
database<PK extends string, S extends Schema<PK>>(
|
|
250
286
|
key: string,
|
|
251
287
|
config: DatabaseConfig<PK, S>,
|
|
@@ -675,6 +711,7 @@ export class Worker {
|
|
|
675
711
|
get manifest(): WorkerManifest {
|
|
676
712
|
return {
|
|
677
713
|
sdkVersion: SDK_VERSION,
|
|
714
|
+
credentials: Array.from(this.#credentials.values()),
|
|
678
715
|
databases: Array.from(this.#databases.values()),
|
|
679
716
|
pacers: Array.from(this.#pacers.values()),
|
|
680
717
|
capabilities: this.capabilities,
|
|
@@ -723,7 +760,12 @@ export class Worker {
|
|
|
723
760
|
if (!key || typeof key !== "string") {
|
|
724
761
|
throw new Error("Capability key must be a non-empty string");
|
|
725
762
|
}
|
|
726
|
-
if (
|
|
763
|
+
if (
|
|
764
|
+
this.#capabilities.has(key) ||
|
|
765
|
+
this.#credentials.has(key) ||
|
|
766
|
+
this.#databases.has(key) ||
|
|
767
|
+
this.#pacers.has(key)
|
|
768
|
+
) {
|
|
727
769
|
throw new Error(`Worker item with key "${key}" already registered`);
|
|
728
770
|
}
|
|
729
771
|
}
|