-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Co-authored-by: Ciaran Morinan <37743469+CiaranMn@users.noreply.github.com>
- Loading branch information
Showing
38 changed files
with
899 additions
and
307 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// This code is written in a way that be *easily* auto generated, which is why we use the classes. | ||
|
||
import { ClientError } from "@local/harpc-client"; | ||
import { Decoder, Encoder } from "@local/harpc-client/codec"; | ||
import { | ||
Connection, | ||
Request, | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- otherwise TypeScript will fail on inference, I don't know why | ||
Response, | ||
Transaction, | ||
} from "@local/harpc-client/net"; | ||
import { | ||
ProcedureDescriptor, | ||
ProcedureId, | ||
SubsystemDescriptor, | ||
SubsystemId, | ||
Version, | ||
} from "@local/harpc-client/types"; | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- otherwise TypeScript will fail on inference, I don't know why | ||
import { RequestIdProducer } from "@local/harpc-client/wire-protocol"; | ||
import { | ||
Effect, | ||
Function, | ||
Option, | ||
pipe, | ||
Predicate, | ||
Schema, | ||
Stream, | ||
} from "effect"; | ||
|
||
const ServerResult = <A, I, R>(ok: Schema.Schema<A, I, R>) => | ||
Schema.transform( | ||
Schema.Union( | ||
Schema.Struct({ | ||
Ok: ok, | ||
}), | ||
Schema.Struct({ | ||
Err: Schema.Unknown, | ||
}), | ||
), | ||
Schema.Either({ | ||
left: Schema.instanceOf(ClientError.ServerError), | ||
right: Schema.typeSchema(ok), | ||
}), | ||
{ | ||
strict: true, | ||
decode: (value) => { | ||
if (Predicate.hasProperty(value, "Ok")) { | ||
return { _tag: "Right", right: value.Ok } as const; | ||
} | ||
|
||
return { | ||
_tag: "Left", | ||
left: new ClientError.ServerError({ cause: value.Err }), | ||
} as const; | ||
}, | ||
encode: (value) => { | ||
if (value._tag === "Right") { | ||
return { Ok: value.right }; | ||
} | ||
|
||
return { Err: value.left.cause } as const; | ||
}, | ||
}, | ||
); | ||
|
||
export class EchoSubsystem { | ||
static #subsystemId = 0x00; | ||
static #version = Version.make(0x00, 0x00); | ||
|
||
// eslint-disable-next-line func-names | ||
static echo = Effect.fn("echo")(function* (payload: string) { | ||
const procedureId = 0x00; | ||
|
||
const connection = yield* Connection.Connection; | ||
const encoder = yield* Encoder.Encoder; | ||
const decoder = yield* Decoder.Decoder; | ||
|
||
// buffer the stream, to send any encoding errors straight to the client | ||
// see: https://linear.app/hash/issue/H-3748/request-interruption | ||
const requestStream = yield* pipe( | ||
payload, | ||
Stream.succeed, | ||
encoder.encode(Schema.String), | ||
Stream.runCollect, | ||
Effect.map(Stream.fromChunk), | ||
); | ||
|
||
const request = yield* Request.make( | ||
SubsystemDescriptor.make( | ||
yield* SubsystemId.make(EchoSubsystem.#subsystemId), | ||
EchoSubsystem.#version, | ||
), | ||
ProcedureDescriptor.make(yield* ProcedureId.make(procedureId)), | ||
requestStream, | ||
); | ||
|
||
const transaction = yield* Connection.send(connection, request); | ||
const response = Transaction.read(transaction); | ||
|
||
const items = decoder.decode(response.body, ServerResult(Schema.String)); | ||
const item = yield* Stream.runHead(items); | ||
|
||
const eitherItem = Option.getOrThrowWith(item, () => | ||
ClientError.ExpectedItemCountMismatchError.exactly(1, 0), | ||
); | ||
|
||
return yield* eitherItem; | ||
}, Effect.map(Function.identity)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { Data, Inspectable, Match, Option, pipe } from "effect"; | ||
|
||
export class InvalidUtf8Error extends Data.TaggedError("InvalidUtf8Error")<{ | ||
readonly cause: unknown; | ||
}> { | ||
get message() { | ||
return "Invalid UTF-8 encoding"; | ||
} | ||
} | ||
|
||
export class ServerError extends Data.TaggedError("ServerError")<{ | ||
readonly cause: unknown; | ||
}> { | ||
get message() { | ||
return `Server error: ${Inspectable.toStringUnknown(this.cause)}`; | ||
} | ||
} | ||
|
||
export class ExpectedItemCountMismatchError extends Data.TaggedError( | ||
"ExpectedItemCountMismatchError", | ||
)<{ | ||
min: Option.Option<number>; | ||
max: Option.Option<number>; | ||
received: number; | ||
}> { | ||
static exactly(expected: number, actual: number) { | ||
return new ExpectedItemCountMismatchError({ | ||
min: Option.some(expected), | ||
max: Option.some(expected), | ||
received: actual, | ||
}); | ||
} | ||
|
||
static atLeast(expected: number, actual: number) { | ||
return new ExpectedItemCountMismatchError({ | ||
min: Option.some(expected), | ||
max: Option.none(), | ||
received: actual, | ||
}); | ||
} | ||
|
||
static atMost(expected: number, actual: number) { | ||
return new ExpectedItemCountMismatchError({ | ||
min: Option.none(), | ||
max: Option.some(expected), | ||
received: actual, | ||
}); | ||
} | ||
|
||
static between(min: number, max: number, actual: number) { | ||
return new ExpectedItemCountMismatchError({ | ||
min: Option.some(min), | ||
max: Option.some(max), | ||
received: actual, | ||
}); | ||
} | ||
|
||
get message() { | ||
return pipe( | ||
Match.value({ min: this.min, max: this.max }), | ||
Match.when( | ||
{ min: Option.isSome<number>, max: Option.isSome<number> }, | ||
({ min, max }) => | ||
min.value === max.value | ||
? `Expected exactly ${min.value} items, got ${this.received}` | ||
: `Expected between ${min.value} and ${max.value} items, got ${this.received}`, | ||
), | ||
Match.when( | ||
{ min: Option.isSome<number>, max: Option.isNone<number> }, | ||
({ min }) => | ||
`Expected at least ${min.value} items, got ${this.received}`, | ||
), | ||
Match.when( | ||
{ min: Option.isNone<number>, max: Option.isSome<number> }, | ||
({ max }) => | ||
`Expected at most ${max.value} items, got ${this.received}`, | ||
), | ||
Match.when( | ||
{ min: Option.isNone<number>, max: Option.isNone<number> }, | ||
() => `Mismatched amount of items, got ${this.received}`, | ||
), | ||
Match.orElseAbsurd, | ||
); | ||
} | ||
} |
Oops, something went wrong.