@geenius/seo 0.1.0 → 0.3.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/package.json +16 -3
- package/packages/convex/dist/index.d.ts +56 -0
- package/packages/convex/dist/index.js +133 -0
- package/packages/convex/dist/index.js.map +1 -0
- package/packages/react/README.md +1 -1
- package/packages/react/dist/index.d.ts +156 -0
- package/packages/react/dist/index.js +567 -0
- package/packages/react/dist/index.js.map +1 -0
- package/packages/react-css/README.md +1 -1
- package/packages/react-css/dist/index.cjs +571 -0
- package/packages/react-css/dist/index.cjs.map +1 -0
- package/packages/react-css/{src/seo.css → dist/index.css} +7 -153
- package/packages/react-css/dist/index.css.map +1 -0
- package/packages/react-css/dist/index.d.cts +53 -0
- package/packages/react-css/dist/index.d.ts +53 -0
- package/packages/react-css/dist/index.js +539 -0
- package/packages/react-css/dist/index.js.map +1 -0
- package/packages/shared/README.md +1 -1
- package/packages/shared/dist/index.d.ts +262 -0
- package/packages/shared/dist/index.js +381 -0
- package/packages/shared/dist/index.js.map +1 -0
- package/packages/solidjs/README.md +1 -1
- package/packages/solidjs/dist/index.d.ts +133 -0
- package/packages/solidjs/dist/index.js +416 -0
- package/packages/solidjs/dist/index.js.map +1 -0
- package/packages/solidjs-css/README.md +1 -1
- package/packages/solidjs-css/dist/index.cjs +399 -0
- package/packages/solidjs-css/dist/index.cjs.map +1 -0
- package/packages/solidjs-css/{src/seo.css → dist/index.css} +7 -153
- package/packages/solidjs-css/dist/index.css.map +1 -0
- package/packages/solidjs-css/dist/index.d.cts +53 -0
- package/packages/solidjs-css/dist/index.d.ts +53 -0
- package/packages/solidjs-css/dist/index.js +367 -0
- package/packages/solidjs-css/dist/index.js.map +1 -0
- package/.changeset/config.json +0 -11
- package/.github/CODEOWNERS +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/ci.yml +0 -23
- package/.github/workflows/release.yml +0 -29
- package/.nvmrc +0 -1
- package/.project/ACCOUNT.yaml +0 -4
- package/.project/IDEAS.yaml +0 -7
- package/.project/PROJECT.yaml +0 -11
- package/.project/ROADMAP.yaml +0 -15
- package/CODE_OF_CONDUCT.md +0 -16
- package/CONTRIBUTING.md +0 -26
- package/SECURITY.md +0 -15
- package/SUPPORT.md +0 -8
- package/packages/convex/package.json +0 -42
- package/packages/convex/src/functions.ts +0 -5
- package/packages/convex/src/mutations.ts +0 -83
- package/packages/convex/src/queries.ts +0 -57
- package/packages/convex/src/schema.ts +0 -23
- package/packages/convex/tsconfig.json +0 -19
- package/packages/convex/tsup.config.ts +0 -18
- package/packages/react/package.json +0 -49
- package/packages/react/src/components/ArticleJsonLd.tsx +0 -42
- package/packages/react/src/components/BreadcrumbsJsonLd.tsx +0 -24
- package/packages/react/src/components/MetaEditor.tsx +0 -147
- package/packages/react/src/components/SEOHead.tsx +0 -107
- package/packages/react/src/components/SEOPreview.tsx +0 -42
- package/packages/react/src/components/SEOScoreCard.tsx +0 -51
- package/packages/react/src/components/SitemapViewer.tsx +0 -36
- package/packages/react/src/components/index.ts +0 -7
- package/packages/react/src/hooks/index.ts +0 -4
- package/packages/react/src/hooks/useSEO.ts +0 -27
- package/packages/react/src/hooks/useSEOAdmin.ts +0 -42
- package/packages/react/src/hooks/useSEOScore.ts +0 -7
- package/packages/react/src/hooks/useSitemap.ts +0 -8
- package/packages/react/src/index.ts +0 -51
- package/packages/react/src/index.tsx +0 -11
- package/packages/react/src/pages/SEOAdminPage.tsx +0 -101
- package/packages/react/src/pages/SEOAnalyticsPage.tsx +0 -96
- package/packages/react/src/pages/index.ts +0 -2
- package/packages/react/tsconfig.json +0 -19
- package/packages/react/tsup.config.ts +0 -12
- package/packages/react-css/package.json +0 -36
- package/packages/react-css/src/components/ArticleJsonLd.tsx +0 -42
- package/packages/react-css/src/components/BreadcrumbsJsonLd.tsx +0 -24
- package/packages/react-css/src/components/MetaEditor.tsx +0 -147
- package/packages/react-css/src/components/SEOHead.tsx +0 -95
- package/packages/react-css/src/components/SEOPreview.tsx +0 -42
- package/packages/react-css/src/components/SEOScoreCard.tsx +0 -42
- package/packages/react-css/src/components/SitemapViewer.tsx +0 -36
- package/packages/react-css/src/components/index.ts +0 -7
- package/packages/react-css/src/index.ts +0 -9
- package/packages/react-css/src/pages/SEOAdminPage.tsx +0 -88
- package/packages/react-css/src/pages/SEOAnalyticsPage.tsx +0 -82
- package/packages/react-css/src/pages/index.ts +0 -2
- package/packages/react-css/tsup.config.ts +0 -2
- package/packages/shared/package.json +0 -42
- package/packages/shared/src/__tests__/seo.test.ts +0 -70
- package/packages/shared/src/config.ts +0 -297
- package/packages/shared/src/index.ts +0 -207
- package/packages/shared/tsconfig.json +0 -18
- package/packages/shared/tsup.config.ts +0 -11
- package/packages/shared/vitest.config.ts +0 -4
- package/packages/solidjs/package.json +0 -45
- package/packages/solidjs/src/components/ArticleJsonLd.tsx +0 -35
- package/packages/solidjs/src/components/BreadcrumbsJsonLd.tsx +0 -24
- package/packages/solidjs/src/components/MetaEditor.tsx +0 -155
- package/packages/solidjs/src/components/SEOHead.tsx +0 -109
- package/packages/solidjs/src/components/SEOPreview.tsx +0 -42
- package/packages/solidjs/src/components/SEOScoreCard.tsx +0 -57
- package/packages/solidjs/src/components/SitemapViewer.tsx +0 -44
- package/packages/solidjs/src/components/index.ts +0 -7
- package/packages/solidjs/src/index.ts +0 -11
- package/packages/solidjs/src/pages/SEOAdminPage.tsx +0 -104
- package/packages/solidjs/src/pages/SEOAnalyticsPage.tsx +0 -102
- package/packages/solidjs/src/pages/index.ts +0 -2
- package/packages/solidjs/src/primitives/index.ts +0 -4
- package/packages/solidjs/src/primitives/useSEO.ts +0 -27
- package/packages/solidjs/src/primitives/useSEOAdmin.ts +0 -42
- package/packages/solidjs/src/primitives/useSEOScore.ts +0 -7
- package/packages/solidjs/src/primitives/useSitemap.ts +0 -8
- package/packages/solidjs/tsconfig.json +0 -20
- package/packages/solidjs/tsup.config.ts +0 -12
- package/packages/solidjs-css/package.json +0 -35
- package/packages/solidjs-css/src/index.ts +0 -5
- package/packages/solidjs-css/src/primitives/index.ts +0 -1
- package/packages/solidjs-css/tsup.config.ts +0 -2
- package/pnpm-workspace.yaml +0 -2
- package/tsconfig.json +0 -23
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geenius/seo",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Geenius Seo — SEO utilities for SaaS apps (React + SolidJS)",
|
|
6
6
|
"author": "Antigravity HQ",
|
|
7
7
|
"license": "MIT",
|
|
@@ -19,19 +19,32 @@
|
|
|
19
19
|
"solidjs",
|
|
20
20
|
"geenius"
|
|
21
21
|
],
|
|
22
|
+
"files": [
|
|
23
|
+
"packages/shared/dist",
|
|
24
|
+
"packages/react/dist",
|
|
25
|
+
"packages/solidjs/dist",
|
|
26
|
+
"packages/convex/dist",
|
|
27
|
+
"packages/react-css/dist",
|
|
28
|
+
"packages/solidjs-css/dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"CHANGELOG.md"
|
|
32
|
+
],
|
|
22
33
|
"scripts": {
|
|
23
34
|
"dev": "pnpm -r --parallel type-check",
|
|
24
35
|
"build": "pnpm -r build",
|
|
25
36
|
"clean": "pnpm -r clean",
|
|
26
37
|
"lint": "pnpm -r --parallel type-check",
|
|
27
|
-
"test": "
|
|
38
|
+
"test": "pnpm -r --parallel test",
|
|
28
39
|
"type-check": "pnpm -r type-check",
|
|
29
40
|
"format": "prettier --write \"packages/*/src/**/*.{ts,tsx}\"",
|
|
30
41
|
"version:patch": "pnpm -r exec -- npm version patch --no-git-tag-version && npm version patch -m 'v%s'",
|
|
31
42
|
"version:minor": "pnpm -r exec -- npm version minor --no-git-tag-version && npm version minor -m 'v%s'",
|
|
32
43
|
"version:major": "pnpm -r exec -- npm version major --no-git-tag-version && npm version major -m 'v%s'",
|
|
33
44
|
"release": "git push && git push --tags",
|
|
34
|
-
"publish:all": "pnpm -r publish --access public"
|
|
45
|
+
"publish:all": "pnpm -r publish --access public",
|
|
46
|
+
"publish:root": "npm publish --access public",
|
|
47
|
+
"prepublishOnly": "pnpm build"
|
|
35
48
|
},
|
|
36
49
|
"devDependencies": {
|
|
37
50
|
"prettier": "^3.8.1",
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as convex_server from 'convex/server';
|
|
2
|
+
import * as convex_values from 'convex/values';
|
|
3
|
+
|
|
4
|
+
declare const seoTables: {
|
|
5
|
+
seo_pages: convex_server.TableDefinition<convex_values.VObject<{
|
|
6
|
+
path: string;
|
|
7
|
+
meta: any;
|
|
8
|
+
lastModified: string;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
}, {
|
|
11
|
+
path: convex_values.VString<string, "required">;
|
|
12
|
+
meta: convex_values.VAny<any, "required", string>;
|
|
13
|
+
lastModified: convex_values.VString<string, "required">;
|
|
14
|
+
createdAt: convex_values.VString<string, "required">;
|
|
15
|
+
}, "required", "path" | "meta" | "lastModified" | "createdAt" | `meta.${string}`>, {
|
|
16
|
+
by_path: ["path", "_creationTime"];
|
|
17
|
+
by_modified: ["lastModified", "_creationTime"];
|
|
18
|
+
}, {}, {}>;
|
|
19
|
+
seo_analytics: convex_server.TableDefinition<convex_values.VObject<{
|
|
20
|
+
path: string;
|
|
21
|
+
views: number;
|
|
22
|
+
avgTimeOnPage: number;
|
|
23
|
+
bounceRate: number;
|
|
24
|
+
date: string;
|
|
25
|
+
}, {
|
|
26
|
+
path: convex_values.VString<string, "required">;
|
|
27
|
+
views: convex_values.VFloat64<number, "required">;
|
|
28
|
+
avgTimeOnPage: convex_values.VFloat64<number, "required">;
|
|
29
|
+
bounceRate: convex_values.VFloat64<number, "required">;
|
|
30
|
+
date: convex_values.VString<string, "required">;
|
|
31
|
+
}, "required", "path" | "views" | "avgTimeOnPage" | "bounceRate" | "date">, {
|
|
32
|
+
by_path: ["path", "_creationTime"];
|
|
33
|
+
by_date: ["date", "_creationTime"];
|
|
34
|
+
}, {}, {}>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Get SEO metadata for a specific page path. */
|
|
38
|
+
declare const getSEOMeta: convex_server.RegisteredQuery<"public", any, Promise<any>>;
|
|
39
|
+
/** List all pages with SEO metadata. */
|
|
40
|
+
declare const listPages: convex_server.RegisteredQuery<"public", any, Promise<any>>;
|
|
41
|
+
/** Get analytics for a specific page within a date range. */
|
|
42
|
+
declare const getPageAnalytics: convex_server.RegisteredQuery<"public", any, Promise<any>>;
|
|
43
|
+
/** Get top pages by total views. */
|
|
44
|
+
declare const getTopPages: convex_server.RegisteredQuery<"public", any, Promise<{
|
|
45
|
+
path: string;
|
|
46
|
+
views: number;
|
|
47
|
+
}[]>>;
|
|
48
|
+
|
|
49
|
+
/** Create or update SEO metadata for a page. */
|
|
50
|
+
declare const upsertSEOMeta: convex_server.RegisteredMutation<"public", any, Promise<any>>;
|
|
51
|
+
/** Create or update analytics for a page on a specific date. */
|
|
52
|
+
declare const updateAnalytics: convex_server.RegisteredMutation<"public", any, Promise<any>>;
|
|
53
|
+
/** Delete SEO metadata for a page. */
|
|
54
|
+
declare const deleteSEOMeta: convex_server.RegisteredMutation<"public", any, Promise<boolean>>;
|
|
55
|
+
|
|
56
|
+
export { deleteSEOMeta, getPageAnalytics, getSEOMeta, getTopPages, listPages, seoTables, updateAnalytics, upsertSEOMeta };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// src/schema.ts
|
|
2
|
+
import { defineTable } from "convex/server";
|
|
3
|
+
import { v } from "convex/values";
|
|
4
|
+
var seoTables = {
|
|
5
|
+
// ─── SEO Pages ──────────────────────────────────────────────────────────
|
|
6
|
+
seo_pages: defineTable({
|
|
7
|
+
path: v.string(),
|
|
8
|
+
meta: v.any(),
|
|
9
|
+
lastModified: v.string(),
|
|
10
|
+
createdAt: v.string()
|
|
11
|
+
}).index("by_path", ["path"]).index("by_modified", ["lastModified"]),
|
|
12
|
+
// ─── SEO Analytics ──────────────────────────────────────────────────────
|
|
13
|
+
seo_analytics: defineTable({
|
|
14
|
+
path: v.string(),
|
|
15
|
+
views: v.number(),
|
|
16
|
+
avgTimeOnPage: v.number(),
|
|
17
|
+
bounceRate: v.number(),
|
|
18
|
+
date: v.string()
|
|
19
|
+
}).index("by_path", ["path"]).index("by_date", ["date"])
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/queries.ts
|
|
23
|
+
import { queryGeneric } from "convex/server";
|
|
24
|
+
import { v as v2 } from "convex/values";
|
|
25
|
+
var getSEOMeta = queryGeneric({
|
|
26
|
+
args: { path: v2.string() },
|
|
27
|
+
handler: async (ctx, args) => {
|
|
28
|
+
return await ctx.db.query("seo_pages").withIndex("by_path", (q) => q.eq("path", args.path)).first();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
var listPages = queryGeneric({
|
|
32
|
+
args: { limit: v2.optional(v2.number()) },
|
|
33
|
+
handler: async (ctx, args) => {
|
|
34
|
+
return await ctx.db.query("seo_pages").order("desc").take(args.limit ?? 100);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
var getPageAnalytics = queryGeneric({
|
|
38
|
+
args: { path: v2.string(), days: v2.optional(v2.number()) },
|
|
39
|
+
handler: async (ctx, args) => {
|
|
40
|
+
const daysAgo = args.days ?? 30;
|
|
41
|
+
const cutoffDate = /* @__PURE__ */ new Date();
|
|
42
|
+
cutoffDate.setDate(cutoffDate.getDate() - daysAgo);
|
|
43
|
+
const cutoffIso = cutoffDate.toISOString().split("T")[0];
|
|
44
|
+
return await ctx.db.query("seo_analytics").withIndex("by_path", (q) => q.eq("path", args.path)).filter((q) => q.gte(q.field("date"), cutoffIso)).collect();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
var getTopPages = queryGeneric({
|
|
48
|
+
args: { limit: v2.optional(v2.number()) },
|
|
49
|
+
handler: async (ctx, args) => {
|
|
50
|
+
const allAnalytics = await ctx.db.query("seo_analytics").collect();
|
|
51
|
+
const grouped = {};
|
|
52
|
+
for (const entry of allAnalytics) {
|
|
53
|
+
grouped[entry.path] = (grouped[entry.path] ?? 0) + entry.views;
|
|
54
|
+
}
|
|
55
|
+
return Object.entries(grouped).sort((a, b) => b[1] - a[1]).slice(0, args.limit ?? 10).map(([path, views]) => ({ path, views }));
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// src/mutations.ts
|
|
60
|
+
import { mutationGeneric } from "convex/server";
|
|
61
|
+
import { v as v3 } from "convex/values";
|
|
62
|
+
var upsertSEOMeta = mutationGeneric({
|
|
63
|
+
args: {
|
|
64
|
+
path: v3.string(),
|
|
65
|
+
meta: v3.any()
|
|
66
|
+
},
|
|
67
|
+
handler: async (ctx, args) => {
|
|
68
|
+
const existing = await ctx.db.query("seo_pages").withIndex("by_path", (q) => q.eq("path", args.path)).first();
|
|
69
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
70
|
+
if (existing) {
|
|
71
|
+
await ctx.db.patch(existing._id, {
|
|
72
|
+
meta: args.meta,
|
|
73
|
+
lastModified: now
|
|
74
|
+
});
|
|
75
|
+
return existing._id;
|
|
76
|
+
}
|
|
77
|
+
return await ctx.db.insert("seo_pages", {
|
|
78
|
+
path: args.path,
|
|
79
|
+
meta: args.meta,
|
|
80
|
+
lastModified: now,
|
|
81
|
+
createdAt: now
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
var updateAnalytics = mutationGeneric({
|
|
86
|
+
args: {
|
|
87
|
+
path: v3.string(),
|
|
88
|
+
views: v3.number(),
|
|
89
|
+
avgTime: v3.number(),
|
|
90
|
+
bounceRate: v3.number(),
|
|
91
|
+
date: v3.string()
|
|
92
|
+
},
|
|
93
|
+
handler: async (ctx, args) => {
|
|
94
|
+
const existing = await ctx.db.query("seo_analytics").withIndex("by_path", (q) => q.eq("path", args.path)).filter((q) => q.eq(q.field("date"), args.date)).first();
|
|
95
|
+
if (existing) {
|
|
96
|
+
await ctx.db.patch(existing._id, {
|
|
97
|
+
views: args.views,
|
|
98
|
+
avgTimeOnPage: args.avgTime,
|
|
99
|
+
bounceRate: args.bounceRate
|
|
100
|
+
});
|
|
101
|
+
return existing._id;
|
|
102
|
+
}
|
|
103
|
+
return await ctx.db.insert("seo_analytics", {
|
|
104
|
+
path: args.path,
|
|
105
|
+
views: args.views,
|
|
106
|
+
avgTimeOnPage: args.avgTime,
|
|
107
|
+
bounceRate: args.bounceRate,
|
|
108
|
+
date: args.date
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
var deleteSEOMeta = mutationGeneric({
|
|
113
|
+
args: { path: v3.string() },
|
|
114
|
+
handler: async (ctx, args) => {
|
|
115
|
+
const entry = await ctx.db.query("seo_pages").withIndex("by_path", (q) => q.eq("path", args.path)).first();
|
|
116
|
+
if (entry) {
|
|
117
|
+
await ctx.db.delete(entry._id);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
export {
|
|
124
|
+
deleteSEOMeta,
|
|
125
|
+
getPageAnalytics,
|
|
126
|
+
getSEOMeta,
|
|
127
|
+
getTopPages,
|
|
128
|
+
listPages,
|
|
129
|
+
seoTables,
|
|
130
|
+
updateAnalytics,
|
|
131
|
+
upsertSEOMeta
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schema.ts","../src/queries.ts","../src/mutations.ts"],"sourcesContent":["// @geenius/seo-convex — src/schema.ts\n\n/**\n * Convex schema definitions for the SEO system.\n * Provides tables for page metadata and analytics tracking.\n *\n * @example\n * ```ts\n * // In your app's convex/schema.ts:\n * import { seoTables } from '@geenius/seo/convex'\n *\n * export default defineSchema({\n * ...seoTables,\n * // your other tables...\n * })\n * ```\n */\n\nimport { defineTable } from 'convex/server'\nimport { v } from 'convex/values'\n\nexport const seoTables = {\n // ─── SEO Pages ──────────────────────────────────────────────────────────\n seo_pages: defineTable({\n path: v.string(),\n meta: v.any(),\n lastModified: v.string(),\n createdAt: v.string(),\n })\n .index('by_path', ['path'])\n .index('by_modified', ['lastModified']),\n\n // ─── SEO Analytics ──────────────────────────────────────────────────────\n seo_analytics: defineTable({\n path: v.string(),\n views: v.number(),\n avgTimeOnPage: v.number(),\n bounceRate: v.number(),\n date: v.string(),\n })\n .index('by_path', ['path'])\n .index('by_date', ['date']),\n}\n","// @geenius/seo-convex — src/queries.ts\n\n/**\n * Convex queries for the SEO system.\n * Read operations for page metadata and analytics.\n */\n\nimport { queryGeneric } from 'convex/server'\nimport { v } from 'convex/values'\n\n// ─── Page Metadata Queries ────────────────────────────────────────────────────\n\n/** Get SEO metadata for a specific page path. */\nexport const getSEOMeta = queryGeneric({\n args: { path: v.string() },\n handler: async (ctx: any, args: any) => {\n return await ctx.db\n .query('seo_pages')\n .withIndex('by_path', (q: any) => q.eq('path', args.path))\n .first()\n },\n})\n\n/** List all pages with SEO metadata. */\nexport const listPages = queryGeneric({\n args: { limit: v.optional(v.number()) },\n handler: async (ctx: any, args: any) => {\n return await ctx.db\n .query('seo_pages')\n .order('desc')\n .take(args.limit ?? 100)\n },\n})\n\n// ─── Analytics Queries ────────────────────────────────────────────────────────\n\n/** Get analytics for a specific page within a date range. */\nexport const getPageAnalytics = queryGeneric({\n args: { path: v.string(), days: v.optional(v.number()) },\n handler: async (ctx: any, args: any) => {\n const daysAgo = args.days ?? 30\n const cutoffDate = new Date()\n cutoffDate.setDate(cutoffDate.getDate() - daysAgo)\n const cutoffIso = cutoffDate.toISOString().split('T')[0]\n\n return await ctx.db\n .query('seo_analytics')\n .withIndex('by_path', (q: any) => q.eq('path', args.path))\n .filter((q: any) => q.gte(q.field('date'), cutoffIso))\n .collect()\n },\n})\n\n/** Get top pages by total views. */\nexport const getTopPages = queryGeneric({\n args: { limit: v.optional(v.number()) },\n handler: async (ctx: any, args: any) => {\n const allAnalytics = await ctx.db.query('seo_analytics').collect()\n\n const grouped: Record<string, number> = {}\n for (const entry of allAnalytics) {\n grouped[entry.path] = (grouped[entry.path] ?? 0) + entry.views\n }\n\n return Object.entries(grouped)\n .sort((a, b) => b[1] - a[1])\n .slice(0, args.limit ?? 10)\n .map(([path, views]) => ({ path, views }))\n },\n})\n","// @geenius/seo-convex — src/mutations.ts\n\n/**\n * Convex mutations for the SEO system.\n * Write operations for page metadata and analytics.\n */\n\nimport { mutationGeneric } from 'convex/server'\nimport { v } from 'convex/values'\n\n// ─── Page Metadata Mutations ──────────────────────────────────────────────────\n\n/** Create or update SEO metadata for a page. */\nexport const upsertSEOMeta = mutationGeneric({\n args: {\n path: v.string(),\n meta: v.any(),\n },\n handler: async (ctx: any, args: any) => {\n const existing = await ctx.db\n .query('seo_pages')\n .withIndex('by_path', (q: any) => q.eq('path', args.path))\n .first()\n\n const now = new Date().toISOString()\n\n if (existing) {\n await ctx.db.patch(existing._id, {\n meta: args.meta,\n lastModified: now,\n })\n return existing._id\n }\n\n return await ctx.db.insert('seo_pages', {\n path: args.path,\n meta: args.meta,\n lastModified: now,\n createdAt: now,\n })\n },\n})\n\n// ─── Analytics Mutations ──────────────────────────────────────────────────────\n\n/** Create or update analytics for a page on a specific date. */\nexport const updateAnalytics = mutationGeneric({\n args: {\n path: v.string(),\n views: v.number(),\n avgTime: v.number(),\n bounceRate: v.number(),\n date: v.string(),\n },\n handler: async (ctx: any, args: any) => {\n const existing = await ctx.db\n .query('seo_analytics')\n .withIndex('by_path', (q: any) => q.eq('path', args.path))\n .filter((q: any) => q.eq(q.field('date'), args.date))\n .first()\n\n if (existing) {\n await ctx.db.patch(existing._id, {\n views: args.views,\n avgTimeOnPage: args.avgTime,\n bounceRate: args.bounceRate,\n })\n return existing._id\n }\n\n return await ctx.db.insert('seo_analytics', {\n path: args.path,\n views: args.views,\n avgTimeOnPage: args.avgTime,\n bounceRate: args.bounceRate,\n date: args.date,\n })\n },\n})\n\n/** Delete SEO metadata for a page. */\nexport const deleteSEOMeta = mutationGeneric({\n args: { path: v.string() },\n handler: async (ctx: any, args: any) => {\n const entry = await ctx.db\n .query('seo_pages')\n .withIndex('by_path', (q: any) => q.eq('path', args.path))\n .first()\n\n if (entry) {\n await ctx.db.delete(entry._id)\n return true\n }\n return false\n },\n})\n"],"mappings":"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","names":["v","v"]}
|
package/packages/react/README.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
# ✦ @geenius-
|
|
1
|
+
# ✦ @geenius/seo-react\n\n> Geenius Seo — React components & hooks\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius/seo-react\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius/seo-react';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { SEOConfig, SEOMeta, SitemapEntry } from '@geenius/seo-shared';
|
|
2
|
+
export * from '@geenius/seo-shared';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
interface SeoContextValue {
|
|
7
|
+
config: SEOConfig;
|
|
8
|
+
}
|
|
9
|
+
interface SeoProviderProps {
|
|
10
|
+
config: SEOConfig;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
declare function SeoProvider({ config, children }: SeoProviderProps): react_jsx_runtime.JSX.Element;
|
|
14
|
+
declare function useSEOContext(): SeoContextValue;
|
|
15
|
+
|
|
16
|
+
interface UseSEOOptions {
|
|
17
|
+
/** Page path to load SEO metadata for */
|
|
18
|
+
path?: string;
|
|
19
|
+
/** Initial metadata (used when no backend is available) */
|
|
20
|
+
initialMeta?: SEOMeta;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Hook to manage page SEO metadata.
|
|
24
|
+
* Supports both local state and optional Convex backend integration.
|
|
25
|
+
*/
|
|
26
|
+
declare function useSEO(options?: UseSEOOptions): {
|
|
27
|
+
meta: SEOMeta | null;
|
|
28
|
+
updateMeta: (newMeta: SEOMeta) => void;
|
|
29
|
+
isLoading: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
interface PageEntry {
|
|
33
|
+
path: string;
|
|
34
|
+
meta: SEOMeta;
|
|
35
|
+
}
|
|
36
|
+
interface UseSEOAdminOptions {
|
|
37
|
+
/** Pre-loaded pages (e.g. from Convex useQuery) */
|
|
38
|
+
initialPages?: PageEntry[];
|
|
39
|
+
/** Callback when a page is upserted (e.g. Convex useMutation) */
|
|
40
|
+
onUpsert?: (path: string, meta: SEOMeta) => Promise<void>;
|
|
41
|
+
/** Callback when a page is deleted (e.g. Convex useMutation) */
|
|
42
|
+
onDelete?: (path: string) => Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Hook to manage multiple page SEO metadata entries.
|
|
46
|
+
* Supports optional Convex backend via callback props.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* // With Convex:
|
|
51
|
+
* const pages = useQuery(api.seo.queries.listPages, {})
|
|
52
|
+
* const upsertMutation = useMutation(api.seo.mutations.upsertSEOMeta)
|
|
53
|
+
* const deleteMutation = useMutation(api.seo.mutations.deleteSEOMeta)
|
|
54
|
+
*
|
|
55
|
+
* const admin = useSEOAdmin({
|
|
56
|
+
* initialPages: pages,
|
|
57
|
+
* onUpsert: (path, meta) => upsertMutation({ path, meta }),
|
|
58
|
+
* onDelete: (path) => deleteMutation({ path }),
|
|
59
|
+
* })
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare function useSEOAdmin(options?: UseSEOAdminOptions): {
|
|
63
|
+
pages: PageEntry[];
|
|
64
|
+
upsertMeta: (path: string, meta: SEOMeta) => Promise<void>;
|
|
65
|
+
deleteMeta: (path: string) => Promise<void>;
|
|
66
|
+
isLoading: boolean;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
declare function useSEOScore(meta: SEOMeta): {
|
|
70
|
+
score: number;
|
|
71
|
+
issues: string[];
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
declare function useSitemap(entries: SitemapEntry[]): {
|
|
75
|
+
xml: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returns a canonical URL for the given path using the site URL from SeoProvider.
|
|
80
|
+
* If no path is provided, defaults to the current window location pathname.
|
|
81
|
+
*/
|
|
82
|
+
declare function useCanonical(path?: string): string;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Injects JSON-LD structured data into the document head.
|
|
86
|
+
* Cleans up the script tag on unmount or when data changes.
|
|
87
|
+
*/
|
|
88
|
+
declare function useStructuredData(data: Record<string, unknown> | Record<string, unknown>[] | undefined): void;
|
|
89
|
+
|
|
90
|
+
interface SEOHeadProps {
|
|
91
|
+
meta: SEOMeta;
|
|
92
|
+
}
|
|
93
|
+
declare function SEOHead({ meta }: SEOHeadProps): null;
|
|
94
|
+
|
|
95
|
+
interface SEOPreviewProps {
|
|
96
|
+
meta: SEOMeta;
|
|
97
|
+
}
|
|
98
|
+
declare function SEOPreview({ meta }: SEOPreviewProps): react_jsx_runtime.JSX.Element;
|
|
99
|
+
|
|
100
|
+
interface SEOScoreCardProps {
|
|
101
|
+
meta: SEOMeta;
|
|
102
|
+
}
|
|
103
|
+
declare function SEOScoreCard({ meta }: SEOScoreCardProps): react_jsx_runtime.JSX.Element;
|
|
104
|
+
|
|
105
|
+
interface MetaEditorProps {
|
|
106
|
+
meta: SEOMeta;
|
|
107
|
+
onChange: (meta: SEOMeta) => void;
|
|
108
|
+
}
|
|
109
|
+
declare function MetaEditor({ meta, onChange }: MetaEditorProps): react_jsx_runtime.JSX.Element;
|
|
110
|
+
|
|
111
|
+
interface SitemapViewerProps {
|
|
112
|
+
entries: SitemapEntry[];
|
|
113
|
+
}
|
|
114
|
+
declare function SitemapViewer({ entries }: SitemapViewerProps): react_jsx_runtime.JSX.Element;
|
|
115
|
+
|
|
116
|
+
interface BreadcrumbsJsonLdProps {
|
|
117
|
+
items: {
|
|
118
|
+
name: string;
|
|
119
|
+
url: string;
|
|
120
|
+
}[];
|
|
121
|
+
}
|
|
122
|
+
declare function BreadcrumbsJsonLd({ items }: BreadcrumbsJsonLdProps): null;
|
|
123
|
+
|
|
124
|
+
interface ArticleJsonLdProps {
|
|
125
|
+
title: string;
|
|
126
|
+
description: string;
|
|
127
|
+
author: string;
|
|
128
|
+
datePublished: string;
|
|
129
|
+
url: string;
|
|
130
|
+
image?: string;
|
|
131
|
+
}
|
|
132
|
+
declare function ArticleJsonLd({ title, description, author, datePublished, url, image, }: ArticleJsonLdProps): null;
|
|
133
|
+
|
|
134
|
+
interface JsonLdProps {
|
|
135
|
+
type: string;
|
|
136
|
+
data: Record<string, unknown>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Renders a JSON-LD structured data script in the document head.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```tsx
|
|
143
|
+
* <JsonLd type="Article" data={{
|
|
144
|
+
* headline: 'My Article',
|
|
145
|
+
* author: { '@type': 'Person', name: 'Alice' },
|
|
146
|
+
* datePublished: '2026-03-15',
|
|
147
|
+
* }} />
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
declare function JsonLd({ type, data }: JsonLdProps): null;
|
|
151
|
+
|
|
152
|
+
declare function SEOAdminPage(): react_jsx_runtime.JSX.Element;
|
|
153
|
+
|
|
154
|
+
declare function SEOAnalyticsPage(): react_jsx_runtime.JSX.Element;
|
|
155
|
+
|
|
156
|
+
export { ArticleJsonLd, BreadcrumbsJsonLd, JsonLd, type JsonLdProps, MetaEditor, SEOAdminPage, SEOAnalyticsPage, SEOHead, SEOPreview, SEOScoreCard, SeoProvider, type SeoProviderProps, SitemapViewer, type UseSEOAdminOptions, useCanonical, useSEO, useSEOAdmin, useSEOContext, useSEOScore, useSitemap, useStructuredData };
|