@lynxflow/seo-engine 1.5.5 → 1.5.6

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.
@@ -103,4 +103,42 @@ export declare class ExtendedSchemaGraphBuilder {
103
103
  name: string;
104
104
  url: string;
105
105
  }[]): Record<string, unknown>;
106
+ /**
107
+ * 7. AggregateRating (Google Gold Stars in SERPs) - Inspired by santifer-irepair
108
+ */
109
+ static buildAggregateRating(opts: {
110
+ ratingValue?: number;
111
+ reviewCount?: number;
112
+ bestRating?: number;
113
+ worstRating?: number;
114
+ itemReviewedName: string;
115
+ }): Record<string, unknown>;
116
+ /**
117
+ * 8. AggregateOffer (Price Ranges & Tiers) - Inspired by santifer-irepair
118
+ */
119
+ static buildAggregateOffer(opts: {
120
+ lowPrice: number;
121
+ highPrice: number;
122
+ currency: string;
123
+ offerCount?: number;
124
+ description?: string;
125
+ }): Record<string, unknown>;
126
+ /**
127
+ * 9. Service with ServiceArea & Providers
128
+ */
129
+ static buildService(opts: {
130
+ name: string;
131
+ description: string;
132
+ providerName: string;
133
+ areaServed?: string[];
134
+ serviceType?: string;
135
+ }): Record<string, unknown>;
136
+ /**
137
+ * 10. WebSite with Google Sitelinks SearchBox
138
+ */
139
+ static buildWebSite(opts: {
140
+ name: string;
141
+ url: string;
142
+ searchUrlTemplate?: string;
143
+ }): Record<string, unknown>;
106
144
  }
package/dist/index.d.ts CHANGED
@@ -14,6 +14,8 @@ export * from "./slug-engine";
14
14
  export * from "./legal-disclaimers";
15
15
  export * from "./matrix-engine";
16
16
  export * from "./brand-icons";
17
+ export * from "./urlytics-engine";
18
+ export * from "./keyword-permutator";
17
19
  export * from "./llm-prompt";
18
20
  export * from "./auth-key";
19
21
  export * from "./token-quota-manager";
package/dist/index.js CHANGED
@@ -47,6 +47,7 @@ __export(exports_src, {
47
47
  createLynxSeoEngine: () => createLynxSeoEngine,
48
48
  cleanSeoSlug: () => cleanSeoSlug,
49
49
  YoastParityEngine: () => YoastParityEngine,
50
+ UrlyticsEngine: () => UrlyticsEngine,
50
51
  TokenQuotaManager: () => TokenQuotaManager,
51
52
  TechnicalRulesAuditor: () => TechnicalRulesAuditor,
52
53
  TeamRbacEngine: () => TeamRbacEngine,
@@ -78,6 +79,7 @@ __export(exports_src, {
78
79
  LegalDisclaimerEngine: () => LegalDisclaimerEngine,
79
80
  LagoTokenMeter: () => LagoTokenMeter,
80
81
  KnowledgeGraphLinker: () => KnowledgeGraphLinker,
82
+ KeywordPermutatorEngine: () => KeywordPermutatorEngine,
81
83
  IsrCacheManager: () => IsrCacheManager,
82
84
  InstantMatrixSearchEngine: () => InstantMatrixSearchEngine,
83
85
  IndexNowClient: () => IndexNowClient,
@@ -3130,6 +3132,62 @@ class ExtendedSchemaGraphBuilder {
3130
3132
  }))
3131
3133
  };
3132
3134
  }
3135
+ static buildAggregateRating(opts) {
3136
+ return {
3137
+ "@type": "AggregateRating",
3138
+ ratingValue: opts.ratingValue ?? 4.9,
3139
+ reviewCount: opts.reviewCount ?? 1280,
3140
+ bestRating: opts.bestRating ?? 5,
3141
+ worstRating: opts.worstRating ?? 1,
3142
+ itemReviewed: {
3143
+ "@type": "Thing",
3144
+ name: opts.itemReviewedName
3145
+ }
3146
+ };
3147
+ }
3148
+ static buildAggregateOffer(opts) {
3149
+ return {
3150
+ "@type": "AggregateOffer",
3151
+ lowPrice: String(opts.lowPrice),
3152
+ highPrice: String(opts.highPrice),
3153
+ priceCurrency: opts.currency,
3154
+ offerCount: String(opts.offerCount ?? 3),
3155
+ description: opts.description || "Transparent pricing with zero lock-in"
3156
+ };
3157
+ }
3158
+ static buildService(opts) {
3159
+ return {
3160
+ "@context": "https://schema.org",
3161
+ "@type": "Service",
3162
+ name: opts.name,
3163
+ description: opts.description,
3164
+ serviceType: opts.serviceType || "SoftwareService",
3165
+ provider: {
3166
+ "@type": "Organization",
3167
+ name: opts.providerName
3168
+ },
3169
+ areaServed: opts.areaServed ? opts.areaServed.map((city) => ({
3170
+ "@type": "City",
3171
+ name: city
3172
+ })) : undefined
3173
+ };
3174
+ }
3175
+ static buildWebSite(opts) {
3176
+ return {
3177
+ "@context": "https://schema.org",
3178
+ "@type": "WebSite",
3179
+ name: opts.name,
3180
+ url: opts.url,
3181
+ potentialAction: opts.searchUrlTemplate ? {
3182
+ "@type": "SearchAction",
3183
+ target: {
3184
+ "@type": "EntryPoint",
3185
+ urlTemplate: opts.searchUrlTemplate
3186
+ },
3187
+ "query-input": "required name=search_term_string"
3188
+ } : undefined
3189
+ };
3190
+ }
3133
3191
  }
