Skip to content

Instantly share code, notes, and snippets.

@junkor-1011
Last active August 23, 2025 16:40
Show Gist options
  • Select an option

  • Save junkor-1011/87565c80c98bb447fa6bba7e3a2a35f0 to your computer and use it in GitHub Desktop.

Select an option

Save junkor-1011/87565c80c98bb447fa6bba7e3a2a35f0 to your computer and use it in GitHub Desktop.
neverthrow practice

neverthrow practice

(todo)

{
"tasks": {
"dev": "deno run --watch main.ts"
},
"imports": {
"@std/assert": "jsr:@std/assert@1",
"neverthrow": "npm:neverthrow@^8.2.0",
"valibot": "npm:valibot@^1.1.0"
}
}
{
"version": "5",
"specifiers": {
"jsr:@std/assert@1": "1.0.14",
"jsr:@std/internal@^1.0.10": "1.0.10",
"npm:neverthrow@^8.2.0": "8.2.0",
"npm:valibot@^1.1.0": "1.1.0"
},
"jsr": {
"@std/[email protected]": {
"integrity": "68d0d4a43b365abc927f45a9b85c639ea18a9fab96ad92281e493e4ed84abaa4",
"dependencies": [
"jsr:@std/internal"
]
},
"@std/[email protected]": {
"integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7"
}
},
"npm": {
"@rollup/[email protected]": {
"integrity": "sha512-GUdZKTeKBq9WmEBzvFYuC88yk26vT66lQV8D5+9TgkfbewhLaTHRNATyzpQwwbHIfJvDJ3N9WJ90wK/uR3cy3Q==",
"os": ["linux"],
"cpu": ["x64"]
},
"[email protected]": {
"integrity": "sha512-kOCT/1MCPAxY5iUV3wytNFUMUolzuwd/VF/1KCx7kf6CutrOsTie+84zTGTpgQycjvfLdBBdvBvFLqFD2c0wkQ==",
"optionalDependencies": [
"@rollup/rollup-linux-x64-gnu"
]
},
"[email protected]": {
"integrity": "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw=="
}
},
"workspace": {
"dependencies": [
"jsr:@std/assert@1",
"npm:neverthrow@^8.2.0",
"npm:valibot@^1.1.0"
]
}
}
import { err, errAsync, ok, type Result, ResultAsync } from "neverthrow";
import * as v from "valibot";
import { type QuakeHistory, quakeHistorySchema } from "./example_1_schema.ts";
/** @see {@link https://www.p2pquake.net/develop/json_api_v2/ P2P地震情報} */
const baseUrl = "https://api.p2pquake.net/v2/history";
interface ErrorBase {
readonly errorName: string;
readonly summary: string;
readonly details?: string;
}
interface NetworkError extends ErrorBase {
readonly errorName: "NetworkError";
readonly inner: unknown;
}
interface JsonParseError extends ErrorBase {
readonly errorName: "JsonParseError";
readonly inner: unknown;
}
interface HttpClientError extends ErrorBase {
readonly errorName: "HttpClientError";
readonly inner: Response;
}
interface HttpServerError extends ErrorBase {
readonly errorName: "HttpServerError";
readonly inner: Response;
}
interface HttpUnknownError extends ErrorBase {
readonly errorName: "HttpUnknownError";
readonly inner: Response;
}
type HttpError = HttpClientError | HttpServerError | HttpUnknownError;
interface ValidationError extends ErrorBase {
readonly errorName: "ValidationError";
// readonly inner: v.ValiError<typeof quakeHistorySchema>;
readonly inner: v.ValiError<typeof quakeHistorySchema>["issues"];
}
type FetchQuakeHistoryError =
| NetworkError
| JsonParseError
| HttpError
| ValidationError;
function fetchQuakeData(): ResultAsync<QuakeHistory, FetchQuakeHistoryError> {
const url = new URL(baseUrl);
url.searchParams.append("limit", String(1));
url.searchParams.append("offset", String(0));
console.debug(url);
const result = ResultAsync.fromPromise(
fetch(baseUrl.toString(), {
method: "GET",
headers: {
"Content-Type": "application/json",
"accept": "application/json",
},
}),
(error): NetworkError => ({
errorName: "NetworkError",
summary: `NetworkError: ${
error instanceof Error ? error.message : String(error)
}`,
inner: error,
}),
)
.andThen((response): ResultAsync<unknown, JsonParseError | HttpError> => {
if (response.ok) {
return ResultAsync.fromPromise(
response.json(),
(e): JsonParseError => ({
errorName: "JsonParseError",
summary: "JsonParseError",
inner: e,
}),
);
} else {
const { status } = response;
if (status >= 400 && status < 500) {
return errAsync({
errorName: "HttpClientError",
summary: "HttpClientError",
inner: response,
});
} else if (status >= 500) {
return errAsync({
errorName: "HttpServerError",
summary: "HttpServerError",
inner: response,
});
} else {
const error: HttpUnknownError = {
errorName: "HttpUnknownError",
summary: "HttpUnknownError",
inner: response,
};
return errAsync(error);
}
}
})
.andThen((data): Result<QuakeHistory, ValidationError> => {
const parseResult = v.safeParse(quakeHistorySchema, data);
if (!parseResult.success) {
const error: ValidationError = {
errorName: "ValidationError",
summary: "ValidationError",
inner: parseResult.issues,
};
return err(error);
}
const validatedData: QuakeHistory = parseResult.output;
return ok(validatedData);
});
return result;
}
async function main() {
await fetchQuakeData().match(
(value) => {
console.log(JSON.stringify(value, null, 2));
},
(error) => {
switch (error.errorName) {
// case "NetworkError":
// case "JsonParseError":
// case "HttpClientError":
// case "HttpServerError":
// case "HttpUnknownError":
// case "ValidationError":
default: {
console.error(error);
}
}
},
);
}
if (import.meta.main) {
await main();
}
import * as v from "valibot";
const nonNegaIntSchema = v.pipe(
v.number(),
v.integer(),
v.minValue(0),
);
const areaPeersSchema = v.looseObject({
areas: v.array(
v.looseObject({
id: nonNegaIntSchema,
peer: nonNegaIntSchema,
}),
),
code: nonNegaIntSchema,
created_at: v.string(),
hop: nonNegaIntSchema,
id: v.string(),
time: v.string(),
uid: v.string(),
ver: v.string(),
detection: v.optional(v.string()),
});
const eewDetectionSchema = v.looseObject({
id: v.string(),
code: v.literal(554),
time: v.string(),
type: v.string(),
detection: v.optional(v.string()),
});
const otherSchema = v.looseObject({});
/** @see {@link https://www.p2pquake.net/develop/json_api_v2/ P2P地震情報} */
export const quakeHistorySchema = v.array(
v.union([
areaPeersSchema,
eewDetectionSchema,
otherSchema,
])
)
export type QuakeHistory = v.InferOutput<typeof quakeHistorySchema>;
export function add(a: number, b: number): number {
return a + b;
}
// Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts
if (import.meta.main) {
console.log("Add 2 + 3 =", add(2, 3));
}
import { assertEquals } from "@std/assert";
import { add } from "./main.ts";
Deno.test(function addTest() {
assertEquals(add(2, 3), 5);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment