nusus 0.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/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nusus contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # Nusus
2
+
3
+ TypeScript SDK for retrieving citable context from classical Islamic and Arabic texts. The first provider is [Turath](https://app.turath.io/).
4
+
5
+ ```bash
6
+ npm install nusus
7
+ # or
8
+ bun add nusus
9
+ ```
10
+
11
+ ```ts
12
+ import { createTurathClient } from "nusus/turath";
13
+
14
+ const turath = createTurathClient({ timeout: 10_000 });
15
+ const results = await turath.search("إنما الأعمال بالنيات", {
16
+ bookIds: [147927],
17
+ });
18
+ const context = await turath.getContext(results.items[0]);
19
+
20
+ console.log(context.text);
21
+ console.log(context.citation);
22
+ console.log(context.url);
23
+ ```
24
+
25
+ Agent-ready retrieval is one call:
26
+
27
+ ```ts
28
+ const context = await turath.retrieve("النية", {
29
+ maxPassages: 5,
30
+ maxCharsPerPassage: 2_000,
31
+ });
32
+ ```
33
+
34
+ All requests support `AbortSignal`; failures use the exported `NususError` codes. Source text is returned unchanged. Search filters currently accept one Turath ID each because that is all the upstream API has verified.
35
+
36
+ ## Agent CLI
37
+
38
+ ```bash
39
+ npx nusus "إنما الأعمال بالنيات" 3
40
+ npx nusus --book-id 147927 "النية" 3
41
+ npx nusus --page 147927 5
42
+
43
+ # Bun
44
+ bunx nusus "إنما الأعمال بالنيات" 3
45
+ ```
46
+
47
+ The CLI emits JSON Lines with passage text, source metadata, citations, and direct Turath URLs. It also supports `--madhhab hanafi|maliki|shafii|hanbali` and `--books "title one,title two"`.
48
+
49
+ ## Known limitations
50
+
51
+ Turath does not expose verified catalog-discovery endpoints, so book, author, and category filters require numeric IDs. Browser support is not claimed because the checked API responses do not advertise CORS support.
52
+
53
+ ## Development
54
+
55
+ ```bash
56
+ bun install
57
+ bun test
58
+ bun run typecheck
59
+ bun run build
60
+ ```
61
+
@@ -0,0 +1,14 @@
1
+ export type NususErrorCode = "INVALID_ARGUMENT" | "NOT_FOUND" | "RATE_LIMITED" | "HTTP_ERROR" | "INVALID_RESPONSE" | "ABORTED";
2
+ export declare class NususError extends Error {
3
+ readonly code: NususErrorCode;
4
+ readonly status?: number;
5
+ readonly url?: string;
6
+ readonly retryAfter?: number;
7
+ constructor(code: NususErrorCode, message: string, options?: {
8
+ status?: number;
9
+ url?: string;
10
+ retryAfter?: number;
11
+ cause?: unknown;
12
+ });
13
+ }
14
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GACtB,kBAAkB,GAClB,WAAW,GACX,cAAc,GACd,YAAY,GACZ,kBAAkB,GAClB,SAAS,CAAC;AAEd,qBAAa,UAAW,SAAQ,KAAK;IACnC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAG3B,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QACP,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,OAAO,CAAC;KACZ;CAST"}
package/dist/errors.js ADDED
@@ -0,0 +1,15 @@
1
+ export class NususError extends Error {
2
+ code;
3
+ status;
4
+ url;
5
+ retryAfter;
6
+ constructor(code, message, options = {}) {
7
+ super(message, { cause: options.cause });
8
+ this.name = "NususError";
9
+ this.code = code;
10
+ this.status = options.status;
11
+ this.url = options.url;
12
+ this.retryAfter = options.retryAfter;
13
+ }
14
+ }
15
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,UAAW,SAAQ,KAAK;IAC1B,IAAI,CAAiB;IACrB,MAAM,CAAU;IAChB,GAAG,CAAU;IACb,UAAU,CAAU;IAE7B,YACE,IAAoB,EACpB,OAAe,EACf,UAKI,EAAE;QAEN,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;QACvB,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IACvC,CAAC;CACF"}
@@ -0,0 +1,4 @@
1
+ export { NususError } from "./errors.js";
2
+ export type { NususErrorCode } from "./errors.js";
3
+ export type { Author, AuthorSummary, Book, BookSummary, CategorySummary, Passage, RetrievedContext, SearchPage, SourceLocation, TurathId, } from "./models.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EACV,MAAM,EACN,aAAa,EACb,IAAI,EACJ,WAAW,EACX,eAAe,EACf,OAAO,EACP,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,QAAQ,GACT,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { NususError } from "./errors.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,57 @@
1
+ export type TurathId = number | string;
2
+ export type AuthorSummary = {
3
+ id?: string;
4
+ name?: string;
5
+ };
6
+ export type CategorySummary = {
7
+ id: string;
8
+ title?: string;
9
+ };
10
+ export type BookSummary = {
11
+ id: string;
12
+ title: string;
13
+ };
14
+ export type Author = AuthorSummary & {
15
+ provider: "turath";
16
+ id: string;
17
+ biography?: string;
18
+ deathYear?: string;
19
+ raw?: unknown;
20
+ };
21
+ export type Book = BookSummary & {
22
+ provider: "turath";
23
+ author?: AuthorSummary;
24
+ category?: CategorySummary;
25
+ description?: string;
26
+ hasPdf?: boolean;
27
+ raw?: unknown;
28
+ };
29
+ export type SourceLocation = {
30
+ internalPage?: number;
31
+ printedPage?: number;
32
+ volume?: string;
33
+ };
34
+ export type Passage = {
35
+ provider: "turath";
36
+ book: BookSummary;
37
+ author?: AuthorSummary;
38
+ category?: CategorySummary;
39
+ location: SourceLocation;
40
+ text: string;
41
+ snippet?: string;
42
+ headings: string[];
43
+ url: string;
44
+ citation: string;
45
+ raw?: unknown;
46
+ };
47
+ export type SearchPage = {
48
+ items: Passage[];
49
+ totalMatches: number;
50
+ page: number;
51
+ };
52
+ export type RetrievedContext = {
53
+ passages: Passage[];
54
+ totalMatches: number;
55
+ query: string;
56
+ };
57
+ //# sourceMappingURL=models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG,aAAa,GAAG;IACnC,QAAQ,EAAE,QAAQ,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG,WAAW,GAAG;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC"}
package/dist/models.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":""}
@@ -0,0 +1,8 @@
1
+ export type FetchLike = typeof fetch;
2
+ export type TransportOptions = {
3
+ baseUrl?: string;
4
+ fetch?: FetchLike;
5
+ timeout?: number;
6
+ };
7
+ export declare const createTransport: ({ baseUrl, fetch: fetcher, timeout, }?: TransportOptions) => <T>(path: string, params: Record<string, string | number | undefined>, signal?: AbortSignal) => Promise<T>;
8
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,OAAO,KAAK,CAAC;AAErC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAUF,eAAO,MAAM,eAAe,GAAI,wCAI7B,gBAAqB,MAQR,CAAC,EAAE,MAAM,MAAM,EAAE,QAAQ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,EAAE,SAAS,WAAW,KAAG,OAAO,CAAC,CAAC,CA6CrH,CAAC"}
@@ -0,0 +1,70 @@
1
+ import { NususError } from "./errors.js";
2
+ const retryAfterSeconds = (value) => {
3
+ if (!value)
4
+ return undefined;
5
+ const seconds = Number(value);
6
+ if (Number.isFinite(seconds))
7
+ return Math.max(0, seconds);
8
+ const date = Date.parse(value);
9
+ return Number.isNaN(date) ? undefined : Math.max(0, Math.ceil((date - Date.now()) / 1000));
10
+ };
11
+ export const createTransport = ({ baseUrl = "https://api.turath.io/", fetch: fetcher = globalThis.fetch, timeout = 10_000, } = {}) => {
12
+ if (typeof fetcher !== "function") {
13
+ throw new NususError("INVALID_ARGUMENT", "A fetch implementation is required");
14
+ }
15
+ if (!Number.isFinite(timeout) || timeout < 0) {
16
+ throw new NususError("INVALID_ARGUMENT", "timeout must be a non-negative number");
17
+ }
18
+ return async (path, params, signal) => {
19
+ const url = new URL(path, baseUrl);
20
+ for (const [key, value] of Object.entries(params)) {
21
+ if (value !== undefined)
22
+ url.searchParams.set(key, String(value));
23
+ }
24
+ const controller = new AbortController();
25
+ const abort = () => controller.abort(signal?.reason);
26
+ signal?.addEventListener("abort", abort, { once: true });
27
+ if (signal?.aborted)
28
+ abort();
29
+ const timer = timeout ? setTimeout(() => controller.abort(new Error("Request timed out")), timeout) : undefined;
30
+ try {
31
+ if (controller.signal.aborted)
32
+ throw controller.signal.reason;
33
+ const response = await fetcher(url, {
34
+ headers: { accept: "application/json" },
35
+ signal: controller.signal,
36
+ });
37
+ if (!response.ok) {
38
+ const code = response.status === 404 ? "NOT_FOUND" : response.status === 429 ? "RATE_LIMITED" : "HTTP_ERROR";
39
+ throw new NususError(code, `Turath request failed with HTTP ${response.status}`, {
40
+ status: response.status,
41
+ url: url.href,
42
+ retryAfter: retryAfterSeconds(response.headers.get("retry-after")),
43
+ });
44
+ }
45
+ try {
46
+ return (await response.json());
47
+ }
48
+ catch (cause) {
49
+ throw new NususError("INVALID_RESPONSE", "Turath returned invalid JSON", { url: url.href, cause });
50
+ }
51
+ }
52
+ catch (cause) {
53
+ if (cause instanceof NususError)
54
+ throw cause;
55
+ if (controller.signal.aborted) {
56
+ throw new NususError("ABORTED", signal?.aborted ? "Request aborted" : "Request timed out", {
57
+ url: url.href,
58
+ cause,
59
+ });
60
+ }
61
+ throw new NususError("HTTP_ERROR", "Turath request failed", { url: url.href, cause });
62
+ }
63
+ finally {
64
+ if (timer)
65
+ clearTimeout(timer);
66
+ signal?.removeEventListener("abort", abort);
67
+ }
68
+ };
69
+ };
70
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { Passage } from "../models.js";
2
+ export type CitationSource = Pick<Passage, "author" | "book" | "location">;
3
+ export declare const getSourceUrl: (source: CitationSource) => string;
4
+ export declare const formatCitation: (source: CitationSource) => string;
5
+ //# sourceMappingURL=citations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"citations.d.ts","sourceRoot":"","sources":["../../src/turath/citations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC,CAAC;AAE3E,eAAO,MAAM,YAAY,GAAI,QAAQ,cAAc,KAAG,MAMrD,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,QAAQ,cAAc,KAAG,MAMvD,CAAC"}
@@ -0,0 +1,18 @@
1
+ export const getSourceUrl = (source) => {
2
+ const url = new URL(`https://app.turath.io/book/${source.book.id}`);
3
+ if (source.location.internalPage !== undefined) {
4
+ url.searchParams.set("page", String(source.location.internalPage));
5
+ }
6
+ return url.href;
7
+ };
8
+ export const formatCitation = (source) => {
9
+ const parts = [source.author?.name, source.book.title].filter(Boolean);
10
+ if (source.location.volume)
11
+ parts.push(`ج ${source.location.volume}`);
12
+ if (source.location.printedPage !== undefined)
13
+ parts.push(`ص ${source.location.printedPage}`);
14
+ else if (source.location.internalPage !== undefined)
15
+ parts.push(`صفحة تراث ${source.location.internalPage}`);
16
+ return parts.join("، ");
17
+ };
18
+ //# sourceMappingURL=citations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"citations.js","sourceRoot":"","sources":["../../src/turath/citations.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAsB,EAAU,EAAE;IAC7D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,8BAA8B,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IACpE,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC;AAClB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAsB,EAAU,EAAE;IAC/D,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAa,CAAC;IACnF,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACtE,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;SACzF,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,aAAa,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC;IAC7G,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { Book, Passage, RetrievedContext, SearchPage, TurathId } from "../models.js";
2
+ import { type TransportOptions } from "../transport.js";
3
+ import { type CitationSource } from "./citations.js";
4
+ export type RequestOptions = {
5
+ signal?: AbortSignal;
6
+ };
7
+ export type TurathSearchOptions = RequestOptions & {
8
+ authorIds?: TurathId[];
9
+ bookIds?: TurathId[];
10
+ categoryIds?: TurathId[];
11
+ page?: number;
12
+ sort?: "relevance" | "page";
13
+ };
14
+ export type ContextOptions = RequestOptions & {
15
+ pagesBefore?: number;
16
+ pagesAfter?: number;
17
+ };
18
+ export type RetrieveOptions = RequestOptions & {
19
+ maxPassages?: number;
20
+ maxCharsPerPassage?: number;
21
+ scope?: {
22
+ bookIds?: TurathId[];
23
+ authorIds?: TurathId[];
24
+ categoryIds?: TurathId[];
25
+ };
26
+ };
27
+ export type TurathClientOptions = TransportOptions;
28
+ export declare const createTurathClient: (options?: TurathClientOptions) => {
29
+ getAuthor: (authorId: TurathId, { signal }?: RequestOptions) => Promise<import("../models.js").Author>;
30
+ getBook: (bookId: TurathId, { signal }?: RequestOptions) => Promise<Book>;
31
+ getPage: (bookId: TurathId, pageId: TurathId, { signal }?: RequestOptions) => Promise<Passage>;
32
+ getPages: (bookId: TurathId, range: {
33
+ from: number;
34
+ to: number;
35
+ }, { signal }?: RequestOptions) => Promise<Passage[]>;
36
+ search: (query: string, options?: TurathSearchOptions) => Promise<SearchPage>;
37
+ searchAll: (query: string, options?: Omit<TurathSearchOptions, "page">) => AsyncGenerator<Passage>;
38
+ getContext: (source: Passage, options?: ContextOptions) => Promise<Passage>;
39
+ retrieve: (query: string, options?: RetrieveOptions) => Promise<RetrievedContext>;
40
+ formatCitation: (source: CitationSource) => string;
41
+ getSourceUrl: (source: CitationSource) => string;
42
+ };
43
+ export type TurathClient = ReturnType<typeof createTurathClient>;
44
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/turath/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAInF,MAAM,MAAM,cAAc,GAAG;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAEtD,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG;IACjD,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC;IACrB,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC;QACrB,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;QACvB,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC;KAC1B,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AAgCnD,eAAO,MAAM,kBAAkB,GAAI,UAAS,mBAAwB;0BAG/B,QAAQ,eAAc,cAAc;sBAOxC,QAAQ,eAAc,cAAc,KAAQ,OAAO,CAAC,IAAI,CAAC;sBAKzD,QAAQ,UAAU,QAAQ,eAAc,cAAc,KAAQ,OAAO,CAAC,OAAO,CAAC;uBASnG,QAAQ,SACT;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,eACvB,cAAc,KACzB,OAAO,CAAC,OAAO,EAAE,CAAC;oBAWQ,MAAM,YAAW,mBAAmB,KAAQ,OAAO,CAAC,UAAU,CAAC;uBA2BlD,MAAM,YAAW,IAAI,CAAC,mBAAmB,EAAE,MAAM,CAAC,KAAQ,cAAc,CAAC,OAAO,CAAC;yBAgBzF,OAAO,YAAW,cAAc,KAAQ,OAAO,CAAC,OAAO,CAAC;sBAmB3D,MAAM,YAAW,eAAe,KAAQ,OAAO,CAAC,gBAAgB,CAAC;6BAgCrE,cAAc;2BAChB,cAAc;CAExC,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
@@ -0,0 +1,156 @@
1
+ import { NususError } from "../errors.js";
2
+ import { createTransport } from "../transport.js";
3
+ import { formatCitation, getSourceUrl } from "./citations.js";
4
+ import { normalizeAuthor, normalizeBook, normalizePage, normalizeSearchHit } from "./normalize.js";
5
+ const id = (value, name = "id") => {
6
+ const result = String(value);
7
+ if (!/^[1-9]\d*$/.test(result))
8
+ throw new NususError("INVALID_ARGUMENT", `${name} must be a positive integer`);
9
+ return result;
10
+ };
11
+ const integer = (value, name, minimum = 0) => {
12
+ if (!Number.isInteger(value) || value < minimum) {
13
+ throw new NususError("INVALID_ARGUMENT", `${name} must be an integer of at least ${minimum}`);
14
+ }
15
+ return value;
16
+ };
17
+ const only = (values, name) => {
18
+ if (!values?.length)
19
+ return undefined;
20
+ if (values.length > 1) {
21
+ throw new NususError("INVALID_ARGUMENT", `Turath currently supports only one ${name} filter per search`);
22
+ }
23
+ return id(values[0], name);
24
+ };
25
+ const truncate = (text, maxChars) => {
26
+ const cut = text.slice(0, maxChars);
27
+ // avoid ending on a lone high surrogate (split code point)
28
+ return /[\uD800-\uDBFF]$/.test(cut) ? cut.slice(0, -1) : cut;
29
+ };
30
+ const emptyObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
31
+ export const createTurathClient = (options = {}) => {
32
+ const request = createTransport(options);
33
+ const getAuthor = async (authorId, { signal } = {}) => {
34
+ const author = id(authorId, "author id");
35
+ const raw = await request("author", { id: author, ver: 3 }, signal);
36
+ if (emptyObject(raw))
37
+ throw new NususError("NOT_FOUND", `Author ${author} not found`);
38
+ return normalizeAuthor(raw);
39
+ };
40
+ const getBook = async (bookId, { signal } = {}) => {
41
+ const book = id(bookId, "book id");
42
+ return normalizeBook(await request("book", { id: book, include: "indexes", ver: 3 }, signal));
43
+ };
44
+ const getPage = async (bookId, pageId, { signal } = {}) => {
45
+ const book = id(bookId, "book id");
46
+ const page = id(pageId, "page id");
47
+ const raw = await request("page", { book_id: book, pg: page, ver: 3 }, signal);
48
+ if (emptyObject(raw))
49
+ throw new NususError("NOT_FOUND", `Book ${book}, page ${page} not found`);
50
+ return normalizePage(raw, book);
51
+ };
52
+ const getPages = async (bookId, range, { signal } = {}) => {
53
+ integer(range.from, "from", 1);
54
+ integer(range.to, "to", range.from);
55
+ // ponytail: sequential page fetches; add bounded concurrency if large ranges get slow (Phase 3)
56
+ const pages = [];
57
+ for (let page = range.from; page <= range.to; page += 1) {
58
+ pages.push(await getPage(bookId, page, { signal }));
59
+ }
60
+ return pages;
61
+ };
62
+ const search = async (query, options = {}) => {
63
+ if (!query.trim())
64
+ throw new NususError("INVALID_ARGUMENT", "query must not be empty");
65
+ const page = integer(options.page ?? 1, "page", 1);
66
+ const raw = await request("search", {
67
+ q: query,
68
+ ver: 3,
69
+ page,
70
+ author: only(options.authorIds, "author"),
71
+ book: only(options.bookIds, "book"),
72
+ cat_id: only(options.categoryIds, "category"),
73
+ sort: options.sort === "page" ? "page_id" : undefined,
74
+ }, options.signal);
75
+ if (typeof raw !== "object" || raw === null ||
76
+ typeof raw.count !== "number" ||
77
+ !Array.isArray(raw.data)) {
78
+ throw new NususError("INVALID_RESPONSE", "Turath returned an invalid search response");
79
+ }
80
+ const response = raw;
81
+ return { items: response.data.map(normalizeSearchHit), totalMatches: response.count, page };
82
+ };
83
+ const searchAll = async function* (query, options = {}) {
84
+ let page = 1;
85
+ let yielded = 0;
86
+ while (true) {
87
+ const result = await search(query, { ...options, page });
88
+ if (!result.items.length)
89
+ return;
90
+ for (const item of result.items) {
91
+ yield item;
92
+ yielded += 1;
93
+ if (yielded >= result.totalMatches)
94
+ return;
95
+ }
96
+ if (yielded >= result.totalMatches)
97
+ return;
98
+ page += 1;
99
+ }
100
+ };
101
+ const getContext = async (source, options = {}) => {
102
+ const center = source.location.internalPage;
103
+ if (center === undefined)
104
+ throw new NususError("INVALID_ARGUMENT", "passage has no internal page");
105
+ integer(center, "internal page", 1);
106
+ const before = integer(options.pagesBefore ?? 1, "pagesBefore");
107
+ const after = integer(options.pagesAfter ?? 1, "pagesAfter");
108
+ const pages = [];
109
+ for (let page = Math.max(1, center - before); page <= center + after; page += 1) {
110
+ try {
111
+ pages.push(await getPage(source.book.id, page, { signal: options.signal }));
112
+ }
113
+ catch (error) {
114
+ if (!(error instanceof NususError) || error.code !== "NOT_FOUND" || page === center)
115
+ throw error;
116
+ }
117
+ }
118
+ const headings = [...new Set(pages.flatMap((page) => page.headings))];
119
+ const result = { ...source, text: pages.map((page) => page.text).join("\n\n"), headings, raw: pages.map((page) => page.raw) };
120
+ return { ...result, citation: formatCitation(result), url: getSourceUrl(result) };
121
+ };
122
+ const retrieve = async (query, options = {}) => {
123
+ const maxPassages = integer(options.maxPassages ?? 5, "maxPassages", 1);
124
+ const maxChars = integer(options.maxCharsPerPassage ?? 4_000, "maxCharsPerPassage", 1);
125
+ const searchOptions = { ...options.scope, signal: options.signal };
126
+ const first = await search(query, searchOptions);
127
+ const hits = first.items.slice(0, maxPassages);
128
+ for (let page = 2; hits.length < Math.min(maxPassages, first.totalMatches); page += 1) {
129
+ const next = await search(query, { ...searchOptions, page });
130
+ if (!next.items.length)
131
+ break;
132
+ hits.push(...next.items.slice(0, maxPassages - hits.length));
133
+ }
134
+ const passages = await Promise.all(hits.map(async (hit) => {
135
+ const page = hit.location.internalPage === undefined
136
+ ? hit
137
+ : await getPage(hit.book.id, hit.location.internalPage, { signal: options.signal });
138
+ const bounded = { ...page, snippet: hit.snippet, text: truncate(page.text, maxChars) };
139
+ return { ...bounded, citation: formatCitation(bounded), url: getSourceUrl(bounded) };
140
+ }));
141
+ return { passages, totalMatches: first.totalMatches, query };
142
+ };
143
+ return {
144
+ getAuthor,
145
+ getBook,
146
+ getPage,
147
+ getPages,
148
+ search,
149
+ searchAll,
150
+ getContext,
151
+ retrieve,
152
+ formatCitation: (source) => formatCitation(source),
153
+ getSourceUrl: (source) => getSourceUrl(source),
154
+ };
155
+ };
156
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/turath/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { createTurathClient } from "./client.js";
2
+ export type { ContextOptions, RequestOptions, RetrieveOptions, TurathClient, TurathClientOptions, TurathSearchOptions, } from "./client.js";
3
+ export { formatCitation, getSourceUrl } from "./citations.js";
4
+ export type { CitationSource } from "./citations.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/turath/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EACV,cAAc,EACd,cAAc,EACd,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9D,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { createTurathClient } from "./client.js";
2
+ export { formatCitation, getSourceUrl } from "./citations.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/turath/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AASjD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Author, Book, Passage } from "../models.js";
2
+ export declare const normalizeAuthor: (raw: unknown) => Author;
3
+ export declare const normalizeBook: (raw: unknown) => Book;
4
+ export declare const normalizePage: (raw: unknown, bookId: string) => Passage;
5
+ export declare const normalizeSearchHit: (raw: unknown) => Passage;
6
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/turath/normalize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AA2B1D,eAAO,MAAM,eAAe,GAAI,KAAK,OAAO,KAAG,MAa9C,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,KAAK,OAAO,KAAG,IAe5C,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,KAAK,OAAO,EAAE,QAAQ,MAAM,KAAG,OAoB5D,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,KAAK,OAAO,KAAG,OAsBjD,CAAC"}
@@ -0,0 +1,102 @@
1
+ import { NususError } from "../errors.js";
2
+ import { formatCitation, getSourceUrl } from "./citations.js";
3
+ const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4
+ const invalid = (message, cause) => {
5
+ throw new NususError("INVALID_RESPONSE", message, { cause });
6
+ };
7
+ const parseMeta = (value) => {
8
+ if (typeof value !== "string")
9
+ return invalid("Turath metadata is not encoded JSON");
10
+ try {
11
+ const parsed = JSON.parse(value);
12
+ if (!record(parsed))
13
+ return invalid("Turath metadata is not an object");
14
+ return parsed;
15
+ }
16
+ catch (cause) {
17
+ if (cause instanceof NususError)
18
+ throw cause;
19
+ return invalid("Turath metadata contains invalid JSON", cause);
20
+ }
21
+ };
22
+ const passage = (input) => {
23
+ const source = { author: input.author, book: input.book, location: input.location };
24
+ return { ...input, url: getSourceUrl(source), citation: formatCitation(source) };
25
+ };
26
+ export const normalizeAuthor = (raw) => {
27
+ if (!record(raw) || typeof raw.id !== "number" || typeof raw.name !== "string") {
28
+ return invalid("Turath returned an invalid author");
29
+ }
30
+ const author = raw;
31
+ return {
32
+ provider: "turath",
33
+ id: String(author.id),
34
+ name: author.name,
35
+ ...(typeof author.biography === "string" && { biography: author.biography }),
36
+ ...(typeof author.death === "string" && { deathYear: author.death }),
37
+ raw,
38
+ };
39
+ };
40
+ export const normalizeBook = (raw) => {
41
+ if (!record(raw) || !record(raw.meta) || typeof raw.meta.id !== "number" || typeof raw.meta.name !== "string") {
42
+ return invalid("Turath returned an invalid book");
43
+ }
44
+ const book = raw;
45
+ return {
46
+ provider: "turath",
47
+ id: String(book.meta.id),
48
+ title: book.meta.name,
49
+ ...(typeof book.meta.author_id === "number" && { author: { id: String(book.meta.author_id) } }),
50
+ ...(typeof book.meta.cat_id === "number" && { category: { id: String(book.meta.cat_id) } }),
51
+ ...(typeof book.meta.info === "string" && { description: book.meta.info }),
52
+ ...(Boolean(book.meta.pdf_links) && { hasPdf: true }),
53
+ raw,
54
+ };
55
+ };
56
+ export const normalizePage = (raw, bookId) => {
57
+ if (!record(raw) || typeof raw.meta !== "string" || typeof raw.text !== "string") {
58
+ return invalid("Turath returned an invalid page");
59
+ }
60
+ const page = raw;
61
+ const meta = parseMeta(page.meta);
62
+ if (typeof meta.book_name !== "string")
63
+ return invalid("Turath page is missing its book name");
64
+ return passage({
65
+ provider: "turath",
66
+ book: { id: bookId, title: meta.book_name },
67
+ ...(meta.author_name && { author: { name: meta.author_name } }),
68
+ location: {
69
+ ...(typeof meta.page_id === "number" && { internalPage: meta.page_id }),
70
+ ...(typeof meta.page === "number" && { printedPage: meta.page }),
71
+ ...(typeof meta.vol === "string" && { volume: meta.vol }),
72
+ },
73
+ text: page.text,
74
+ headings: Array.isArray(meta.headings) && meta.headings.every((item) => typeof item === "string") ? meta.headings : [],
75
+ raw,
76
+ });
77
+ };
78
+ export const normalizeSearchHit = (raw) => {
79
+ if (!record(raw) || typeof raw.book_id !== "number" || typeof raw.meta !== "string" || typeof raw.text !== "string") {
80
+ return invalid("Turath returned an invalid search result");
81
+ }
82
+ const hit = raw;
83
+ const meta = parseMeta(hit.meta);
84
+ if (typeof meta.book_name !== "string")
85
+ return invalid("Turath search result is missing its book name");
86
+ return passage({
87
+ provider: "turath",
88
+ book: { id: String(hit.book_id), title: meta.book_name },
89
+ ...(typeof hit.author_id === "number" && { author: { id: String(hit.author_id), ...(meta.author_name && { name: meta.author_name }) } }),
90
+ ...(typeof hit.cat_id === "number" && { category: { id: String(hit.cat_id) } }),
91
+ location: {
92
+ ...(typeof meta.page_id === "number" && { internalPage: meta.page_id }),
93
+ ...(typeof meta.page === "number" && { printedPage: meta.page }),
94
+ ...(typeof meta.vol === "string" && { volume: meta.vol }),
95
+ },
96
+ text: hit.text,
97
+ ...(typeof hit.snip === "string" && { snippet: hit.snip }),
98
+ headings: Array.isArray(meta.headings) && meta.headings.every((item) => typeof item === "string") ? meta.headings : [],
99
+ raw,
100
+ });
101
+ };
102
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/turath/normalize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ export type RawAuthor = {
2
+ id: number;
3
+ name: string;
4
+ biography?: string;
5
+ death?: string;
6
+ [key: string]: unknown;
7
+ };
8
+ export type RawBook = {
9
+ meta: {
10
+ id: number;
11
+ name: string;
12
+ author_id?: number;
13
+ cat_id?: number;
14
+ info?: string;
15
+ pdf_links?: unknown;
16
+ [key: string]: unknown;
17
+ };
18
+ indexes?: Record<string, unknown>;
19
+ };
20
+ export type RawPageMeta = {
21
+ headings?: string[];
22
+ page_id?: number;
23
+ page?: number;
24
+ vol?: string;
25
+ book_name?: string;
26
+ author_name?: string;
27
+ };
28
+ export type RawPage = {
29
+ meta: string;
30
+ text: string;
31
+ };
32
+ export type RawSearchHit = {
33
+ author_id?: number;
34
+ book_id: number;
35
+ cat_id?: number;
36
+ meta: string;
37
+ snip?: string;
38
+ text: string;
39
+ };
40
+ export type RawSearch = {
41
+ count: number;
42
+ data: RawSearchHit[];
43
+ };
44
+ //# sourceMappingURL=raw-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raw-types.d.ts","sourceRoot":"","sources":["../../src/turath/raw-types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,EAAE,CAAA;CAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=raw-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raw-types.js","sourceRoot":"","sources":["../../src/turath/raw-types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "nusus",
3
+ "version": "0.1.0",
4
+ "description": "Citable context from classical Islamic and Arabic texts",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/mwijanarko1/nusus.git"
8
+ },
9
+ "homepage": "https://github.com/mwijanarko1/nusus#readme",
10
+ "bugs": "https://github.com/mwijanarko1/nusus/issues",
11
+ "type": "module",
12
+ "files": [
13
+ "dist",
14
+ "scripts/search.mjs",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "bin": {
19
+ "nusus": "scripts/search.mjs"
20
+ },
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js"
25
+ },
26
+ "./turath": {
27
+ "types": "./dist/turath/index.d.ts",
28
+ "import": "./dist/turath/index.js"
29
+ }
30
+ },
31
+ "scripts": {
32
+ "build": "tsc -p tsconfig.json",
33
+ "prepack": "npm run build",
34
+ "typecheck": "tsc -p tsconfig.json --noEmit",
35
+ "test": "bun test",
36
+ "test:live": "NUSUS_LIVE=1 bun test ./tests/live.test.ts"
37
+ },
38
+ "devDependencies": {
39
+ "typescript": "^5.8.3"
40
+ },
41
+ "engines": {
42
+ "node": ">=20"
43
+ },
44
+ "license": "MIT"
45
+ }
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+ // Agent-facing Turath search CLI built on nusus.
3
+ // Drop-in replacement for the old turath-sdk search.mjs.
4
+ //
5
+ // Usage:
6
+ // node search.mjs "<Arabic query>" [max_results]
7
+ // node search.mjs --madhhab hanafi|maliki|shafii|hanbali "<topic>" [max_results]
8
+ // node search.mjs --books "بدائع الصنائع,الهداية" "<query>" [max_results]
9
+ // node search.mjs --book-id 147927 "<query>" [max_results]
10
+ // node search.mjs --page <book_id> <page_id> # fetch one page + context
11
+ //
12
+ // Output: JSON per line — book_id, title, author, volume, page_id, printed_page,
13
+ // headings, citation, url, text (bounded), prev/next context.
14
+
15
+ import { createTurathClient } from "../dist/turath/index.js";
16
+
17
+ const MADHHAB_BOOKS = {
18
+ hanafi: ["رد المحتار ابن عابدين", "الهداية المرغيناني", "فتح القدير ابن الهمام", "بدائع الصنائع الكاساني", "المبسوط السرخسي"],
19
+ maliki: ["مختصر خليل", "المدونة الكبرى سحنون", "التوضيح شرح مختصر ابن الحاجب خليل", "الشرح الكبير الدردير حاشية الدسوقي", "مواهب الجليل الحطاب"],
20
+ shafii: ["منهاج الطالبين النووي", "تحفة المحتاج ابن حجر الهيتمي", "نهاية المحتاج الرملي", "مغني المحتاج الشربيني", "المجموع شرح المهذب النووي"],
21
+ hanbali: ["منتهى الإرادات ابن النجار", "الإقناع الحجاوي", "كشاف القناع البهوتي", "شرح منتهى الإرادات البهوتي", "المغني ابن قدامة"],
22
+ };
23
+
24
+ const MADHHAB_LABELS = {
25
+ hanafi: { name: "الحنفية", at: "عند الحنفية", fi: "في مذهب الحنفية" },
26
+ maliki: { name: "المالكية", at: "عند المالكية", fi: "في المذهب المالكي" },
27
+ shafii: { name: "الشافعية", at: "عند الشافعية", fi: "في مذهب الشافعية" },
28
+ hanbali: { name: "الحنابلة", at: "عند الحنابلة", fi: "في المذهب الحنبلي" },
29
+ };
30
+
31
+ // === arg parsing ===
32
+ const args = process.argv.slice(2);
33
+ let madhhab = null;
34
+ let books = null;
35
+ let bookId = null;
36
+ let pageMode = false;
37
+ let query = null;
38
+ let maxResults = 5;
39
+
40
+ for (let i = 0; i < args.length; i++) {
41
+ const a = args[i];
42
+ if (a === "--madhhab") {
43
+ madhhab = args[++i]?.toLowerCase();
44
+ if (!MADHHAB_BOOKS[madhhab]) fail(`Invalid madhhab. Use: ${Object.keys(MADHHAB_BOOKS).join(", ")}`);
45
+ } else if (a === "--books") books = args[++i]?.split(",").map((s) => s.trim()).filter(Boolean);
46
+ else if (a === "--book-id") bookId = args[++i];
47
+ else if (a === "--page") pageMode = true;
48
+ else if (a === "--topic") query = args[++i];
49
+ else if (!a.startsWith("--") && query === null && isNaN(Number(a))) query = a;
50
+ }
51
+ // last numeric non-flag arg = max results (or page args in --page mode)
52
+ const numbers = args.filter((a) => !a.startsWith("--") && /^\d+$/.test(a));
53
+ if (numbers.length && !pageMode) maxResults = parseInt(numbers[numbers.length - 1], 10);
54
+
55
+ function fail(message) {
56
+ console.error(message);
57
+ process.exit(1);
58
+ }
59
+
60
+ const turath = createTurathClient({ timeout: 15_000 });
61
+
62
+ const emit = (item) => console.log(JSON.stringify(item));
63
+
64
+ const passageToJson = (p, extra = {}) => ({
65
+ book_id: Number(p.book.id),
66
+ title: p.book.title,
67
+ author: p.author?.name,
68
+ volume: p.location.volume,
69
+ page_id: p.location.internalPage,
70
+ printed_page: p.location.printedPage,
71
+ headings: p.headings.join(" > "),
72
+ citation: p.citation,
73
+ url: p.url,
74
+ ...extra,
75
+ });
76
+
77
+ // === page mode: node search.mjs --page <book_id> <page_id> ===
78
+ if (pageMode) {
79
+ const [pBook, pPage] = numbers;
80
+ if (!pBook || !pPage) fail("Usage: node search.mjs --page <book_id> <page_id>");
81
+ const page = await turath.getPage(pBook, pPage);
82
+ const context = await turath.getContext(page, { pagesBefore: 1, pagesAfter: 1 });
83
+ emit(passageToJson(page, { text: page.text, context_text: context.text }));
84
+ process.exit(0);
85
+ }
86
+
87
+ if (!query) fail('Usage: node search.mjs [--madhhab hanafi|maliki|shafii|hanbali] [--books "t1,t2"] [--book-id N] "<query>" [max_results]');
88
+
89
+ // === build tiered queries ===
90
+ const searchQueries = [];
91
+ if (madhhab) {
92
+ const label = MADHHAB_LABELS[madhhab];
93
+ searchQueries.push({ q: `${query} ${label.at}`, tier: 1, source_type: `madhhab:${madhhab}` });
94
+ searchQueries.push({ q: `${query} ${label.fi}`, tier: 1, source_type: `madhhab:${madhhab}` });
95
+ searchQueries.push({ q: `${query} ${label.name}`, tier: 2, source_type: `madhhab:${madhhab}` });
96
+ for (const book of MADHHAB_BOOKS[madhhab]) {
97
+ searchQueries.push({ q: `${query} ${book}`, tier: 3, source_type: `book:${madhhab}` });
98
+ }
99
+ } else if (books) {
100
+ for (const b of books) searchQueries.push({ q: `${query} ${b}`, tier: 3, source_type: "targeted" });
101
+ }
102
+ searchQueries.push({ q: query, tier: 4, source_type: "broad" });
103
+
104
+ // === execute ===
105
+ const seen = new Set();
106
+ const collected = [];
107
+ const scope = bookId ? { bookIds: [bookId] } : undefined;
108
+
109
+ for (const sq of searchQueries) {
110
+ if (collected.length >= maxResults * 3) break;
111
+ try {
112
+ const result = await turath.search(sq.q, scope ? { ...scope } : {});
113
+ for (const hit of result.items) {
114
+ const key = `${hit.book.id}:${hit.location.internalPage ?? ""}`;
115
+ if (seen.has(key)) continue;
116
+ seen.add(key);
117
+ collected.push({ hit, tier: sq.tier, source_type: sq.source_type, matched_term: sq.q.replace(query, "").trim() });
118
+ }
119
+ } catch {
120
+ // skip failed tier queries; broad fallback still runs
121
+ }
122
+ }
123
+
124
+ collected.sort((a, b) => a.tier - b.tier);
125
+ const top = collected.slice(0, maxResults);
126
+
127
+ for (const { hit, tier, source_type, matched_term } of top) {
128
+ let text = hit.text;
129
+ let prevTail = "";
130
+ let nextHead = "";
131
+ if (hit.location.internalPage !== undefined) {
132
+ try {
133
+ const page = await turath.getPage(hit.book.id, hit.location.internalPage);
134
+ text = page.text;
135
+ const pg = hit.location.internalPage;
136
+ if (pg > 1) {
137
+ try { prevTail = (await turath.getPage(hit.book.id, pg - 1)).text.slice(-300); } catch {}
138
+ }
139
+ try { nextHead = (await turath.getPage(hit.book.id, pg + 1)).text.slice(0, 300); } catch {}
140
+ } catch {}
141
+ }
142
+ emit(passageToJson(hit, {
143
+ source_type,
144
+ tier,
145
+ matched_term,
146
+ snippet: hit.snippet,
147
+ prev_text_tail: prevTail,
148
+ text: text.slice(0, 1500),
149
+ next_text_head: nextHead,
150
+ }));
151
+ }
152
+
153
+ if (top.length === 0) emit({ error: "No results", query, madhhab });