sol-components 2.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.
Files changed (150) hide show
  1. package/README.md +7 -0
  2. package/core/activate.js +27 -0
  3. package/core/adopt.js +71 -0
  4. package/core/auth-core.js +73 -0
  5. package/core/auth-fetch.js +154 -0
  6. package/core/component-mount.js +110 -0
  7. package/core/defaults.js +48 -0
  8. package/core/define.js +15 -0
  9. package/core/display-target.js +166 -0
  10. package/core/edit-placements.js +28 -0
  11. package/core/editor-self.js +127 -0
  12. package/core/editor.js +162 -0
  13. package/core/events.js +27 -0
  14. package/core/extension-points.js +189 -0
  15. package/core/form-utils.js +210 -0
  16. package/core/from-query.js +138 -0
  17. package/core/from-rdf.js +52 -0
  18. package/core/here.js +33 -0
  19. package/core/include-core.js +73 -0
  20. package/core/inrupt-global.js +18 -0
  21. package/core/menu-consumer.js +41 -0
  22. package/core/menu-rdf.js +154 -0
  23. package/core/pod-ops.js +392 -0
  24. package/core/pod-registry.js +82 -0
  25. package/core/popup-proxy.js +255 -0
  26. package/core/rdf-core.js +280 -0
  27. package/core/rdf-render.js +136 -0
  28. package/core/rdf-utils.js +411 -0
  29. package/core/rdf.js +154 -0
  30. package/core/services.js +106 -0
  31. package/core/shape-to-form.js +741 -0
  32. package/core/sparql-safety.js +20 -0
  33. package/core/utils.js +196 -0
  34. package/dist/importmap-cdn.json +49 -0
  35. package/dist/importmap-local.json +49 -0
  36. package/dist/sol-loader.manifest.json +140 -0
  37. package/dist/vendor/@comunica-query-sparql.js +137851 -0
  38. package/dist/vendor/@inrupt-solid-client-authn-browser.js +7503 -0
  39. package/dist/vendor/dompurify.js +1476 -0
  40. package/dist/vendor/ical.js.js +9739 -0
  41. package/dist/vendor/marked.js +85 -0
  42. package/dist/vendor/n3.js +14670 -0
  43. package/dist/vendor/rdf-validate-shacl.js +6970 -0
  44. package/dist/vendor/rdflib.js +35172 -0
  45. package/dist/vendor/solid-logic.js +6819 -0
  46. package/dist/vendor/solid-ui.js +21945 -0
  47. package/node/sol-form.js +133 -0
  48. package/node/sol-include.js +55 -0
  49. package/node/sol-login.js +632 -0
  50. package/node/sol-menu.js +639 -0
  51. package/node/sol-query.js +116 -0
  52. package/package.json +133 -0
  53. package/web/menu-from-rdf.js +23 -0
  54. package/web/scripts/prefs.js +25 -0
  55. package/web/sol-accordion.js +114 -0
  56. package/web/sol-basic.js +50 -0
  57. package/web/sol-breadcrumb.js +131 -0
  58. package/web/sol-button.js +244 -0
  59. package/web/sol-calendar.js +465 -0
  60. package/web/sol-default.js +118 -0
  61. package/web/sol-dropdown-button.js +222 -0
  62. package/web/sol-feed.js +1336 -0
  63. package/web/sol-form.js +949 -0
  64. package/web/sol-full.js +43 -0
  65. package/web/sol-gallery.js +303 -0
  66. package/web/sol-include.js +246 -0
  67. package/web/sol-live-edit.js +415 -0
  68. package/web/sol-login.js +856 -0
  69. package/web/sol-menu.js +593 -0
  70. package/web/sol-modal.js +377 -0
  71. package/web/sol-pod-extras.js +17 -0
  72. package/web/sol-pod-ops.js +680 -0
  73. package/web/sol-pod.js +1039 -0
  74. package/web/sol-query.js +546 -0
  75. package/web/sol-rolodex.js +95 -0
  76. package/web/sol-search.js +402 -0
  77. package/web/sol-settings.js +199 -0
  78. package/web/sol-solidos.js +93 -0
  79. package/web/sol-tabs.js +445 -0
  80. package/web/sol-time.js +194 -0
  81. package/web/sol-tree-edit.js +492 -0
  82. package/web/sol-wac.js +456 -0
  83. package/web/sol-weather.js +337 -0
  84. package/web/sol-window.js +142 -0
  85. package/web/styles/buttons-css.js +108 -0
  86. package/web/styles/help.css +242 -0
  87. package/web/styles/root.css +112 -0
  88. package/web/styles/sol-accordion-css.js +97 -0
  89. package/web/styles/sol-calendar-css.js +154 -0
  90. package/web/styles/sol-feed-css.js +475 -0
  91. package/web/styles/sol-form-css.js +471 -0
  92. package/web/styles/sol-gallery-css.js +181 -0
  93. package/web/styles/sol-include-css.js +95 -0
  94. package/web/styles/sol-live-edit-css.js +84 -0
  95. package/web/styles/sol-live-edit.css +101 -0
  96. package/web/styles/sol-login-css.js +116 -0
  97. package/web/styles/sol-menu-css.js +145 -0
  98. package/web/styles/sol-modal-css.js +134 -0
  99. package/web/styles/sol-pod-css.js +187 -0
  100. package/web/styles/sol-pod-modal-css.js +203 -0
  101. package/web/styles/sol-query-css.js +140 -0
  102. package/web/styles/sol-query-help.css +267 -0
  103. package/web/styles/sol-query-one-pager.css +67 -0
  104. package/web/styles/sol-search-css.js +157 -0
  105. package/web/styles/sol-solidos-css.js +7 -0
  106. package/web/styles/sol-tabs-css.js +114 -0
  107. package/web/styles/sol-time-css.js +30 -0
  108. package/web/styles/sol-wac-css.js +73 -0
  109. package/web/styles/sol-weather-css.js +59 -0
  110. package/web/styles/solid-logo.svg +9 -0
  111. package/web/styles/view-accordion-css.js +66 -0
  112. package/web/styles/view-anchorlist-css.js +22 -0
  113. package/web/styles/view-autocomplete-css.js +59 -0
  114. package/web/styles/view-rolodex-css.js +102 -0
  115. package/web/styles/view-select-css.js +21 -0
  116. package/web/utils/calendar-fetch.js +388 -0
  117. package/web/utils/code-mirror-editor.js +82 -0
  118. package/web/utils/commons-fetch.js +108 -0
  119. package/web/utils/feed-edit.js +159 -0
  120. package/web/utils/feed-edit.smoke.mjs +74 -0
  121. package/web/utils/feed-fetch.js +573 -0
  122. package/web/utils/live-edit-help/csv.js +64 -0
  123. package/web/utils/live-edit-help/graphviz.js +41 -0
  124. package/web/utils/live-edit-help/jsonld.js +55 -0
  125. package/web/utils/live-edit-help/markdown.js +52 -0
  126. package/web/utils/live-edit-help/mermaid.js +48 -0
  127. package/web/utils/live-edit-help/turtle.js +85 -0
  128. package/web/utils/rdf-config.js +125 -0
  129. package/web/utils/renderers/csv.js +124 -0
  130. package/web/utils/renderers/d3-force.js +82 -0
  131. package/web/utils/renderers/graphviz.js +13 -0
  132. package/web/utils/renderers/html.js +10 -0
  133. package/web/utils/renderers/jsonld.js +63 -0
  134. package/web/utils/renderers/markdown.js +19 -0
  135. package/web/utils/renderers/mermaid.js +54 -0
  136. package/web/utils/renderers/turtle.js +51 -0
  137. package/web/utils/sol-query-triple-patterns.js +151 -0
  138. package/web/utils/sol-query-ui.js +250 -0
  139. package/web/utils/sol-query-views.js +32 -0
  140. package/web/views/_helpers.js +34 -0
  141. package/web/views/accordion.js +133 -0
  142. package/web/views/anchorlist.js +59 -0
  143. package/web/views/auto-complete.js +183 -0
  144. package/web/views/dl.js +38 -0
  145. package/web/views/list.js +19 -0
  146. package/web/views/menu.js +56 -0
  147. package/web/views/rolodex.js +126 -0
  148. package/web/views/select.js +79 -0
  149. package/web/views/table.js +73 -0
  150. package/web/views/tabs.js +57 -0