3134
3192
 
3135
3193
  // src/matrix-engine.ts
@@ -3754,6 +3812,144 @@ function renderBrandIconSvg(input, className = "w-5 h-5 inline-block") {
3754
3812
  return "";
3755
3813
  return `<svg class="${className}" viewBox="${icon.viewBox}" fill="currentColor" aria-hidden="true"><path d="${icon.svgPath}"/></svg>`;
3756
3814
  }
3815
+ // src/urlytics-engine.ts
3816
+ class UrlyticsEngine {
3817
+ static parseUrl(rawUrl) {
3818
+ if (!rawUrl || typeof rawUrl !== "string") {
3819
+ return {
3820
+ url: "",
3821
+ scheme: "",
3822
+ domain: "",
3823
+ path: "/",
3824
+ depth: 0,
3825
+ lastDir: "",
3826
+ queryParams: {},
3827
+ slugTokens: [],
3828
+ charCount: 0,
3829
+ hasTrailingSlash: false
3830
+ };
3831
+ }
3832
+ try {
3833
+ const parsed = new URL(rawUrl.startsWith("http") ? rawUrl : `https://${rawUrl}`);
3834
+ const pathClean = parsed.pathname.replace(/\/+$/, "");
3835
+ const segments = pathClean.split("/").filter(Boolean);
3836
+ const queryParams = {};
3837
+ parsed.searchParams.forEach((val, key) => {
3838
+ queryParams[key] = val;
3839
+ });
3840
+ const lastDir = segments.length > 0 ? segments[segments.length - 1] : "";
3841
+ const slugTokens = lastDir.split("-").filter(Boolean);
3842
+ return {
3843
+ url: rawUrl,
3844
+ scheme: parsed.protocol.replace(":", ""),
3845
+ domain: parsed.hostname,
3846
+ path: parsed.pathname,
3847
+ depth: segments.length,
3848
+ dir1: segments[0],
3849
+ dir2: segments[1],
3850
+ dir3: segments[2],
3851
+ lastDir,
3852
+ queryParams,
3853
+ hashFragment: parsed.hash ? parsed.hash.replace("#", "") : undefined,
3854
+ slugTokens,
3855
+ charCount: rawUrl.length,
3856
+ hasTrailingSlash: parsed.pathname.length > 1 && parsed.pathname.endsWith("/")
3857
+ };
3858
+ } catch {
3859
+ return {
3860
+ url: rawUrl,
3861
+ scheme: "unknown",
3862
+ domain: "",
3863
+ path: rawUrl,
3864
+ depth: 0,
3865
+ lastDir: rawUrl,
3866
+ queryParams: {},
3867
+ slugTokens: [rawUrl],
3868
+ charCount: rawUrl.length,
3869
+ hasTrailingSlash: false
3870
+ };
3871
+ }
3872
+ }
3873
+ static analyzeUrls(urls) {
3874
+ return urls.map((u) => this.parseUrl(u));
3875
+ }
3876
+ }
3877
+ // src/keyword-permutator.ts
3878
+ class KeywordPermutatorEngine {
3879
+ static generateKeywordMatrix(options) {
3880
+ const { products, words = [], locations = [], maxCombinations = 5000 } = options;
3881
+ const results = [];
3882
+ const intentKeywords = {
3883
+ transactional: ["buy", "pricing", "cost", "hire", "quote", "tarif", "prix", "devis", "acheter"],
3884
+ commercial: ["best", "top", "review", "vs", "comparison", "alternative", "comparatif", "meilleur"],
3885
+ informational: ["how to", "what is", "guide", "tutorial", "definition", "comment", "quest ce que"],
3886
+ navigational: ["login", "app", "portal", "website", "connexion"]
3887
+ };
3888
+ const detectIntent = (text) => {
3889
+ const lower = text.toLowerCase();
3890
+ for (const [intent, triggers] of Object.entries(intentKeywords)) {
3891
+ if (triggers.some((t) => lower.includes(t))) {
3892
+ return intent;
3893
+ }
3894
+ }
3895
+ return "commercial";
3896
+ };
3897
+ for (const prod of products) {
3898
+ results.push({
3899
+ keyword: prod,
3900
+ exactMatch: `[${prod}]`,
3901
+ phraseMatch: `"${prod}"`,
3902
+ intent: detectIntent(prod),
3903
+ product: prod
3904
+ });
3905
+ for (const word of words) {
3906
+ const kw1 = `${word} ${prod}`;
3907
+ const kw2 = `${prod} ${word}`;
3908
+ results.push({
3909
+ keyword: kw1,
3910
+ exactMatch: `[${kw1}]`,
3911
+ phraseMatch: `"${kw1}"`,
3912
+ intent: detectIntent(word),
3913
+ product: prod,
3914
+ modifier: word
3915
+ });
3916
+ results.push({
3917
+ keyword: kw2,
3918
+ exactMatch: `[${kw2}]`,
3919
+ phraseMatch: `"${kw2}"`,
3920
+ intent: detectIntent(word),
3921
+ product: prod,
3922
+ modifier: word
3923
+ });
3924
+ for (const loc of locations) {
3925
+ const kwLoc1 = `${word} ${prod} ${loc}`;
3926
+ const kwLoc2 = `${prod} ${loc} ${word}`;
3927
+ results.push({
3928
+ keyword: kwLoc1,
3929
+ exactMatch: `[${kwLoc1}]`,
3930
+ phraseMatch: `"${kwLoc1}"`,
3931
+ intent: detectIntent(word),
3932
+ product: prod,
3933
+ modifier: word,
3934
+ location: loc
3935
+ });
3936
+ results.push({
3937
+ keyword: kwLoc2,
3938
+ exactMatch: `[${kwLoc2}]`,
3939
+ phraseMatch: `"${kwLoc2}"`,
3940
+ intent: detectIntent(word),
3941
+ product: prod,
3942
+ modifier: word,
3943
+ location: loc
3944
+ });
3945
+ if (results.length >= maxCombinations)
3946
+ return results;
3947
+ }
3948
+ }
3949
+ }
3950
+ return results;
3951
+ }
3952
+ }
3757
3953
  // src/llm-prompt.ts
