Skip to content

Commit a4970df

Browse files
hyperpolymathclaude
andcommitted
feat(rescript): port monitoring-api from TS to ReScript
Replaces 7 TS files (server + 6 routes, 659 LOC) with a single Server.res that mounts all six routers, plus shared FFI modules: - Express.res — Router, app, req/res accessors, middleware (cors, helmet, compression, rate-limit, json-parser). - Joi.res — schema builder + validator. - Db.res — service + collection methods (save/update/document/byExample/count) and scanner FFI, both backed by @module externals to the workspace packages. All six route surfaces preserved with the same paths: - POST /v1/scan, GET /v1/scan/:scanId - POST /v1/violations, GET /v1/violations/common, GET /v1/violations/site/:k, PATCH /v1/violations/:id/fixed - GET /v1/leaderboard, GET /v1/leaderboard/category/:c - GET /v1/badge/:domain (json + svg formats; SVG template inlined) - GET /v1/stats, GET /v1/stats/site/:k - GET /v1/dashboard/:orgId Tests deferred: 4 jest-style suites (e2e, property, aspect, benches; 1313 LOC). This completes the ReScript src migration for all 6 TS-bearing packages. Test ports across cli/github-action/monitoring-api remain. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 13664bb commit a4970df

18 files changed

Lines changed: 2591 additions & 675 deletions

tools/monitoring-api/package-lock.json

Lines changed: 972 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/monitoring-api/package.json

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22
"name": "@accessibility-everywhere/monitoring-api",
33
"version": "1.0.0",
44
"description": "Monitoring API for accessibility violation reporting and analytics",
5-
"main": "dist/server.js",
5+
"main": "src/Server.mjs",
6+
"type": "module",
67
"scripts": {
7-
"build": "tsc",
8-
"dev": "ts-node-dev --respawn src/server.ts",
9-
"start": "node dist/server.js",
10-
"test": "jest"
8+
"build": "rescript build",
9+
"dev": "rescript build -w",
10+
"start": "node src/Server.mjs",
11+
"clean": "rescript clean"
1112
},
1213
"dependencies": {
13-
"@accessibility-everywhere/core": "^1.0.0",
14-
"@accessibility-everywhere/scanner": "^1.0.0",
1514
"express": "^4.18.2",
1615
"cors": "^2.8.5",
1716
"helmet": "^7.1.0",
@@ -22,14 +21,7 @@
2221
"uuid": "^14.0.0"
2322
},
2423
"devDependencies": {
25-
"@types/express": "^4.17.21",
26-
"@types/cors": "^2.8.17",
27-
"@types/compression": "^1.7.5",
28-
"@types/uuid": "^9.0.7",
29-
"@types/node": "^20.10.0",
30-
"typescript": "^5.3.2",
31-
"ts-node-dev": "^2.0.0",
32-
"jest": "^29.7.0",
33-
"@types/jest": "^29.5.8"
24+
"rescript": "^11.1.0",
25+
"@rescript/core": "^1.5.0"
3426
}
3527
}

tools/monitoring-api/rescript.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "@accessibility-everywhere/monitoring-api",
3+
"sources": [
4+
{
5+
"dir": "src",
6+
"subdirs": true
7+
}
8+
],
9+
"package-specs": {
10+
"module": "esmodule",
11+
"in-source": true
12+
},
13+
"suffix": ".mjs",
14+
"bs-dependencies": ["@rescript/core"],
15+
"bsc-flags": ["-open RescriptCore"]
16+
}

tools/monitoring-api/src/Db.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Generated by ReScript, PLEASE EDIT WITH CARE
2+
/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */

