@ny4n/server 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +62 -2
- package/dist/connection.d.ts +32 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +4 -0
- package/dist/connection.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +41 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/listener.d.ts +8 -0
- package/dist/listener.d.ts.map +1 -0
- package/dist/listener.js +2 -0
- package/dist/listener.js.map +1 -0
- package/dist/server.d.ts +45 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +547 -0
- package/dist/server.js.map +1 -0
- package/dist/session-router.d.ts +39 -0
- package/dist/session-router.d.ts.map +1 -0
- package/dist/session-router.js +264 -0
- package/dist/session-router.js.map +1 -0
- package/dist/testing/client.d.ts +35 -0
- package/dist/testing/client.d.ts.map +1 -0
- package/dist/testing/client.js +138 -0
- package/dist/testing/client.js.map +1 -0
- package/dist/testing/host.d.ts +55 -0
- package/dist/testing/host.d.ts.map +1 -0
- package/dist/testing/host.js +178 -0
- package/dist/testing/host.js.map +1 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +4 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/server.d.ts +13 -0
- package/dist/testing/server.d.ts.map +1 -0
- package/dist/testing/server.js +17 -0
- package/dist/testing/server.js.map +1 -0
- package/dist/transports/unix/address.d.ts +3 -0
- package/dist/transports/unix/address.d.ts.map +1 -0
- package/dist/transports/unix/address.js +9 -0
- package/dist/transports/unix/address.js.map +1 -0
- package/dist/transports/unix/index.d.ts +5 -0
- package/dist/transports/unix/index.d.ts.map +1 -0
- package/dist/transports/unix/index.js +4 -0
- package/dist/transports/unix/index.js.map +1 -0
- package/dist/transports/unix/listener.d.ts +24 -0
- package/dist/transports/unix/listener.d.ts.map +1 -0
- package/dist/transports/unix/listener.js +421 -0
- package/dist/transports/unix/listener.js.map +1 -0
- package/dist/transports/unix/preset.d.ts +6 -0
- package/dist/transports/unix/preset.d.ts.map +1 -0
- package/dist/transports/unix/preset.js +22 -0
- package/dist/transports/unix/preset.js.map +1 -0
- package/dist/transports/unix/types.d.ts +15 -0
- package/dist/transports/unix/types.d.ts.map +1 -0
- package/dist/transports/unix/types.js +2 -0
- package/dist/transports/unix/types.js.map +1 -0
- package/dist/types.d.ts +52 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -5
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { SessionNotFoundError } from "../errors.js";
|
|
2
|
+
export class Deferred {
|
|
3
|
+
promise;
|
|
4
|
+
resolvePromise;
|
|
5
|
+
constructor() {
|
|
6
|
+
this.promise = new Promise((resolve) => {
|
|
7
|
+
this.resolvePromise = resolve;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
resolve(value) {
|
|
11
|
+
this.resolvePromise(value);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class TestHarness {
|
|
15
|
+
metadata;
|
|
16
|
+
closed = new Deferred();
|
|
17
|
+
#termination = new Deferred();
|
|
18
|
+
terminated = this.#termination.promise;
|
|
19
|
+
attachedClients = 0;
|
|
20
|
+
attachmentReleaseCount = 0;
|
|
21
|
+
closeCount = 0;
|
|
22
|
+
serviceCalls = [];
|
|
23
|
+
failAttachmentRelease;
|
|
24
|
+
failClose;
|
|
25
|
+
nextServiceError;
|
|
26
|
+
nextServiceResult = { ok: true };
|
|
27
|
+
nextCloseGate;
|
|
28
|
+
nextServiceGate;
|
|
29
|
+
constructor(metadata) {
|
|
30
|
+
this.metadata = metadata;
|
|
31
|
+
}
|
|
32
|
+
attachClient(_context) {
|
|
33
|
+
this.attachedClients += 1;
|
|
34
|
+
let released = false;
|
|
35
|
+
return {
|
|
36
|
+
invokeService: (call) => this.invokeService(call),
|
|
37
|
+
release: (_context) => {
|
|
38
|
+
if (released)
|
|
39
|
+
return;
|
|
40
|
+
this.attachmentReleaseCount += 1;
|
|
41
|
+
if (this.failAttachmentRelease)
|
|
42
|
+
throw this.failAttachmentRelease;
|
|
43
|
+
released = true;
|
|
44
|
+
this.attachedClients -= 1;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
async invokeService(call) {
|
|
49
|
+
this.serviceCalls.push(call);
|
|
50
|
+
if (this.nextServiceError) {
|
|
51
|
+
const error = this.nextServiceError;
|
|
52
|
+
this.nextServiceError = undefined;
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
const gate = this.nextServiceGate;
|
|
56
|
+
if (gate) {
|
|
57
|
+
this.nextServiceGate = undefined;
|
|
58
|
+
gate.entered.resolve(undefined);
|
|
59
|
+
await gate.release.promise;
|
|
60
|
+
}
|
|
61
|
+
const result = this.nextServiceResult;
|
|
62
|
+
this.nextServiceResult = { ok: true };
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
async close(_context) {
|
|
66
|
+
this.closeCount += 1;
|
|
67
|
+
const gate = this.nextCloseGate;
|
|
68
|
+
if (gate) {
|
|
69
|
+
this.nextCloseGate = undefined;
|
|
70
|
+
gate.entered.resolve(undefined);
|
|
71
|
+
await gate.release.promise;
|
|
72
|
+
}
|
|
73
|
+
if (this.failClose) {
|
|
74
|
+
const error = this.failClose;
|
|
75
|
+
this.failClose = undefined;
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
this.closed.resolve(undefined);
|
|
79
|
+
this.#termination.resolve(undefined);
|
|
80
|
+
}
|
|
81
|
+
async terminate(error) {
|
|
82
|
+
this.#termination.resolve(error);
|
|
83
|
+
}
|
|
84
|
+
gateNextClose() {
|
|
85
|
+
const gate = { entered: new Deferred(), release: new Deferred() };
|
|
86
|
+
this.nextCloseGate = gate;
|
|
87
|
+
return gate;
|
|
88
|
+
}
|
|
89
|
+
gateNextServiceCall() {
|
|
90
|
+
const gate = { entered: new Deferred(), release: new Deferred() };
|
|
91
|
+
this.nextServiceGate = gate;
|
|
92
|
+
return gate;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function createTestServerServices() {
|
|
96
|
+
return {
|
|
97
|
+
attachClient(presentation) {
|
|
98
|
+
return {
|
|
99
|
+
async invokeService(call, _publish, context) {
|
|
100
|
+
if (call.instance === undefined &&
|
|
101
|
+
call.serviceId === "nyan.session-management" &&
|
|
102
|
+
call.member === "attach" &&
|
|
103
|
+
call.args.length === 1 &&
|
|
104
|
+
typeof call.args[0] === "string") {
|
|
105
|
+
await presentation.attachSession(call.args[0], context);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
if (call.instance === undefined &&
|
|
109
|
+
call.serviceId === "nyan.session-management" &&
|
|
110
|
+
call.member === "detach" &&
|
|
111
|
+
call.args.length === 0) {
|
|
112
|
+
await presentation.detachSession(context);
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`Unsupported test server service ${call.serviceId}.${call.member}`);
|
|
116
|
+
},
|
|
117
|
+
release() { },
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export class TestServerHost {
|
|
123
|
+
serverServices = createTestServerServices();
|
|
124
|
+
sessions = new Map();
|
|
125
|
+
harnesses = new Map();
|
|
126
|
+
openSessionCount = 0;
|
|
127
|
+
nextOpenSessionError;
|
|
128
|
+
nextHarnessCloseError;
|
|
129
|
+
nextOpenSessionGate;
|
|
130
|
+
async resolveSession(sessionId, _context) {
|
|
131
|
+
const metadata = this.sessions.get(sessionId);
|
|
132
|
+
if (!metadata)
|
|
133
|
+
throw new SessionNotFoundError(`Unknown session: ${sessionId}`);
|
|
134
|
+
return metadata;
|
|
135
|
+
}
|
|
136
|
+
async openSession(metadata, _context) {
|
|
137
|
+
this.openSessionCount += 1;
|
|
138
|
+
const gate = this.nextOpenSessionGate;
|
|
139
|
+
if (gate) {
|
|
140
|
+
this.nextOpenSessionGate = undefined;
|
|
141
|
+
gate.entered.resolve(undefined);
|
|
142
|
+
await gate.release.promise;
|
|
143
|
+
}
|
|
144
|
+
if (!this.sessions.has(metadata.id))
|
|
145
|
+
throw new SessionNotFoundError(`Unknown session: ${metadata.id}`);
|
|
146
|
+
if (this.nextOpenSessionError) {
|
|
147
|
+
const error = this.nextOpenSessionError;
|
|
148
|
+
this.nextOpenSessionError = undefined;
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
const harness = new TestHarness(metadata);
|
|
152
|
+
if (this.nextHarnessCloseError) {
|
|
153
|
+
harness.failClose = this.nextHarnessCloseError;
|
|
154
|
+
this.nextHarnessCloseError = undefined;
|
|
155
|
+
}
|
|
156
|
+
const harnesses = this.harnesses.get(metadata.id) ?? [];
|
|
157
|
+
harnesses.push(harness);
|
|
158
|
+
this.harnesses.set(metadata.id, harnesses);
|
|
159
|
+
return harness;
|
|
160
|
+
}
|
|
161
|
+
async seed(id = "session-1") {
|
|
162
|
+
const metadata = { id };
|
|
163
|
+
this.sessions.set(id, metadata);
|
|
164
|
+
return metadata;
|
|
165
|
+
}
|
|
166
|
+
gateNextOpenSession() {
|
|
167
|
+
const gate = { entered: new Deferred(), release: new Deferred() };
|
|
168
|
+
this.nextOpenSessionGate = gate;
|
|
169
|
+
return gate;
|
|
170
|
+
}
|
|
171
|
+
latestHarness(id) {
|
|
172
|
+
const harnesses = this.harnesses.get(id);
|
|
173
|
+
if (!harnesses?.length)
|
|
174
|
+
throw new Error(`No harness for ${id}`);
|
|
175
|
+
return harnesses.at(-1);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/testing/host.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context, JsonValue, ServiceCall } from \"@ny4n/chord\";\nimport { SessionNotFoundError } from \"../errors.ts\";\nimport type { RoutedServerServiceHost, RoutedSessionHandle, ServerHost, SessionMetadata } from \"../types.ts\";\n\nexport class Deferred<T> {\n\treadonly promise: Promise<T>;\n\tprivate resolvePromise!: (value: T) => void;\n\n\tconstructor() {\n\t\tthis.promise = new Promise<T>((resolve) => {\n\t\t\tthis.resolvePromise = resolve;\n\t\t});\n\t}\n\n\tresolve(value: T): void {\n\t\tthis.resolvePromise(value);\n\t}\n}\n\ninterface OpenGate {\n\tentered: Deferred<void>;\n\trelease: Deferred<void>;\n}\n\nexport class TestHarness {\n\treadonly metadata: SessionMetadata;\n\treadonly closed = new Deferred<void>();\n\treadonly #termination = new Deferred<Error | undefined>();\n\treadonly terminated = this.#termination.promise;\n\tattachedClients = 0;\n\tattachmentReleaseCount = 0;\n\tcloseCount = 0;\n\treadonly serviceCalls: ServiceCall[] = [];\n\tfailAttachmentRelease?: Error;\n\tfailClose?: Error;\n\tnextServiceError?: Error;\n\tnextServiceResult: JsonValue | undefined = { ok: true };\n\tprivate nextCloseGate?: OpenGate;\n\tprivate nextServiceGate?: OpenGate;\n\n\tconstructor(metadata: SessionMetadata) {\n\t\tthis.metadata = metadata;\n\t}\n\n\tattachClient(_context: Context): {\n\t\tinvokeService: TestHarness[\"invokeService\"];\n\t\trelease(context: Context): void;\n\t} {\n\t\tthis.attachedClients += 1;\n\t\tlet released = false;\n\t\treturn {\n\t\t\tinvokeService: (call) => this.invokeService(call),\n\t\t\trelease: (_context) => {\n\t\t\t\tif (released) return;\n\t\t\t\tthis.attachmentReleaseCount += 1;\n\t\t\t\tif (this.failAttachmentRelease) throw this.failAttachmentRelease;\n\t\t\t\treleased = true;\n\t\t\t\tthis.attachedClients -= 1;\n\t\t\t},\n\t\t};\n\t}\n\n\tasync invokeService(call: ServiceCall): Promise<JsonValue | undefined> {\n\t\tthis.serviceCalls.push(call);\n\t\tif (this.nextServiceError) {\n\t\t\tconst error = this.nextServiceError;\n\t\t\tthis.nextServiceError = undefined;\n\t\t\tthrow error;\n\t\t}\n\t\tconst gate = this.nextServiceGate;\n\t\tif (gate) {\n\t\t\tthis.nextServiceGate = undefined;\n\t\t\tgate.entered.resolve(undefined);\n\t\t\tawait gate.release.promise;\n\t\t}\n\t\tconst result = this.nextServiceResult;\n\t\tthis.nextServiceResult = { ok: true };\n\t\treturn result;\n\t}\n\n\tasync close(_context: Context): Promise<void> {\n\t\tthis.closeCount += 1;\n\t\tconst gate = this.nextCloseGate;\n\t\tif (gate) {\n\t\t\tthis.nextCloseGate = undefined;\n\t\t\tgate.entered.resolve(undefined);\n\t\t\tawait gate.release.promise;\n\t\t}\n\t\tif (this.failClose) {\n\t\t\tconst error = this.failClose;\n\t\t\tthis.failClose = undefined;\n\t\t\tthrow error;\n\t\t}\n\t\tthis.closed.resolve(undefined);\n\t\tthis.#termination.resolve(undefined);\n\t}\n\n\tasync terminate(error: Error): Promise<void> {\n\t\tthis.#termination.resolve(error);\n\t}\n\n\tgateNextClose(): OpenGate {\n\t\tconst gate = { entered: new Deferred<void>(), release: new Deferred<void>() };\n\t\tthis.nextCloseGate = gate;\n\t\treturn gate;\n\t}\n\n\tgateNextServiceCall(): OpenGate {\n\t\tconst gate = { entered: new Deferred<void>(), release: new Deferred<void>() };\n\t\tthis.nextServiceGate = gate;\n\t\treturn gate;\n\t}\n}\n\nexport function createTestServerServices(): RoutedServerServiceHost {\n\treturn {\n\t\tattachClient(presentation) {\n\t\t\treturn {\n\t\t\t\tasync invokeService(call, _publish, context) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tcall.instance === undefined &&\n\t\t\t\t\t\tcall.serviceId === \"nyan.session-management\" &&\n\t\t\t\t\t\tcall.member === \"attach\" &&\n\t\t\t\t\t\tcall.args.length === 1 &&\n\t\t\t\t\t\ttypeof call.args[0] === \"string\"\n\t\t\t\t\t) {\n\t\t\t\t\t\tawait presentation.attachSession(call.args[0], context);\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t\tif (\n\t\t\t\t\t\tcall.instance === undefined &&\n\t\t\t\t\t\tcall.serviceId === \"nyan.session-management\" &&\n\t\t\t\t\t\tcall.member === \"detach\" &&\n\t\t\t\t\t\tcall.args.length === 0\n\t\t\t\t\t) {\n\t\t\t\t\t\tawait presentation.detachSession(context);\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t\tthrow new Error(`Unsupported test server service ${call.serviceId}.${call.member}`);\n\t\t\t\t},\n\t\t\t\trelease() {},\n\t\t\t};\n\t\t},\n\t};\n}\n\nexport class TestServerHost implements ServerHost {\n\treadonly serverServices = createTestServerServices();\n\treadonly sessions = new Map<string, SessionMetadata>();\n\treadonly harnesses = new Map<string, TestHarness[]>();\n\topenSessionCount = 0;\n\tnextOpenSessionError?: Error;\n\tnextHarnessCloseError?: Error;\n\tprivate nextOpenSessionGate?: OpenGate;\n\n\tasync resolveSession(sessionId: string, _context: Context): Promise<SessionMetadata> {\n\t\tconst metadata = this.sessions.get(sessionId);\n\t\tif (!metadata) throw new SessionNotFoundError(`Unknown session: ${sessionId}`);\n\t\treturn metadata;\n\t}\n\n\tasync openSession(metadata: SessionMetadata, _context: Context): Promise<RoutedSessionHandle> {\n\t\tthis.openSessionCount += 1;\n\t\tconst gate = this.nextOpenSessionGate;\n\t\tif (gate) {\n\t\t\tthis.nextOpenSessionGate = undefined;\n\t\t\tgate.entered.resolve(undefined);\n\t\t\tawait gate.release.promise;\n\t\t}\n\t\tif (!this.sessions.has(metadata.id)) throw new SessionNotFoundError(`Unknown session: ${metadata.id}`);\n\t\tif (this.nextOpenSessionError) {\n\t\t\tconst error = this.nextOpenSessionError;\n\t\t\tthis.nextOpenSessionError = undefined;\n\t\t\tthrow error;\n\t\t}\n\t\tconst harness = new TestHarness(metadata);\n\t\tif (this.nextHarnessCloseError) {\n\t\t\tharness.failClose = this.nextHarnessCloseError;\n\t\t\tthis.nextHarnessCloseError = undefined;\n\t\t}\n\t\tconst harnesses = this.harnesses.get(metadata.id) ?? [];\n\t\tharnesses.push(harness);\n\t\tthis.harnesses.set(metadata.id, harnesses);\n\t\treturn harness;\n\t}\n\n\tasync seed(id = \"session-1\"): Promise<SessionMetadata> {\n\t\tconst metadata: SessionMetadata = { id };\n\t\tthis.sessions.set(id, metadata);\n\t\treturn metadata;\n\t}\n\n\tgateNextOpenSession(): OpenGate {\n\t\tconst gate = { entered: new Deferred<void>(), release: new Deferred<void>() };\n\t\tthis.nextOpenSessionGate = gate;\n\t\treturn gate;\n\t}\n\n\tlatestHarness(id: string): TestHarness {\n\t\tconst harnesses = this.harnesses.get(id);\n\t\tif (!harnesses?.length) throw new Error(`No harness for ${id}`);\n\t\treturn harnesses.at(-1)!;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { WireChannel } from "./client.ts";
|
|
2
|
+
export { connectUnixTestClient, ProtocolTestClient } from "./client.ts";
|
|
3
|
+
export { createTestServerServices, Deferred, TestHarness, TestServerHost } from "./host.ts";
|
|
4
|
+
export type { TestServer, TestServerOptions } from "./server.ts";
|
|
5
|
+
export { createTestServer } from "./server.ts";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,wBAAwB,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC5F,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,wBAAwB,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE5F,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC","sourcesContent":["export type { WireChannel } from \"./client.ts\";\nexport { connectUnixTestClient, ProtocolTestClient } from \"./client.ts\";\nexport { createTestServerServices, Deferred, TestHarness, TestServerHost } from \"./host.ts\";\nexport type { TestServer, TestServerOptions } from \"./server.ts\";\nexport { createTestServer } from \"./server.ts\";\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Server } from "../server.ts";
|
|
2
|
+
import type { ServerHost, ServerOptions } from "../types.ts";
|
|
3
|
+
export interface TestServerOptions extends Omit<ServerOptions, "serverId"> {
|
|
4
|
+
host?: ServerHost;
|
|
5
|
+
serverId?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TestServer {
|
|
8
|
+
server: Server;
|
|
9
|
+
host: ServerHost;
|
|
10
|
+
}
|
|
11
|
+
/** Create an unstarted Server with deterministic defaults for transport conformance tests. */
|
|
12
|
+
export declare function createTestServer(options: TestServerOptions): TestServer;
|
|
13
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/testing/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG7D,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC;IACzE,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,CAAC;CACjB;AAED,8FAA8F;AAC9F,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAYvE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Server } from "../server.js";
|
|
2
|
+
import { TestServerHost } from "./host.js";
|
|
3
|
+
/** Create an unstarted Server with deterministic defaults for transport conformance tests. */
|
|
4
|
+
export function createTestServer(options) {
|
|
5
|
+
const host = options.host ?? new TestServerHost();
|
|
6
|
+
return {
|
|
7
|
+
server: new Server(host, {
|
|
8
|
+
listeners: options.listeners,
|
|
9
|
+
maxFrameLength: options.maxFrameLength,
|
|
10
|
+
handshakeTimeoutMs: options.handshakeTimeoutMs,
|
|
11
|
+
serverId: options.serverId ?? "00000000-0000-4000-8000-000000000001",
|
|
12
|
+
onError: options.onError,
|
|
13
|
+
}),
|
|
14
|
+
host,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/testing/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAY3C,8FAA8F;AAC9F,MAAM,UAAU,gBAAgB,CAAC,OAA0B;IAC1D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,cAAc,EAAE,CAAC;IAClD,OAAO;QACN,MAAM,EAAE,IAAI,MAAM,CAAC,IAAI,EAAE;YACxB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,sCAAsC;YACpE,OAAO,EAAE,OAAO,CAAC,OAAO;SACxB,CAAC;QACF,IAAI;KACJ,CAAC;AACH,CAAC","sourcesContent":["import { Server } from \"../server.ts\";\nimport type { ServerHost, ServerOptions } from \"../types.ts\";\nimport { TestServerHost } from \"./host.ts\";\n\nexport interface TestServerOptions extends Omit<ServerOptions, \"serverId\"> {\n\thost?: ServerHost;\n\tserverId?: string;\n}\n\nexport interface TestServer {\n\tserver: Server;\n\thost: ServerHost;\n}\n\n/** Create an unstarted Server with deterministic defaults for transport conformance tests. */\nexport function createTestServer(options: TestServerOptions): TestServer {\n\tconst host = options.host ?? new TestServerHost();\n\treturn {\n\t\tserver: new Server(host, {\n\t\t\tlisteners: options.listeners,\n\t\t\tmaxFrameLength: options.maxFrameLength,\n\t\t\thandshakeTimeoutMs: options.handshakeTimeoutMs,\n\t\t\tserverId: options.serverId ?? \"00000000-0000-4000-8000-000000000001\",\n\t\t\tonError: options.onError,\n\t\t}),\n\t\thost,\n\t};\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/address.ts"],"names":[],"mappings":"AAEA,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,CAKnF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
/** Derive the local Unix socket path for one logical server identity. */
|
|
3
|
+
export function getUnixSocketPath(serverId, serverDirectory) {
|
|
4
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(serverId)) {
|
|
5
|
+
throw new TypeError("Unix serverId must be a canonical lowercase UUIDv4");
|
|
6
|
+
}
|
|
7
|
+
return join(serverDirectory, `${serverId}.sock`);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=address.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address.js","sourceRoot":"","sources":["../../../src/transports/unix/address.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,yEAAyE;AACzE,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,eAAuB;IAC1E,IAAI,CAAC,uEAAuE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7F,MAAM,IAAI,SAAS,CAAC,oDAAoD,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,IAAI,CAAC,eAAe,EAAE,GAAG,QAAQ,OAAO,CAAC,CAAC;AAClD,CAAC","sourcesContent":["import { join } from \"node:path\";\n\n/** Derive the local Unix socket path for one logical server identity. */\nexport function getUnixSocketPath(serverId: string, serverDirectory: string): string {\n\tif (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(serverId)) {\n\t\tthrow new TypeError(\"Unix serverId must be a canonical lowercase UUIDv4\");\n\t}\n\treturn join(serverDirectory, `${serverId}.sock`);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/transports/unix/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC","sourcesContent":["export { getUnixSocketPath } from \"./address.ts\";\nexport { createUnixListener } from \"./listener.ts\";\nexport { createUnixServer } from \"./preset.ts\";\nexport type { UnixListenerOptions, UnixServerOptions } from \"./types.ts\";\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Socket } from "node:net";
|
|
2
|
+
import type { ByteConnection } from "../../connection.ts";
|
|
3
|
+
import type { ServerListener } from "../../listener.ts";
|
|
4
|
+
import type { UnixListenerOptions } from "./types.ts";
|
|
5
|
+
/** @internal Exported only for transport-level verification. */
|
|
6
|
+
export declare class UnixByteConnection implements ByteConnection {
|
|
7
|
+
private readonly socket;
|
|
8
|
+
private readonly gracefulCloseTimeoutMs;
|
|
9
|
+
private readonly maxPendingBytes;
|
|
10
|
+
private pendingBytes;
|
|
11
|
+
private closedValue;
|
|
12
|
+
private closing;
|
|
13
|
+
private writeTail;
|
|
14
|
+
private closePromise?;
|
|
15
|
+
private resolveClose?;
|
|
16
|
+
constructor(socket: Socket, gracefulCloseTimeoutMs: number, maxPendingBytes: number);
|
|
17
|
+
get closed(): boolean;
|
|
18
|
+
send(chunk: Uint8Array): Promise<void>;
|
|
19
|
+
close(finalChunk?: Uint8Array): Promise<void>;
|
|
20
|
+
markClosed(): void;
|
|
21
|
+
private write;
|
|
22
|
+
}
|
|
23
|
+
export declare function createUnixListener(options: UnixListenerOptions): ServerListener;
|
|
24
|
+
//# sourceMappingURL=listener.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/listener.ts"],"names":[],"mappings":"AAGA,OAAO,EAA+C,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAGpF,OAAO,KAAK,EAAE,cAAc,EAA0B,MAAM,qBAAqB,CAAC;AAClF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAqLtD,gEAAgE;AAChE,qBAAa,kBAAmB,YAAW,cAAc;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAS;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,YAAY,CAAC,CAAgB;IACrC,OAAO,CAAC,YAAY,CAAC,CAAa;IAElC,YAAY,MAAM,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAIlF;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBrC;IAED,KAAK,CAAC,UAAU,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B5C;IAED,UAAU,IAAI,IAAI,CAMjB;IAED,OAAO,CAAC,KAAK;CAsBb;AAgGD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,cAAc,CAE/E"}
|