|
1 |
| -# jsonldjs |
| 1 | +# `jsonldjs` |
| 2 | + |
| 3 | +A powerful, generic JSON-LD builder with comprehensive entity and property filtering capabilities. Provides both immutable configuration building and mutable graph processing with a fluent interface. |
| 4 | + |
| 5 | +## Installation |
| 6 | + |
| 7 | +```bash |
| 8 | +# Using pnpm (recommended) |
| 9 | +pnpm add jsonldjs |
| 10 | + |
| 11 | +# Using npm |
| 12 | +npm install jsonldjs |
| 13 | + |
| 14 | +# Using yarn |
| 15 | +yarn add jsonldjs |
| 16 | +``` |
| 17 | + |
| 18 | +## Features |
| 19 | + |
| 20 | +- **Configuration-First Design**: Separate immutable configuration from graph processing for maximum flexibility |
| 21 | +- **Fluent Interface**: Chainable methods for building complex filtering logic |
| 22 | +- **Property-Level Filtering**: Filter properties by entity IDs or types |
| 23 | +- **Subgraph Extraction**: Extract connected subgraphs with reference following |
| 24 | +- **Runtime Overrides**: Apply configuration and then override at runtime |
| 25 | +- **Type Safety**: Full TypeScript support with proper type inference |
| 26 | +- **Extensibility**: Custom pipes and transformation functions |
| 27 | + |
| 28 | +## Quick Start |
| 29 | + |
| 30 | +### Basic Usage |
| 31 | + |
| 32 | +```typescript |
| 33 | +import { createJsonLdBuilder } from 'jsonldjs'; |
| 34 | +import { jsonldGraph } from '@/data/jsonld'; |
| 35 | + |
| 36 | +// Simple filtering |
| 37 | +const result = createJsonLdBuilder() |
| 38 | + .baseGraph(jsonldGraph) |
| 39 | + .includeTypes(['Organization', 'Person']) |
| 40 | + .excludeTypes(['ImageObject']) |
| 41 | + .maxEntities(10) |
| 42 | + .build({ |
| 43 | + prettyPrint: true, |
| 44 | + withScriptTag: true, |
| 45 | + scriptId: 'json-ld', |
| 46 | + }); |
| 47 | +``` |
| 48 | + |
| 49 | +### Configuration-First Approach |
| 50 | + |
| 51 | +```typescript |
| 52 | +import { createJsonLdBuilder, createJsonLdConfig } from 'jsonldjs'; |
| 53 | + |
| 54 | +// Create reusable configurations |
| 55 | +const globalConfig = createJsonLdConfig() |
| 56 | + .baseGraph(jsonldGraph) |
| 57 | + .includeIds(['org:hyperweb', 'website:hyperweb.io']) |
| 58 | + .filterPropertiesByIds(['org:hyperweb'], { exclude: ['subjectOf'] }); |
| 59 | + |
| 60 | +// Extend configurations immutably |
| 61 | +const homeConfig = globalConfig.excludeTypes(['ImageObject']); |
| 62 | +const blogConfig = globalConfig.includeTypes(['Article']); |
| 63 | + |
| 64 | +// Use configurations |
| 65 | +const result = createJsonLdBuilder() |
| 66 | + .applyConfig(homeConfig) |
| 67 | + .excludeIds(['runtime:override']) // Runtime overrides |
| 68 | + .build({ prettyPrint: true }); |
| 69 | +``` |
| 70 | + |
| 71 | +## Configuration Merging Behavior |
| 72 | + |
| 73 | +### Default Merging |
| 74 | + |
| 75 | +All configuration methods **merge by default** instead of replacing. This provides predictable behavior across all methods: |
| 76 | + |
| 77 | +```typescript |
| 78 | +const config = createJsonLdConfig() |
| 79 | + .includeIds(['a', 'b']) |
| 80 | + .includeIds(['c', 'd']) // Result: ['a', 'b', 'c', 'd'] |
| 81 | + .includeTypes(['Person']) |
| 82 | + .includeTypes(['Organization']); // Result: ['Person', 'Organization'] |
| 83 | +``` |
| 84 | + |
| 85 | +### Clear Methods |
| 86 | + |
| 87 | +When you need to replace instead of merge, use the clear methods: |
| 88 | + |
| 89 | +```typescript |
| 90 | +const config = createJsonLdConfig() |
| 91 | + .includeIds(['old1', 'old2']) |
| 92 | + .clearIds() // Clear both includeIds and excludeIds |
| 93 | + .includeIds(['new1', 'new2']); // Result: ['new1', 'new2'] |
| 94 | +``` |
| 95 | + |
| 96 | +#### Available Clear Methods |
| 97 | + |
| 98 | +- `clearIds()` - Clears both `includeIds` and `excludeIds` |
| 99 | +- `clearTypes()` - Clears both `includeTypes` and `excludeTypes` |
| 100 | +- `clearPropertyRequirements()` - Clears both `requiredProperties` and `excludeEntitiesWithProperties` |
| 101 | +- `clearPropertyFilters()` - Clears both `propertyFiltersByIds` and `propertyFiltersByTypes` |
| 102 | +- `clearSubgraph()` - Clears `subgraphRoots` |
| 103 | +- `clearAll()` - Clears entire configuration (except `baseGraph`) |
| 104 | + |
| 105 | +## API Reference |
| 106 | + |
| 107 | +### Factory Functions |
| 108 | + |
| 109 | +#### `createJsonLdConfig()` |
| 110 | + |
| 111 | +Creates a new immutable configuration builder. |
| 112 | + |
| 113 | +```typescript |
| 114 | +const config = createJsonLdConfig() |
| 115 | + .baseGraph(graph) |
| 116 | + .includeIds(['org:hyperweb']) |
| 117 | + .excludeTypes(['ImageObject']); |
| 118 | +``` |
| 119 | + |
| 120 | +#### `createJsonLdBuilder()` |
| 121 | + |
| 122 | +Creates a new builder that extends the configuration builder with graph processing capabilities. |
| 123 | + |
| 124 | +```typescript |
| 125 | +const builder = createJsonLdBuilder().baseGraph(graph).applyConfig(config); |
| 126 | +``` |
| 127 | + |
| 128 | +### Configuration Methods |
| 129 | + |
| 130 | +All methods are inherited by the builder from the configuration builder: |
| 131 | + |
| 132 | +#### Entity Filtering |
| 133 | + |
| 134 | +- `.includeIds(ids: string[])` - Include entities with these IDs (merges with existing) |
| 135 | +- `.excludeIds(ids: string[])` - Exclude entities with these IDs (merges with existing) |
| 136 | +- `.includeTypes(types: string[])` - Include these entity types (merges with existing) |
| 137 | +- `.excludeTypes(types: string[])` - Exclude these entity types (merges with existing) |
| 138 | +- `.customFilter(fn: JsonLdFilter)` - Apply custom filter function |
| 139 | +- `.maxEntities(max: number)` - Limit maximum number of entities |
| 140 | +- `.requiredProperties(props: string[])` - Include entities with these properties (merges with existing) |
| 141 | +- `.excludeEntitiesWithProperties(props: string[])` - Exclude entities with these properties (merges with existing) |
| 142 | + |
| 143 | +#### Clear Methods |
| 144 | + |
| 145 | +- `.clearIds()` - Clear both includeIds and excludeIds |
| 146 | +- `.clearTypes()` - Clear both includeTypes and excludeTypes |
| 147 | +- `.clearPropertyRequirements()` - Clear both requiredProperties and excludeEntitiesWithProperties |
| 148 | +- `.clearPropertyFilters()` - Clear both propertyFiltersByIds and propertyFiltersByTypes |
| 149 | +- `.clearSubgraph()` - Clear subgraphRoots |
| 150 | +- `.clearAll()` - Clear entire configuration (except baseGraph) |
| 151 | + |
| 152 | +#### Property Filtering |
| 153 | + |
| 154 | +- `.filterPropertiesByIds(entityIds, rule)` - Filter properties for specific entity IDs |
| 155 | +- `.filterPropertiesByTypes(entityTypes, rule)` - Filter properties for specific entity types |
| 156 | + |
| 157 | +```typescript |
| 158 | +// Filter properties by entity ID |
| 159 | +.filterPropertiesByIds(['org:hyperweb'], { |
| 160 | + exclude: ['subjectOf', 'member'] |
| 161 | +}) |
| 162 | + |
| 163 | +// Filter properties by entity type |
| 164 | +.filterPropertiesByTypes(['Article'], { |
| 165 | + include: ['headline', 'author', 'datePublished'] |
| 166 | +}) |
| 167 | +``` |
| 168 | + |
| 169 | +#### Graph Operations |
| 170 | + |
| 171 | +- `.baseGraph(graph: JsonLdGraph)` - Set the base graph to process |
| 172 | +- `.subgraph(rootIds: string[])` - Extract subgraph starting from these root IDs |
| 173 | +- `.addEntities(entities: JsonLdEntity[])` - Add additional entities |
| 174 | +- `.pipe(fn: PipeFunction)` - Add custom transformation function |
| 175 | + |
| 176 | +#### Builder-Only Methods |
| 177 | + |
| 178 | +- `.applyConfig(config: JsonLdConfig)` - Apply a pre-built configuration |
| 179 | +- `.getCurrentGraph()` - Get the current graph state |
| 180 | +- `.build(options?: BuildOptions)` - Build the final JSON-LD output |
| 181 | + |
| 182 | +### Build Options |
| 183 | + |
| 184 | +```typescript |
| 185 | +interface BuildOptions { |
| 186 | + prettyPrint?: boolean; // Pretty-print JSON output (default: true) |
| 187 | + contextUrl?: string; // Custom context URL (default: 'https://schema.org') |
| 188 | + withScriptTag?: boolean; // Wrap in script tag (default: false) |
| 189 | + scriptId?: string; // Script tag ID |
| 190 | +} |
| 191 | +``` |
| 192 | + |
| 193 | +## Advanced Usage |
| 194 | + |
| 195 | +### Complex Filtering Logic |
| 196 | + |
| 197 | +The builder implements three distinct filtering paths based on configuration: |
| 198 | + |
| 199 | +1. **Subgraph Mode**: When `subgraph()` is used, property filtering is applied during traversal |
| 200 | +2. **IncludeIds Mode**: When `includeIds()` is used, entities are filtered first, then additional filters applied |
| 201 | +3. **Global Mode**: Property filtering is applied first, then entity filtering |
| 202 | + |
| 203 | +```typescript |
| 204 | +// Subgraph mode - follows references with property filtering |
| 205 | +const result = createJsonLdBuilder() |
| 206 | + .baseGraph(graph) |
| 207 | + .subgraph(['org:hyperweb']) |
| 208 | + .filterPropertiesByIds(['org:hyperweb'], { exclude: ['subjectOf'] }) |
| 209 | + .build(); |
| 210 | + |
| 211 | +// IncludeIds mode - simple entity filtering |
| 212 | +const result = createJsonLdBuilder() |
| 213 | + .baseGraph(graph) |
| 214 | + .includeIds(['org:hyperweb', 'person:john']) |
| 215 | + .excludeTypes(['ImageObject']) |
| 216 | + .build(); |
| 217 | +``` |
| 218 | + |
| 219 | +### Custom Transformations |
| 220 | + |
| 221 | +```typescript |
| 222 | +const result = createJsonLdBuilder() |
| 223 | + .baseGraph(graph) |
| 224 | + .includeTypes(['Person']) |
| 225 | + .pipe((graph) => |
| 226 | + graph.map((entity) => ({ |
| 227 | + ...entity, |
| 228 | + processed: true, |
| 229 | + })) |
| 230 | + ) |
| 231 | + .pipe((graph) => graph.filter((entity) => entity.name)) |
| 232 | + .build(); |
| 233 | +``` |
| 234 | + |
| 235 | +### Configuration Reuse |
| 236 | + |
| 237 | +```typescript |
| 238 | +// Base configuration |
| 239 | +const baseConfig = createJsonLdConfig() |
| 240 | + .baseGraph(jsonldGraph) |
| 241 | + .filterPropertiesByIds(['org:hyperweb'], { exclude: ['subjectOf'] }); |
| 242 | + |
| 243 | +// Page-specific configurations |
| 244 | +const homeConfig = baseConfig.excludeTypes(['ImageObject']); |
| 245 | +const blogConfig = baseConfig.includeTypes(['Article']); |
| 246 | +const personConfig = baseConfig.includeTypes(['Person', 'Organization']); |
| 247 | + |
| 248 | +// Use with different base graphs |
| 249 | +const articlesConfig = baseConfig.baseGraph(articlesGraph); |
| 250 | +``` |
| 251 | + |
| 252 | +## Options Processing Order |
| 253 | + |
| 254 | +The JSON-LD builder processes options in a specific order defined by the `processGraph` method. Understanding this order is crucial for predicting the final output when multiple filtering options are applied. |
| 255 | + |
| 256 | +### Processing Layers |
| 257 | + |
| 258 | +The builder processes options in the following sequential layers: |
| 259 | + |
| 260 | +#### 1. **Configuration Validation** |
| 261 | + |
| 262 | +- Validates that a base graph is provided |
| 263 | +- Checks for critical configuration errors that would break processing |
| 264 | +- Throws errors if validation fails |
| 265 | + |
| 266 | +#### 2. **Subgraph Extraction** (Layer 1) |
| 267 | + |
| 268 | +- **Condition**: Only when `subgraphRoots` are configured via `.subgraph(rootIds)` |
| 269 | +- **Process**: Extracts connected subgraphs starting from root entities |
| 270 | +- **Property Filtering**: Applied **during** subgraph traversal for optimal performance |
| 271 | +- **Result**: Property filters are marked as applied to avoid duplicate processing |
| 272 | + |
| 273 | +```typescript |
| 274 | +// Example: Subgraph extraction with property filtering |
| 275 | +const result = createJsonLdBuilder() |
| 276 | + .baseGraph(graph) |
| 277 | + .subgraph(['org:hyperweb']) // Layer 1: Extract subgraph |
| 278 | + .filterPropertiesByIds(['org:hyperweb'], { exclude: ['subjectOf'] }) // Applied during traversal |
| 279 | + .build(); |
| 280 | +``` |
| 281 | + |
| 282 | +#### 3. **Entity and Property Filtering** (Layer 2) |
| 283 | + |
| 284 | +- **Condition**: When entity filters are configured (includeIds, excludeIds, includeTypes, etc.) |
| 285 | +- **Sub-step 3a - Property Filtering**: Applied first if not already done in Layer 1 |
| 286 | + - Filters properties based on entity IDs or types |
| 287 | + - Uses `filterGraphProperties()` function |
| 288 | +- **Sub-step 3b - Entity Filtering**: Applied after property filtering |
| 289 | + - Filters entire entities based on ID, type, and other criteria |
| 290 | + - Uses `filterJsonLdGraph()` function |
| 291 | + |
| 292 | +```typescript |
| 293 | +// Example: Property filtering followed by entity filtering |
| 294 | +const result = createJsonLdBuilder() |
| 295 | + .baseGraph(graph) |
| 296 | + .filterPropertiesByTypes(['Article'], { include: ['headline', 'author'] }) // 3a: Property filtering |
| 297 | + .includeTypes(['Article', 'Person']) // 3b: Entity filtering |
| 298 | + .excludeIds(['unwanted:id']) // 3b: Additional entity filtering |
| 299 | + .build(); |
| 300 | +``` |
| 301 | + |
| 302 | +#### 4. **Entity Population** |
| 303 | + |
| 304 | +- **Condition**: When `populateConfig` is set |
| 305 | +- **Process**: Applies population rules to add related entities |
| 306 | +- **Function**: Uses `applyPopulateConfig()` |
| 307 | + |
| 308 | +#### 5. **Additional Entities** |
| 309 | + |
| 310 | +- **Condition**: When additional entities are specified via `.addEntities()` |
| 311 | +- **Process**: Appends additional entities to the graph |
| 312 | +- **Note**: These entities bypass all previous filtering |
| 313 | + |
| 314 | +#### 6. **Custom Transformation Pipes** |
| 315 | + |
| 316 | +- **Condition**: When custom pipes are added via `.pipe(fn)` |
| 317 | +- **Process**: Applies custom transformation functions in the order they were added |
| 318 | +- **Note**: This is the final processing step before output |
| 319 | + |
| 320 | +```typescript |
| 321 | +// Example: Custom pipes applied last |
| 322 | +const result = createJsonLdBuilder() |
| 323 | + .baseGraph(graph) |
| 324 | + .includeTypes(['Person']) |
| 325 | + .pipe((graph) => graph.map((entity) => ({ ...entity, processed: true }))) // Applied last |
| 326 | + .pipe((graph) => graph.filter((entity) => entity.name)) // Applied after previous pipe |
| 327 | + .build(); |
| 328 | +``` |
| 329 | + |
| 330 | +### Key Processing Rules |
| 331 | + |
| 332 | +1. **Property Filters Before Entity Filters**: Property filtering always happens before entity filtering (except in subgraph mode where they're combined) |
| 333 | + |
| 334 | +2. **Subgraph Mode Optimization**: When using subgraphs, property filtering is applied during traversal for better performance |
| 335 | + |
| 336 | +3. **Single Property Filter Application**: Property filters are only applied once to avoid duplicate processing |
| 337 | + |
| 338 | +4. **Additive Additional Entities**: Entities added via `.addEntities()` are appended after all filtering |
| 339 | + |
| 340 | +5. **Sequential Pipe Execution**: Custom pipes are executed in the order they were added |
| 341 | + |
| 342 | +## Performance Considerations |
| 343 | + |
| 344 | +- **Immutable Configurations**: Each configuration method returns a new object, enabling safe reuse |
| 345 | +- **Lazy Evaluation**: Graph processing only occurs when `build()` or `getCurrentGraph()` is called |
| 346 | +- **Efficient Filtering**: Uses optimized filtering paths based on configuration type |
| 347 | +- **Memory Management**: Avoids unnecessary intermediate copies of large graphs |
| 348 | + |
| 349 | +## Development |
| 350 | + |
| 351 | +### Prerequisites |
| 352 | + |
| 353 | +- Node.js 16+ |
| 354 | +- pnpm (recommended package manager) |
| 355 | + |
| 356 | +### Setup |
| 357 | + |
| 358 | +1. Clone the repository: |
| 359 | + |
| 360 | +```bash |
| 361 | +git clone https://github.com/hyperweb-io/jsonld-tools.git |
| 362 | +cd jsonld-tools |
| 363 | +``` |
| 364 | + |
| 365 | +2. Install dependencies: |
| 366 | + |
| 367 | +```bash |
| 368 | +pnpm install |
| 369 | +``` |
| 370 | + |
| 371 | +3. Build the project: |
| 372 | + |
| 373 | +```bash |
| 374 | +pnpm run build |
| 375 | +``` |
0 commit comments