tools/monitoring-api/src/Db.res

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
type collection
2+
type cursor
3+
type savedDoc = {_key: string}
4+
5+
@send external collSave: (collection, 'a) => promise<savedDoc> = "save"
6+
@send external collUpdate: (collection, string, 'a) => promise<unit> = "update"
7+
@send external collDocument: (collection, string) => promise<'a> = "document"
8+
@send external collByExample: (collection, 'a) => promise<cursor> = "byExample"
9+
@send external collCount: collection => promise<{"count": int}> = "count"
10+
11+
@send external cursorAll: cursor => promise<array<'a>> = "all"
12+
@get external cursorCount: cursor => option<int> = "count"
13+
14+
type wcagLevel = [#A | #"AA" | #"AAA"]
15+
type siteStatus = [#active | #inactive | #failed]
16+
17+
type site = {
18+
_key: string,
19+
url: string,
20+
domain: string,
21+
firstScanned: Date.t,
22+
lastScanned: Date.t,
23+
scanCount: int,
24+
currentScore: int,
25+
previousScore?: int,
26+
status: siteStatus,
27+
}
28+
29+
type scan = {
30+
_key: string,
31+
siteKey: string,
32+
timestamp: string,
33+
score: int,
34+
violations: int,
35+
passes: int,
36+
incomplete: int,
37+
url: string,
38+
wcagLevel: wcagLevel,
39+
duration: int,
40+
userAgent?: string,
41+
}
42+
43+
type violation = {
44+
_key: string,
45+
scanKey: string,
46+
siteKey: string,
47+
wcagCriterion: string,
48+
wcagLevel: wcagLevel,
49+
impact: string,
50+
description: string,
51+
helpUrl: string,
52+
selector: string,
53+
html: string,
54+
timestamp: Date.t,
55+
fixed: bool,
56+
}
57+
58+
type organization = {
59+
_key: string,
60+
name: string,
61+
tier: string,
62+
}
63+
64+
type criterionCount = {criterion: string, count: int}
65+
type trendPoint = {timestamp: Date.t, violations: int, score: int}
66+
67+
type service = {
68+
sites: collection,
69+
scans: collection,
70+
violations: collection,
71+
wcagCriteria: collection,
72+
organizations: collection,
73+
siteScans: collection,
74+
scanViolations: collection,
75+
violationCriteria: collection,
76+
orgSites: collection,
77+
}
78+
79+
@module("@accessibility-everywhere/core")
80+
external createArangoDBService: unit => service = "createArangoDBService"
81+
@module("@accessibility-everywhere/core") external initialize: service => promise<unit> = "initialize"
82+
@module("@accessibility-everywhere/core") external getSiteByUrl: (service, string) => promise<option<site>> = "getSiteByUrl"
83+
@module("@accessibility-everywhere/core")
84+
external getRecentScansForSite: (service, string, ~limit: int=?) => promise<array<scan>> = "getRecentScansForSite"
85+
@module("@accessibility-everywhere/core")
86+
external getViolationsForScan: (service, string) => promise<array<violation>> = "getViolationsForScan"
87+
@module("@accessibility-everywhere/core")
88+
external getTopSites: (service, ~limit: int=?) => promise<array<site>> = "getTopSites"
89+
@module("@accessibility-everywhere/core")
90+
external getCommonViolations: (service, ~limit: int=?) => promise<array<criterionCount>> = "getCommonViolations"
91+
@module("@accessibility-everywhere/core")
92+
external getSiteViolationTrend: (service, string, ~days: int=?) => promise<array<trendPoint>> = "getSiteViolationTrend"
93+
@module("@accessibility-everywhere/core")
94+
external getOrganizationSites: (service, string) => promise<array<site>> = "getOrganizationSites"
95+
96+
type scanner
97+
98+
type scanOptions = {
99+
url: string,
100+
wcagLevel: wcagLevel,
101+
screenshot?: bool,
102+
}
103+
104+
type scanNodeDetail = {target: array<string>, html: string}
105+
type scanViolationDetail = {
106+
impact: string,
107+
description: string,
108+
helpUrl: string,
109+
wcag: array<string>,
110+
nodes: array<scanNodeDetail>,
111+
}
112+
type scanPassDetail = {description: string}
113+
type scanIncompleteDetail = {description: string}
114+
type scanMetadata = {userAgent: string}
115+
type scanResult = {
116+
url: string,
117+
timestamp: string,
118+
score: int,
119+
duration: int,
120+
violations: array<scanViolationDetail>,
121+
passes: array<scanPassDetail>,
122+
incomplete: array<scanIncompleteDetail>,
123+
metadata: scanMetadata,
124+
}
125+
126+
@module("@accessibility-everywhere/scanner") external createScanner: unit => scanner = "createScanner"
127+
@send external runScan: (scanner, scanOptions) => promise<scanResult> = "scan"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Generated by ReScript, PLEASE EDIT WITH CARE
2+
3+
4+
var Cors = {};
5+
6+
var Helmet = {};
7+
8+
var Compression = {};
9+
10+
var RateLimit = {};
11+
12+
var Dotenv = {};
13+
14+
export {
15+
Cors ,
16+
Helmet ,
17+
Compression ,
18+
RateLimit ,
19+
Dotenv ,
20+
}
21+
/* No side effect */
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
type app
2+
type router
3+
type req
4+
type res
5+
type nextFn = Exn.t => unit
6+
type errorMiddleware = (Exn.t, req, res, nextFn) => unit
7+
type middleware = (req, res, nextFn) => unit
8+
type asyncHandler = (req, res, nextFn) => promise<unit>
9+
10+
@module("express") external make: unit => app = "default"
11+
@module("express") @scope("default") external router: unit => router = "Router"
12+
@module("express") @scope("default") external jsonParser: {..} => middleware = "json"
13+
14+
@send external use: (app, middleware) => unit = "use"
15+
@send external usePath: (app, string, middleware) => unit = "use"
16+
@send external useRouter: (app, string, router) => unit = "use"
17+
@send external useError: (app, errorMiddleware) => unit = "use"
18+
@send external useFinal: (app, middleware) => unit = "use"
19+
20+
@send external get: (app, string, asyncHandler) => unit = "get"
21+
@send external listen: (app, int, unit => unit) => unit = "listen"
22+
23+
@send external routerGet: (router, string, asyncHandler) => unit = "get"
24+
@send external routerPost: (router, string, asyncHandler) => unit = "post"
25+
@send external routerPatch: (router, string, asyncHandler) => unit = "patch"
26+
@send external routerPut: (router, string, asyncHandler) => unit = "put"
27+
@send external routerDelete: (router, string, asyncHandler) => unit = "delete"
28+
29+
@get external body: req => Dict.t<JSON.t> = "body"
30+
@get external params: req => Dict.t<string> = "params"
31+
@get external query: req => Dict.t<string> = "query"
32+
@get external protocol: req => string = "protocol"
33+
@send external getHeader: (req, string) => string = "get"
34+
35+
@send external resJson: (res, 'a) => unit = "json"
36+
@send external status: (res, int) => res = "status"
37+
@send external send: (res, string) => unit = "send"
38+
@send external setHeader: (res, string, string) => unit = "setHeader"
39+
40+
module Cors = {
41+
@module("cors") external make: unit => middleware = "default"
42+
}
43+
44+
module Helmet = {
45+
@module("helmet") external make: unit => middleware = "default"
46+
}
47+
48+
module Compression = {
49+
@module("compression") external make: unit => middleware = "default"
50+
}
51+
52+
module RateLimit = {
53+
type opts = {
54+
windowMs: int,
55+
max: int,
56+
message: string,
57+
}
58+
@module("express-rate-limit") external make: opts => middleware = "default"
59+
}
60+
61+
module Dotenv = {
62+
@module("dotenv") external config: unit => unit = "config"
63+
}

tools/monitoring-api/src/Joi.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Generated by ReScript, PLEASE EDIT WITH CARE
2+
/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */

tools/monitoring-api/src/Joi.res

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
type schema
2+
type validationDetail = {message: string}
3+
type validationError = {details: array<validationDetail>}
4+
type validationResult = {error: option<validationError>, value: Dict.t<JSON.t>}
5+
6+
@module("joi") external object: Dict.t<schema> => schema = "object"
7+
8+
@module("joi") external string: unit => schema = "string"
9+
@module("joi") external boolean: unit => schema = "boolean"
10+
@send external uri: schema => schema = "uri"
11+
@send external required: schema => schema = "required"
12+
@send external valid: (schema, array<string>) => schema = "valid"
13+
@send external defaultStr: (schema, string) => schema = "default"
14+
@send external defaultBool: (schema, bool) => schema = "default"
15+
16+
@send external validate: (schema, 'a) => validationResult = "validate"

0 commit comments

Comments
 (0)