@@ -0,0 +1,411 @@
1
+ // All RDF operations for sol-query.
2
+ // Adapters (SPARQL execution), store loading, triple-pattern matching.
3
+
4
+ import {
5
+ sanitizeHtml,
6
+ toPlainResults,
7
+ ComunicaSparqlAdapter,
8
+ NativeSparqlAdapter,
9
+ } from './utils.js';
10
+ import { rdf } from './rdf.js';
11
+
12
+ // Re-export pure-logic functions from rdf-core so existing importers
13
+ // (sol-query.js, views, tests) continue to work unchanged.
14
+ export {
15
+ KNOWN_PREFIXES,
16
+ ACCEPT_TYPES,
17
+ detectFormat,
18
+ termToString,
19
+ termToCell,
20
+ expandCurie,
21
+ knownPrefixesAsSparql,
22
+ w3cResults,
23
+ _NAMED_VAR_RE,
24
+ triplePatternTermToNode,
25
+ tokenizeTriplePattern,
26
+ parsePatternParts,
27
+ patternVarNames,
28
+ matchStore,
29
+ selectVars as _selectVars,
30
+ isRdfDoc as _isRdfDoc,
31
+ } from './rdf-core.js';
32
+
33
+ import {
34
+ ACCEPT_TYPES,
35
+ detectFormat,
36
+ termToCell,
37
+ parsePatternParts,
38
+ matchStore,
39
+ patternVarNames,
40
+ selectVars as _selectVars,
41
+ isRdfDoc as _isRdfDoc,
42
+ bindingsToResults,
43
+ w3cResults,
44
+ } from './rdf-core.js';
45
+
46
+ // Re-export the rdf singleton so downstream can import either symbol
47
+ export { rdf };
48
+
49
+ // Re-export sanitizeHtml so downstream (sol-include) can import from here or utils
50
+ export { sanitizeHtml };
51
+ // Re-export queryHtmlWithSelector for sol-query.js
52
+ export { queryHtmlWithSelector } from './utils.js';
53
+
54
+ // Backwards-compatible alias (deprecated — prefer triplePatternTermToNode).
55
+ export { triplePatternTermToNode as miniTermToNode } from './rdf-core.js';
56
+
57
+ // ─── Store loading ───────────────────────────────────────────────────────────
58
+
59
+ export async function loadRdfStore(endpoint, fetchFn = fetch, opts = {}) {
60
+ if (!rdf.isReady()) throw new Error('rdflib not available');
61
+ const rdflib = rdf;
62
+
63
+ // Parsers (notably JSON-LD) require an absolute base IRI, so resolve relative
64
+ // endpoints against the current document before handing them to rdflib.parse.
65
+ try { endpoint = new URL(endpoint, document.baseURI).href; } catch {}
66
+
67
+ // shared=true routes parsing into rdf.store (the singleton shared with
68
+ // solid-logic/solid-ui/mashlib) and dedupes subsequent loads of the same
69
+ // URL. Intended for stored-query RDF libraries where many <sol-query>
70
+ // elements reference the same .ttl file.
71
+ //
72
+ // Cache reads are unconditional: once a URL is in the singleton (because
73
+ // some earlier caller asked for shared), every subsequent load hits the
74
+ // cache regardless of opts. Cache writes still require shared, so callers
75
+ // can't pollute the singleton without opting in.
76
+ const shared = !!opts.shared;
77
+ if (rdf.isLoaded(endpoint)) return rdf.store;
78
+
79
+ const store = shared ? rdf.store : rdflib.graph();
80
+ const diagnostics = [];
81
+ let lastError = null;
82
+ const markOk = () => { if (shared) rdf.markLoaded(endpoint); };
83
+
84
+ // Attempt each Accept type; 405 Method-Not-Allowed is fatal (wrong HTTP method),
85
+ // everything else (including 501) just moves on to the next type.
86
+ for (const accept of ACCEPT_TYPES) {
87
+ try {
88
+ const resp = await fetchFn(endpoint, { headers: { Accept: accept } });
89
+ diagnostics.push({ accept, status: resp.status, contentType: resp.headers.get('content-type') });
90
+
91
+ if (!resp.ok) {
92
+ lastError = new Error(`HTTP ${resp.status}`);
93
+ if (resp.status === 405) break; // method not supported — no point retrying
94
+ continue;
95
+ }
96
+
97
+ const text = await resp.text();
98
+ const ct = (resp.headers.get('content-type') || '').split(';')[0].trim();
99
+ if (ct.includes('html') || /<html/i.test(text)) {
100
+ // Try RDFa parsing; tag store so callers know it came from HTML
101
+ try {
102
+ const clean = await sanitizeHtml(text, { WHOLE_DOCUMENT: true, FORCE_BODY: false });
103
+ rdflib.parse(clean, store, endpoint, 'text/html');
104
+ store._isHtml = true;
105
+ store._rawHtml = clean;
106
+ store._diagnostics = diagnostics;
107
+ markOk();
108
+ return store;
109
+ } catch (rdfa) {
110
+ lastError = new Error(`Server returned HTML (${ct})`);
111
+ continue;
112
+ }
113
+ }
114
+
115
+ try {
116
+ rdflib.parse(text, store, endpoint, detectFormat(ct, accept));
117
+ store._diagnostics = diagnostics;
118
+ markOk();
119
+ return store;
120
+ } catch (parseErr) {
121
+ lastError = parseErr;
122
+ diagnostics.push({ accept, parseError: parseErr.message });
123
+ }
124
+ } catch (err) {
125
+ diagnostics.push({ accept, error: err.message });
126
+ lastError = err;
127
+ }
128
+ }
129
+
130
+ // Last resort: no Accept header (plain fetch, let server decide content-type)
131
+ try {
132
+ const resp = await fetchFn(endpoint);
133
+ if (resp.ok) {
134
+ const text = await resp.text();
135
+ const ct = (resp.headers.get('content-type') || '').split(';')[0].trim();
136
+ if (ct.includes('html') || /<html/i.test(text)) {
137
+ try {
138
+ const clean = await sanitizeHtml(text, { WHOLE_DOCUMENT: true, FORCE_BODY: false });
139
+ rdflib.parse(clean, store, endpoint, 'text/html');
140
+ store._isHtml = true;
141
+ store._rawHtml = clean;
142
+ store._diagnostics = diagnostics;
143
+ markOk();
144
+ return store;
145
+ } catch {}
146
+ } else {
147
+ try {
148
+ rdflib.parse(text, store, endpoint, detectFormat(ct, ''));
149
+ store._diagnostics = diagnostics;
150
+ markOk();
151
+ return store;
152
+ } catch (parseErr) {
153
+ lastError = parseErr;
154
+ }
155
+ }
156
+ }
157
+ } catch (err) {
158
+ lastError = err;
159
+ }
160
+
161
+ const err = lastError || new Error('Failed to load or parse RDF');
162
+ err.diagnostics = diagnostics;
163
+ throw err;
164
+ }
165
+
166
+ // ─── Blank-node processing ───────────────────────────────────────────────────
167
+ export function expandBnodes(store, data) {
168
+ let vars = data.head.vars;
169
+ let bindings = data.results.bindings;
170
+
171
+ if (bindings.length > 0) {
172
+ const allBnodeCols = vars.filter(v => bindings.every(r => r[v]?.type === 'bnode'));
173
+ if (allBnodeCols.length) {
174
+ vars = vars.filter(v => !allBnodeCols.includes(v));
175
+ bindings = bindings.map(r => {
176
+ const nr = { ...r };
177
+ allBnodeCols.forEach(v => delete nr[v]);
178
+ return nr;
179
+ });
180
+ }
181
+ }
182
+
183
+ const objVars = vars.filter(v => v !== 's' && v !== 'p');
184
+ const hasBnode = bindings.some(row => objVars.some(v => row[v]?.type === 'bnode'));
185
+ if (!hasBnode) return w3cResults(vars, bindings);
186
+
187
+ bindings = bindings.map(row => {
188
+ const newRow = {};
189
+ for (const [k, cell] of Object.entries(row)) {
190
+ newRow[k] = (objVars.includes(k) && cell?.type === 'bnode')
191
+ ? _bnodeToModal(store, cell._term)
192
+ : cell;
193
+ }
194
+ return newRow;
195
+ });
196
+ return w3cResults(vars, bindings);
197
+ }
198
+
199
+ function _bnodeToModal(store, bnodeTerm) {
200
+ const stmts = (store.match(bnodeTerm, null, null, null) || [])
201
+ .filter(st => st.object.termType !== 'BlankNode');
202
+ const bindings = stmts.map(st => ({
203
+ p: termToCell(st.predicate),
204
+ o: termToCell(st.object),
205
+ }));
206
+ return { type: 'bnode', value: '[…]', _data: w3cResults(['p', 'o'], bindings) };
207
+ }
208
+
209
+ // ─── Widen subjects into rows-of-properties ──────────────────────────────────
210
+ export function pivotSubjectsToRows(store, subjects, subjectName = 's') {
211
+ const shortName = uri => (uri || '').replace(/.*[/#]([^/#]+)\/?$/, '$1') || uri;
212
+ const cols = [subjectName];
213
+ const seen = new Set([subjectName]);
214
+ const rows = [];
215
+
216
+ for (const subj of subjects) {
217
+ const row = { [subjectName]: termToCell(subj) };
218
+ const stmts = store.match(subj, null, null, null) || [];
219
+ for (const st of stmts) {
220
+ const col = shortName(st.predicate.value);
221
+ const cell = termToCell(st.object);
222
+ if (!seen.has(col)) { cols.push(col); seen.add(col); }
223
+ if (row[col] === undefined) row[col] = cell;
224
+ else if (row[col].type === 'multi') row[col].values.push(cell);
225
+ else row[col] = { type: 'multi', values: [row[col], cell] };
226
+ }
227
+ rows.push(row);
228
+ }
229
+ return w3cResults(cols, rows);
230
+ }
231
+
232
+ export function promoteDisplayColumns(data, subjectCol) {
233
+ let cols = data.head.vars.filter(v => v !== subjectCol);
234
+ const lead = cols.find(v => /^(name|label|title)$/i.test(v));
235
+ if (lead) cols = [lead, ...cols.filter(v => v !== lead)];
236
+ return w3cResults(cols, data.results.bindings);
237
+ }
238
+
239
+ // ─── Full store → flat renderer results ──────────────────────────────────────
240
+ export function storeToResults(store) {
241
+ const stmts = Array.isArray(store.statements)
242
+ ? store.statements
243
+ : (typeof store.match === 'function' ? store.match(null, null, null) : []);
244
+
245
+ const bindings = stmts.map(st => ({
246
+ s: termToCell(st.subject),
247
+ p: termToCell(st.predicate),
248
+ o: termToCell(st.object),
249
+ }));
250
+
251
+ return w3cResults(['s', 'p', 'o'], bindings);
252
+ }
253
+
254
+ // ─── Fetch SPARQL query text from an RDF file ────────────────────────────────
255
+ export async function fetchQueryFromRdf(queryUrl, fetchFn = fetch) {
256
+ if (!rdf.isReady()) throw new Error('rdflib not available');
257
+ const rdflib = rdf;
258
+
259
+ try { queryUrl = new URL(queryUrl, document.baseURI).href; } catch {}
260
+
261
+ const docUrl = queryUrl.split('#')[0];
262
+ const store = await loadRdfStore(docUrl, fetchFn, { shared: true });
263
+
264
+ const subject = rdflib.sym(queryUrl);
265
+ const q = store.any(subject, rdflib.sym('http://www.w3.org/ns/sparql#query'))
266
+ || store.any(subject, rdflib.sym('http://www.w3.org/2000/01/rdf-schema#comment'))
267
+ || store.any(subject, rdflib.sym('http://purl.org/dc/terms/description'));
268
+
269
+ if (!q) throw new Error(`No query found at ${queryUrl}`);
270
+ return q.value;
271
+ }
272
+
273
+ // ─── runQuery: call from a script, returns plain JS values ───────────────────
274
+ export async function runQuery({ endpoint, sparql, pattern, wanted, vars: patternVars } = {}) {
275
+ const pat = pattern || wanted;
276
+ if (!endpoint) throw new Error('endpoint is required');
277
+ if (!sparql && !pat) throw new Error('sparql or pattern is required');
278
+
279
+ let data;
280
+
281
+ if (sparql) {
282
+ let queryText = sparql;
283
+ const isStoredRef = !/\s/.test(sparql) && /^https?:\/\/|^\/|^\.\.?\//.test(sparql.trim());
284
+ if (isStoredRef) queryText = await fetchQueryFromRdf(sparql);
285
+ data = await execSparql(queryText, endpoint);
286
+ } else {
287
+ const store = await loadRdfStore(endpoint);
288
+ const [s, p, o] = parsePatternParts(pat, rdf, {}, endpoint);
289
+ data = matchStore(store, s, p, o);
290
+ }
291
+
292
+ return toPlainResults(data, patternVars);
293
+ }
294
+
295
+ // ─── Run SPARQL against a pre-loaded rdflib store ────────────────────────────
296
+ async function _localSparql(queryText, endpoint) {
297
+ if (!rdf.hasSparqlEngine()) throw new Error('rdflib SPARQL engine not available');
298
+
299
+ // rdflib's local SPARQL parses LIMIT/OFFSET/ORDER BY but doesn't enforce
300
+ // them, so a query with these clauses silently returns the unbounded /
301
+ // unsorted result set. Surface it instead of letting the user wonder why
302
+ // their LIMIT is being ignored. To honor these clauses, load Comunica
303
+ // (e.g. via the vendored UMD: `<script src=".../@comunica-query-sparql.umd.js">`).
304
+ if (/\b(LIMIT|OFFSET|ORDER\s+BY)\b/i.test(queryText)) {
305
+ console.warn(
306
+ '[sol-query] LIMIT/OFFSET/ORDER BY present but rdflib local SPARQL ignores them. ' +
307
+ 'Load @comunica/query-sparql to enforce these clauses against RDF documents.'
308
+ );
309
+ }
310
+
311
+ const store = await loadRdfStore(endpoint);
312
+ const parsed = rdf.sparqlToQuery(queryText, false, store);
313
+ if (!parsed) throw new Error('Could not parse SPARQL query');
314
+
315
+ return new Promise((resolve, reject) => {
316
+ const bindings = [];
317
+ try {
318
+ store.query(
319
+ parsed,
320
+ b => bindings.push(b),
321
+ rdf.fetcher(store),
322
+ () => resolve(bindingsToResults(bindings, queryText)),
323
+ );
324
+ } catch (err) {
325
+ reject(err);
326
+ }
327
+ });
328
+ }
329
+
330
+ // ─── SPARQL execution ────────────────────────────────────────────────────────
331
+ // `fetchFn` is the authenticated fetch (from getAuthFetch) when one is
332
+ // available; passed to Comunica via its query context and to the native
333
+ // SPARQL adapter for endpoints that aren't RDF documents.
334
+ export async function execSparql(query, endpoint, fetchFn) {
335
+ // Multi-source federation only works through Comunica.
336
+ if (Array.isArray(endpoint)) {
337
+ const factory = ComunicaSparqlAdapter.getComunicaEngine();
338
+ if (!factory) throw new Error('Multiple endpoints require Comunica');
339
+ return await new ComunicaSparqlAdapter(factory).executeQuery(query, endpoint, fetchFn);
340
+ }
341
+
342
+ try { endpoint = new URL(endpoint, document.baseURI).href; } catch {}
343
+
344
+ const isRdf = _isRdfDoc(endpoint);
345
+
346
+ const comunicaFactory = ComunicaSparqlAdapter.getComunicaEngine();
347
+ if (comunicaFactory) {
348
+ try { return await new ComunicaSparqlAdapter(comunicaFactory).executeQuery(query, endpoint, fetchFn); }
349
+ catch (err) {
350
+ console.warn('[sol-query] Comunica failed, falling back to rdflib:', err);
351
+ }
352
+ } else if (typeof window !== 'undefined' && window.Comunica) {
353
+ console.warn('[sol-query] window.Comunica is set but no QueryEngine factory was found.',
354
+ 'Top-level keys:', Object.keys(window.Comunica),
355
+ window.Comunica.default ? 'default keys: ' + Object.keys(window.Comunica.default).slice(0, 10).join(', ') : '');
356
+ }
357
+
358
+ if (isRdf) {
359
+ return await _localSparql(query, endpoint);
360
+ }
361
+
362
+ const rdflibAdapter = new RdflibSparqlAdapter();
363
+ if (rdflibAdapter.isAvailable()) {
364
+ try { return await rdflibAdapter.executeQuery(query, endpoint, true); } catch {
365
+ try { return await rdflibAdapter.executeQuery(query, endpoint, false); } catch {}
366
+ }
367
+ }
368
+ return await new NativeSparqlAdapter().executeQuery(query, endpoint, fetchFn);
369
+ }
370
+
371
+ // ─── SPARQL adapter (rdflib-based) ───────────────────────────────────────────
372
+
373
+ export class RdflibSparqlAdapter {
374
+ isAvailable() {
375
+ return rdf.hasRemoteSparql();
376
+ }
377
+
378
+ async executeQuery(query, endpoint, withCredentials = true) {
379
+ if (!rdf.isReady()) throw new Error('rdflib not available');
380
+ try {
381
+ return await this._attempt(query, endpoint, withCredentials);
382
+ } catch (err) {
383
+ if (withCredentials) {
384
+ return await this._attempt(query, endpoint, false);
385
+ }
386
+ throw err;
387
+ }
388
+ }
389
+
390
+ async _attempt(query, endpoint, withCredentials) {
391
+ const raw = await rdf.sparqlQuery(query, { endpoint, withCredentials });
392
+ const selectVarsResult = _selectVars(query);
393
+ const vars = selectVarsResult
394
+ ?? (raw.length ? Object.keys(raw[0]).filter(k => !k.startsWith('?')) : []);
395
+ const bindings = raw.map(binding => {
396
+ const row = {};
397
+ vars.forEach(v => {
398
+ const val = binding[v];
399
+ const cell = {
400
+ type: val ? (val.termType || 'literal').toLowerCase() : 'literal',
401
+ value: val ? (val.value || String(val)) : '',
402
+ };
403
+ if (val?.language) cell['xml:lang'] = val.language;
404
+ if (val?.datatype?.value) cell.datatype = val.datatype.value;
405
+ row[v] = cell;
406
+ });
407
+ return row;
408
+ });
409
+ return w3cResults(vars, bindings);
410
+ }
411
+ }
package/core/rdf.js ADDED
@@ -0,0 +1,154 @@
1
+ // Singleton wrapper around rdflib. The rest of the codebase goes through this
2
+ // class so rdflib is imported in exactly one place. Rollup treats `rdflib` as
3
+ // external (mapped to the `$rdf` UMD global); jest's moduleNameMapper maps it
4
+ // to a mock; importmaps/bundlers resolve it normally.
5
+
6
+ // Bare specifier — resolved by the consumer's importmap (CDN or local
7
+ // vendored copy) or by a bundler. Per-component UMD builds list `rdflib`
8
+ // in `external` so it stays a runtime global.
9
+ import * as _rdflib from 'rdflib';
10
+ import { register as registerService } from './services.js';
11
+
12
+ // `import * as _rdflib` exposes rdflib's named exports directly.
13
+ const _lib = _rdflib;
14
+
15
+ class Rdf {
16
+ constructor() {
17
+ this._store = null; // lazy shared singleton store
18
+ this._fetcher = null; // fetcher bound to _store
19
+ this._adopted = false; // a host explicitly adopted an external store (wins)
20
+ this._loaded = new Set(); // URLs already parsed into _store (cache key)
21
+ }
22
+
23
+ // Record that `url` has been parsed into the shared store.
24
+ markLoaded(url) { this._loaded.add(url); }
25
+ isLoaded(url) { return this._loaded.has(url); }
26
+
27
+ // Term constructors
28
+ sym(uri) { return _lib.sym(uri); }
29
+ // rdflib.literal accepts either (value, langOrDatatype) — second arg
30
+ // is the language tag if a string, or the datatype if a NamedNode —
31
+ // or (value, lang, datatype). Pass through whichever form the caller
32
+ // used so typed literals like "45.52"^^xsd:decimal survive.
33
+ literal(value, langOrDatatype, datatype) {
34
+ if (datatype !== undefined) return _lib.literal(value, langOrDatatype, datatype);
35
+ return _lib.literal(value, langOrDatatype);
36
+ }
37
+ blankNode(id) { return _lib.blankNode(id); }
38
+
39
+ // Stores & parsing
40
+ graph() { return _lib.graph(); }
41
+ parse(text, store, base, type) { return _lib.parse(text, store, base, type); }
42
+ st(s, p, o, g) { return _lib.st(s, p, o, g); }
43
+
44
+ // Shared singleton store — interop point with solid-logic / solid-ui / mashlib.
45
+ // **When solid-logic's singleton is present on `window`, that's THE store**
46
+ // — every sol-* component, solid-ui module, mashlib, etc. point at the same
47
+ // rdflib graph, so cross-component reads/writes are coherent and solid-ui's
48
+ // captured-at-import-time `kb` references work without any swap dance.
49
+ // Falls back to a freshly created graph in environments without solid-logic
50
+ // (unit tests, headless scripts without the singleton wired up).
51
+ get store() {
52
+ // A host that explicitly adopted a foreign store (useStore — e.g. another
53
+ // component library's rdflib graph handed over at runtime) wins outright,
54
+ // so swc components share THAT graph regardless of solid-logic's singleton.
55
+ if (this._adopted && this._store) return this._store;
56
+ // Otherwise always re-probe so consumers reach solid-logic's singleton even
57
+ // if an early access happened before solid-logic finished loading. Once
58
+ // solid-logic is up, every call returns ITS store; before then, a
59
+ // local fresh graph is used and persists until the singleton appears.
60
+ const sl = (typeof window !== 'undefined') &&
61
+ (window[Symbol.for('solid-logic-singleton')] || window.SolidLogic);
62
+ if (sl?.store) {
63
+ this._store = sl.store;
64
+ return sl.store;
65
+ }
66
+ if (!this._store) this._store = _lib.graph();
67
+ return this._store;
68
+ }
69
+ useStore(externalStore) {
70
+ if (!externalStore || typeof externalStore.match !== 'function') return false;
71
+ this._store = externalStore;
72
+ this._fetcher = externalStore.fetcher || null;
73
+ this._adopted = true;
74
+ this._loaded.clear();
75
+ return true;
76
+ }
77
+ get storeFetcher() {
78
+ if (this._fetcher) return this._fetcher;
79
+ if (this.store.fetcher) { this._fetcher = this.store.fetcher; return this._fetcher; }
80
+ this._fetcher = new _lib.Fetcher(this.store);
81
+ this.store.fetcher = this._fetcher;
82
+ return this._fetcher;
83
+ }
84
+
85
+ // Fetch a document into the shared store via the shared (auth-aware) Fetcher,
86
+ // at most once per document. Returns the shared store. This is the convenience
87
+ // a component reaches through window.SolidWebComponents.rdf.load(url).
88
+ async load(url) {
89
+ const doc = String(url).split('#')[0];
90
+ if (!this.isLoaded(doc)) {
91
+ await this.storeFetcher.load(doc);
92
+ this.markLoaded(doc);
93
+ }
94
+ return this.store;
95
+ }
96
+
97
+ // SPARQL
98
+ fetcher(store, opts) { return new _lib.Fetcher(store, opts); }
99
+ sparqlToQuery(query, isUpdate, store) { return _lib.SPARQLToQuery(query, isUpdate, store); }
100
+ sparqlQuery(query, opts) { return _lib.sparqlQuery(query, opts); }
101
+
102
+ // Capability probes
103
+ isReady() { return !!_lib && typeof _lib.graph === 'function'; }
104
+ hasSparqlEngine() { return typeof _lib.SPARQLToQuery === 'function'; }
105
+ hasRemoteSparql() { return typeof _lib.sparqlQuery === 'function'; }
106
+
107
+ // Serialization
108
+ serialize(doc, store, base, contentType) {
109
+ return _lib.serialize(doc, store, base, contentType);
110
+ }
111
+
112
+ // UpdateManager — for PATCH-based edits and putBack
113
+ get UpdateManager() { return _lib.UpdateManager; }
114
+
115
+ // Escape hatches for the few places that need rdflib-shaped access
116
+ // (e.g. `new rdflib.Fetcher(...)`). Prefer the methods above.
117
+ get SPARQLToQuery() { return _lib.SPARQLToQuery; }
118
+ get Fetcher() { return _lib.Fetcher; }
119
+ get NamedNode() { return _lib.NamedNode; }
120
+ get BlankNode() { return _lib.BlankNode; }
121
+ get Literal() { return _lib.Literal; }
122
+ get Collection() { return _lib.Collection; }
123
+ get Statement() { return _lib.Statement; }
124
+ }
125
+
126
+ // Cross-bundle singleton. Every bundle (each UMD component, the app's own
127
+ // code, solid-ui's world) compiles its own copy of this module; without a
128
+ // shared instance each would mint its OWN store + storeFetcher, so e.g.
129
+ // <sol-login>'s `_integrateWithRdflib()` patch would be invisible to an app
130
+ // reading `rdf.store` from a different bundle. Publishing ONE instance on
131
+ // `window` makes the store, fetcher and loaded-set page-wide, so any app can
132
+ // just load components from sol-loader and share one coherent store — no
133
+ // bundling-from-source workaround. (Paired with the rdflib→window.$rdf shim
134
+ // so all bundles also share one rdflib *library*, for term `instanceof`.)
135
+ //
136
+ // Browser only: in Node / jest each module keeps its own instance (no `window`),
137
+ // preserving test isolation.
138
+ const _RDF_SINGLETON = Symbol.for('sol-components:rdf-singleton');
139
+ export const rdf = (typeof window !== 'undefined')
140
+ ? (window[_RDF_SINGLETON] || (window[_RDF_SINGLETON] = new Rdf()))
141
+ : new Rdf();
142
+ export default rdf;
143
+
144
+ // Publish the shared store as the `rdf` host-service so any component can reach
145
+ // it via window.SolidWebComponents.rdf — no import of this module required.
146
+ registerService('rdf', rdf);
147
+
148
+ // Register the broker consumer that adopts a foreign rdflib store: the loader
149
+ // invokes it for a manifest `consumes: { rdf: { call: 'rdf.useStore' } }`. (The
150
+ // loader publishes registerConsumer; absent in Node/jest, so this is guarded.)
151
+ if (typeof window !== 'undefined' && window.SolidWebComponents
152
+ && typeof window.SolidWebComponents.registerConsumer === 'function') {
153
+ window.SolidWebComponents.registerConsumer('rdf.useStore', function (store) { rdf.useStore(store); });
154
+ }
@@ -0,0 +1,106 @@
1
+ // core/services.js — import-side accessor for the host-services registry.
2
+ //
3
+ // The ecosystem's "share resources without importing each other" surface. The
4
+ // component-interop loader publishes a tiny registry at `window.ComponentInterop.services`
5
+ // (services.js#root() also accepts a window.SolidWebComponents surface); swc capability modules import THIS file
6
+ // to register the shared services they provide:
7
+ //
8
+ // import { register } from '../core/services.js';
9
+ // register('rdf', rdf); // core/rdf.js
10
+ // register('auth', { fetch, manager }); // web/sol-login.js
11
+ // register('defaults', { get, onChange });
12
+ //
13
+ // Any component (swc's or a third party's) then reads them, import-free, via
14
+ // `window.SolidWebComponents.{ rdf, auth, fetch, defaults, has, services }`.
15
+ //
16
+ // Mirrors core/rdf.js: in the browser everything funnels through the one
17
+ // window-shared registry; in Node/jest there's no `window`, so a module-local
18
+ // fallback registry is used (preserving test isolation). The registry is
19
+ // duck-typed by its methods, so it doesn't matter whether the loader or this
20
+ // module created it.
21
+
22
+ import { EVENTS } from './events.js';
23
+
24
+ function makeRegistry() {
25
+ const map = new Map();
26
+ const waiters = new Map();
27
+ return {
28
+ register(name, impl) {
29
+ map.set(name, impl);
30
+ const ws = waiters.get(name);
31
+ if (ws) { waiters.delete(name); ws.forEach((fn) => fn(impl)); }
32
+ },
33
+ get(name) { return map.get(name); },
34
+ has(name) { return map.has(name); },
35
+ names() { return Array.from(map.keys()); },
36
+ whenReady(name) {
37
+ if (map.has(name)) return Promise.resolve(map.get(name));
38
+ return new Promise((res) => {
39
+ const a = waiters.get(name) || [];
40
+ a.push(res);
41
+ waiters.set(name, a);
42
+ });
43
+ },
44
+ };
45
+ }
46
+
47
+ let _local = null;
48
+
49
+ /** The one shared host surface. It is the broker's `window.ComponentInterop`
50
+ * when component-interop's loader is on the page; `window.SolidWebComponents`
51
+ * is swc's historical alias for the SAME object. Unifying them here lets swc
52
+ * work whether the page loaded component-interop's generic loader OR swc's own
53
+ * (which vendors it). A Node-side stand-in is used in tests without a window. */
54
+ export function root() {
55
+ if (typeof window !== 'undefined') {
56
+ const surface = window.ComponentInterop || window.SolidWebComponents || {};
57
+ window.ComponentInterop = surface;
58
+ window.SolidWebComponents = surface;
59
+ return surface;
60
+ }
61
+ return (_local = _local || {});
62
+ }
63
+
64
+ /** The shared services registry — the loader's if present, else one we create. */
65
+ export function services() {
66
+ const r = root();
67
+ if (!r.services) r.services = makeRegistry();
68
+ if (!r.EVENTS) r.EVENTS = EVENTS; // the loader doesn't bake the table
69
+ return r.services;
70
+ }
71
+
72
+ export function register(name, impl) { return services().register(name, impl); }
73
+ export function get(name) { return services().get(name); }
74
+ export function has(name) { return services().has(name); }
75
+ export function whenReady(name) { return services().whenReady(name); }
76
+
77
+ /**
78
+ * Adopt a foreign authenticated fetch as the page's default authenticated fetch
79
+ * when no <sol-login> is present. This lets swc components ride a session
80
+ * established by another component library (e.g. PodOS, which hands its
81
+ * `authenticatedFetch` out via its `pod-os:loaded` event). getAuthFetch()
82
+ * (core/auth-fetch.js) returns it as the fallback after the <sol-login> lookup.
83
+ * A logged-in <sol-login> still wins — this is the no-sol-login fallback.
84
+ *
85
+ * @param {(input: RequestInfo, init?: RequestInit) => Promise<Response>} fn
86
+ * @param {object} [info] e.g. { webId }
87
+ * @returns the adopted fetch (or null when cleared)
88
+ */
89
+ export function adoptFetch(fn, info) {
90
+ const r = root();
91
+ r.adoptedFetch = (typeof fn === 'function') ? fn : null;
92
+ if (info && info.webId) r.adoptedWebId = info.webId;
93
+ return r.adoptedFetch;
94
+ }
95
+
96
+ // Expose adoptFetch on the host surface so import-free host glue can call
97
+ // `window.SolidWebComponents.adoptFetch(fn, { webId })`.
98
+ if (typeof window !== 'undefined') {
99
+ const r = root();
100
+ if (!r.adoptFetch) r.adoptFetch = adoptFetch;
101
+ // Register the broker consumer that adopts a foreign authenticated fetch: the
102
+ // loader invokes it for a manifest `consumes: { auth: { call: 'adoptFetch' } }`.
103
+ if (typeof r.registerConsumer === 'function') r.registerConsumer('adoptFetch', (fn) => adoptFetch(fn));
104
+ }
105
+
106
+ export { EVENTS };