@remit/data-ports 0.0.28 → 0.0.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/data-ports",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -1,2 +1,3 @@
1
1
  export type { RepositoryConformanceHarness } from "./harness.js";
2
2
  export { labelRepositoryConformance } from "./label.js";
3
+ export { senderSignerStandingRepositoryConformance } from "./sender-signer-standing.js";
@@ -0,0 +1,117 @@
1
+ import assert from "node:assert/strict";
2
+ import { after, before, describe, test } from "node:test";
3
+ import type { ISenderSignerStandingRepository } from "../interfaces/sender-signer-standing.js";
4
+ import type { RepositoryConformanceHarness } from "./harness.js";
5
+
6
+ export function senderSignerStandingRepositoryConformance(
7
+ harness: RepositoryConformanceHarness<ISenderSignerStandingRepository>,
8
+ ): void {
9
+ describe("ISenderSignerStandingRepository conformance", () => {
10
+ let repo: ISenderSignerStandingRepository;
11
+
12
+ before(async () => {
13
+ repo = await harness.createRepository();
14
+ });
15
+
16
+ after(() => harness.teardown());
17
+
18
+ test("the first observation creates the row at a count of one", async () => {
19
+ const accountConfigId = harness.makeId();
20
+ const observedAt = 1_700_000_000_000;
21
+
22
+ const standing = await repo.observe({
23
+ accountConfigId,
24
+ senderKey: "example.com",
25
+ signerDomain: "esp.example",
26
+ observedAt,
27
+ });
28
+
29
+ assert.equal(standing.messageCount, 1);
30
+ assert.equal(standing.firstSeenAt, observedAt);
31
+ assert.equal(standing.lastSeenAt, observedAt);
32
+ assert.equal(standing.userAffirmedAt, 0);
33
+ });
34
+
35
+ test("a repeat observation increments the count", async () => {
36
+ const accountConfigId = harness.makeId();
37
+ const key = {
38
+ accountConfigId,
39
+ senderKey: "example.com",
40
+ signerDomain: "esp.example",
41
+ };
42
+
43
+ await repo.observe({ ...key, observedAt: 1_700_000_000_000 });
44
+ const second = await repo.observe({
45
+ ...key,
46
+ observedAt: 1_700_000_060_000,
47
+ });
48
+
49
+ assert.equal(second.messageCount, 2);
50
+ assert.equal(
51
+ (await repo.get(accountConfigId, key.senderKey, key.signerDomain))
52
+ .messageCount,
53
+ 2,
54
+ );
55
+ });
56
+
57
+ test("firstSeenAt survives every later observation while lastSeenAt advances", async () => {
58
+ const accountConfigId = harness.makeId();
59
+ const key = {
60
+ accountConfigId,
61
+ senderKey: "news.example.com",
62
+ signerDomain: "unverified",
63
+ };
64
+ const first = 1_700_000_000_000;
65
+
66
+ await repo.observe({ ...key, observedAt: first });
67
+ await repo.observe({ ...key, observedAt: first + 60_000 });
68
+ const third = await repo.observe({ ...key, observedAt: first + 120_000 });
69
+
70
+ assert.equal(third.messageCount, 3);
71
+ assert.equal(third.firstSeenAt, first);
72
+ assert.equal(third.lastSeenAt, first + 120_000);
73
+
74
+ const reread = await repo.get(
75
+ accountConfigId,
76
+ key.senderKey,
77
+ key.signerDomain,
78
+ );
79
+ assert.equal(reread.firstSeenAt, first);
80
+ assert.equal(reread.lastSeenAt, first + 120_000);
81
+ });
82
+
83
+ test("a key differing only in signer domain is a separate row", async () => {
84
+ const accountConfigId = harness.makeId();
85
+ const observedAt = 1_700_000_000_000;
86
+
87
+ await repo.observe({
88
+ accountConfigId,
89
+ senderKey: "example.com",
90
+ signerDomain: "esp-one.example",
91
+ observedAt,
92
+ });
93
+ const other = await repo.observe({
94
+ accountConfigId,
95
+ senderKey: "example.com",
96
+ signerDomain: "esp-two.example",
97
+ observedAt,
98
+ });
99
+
100
+ assert.equal(other.messageCount, 1);
101
+ });
102
+
103
+ test("get reports not-found for a key never observed, and does not create it", async () => {
104
+ const accountConfigId = harness.makeId();
105
+
106
+ await assert.rejects(
107
+ repo.get(accountConfigId, "stranger.example", "esp.example"),
108
+ (error) => harness.isNotFoundError(error),
109
+ );
110
+
111
+ await assert.rejects(
112
+ repo.get(accountConfigId, "stranger.example", "esp.example"),
113
+ (error) => harness.isNotFoundError(error),
114
+ );
115
+ });
116
+ });
117
+ }
package/src/index.ts CHANGED
@@ -26,6 +26,7 @@ export type {
26
26
  export { holdsRoom } from "./interfaces/outbox-attachment.js";
27
27
  export type { IOutboxMessageRepository } from "./interfaces/outbox-message.js";
28
28
  export type { IQuarantineRepository } from "./interfaces/quarantine.js";
29
+ export type { ISenderSignerStandingRepository } from "./interfaces/sender-signer-standing.js";
29
30
  export type { IThreadMessageRepository } from "./interfaces/thread-message.js";
30
31
  export type {
31
32
  IUnitOfWork,
@@ -85,6 +86,7 @@ export type {
85
86
  MessageLabelItem,
86
87
  MessagePlacementMoveItem,
87
88
  MessageReferenceItem,
89
+ ObserveSenderSignerStandingInput,
88
90
  OrganizeJobRequestItem,
89
91
  OutboxAttachmentItem,
90
92
  OutboxMessageItem,
@@ -96,6 +98,7 @@ export type {
96
98
  RawMessageStorageItem,
97
99
  ResultList,
98
100
  SearchOptions,
101
+ SenderSignerStandingItem,
99
102
  ThreadMessageItem,
100
103
  UpdateAccountConfigInput,
101
104
  UpdateAccountExportRequestInput,
@@ -0,0 +1,37 @@
1
+ import type {
2
+ ObserveSenderSignerStandingInput,
3
+ SenderSignerStandingItem,
4
+ } from "../types.js";
5
+
6
+ /**
7
+ * Per-account record of how often one sender has been seen arriving under one
8
+ * DKIM signing identity.
9
+ *
10
+ * The authenticity derivation asks this port two things: whether a key already
11
+ * had standing when a message arrived, and — afterwards — that this message be
12
+ * counted. `get` answers the first and must not create anything; `observe`
13
+ * answers the second and is the only writer.
14
+ */
15
+ export interface ISenderSignerStandingRepository {
16
+ /**
17
+ * Record one observation of a key, creating the row at `messageCount = 1`
18
+ * or incrementing an existing one.
19
+ *
20
+ * `firstSeenAt` is set once, on creation, and never moves — an
21
+ * implementation that rewrites it on the increment path erases the age the
22
+ * derivation reads standing from. `lastSeenAt` advances on every call.
23
+ */
24
+ observe(
25
+ input: ObserveSenderSignerStandingInput,
26
+ ): Promise<SenderSignerStandingItem>;
27
+
28
+ /**
29
+ * The standing of one key. Throws the adapter's not-found error when the
30
+ * key has never been observed, and creates nothing.
31
+ */
32
+ get(
33
+ accountConfigId: string,
34
+ senderKey: string,
35
+ signerDomain: string,
36
+ ): Promise<SenderSignerStandingItem>;
37
+ }
package/src/types.ts CHANGED
@@ -25,6 +25,7 @@ import type {
25
25
  OutboxMessageSchema,
26
26
  QuarantineSchema,
27
27
  RawMessageStorageSchema,
28
+ SenderSignerStandingSchema,
28
29
  ThreadMessageSchema,
29
30
  } from "@remit/api-zod-schemas";
30
31
  import type { z } from "zod";
@@ -360,6 +361,7 @@ export type CreateMessageInput = Omit<
360
361
  | "status"
361
362
  | "syncStatus"
362
363
  | "category"
364
+ | "authenticityVerdict"
363
365
  | "hasListUnsubscribe"
364
366
  | "movedByRemit"
365
367
  > & {
@@ -367,6 +369,7 @@ export type CreateMessageInput = Omit<
367
369
  status?: MessageItem["status"];
368
370
  syncStatus?: MessageItem["syncStatus"];
369
371
  category?: MessageItem["category"];
372
+ authenticityVerdict?: MessageItem["authenticityVerdict"];
370
373
  hasListUnsubscribe?: MessageItem["hasListUnsubscribe"];
371
374
  movedByRemit?: MessageItem["movedByRemit"];
372
375
  };
@@ -527,3 +530,21 @@ export type CreateFilterAnchorInput = Omit<
527
530
  FilterAnchorItem,
528
531
  "createdAt" | "updatedAt"
529
532
  >;
533
+
534
+ export type SenderSignerStandingItem = z.infer<
535
+ typeof SenderSignerStandingSchema
536
+ >;
537
+
538
+ /**
539
+ * One message observed arriving for a `(senderKey, signerDomain)` pair.
540
+ *
541
+ * `observedAt` is supplied rather than taken from the clock so a caller
542
+ * replaying a body-sync batch records the arrival's own time, and so the
543
+ * `firstSeenAt`-immutability contract is assertable without sleeping.
544
+ */
545
+ export type ObserveSenderSignerStandingInput = {
546
+ accountConfigId: string;
547
+ senderKey: string;
548
+ signerDomain: string;
549
+ observedAt: number;
550
+ };