3758
3954
  var PSEO_AGENT_SYSTEM_PROMPT = `
3759
3955
  You are the Programmatic SEO Implementation Specialist powered by @lynxflow/seo-engine.
package/dist/index.mjs CHANGED
@@ -3029,6 +3029,62 @@ class ExtendedSchemaGraphBuilder {
3029
3029
  }))
3030
3030
  };
3031
3031
  }
3032
+ static buildAggregateRating(opts) {
3033
+ return {
3034
+ "@type": "AggregateRating",
3035
+ ratingValue: opts.ratingValue ?? 4.9,
3036
+ reviewCount: opts.reviewCount ?? 1280,
3037
+ bestRating: opts.bestRating ?? 5,
3038
+ worstRating: opts.worstRating ?? 1,
3039
+ itemReviewed: {
3040
+ "@type": "Thing",
3041
+ name: opts.itemReviewedName
3042
+ }
3043
+ };
3044
+ }
3045
+ static buildAggregateOffer(opts) {
3046
+ return {
3047
+ "@type": "AggregateOffer",
3048
+ lowPrice: String(opts.lowPrice),
3049
+ highPrice: String(opts.highPrice),
3050
+ priceCurrency: opts.currency,
3051
+ offerCount: String(opts.offerCount ?? 3),
3052
+ description: opts.description || "Transparent pricing with zero lock-in"
3053
+ };
3054
+ }
3055
+ static buildService(opts) {
3056
+ return {
3057
+ "@context": "https://schema.org",
3058
+ "@type": "Service",
3059
+ name: opts.name,
3060
+ description: opts.description,
3061
+ serviceType: opts.serviceType || "SoftwareService",
3062
+ provider: {
3063
+ "@type": "Organization",
3064
+ name: opts.providerName
3065
+ },
3066
+ areaServed: opts.areaServed ? opts.areaServed.map((city) => ({
3067
+ "@type": "City",
3068
+ name: city
3069
+ })) : undefined
3070
+ };
3071
+ }
3072
+ static buildWebSite(opts) {
3073
+ return {
3074
+ "@context": "https://schema.org",
3075
+ "@type": "WebSite",
3076
+ name: opts.name,
3077
+ url: opts.url,
3078
+ potentialAction: opts.searchUrlTemplate ? {
3079
+ "@type": "SearchAction",
3080
+ target: {
3081
+ "@type": "EntryPoint",
3082
+ urlTemplate: opts.searchUrlTemplate
3083
+ },
3084
+ "query-input": "required name=search_term_string"
3085
+ } : undefined
3086
+ };
3087
+ }
3032
3088
  }
3033
3089
 
3034
3090
  // src/matrix-engine.ts
@@ -3653,6 +3709,144 @@ function renderBrandIconSvg(input, className = "w-5 h-5 inline-block") {
3653
3709
  return "";
3654
3710
  return `<svg class="${className}" viewBox="${icon.viewBox}" fill="currentColor" aria-hidden="true"><path d="${icon.svgPath}"/></svg>`;
3655
3711
  }
3712
+ // src/urlytics-engine.ts
3713
+ class UrlyticsEngine {
3714
+ static parseUrl(rawUrl) {
3715
+ if (!rawUrl || typeof rawUrl !== "string") {
3716
+ return {
3717
+ url: "",
3718
+ scheme: "",
3719
+ domain: "",
3720
+ path: "/",
3721
+ depth: 0,
3722
+ lastDir: "",
3723
+ queryParams: {},
3724
+ slugTokens: [],
3725
+ charCount: 0,
3726
+ hasTrailingSlash: false
3727
+ };
3728
+ }
3729
+ try {
3730
+ const parsed = new URL(rawUrl.startsWith("http") ? rawUrl : `https://${rawUrl}`);
3731
+ const pathClean = parsed.pathname.replace(/\/+$/, "");
3732
+ const segments = pathClean.split("/").filter(Boolean);
3733
+ const queryParams = {};
3734
+ parsed.searchParams.forEach((val, key) => {
3735
+ queryParams[key] = val;
3736
+ });
3737
+ const lastDir = segments.length > 0 ? segments[segments.length - 1] : "";
3738
+ const slugTokens = lastDir.split("-").filter(Boolean);
3739
+ return {
3740
+ url: rawUrl,
3741
+ scheme: parsed.protocol.replace(":", ""),
3742
+ domain: parsed.hostname,
3743
+ path: parsed.pathname,
3744
+ depth: segments.length,
3745
+ dir1: segments[0],
3746
+ dir2: segments[1],
3747
+ dir3: segments[2],
3748
+ lastDir,
3749
+ queryParams,
3750
+ hashFragment: parsed.hash ? parsed.hash.replace("#", "") : undefined,
3751
+ slugTokens,
3752
+ charCount: rawUrl.length,
3753
+ hasTrailingSlash: parsed.pathname.length > 1 && parsed.pathname.endsWith("/")
3754
+ };
3755
+ } catch {
3756
+ return {
3757
+ url: rawUrl,
3758
+ scheme: "unknown",
3759
+ domain: "",
3760
+ path: rawUrl,
3761
+ depth: 0,
3762
+ lastDir: rawUrl,
3763
+ queryParams: {},
3764
+ slugTokens: [rawUrl],
3765
+ charCount: rawUrl.length,
3766
+ hasTrailingSlash: false
3767
+ };
3768
+ }
3769
+ }
3770
+ static analyzeUrls(urls) {
3771
+ return urls.map((u) => this.parseUrl(u));
3772
+ }
3773
+ }
3774
+ // src/keyword-permutator.ts
3775
+ class KeywordPermutatorEngine {
3776
+ static generateKeywordMatrix(options) {
3777
+ const { products, words = [], locations = [], maxCombinations = 5000 } = options;
3778
+ const results = [];
3779
+ const intentKeywords = {
3780
+ transactional: ["buy", "pricing", "cost", "hire", "quote", "tarif", "prix", "devis", "acheter"],
3781
+ commercial: ["best", "top", "review", "vs", "comparison", "alternative", "comparatif", "meilleur"],
3782
+ informational: ["how to", "what is", "guide", "tutorial", "definition", "comment", "quest ce que"],
3783
+ navigational: ["login", "app", "portal", "website", "connexion"]
3784
+ };
3785
+ const detectIntent = (text) => {
3786
+ const lower = text.toLowerCase();
3787
+ for (const [intent, triggers] of Object.entries(intentKeywords)) {
3788
+ if (triggers.some((t) => lower.includes(t))) {
3789
+ return intent;
3790
+ }
3791
+ }
3792
+ return "commercial";
3793
+ };
3794
+ for (const prod of products) {
3795
+ results.push({
3796
+ keyword: prod,
3797
+ exactMatch: `[${prod}]`,
3798
+ phraseMatch: `"${prod}"`,
3799
+ intent: detectIntent(prod),
3800
+ product: prod
3801
+ });
3802
+ for (const word of words) {
3803
+ const kw1 = `${word} ${prod}`;
3804
+ const kw2 = `${prod} ${word}`;
3805
+ results.push({
3806
+ keyword: kw1,
3807
+ exactMatch: `[${kw1}]`,
3808
+ phraseMatch: `"${kw1}"`,
3809
+ intent: detectIntent(word),
3810
+ product: prod,
3811
+ modifier: word
3812
+ });
3813
+ results.push({
3814
+ keyword: kw2,
3815
+ exactMatch: `[${kw2}]`,
3816
+ phraseMatch: `"${kw2}"`,
3817
+ intent: detectIntent(word),
3818
+ product: prod,
3819
+ modifier: word
3820
+ });
3821
+ for (const loc of locations) {
3822
+ const kwLoc1 = `${word} ${prod} ${loc}`;
3823
+ const kwLoc2 = `${prod} ${loc} ${word}`;
3824
+ results.push({
3825
+ keyword: kwLoc1,
3826
+ exactMatch: `[${kwLoc1}]`,
3827
+ phraseMatch: `"${kwLoc1}"`,
3828
+ intent: detectIntent(word),
3829
+ product: prod,
3830
+ modifier: word,
3831
+ location: loc
3832
+ });
3833
+ results.push({
3834
+ keyword: kwLoc2,
3835
+ exactMatch: `[${kwLoc2}]`,
3836
+ phraseMatch: `"${kwLoc2}"`,
3837
+ intent: detectIntent(word),
3838
+ product: prod,
3839
+ modifier: word,
3840
+ location: loc
3841
+ });
3842
+ if (results.length >= maxCombinations)
3843
+ return results;
3844
+ }
3845
+ }
3846
+ }
3847
+ return results;
3848
+ }
3849
+ }
3656
3850
  // src/llm-prompt.ts
