mihrab-methods 1.0.2 → 1.1.0
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/README.md +23 -6
- package/certification.json +22 -22
- package/data/corrections/indonesia.json +721 -28
- package/data/corrections/morocco.json +201 -914
- package/dist/cjs/authorities/morocco.js +7 -6
- package/dist/cjs/core/compute.js +1 -1
- package/dist/cjs/data/generated/algeriaCertification.js +1 -1
- package/dist/cjs/data/generated/diyanetCertification.js +1 -1
- package/dist/cjs/data/generated/diyanetWorldPoints.js +1 -1
- package/dist/cjs/data/generated/indonesiaCertification.js +1 -1
- package/dist/cjs/data/generated/indonesiaCorrections.js +1 -1
- package/dist/cjs/data/generated/moroccoCertification.js +1 -1
- package/dist/cjs/data/generated/moroccoCorrections.js +1 -1
- package/dist/cjs/data/generated/moroccoMethodPoints.js +1 -1
- package/dist/cjs/data/generated/swedenCertification.js +1 -1
- package/dist/cjs/methods/morocco.d.ts +43 -20
- package/dist/cjs/methods/morocco.js +59 -25
- package/dist/cjs/methods/revisions.d.ts +1 -1
- package/dist/cjs/methods/revisions.js +1 -1
- package/dist/cjs/server.d.ts +12 -0
- package/dist/cjs/server.js +132 -0
- package/dist/esm/authorities/morocco.js +7 -6
- package/dist/esm/core/compute.js +1 -1
- package/dist/esm/data/generated/algeriaCertification.js +1 -1
- package/dist/esm/data/generated/diyanetCertification.js +1 -1
- package/dist/esm/data/generated/diyanetWorldPoints.js +1 -1
- package/dist/esm/data/generated/indonesiaCertification.js +1 -1
- package/dist/esm/data/generated/indonesiaCorrections.js +1 -1
- package/dist/esm/data/generated/moroccoCertification.js +1 -1
- package/dist/esm/data/generated/moroccoCorrections.js +1 -1
- package/dist/esm/data/generated/moroccoMethodPoints.js +1 -1
- package/dist/esm/data/generated/swedenCertification.js +1 -1
- package/dist/esm/methods/morocco.d.ts +43 -20
- package/dist/esm/methods/morocco.js +54 -24
- package/dist/esm/methods/revisions.d.ts +1 -1
- package/dist/esm/methods/revisions.js +1 -1
- package/dist/esm/server.d.ts +12 -0
- package/dist/esm/server.js +126 -0
- package/docs/methods.md +24 -8
- package/package.json +3 -1
- package/dist/cjs/methods/scratch-congress.d.ts +0 -87
- package/dist/cjs/methods/scratch-congress.js +0 -539
- package/dist/esm/methods/scratch-congress.d.ts +0 -87
- package/dist/esm/methods/scratch-congress.js +0 -534
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HttpError = void 0;
|
|
4
|
+
exports.handle = handle;
|
|
5
|
+
exports.respond = respond;
|
|
6
|
+
/**
|
|
7
|
+
* The HTTP API over the library — one handler, served two ways: by
|
|
8
|
+
* `tools/serve.ts` on Node (`npm run serve`) and by `api/worker.ts` at the
|
|
9
|
+
* edge (the public API). Nothing is logged or stored; every answer is JSON.
|
|
10
|
+
*
|
|
11
|
+
* GET / the routes, the methods' certification
|
|
12
|
+
* GET /v1/methods every method, as methodInfo gives it
|
|
13
|
+
* GET /v1/places?authority=algeria the authority's places (id, name, country, lat, lon, tz)
|
|
14
|
+
* GET /v1/nearest?authority=algeria&lat=36.75&lng=3.06
|
|
15
|
+
* GET /v1/times?place=algeria:27&date=2026-10-15 one day, as timesFor gives it
|
|
16
|
+
* GET /v1/times?authority=algeria&lat=36.75&lng=3.06&date=2026-10-15
|
|
17
|
+
* GET /v1/times?place=algeria:27&from=2026-10-01&to=2026-10-31
|
|
18
|
+
* GET /v1/authority?lat=36.75&lng=3.06 defaultAuthorityFor, with the country
|
|
19
|
+
*
|
|
20
|
+
* Options on /v1/times: asr=2 for the Hanafi Asr; strict=1 to refuse a date
|
|
21
|
+
* outside the certified span (HTTP 422) instead of answering 'method-only'.
|
|
22
|
+
*/
|
|
23
|
+
const index_js_1 = require("./index.js");
|
|
24
|
+
const locate_js_1 = require("./locate.js");
|
|
25
|
+
class HttpError extends Error {
|
|
26
|
+
status;
|
|
27
|
+
constructor(status, message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.status = status;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.HttpError = HttpError;
|
|
33
|
+
const isAuthority = (x) => !!x && index_js_1.AUTHORITIES.includes(x);
|
|
34
|
+
const num = (q, k) => {
|
|
35
|
+
const v = Number(q.get(k));
|
|
36
|
+
if (q.get(k) === null || !Number.isFinite(v))
|
|
37
|
+
throw new HttpError(400, `${k}: a number is needed`);
|
|
38
|
+
return v;
|
|
39
|
+
};
|
|
40
|
+
const authorityOf = (q) => {
|
|
41
|
+
const a = q.get('authority');
|
|
42
|
+
if (!isAuthority(a))
|
|
43
|
+
throw new HttpError(400, `authority: one of ${index_js_1.AUTHORITIES.join(', ')}`);
|
|
44
|
+
return a;
|
|
45
|
+
};
|
|
46
|
+
const placeOf = (q) => {
|
|
47
|
+
const id = q.get('place');
|
|
48
|
+
if (id) {
|
|
49
|
+
const p = (0, index_js_1.placeById)(id);
|
|
50
|
+
if (!p)
|
|
51
|
+
throw new HttpError(404, `no place ${id}`);
|
|
52
|
+
return p;
|
|
53
|
+
}
|
|
54
|
+
const near = (0, index_js_1.nearestPlace)(authorityOf(q), num(q, 'lat'), num(q, 'lng'));
|
|
55
|
+
if (!near)
|
|
56
|
+
throw new HttpError(404, 'no place near there');
|
|
57
|
+
return near.place;
|
|
58
|
+
};
|
|
59
|
+
const brief = (p) => ({ id: p.id, name: p.name, country: p.country, lat: p.lat, lon: p.lon, tz: p.tz });
|
|
60
|
+
const ROUTES = [
|
|
61
|
+
'/v1/methods',
|
|
62
|
+
'/v1/places?authority=<authority>',
|
|
63
|
+
'/v1/nearest?authority=<authority>&lat=<lat>&lng=<lng>',
|
|
64
|
+
'/v1/times?place=<authority:id>&date=<YYYY-MM-DD>[&asr=2][&strict=1]',
|
|
65
|
+
'/v1/times?authority=<authority>&lat=<lat>&lng=<lng>&date=<YYYY-MM-DD>',
|
|
66
|
+
'/v1/times?place=<authority:id>&from=<YYYY-MM-DD>&to=<YYYY-MM-DD>',
|
|
67
|
+
'/v1/authority?lat=<lat>&lng=<lng>',
|
|
68
|
+
];
|
|
69
|
+
/** The answer to a request path and query. Throws HttpError, UncertifiedMethodError, RangeError or TypeError. */
|
|
70
|
+
function handle(url) {
|
|
71
|
+
const q = url.searchParams;
|
|
72
|
+
switch (url.pathname.replace(/\/+$/, '') || '/') {
|
|
73
|
+
case '/':
|
|
74
|
+
return {
|
|
75
|
+
name: 'mihrab-methods',
|
|
76
|
+
description: 'The prayer-time authorities’ own calculation methods, each reproducing its published timetable to the minute.',
|
|
77
|
+
docs: 'https://github.com/MihrabHQ/mihrab-methods#readme',
|
|
78
|
+
routes: ROUTES,
|
|
79
|
+
authorities: index_js_1.AUTHORITIES,
|
|
80
|
+
// each method's record, as the library carries it (the feed on main may be newer: methodInfo says the date)
|
|
81
|
+
methods: index_js_1.AUTHORITIES.map(a => {
|
|
82
|
+
const m = (0, index_js_1.methodInfo)(a);
|
|
83
|
+
return { authority: a, name: m.name, revision: m.revision, certified: m.certified, checkedAt: m.certification?.checkedAt ?? null, places: m.places };
|
|
84
|
+
}),
|
|
85
|
+
};
|
|
86
|
+
case '/v1/methods':
|
|
87
|
+
return index_js_1.AUTHORITIES.map(index_js_1.methodInfo);
|
|
88
|
+
case '/v1/places':
|
|
89
|
+
return (0, index_js_1.placesOf)(authorityOf(q)).map(brief);
|
|
90
|
+
case '/v1/nearest': {
|
|
91
|
+
const near = (0, index_js_1.nearestPlace)(authorityOf(q), num(q, 'lat'), num(q, 'lng'));
|
|
92
|
+
return near && { place: brief(near.place), distanceKm: Math.round(near.distanceKm * 10) / 10 };
|
|
93
|
+
}
|
|
94
|
+
case '/v1/authority': {
|
|
95
|
+
const lat = num(q, 'lat'), lng = num(q, 'lng');
|
|
96
|
+
return { country: (0, locate_js_1.countryAt)(lat, lng), authority: (0, locate_js_1.defaultAuthorityFor)(lat, lng) };
|
|
97
|
+
}
|
|
98
|
+
case '/v1/times': {
|
|
99
|
+
const place = placeOf(q);
|
|
100
|
+
const opts = { asrShadow: q.get('asr') === '2' ? 2 : 1, strict: q.get('strict') === '1' };
|
|
101
|
+
const from = q.get('from'), to = q.get('to');
|
|
102
|
+
if (from && to)
|
|
103
|
+
return { place: brief(place), days: (0, index_js_1.timesForRange)(place, from, to, opts) };
|
|
104
|
+
return { place: brief(place), ...(0, index_js_1.timesFor)(place, q.get('date') ?? new Date().toISOString().slice(0, 10), opts) };
|
|
105
|
+
}
|
|
106
|
+
default:
|
|
107
|
+
throw new HttpError(404, `no such route; the routes are ${ROUTES.join(' · ')}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Status, body and headers for a request, errors included — what both servers send. */
|
|
111
|
+
function respond(url) {
|
|
112
|
+
let status = 200;
|
|
113
|
+
let body;
|
|
114
|
+
try {
|
|
115
|
+
body = handle(url);
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
status = e instanceof HttpError ? e.status : e instanceof index_js_1.UncertifiedMethodError ? 422 : e instanceof RangeError || e instanceof TypeError ? 400 : 500;
|
|
119
|
+
body = { error: e.message };
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
status,
|
|
123
|
+
body: JSON.stringify(body),
|
|
124
|
+
headers: {
|
|
125
|
+
'content-type': 'application/json; charset=utf-8',
|
|
126
|
+
'access-control-allow-origin': '*',
|
|
127
|
+
'access-control-allow-methods': 'GET, OPTIONS',
|
|
128
|
+
// A day's times never change between releases: let browsers and the edge keep an answer for an hour.
|
|
129
|
+
'cache-control': status === 200 ? 'public, max-age=3600' : 'no-store',
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -7,6 +7,7 @@ import points from '../data/generated/moroccoMethodPoints.js';
|
|
|
7
7
|
import corrections from '../data/generated/moroccoCorrections.js';
|
|
8
8
|
import certification from '../data/generated/moroccoCertification.js';
|
|
9
9
|
import { METHOD_REVISION } from '../methods/revisions.js';
|
|
10
|
+
import { moroccoDipEast, moroccoDipWest } from '../methods/morocco.js';
|
|
10
11
|
import { registerAuthority } from '../core/registry.js';
|
|
11
12
|
const AUTHORITY = 'morocco';
|
|
12
13
|
/**
|
|
@@ -16,7 +17,6 @@ const AUTHORITY = 'morocco';
|
|
|
16
17
|
const MOROCCO_TZ = 'Etc/GMT';
|
|
17
18
|
/** The ministry's clock: GMT+1, then GMT — its tables print GMT from 17 September 2026, three days before the clocks moved. */
|
|
18
19
|
export const MOROCCO_CLOCK = [['2020-01-01', 60], ['2026-09-17', 0]];
|
|
19
|
-
const MOROCCO_DIP_PER_SQRT_M = 0.02;
|
|
20
20
|
function buildPlaces() {
|
|
21
21
|
return cities
|
|
22
22
|
.filter(c => typeof c.lat === 'number' && typeof c.lng === 'number')
|
|
@@ -26,7 +26,7 @@ function buildPlaces() {
|
|
|
26
26
|
const p = points[key];
|
|
27
27
|
return p
|
|
28
28
|
? { ...base, lat: p[0], lon: p[1], dipDeg: p[2], ...(p.length > 3 ? { dipMaghribDeg: p[3] } : {}) }
|
|
29
|
-
: { ...base, lat: c.lat, lon: c.lng, dipDeg:
|
|
29
|
+
: { ...base, lat: c.lat, lon: c.lng, dipDeg: moroccoDipEast(c.heightM ?? 0), dipMaghribDeg: moroccoDipWest(c.heightM ?? 0) };
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
export const morocco = registerAuthority({
|
|
@@ -37,18 +37,19 @@ export const morocco = registerAuthority({
|
|
|
37
37
|
certification,
|
|
38
38
|
description: {
|
|
39
39
|
name: 'Morocco — Ministry of Habous and Islamic Affairs',
|
|
40
|
-
method: 'Meeus’s apparent sun taken at each prayer’s own moment; Fajr 19°, Isha 17
|
|
40
|
+
method: 'Meeus’s apparent sun taken at each prayer’s own moment; Fajr 19°, Isha 17°; Asr on the apparent sun — the gnomon’s shadow at noon plus one length, refraction on both altitudes; Dhuhr five minutes after transit; sunrise one minute before and Maghrib 1.8 minutes after the sun on −(34′ + s) less each city’s own horizon allowance east and west (an angle, about 0.32°–0.37° + 0.02°·√height); Fajr rounded down, the rest to the nearest minute; printed on the ministry’s clock (GMT+1 until 17 September 2026, then GMT).',
|
|
41
41
|
countries: ['MA'],
|
|
42
42
|
parameters: {
|
|
43
43
|
fajrAngle: 19,
|
|
44
44
|
ishaAngle: 17,
|
|
45
45
|
asrShadow: 1,
|
|
46
46
|
dhuhrAfterTransitMinutes: 5,
|
|
47
|
-
sunriseMarginMinutes: -
|
|
48
|
-
maghribMarginMinutes:
|
|
47
|
+
sunriseMarginMinutes: -1.0,
|
|
48
|
+
maghribMarginMinutes: 1.8,
|
|
49
49
|
refractionArcmin: 34,
|
|
50
|
+
asrRefraction: 'Sæmundsson from the true altitude at noon, Bennett under the apparent altitude at Asr (Meeus 16.4, 16.3)',
|
|
50
51
|
sunSemidiameter: 'by the sun’s distance that day',
|
|
51
|
-
horizonDip: 'per city and per side, fitted (Place.dipDeg, Place.dipMaghribDeg); else 0.
|
|
52
|
+
horizonDip: 'per city and per side, fitted (Place.dipDeg, Place.dipMaghribDeg); else 0.32° + 0.025°·√height east, 0.37° + 0.021°·√height west',
|
|
52
53
|
rounding: 'Fajr down to the minute, the rest to the nearest',
|
|
53
54
|
sun: 'Meeus, apparent, with the full nutation; chapter-28 equation of time; iterated at each prayer',
|
|
54
55
|
},
|
package/dist/esm/core/compute.js
CHANGED
|
@@ -32,7 +32,7 @@ export function candidateInstants(place, year, month, day, asrShadow = 1, opts =
|
|
|
32
32
|
case 'algeria':
|
|
33
33
|
case 'indonesia': {
|
|
34
34
|
const t = place.authority === 'morocco'
|
|
35
|
-
? moroccoTimes(place.lat, place.lon, year, month, day, place.dipDeg
|
|
35
|
+
? moroccoTimes(place.lat, place.lon, year, month, day, place.dipDeg, asrShadow, place.dipMaghribDeg)
|
|
36
36
|
: place.authority === 'algeria'
|
|
37
37
|
? algeriaTimes(place.lat, place.lon, year, month, day, asrShadow)
|
|
38
38
|
: kemenagTimes(place.lat, place.lon, year, month, day, { highland: place.highland, twilightDip: place.twilightDip, asrShadow, afternoon: place.afternoon });
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const data = { "certified": true, "checkedAt": "2026-
|
|
1
|
+
const data = { "certified": true, "checkedAt": "2026-11-04", "places": 68, "checks": [{ "tables": "Algeria — Ministry of Religious Affairs", "places": 68, "times": 101184, "off": 0, "offWithoutCorrections": 642, "from": "2026-10-01", "to": "2027-06-05" }], "corrections": { "from": "2026-10-01", "to": "2027-06-05", "entries": 642, "sha256": "f8d227cdd88aabe5bcd1775ec0324abfdae07a8a3986fdd19816337c2d4ab3ca" }, "revision": 1 };
|
|
2
2
|
export default data;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const data = { "certified": true, "checkedAt": "2026-
|
|
1
|
+
const data = { "certified": true, "checkedAt": "2026-11-04", "places": 8132, "checks": [{ "tables": "Diyanet — its tables worldwide", "places": 8132, "times": 69999603, "off": 0, "offWithoutCorrections": 9129, "from": "2024-01-01", "to": "2027-12-31" }], "corrections": { "from": "2024-01-01", "to": "2027-12-31", "entries": 9118, "sha256": "47997767e178ce48babd80f0e4a53460c5d379eb098bff1e7ebbba1e6afdf605" }, "revision": 1 };
|
|
2
2
|
export default data;
|