glossarist 0.4.14 → 0.4.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glossarist",
3
- "version": "0.4.14",
3
+ "version": "0.4.15",
4
4
  "description": "JavaScript SDK for Glossarist GCR packages — read, write, validate, and manage terminology concepts",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -42,6 +42,11 @@
42
42
  "import": "./src/rdf/index.js",
43
43
  "default": "./src/rdf/index.js"
44
44
  },
45
+ "./rdf/shacl": {
46
+ "types": "./src/rdf/shacl-entry.d.ts",
47
+ "import": "./src/rdf/shacl-entry.js",
48
+ "default": "./src/rdf/shacl-entry.js"
49
+ },
45
50
  "./rdf/prefixes": {
46
51
  "types": "./src/rdf/predicates.d.ts",
47
52
  "import": "./src/rdf/predicates.js",
@@ -6,11 +6,30 @@
6
6
  // strategy: MD5 (via node:crypto) when available, FNV-1a fallback for
7
7
  // browser bundles.
8
8
  //
9
- // Cross-process stability matches glossarist-ruby's deterministic-id
10
- // when node:crypto is available. Within a single process, FNV-1a is
11
- // also stable.
9
+ // Browser safety: node:crypto is loaded lazily via runtime check so
10
+ // bundlers don't tree-shake the import into the browser bundle.
12
11
 
13
- import { createHash } from 'node:crypto';
12
+ let nodeCreateHash = null;
13
+ let nodeCryptoChecked = false;
14
+
15
+ function getNodeCreateHash() {
16
+ if (nodeCryptoChecked) return nodeCreateHash;
17
+ nodeCryptoChecked = true;
18
+ try {
19
+ let requireFn;
20
+ try {
21
+ const mod = (Function('m', 'return require(m)'))('node:module');
22
+ requireFn = mod.createRequire(import.meta.url);
23
+ } catch {
24
+ requireFn = (m) => (Function('m', 'return require(m)'))(m);
25
+ }
26
+ const crypto = requireFn('node:crypto');
27
+ nodeCreateHash = crypto.createHash.bind(crypto);
28
+ } catch {
29
+ nodeCreateHash = null;
30
+ }
31
+ return nodeCreateHash;
32
+ }
14
33
 
15
34
  function fnv1a(seed) {
16
35
  let h = 0x811c9dc5;
@@ -24,12 +43,13 @@ function fnv1a(seed) {
24
43
  export function deterministicBnodeId(...parts) {
25
44
  const seed = parts.filter(p => p != null && p !== '').join('|');
26
45
  if (!seed) return `b${fnv1a('empty')}`;
27
- // MD5 matches glossarist-ruby's deterministic-id for cross-process
28
- // stability. Truncated to 12 hex chars — enough collision resistance
29
- // for bnode IDs within a single document.
30
- try {
31
- return createHash('md5').update(seed).digest('hex').slice(0, 12);
32
- } catch {
33
- return `b${fnv1a(seed)}`;
46
+ const createHash = getNodeCreateHash();
47
+ if (createHash) {
48
+ try {
49
+ return createHash('md5').update(seed).digest('hex').slice(0, 12);
50
+ } catch {
51
+ // fall through to FNV
52
+ }
34
53
  }
54
+ return `b${fnv1a(seed)}`;
35
55
  }
@@ -1,17 +1,71 @@
1
- import { createHash } from 'node:crypto';
1
+ // Stable ID helpers for RDF emission. Mirrors glossarist-ruby's
2
+ // Digest::MD5.hexdigest(content)[0..11] for cross-runtime stability.
3
+ //
4
+ // Browser safety: node:crypto is loaded lazily so bundlers don't
5
+ // tree-shake the import into browser bundles. The runtime check uses
6
+ // `createRequire` from `node:module` (also lazy) which is the
7
+ // documented way to use CommonJS `require` from ESM.
8
+
9
+ let nodeCreateHash = null;
10
+ let nodeCryptoChecked = false;
11
+
12
+ function getNodeCreateHash() {
13
+ if (nodeCryptoChecked) return nodeCreateHash;
14
+ nodeCryptoChecked = true;
15
+ try {
16
+ // Lazy require — works in both CJS and ESM (Node ≥ 12).
17
+ // Wrapped in Function() so bundlers don't statically resolve it.
18
+ let requireFn;
19
+ try {
20
+ // ESM: use createRequire from node:module
21
+ const mod = (Function('m', 'return require(m)'))('node:module');
22
+ requireFn = mod.createRequire(import.meta.url);
23
+ } catch {
24
+ // CJS or already-require-available environment
25
+ requireFn = (m) => (Function('m', 'return require(m)'))(m);
26
+ }
27
+ const crypto = requireFn('node:crypto');
28
+ nodeCreateHash = crypto.createHash.bind(crypto);
29
+ } catch {
30
+ nodeCreateHash = null;
31
+ }
32
+ return nodeCreateHash;
33
+ }
2
34
 
3
35
  // 12-char hex prefix, mirroring glossarist-ruby's
4
36
  // `Digest::MD5.hexdigest(content)[0..11]`. Same content → same ID across
5
- // processes, machines, and language runtimes (Ruby vs JS).
37
+ // processes, machines, and language runtimes (Ruby vs JS) when MD5 is
38
+ // available. Falls back to a 12-char FNV-1a hash in the browser.
6
39
  export function deterministicId(...parts) {
7
40
  const seed = parts.filter(p => p !== null && p !== undefined).join('|');
8
- return createHash('md5').update(seed).digest('hex').slice(0, 12);
41
+ const createHash = getNodeCreateHash();
42
+ if (createHash) {
43
+ return createHash('md5').update(seed).digest('hex').slice(0, 12);
44
+ }
45
+ return fnv1a12(seed);
9
46
  }
10
47
 
11
48
  // Stable blank-node label for an RDF fragment identified by its parent
12
- // subject + role + index. Useful for reified resources like DetailedDefinition,
13
- // Designation, ConceptSource where the bnode ID must be deterministic to
14
- // produce byte-equivalent Turtle across runs.
49
+ // subject + role + index.
15
50
  export function deterministicBnode(subject, role, index) {
16
51
  return `_:b${deterministicId(subject, role, index)}`;
17
52
  }
53
+
54
+ // 12-char FNV-1a hash. Two 32-bit passes concatenated and truncated.
55
+ // Not as collision-resistant as MD5 but adequate for bnode IDs in a
56
+ // single document. Used only when node:crypto is unavailable.
57
+ function fnv1a12(seed) {
58
+ let h1 = 0x811c9dc5;
59
+ for (let i = 0; i < seed.length; i++) {
60
+ h1 ^= seed.charCodeAt(i);
61
+ h1 = Math.imul(h1, 0x01000193);
62
+ }
63
+ const seed2 = `${seed}|fnv2`;
64
+ let h2 = 0x811c9dc5;
65
+ for (let i = 0; i < seed2.length; i++) {
66
+ h2 ^= seed2.charCodeAt(i);
67
+ h2 = Math.imul(h2, 0x01000193);
68
+ }
69
+ return ((h1 >>> 0).toString(16).padStart(8, '0')
70
+ + (h2 >>> 0).toString(16).padStart(8, '0')).slice(0, 12);
71
+ }
@@ -315,7 +315,5 @@ export interface ValidationReport {
315
315
  }>;
316
316
  }
317
317
 
318
- export declare function loadShapes(options?: { shapesPath?: string }): Promise<Dataset>;
319
- export declare function validateShacl(dataDataset: Dataset, options?: { shapes?: Dataset; shapesPath?: string }): Promise<ValidationReport>;
320
- export declare function clearShapesCache(): void;
321
- export declare function quadsToDataset(quads: Iterable<Quad>): Dataset;
318
+ // SHACL validator moved to ./shacl-entry subpath. Importing from
319
+ // 'glossarist/rdf/shacl' in Node-only; 'glossarist/rdf' is browser-safe.
package/src/rdf/index.js CHANGED
@@ -50,6 +50,8 @@ export {
50
50
  sortQuads,
51
51
  } from './document-writer.js';
52
52
  export { writeTurtleSync } from './write-turtle-sync.js';
53
- export { validateShacl, loadShapes, clearShapesCache, quadsToDataset } from './shacl.js';
53
+ // SHACL validator moved to glossarist/rdf/shacl subpath — it depends on
54
+ // node:fs/promises, node:path, node:url at module top-level, which would
55
+ // break browser bundles. Importing 'glossarist/rdf' alone is browser-safe.
54
56
  export { quadSectionsToClassInstances } from './sections-builder.js';
55
57
  export { provenanceToQuads } from './provenance-emitter.js';
@@ -0,0 +1,6 @@
1
+ // Type declarations for the Node-only SHACL validator subpath.
2
+ //
3
+ // Mirrors src/rdf/shacl-entry.js. Separated from ./rdf so that browser
4
+ // bundles don't pull in node:fs/promises, node:path, node:url.
5
+
6
+ export { validateShacl, loadShapes, clearShapesCache, quadsToDataset } from './shacl';
@@ -0,0 +1,9 @@
1
+ // Node-only SHACL validator entry. Separated from glossarist/rdf so that
2
+ // browser bundles can import RDF emitters/writers without pulling in
3
+ // node:fs/promises, node:path, node:url.
4
+ //
5
+ // Usage (Node only):
6
+ // import { validateShacl, loadShapes, clearShapesCache, quadsToDataset }
7
+ // from 'glossarist/rdf/shacl';
8
+
9
+ export { validateShacl, loadShapes, clearShapesCache, quadsToDataset } from './shacl.js';