@factoidal/core 0.11.0 → 0.13.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/lib/api.mjs CHANGED
@@ -28,32 +28,22 @@ import { version as packageVersion } from './version.mjs';
28
28
  import { openGeneration, readRegion, manifestKeys, hexOfBytes } from './store.mjs';
29
29
  import { fullEngineMessage } from './lean-entry.mjs';
30
30
  import { memoryDeltaLogStore } from './delta-log-hosts.mjs';
31
+ import { FactoidalError, errorFromEnvelope, envelopeResult } from './errors.mjs';
31
32
 
32
33
  // The format names, regimes and query outputs are the engine's tables (op
33
34
  // engineTables, formal/lean4/Wasm/Ops/Handles.lean), read once per engine:
34
35
  // no host copy (https://github.com/danbri/factoidal/issues/894 item 5).
35
36
 
36
37
  // The parse tag a format name selects, from the engine's table.
37
- function parseTagOf(tables, fmt) {
38
+ // A name outside the table is refused by the engine (op apiCheck,
39
+ // code format.unknown).
40
+ function parseTagOf(e, tables, fmt, who) {
38
41
  const key = String(fmt || 'turtle').toLowerCase();
39
42
  const tag = tables.parseFormats[key];
40
- if (tag === undefined) {
41
- throw new TypeError(
42
- `Unknown format '${fmt}'. Expected one of: ` + Object.keys(tables.parseFormats).join(', '));
43
- }
43
+ if (tag === undefined) checkOptions(e, who || 'parse', { format: String(fmt) });
44
44
  return tag;
45
45
  }
46
46
 
47
- // The engine's refusal of a regime name (table refusedRegimePrefixes), or null.
48
- function iklRefusalOf(tables, who, regime) {
49
- if (regime == null) return null;
50
- const r = String(regime);
51
- for (const [prefix, message] of Object.entries(tables.refusedRegimePrefixes)) {
52
- if (r.toLowerCase().startsWith(prefix)) return message.replace('WHO', who).replace('REGIME', r);
53
- }
54
- return null;
55
- }
56
-
57
47
  // Where a function takes an options object, a bare string names the
58
48
  // format: serialize(ds, 'turtle') is serialize(ds, { format: 'turtle' }).
59
49
  // Until 0.8.0 the string was read as an empty options object, so
@@ -72,16 +62,28 @@ function optionsOf(fn, options) {
72
62
  // package, or a dispatch table that does not serve it). When the entry is
73
63
  // the core engine and the op is one only the full engine serves, the error
74
64
  // names the entry that has it (issue 713 stage 4).
65
+ // The engine's own answer to the op is the error (code op.unknown); an entry
66
+ // with no engine handle to ask gets the same code.
75
67
  function missingOpError(what, e, op) {
76
- if (e && e.profile === 'core' && op && Array.isArray(e.fullOnlyOps) &&
77
- e.fullOnlyOps.includes(op)) {
78
- return new Error(`${what}: ${fullEngineMessage(`the op '${op}'`)}`);
68
+ const eng = e && e.engine;
69
+ if (eng && typeof eng.call === 'function' && op) {
70
+ try {
71
+ eng.call(op, []);
72
+ } catch (err) {
73
+ if (err && err.envelope && err.envelope.code === 'op.unknown') {
74
+ const out = errorFromEnvelope(err.envelope);
75
+ if (e.profile === 'core' && Array.isArray(e.fullOnlyOps) && e.fullOnlyOps.includes(op)) {
76
+ out.message = `${what}: ${fullEngineMessage(`the op '${op}'`)}`;
77
+ }
78
+ return out;
79
+ }
80
+ }
79
81
  }
80
- return new Error(
82
+ return new FactoidalError(
81
83
  `${what}: the loaded Lean wasm module has no dispatch op for this ` +
82
84
  'operation. The module is older than this package; rebuild it with ' +
83
85
  'formal/lean4/Wasm/build-wasm.sh, or point FACTOIDAL_L4_ASSETS at a ' +
84
- 'current build.');
86
+ 'current build.', { code: 'op.unknown' });
85
87
  }
86
88
 
87
89
  // The query form, from the engine's SPARQL parser (op queryForm). A query
@@ -124,85 +126,23 @@ function bindingsFromSrj(srj) {
124
126
  }
125
127
 
126
128
  // ---------------------------------------------------------------------
127
- // Strict parsing (issue #344) -- ParseError, and the shared envelope
128
- // unwrapper every engine call routes through.
129
+ // The envelope unwrapper every engine call routes through. A refusal is a
130
+ // FactoidalError (lib/errors.mjs) whose code, spec section and position the
131
+ // engine decided (formal/lean4/Wasm/Ops/ErrorModel.lean).
129
132
  // ---------------------------------------------------------------------
130
133
 
131
- /**
132
- * A parse failure that carries a position: `line`/`column`/`offset`
133
- * when the failing engine call reported one (a parseToDatasetJson or
134
- * datasetOpen error over Turtle, TriG, N-Triples or N-Quads), else
135
- * `undefined`. RDF/XML and JSON-LD failures, and any non-parse engine
136
- * error, stay plain Error -- entryResult() below decides which one to
137
- * throw.
138
- *
139
- * Units: `line` and `column` are 1-based; `column` counts Unicode scalar
140
- * values, as the engine reports it. `offset` is a UTF-16 code-unit index
141
- * into the input JavaScript string, so `text.slice(err.offset)` starts
142
- * at the error. The engine counts Unicode scalar values;
143
- * utf16OffsetOf() below converts.
144
- */
145
- class ParseError extends Error {
146
- constructor(message, info) {
147
- super(message);
148
- this.name = 'ParseError';
149
- const i = info || {};
150
- this.line = i.line;
151
- this.column = i.column;
152
- this.offset = i.offset;
153
- this.format = i.format;
154
- if (typeof Error.captureStackTrace === 'function') {
155
- Error.captureStackTrace(this, ParseError);
156
- }
157
- }
134
+ function entryResult(jsonText, _what, text) {
135
+ return envelopeResult(jsonText, text);
158
136
  }
159
137
 
160
- // Unwrap one dispatch-op JSON envelope ({"ok":true,...} |
161
- // {"ok":false,"error":...}). A `line` or `offset` member on the error
162
- // envelope means the failing call was a parse (Turtle/TriG/N-Triples/
163
- // N-Quads carry a position; RDF/XML and JSON-LD carry a message only)
164
- // -- throw ParseError so a caller can branch on position without
165
- // string-matching the message. Every other failure stays a plain
166
- // Error, as before. Pure (no per-driver state), so every buildApi()
167
- // instance and DatasetHandle share this one function.
168
- function entryResult(jsonText, what) {
169
- const r = JSON.parse(jsonText);
170
- if (!r.ok) {
171
- if (r.line !== undefined || r.offset !== undefined) {
172
- throw new ParseError(`${what}: ${r.error}`, {
173
- line: r.line, column: r.column, offset: r.offset, format: r.format,
174
- });
175
- }
176
- throw new Error(`${what}: ${r.error}`);
177
- }
178
- return r;
179
- }
180
-
181
- // The Lean engine reports a parse error's `offset` as a count of Unicode
182
- // scalar values from the start of the input. A JavaScript string is
183
- // indexed in UTF-16 code units, where a character outside the Basic
184
- // Multilingual Plane (U+10000 and above) takes two. This converts the
185
- // engine's count into the UTF-16 index of the same position. A count past
186
- // the end of the text gives text.length. Iterating a string with for..of
187
- // yields one element per code point (a lone surrogate is one element, and
188
- // the engine reads it as one replacement character), so both sides count
189
- // the same units.
190
- function utf16OffsetOf(text, scalarOffset) {
191
- if (typeof text !== 'string' || !Number.isInteger(scalarOffset) || scalarOffset < 0) {
192
- return scalarOffset;
193
- }
194
- let units = 0;
195
- let scalars = 0;
196
- for (const ch of text) {
197
- if (scalars >= scalarOffset) break;
198
- units += ch.length;
199
- scalars += 1;
200
- }
201
- return units;
138
+ // entryResult() for an op that read `text`: the position's offset becomes a
139
+ // UTF-16 index into `text`.
140
+ function parseResult(jsonText, what, text) {
141
+ return entryResult(jsonText, what, text);
202
142
  }
203
143
 
204
144
  // The lenient parse's diagnostics (https://github.com/danbri/factoidal/issues/716)
205
- // with each `offset` converted as utf16OffsetOf() converts a ParseError's, so
145
+ // with each `offset` converted to a UTF-16 index as a FactoidalError's, so
206
146
  // `text.slice(d.offset)` starts at the error. `line` and `column` are kept as
207
147
  // the engine reports them. One pass over `text` for all diagnostics: the
208
148
  // offsets are visited in increasing order, whatever order the list has.
@@ -230,23 +170,6 @@ function utf16Diagnostics(text, diagnostics) {
230
170
  return diagnostics.map((d, i) => (units.has(i) ? { ...d, offset: units.get(i) } : d));
231
171
  }
232
172
 
233
- // entryResult() for an op that parsed `text`: a positioned ParseError gets
234
- // its offset converted to a UTF-16 index into `text`.
235
- function parseResult(jsonText, what, text) {
236
- try {
237
- return entryResult(jsonText, what);
238
- } catch (err) {
239
- if (err instanceof ParseError && typeof err.offset === 'number') {
240
- err.offset = utf16OffsetOf(text, err.offset);
241
- }
242
- throw err;
243
- }
244
- }
245
-
246
- // DatasetHandle: the name of the engine-resident dataset class before
247
- // 0.10.0. Deprecated alias of Dataset, kept for 0.10.x only.
248
- const DatasetHandle = Dataset;
249
-
250
173
  // ---------------------------------------------------------------------
251
174
  // The Lean persisted store (Shardborough generations: IBK3, IBK4, IBK5
252
175
  // blocks with their index sidecars; skills/shardborough-storage). A
@@ -289,7 +212,8 @@ class Store {
289
212
  get closed() { return this._closed; }
290
213
 
291
214
  _assertOpen(who) {
292
- if (this._closed) throw new Error(`Store.${who}: store handle '${this.handle}' is closed`);
215
+ // The engine answers handle.disposed for a closed store handle.
216
+ if (this._closed) entryResult(this._e.storeHandleClose(this.handle), `Store.${who}`);
293
217
  }
294
218
 
295
219
  /**
@@ -300,7 +224,7 @@ class Store {
300
224
  async query(sparql) {
301
225
  this._assertOpen('query');
302
226
  if (typeof sparql !== 'string') throw new TypeError('Store.query: sparql must be a string');
303
- const run = () => entryResult(this._e.storeHandleQuery(this.handle, sparql), 'Store.query');
227
+ const run = () => entryResult(this._e.storeHandleQuery(this.handle, sparql), 'Store.query', sparql);
304
228
  const r = this._withExtensionRounds ? await this._withExtensionRounds(run) : run();
305
229
  if (r.kind === 'ask') return r.boolean;
306
230
  if (r.kind === 'construct' || r.kind === 'describe') {
@@ -351,16 +275,24 @@ function buildApi(driver) {
351
275
  entryPromise = Promise.resolve()
352
276
  .then(() => driver.loadEntry())
353
277
  .then((e) => {
354
- if (!e) throw new Error('factoidal: the engine loader returned no entry object');
278
+ if (!e) throw new FactoidalError('factoidal: the engine loader returned no entry object', { code: 'host.io' });
355
279
  return e;
356
280
  })
357
- .catch((err) => { entryPromise = undefined; throw err; });
281
+ .catch((err) => {
282
+ entryPromise = undefined;
283
+ throw err instanceof FactoidalError ? err
284
+ : new FactoidalError(`factoidal: the engine did not load: ${err && err.message}`,
285
+ { code: 'host.io', cause: err });
286
+ });
358
287
  }
359
288
  return entryPromise;
360
289
  }
361
290
 
362
- // entryResult() is now module-level (see above ParseError) -- pure,
363
- // shared by every buildApi() instance and by DatasetHandle.
291
+ // The options object of a public function, checked by the engine (op
292
+ // apiCheck): unknown keys, format names, regimes, outputs and versions.
293
+ async function checkedOptions(who, options) {
294
+ return checkOptions(await entry(), who, options);
295
+ }
364
296
 
365
297
 
366
298
  // ---------------------------------------------------------------
@@ -492,7 +424,7 @@ function buildApi(driver) {
492
424
  if (typeof e.registerServiceEndpoint !== 'function') {
493
425
  throw missingOpError('registerServiceEndpoint');
494
426
  }
495
- const opts = checkOptions('registerServiceEndpoint', optionsOf('registerServiceEndpoint', options), ['format', 'baseIRI']);
427
+ const opts = await checkedOptions('registerServiceEndpoint', optionsOf('registerServiceEndpoint', options));
496
428
  const nq = await nquadsOf(data, opts, 'registerServiceEndpoint');
497
429
  return entryResult(e.registerServiceEndpoint(iri, nq),
498
430
  'registerServiceEndpoint');
@@ -516,9 +448,9 @@ function buildApi(driver) {
516
448
  const result = runOnce();
517
449
  if (extPending.length === 0) return result;
518
450
  if (round >= EXT_MAX_ROUNDS) {
519
- throw new Error(
451
+ throw new FactoidalError(
520
452
  'extension functions: async resolution did not converge ' +
521
- `within ${EXT_MAX_ROUNDS} evaluation rounds`);
453
+ `within ${EXT_MAX_ROUNDS} evaluation rounds`, { code: 'capacity.exceeded' });
522
454
  }
523
455
  const pend = extPending;
524
456
  extPending = [];
@@ -588,7 +520,9 @@ function buildApi(driver) {
588
520
  tables() {
589
521
  if (!tablesCache) {
590
522
  requireEntryFn(e, 'engineTables', 'format and regime tables');
591
- tablesCache = entryResult(e.engineTables(), 'engineTables').tables;
523
+ const t = entryResult(e.engineTables(), 'engineTables');
524
+ tablesCache = { ...t.tables, errorCodes: t.errorCodes || [],
525
+ errorModelExemptOps: t.errorModelExemptOps || [] };
592
526
  for (const k of Object.keys(WRITER_TAGS)) delete WRITER_TAGS[k];
593
527
  Object.assign(WRITER_TAGS, tablesCache.writerFormats);
594
528
  }
@@ -643,7 +577,7 @@ function buildApi(driver) {
643
577
  requireEntryFn(c.e, 'detectFormat', `${who} (format inference)`);
644
578
  return { tag: entryResult(c.e.detectFormat(text), who).format, inferred: true };
645
579
  }
646
- return { tag: parseTagOf(c.tables(), format), inferred: false };
580
+ return { tag: parseTagOf(c.e, c.tables(), format, who), inferred: false };
647
581
  }
648
582
 
649
583
  // Open `text` as a Dataset. A parse error names the format and says
@@ -655,11 +589,10 @@ function buildApi(driver) {
655
589
  try {
656
590
  r = parseResult(c.e.datasetOpen(text, tag, baseIRI || ''), who, text);
657
591
  } catch (err) {
658
- if (inferred) {
659
- err.message += ` (format inferred as '${tag}' because none was given; pass {format} to choose)`;
660
- err.formatInferred = true;
592
+ if (err instanceof FactoidalError) {
593
+ err.formatInferred = inferred;
594
+ err.format = err.format || tag;
661
595
  }
662
- err.format = err.format || tag;
663
596
  throw err;
664
597
  }
665
598
  return c.wrap(r, undefined, { format: tag, formatInferred: inferred });
@@ -709,43 +642,29 @@ function buildApi(driver) {
709
642
 
710
643
  async function queryDatasetHandle(c, ds, sparql, opts) {
711
644
  if (typeof sparql !== 'string') throw new TypeError('query: sparql must be a string');
645
+ // The options were checked by the engine (op apiCheck, as 'query' or
646
+ // 'Dataset.query'): regime, version, output and their combination.
712
647
  const entail = opts.entail || 'none';
713
- const tables = c.tables();
714
- const ikl = iklRefusalOf(tables, 'query', entail);
715
- if (ikl) throw new TypeError(ikl);
716
- if (!tables.entailRegimes.includes(entail)) {
717
- throw new TypeError(`query: entail must be one of ${tables.entailRegimes.join(', ')}`);
718
- }
719
- if (opts.version !== undefined && !['1.1', '1.2'].includes(String(opts.version))) {
720
- throw new TypeError("query: version must be '1.1' or '1.2'");
721
- }
722
648
  const sparql12 = opts.sparql12 === true || String(opts.version || '') === '1.2';
723
649
  const e = c.e;
724
650
  if (entail !== 'none' || opts.output !== undefined) {
725
651
  // queryRaw over the stored dataset: an entailment regime, or a
726
652
  // results format (SPARQL 1.1 Query Results JSON, XML, CSV, TSV).
727
- if (sparql12) {
728
- throw new TypeError(
729
- "query: entail/output and SPARQL 1.2 cannot be combined; that path reads SPARQL 1.1");
730
- }
731
653
  const output = opts.output || 'json';
732
- if (!tables.queryOutputs.includes(output)) {
733
- throw new TypeError(`query: output must be one of ${tables.queryOutputs.join(', ')}`);
734
- }
735
654
  const form = queryFormOf(e, sparql);
736
655
  const graphForm = form === 'construct' || form === 'describe';
737
656
  requireEntryFn(e, 'datasetQueryRaw', `query {entail/output}`);
738
657
  if (opts.output === undefined) {
739
658
  // An entailment regime with the default answer shape.
740
659
  const r = entryResult(e.datasetQueryRaw(ds.handle, sparql, JSON.stringify({
741
- entail, output: graphForm ? 'ntriples' : 'json' })), 'query');
660
+ entail, output: graphForm ? 'ntriples' : 'json' })), 'query', sparql);
742
661
  if (graphForm) return c.fromNQuads(r.text, ds.prefixes);
743
662
  const srj = JSON.parse(r.text);
744
663
  if (typeof srj.boolean === 'boolean') return srj.boolean;
745
664
  return bindingsFromSrj(srj);
746
665
  }
747
666
  const r = entryResult(e.datasetQueryRaw(ds.handle, sparql,
748
- JSON.stringify({ entail, output })), 'query');
667
+ JSON.stringify({ entail, output })), 'query', sparql);
749
668
  return output === 'json' && !graphForm ? JSON.parse(r.text) : r.text;
750
669
  }
751
670
  const fn = sparql12 ? e.datasetQuery12 : e.datasetQuery;
@@ -753,7 +672,7 @@ function buildApi(driver) {
753
672
  throw missingOpError(`query (datasetQuery${sparql12 ? '12' : ''})`, e,
754
673
  sparql12 ? 'datasetQuery12' : 'datasetQuery');
755
674
  }
756
- const r = await withExtensionRounds(() => entryResult(fn(ds.handle, sparql), 'query'));
675
+ const r = await withExtensionRounds(() => entryResult(fn(ds.handle, sparql), 'query', sparql));
757
676
  if (r.kind === 'ask') return r.boolean;
758
677
  if (r.kind === 'construct' || r.kind === 'describe') {
759
678
  return c.fromNQuads(r.nquads, ds.prefixes);
@@ -783,10 +702,7 @@ function buildApi(driver) {
783
702
 
784
703
  async function closureOnHandle(c, ds, regime) {
785
704
  const r0 = c.tables().closureAliases[String(regime || '').toLowerCase()];
786
- if (!r0) {
787
- throw new TypeError(
788
- `closure: regime must be one of rdfs, owl-rl, rho-df, rdfs-plus (got '${regime}')`);
789
- }
705
+ if (!r0) checkOptions(c.e, 'Dataset.closure', { regime: String(regime == null ? '' : regime) });
790
706
  requireEntryFn(c.e, 'datasetClosure', 'closure');
791
707
  const r = entryResult(c.e.datasetClosure(ds.handle, r0), 'closure');
792
708
  return c.wrap(r, ds.prefixes);
@@ -797,7 +713,7 @@ function buildApi(driver) {
797
713
 
798
714
  /**
799
715
  * Parse one RDF document into a Dataset. Strict by default: a syntax
800
- * error rejects with a ParseError (`line`/`column`/`offset` for Turtle,
716
+ * error rejects with a FactoidalError (`code`, `position`, `spec`; for Turtle,
801
717
  * TriG, N-Triples and N-Quads). `{lenient:true}` skips each bad statement
802
718
  * of Turtle, TriG, N-Triples and N-Quads and lists one diagnostic per
803
719
  * skipped statement in `dataset.diagnostics`
@@ -811,8 +727,7 @@ function buildApi(driver) {
811
727
  */
812
728
  async function parse(text, options) {
813
729
  if (typeof text !== 'string') throw new TypeError('parse: text must be a string');
814
- const opts = checkOptions('parse', optionsOf('parse', options),
815
- ['format', 'baseIRI', 'lenient', 'maxErrors']);
730
+ const opts = await checkedOptions('parse', optionsOf('parse', options));
816
731
  const c = await ctx();
817
732
  if (opts.lenient) {
818
733
  const { tag, inferred } = formatTagOf(c, text, opts.format, 'parse');
@@ -840,7 +755,7 @@ function buildApi(driver) {
840
755
  * @returns {Promise<Dataset>}
841
756
  */
842
757
  async function openDataset(data, options) {
843
- const opts = checkOptions('openDataset', optionsOf('openDataset', options), ['format', 'baseIRI']);
758
+ const opts = await checkedOptions('openDataset', optionsOf('openDataset', options));
844
759
  const c = await ctx();
845
760
  if (data instanceof Dataset) return data.clone();
846
761
  return datasetOf(c, data, opts, 'openDataset').ds;
@@ -860,8 +775,7 @@ function buildApi(driver) {
860
775
  */
861
776
  async function query(data, sparql, options) {
862
777
  if (typeof sparql !== 'string') throw new TypeError('query: sparql must be a string');
863
- const opts = checkOptions('query', optionsOf('query', options),
864
- ['format', 'baseIRI', 'entail', 'sparql12', 'version', 'output']);
778
+ const opts = await checkedOptions('query', optionsOf('query', options));
865
779
  return withDataset(data, opts, 'query', (c, ds) => queryDatasetHandle(c, ds, sparql, opts));
866
780
  }
867
781
 
@@ -894,7 +808,7 @@ function buildApi(driver) {
894
808
  */
895
809
  async function update(data, updateText, options) {
896
810
  if (typeof updateText !== 'string') throw new TypeError('update: updateText must be a string');
897
- const opts = checkOptions('update', optionsOf('update', options), ['format', 'baseIRI']);
811
+ const opts = await checkedOptions('update', optionsOf('update', options));
898
812
  const c = await ctx();
899
813
  const { ds, temp } = datasetOf(c, data, opts, 'update');
900
814
  const out = temp ? ds : ds.clone();
@@ -918,15 +832,8 @@ function buildApi(driver) {
918
832
  * @returns {Promise<string>}
919
833
  */
920
834
  async function serialize(data, options) {
921
- const opts = checkOptions('serialize', optionsOf('serialize', options),
922
- ['format', 'version', 'prefixes', 'literalShorthand', 'useNativeTypes',
923
- 'useRdfType', 'rdfDirection', 'inputFormat', 'baseIRI']);
835
+ const opts = await checkedOptions('serialize', optionsOf('serialize', options));
924
836
  const { inputFormat, baseIRI, ...writeOpts } = opts;
925
- const writerTags = (await ctx()).tables().writerFormats;
926
- if (writeOpts.format !== undefined && !writerTags[String(writeOpts.format).toLowerCase()]) {
927
- throw new TypeError(`serialize: unknown format '${writeOpts.format}'. Known formats: ` +
928
- [...new Set(Object.values(writerTags))].join(', '));
929
- }
930
837
  return withDataset(data, { format: inputFormat, baseIRI }, 'serialize',
931
838
  (_c, ds) => ds.serialize(writeOpts));
932
839
  }
@@ -938,7 +845,7 @@ function buildApi(driver) {
938
845
  * @returns {Promise<string>}
939
846
  */
940
847
  async function canonicalize(data, options) {
941
- const opts = checkOptions('canonicalize', optionsOf('canonicalize', options), ['format', 'baseIRI']);
848
+ const opts = await checkedOptions('canonicalize', optionsOf('canonicalize', options));
942
849
  return withDataset(data, opts, 'canonicalize', (_c, ds) => ds.toString('rdfc'));
943
850
  }
944
851
 
@@ -994,7 +901,7 @@ function buildApi(driver) {
994
901
  * @returns {Promise<boolean>}
995
902
  */
996
903
  async function isomorphic(a, b, options) {
997
- const opts = checkOptions('isomorphic', optionsOf('isomorphic', options), ['format', 'baseIRI']);
904
+ const opts = await checkedOptions('isomorphic', optionsOf('isomorphic', options));
998
905
  return withDataset(a, opts, 'isomorphic', (c, da) =>
999
906
  withDataset(b, opts, 'isomorphic', (_c, db) => {
1000
907
  requireEntryFn(c.e, 'datasetIsomorphic', 'isomorphic');
@@ -1040,7 +947,7 @@ function buildApi(driver) {
1040
947
  * @returns {Promise<{conforms: boolean, report: Dataset}>}
1041
948
  */
1042
949
  async function shaclValidate(data, shapes, options) {
1043
- const opts = checkOptions('shaclValidate', optionsOf('shaclValidate', options), ['format', 'baseIRI']);
950
+ const opts = await checkedOptions('shaclValidate', optionsOf('shaclValidate', options));
1044
951
  return withDataset(data, opts, 'shaclValidate(data)', (c, ds) => shaclOnHandle(c, ds, shapes, opts));
1045
952
  }
1046
953
 
@@ -1056,7 +963,7 @@ function buildApi(driver) {
1056
963
  * @returns {Promise<boolean>}
1057
964
  */
1058
965
  async function shexValidate(data, schema, focus, shape, options) {
1059
- const opts = checkOptions('shexValidate', optionsOf('shexValidate', options), ['format', 'baseIRI']);
966
+ const opts = await checkedOptions('shexValidate', optionsOf('shexValidate', options));
1060
967
  if (typeof schema !== 'string') throw new TypeError('shexValidate: schema must be a string');
1061
968
  return withDataset(data, opts, 'shexValidate(data)', (c, ds) => shexOnHandle(c, ds, schema, focus, shape));
1062
969
  }
@@ -1069,7 +976,7 @@ function buildApi(driver) {
1069
976
  * @returns {Promise<Dataset>}
1070
977
  */
1071
978
  async function closure(data, regime, options) {
1072
- const opts = checkOptions('closure', optionsOf('closure', options), ['format', 'baseIRI']);
979
+ const opts = await checkedOptions('closure', optionsOf('closure', options));
1073
980
  return withDataset(data, opts, 'closure', (c, ds) => closureOnHandle(c, ds, regime));
1074
981
  }
1075
982
 
@@ -1082,10 +989,8 @@ function buildApi(driver) {
1082
989
  */
1083
990
  async function owlClosure(data, mode, options) {
1084
991
  const m = String(mode || '').toLowerCase();
1085
- const aliases = (await ctx()).tables().closureAliases;
1086
- if (!['rdfs', 'owl-rl'].includes(aliases[m])) {
1087
- throw new TypeError(`owlClosure: mode must be 'RDFS' or 'OWL-RL' (got '${mode}')`);
1088
- }
992
+ // The engine checks the mode against its regime table (op apiCheck).
993
+ await checkedOptions('owlClosure', { mode: String(mode == null ? '' : mode) });
1089
994
  return closure(data, m, options);
1090
995
  }
1091
996
 
@@ -1109,7 +1014,7 @@ function buildApi(driver) {
1109
1014
  * @returns {Promise<{ok: boolean, fragment: boolean}>}
1110
1015
  */
1111
1016
  async function coreRdfsCheck(data, options) {
1112
- const opts = checkOptions('coreRdfsCheck', optionsOf('coreRdfsCheck', options), ['format', 'baseIRI']);
1017
+ const opts = await checkedOptions('coreRdfsCheck', optionsOf('coreRdfsCheck', options));
1113
1018
  const nq = await nquadsOf(data, opts, 'coreRdfsCheck(data)');
1114
1019
  const e = await entry();
1115
1020
  requireEntryFn(e, 'rhoDfFragmentCheck', 'core-RDFS fragment check');
@@ -1137,7 +1042,7 @@ function buildApi(driver) {
1137
1042
  * @returns {Promise<{dataset: Dataset, addedCount: number, budgetHit?: boolean}>}
1138
1043
  */
1139
1044
  async function tableauMaterialise(data, options) {
1140
- const opts = checkOptions('tableauMaterialise', optionsOf('tableauMaterialise', options), ['format', 'baseIRI']);
1045
+ const opts = await checkedOptions('tableauMaterialise', optionsOf('tableauMaterialise', options));
1141
1046
  const nq = await nquadsOf(data, opts, 'tableauMaterialise(data)');
1142
1047
  const c = await ctx();
1143
1048
  requireEntryFn(c.e, 'tableauMaterialise', 'OWL tableau materialisation');
@@ -1157,7 +1062,7 @@ function buildApi(driver) {
1157
1062
  * @returns {Promise<{inconsistent: boolean, rlAlone: boolean}>}
1158
1063
  */
1159
1064
  async function tableauDlInconsistent(data, options) {
1160
- const opts = checkOptions('tableauDlInconsistent', optionsOf('tableauDlInconsistent', options), ['format', 'baseIRI']);
1065
+ const opts = await checkedOptions('tableauDlInconsistent', optionsOf('tableauDlInconsistent', options));
1161
1066
  const nq = await nquadsOf(data, opts, 'tableauDlInconsistent(data)');
1162
1067
  const e = await entry();
1163
1068
  requireEntryFn(e, 'tableauDlInconsistent', 'OWL tableau DL inconsistency check');
@@ -1166,14 +1071,15 @@ function buildApi(driver) {
1166
1071
  }
1167
1072
 
1168
1073
  /**
1169
- * OWL DL consistency: `consistent` is true, false, or null (the refuter's
1170
- * budget ran out; `reason` names the cap).
1074
+ * Checked atomic OWL consistency of the default graph: false is a checked
1075
+ * contradiction; null means unsupported syntax or no accepted refutation.
1076
+ * No model producer exists. fuel does not change the fixed checker bound.
1171
1077
  * @param {Dataset|string|Array} data
1172
1078
  * @param {{format?: string, baseIRI?: string, fuel?: number|string}} [options]
1173
1079
  * @returns {Promise<{consistent: boolean|null, reason?: string}>}
1174
1080
  */
1175
1081
  async function owlIsConsistent(data, options) {
1176
- const opts = checkOptions('owlIsConsistent', optionsOf('owlIsConsistent', options), ['format', 'baseIRI', 'fuel']);
1082
+ const opts = await checkedOptions('owlIsConsistent', optionsOf('owlIsConsistent', options));
1177
1083
  const nq = await nquadsOf(data, opts, 'owlIsConsistent(data)');
1178
1084
  const e = await entry();
1179
1085
  requireEntryFn(e, 'owlIsConsistent', 'OWL DL consistency check');
@@ -1193,7 +1099,7 @@ function buildApi(driver) {
1193
1099
  * @returns {Promise<{entailed: boolean|null, via: 'closure'|'refutation', reason?: string}>}
1194
1100
  */
1195
1101
  async function owlEntails(premise, conclusion, options) {
1196
- const opts = checkOptions('owlEntails', optionsOf('owlEntails', options), ['format', 'baseIRI', 'fuel']);
1102
+ const opts = await checkedOptions('owlEntails', optionsOf('owlEntails', options));
1197
1103
  const premiseNq = await nquadsOf(premise, opts, 'owlEntails(premise)');
1198
1104
  const conclusionNq = await nquadsOf(conclusion, opts, 'owlEntails(conclusion)');
1199
1105
  const e = await entry();
@@ -1217,8 +1123,8 @@ function buildApi(driver) {
1217
1123
  async function rmlMap(mapping, sourceData, sourceKind, options) {
1218
1124
  if (typeof sourceData !== 'string') throw new TypeError('rmlMap: sourceData must be a string');
1219
1125
  const kind = String(sourceKind || '').toLowerCase();
1220
- if (kind !== 'json' && kind !== 'csv') throw new TypeError("rmlMap: sourceKind must be 'json' or 'csv'");
1221
- const opts = checkOptions('rmlMap', optionsOf('rmlMap', options), ['format', 'baseIRI']);
1126
+ await checkedOptions('rmlMap', { sourceKind: kind });
1127
+ const opts = await checkedOptions('rmlMap', optionsOf('rmlMap', options));
1222
1128
  return withDataset(mapping, opts, 'rmlMap(mapping)', (c, ds) => {
1223
1129
  requireEntryFn(c.e, 'datasetFromRml', 'RML mapping evaluation');
1224
1130
  return c.wrap(entryResult(c.e.datasetFromRml(ds.handle, sourceData, kind), 'rmlMap'), {});
@@ -1238,7 +1144,7 @@ function buildApi(driver) {
1238
1144
  if (typeof csvText !== 'string') throw new TypeError('csvwToRdf: csvText must be a string');
1239
1145
  const meta = metadataJson == null ? '' : metadataJson;
1240
1146
  if (typeof meta !== 'string') throw new TypeError('csvwToRdf: metadataJson must be a string');
1241
- const opts = checkOptions('csvwToRdf', options, ['mode', 'base', 'url']);
1147
+ const opts = await checkedOptions('csvwToRdf', options);
1242
1148
  const c = await ctx();
1243
1149
  requireEntryFn(c.e, 'datasetFromCsvw', 'CSVW csv2rdf conversion');
1244
1150
  const optionsJson = JSON.stringify({
@@ -1259,7 +1165,7 @@ function buildApi(driver) {
1259
1165
  */
1260
1166
  async function jsonldToRdf(jsonldText, options) {
1261
1167
  if (typeof jsonldText !== 'string') throw new TypeError('jsonldToRdf: jsonldText must be a string');
1262
- const opts = checkOptions('jsonldToRdf', options, ['base', 'rdfDirection', 'expandContext', 'processingMode']);
1168
+ const opts = await checkedOptions('jsonldToRdf', options);
1263
1169
  const c = await ctx();
1264
1170
  requireEntryFn(c.e, 'datasetFromJsonLd', 'jsonldToRdf');
1265
1171
  const optionsJson = JSON.stringify({
@@ -1282,7 +1188,7 @@ function buildApi(driver) {
1282
1188
  */
1283
1189
  async function rifEval(data, rifRulesXml, options) {
1284
1190
  if (typeof rifRulesXml !== 'string') throw new TypeError('rifEval: rifRulesXml must be a string');
1285
- const opts = checkOptions('rifEval', optionsOf('rifEval', options), ['format', 'baseIRI']);
1191
+ const opts = await checkedOptions('rifEval', optionsOf('rifEval', options));
1286
1192
  return withDataset(data, opts, 'rifEval(data)', (c, ds) => {
1287
1193
  requireEntryFn(c.e, 'datasetRifEval', 'RIF Core evaluation');
1288
1194
  return c.wrap(entryResult(c.e.datasetRifEval(rifRulesXml, ds.handle), 'rifEval'), ds.prefixes);
@@ -1298,8 +1204,7 @@ function buildApi(driver) {
1298
1204
  * @returns {Promise<any>}
1299
1205
  */
1300
1206
  async function jsonldFromRdf(data, options) {
1301
- const opts = checkOptions('jsonldFromRdf', optionsOf('jsonldFromRdf', options),
1302
- ['useNativeTypes', 'useRdfType', 'rdfDirection', 'format', 'baseIRI']);
1207
+ const opts = await checkedOptions('jsonldFromRdf', optionsOf('jsonldFromRdf', options));
1303
1208
  const { format, baseIRI, ...w } = opts;
1304
1209
  return withDataset(data, { format, baseIRI }, 'jsonldFromRdf(data)',
1305
1210
  (_c, ds) => JSON.parse(ds.toString({ format: 'jsonld', ...w })));
@@ -2012,29 +1917,16 @@ function buildApi(driver) {
2012
1917
  if (typeof queryString !== 'string') {
2013
1918
  throw new TypeError('queryRaw: queryString must be a string');
2014
1919
  }
2015
- const opts = checkOptions('queryRaw', options, ['dataFormat', 'entail', 'output']);
1920
+ const opts = await checkedOptions('queryRaw', options);
2016
1921
  const dataFormat = String(opts.dataFormat || 'turtle').toLowerCase();
2017
1922
  const entail = opts.entail || 'none';
2018
1923
  const output = opts.output || 'json';
2019
1924
  const tables = (await ctx()).tables();
2020
- if (!(dataFormat in tables.rawDataFormats)) {
2021
- throw new TypeError(`queryRaw: unknown dataFormat '${opts.dataFormat}'`);
2022
- }
2023
- if (!tables.entailRegimes.includes(entail)) {
2024
- throw new TypeError(`queryRaw: entail must be one of ${tables.entailRegimes.join(', ')}`);
2025
- }
2026
- if (!tables.queryOutputs.includes(output)) {
2027
- throw new TypeError(`queryRaw: output must be one of ${tables.queryOutputs.join(', ')}`);
2028
- }
2029
1925
  const e = await entry();
2030
1926
  requireEntryFn(e, 'queryRaw', 'queryRaw');
2031
- const r = JSON.parse(e.queryRaw(dataString, queryString,
2032
- JSON.stringify({ dataFormat: tables.rawDataFormats[dataFormat], entail, output })));
2033
- if (!r.ok) {
2034
- const err = new Error('SPARQL query failed: ' + r.error);
2035
- err.stderr = String(r.error);
2036
- throw err;
2037
- }
1927
+ const r = entryResult(e.queryRaw(dataString, queryString,
1928
+ JSON.stringify({ dataFormat: tables.rawDataFormats[dataFormat], entail, output })),
1929
+ 'queryRaw', queryString);
2038
1930
  return output === 'json' ? JSON.parse(r.text) : r.text;
2039
1931
  }
2040
1932
 
@@ -2318,9 +2210,8 @@ function buildApi(driver) {
2318
2210
  ready,
2319
2211
  engineTables,
2320
2212
  Dataset,
2321
- DatasetHandle,
2322
2213
  Store,
2323
- ParseError,
2214
+ FactoidalError,
2324
2215
  dataFactory,
2325
2216
  // Metadata (issue #682): the same two fields on every driver's
2326
2217
  // typed surface, so a caller can log/report which package version
@@ -2333,5 +2224,5 @@ function buildApi(driver) {
2333
2224
 
2334
2225
  export {
2335
2226
  buildApi, bindingsFromSrj, entryResult, parseResult,
2336
- utf16OffsetOf, missingOpError, ParseError, DatasetHandle, Store,
2227
+ missingOpError, FactoidalError, Store,
2337
2228
  };