@soulcraft/brainy 3.32.2 → 3.35.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/CHANGELOG.md +175 -0
- package/dist/augmentations/typeMatching/brainyTypes.d.ts +5 -1
- package/dist/augmentations/typeMatching/brainyTypes.js +14 -7
- package/dist/brainy.d.ts +31 -0
- package/dist/brainy.js +119 -34
- package/dist/hnsw/hnswIndex.d.ts +24 -0
- package/dist/hnsw/hnswIndex.js +137 -0
- package/dist/hnsw/hnswIndexOptimized.d.ts +2 -13
- package/dist/hnsw/hnswIndexOptimized.js +8 -37
- package/dist/importers/SmartExcelImporter.js +12 -0
- package/dist/interfaces/IIndex.d.ts +186 -0
- package/dist/interfaces/IIndex.js +15 -0
- package/dist/neural/embeddedTypeEmbeddings.d.ts +34 -0
- package/dist/neural/embeddedTypeEmbeddings.js +96 -0
- package/dist/neural/entityExtractor.d.ts +2 -0
- package/dist/neural/entityExtractor.js +21 -42
- package/dist/neural/naturalLanguageProcessor.d.ts +2 -1
- package/dist/neural/naturalLanguageProcessor.js +17 -31
- package/dist/storage/adapters/baseStorageAdapter.d.ts +54 -0
- package/dist/storage/adapters/baseStorageAdapter.js +105 -10
- package/dist/storage/adapters/fileSystemStorage.d.ts +32 -0
- package/dist/storage/adapters/fileSystemStorage.js +66 -0
- package/dist/storage/adapters/gcsStorage.d.ts +45 -0
- package/dist/storage/adapters/gcsStorage.js +122 -4
- package/dist/storage/adapters/memoryStorage.d.ts +32 -0
- package/dist/storage/adapters/memoryStorage.js +43 -0
- package/dist/storage/adapters/opfsStorage.d.ts +36 -0
- package/dist/storage/adapters/opfsStorage.js +101 -0
- package/dist/storage/adapters/s3CompatibleStorage.d.ts +45 -0
- package/dist/storage/adapters/s3CompatibleStorage.js +123 -0
- package/package.json +5 -2
|
@@ -595,5 +595,48 @@ export class MemoryStorage extends BaseStorage {
|
|
|
595
595
|
// No persistence needed for in-memory storage
|
|
596
596
|
// Counts are always accurate from the live data structures
|
|
597
597
|
}
|
|
598
|
+
// =============================================
|
|
599
|
+
// HNSW Index Persistence (v3.35.0+)
|
|
600
|
+
// =============================================
|
|
601
|
+
/**
|
|
602
|
+
* Get vector for a noun
|
|
603
|
+
*/
|
|
604
|
+
async getNounVector(id) {
|
|
605
|
+
const noun = this.nouns.get(id);
|
|
606
|
+
return noun ? [...noun.vector] : null;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Save HNSW graph data for a noun
|
|
610
|
+
*/
|
|
611
|
+
async saveHNSWData(nounId, hnswData) {
|
|
612
|
+
// For memory storage, HNSW data is already in the noun object
|
|
613
|
+
// This method is a no-op since saveNoun already stores the full graph
|
|
614
|
+
// But we store it separately for consistency with other adapters
|
|
615
|
+
const path = `hnsw/${nounId}.json`;
|
|
616
|
+
await this.writeObjectToPath(path, hnswData);
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Get HNSW graph data for a noun
|
|
620
|
+
*/
|
|
621
|
+
async getHNSWData(nounId) {
|
|
622
|
+
const path = `hnsw/${nounId}.json`;
|
|
623
|
+
const data = await this.readObjectFromPath(path);
|
|
624
|
+
return data || null;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Save HNSW system data (entry point, max level)
|
|
628
|
+
*/
|
|
629
|
+
async saveHNSWSystem(systemData) {
|
|
630
|
+
const path = 'system/hnsw-system.json';
|
|
631
|
+
await this.writeObjectToPath(path, systemData);
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Get HNSW system data
|
|
635
|
+
*/
|
|
636
|
+
async getHNSWSystem() {
|
|
637
|
+
const path = 'system/hnsw-system.json';
|
|
638
|
+
const data = await this.readObjectFromPath(path);
|
|
639
|
+
return data || null;
|
|
640
|
+
}
|
|
598
641
|
}
|
|
599
642
|
//# sourceMappingURL=memoryStorage.js.map
|
|
@@ -262,5 +262,41 @@ export declare class OPFSStorage extends BaseStorage {
|
|
|
262
262
|
* Persist counts to OPFS storage
|
|
263
263
|
*/
|
|
264
264
|
protected persistCounts(): Promise<void>;
|
|
265
|
+
/**
|
|
266
|
+
* Get a noun's vector for HNSW rebuild
|
|
267
|
+
*/
|
|
268
|
+
getNounVector(id: string): Promise<number[] | null>;
|
|
269
|
+
/**
|
|
270
|
+
* Save HNSW graph data for a noun
|
|
271
|
+
* Storage path: nouns/hnsw/{shard}/{id}.json
|
|
272
|
+
*/
|
|
273
|
+
saveHNSWData(nounId: string, hnswData: {
|
|
274
|
+
level: number;
|
|
275
|
+
connections: Record<string, string[]>;
|
|
276
|
+
}): Promise<void>;
|
|
277
|
+
/**
|
|
278
|
+
* Get HNSW graph data for a noun
|
|
279
|
+
* Storage path: nouns/hnsw/{shard}/{id}.json
|
|
280
|
+
*/
|
|
281
|
+
getHNSWData(nounId: string): Promise<{
|
|
282
|
+
level: number;
|
|
283
|
+
connections: Record<string, string[]>;
|
|
284
|
+
} | null>;
|
|
285
|
+
/**
|
|
286
|
+
* Save HNSW system data (entry point, max level)
|
|
287
|
+
* Storage path: index/hnsw-system.json
|
|
288
|
+
*/
|
|
289
|
+
saveHNSWSystem(systemData: {
|
|
290
|
+
entryPointId: string | null;
|
|
291
|
+
maxLevel: number;
|
|
292
|
+
}): Promise<void>;
|
|
293
|
+
/**
|
|
294
|
+
* Get HNSW system data (entry point, max level)
|
|
295
|
+
* Storage path: index/hnsw-system.json
|
|
296
|
+
*/
|
|
297
|
+
getHNSWSystem(): Promise<{
|
|
298
|
+
entryPointId: string | null;
|
|
299
|
+
maxLevel: number;
|
|
300
|
+
} | null>;
|
|
265
301
|
}
|
|
266
302
|
export {};
|
|
@@ -1471,5 +1471,106 @@ export class OPFSStorage extends BaseStorage {
|
|
|
1471
1471
|
console.error('Error persisting counts to OPFS:', error);
|
|
1472
1472
|
}
|
|
1473
1473
|
}
|
|
1474
|
+
// HNSW Index Persistence (v3.35.0+)
|
|
1475
|
+
/**
|
|
1476
|
+
* Get a noun's vector for HNSW rebuild
|
|
1477
|
+
*/
|
|
1478
|
+
async getNounVector(id) {
|
|
1479
|
+
await this.ensureInitialized();
|
|
1480
|
+
const noun = await this.getNoun_internal(id);
|
|
1481
|
+
return noun ? noun.vector : null;
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Save HNSW graph data for a noun
|
|
1485
|
+
* Storage path: nouns/hnsw/{shard}/{id}.json
|
|
1486
|
+
*/
|
|
1487
|
+
async saveHNSWData(nounId, hnswData) {
|
|
1488
|
+
await this.ensureInitialized();
|
|
1489
|
+
try {
|
|
1490
|
+
// Get or create the hnsw directory under nouns
|
|
1491
|
+
const hnswDir = await this.nounsDir.getDirectoryHandle('hnsw', { create: true });
|
|
1492
|
+
// Use sharded path for HNSW data
|
|
1493
|
+
const shard = getShardIdFromUuid(nounId);
|
|
1494
|
+
const shardDir = await hnswDir.getDirectoryHandle(shard, { create: true });
|
|
1495
|
+
// Create or get the file in the shard directory
|
|
1496
|
+
const fileHandle = await shardDir.getFileHandle(`${nounId}.json`, { create: true });
|
|
1497
|
+
// Write the HNSW data to the file
|
|
1498
|
+
const writable = await fileHandle.createWritable();
|
|
1499
|
+
await writable.write(JSON.stringify(hnswData, null, 2));
|
|
1500
|
+
await writable.close();
|
|
1501
|
+
}
|
|
1502
|
+
catch (error) {
|
|
1503
|
+
console.error(`Failed to save HNSW data for ${nounId}:`, error);
|
|
1504
|
+
throw new Error(`Failed to save HNSW data for ${nounId}: ${error}`);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
/**
|
|
1508
|
+
* Get HNSW graph data for a noun
|
|
1509
|
+
* Storage path: nouns/hnsw/{shard}/{id}.json
|
|
1510
|
+
*/
|
|
1511
|
+
async getHNSWData(nounId) {
|
|
1512
|
+
await this.ensureInitialized();
|
|
1513
|
+
try {
|
|
1514
|
+
// Get the hnsw directory under nouns
|
|
1515
|
+
const hnswDir = await this.nounsDir.getDirectoryHandle('hnsw');
|
|
1516
|
+
// Use sharded path for HNSW data
|
|
1517
|
+
const shard = getShardIdFromUuid(nounId);
|
|
1518
|
+
const shardDir = await hnswDir.getDirectoryHandle(shard);
|
|
1519
|
+
// Get the file handle from the shard directory
|
|
1520
|
+
const fileHandle = await shardDir.getFileHandle(`${nounId}.json`);
|
|
1521
|
+
// Read the HNSW data from the file
|
|
1522
|
+
const file = await fileHandle.getFile();
|
|
1523
|
+
const text = await file.text();
|
|
1524
|
+
return JSON.parse(text);
|
|
1525
|
+
}
|
|
1526
|
+
catch (error) {
|
|
1527
|
+
if (error.name === 'NotFoundError') {
|
|
1528
|
+
return null;
|
|
1529
|
+
}
|
|
1530
|
+
console.error(`Failed to get HNSW data for ${nounId}:`, error);
|
|
1531
|
+
throw new Error(`Failed to get HNSW data for ${nounId}: ${error}`);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Save HNSW system data (entry point, max level)
|
|
1536
|
+
* Storage path: index/hnsw-system.json
|
|
1537
|
+
*/
|
|
1538
|
+
async saveHNSWSystem(systemData) {
|
|
1539
|
+
await this.ensureInitialized();
|
|
1540
|
+
try {
|
|
1541
|
+
// Create or get the file in the index directory
|
|
1542
|
+
const fileHandle = await this.indexDir.getFileHandle('hnsw-system.json', { create: true });
|
|
1543
|
+
// Write the system data to the file
|
|
1544
|
+
const writable = await fileHandle.createWritable();
|
|
1545
|
+
await writable.write(JSON.stringify(systemData, null, 2));
|
|
1546
|
+
await writable.close();
|
|
1547
|
+
}
|
|
1548
|
+
catch (error) {
|
|
1549
|
+
console.error('Failed to save HNSW system data:', error);
|
|
1550
|
+
throw new Error(`Failed to save HNSW system data: ${error}`);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Get HNSW system data (entry point, max level)
|
|
1555
|
+
* Storage path: index/hnsw-system.json
|
|
1556
|
+
*/
|
|
1557
|
+
async getHNSWSystem() {
|
|
1558
|
+
await this.ensureInitialized();
|
|
1559
|
+
try {
|
|
1560
|
+
// Get the file handle from the index directory
|
|
1561
|
+
const fileHandle = await this.indexDir.getFileHandle('hnsw-system.json');
|
|
1562
|
+
// Read the system data from the file
|
|
1563
|
+
const file = await fileHandle.getFile();
|
|
1564
|
+
const text = await file.text();
|
|
1565
|
+
return JSON.parse(text);
|
|
1566
|
+
}
|
|
1567
|
+
catch (error) {
|
|
1568
|
+
if (error.name === 'NotFoundError') {
|
|
1569
|
+
return null;
|
|
1570
|
+
}
|
|
1571
|
+
console.error('Failed to get HNSW system data:', error);
|
|
1572
|
+
throw new Error(`Failed to get HNSW system data: ${error}`);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1474
1575
|
}
|
|
1475
1576
|
//# sourceMappingURL=opfsStorage.js.map
|
|
@@ -563,4 +563,49 @@ export declare class S3CompatibleStorage extends BaseStorage {
|
|
|
563
563
|
* Persist counts to S3 storage
|
|
564
564
|
*/
|
|
565
565
|
protected persistCounts(): Promise<void>;
|
|
566
|
+
/**
|
|
567
|
+
* Override base class to enable smart batching for cloud storage (v3.32.3+)
|
|
568
|
+
*
|
|
569
|
+
* S3 is cloud storage with network latency (~50ms per write).
|
|
570
|
+
* Smart batching reduces writes from 1000 ops → 100 batches.
|
|
571
|
+
*
|
|
572
|
+
* @returns true (S3 is cloud storage)
|
|
573
|
+
*/
|
|
574
|
+
protected isCloudStorage(): boolean;
|
|
575
|
+
/**
|
|
576
|
+
* Get a noun's vector for HNSW rebuild
|
|
577
|
+
*/
|
|
578
|
+
getNounVector(id: string): Promise<number[] | null>;
|
|
579
|
+
/**
|
|
580
|
+
* Save HNSW graph data for a noun
|
|
581
|
+
* Storage path: entities/nouns/hnsw/{shard}/{id}.json
|
|
582
|
+
*/
|
|
583
|
+
saveHNSWData(nounId: string, hnswData: {
|
|
584
|
+
level: number;
|
|
585
|
+
connections: Record<string, string[]>;
|
|
586
|
+
}): Promise<void>;
|
|
587
|
+
/**
|
|
588
|
+
* Get HNSW graph data for a noun
|
|
589
|
+
* Storage path: entities/nouns/hnsw/{shard}/{id}.json
|
|
590
|
+
*/
|
|
591
|
+
getHNSWData(nounId: string): Promise<{
|
|
592
|
+
level: number;
|
|
593
|
+
connections: Record<string, string[]>;
|
|
594
|
+
} | null>;
|
|
595
|
+
/**
|
|
596
|
+
* Save HNSW system data (entry point, max level)
|
|
597
|
+
* Storage path: system/hnsw-system.json
|
|
598
|
+
*/
|
|
599
|
+
saveHNSWSystem(systemData: {
|
|
600
|
+
entryPointId: string | null;
|
|
601
|
+
maxLevel: number;
|
|
602
|
+
}): Promise<void>;
|
|
603
|
+
/**
|
|
604
|
+
* Get HNSW system data (entry point, max level)
|
|
605
|
+
* Storage path: system/hnsw-system.json
|
|
606
|
+
*/
|
|
607
|
+
getHNSWSystem(): Promise<{
|
|
608
|
+
entryPointId: string | null;
|
|
609
|
+
maxLevel: number;
|
|
610
|
+
} | null>;
|
|
566
611
|
}
|
|
@@ -2754,5 +2754,128 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
2754
2754
|
console.error('Error persisting counts to S3:', error);
|
|
2755
2755
|
}
|
|
2756
2756
|
}
|
|
2757
|
+
/**
|
|
2758
|
+
* Override base class to enable smart batching for cloud storage (v3.32.3+)
|
|
2759
|
+
*
|
|
2760
|
+
* S3 is cloud storage with network latency (~50ms per write).
|
|
2761
|
+
* Smart batching reduces writes from 1000 ops → 100 batches.
|
|
2762
|
+
*
|
|
2763
|
+
* @returns true (S3 is cloud storage)
|
|
2764
|
+
*/
|
|
2765
|
+
isCloudStorage() {
|
|
2766
|
+
return true; // S3 benefits from batching
|
|
2767
|
+
}
|
|
2768
|
+
// HNSW Index Persistence (v3.35.0+)
|
|
2769
|
+
/**
|
|
2770
|
+
* Get a noun's vector for HNSW rebuild
|
|
2771
|
+
*/
|
|
2772
|
+
async getNounVector(id) {
|
|
2773
|
+
await this.ensureInitialized();
|
|
2774
|
+
const noun = await this.getNode(id);
|
|
2775
|
+
return noun ? noun.vector : null;
|
|
2776
|
+
}
|
|
2777
|
+
/**
|
|
2778
|
+
* Save HNSW graph data for a noun
|
|
2779
|
+
* Storage path: entities/nouns/hnsw/{shard}/{id}.json
|
|
2780
|
+
*/
|
|
2781
|
+
async saveHNSWData(nounId, hnswData) {
|
|
2782
|
+
await this.ensureInitialized();
|
|
2783
|
+
try {
|
|
2784
|
+
const { PutObjectCommand } = await import('@aws-sdk/client-s3');
|
|
2785
|
+
// Use sharded path for HNSW data
|
|
2786
|
+
const shard = getShardIdFromUuid(nounId);
|
|
2787
|
+
const key = `entities/nouns/hnsw/${shard}/${nounId}.json`;
|
|
2788
|
+
await this.s3Client.send(new PutObjectCommand({
|
|
2789
|
+
Bucket: this.bucketName,
|
|
2790
|
+
Key: key,
|
|
2791
|
+
Body: JSON.stringify(hnswData, null, 2),
|
|
2792
|
+
ContentType: 'application/json'
|
|
2793
|
+
}));
|
|
2794
|
+
}
|
|
2795
|
+
catch (error) {
|
|
2796
|
+
this.logger.error(`Failed to save HNSW data for ${nounId}:`, error);
|
|
2797
|
+
throw new Error(`Failed to save HNSW data for ${nounId}: ${error}`);
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
/**
|
|
2801
|
+
* Get HNSW graph data for a noun
|
|
2802
|
+
* Storage path: entities/nouns/hnsw/{shard}/{id}.json
|
|
2803
|
+
*/
|
|
2804
|
+
async getHNSWData(nounId) {
|
|
2805
|
+
await this.ensureInitialized();
|
|
2806
|
+
try {
|
|
2807
|
+
const { GetObjectCommand } = await import('@aws-sdk/client-s3');
|
|
2808
|
+
const shard = getShardIdFromUuid(nounId);
|
|
2809
|
+
const key = `entities/nouns/hnsw/${shard}/${nounId}.json`;
|
|
2810
|
+
const response = await this.s3Client.send(new GetObjectCommand({
|
|
2811
|
+
Bucket: this.bucketName,
|
|
2812
|
+
Key: key
|
|
2813
|
+
}));
|
|
2814
|
+
if (!response || !response.Body) {
|
|
2815
|
+
return null;
|
|
2816
|
+
}
|
|
2817
|
+
const bodyContents = await response.Body.transformToString();
|
|
2818
|
+
return JSON.parse(bodyContents);
|
|
2819
|
+
}
|
|
2820
|
+
catch (error) {
|
|
2821
|
+
if (error.name === 'NoSuchKey' ||
|
|
2822
|
+
error.message?.includes('NoSuchKey') ||
|
|
2823
|
+
error.message?.includes('not found')) {
|
|
2824
|
+
return null;
|
|
2825
|
+
}
|
|
2826
|
+
this.logger.error(`Failed to get HNSW data for ${nounId}:`, error);
|
|
2827
|
+
throw new Error(`Failed to get HNSW data for ${nounId}: ${error}`);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
/**
|
|
2831
|
+
* Save HNSW system data (entry point, max level)
|
|
2832
|
+
* Storage path: system/hnsw-system.json
|
|
2833
|
+
*/
|
|
2834
|
+
async saveHNSWSystem(systemData) {
|
|
2835
|
+
await this.ensureInitialized();
|
|
2836
|
+
try {
|
|
2837
|
+
const { PutObjectCommand } = await import('@aws-sdk/client-s3');
|
|
2838
|
+
const key = `${this.systemPrefix}hnsw-system.json`;
|
|
2839
|
+
await this.s3Client.send(new PutObjectCommand({
|
|
2840
|
+
Bucket: this.bucketName,
|
|
2841
|
+
Key: key,
|
|
2842
|
+
Body: JSON.stringify(systemData, null, 2),
|
|
2843
|
+
ContentType: 'application/json'
|
|
2844
|
+
}));
|
|
2845
|
+
}
|
|
2846
|
+
catch (error) {
|
|
2847
|
+
this.logger.error('Failed to save HNSW system data:', error);
|
|
2848
|
+
throw new Error(`Failed to save HNSW system data: ${error}`);
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* Get HNSW system data (entry point, max level)
|
|
2853
|
+
* Storage path: system/hnsw-system.json
|
|
2854
|
+
*/
|
|
2855
|
+
async getHNSWSystem() {
|
|
2856
|
+
await this.ensureInitialized();
|
|
2857
|
+
try {
|
|
2858
|
+
const { GetObjectCommand } = await import('@aws-sdk/client-s3');
|
|
2859
|
+
const key = `${this.systemPrefix}hnsw-system.json`;
|
|
2860
|
+
const response = await this.s3Client.send(new GetObjectCommand({
|
|
2861
|
+
Bucket: this.bucketName,
|
|
2862
|
+
Key: key
|
|
2863
|
+
}));
|
|
2864
|
+
if (!response || !response.Body) {
|
|
2865
|
+
return null;
|
|
2866
|
+
}
|
|
2867
|
+
const bodyContents = await response.Body.transformToString();
|
|
2868
|
+
return JSON.parse(bodyContents);
|
|
2869
|
+
}
|
|
2870
|
+
catch (error) {
|
|
2871
|
+
if (error.name === 'NoSuchKey' ||
|
|
2872
|
+
error.message?.includes('NoSuchKey') ||
|
|
2873
|
+
error.message?.includes('not found')) {
|
|
2874
|
+
return null;
|
|
2875
|
+
}
|
|
2876
|
+
this.logger.error('Failed to get HNSW system data:', error);
|
|
2877
|
+
throw new Error(`Failed to get HNSW system data: ${error}`);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2757
2880
|
}
|
|
2758
2881
|
//# sourceMappingURL=s3CompatibleStorage.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.35.0",
|
|
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",
|
|
@@ -55,7 +55,10 @@
|
|
|
55
55
|
"node": "22.x"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
|
-
"build": "npm run build:patterns:if-needed && tsc && tsc -p tsconfig.cli.json",
|
|
58
|
+
"build": "npm run build:types:if-needed && npm run build:patterns:if-needed && tsc && tsc -p tsconfig.cli.json",
|
|
59
|
+
"build:types": "tsx scripts/buildTypeEmbeddings.ts",
|
|
60
|
+
"build:types:if-needed": "node scripts/check-type-embeddings.cjs || npm run build:types",
|
|
61
|
+
"build:types:force": "npm run build:types",
|
|
59
62
|
"build:patterns": "tsx scripts/buildEmbeddedPatterns.ts",
|
|
60
63
|
"build:patterns:if-needed": "node scripts/check-patterns.cjs || npm run build:patterns",
|
|
61
64
|
"build:patterns:force": "npm run build:patterns",
|