• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RobinTail / zod-sockets / 15530075333

09 Jun 2025 08:18AM UTC coverage: 99.863% (-0.1%) from 100.0%
15530075333

Pull #489

github

web-flow
Merge 8411ff06a into 546b91127
Pull Request #489: Switching to Zod 4

390 of 412 branches covered (94.66%)

622 of 623 new or added lines in 15 files covered. (99.84%)

1 existing line in 1 file now uncovered.

1459 of 1461 relevant lines covered (99.86%)

179.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.35
/src/integration.ts
1
import ts from "typescript";
6✔
2
import { z } from "zod/v4";
3
import { AbstractAction } from "./action";
4
import { makeCleanId } from "./common-helpers";
6✔
5
import { Config } from "./config";
6
import { makeEventFnSchema } from "./integration-helpers";
6✔
7
import { Namespaces, normalizeNS } from "./namespace";
6✔
8
import { zodToTs } from "./zts";
6✔
9
import {
6✔
10
  addJsDoc,
11
  makeType,
12
  printNode,
13
  f,
14
  exportModifier,
15
} from "./typescript-api";
16

17
interface IntegrationProps {
18
  config: Config<Namespaces>;
19
  actions: AbstractAction[];
20
  /**
21
   * @desc When event has both .rest() and an acknowledgement, the "...rest" can not be placed in a middle.
22
   * @desc In this case, overloads are used to reflect variations on different number of the function arguments.
23
   * @default 3
24
   * @example ( (cb) => void ) | ( (rest1, cb) => void ) | ( (rest1, rest2, cb) => void )
25
   */
26
  maxOverloads?: number;
27
}
28

29
const fallbackNs = "root";
6✔
30
const registryScopes = ["emission", "actions"];
6✔
31