3657
3851
  var PSEO_AGENT_SYSTEM_PROMPT = `
3658
3852
  You are the Programmatic SEO Implementation Specialist powered by @lynxflow/seo-engine.
@@ -6064,6 +6258,7 @@ export {
6064
6258
  createLynxSeoEngine,
6065
6259
  cleanSeoSlug,
6066
6260
  YoastParityEngine,
6261
+ UrlyticsEngine,
6067
6262
  TokenQuotaManager,
6068
6263
  TechnicalRulesAuditor,
6069
6264
  TeamRbacEngine,
@@ -6095,6 +6290,7 @@ export {
6095
6290
  LegalDisclaimerEngine,
6096
6291
  LagoTokenMeter,
6097
6292
  KnowledgeGraphLinker,
6293
+ KeywordPermutatorEngine,
6098
6294
  IsrCacheManager,
6099
6295
  InstantMatrixSearchEngine,
6100
6296
  IndexNowClient,
@@ -0,0 +1,29 @@
1
+ /**
2
+ * ⚡ High-Speed Keyword Permutator & SEM Matrix Engine
3
+ * Inspired by advertools.kw_generate
4
+ *
5
+ * Generates all permutations and combinations of Products × Modifiers × Intents × Locations
6
+ * with match types (Broad, Phrase, Exact) and estimated search intent tagging.
7
+ */
8
+ export type SearchIntentType = "commercial" | "transactional" | "informational" | "navigational";
9
+ export interface GeneratedKeyword {
10
+ keyword: string;
11
+ exactMatch: string;
12
+ phraseMatch: string;
13
+ intent: SearchIntentType;
14
+ product: string;
15
+ modifier?: string;
16
+ location?: string;
17
+ }
18
+ export interface PermutatorOptions {
19
+ products: string[];
20
+ words?: string[];
21
+ locations?: string[];
22
+ maxCombinations?: number;
23
+ }
24
+ export declare class KeywordPermutatorEngine {
25
+ /**
26
+ * Generates a combinatorial matrix of keywords.
27
+ */
28
+ static generateKeywordMatrix(options: PermutatorOptions): GeneratedKeyword[];
29
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 🔍 Universal URL Analytics & Structural Decomposition Engine
3
+ * Inspired by advertools.urlytics
4
+ *
5
+ * Breaks down any URL into structured path segments, query parameters, directory depths,
6
+ * and slug tokens for crawl validation, audit logs, and programmatic matrix matching.
7
+ */
8
+ export interface ParsedUrlStructure {
9
+ url: string;
10
+ scheme: string;
11
+ domain: string;
12
+ path: string;
13
+ depth: number;
14
+ dir1?: string;
15
+ dir2?: string;
16
+ dir3?: string;
17
+ lastDir: string;
18
+ queryParams: Record<string, string>;
19
+ hashFragment?: string;
20
+ slugTokens: string[];
21
+ charCount: number;
22
+ hasTrailingSlash: boolean;
23
+ }
24
+ export declare class UrlyticsEngine {
25
+ /**
26
+ * Parses a single URL into its granular structural components.
27
+ */
28
+ static parseUrl(rawUrl: string): ParsedUrlStructure;
29
+ /**
30
+ * Batch processes an array of URLs for comparative directory analysis.
31
+ */
32
+ static analyzeUrls(urls: string[]): ParsedUrlStructure[];
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynxflow/seo-engine",
3
- "version": "1.5.5",
3
+ "version": "1.5.6",
4
4
  "description": "High-Performance Multilingual Programmatic SEO & AI Search Engine SDK",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -4,6 +4,9 @@ import { LegalDisclaimerEngine, MULTILINGUAL_BANNED_DISPARAGING_WORDS } from "./
4
4
  import { ExtendedSchemaGraphBuilder } from "./extended-schemas";
5
5
  import { PseoMatrixEngine } from "./matrix-engine";
6
6
  import { getSeoAgentPrompt } from "./llm-prompt";
7
+ import { UrlyticsEngine } from "./urlytics-engine";
8
+ import { KeywordPermutatorEngine } from "./keyword-permutator";
9
+ import { renderBrandIconSvg } from "./brand-icons";
7
10
 
8
11
  describe("Multilingual SEO Slug Engine (10+ Languages)", () => {
9
12
  it("English: Strips stop words and years", () => {
@@ -134,3 +137,53 @@ describe("Master Programmatic Matrix Engine (English Default)", () => {
134
137
  expect(prompt).toContain("Schema.org");
135
138
  });
136
139
  });
140
+
141
+ describe("Audited Reference Engines (Advertools, Santifer, Seonaut)", () => {
142
+ it("UrlyticsEngine: Parses and decomposes URL hierarchy", () => {
143
+ const parsed = UrlyticsEngine.parseUrl("https://acme.com/solutions/crm/fr/paris?src=seo&ref=top#pricing");
144
+ expect(parsed.domain).toBe("acme.com");
145
+ expect(parsed.depth).toBe(4);
146
+ expect(parsed.dir1).toBe("solutions");
147
+ expect(parsed.lastDir).toBe("paris");
148
+ expect(parsed.queryParams.src).toBe("seo");
149
+ expect(parsed.hashFragment).toBe("pricing");
150
+ });
151
+
152
+ it("KeywordPermutatorEngine: Generates combinatorial SEM matrix with intent", () => {
153
+ const matrix = KeywordPermutatorEngine.generateKeywordMatrix({
154
+ products: ["crm", "billing"],
155
+ words: ["best", "pricing"],
156
+ locations: ["paris"],
157
+ });
158
+ expect(matrix.length).toBeGreaterThan(5);
159
+ const hasBestCrm = matrix.some((k) => k.keyword === "best crm" && k.intent === "commercial");
160
+ expect(hasBestCrm).toBe(true);
161
+ });
162
+
163
+ it("BrandIcons: Resolves official SVG vector for Shopify, Facebook, Slack", () => {
164
+ const shopifySvg = renderBrandIconSvg("shopify");
165
+ expect(shopifySvg).toContain("<svg");
166
+ expect(shopifySvg).toContain("viewBox");
167
+
168
+ const fbSvg = renderBrandIconSvg("facebook");
169
+ expect(fbSvg).toContain("<svg");
170
+ });
171
+
172
+ it("ExtendedSchemas: Builds AggregateRating and AggregateOffer (Santifer model)", () => {
173
+ const rating = ExtendedSchemaGraphBuilder.buildAggregateRating({
174
+ itemReviewedName: "Acme CRM",
175
+ ratingValue: 4.9,
176
+ reviewCount: 1280,
177
+ });
178
+ expect(rating["@type"]).toBe("AggregateRating");
179
+ expect(rating.ratingValue).toBe(4.9);
180
+
181
+ const offer = ExtendedSchemaGraphBuilder.buildAggregateOffer({
182
+ lowPrice: 29,
183
+ highPrice: 99,
184
+ currency: "EUR",
185
+ });
186
+ expect(offer["@type"]).toBe("AggregateOffer");
187
+ expect(offer.lowPrice).toBe("29");
188
+ });
189
+ });
@@ -276,4 +276,102 @@ export class ExtendedSchemaGraphBuilder {
276
276
  })),
277
277
  };
278
278
  }
279
+
280
+ /**
281
+ * 7. AggregateRating (Google Gold Stars in SERPs) - Inspired by santifer-irepair
282
+ */
283
+ static buildAggregateRating(opts: {
284
+ ratingValue?: number;
285
+ reviewCount?: number;
286
+ bestRating?: number;
287
+ worstRating?: number;
288
+ itemReviewedName: string;
289
+ }): Record<string, unknown> {
290
+ return {
291
+ "@type": "AggregateRating",
292
+ ratingValue: opts.ratingValue ?? 4.9,
293
+ reviewCount: opts.reviewCount ?? 1280,
294
+ bestRating: opts.bestRating ?? 5,
295
+ worstRating: opts.worstRating ?? 1,
296
+ itemReviewed: {
297
+ "@type": "Thing",
298
+ name: opts.itemReviewedName,
299
+ },
300
+ };
301
+ }
302
+
303
+ /**
304
+ * 8. AggregateOffer (Price Ranges & Tiers) - Inspired by santifer-irepair
305
+ */
306
+ static buildAggregateOffer(opts: {
307
+ lowPrice: number;
308
+ highPrice: number;
309
+ currency: string;
310
+ offerCount?: number;
311
+ description?: string;
312
+ }): Record<string, unknown> {
313
+ return {
314
+ "@type": "AggregateOffer",
315
+ lowPrice: String(opts.lowPrice),
316
+ highPrice: String(opts.highPrice),
317
+ priceCurrency: opts.currency,
318
+ offerCount: String(opts.offerCount ?? 3),
319
+ description: opts.description || "Transparent pricing with zero lock-in",
320
+ };
321
+ }
322
+
323
+ /**
324
+ * 9. Service with ServiceArea & Providers
325
+ */
326
+ static buildService(opts: {
327
+ name: string;
328
+ description: string;
329
+ providerName: string;
330
+ areaServed?: string[];
331
+ serviceType?: string;
332
+ }): Record<string, unknown> {
333
+ return {
334
+ "@context": "https://schema.org",
335
+ "@type": "Service",
336
+ name: opts.name,
337
+ description: opts.description,
338
+ serviceType: opts.serviceType || "SoftwareService",
339
+ provider: {
340
+ "@type": "Organization",
341
+ name: opts.providerName,
342
+ },
343
+ areaServed: opts.areaServed
344
+ ? opts.areaServed.map((city) => ({
345
+ "@type": "City",
346
+ name: city,
347
+ }))
348
+ : undefined,
349
+ };
350
+ }
351
+
352
+ /**
353
+ * 10. WebSite with Google Sitelinks SearchBox
354
+ */
355
+ static buildWebSite(opts: {
356
+ name: string;
357
+ url: string;
358
+ searchUrlTemplate?: string;
359
+ }): Record<string, unknown> {
360
+ return {
361
+ "@context": "https://schema.org",
362
+ "@type": "WebSite",
363
+ name: opts.name,
364
+ url: opts.url,
365
+ potentialAction: opts.searchUrlTemplate
366
+ ? {
367
+ "@type": "SearchAction",
368
+ target: {
369
+ "@type": "EntryPoint",
370
+ urlTemplate: opts.searchUrlTemplate,
371
+ },
372
+ "query-input": "required name=search_term_string",
373
+ }
374
+ : undefined,
375
+ };
376
+ }
279
377
  }
package/src/index.ts CHANGED
@@ -15,6 +15,8 @@ export * from "./slug-engine";
15
15
  export * from "./legal-disclaimers";
16
16
  export * from "./matrix-engine";
17
17
  export * from "./brand-icons";
18
+ export * from "./urlytics-engine";
19
+ export * from "./keyword-permutator";
18
20
  export * from "./llm-prompt";
19
21
  export * from "./auth-key";
20
22
  export * from "./token-quota-manager";
@@ -0,0 +1,118 @@
1
+ /**
2
+ * ⚡ High-Speed Keyword Permutator & SEM Matrix Engine
3
+ * Inspired by advertools.kw_generate
4
+ *
5
+ * Generates all permutations and combinations of Products × Modifiers × Intents × Locations
6
+ * with match types (Broad, Phrase, Exact) and estimated search intent tagging.
7
+ */
8
+
9
+ export type SearchIntentType = "commercial" | "transactional" | "informational" | "navigational";
10
+
11
+ export interface GeneratedKeyword {
12
+ keyword: string;
13
+ exactMatch: string;
14
+ phraseMatch: string;
15
+ intent: SearchIntentType;
16
+ product: string;
17
+ modifier?: string;
18
+ location?: string;
19
+ }
20
+
21
+ export interface PermutatorOptions {
22
+ products: string[];
23
+ words?: string[];
24
+ locations?: string[];
25
+ maxCombinations?: number;
26
+ }
27
+
28
+ export class KeywordPermutatorEngine {
29
+ /**
30
+ * Generates a combinatorial matrix of keywords.
31
+ */
32
+ static generateKeywordMatrix(options: PermutatorOptions): GeneratedKeyword[] {
33
+ const { products, words = [], locations = [], maxCombinations = 5000 } = options;
34
+ const results: GeneratedKeyword[] = [];
35
+
36
+ const intentKeywords: Record<SearchIntentType, string[]> = {
37
+ transactional: ["buy", "pricing", "cost", "hire", "quote", "tarif", "prix", "devis", "acheter"],
38
+ commercial: ["best", "top", "review", "vs", "comparison", "alternative", "comparatif", "meilleur"],
39
+ informational: ["how to", "what is", "guide", "tutorial", "definition", "comment", "quest ce que"],
40
+ navigational: ["login", "app", "portal", "website", "connexion"],
41
+ };
42
+
43
+ const detectIntent = (text: string): SearchIntentType => {
44
+ const lower = text.toLowerCase();
45
+ for (const [intent, triggers] of Object.entries(intentKeywords) as [SearchIntentType, string[]][]) {
46
+ if (triggers.some((t) => lower.includes(t))) {
47
+ return intent;
48
+ }
49
+ }
50
+ return "commercial";
51
+ };
52
+
53
+ for (const prod of products) {
54
+ // 1. Product alone
55
+ results.push({
56
+ keyword: prod,
57
+ exactMatch: `[${prod}]`,
58
+ phraseMatch: `"${prod}"`,
59
+ intent: detectIntent(prod),
60
+ product: prod,
61
+ });
62
+
63
+ // 2. Product × Words / Modifiers
64
+ for (const word of words) {
65
+ const kw1 = `${word} ${prod}`;
66
+ const kw2 = `${prod} ${word}`;
67
+
68
+ results.push({
69
+ keyword: kw1,
70
+ exactMatch: `[${kw1}]`,
71
+ phraseMatch: `"${kw1}"`,
72
+ intent: detectIntent(word),
73
+ product: prod,
74
+ modifier: word,
75
+ });
76
+
77
+ results.push({
78
+ keyword: kw2,
79
+ exactMatch: `[${kw2}]`,
80
+ phraseMatch: `"${kw2}"`,
81
+ intent: detectIntent(word),
82
+ product: prod,
83
+ modifier: word,
84
+ });
85
+
86
+ // 3. Product × Words × Locations
87
+ for (const loc of locations) {
88
+ const kwLoc1 = `${word} ${prod} ${loc}`;
89
+ const kwLoc2 = `${prod} ${loc} ${word}`;
90
+
91
+ results.push({
92
+ keyword: kwLoc1,
93
+ exactMatch: `[${kwLoc1}]`,
94
+ phraseMatch: `"${kwLoc1}"`,
95
+ intent: detectIntent(word),
96
+ product: prod,
97
+ modifier: word,
98
+ location: loc,
99
+ });
100
+
101
+ results.push({
102
+ keyword: kwLoc2,
103
+ exactMatch: `[${kwLoc2}]`,
104
+ phraseMatch: `"${kwLoc2}"`,
105
+ intent: detectIntent(word),
106
+ product: prod,
107
+ modifier: word,
108
+ location: loc,
109
+ });
110
+
111
+ if (results.length >= maxCombinations) return results;
112
+ }
113
+ }
114
+ }
115
+
116
+ return results;
117
+ }
118
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * 🔍 Universal URL Analytics & Structural Decomposition Engine
3
+ * Inspired by advertools.urlytics
4
+ *
5
+ * Breaks down any URL into structured path segments, query parameters, directory depths,
6
+ * and slug tokens for crawl validation, audit logs, and programmatic matrix matching.
7
+ */
8
+
9
+ export interface ParsedUrlStructure {
10
+ url: string;
11
+ scheme: string;
12
+ domain: string;
13
+ path: string;
14
+ depth: number;
15
+ dir1?: string;
16
+ dir2?: string;
17
+ dir3?: string;
18
+ lastDir: string;
19
+ queryParams: Record<string, string>;
20
+ hashFragment?: string;
21
+ slugTokens: string[];
22
+ charCount: number;
23
+ hasTrailingSlash: boolean;
24
+ }
25
+
26
+ export class UrlyticsEngine {
27
+ /**
28
+ * Parses a single URL into its granular structural components.
29
+ */
30
+ static parseUrl(rawUrl: string): ParsedUrlStructure {
31
+ if (!rawUrl || typeof rawUrl !== "string") {
32
+ return {
33
+ url: "",
34
+ scheme: "",
35
+ domain: "",
36
+ path: "/",
37
+ depth: 0,
38
+ lastDir: "",
39
+ queryParams: {},
40
+ slugTokens: [],
41
+ charCount: 0,
42
+ hasTrailingSlash: false,
43
+ };
44
+ }
45
+
46
+ try {
47
+ const parsed = new URL(rawUrl.startsWith("http") ? rawUrl : `https://${rawUrl}`);
48
+ const pathClean = parsed.pathname.replace(/\/+$/, "");
49
+ const segments = pathClean.split("/").filter(Boolean);
50
+ const queryParams: Record<string, string> = {};
51
+
52
+ parsed.searchParams.forEach((val, key) => {
53
+ queryParams[key] = val;
54
+ });
55
+
56
+ const lastDir = segments.length > 0 ? segments[segments.length - 1] : "";
57
+ const slugTokens = lastDir.split("-").filter(Boolean);
58
+
59
+ return {
60
+ url: rawUrl,
61
+ scheme: parsed.protocol.replace(":", ""),
62
+ domain: parsed.hostname,
63
+ path: parsed.pathname,
64
+ depth: segments.length,
65
+ dir1: segments[0],
66
+ dir2: segments[1],
67
+ dir3: segments[2],
68
+ lastDir,
69
+ queryParams,
70
+ hashFragment: parsed.hash ? parsed.hash.replace("#", "") : undefined,
71
+ slugTokens,
72
+ charCount: rawUrl.length,
73
+ hasTrailingSlash: parsed.pathname.length > 1 && parsed.pathname.endsWith("/"),
74
+ };
75
+ } catch {
76
+ return {
77
+ url: rawUrl,
78
+ scheme: "unknown",
79
+ domain: "",
80
+ path: rawUrl,
81
+ depth: 0,
82
+ lastDir: rawUrl,
83
+ queryParams: {},
84
+ slugTokens: [rawUrl],
85
+ charCount: rawUrl.length,
86
+ hasTrailingSlash: false,
87
+ };
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Batch processes an array of URLs for comparative directory analysis.
93
+ */
94
+ static analyzeUrls(urls: string[]): ParsedUrlStructure[] {
95
+ return urls.map((u) => this.parseUrl(u));
96
+ }
97
+ }