@soulcraft/brainy 4.1.2 → 4.1.3

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/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [4.1.3](https://github.com/soulcraftlabs/brainy/compare/v4.1.2...v4.1.3) (2025-10-21)
6
+
7
+ - perf: make getRelations() pagination consistent and efficient (54d819c)
8
+ - fix: resolve getRelations() empty array bug and add string ID shorthand (8d217f3)
9
+
10
+
11
+ ### [4.1.3](https://github.com/soulcraftlabs/brainy/compare/v4.1.2...v4.1.3) (2025-10-21)
12
+
13
+
14
+ ### 🐛 Bug Fixes
15
+
16
+ * **api**: fix getRelations() returning empty array when called without parameters
17
+ - Fixed critical bug where `brain.getRelations()` returned `[]` instead of all relationships
18
+ - Added support for retrieving all relationships with pagination (default limit: 100)
19
+ - Added string ID shorthand syntax: `brain.getRelations(entityId)` as alias for `brain.getRelations({ from: entityId })`
20
+ - **Performance**: Made pagination consistent - now ALL query patterns paginate at storage layer
21
+ - **Efficiency**: `getRelations({ from: id, limit: 10 })` now fetches only 10 instead of fetching ALL then slicing
22
+ - Fixed storage.getVerbs() offset handling - now properly converts offset to cursor for adapters
23
+ - Production safety: Warns when fetching >10k relationships without filters
24
+ - Fixed broken method calls in improvedNeuralAPI.ts (replaced non-existent `getVerbsForNoun` with `getRelations`)
25
+ - Fixed property access bugs: `verb.target` → `verb.to`, `verb.verb` → `verb.type`
26
+ - Added comprehensive integration tests (14 tests covering all query patterns)
27
+ - Updated JSDoc documentation with usage examples
28
+ - **Impact**: Resolves Workshop team bug where 524 imported relationships were inaccessible
29
+ - **Breaking**: None - fully backward compatible
30
+
5
31
  ### [4.1.2](https://github.com/soulcraftlabs/brainy/compare/v4.1.1...v4.1.2) (2025-10-21)
6
32
 
7
33
 
package/dist/brainy.d.ts CHANGED
@@ -314,9 +314,38 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
314
314
  */
315
315
  unrelate(id: string): Promise<void>;
316
316
  /**
317
- * Get relationships
317
+ * Get relationships between entities
318
+ *
319
+ * Supports multiple query patterns:
320
+ * - No parameters: Returns all relationships (paginated, default limit: 100)
321
+ * - String ID: Returns relationships from that entity (shorthand for { from: id })
322
+ * - Parameters object: Fine-grained filtering and pagination
323
+ *
324
+ * @param paramsOrId - Optional string ID or parameters object
325
+ * @returns Promise resolving to array of relationships
326
+ *
327
+ * @example
328
+ * ```typescript
329
+ * // Get all relationships (first 100)
330
+ * const all = await brain.getRelations()
331
+ *
332
+ * // Get relationships from specific entity (shorthand syntax)
333
+ * const fromEntity = await brain.getRelations(entityId)
334
+ *
335
+ * // Get relationships with filters
336
+ * const filtered = await brain.getRelations({
337
+ * type: VerbType.FriendOf,
338
+ * limit: 50
339
+ * })
340
+ *
341
+ * // Pagination
342
+ * const page2 = await brain.getRelations({ offset: 100, limit: 100 })
343
+ * ```
344
+ *
345
+ * @since v4.1.3 - Fixed bug where calling without parameters returned empty array
346
+ * @since v4.1.3 - Added string ID shorthand syntax: getRelations(id)
318
347
  */
319
- getRelations(params?: GetRelationsParams): Promise<Relation<T>[]>;
348
+ getRelations(paramsOrId?: string | GetRelationsParams): Promise<Relation<T>[]>;
320
349
  /**
321
350
  * Unified find method - supports natural language and structured queries
322
351
  * Implements Triple Intelligence with parallel search optimization
package/dist/brainy.js CHANGED
@@ -719,33 +719,75 @@ export class Brainy {
719
719
  });
720
720
  }
721
721
  /**
722
- * Get relationships
722
+ * Get relationships between entities
723
+ *
724
+ * Supports multiple query patterns:
725
+ * - No parameters: Returns all relationships (paginated, default limit: 100)
726
+ * - String ID: Returns relationships from that entity (shorthand for { from: id })
727
+ * - Parameters object: Fine-grained filtering and pagination
728
+ *
729
+ * @param paramsOrId - Optional string ID or parameters object
730
+ * @returns Promise resolving to array of relationships
731
+ *
732
+ * @example
733
+ * ```typescript
734
+ * // Get all relationships (first 100)
735
+ * const all = await brain.getRelations()
736
+ *
737
+ * // Get relationships from specific entity (shorthand syntax)
738
+ * const fromEntity = await brain.getRelations(entityId)
739
+ *
740
+ * // Get relationships with filters
741
+ * const filtered = await brain.getRelations({
742
+ * type: VerbType.FriendOf,
743
+ * limit: 50
744
+ * })
745
+ *
746
+ * // Pagination
747
+ * const page2 = await brain.getRelations({ offset: 100, limit: 100 })
748
+ * ```
749
+ *
750
+ * @since v4.1.3 - Fixed bug where calling without parameters returned empty array
751
+ * @since v4.1.3 - Added string ID shorthand syntax: getRelations(id)
723
752
  */
724
- async getRelations(params = {}) {
753
+ async getRelations(paramsOrId) {
725
754
  await this.ensureInitialized();
726
- const relations = [];
755
+ // Handle string ID shorthand: getRelations(id) -> getRelations({ from: id })
756
+ const params = typeof paramsOrId === 'string'
757
+ ? { from: paramsOrId }
758
+ : (paramsOrId || {});
759
+ const limit = params.limit || 100;
760
+ const offset = params.offset || 0;
761
+ // Production safety: warn for large unfiltered queries
762
+ if (!params.from && !params.to && !params.type && limit > 10000) {
763
+ console.warn(`[Brainy] getRelations(): Fetching ${limit} relationships without filters. ` +
764
+ `Consider adding 'from', 'to', or 'type' filter for better performance.`);
765
+ }
766
+ // Build filter for storage query
767
+ const filter = {};
727
768
  if (params.from) {
728
- const verbs = await this.storage.getVerbsBySource(params.from);
729
- relations.push(...this.verbsToRelations(verbs));
769
+ filter.sourceId = params.from;
730
770
  }
731
771
  if (params.to) {
732
- const verbs = await this.storage.getVerbsByTarget(params.to);
733
- relations.push(...this.verbsToRelations(verbs));
772
+ filter.targetId = params.to;
734
773
  }
735
- // Filter by type
736
- let filtered = relations;
737
774
  if (params.type) {
738
- const types = Array.isArray(params.type) ? params.type : [params.type];
739
- filtered = relations.filter((r) => types.includes(r.type));
775
+ filter.verbType = Array.isArray(params.type) ? params.type : [params.type];
740
776
  }
741
- // Filter by service
742
777
  if (params.service) {
743
- filtered = filtered.filter((r) => r.service === params.service);
744
- }
745
- // Apply pagination
746
- const limit = params.limit || 100;
747
- const offset = params.offset || 0;
748
- return filtered.slice(offset, offset + limit);
778
+ filter.service = params.service;
779
+ }
780
+ // Fetch from storage with pagination at storage layer (efficient!)
781
+ const result = await this.storage.getVerbs({
782
+ pagination: {
783
+ limit,
784
+ offset,
785
+ cursor: params.cursor
786
+ },
787
+ filter: Object.keys(filter).length > 0 ? filter : undefined
788
+ });
789
+ // Convert to Relation format
790
+ return this.verbsToRelations(result.items);
749
791
  }
750
792
  // ============= SEARCH & DISCOVERY =============
751
793
  /**
@@ -1310,14 +1310,14 @@ export class ImprovedNeuralAPI {
1310
1310
  for (const sourceId of itemIds) {
1311
1311
  const sourceVerbs = await this.brain.getRelations(sourceId);
1312
1312
  for (const verb of sourceVerbs) {
1313
- const targetId = verb.target;
1313
+ const targetId = verb.to;
1314
1314
  if (nodes.has(targetId) && sourceId !== targetId) {
1315
1315
  // Initialize edge map if needed
1316
1316
  if (!edges.has(sourceId)) {
1317
1317
  edges.set(sourceId, new Map());
1318
1318
  }
1319
1319
  // Calculate edge weight from verb type and metadata
1320
- const verbType = verb.verb;
1320
+ const verbType = verb.type;
1321
1321
  const baseWeight = relationshipWeights[verbType] || 0.5;
1322
1322
  const confidenceWeight = verb.confidence || 1.0;
1323
1323
  const weight = baseWeight * confidenceWeight;
@@ -2743,7 +2743,7 @@ export class ImprovedNeuralAPI {
2743
2743
  const sampleSize = Math.min(50, itemIds.length);
2744
2744
  for (let i = 0; i < sampleSize; i++) {
2745
2745
  try {
2746
- const verbs = await this.brain.getVerbsForNoun(itemIds[i]);
2746
+ const verbs = await this.brain.getRelations({ from: itemIds[i] });
2747
2747
  connectionCount += verbs.length;
2748
2748
  }
2749
2749
  catch (error) {
@@ -2797,9 +2797,9 @@ export class ImprovedNeuralAPI {
2797
2797
  if (fromType !== toType) {
2798
2798
  for (const fromItem of fromItems.slice(0, 10)) { // Sample to avoid N^2
2799
2799
  try {
2800
- const verbs = await this.brain.getVerbsForNoun(fromItem.id);
2800
+ const verbs = await this.brain.getRelations({ from: fromItem.id });
2801
2801
  for (const verb of verbs) {
2802
- const toItem = toItems.find(item => item.id === verb.target);
2802
+ const toItem = toItems.find(item => item.id === verb.to);
2803
2803
  if (toItem) {
2804
2804
  connections.push({
2805
2805
  from: fromItem.id,
@@ -600,13 +600,15 @@ export class BaseStorage extends BaseStorageAdapter {
600
600
  // Check if the adapter has a paginated method for getting verbs
601
601
  if (typeof this.getVerbsWithPagination === 'function') {
602
602
  // Use the adapter's paginated method
603
+ // Convert offset to cursor if no cursor provided (adapters use cursor for offset)
604
+ const effectiveCursor = cursor || (offset > 0 ? offset.toString() : undefined);
603
605
  const result = await this.getVerbsWithPagination({
604
606
  limit,
605
- cursor,
607
+ cursor: effectiveCursor,
606
608
  filter: options?.filter
607
609
  });
608
- // Apply offset if needed (some adapters might not support offset)
609
- const items = result.items.slice(offset);
610
+ // Items are already offset by the adapter via cursor, no need to slice
611
+ const items = result.items;
610
612
  // CRITICAL SAFETY CHECK: Prevent infinite loops
611
613
  // If we have no items but hasMore is true, force hasMore to false
612
614
  // This prevents pagination bugs from causing infinite loops
@@ -172,14 +172,83 @@ export interface SimilarParams<T = any> {
172
172
  }
173
173
  /**
174
174
  * Parameters for getting relationships
175
+ *
176
+ * All parameters are optional. When called without parameters, returns all relationships
177
+ * with pagination (default limit: 100).
178
+ *
179
+ * @example
180
+ * ```typescript
181
+ * // Get all relationships (default limit: 100)
182
+ * const all = await brain.getRelations()
183
+ *
184
+ * // Get relationships from a specific entity (string shorthand)
185
+ * const fromEntity = await brain.getRelations(entityId)
186
+ *
187
+ * // Equivalent to:
188
+ * const fromEntity2 = await brain.getRelations({ from: entityId })
189
+ *
190
+ * // Get relationships to a specific entity
191
+ * const toEntity = await brain.getRelations({ to: entityId })
192
+ *
193
+ * // Filter by relationship type
194
+ * const friends = await brain.getRelations({ type: VerbType.FriendOf })
195
+ *
196
+ * // Pagination
197
+ * const page2 = await brain.getRelations({ offset: 100, limit: 50 })
198
+ *
199
+ * // Combined filters
200
+ * const filtered = await brain.getRelations({
201
+ * from: entityId,
202
+ * type: VerbType.WorksWith,
203
+ * limit: 20
204
+ * })
205
+ * ```
206
+ *
207
+ * @since v4.1.3 - Fixed bug where calling without parameters returned empty array
208
+ * @since v4.1.3 - Added string ID shorthand syntax
175
209
  */
176
210
  export interface GetRelationsParams {
211
+ /**
212
+ * Filter by source entity ID
213
+ *
214
+ * Returns all relationships originating from this entity.
215
+ */
177
216
  from?: string;
217
+ /**
218
+ * Filter by target entity ID
219
+ *
220
+ * Returns all relationships pointing to this entity.
221
+ */
178
222
  to?: string;
223
+ /**
224
+ * Filter by relationship type(s)
225
+ *
226
+ * Can be a single VerbType or array of VerbTypes.
227
+ */
179
228
  type?: VerbType | VerbType[];
229
+ /**
230
+ * Maximum number of results to return
231
+ *
232
+ * @default 100
233
+ */
180
234
  limit?: number;
235
+ /**
236
+ * Number of results to skip (offset-based pagination)
237
+ *
238
+ * @default 0
239
+ */
181
240
  offset?: number;
241
+ /**
242
+ * Cursor for cursor-based pagination
243
+ *
244
+ * More efficient than offset for large result sets.
245
+ */
182
246
  cursor?: string;
247
+ /**
248
+ * Filter by service (multi-tenancy)
249
+ *
250
+ * Only return relationships belonging to this service.
251
+ */
183
252
  service?: string;
184
253
  }
185
254
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/brainy",
3
- "version": "4.1.2",
3
+ "version": "4.1.3",
4
4
  "description": "Universal Knowledge Protocol™ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. 31 nouns × 40 verbs for infinite expressiveness.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",