32
export class Integration {
6✔
33
  protected program: ts.Node[] = [];
10✔
34
  protected aliases: Record<
12✔
35
    string, // namespace
36
    Map<object, ts.TypeAliasDeclaration>
37
  > = {};
12✔
38
  protected ids = {
12✔
39
    path: f.createIdentifier("path"),
12✔
40
    socket: f.createIdentifier("Socket"),
12✔
41
    socketBase: f.createIdentifier("SocketBase"),
12✔
42
    ioClient: f.createStringLiteral("socket.io-client"),
12✔
43
    emission: f.createIdentifier(makeCleanId(registryScopes[0])),
12✔
44
    actions: f.createIdentifier(makeCleanId(registryScopes[1])),
12✔
45
  };
12✔
46
  protected registry: Record<
12✔
47
    string, // namespace
48
    Record<
49
      (typeof registryScopes)[number],
50
      { event: string; node: ts.TypeNode }[]
51
    >
52
  > = {};
12✔
53

54
  protected makeAlias(
10✔
55
    ns: string,
12✔
56
    key: object,
12✔
57
    produce: () => ts.TypeNode,
12✔
58
  ): ts.TypeReferenceNode {
12✔
59
    let name = this.aliases[ns].get(key)?.name?.text;
12✔
60
    if (!name) {
12✔
61
      name = `Type${this.aliases[ns].size + 1}`;
6✔
62
      const temp = f.createLiteralTypeNode(f.createNull());
6✔
63
      this.aliases[ns].set(key, makeType(name, temp));
6✔
64
      this.aliases[ns].set(key, makeType(name, produce()));
6✔
65
    }
6✔
66
    return f.createTypeReferenceNode(name);
12✔
67
  }
12✔
68

69
  constructor({
10✔
70
    config: { namespaces },
12✔
71
    actions,
12✔
72
    maxOverloads = 3,
12✔
73
  }: IntegrationProps) {
12✔
74
    this.program.push(
12✔
75
      f.createImportDeclaration(
12✔
76
        undefined,
12✔
77
        f.createImportClause(
12✔
78
          true,
12✔
79
          undefined,
12✔
80
          f.createNamedImports([
12✔
81
            f.createImportSpecifier(
12✔
82
              false,
12✔
83
              this.ids.socket,
12✔
84
              this.ids.socketBase,
12✔
85
            ),
12✔
86
          ]),
12✔
87
        ),
12✔
88
        this.ids.ioClient,
12✔
89
      ),
12✔
90
    );
12✔
91

92
    for (const [ns, { emission }] of Object.entries(namespaces)) {
12✔
93
      this.aliases[ns] = new Map<z.ZodTypeAny, ts.TypeAliasDeclaration>();
12✔
94
      this.registry[ns] = { emission: [], actions: [] };
12✔
95
      const commons = { makeAlias: this.makeAlias.bind(this, ns) };
12✔
96
      for (const [event, { schema, ack }] of Object.entries(emission)) {
12✔
97
        const node = zodToTs(makeEventFnSchema(schema, ack, maxOverloads), {
48✔
98
          isResponse: true,
48✔
99
          ...commons,
48✔
100
        });
48✔
101
        this.registry[ns].emission.push({ event, node });
48✔
102
      }
48✔
103
      for (const action of actions) {
12✔
104
        if (action.getNamespace() === ns) {
30✔
105
          const event = action.getEvent();
30✔
106
          const input = action.getSchema("input");
30✔
107
          const output = action.getSchema("output");
30✔
108
          const node = zodToTs(makeEventFnSchema(input, output, maxOverloads), {
30✔
109
            isResponse: false,
30✔
110
            ...commons,
30✔
111
          });
30✔
112
          this.registry[ns].actions.push({ event, node });
30✔
113
        }
30✔
114
      }
30✔
115
    }
12✔
116

117
    for (const ns in this.registry) {
12✔
118
      const publicName = makeCleanId(ns) || makeCleanId(fallbackNs);
12✔
119

120
      const nsNameNode = f.createVariableStatement(
12✔
121
        exportModifier,
12✔
122
        f.createVariableDeclarationList(
12✔
123
          [
12✔
124
            f.createVariableDeclaration(
12✔
125
              this.ids.path,
12✔
126
              undefined,
12✔
127
              undefined,
12✔
128
              f.createStringLiteral(normalizeNS(ns)),
12✔
129
            ),
12✔
130
          ],
12✔
131
          ts.NodeFlags.Const,
12✔
132
        ),
12✔
133
      );
12✔
134
      addJsDoc(
12✔
135
        nsNameNode,
12✔
136
        `@desc The actual path of the ${publicName} namespace`,
12✔
137
      );
12✔
138

139
      const interfaces = Object.entries(this.registry[ns]).map(
12✔
140
        ([scope, events]) =>
12✔
141
          f.createInterfaceDeclaration(
24✔
142
            exportModifier,
24✔
143
            makeCleanId(scope),
24✔
144
            undefined,
24✔
145
            undefined,
24✔
146
            events.map(({ event, node }) =>
24✔
147
              f.createPropertySignature(undefined, event, undefined, node),
78✔
148
            ),
24✔
149
          ),
24✔
150
      );
12✔
151
      const socketNode = f.createTypeAliasDeclaration(
12✔
152
        exportModifier,
12✔
153
        this.ids.socket,
12✔
154
        undefined,
12✔
155
        f.createTypeReferenceNode(this.ids.socketBase, [
12✔
156
          f.createTypeReferenceNode(this.ids.emission),
12✔
157
          f.createTypeReferenceNode(this.ids.actions),
12✔
158
        ]),
12✔
159
      );
12✔
160
      addJsDoc(
12✔
161
        socketNode,
12✔
162
        `@example const socket: ${publicName}.${this.ids.socket.text} = io(${publicName}.${this.ids.path.text})`,
12✔
163
      );
12✔
164
      this.program.push(
12✔
165
        f.createModuleDeclaration(
12✔
166
          exportModifier,
12✔
167
          f.createIdentifier(publicName),
12✔
168
          f.createModuleBlock([
12✔
169
            nsNameNode,
12✔
170
            ...this.aliases[ns].values(),
12✔
171
            ...interfaces,
12✔
172
            socketNode,
12✔
173
          ]),
12✔
174
          ts.NodeFlags.Namespace,
12✔
175
        ),
12✔
176
      );
12✔
177
    }
12✔
178
  }
12✔
179

180
  public print(printerOptions?: ts.PrinterOptions) {
10✔
181
    return this.program
12✔
182
      .map((node, index) =>
12✔
183
        printNode(
24✔
184
          node,
24✔
185
          index < this.program.length
24✔
186
            ? printerOptions
24!
UNCOV
187
            : { ...printerOptions, omitTrailingSemicolon: true },
×
188
        ),
24✔
189
      )
12✔
190
      .join("\n\n");
12✔
191
  }
12✔
192
}
10✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc