@shapething/typed-sparql 0.1.4

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 ADDED
@@ -0,0 +1,5 @@
1
+ # Typed SPARQL
2
+
3
+ A development toolkit that generates @rdf/js types for SPARQL queries.
4
+
5
+ It provides a Vite plugin and a standalone CLI version.
@@ -0,0 +1,45 @@
1
+ import { Parser } from '@traqula/parser-sparql-1-2';
2
+ import { SparqlTypesOptions } from './vite-plugin.ts';
3
+ import type { Literal, NamedNode } from '@rdfjs/types';
4
+ export { typedSparql } from './vite-plugin.ts';
5
+ /**
6
+ * Finds .rq files in the given directory and all subdirectories, ignoring commonly excluded ones like node_modules and .git. Returns an array of file paths.
7
+ */
8
+ export declare function findRqFiles(dir: string): string[];
9
+ /**
10
+ * Pure function: parses a SPARQL query string and returns the `.rq.d.ts` file
11
+ * content, or `null` if no type declaration can be generated (non-SELECT
12
+ * queries, SELECT *, parse errors, empty projections).
13
+ */
14
+ export declare function generateDts(query: string, options: SparqlTypesOptions, parser: Parser): string | null;
15
+ /**
16
+ * Processes a .rq file, generating a corresponding TypeScript declaration file.
17
+ * @param rqPath - The path to the .rq file.
18
+ * @param options - Options for SPARQL type generation.
19
+ * @param parser - The SPARQL parser instance.
20
+ */
21
+ export declare function processRqFile(rqPath: string, options: SparqlTypesOptions, parser: Parser): void;
22
+ /**
23
+ * A helper type representing an RDF literal with a specific datatype IRI. This is used to provide more precise typings for variables that are known to be literals of a certain datatype, such as those narrowed via BOUND() or identified by user-defined function return types.
24
+ */
25
+ export type LiteralWithDatatype<TDatatype extends string> = Literal & {
26
+ datatype: NamedNode<TDatatype>;
27
+ };
28
+ /**
29
+ * Expands a type so that IDEs display the concrete property names instead of
30
+ * a named alias. Useful for generated query-result interfaces.
31
+ */
32
+ export type Prettify<T> = {
33
+ [K in keyof T]: T[K];
34
+ } & {};
35
+ declare const __queryBrand: unique symbol;
36
+ /**
37
+ * A SPARQL SELECT query string that carries its result row type as a phantom
38
+ * type parameter. At runtime this is just a plain string; the type parameter
39
+ * is erased and only exists for TypeScript inference.
40
+ *
41
+ * Generated automatically by the sparql-types Vite plugin for every .rq file.
42
+ */
43
+ export type TypedQuery<TRow extends object> = string & {
44
+ readonly [__queryBrand]: TRow;
45
+ };
package/dist/index.js ADDED
@@ -0,0 +1,191 @@
1
+ import e from "node:fs";
2
+ import t from "node:path";
3
+ import { Parser as n } from "@traqula/parser-sparql-1-2";
4
+ //#region vite-plugin.ts
5
+ function r(r = {}) {
6
+ let i = new n();
7
+ return {
8
+ name: "typed-sparql",
9
+ enforce: "pre",
10
+ buildStart() {
11
+ let e = t.resolve(process.cwd(), "src");
12
+ for (let t of g(e)) v(t, r, i), this.addWatchFile(t);
13
+ },
14
+ load(t) {
15
+ if (t.endsWith(".rq")) {
16
+ let n = e.readFileSync(t, "utf-8");
17
+ return `export default ${JSON.stringify(n)}\n`;
18
+ }
19
+ },
20
+ watchChange(e, { event: t }) {
21
+ e.endsWith(".rq") && t !== "delete" && v(e, r, i);
22
+ }
23
+ };
24
+ }
25
+ //#endregion
26
+ //#region index.ts
27
+ var i = /* @__PURE__ */ new Set(/* @__PURE__ */ "SELECT.WHERE.FILTER.OPTIONAL.GRAPH.BIND.UNION.MINUS.ORDER.LIMIT.OFFSET.GROUP.HAVING.AS.BY.ASC.DESC.FROM.NAMED.SERVICE.SILENT.VALUES.INSERT.DELETE.WITH.USING.LOAD.CLEAR.DROP.ADD.MOVE.COPY.CREATE.BASE.PREFIX.CONSTRUCT.DESCRIBE.ASK.NOT.IN.EXISTS.DISTINCT.REDUCED.SEPARATOR".split("."));
28
+ function a(e) {
29
+ let t = /* @__PURE__ */ new Set();
30
+ for (let n of e.matchAll(/PREFIX\s+(\w*)\s*:/gi)) t.add(n[1]);
31
+ let n = /* @__PURE__ */ new Set();
32
+ for (let t of e.matchAll(/\b([a-zA-Z]\w*):[a-zA-Z_]/g)) i.has(t[1].toUpperCase()) || n.add(t[1]);
33
+ let r = [...n].filter((e) => !t.has(e));
34
+ return r.length ? `${r.map((e) => `PREFIX ${e}: <http://dummy/${e}/>`).join("\n")}\n${e}` : e;
35
+ }
36
+ function o(e) {
37
+ return e.prefix ? `${e.prefix}:${e.value}` : e.value;
38
+ }
39
+ var s = {
40
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#type": "NamedNode",
41
+ "rdf:type": "NamedNode",
42
+ "sh:targetClass": "NamedNode",
43
+ "sh:targetSubjectsOf": "NamedNode",
44
+ "sh:targetObjectsOf": "NamedNode",
45
+ "sh:class": "NamedNode",
46
+ "sh:datatype": "NamedNode",
47
+ "sh:nodeKind": "NamedNode"
48
+ };
49
+ function* c(e, t = !1) {
50
+ for (let n of e) t && n.type === "pattern" && n.subType === "optional" || (yield n, Array.isArray(n.patterns) && (yield* c(n.patterns, t)), n.type === "query" && Array.isArray(n.where?.patterns) && (yield* c(n.where.patterns, t)));
51
+ }
52
+ function l(e) {
53
+ let t = /* @__PURE__ */ new Set();
54
+ for (let n of c(e)) n.type === "pattern" && n.subType === "bind" && n.expression?.type === "expression" && n.expression.subType === "operation" && n.expression.operator === "bound" && t.add(n.variable.value);
55
+ return t;
56
+ }
57
+ function u(e) {
58
+ let t = /* @__PURE__ */ new Set();
59
+ for (let n of c(e)) n.type === "pattern" && n.subType === "graph" && n.name?.type === "term" && n.name.subType === "variable" && t.add(n.name.value);
60
+ return t;
61
+ }
62
+ function d(e) {
63
+ let t = /* @__PURE__ */ new Set();
64
+ for (let n of c(e)) if (n.type === "pattern" && n.subType === "bgp") for (let e of n.triples ?? []) {
65
+ let n = e.subject;
66
+ n?.type === "term" && n.subType === "variable" && t.add(n.value);
67
+ }
68
+ return t;
69
+ }
70
+ function f(e) {
71
+ let t = /* @__PURE__ */ new Set();
72
+ for (let n of c(e, !0)) {
73
+ if (n.type === "pattern" && n.subType === "graph" && n.name?.type === "term" && n.name.subType === "variable" && t.add(n.name.value), n.type === "pattern" && n.subType === "bgp") for (let e of n.triples ?? []) for (let n of [
74
+ e.subject,
75
+ e.predicate,
76
+ e.object
77
+ ]) n?.type === "term" && n.subType === "variable" && t.add(n.value);
78
+ n.type === "pattern" && n.subType === "bind" && n.variable?.value && t.add(n.variable.value);
79
+ }
80
+ return t;
81
+ }
82
+ function p(e, t) {
83
+ let n = {
84
+ ...s,
85
+ ...t.predicateObjectTypes
86
+ }, r = /* @__PURE__ */ new Map();
87
+ for (let t of c(e)) if (t.type === "pattern" && t.subType === "bgp") for (let e of t.triples ?? []) {
88
+ let t = e.predicate;
89
+ if (t?.type === "term" && t.subType !== "variable") {
90
+ let i = n[o(t)];
91
+ if (i) {
92
+ let t = e.object;
93
+ t?.type === "term" && t.subType === "variable" && !r.has(t.value) && r.set(t.value, i);
94
+ }
95
+ }
96
+ }
97
+ return r;
98
+ }
99
+ function m(e, t, n, r) {
100
+ let i = /* @__PURE__ */ new Map();
101
+ for (let a of c(e)) if (a.type === "pattern" && a.subType === "bind") {
102
+ let e = a.variable?.value;
103
+ if (e && !n.has(e) && !i.has(e)) {
104
+ let o = h(a.expression, t, n, r);
105
+ o !== "NamedNode | BlankNode | Literal" && i.set(e, o);
106
+ }
107
+ }
108
+ return i;
109
+ }
110
+ function h(e, t, n, r) {
111
+ if (e.type === "term" && e.subType === "variable") {
112
+ let t = e.value;
113
+ return n.has(t) ? "LiteralWithDatatype<'http://www.w3.org/2001/XMLSchema#boolean'>" : r.get(t) ?? "NamedNode | BlankNode | Literal";
114
+ }
115
+ if (e.type === "expression" && e.subType === "functionCall") {
116
+ let n = o(e.function);
117
+ return t.functions?.[n] ?? "Term";
118
+ }
119
+ if (e.type === "expression" && e.subType === "aggregate") {
120
+ let t = e.expression?.[0];
121
+ return t?.type === "term" && t.subType === "variable" && n.has(t.value) ? "LiteralWithDatatype<'http://www.w3.org/2001/XMLSchema#boolean'>" : "Literal";
122
+ }
123
+ return "Term";
124
+ }
125
+ function g(n) {
126
+ let r = [];
127
+ try {
128
+ for (let i of e.readdirSync(n, { withFileTypes: !0 })) {
129
+ let e = t.join(n, i.name);
130
+ i.isDirectory() && ![
131
+ "node_modules",
132
+ ".git",
133
+ "dist"
134
+ ].includes(i.name) ? r.push(...g(e)) : i.isFile() && i.name.endsWith(".rq") && r.push(e);
135
+ }
136
+ } catch {}
137
+ return r;
138
+ }
139
+ function _(e, t, n) {
140
+ let r;
141
+ try {
142
+ r = n.parse(a(e));
143
+ } catch {
144
+ return null;
145
+ }
146
+ if (r.type !== "query" || r.subType !== "select") return null;
147
+ let i = r.where?.patterns ?? [], o = l(i), s = u(i), c = d(i), g = f(i), _ = p(i, t), v = /* @__PURE__ */ new Map();
148
+ for (let e of c) v.set(e, "NamedNode | BlankNode");
149
+ for (let [e, t] of _) v.set(e, t);
150
+ for (let e of s) v.set(e, "NamedNode");
151
+ let y = m(i, t, o, v), b = [];
152
+ for (let e of r.variables) {
153
+ if (e.type === "wildcard") return null;
154
+ if (e.type === "term" && e.subType === "variable") {
155
+ let t = s.has(e.value) ? "NamedNode" : o.has(e.value) ? "LiteralWithDatatype<'http://www.w3.org/2001/XMLSchema#boolean'>" : _.get(e.value) ?? (c.has(e.value) ? "NamedNode | BlankNode" : void 0) ?? y.get(e.value) ?? "NamedNode | BlankNode | Literal", n = !g.has(e.value);
156
+ b.push({
157
+ name: e.value,
158
+ tsType: t,
159
+ optional: n
160
+ });
161
+ } else e.type === "pattern" && e.subType === "bind" && b.push({
162
+ name: e.variable.value,
163
+ tsType: h(e.expression, t, o, v),
164
+ optional: !1
165
+ });
166
+ }
167
+ if (!b.length) return null;
168
+ let x = b.some((e) => e.tsType.includes("LiteralWithDatatype")), S = /* @__PURE__ */ new Set();
169
+ for (let { tsType: e } of b) e.includes("NamedNode") && S.add("NamedNode"), e.includes("BlankNode") && S.add("BlankNode"), e.includes("Literal") && S.add("Literal"), (e === "Term" || e.includes("| Term") || e.includes("Term |")) && S.add("Term");
170
+ x && (S.add("Literal"), S.add("NamedNode"));
171
+ let C = `import type { ${[...S].sort().join(", ")} } from '@rdfjs/types'`, w = ["Prettify", "TypedQuery"];
172
+ return x && w.push("LiteralWithDatatype"), `/* eslint-disable @typescript-eslint/no-unused-vars */\n${C}\n${`import type { ${w.sort().join(", ")} } from '@shapething/typed-sparql'`}\n\ninterface QueryResult {\n${b.map(({ name: e, tsType: t, optional: n }) => ` ${e}${n ? "?" : ""}: ${t}`).join("\n")}\n}\n\ndeclare const _default: TypedQuery<Prettify<QueryResult>>\nexport default _default\n`;
173
+ }
174
+ function v(t, n, r) {
175
+ let i;
176
+ try {
177
+ i = e.readFileSync(t, "utf-8");
178
+ } catch {
179
+ return;
180
+ }
181
+ let a;
182
+ try {
183
+ a = _(i, n, r);
184
+ } catch (e) {
185
+ console.warn(`[sparql-types] Failed to parse ${t}: ${e.message}`);
186
+ return;
187
+ }
188
+ a !== null && e.writeFileSync(`${t}.d.ts`, a, "utf-8");
189
+ }
190
+ //#endregion
191
+ export { g as findRqFiles, _ as generateDts, v as processRqFile, r as typedSparql };
@@ -0,0 +1,49 @@
1
+ import type { Plugin } from 'vite';
2
+ /**
3
+ * Options for the `sparqlTypes` Vite plugin, allowing users to customize the TypeScript types generated for SPARQL queries based on function return types and predicate-object patterns. This enables more precise typings for query results, improving type safety and developer experience when working with SPARQL queries in a TypeScript codebase.
4
+ */
5
+ export interface SparqlTypesOptions {
6
+ /**
7
+ * Map from function IRI or prefixed name to its TypeScript return type string.
8
+ *
9
+ * Keys can be:
10
+ * - A prefixed name: `"sta:label"`
11
+ * - A full IRI: `"http://example.org/ns/label"`
12
+ *
13
+ * Values are TypeScript type expressions inserted verbatim, e.g.
14
+ * `"Literal"`, `"NamedNode"`, `"NamedNode | Literal"`.
15
+ *
16
+ * When a function is not listed, `Term` is used as the fallback type.
17
+ *
18
+ * @example
19
+ * sparqlTypes({
20
+ * functions: {
21
+ * 'sta:label': 'Literal',
22
+ * 'sta:icon': 'Literal',
23
+ * },
24
+ * })
25
+ */
26
+ functions?: Record<string, string>;
27
+ /**
28
+ * Additional predicate → object TypeScript type rules, merged on top of the
29
+ * built-in SHACL/RDF rules.
30
+ *
31
+ * Keys can be prefixed names or full IRIs (same format as `functions`).
32
+ * Values are TypeScript type expressions inserted verbatim.
33
+ *
34
+ * @example
35
+ * sparqlTypes({
36
+ * predicateObjectTypes: {
37
+ * 'owl:sameAs': 'NamedNode',
38
+ * },
39
+ * })
40
+ */
41
+ predicateObjectTypes?: Record<string, string>;
42
+ }
43
+ /**
44
+ * A Vite plugin that processes SPARQL query files (.rq) and generates corresponding TypeScript types.
45
+ * This allows for type-safe usage of SPARQL queries in a TypeScript codebase.
46
+ * @param options - Options for customizing the generated TypeScript types.
47
+ * @returns A Vite plugin instance.
48
+ */
49
+ export declare function typedSparql(options?: SparqlTypesOptions): Plugin;
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@shapething/typed-sparql",
3
+ "version": "0.1.4",
4
+ "description": "",
5
+ "license": "GPL-3.0-only",
6
+ "author": "",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "files": ["dist", "README.md"],
20
+ "scripts": {
21
+ "build": "vite build --config vite.lib.config.ts && tsc -p tsconfig.build.json",
22
+ "lint": "eslint .",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest"
25
+ },
26
+ "dependencies": {
27
+ "@traqula/parser-sparql-1-2": "^1.0.7"
28
+ },
29
+ "devDependencies": {
30
+ "@rdfjs/types": "^2.0.1",
31
+ "@types/node": "^22.15.29",
32
+ "vite": "^8.0.8",
33
+ "vite-tsconfig-paths": "^6.1.1",
34
+ "vitest": "^4.1.4"
35
+ }
36
+ }