pilothub 0.0.1 → 0.0.2
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/LICENSE +1 -0
- package/README.md +36 -129
- package/dist/browserAuth.d.ts +20 -0
- package/dist/browserAuth.js +156 -0
- package/dist/browserAuth.js.map +1 -0
- package/dist/browserAuth.test.d.ts +1 -0
- package/dist/browserAuth.test.js +83 -0
- package/dist/browserAuth.test.js.map +1 -0
- package/dist/cli/buildInfo.d.ts +3 -0
- package/dist/cli/buildInfo.js +103 -0
- package/dist/cli/buildInfo.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +9 -0
- package/dist/cli/commands/auth.js +75 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/delete.d.ts +11 -0
- package/dist/cli/commands/delete.js +67 -0
- package/dist/cli/commands/delete.js.map +1 -0
- package/dist/cli/commands/delete.test.d.ts +1 -0
- package/dist/cli/commands/delete.test.js +52 -0
- package/dist/cli/commands/delete.test.js.map +1 -0
- package/dist/cli/commands/publish.d.ts +9 -0
- package/dist/cli/commands/publish.js +87 -0
- package/dist/cli/commands/publish.js.map +1 -0
- package/dist/cli/commands/publish.test.d.ts +1 -0
- package/dist/cli/commands/publish.test.js +104 -0
- package/dist/cli/commands/publish.test.js.map +1 -0
- package/dist/cli/commands/skills.d.ts +23 -0
- package/dist/cli/commands/skills.js +298 -0
- package/dist/cli/commands/skills.js.map +1 -0
- package/dist/cli/commands/skills.test.d.ts +1 -0
- package/dist/cli/commands/skills.test.js +156 -0
- package/dist/cli/commands/skills.test.js.map +1 -0
- package/dist/cli/commands/star.d.ts +8 -0
- package/dist/cli/commands/star.js +38 -0
- package/dist/cli/commands/star.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +3 -0
- package/dist/cli/commands/sync.js +160 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/commands/sync.test.d.ts +1 -0
- package/dist/cli/commands/sync.test.js +277 -0
- package/dist/cli/commands/sync.test.js.map +1 -0
- package/dist/cli/commands/syncHelpers.d.ts +76 -0
- package/dist/cli/commands/syncHelpers.js +349 -0
- package/dist/cli/commands/syncHelpers.js.map +1 -0
- package/dist/cli/commands/syncHelpers.test.d.ts +1 -0
- package/dist/cli/commands/syncHelpers.test.js +22 -0
- package/dist/cli/commands/syncHelpers.test.js.map +1 -0
- package/dist/cli/commands/syncTypes.d.ts +24 -0
- package/dist/cli/commands/syncTypes.js +2 -0
- package/dist/cli/commands/syncTypes.js.map +1 -0
- package/dist/cli/commands/unstar.d.ts +8 -0
- package/dist/cli/commands/unstar.js +38 -0
- package/dist/cli/commands/unstar.js.map +1 -0
- package/dist/cli/helpStyle.d.ts +13 -0
- package/dist/cli/helpStyle.js +38 -0
- package/dist/cli/helpStyle.js.map +1 -0
- package/dist/cli/pilotbotConfig.d.ts +6 -0
- package/dist/cli/pilotbotConfig.js +110 -0
- package/dist/cli/pilotbotConfig.js.map +1 -0
- package/dist/cli/pilotbotConfig.test.d.ts +1 -0
- package/dist/cli/pilotbotConfig.test.js +133 -0
- package/dist/cli/pilotbotConfig.test.js.map +1 -0
- package/dist/cli/registry.d.ts +7 -0
- package/dist/cli/registry.js +42 -0
- package/dist/cli/registry.js.map +1 -0
- package/dist/cli/registry.test.d.ts +1 -0
- package/dist/cli/registry.test.js +48 -0
- package/dist/cli/registry.test.js.map +1 -0
- package/dist/cli/scanSkills.d.ts +7 -0
- package/dist/cli/scanSkills.js +75 -0
- package/dist/cli/scanSkills.js.map +1 -0
- package/dist/cli/scanSkills.test.d.ts +1 -0
- package/dist/cli/scanSkills.test.js +60 -0
- package/dist/cli/scanSkills.test.js.map +1 -0
- package/dist/cli/slug.d.ts +2 -0
- package/dist/cli/slug.js +16 -0
- package/dist/cli/slug.js.map +1 -0
- package/dist/cli/types.d.ts +15 -0
- package/dist/cli/types.js +2 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/ui.d.ts +7 -0
- package/dist/cli/ui.js +72 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +268 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -0
- package/dist/discovery.d.ts +5 -0
- package/dist/discovery.js +21 -0
- package/dist/discovery.js.map +1 -0
- package/dist/discovery.test.d.ts +1 -0
- package/dist/discovery.test.js +46 -0
- package/dist/discovery.test.js.map +1 -0
- package/dist/http.d.ts +32 -0
- package/dist/http.js +261 -0
- package/dist/http.js.map +1 -0
- package/dist/http.test.d.ts +1 -0
- package/dist/http.test.js +135 -0
- package/dist/http.test.js.map +1 -0
- package/dist/schema/ark.js.map +1 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/routes.js.map +1 -0
- package/{packages/schema/dist → dist/schema}/schemas.d.ts +0 -39
- package/{packages/schema/dist → dist/schema}/schemas.js +0 -22
- package/dist/schema/schemas.js.map +1 -0
- package/dist/schema/textFiles.js.map +1 -0
- package/dist/schema/textFiles.test.d.ts +1 -0
- package/dist/schema/textFiles.test.js +20 -0
- package/dist/schema/textFiles.test.js.map +1 -0
- package/dist/skills.d.ts +43 -0
- package/dist/skills.js +163 -0
- package/dist/skills.js.map +1 -0
- package/dist/skills.test.d.ts +1 -0
- package/dist/skills.test.js +144 -0
- package/dist/skills.test.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +27 -70
- package/.env.local.example +0 -19
- package/.github/workflows/ci.yml +0 -40
- package/.oxlintrc.json +0 -3
- package/AGENTS.md +0 -45
- package/CHANGELOG.md +0 -138
- package/DEPRECATIONS.md +0 -7
- package/biome.json +0 -41
- package/convex/_generated/api.d.ts +0 -153
- package/convex/_generated/api.js +0 -23
- package/convex/_generated/dataModel.d.ts +0 -60
- package/convex/_generated/server.d.ts +0 -143
- package/convex/_generated/server.js +0 -93
- package/convex/auth.config.ts +0 -8
- package/convex/auth.ts +0 -19
- package/convex/comments.ts +0 -88
- package/convex/crons.ts +0 -34
- package/convex/devSeed.ts +0 -459
- package/convex/devSeedExtra.ts +0 -541
- package/convex/downloads.ts +0 -78
- package/convex/githubBackups.ts +0 -170
- package/convex/githubBackupsNode.ts +0 -183
- package/convex/githubImport.ts +0 -317
- package/convex/githubSoulBackups.ts +0 -170
- package/convex/githubSoulBackupsNode.ts +0 -186
- package/convex/http.ts +0 -194
- package/convex/httpApi.handlers.test.ts +0 -488
- package/convex/httpApi.test.ts +0 -70
- package/convex/httpApi.ts +0 -305
- package/convex/httpApiV1.handlers.test.ts +0 -584
- package/convex/httpApiV1.ts +0 -1172
- package/convex/leaderboards.ts +0 -39
- package/convex/lib/access.ts +0 -36
- package/convex/lib/apiTokenAuth.ts +0 -36
- package/convex/lib/badges.ts +0 -50
- package/convex/lib/changelog.test.ts +0 -34
- package/convex/lib/changelog.ts +0 -278
- package/convex/lib/embeddings.ts +0 -38
- package/convex/lib/githubBackup.ts +0 -443
- package/convex/lib/githubImport.test.ts +0 -247
- package/convex/lib/githubImport.ts +0 -425
- package/convex/lib/githubSoulBackup.ts +0 -443
- package/convex/lib/leaderboards.ts +0 -103
- package/convex/lib/moderation.ts +0 -42
- package/convex/lib/public.ts +0 -89
- package/convex/lib/searchText.test.ts +0 -46
- package/convex/lib/searchText.ts +0 -27
- package/convex/lib/skillBackfill.test.ts +0 -34
- package/convex/lib/skillBackfill.ts +0 -67
- package/convex/lib/skillPublish.test.ts +0 -28
- package/convex/lib/skillPublish.ts +0 -284
- package/convex/lib/skillStats.ts +0 -80
- package/convex/lib/skills.test.ts +0 -197
- package/convex/lib/skills.ts +0 -273
- package/convex/lib/soulChangelog.ts +0 -273
- package/convex/lib/soulPublish.ts +0 -236
- package/convex/lib/tokens.test.ts +0 -33
- package/convex/lib/tokens.ts +0 -51
- package/convex/lib/webhooks.test.ts +0 -91
- package/convex/lib/webhooks.ts +0 -112
- package/convex/maintenance.test.ts +0 -270
- package/convex/maintenance.ts +0 -840
- package/convex/rateLimits.ts +0 -50
- package/convex/schema.ts +0 -472
- package/convex/search.test.ts +0 -12
- package/convex/search.ts +0 -254
- package/convex/seed.test.ts +0 -37
- package/convex/seed.ts +0 -254
- package/convex/seedSouls.ts +0 -111
- package/convex/skillStatEvents.ts +0 -568
- package/convex/skills.ts +0 -1606
- package/convex/soulComments.ts +0 -88
- package/convex/soulDownloads.ts +0 -14
- package/convex/soulStars.ts +0 -71
- package/convex/souls.ts +0 -570
- package/convex/stars.ts +0 -108
- package/convex/statsMaintenance.ts +0 -205
- package/convex/telemetry.ts +0 -434
- package/convex/tokens.ts +0 -88
- package/convex/tsconfig.json +0 -7
- package/convex/uploads.ts +0 -20
- package/convex/users.ts +0 -122
- package/convex/webhooks.ts +0 -50
- package/convex.json +0 -3
- package/docs/README.md +0 -32
- package/docs/api.md +0 -51
- package/docs/architecture.md +0 -61
- package/docs/auth.md +0 -54
- package/docs/cli.md +0 -117
- package/docs/deploy.md +0 -78
- package/docs/diffing.md +0 -84
- package/docs/github-import.md +0 -171
- package/docs/http-api.md +0 -187
- package/docs/manual-testing.md +0 -64
- package/docs/mintlify.md +0 -43
- package/docs/quickstart.md +0 -120
- package/docs/skill-format.md +0 -58
- package/docs/soul-format.md +0 -37
- package/docs/spec.md +0 -177
- package/docs/telemetry.md +0 -91
- package/docs/troubleshooting.md +0 -49
- package/docs/webhook.md +0 -51
- package/e2e/menu-smoke.pw.test.ts +0 -49
- package/e2e/pilothub.e2e.test.ts +0 -494
- package/e2e/search-exact.pw.test.ts +0 -97
- package/packages/pilothub/LICENSE +0 -22
- package/packages/pilothub/README.md +0 -57
- package/packages/pilothub/package.json +0 -41
- package/packages/pilothub/src/browserAuth.test.ts +0 -96
- package/packages/pilothub/src/browserAuth.ts +0 -174
- package/packages/pilothub/src/cli/buildInfo.ts +0 -94
- package/packages/pilothub/src/cli/commands/auth.ts +0 -97
- package/packages/pilothub/src/cli/commands/delete.test.ts +0 -73
- package/packages/pilothub/src/cli/commands/delete.ts +0 -83
- package/packages/pilothub/src/cli/commands/publish.test.ts +0 -122
- package/packages/pilothub/src/cli/commands/publish.ts +0 -108
- package/packages/pilothub/src/cli/commands/skills.test.ts +0 -191
- package/packages/pilothub/src/cli/commands/skills.ts +0 -380
- package/packages/pilothub/src/cli/commands/star.ts +0 -46
- package/packages/pilothub/src/cli/commands/sync.test.ts +0 -310
- package/packages/pilothub/src/cli/commands/sync.ts +0 -200
- package/packages/pilothub/src/cli/commands/syncHelpers.test.ts +0 -26
- package/packages/pilothub/src/cli/commands/syncHelpers.ts +0 -427
- package/packages/pilothub/src/cli/commands/syncTypes.ts +0 -27
- package/packages/pilothub/src/cli/commands/unstar.ts +0 -48
- package/packages/pilothub/src/cli/helpStyle.ts +0 -45
- package/packages/pilothub/src/cli/pilotbotConfig.test.ts +0 -159
- package/packages/pilothub/src/cli/pilotbotConfig.ts +0 -147
- package/packages/pilothub/src/cli/registry.test.ts +0 -63
- package/packages/pilothub/src/cli/registry.ts +0 -43
- package/packages/pilothub/src/cli/scanSkills.test.ts +0 -64
- package/packages/pilothub/src/cli/scanSkills.ts +0 -84
- package/packages/pilothub/src/cli/slug.ts +0 -16
- package/packages/pilothub/src/cli/types.ts +0 -12
- package/packages/pilothub/src/cli/ui.ts +0 -75
- package/packages/pilothub/src/cli.ts +0 -311
- package/packages/pilothub/src/config.ts +0 -36
- package/packages/pilothub/src/discovery.test.ts +0 -75
- package/packages/pilothub/src/discovery.ts +0 -19
- package/packages/pilothub/src/http.test.ts +0 -156
- package/packages/pilothub/src/http.ts +0 -301
- package/packages/pilothub/src/schema/ark.ts +0 -29
- package/packages/pilothub/src/schema/index.ts +0 -5
- package/packages/pilothub/src/schema/routes.ts +0 -22
- package/packages/pilothub/src/schema/schemas.ts +0 -260
- package/packages/pilothub/src/schema/textFiles.test.ts +0 -23
- package/packages/pilothub/src/schema/textFiles.ts +0 -66
- package/packages/pilothub/src/skills.test.ts +0 -191
- package/packages/pilothub/src/skills.ts +0 -172
- package/packages/pilothub/src/types.ts +0 -10
- package/packages/pilothub/tsconfig.json +0 -14
- package/packages/schema/README.md +0 -3
- package/packages/schema/dist/ark.js.map +0 -1
- package/packages/schema/dist/index.js.map +0 -1
- package/packages/schema/dist/routes.js.map +0 -1
- package/packages/schema/dist/schemas.js.map +0 -1
- package/packages/schema/dist/textFiles.js.map +0 -1
- package/packages/schema/package.json +0 -26
- package/packages/schema/src/ark.ts +0 -29
- package/packages/schema/src/index.ts +0 -5
- package/packages/schema/src/routes.ts +0 -22
- package/packages/schema/src/schemas.test.ts +0 -123
- package/packages/schema/src/schemas.ts +0 -287
- package/packages/schema/src/textFiles.test.ts +0 -23
- package/packages/schema/src/textFiles.ts +0 -66
- package/packages/schema/tsconfig.json +0 -15
- package/pilothub +0 -46
- package/playwright.config.ts +0 -33
- package/public/.well-known/pilothub.json +0 -6
- package/public/api/v1/openapi.json +0 -379
- package/public/favicon.ico +0 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +0 -25
- package/public/og.png +0 -0
- package/public/og.svg +0 -98
- package/public/pilot-logo.png +0 -0
- package/public/pilot-mark.png +0 -0
- package/public/robots.txt +0 -3
- package/public/tanstack-circle-logo.png +0 -0
- package/public/tanstack-word-logo-white.svg +0 -1
- package/scripts/check-peer-deps.ts +0 -56
- package/scripts/docs-list.ts +0 -148
- package/scripts/run-playwright-local.sh +0 -14
- package/server/og/fetchSkillOgMeta.ts +0 -27
- package/server/og/fetchSoulOgMeta.ts +0 -27
- package/server/og/ogAssets.ts +0 -80
- package/server/og/skillOgSvg.test.ts +0 -59
- package/server/og/skillOgSvg.ts +0 -258
- package/server/og/soulOgSvg.ts +0 -209
- package/server/routes/og/skill.png.ts +0 -103
- package/server/routes/og/soul.png.ts +0 -111
- package/src/__tests__/skill-detail-page.test.tsx +0 -86
- package/src/__tests__/skills-index.test.tsx +0 -145
- package/src/__tests__/upload.route.test.tsx +0 -228
- package/src/components/AppProviders.tsx +0 -19
- package/src/components/ClientOnly.tsx +0 -18
- package/src/components/Footer.tsx +0 -29
- package/src/components/Header.tsx +0 -295
- package/src/components/InstallSwitcher.tsx +0 -53
- package/src/components/SkillCard.tsx +0 -36
- package/src/components/SkillDetailPage.tsx +0 -817
- package/src/components/SkillDiffCard.tsx +0 -485
- package/src/components/SoulCard.tsx +0 -19
- package/src/components/SoulDetailPage.tsx +0 -263
- package/src/components/UserBootstrap.tsx +0 -18
- package/src/components/ui/dropdown-menu.tsx +0 -67
- package/src/components/ui/toggle-group.tsx +0 -35
- package/src/convex/client.ts +0 -3
- package/src/lib/badges.ts +0 -29
- package/src/lib/diffing.test.ts +0 -163
- package/src/lib/diffing.ts +0 -106
- package/src/lib/gravatar.test.ts +0 -9
- package/src/lib/gravatar.ts +0 -158
- package/src/lib/og.test.ts +0 -142
- package/src/lib/og.ts +0 -156
- package/src/lib/publicUser.ts +0 -39
- package/src/lib/roles.ts +0 -19
- package/src/lib/site.test.ts +0 -130
- package/src/lib/site.ts +0 -84
- package/src/lib/theme-transition.test.ts +0 -134
- package/src/lib/theme-transition.ts +0 -134
- package/src/lib/theme.test.tsx +0 -88
- package/src/lib/theme.ts +0 -43
- package/src/lib/uploadFiles.jsdom.test.ts +0 -33
- package/src/lib/uploadFiles.test.ts +0 -123
- package/src/lib/uploadFiles.ts +0 -245
- package/src/lib/uploadUtils.test.ts +0 -78
- package/src/lib/uploadUtils.ts +0 -93
- package/src/lib/useAuthStatus.ts +0 -12
- package/src/lib/utils.test.ts +0 -9
- package/src/lib/utils.ts +0 -6
- package/src/logo.svg +0 -12
- package/src/routeTree.gen.ts +0 -345
- package/src/router.tsx +0 -17
- package/src/routes/$owner/$slug.tsx +0 -55
- package/src/routes/__root.tsx +0 -136
- package/src/routes/admin.tsx +0 -11
- package/src/routes/cli/auth.tsx +0 -168
- package/src/routes/dashboard.tsx +0 -97
- package/src/routes/import.tsx +0 -415
- package/src/routes/index.tsx +0 -252
- package/src/routes/management.tsx +0 -529
- package/src/routes/settings.tsx +0 -203
- package/src/routes/skills/index.tsx +0 -422
- package/src/routes/souls/$slug.tsx +0 -55
- package/src/routes/souls/index.tsx +0 -243
- package/src/routes/stars.tsx +0 -68
- package/src/routes/u/$handle.tsx +0 -307
- package/src/routes/upload/utils.ts +0 -81
- package/src/routes/upload.tsx +0 -499
- package/src/styles.css +0 -2718
- package/tsconfig.json +0 -24
- package/tsconfig.oxlint.json +0 -16
- package/vercel.json +0 -8
- package/vite.config.ts +0 -48
- package/vitest.config.ts +0 -47
- package/vitest.e2e.config.ts +0 -11
- package/vitest.setup.ts +0 -1
- /package/{packages/pilothub/bin → bin}/pilothub.js +0 -0
- /package/{packages/schema/dist → dist/schema}/ark.d.ts +0 -0
- /package/{packages/schema/dist → dist/schema}/ark.js +0 -0
- /package/{packages/schema/dist → dist/schema}/index.d.ts +0 -0
- /package/{packages/schema/dist → dist/schema}/index.js +0 -0
- /package/{packages/schema/dist → dist/schema}/routes.d.ts +0 -0
- /package/{packages/schema/dist → dist/schema}/routes.js +0 -0
- /package/{packages/schema/dist → dist/schema}/textFiles.d.ts +0 -0
- /package/{packages/schema/dist → dist/schema}/textFiles.js +0 -0
package/convex/search.ts
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import { v } from 'convex/values'
|
|
2
|
-
import { internal } from './_generated/api'
|
|
3
|
-
import type { Doc, Id } from './_generated/dataModel'
|
|
4
|
-
import { action, internalQuery } from './_generated/server'
|
|
5
|
-
import { getSkillBadgeMaps, isSkillHighlighted, type SkillBadgeMap } from './lib/badges'
|
|
6
|
-
import { generateEmbedding } from './lib/embeddings'
|
|
7
|
-
import { toPublicSkill, toPublicSoul } from './lib/public'
|
|
8
|
-
import { matchesExactTokens, tokenize } from './lib/searchText'
|
|
9
|
-
|
|
10
|
-
type HydratedEntry = {
|
|
11
|
-
embeddingId: Id<'skillEmbeddings'>
|
|
12
|
-
skill: NonNullable<ReturnType<typeof toPublicSkill>>
|
|
13
|
-
version: Doc<'skillVersions'> | null
|
|
14
|
-
ownerHandle: string | null
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type SearchResult = HydratedEntry & { score: number }
|
|
18
|
-
|
|
19
|
-
function getNextCandidateLimit(current: number, max: number) {
|
|
20
|
-
const next = Math.min(current * 2, max)
|
|
21
|
-
return next > current ? next : null
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const searchSkills: ReturnType<typeof action> = action({
|
|
25
|
-
args: {
|
|
26
|
-
query: v.string(),
|
|
27
|
-
limit: v.optional(v.number()),
|
|
28
|
-
highlightedOnly: v.optional(v.boolean()),
|
|
29
|
-
},
|
|
30
|
-
handler: async (ctx, args): Promise<SearchResult[]> => {
|
|
31
|
-
const query = args.query.trim()
|
|
32
|
-
if (!query) return []
|
|
33
|
-
const queryTokens = tokenize(query)
|
|
34
|
-
if (queryTokens.length === 0) return []
|
|
35
|
-
let vector: number[]
|
|
36
|
-
try {
|
|
37
|
-
vector = await generateEmbedding(query)
|
|
38
|
-
} catch (error) {
|
|
39
|
-
console.warn('Search embedding generation failed', error)
|
|
40
|
-
return []
|
|
41
|
-
}
|
|
42
|
-
const limit = args.limit ?? 10
|
|
43
|
-
// Convex vectorSearch max limit is 256; clamp candidate sizes accordingly.
|
|
44
|
-
const maxCandidate = Math.min(Math.max(limit * 10, 200), 256)
|
|
45
|
-
let candidateLimit = Math.min(Math.max(limit * 3, 50), 256)
|
|
46
|
-
let hydrated: HydratedEntry[] = []
|
|
47
|
-
let scoreById = new Map<Id<'skillEmbeddings'>, number>()
|
|
48
|
-
let exactMatches: HydratedEntry[] = []
|
|
49
|
-
|
|
50
|
-
while (candidateLimit <= maxCandidate) {
|
|
51
|
-
const results = await ctx.vectorSearch('skillEmbeddings', 'by_embedding', {
|
|
52
|
-
vector,
|
|
53
|
-
limit: candidateLimit,
|
|
54
|
-
filter: (q) => q.or(q.eq('visibility', 'latest'), q.eq('visibility', 'latest-approved')),
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
hydrated = (await ctx.runQuery(internal.search.hydrateResults, {
|
|
58
|
-
embeddingIds: results.map((result) => result._id),
|
|
59
|
-
})) as HydratedEntry[]
|
|
60
|
-
|
|
61
|
-
scoreById = new Map<Id<'skillEmbeddings'>, number>(
|
|
62
|
-
results.map((result) => [result._id, result._score]),
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
const badgeMapEntries = (await ctx.runQuery(internal.search.getSkillBadgeMapsInternal, {
|
|
66
|
-
skillIds: hydrated.map((entry) => entry.skill._id),
|
|
67
|
-
})) as Array<[Id<'skills'>, SkillBadgeMap]>
|
|
68
|
-
const badgeMapBySkillId = new Map(badgeMapEntries)
|
|
69
|
-
const hydratedWithBadges = hydrated.map((entry) => ({
|
|
70
|
-
...entry,
|
|
71
|
-
skill: {
|
|
72
|
-
...entry.skill,
|
|
73
|
-
badges: badgeMapBySkillId.get(entry.skill._id) ?? {},
|
|
74
|
-
},
|
|
75
|
-
}))
|
|
76
|
-
|
|
77
|
-
const filtered = args.highlightedOnly
|
|
78
|
-
? hydratedWithBadges.filter((entry) => isSkillHighlighted(entry.skill))
|
|
79
|
-
: hydratedWithBadges
|
|
80
|
-
|
|
81
|
-
exactMatches = filtered.filter((entry) =>
|
|
82
|
-
matchesExactTokens(queryTokens, [
|
|
83
|
-
entry.skill.displayName,
|
|
84
|
-
entry.skill.slug,
|
|
85
|
-
entry.skill.summary,
|
|
86
|
-
]),
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
if (exactMatches.length >= limit || results.length < candidateLimit) {
|
|
90
|
-
break
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const nextLimit = getNextCandidateLimit(candidateLimit, maxCandidate)
|
|
94
|
-
if (!nextLimit) break
|
|
95
|
-
candidateLimit = nextLimit
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return exactMatches
|
|
99
|
-
.map((entry) => ({
|
|
100
|
-
...entry,
|
|
101
|
-
score: scoreById.get(entry.embeddingId) ?? 0,
|
|
102
|
-
}))
|
|
103
|
-
.filter((entry) => entry.skill)
|
|
104
|
-
.slice(0, limit)
|
|
105
|
-
},
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
export const getBadgeMapsForSkills = internalQuery({
|
|
109
|
-
args: { skillIds: v.array(v.id('skills')) },
|
|
110
|
-
handler: async (ctx, args): Promise<Array<[Id<'skills'>, SkillBadgeMap]>> => {
|
|
111
|
-
const badgeMap = await getSkillBadgeMaps(ctx, args.skillIds)
|
|
112
|
-
return Array.from(badgeMap.entries())
|
|
113
|
-
},
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
export const hydrateResults = internalQuery({
|
|
117
|
-
args: { embeddingIds: v.array(v.id('skillEmbeddings')) },
|
|
118
|
-
handler: async (ctx, args): Promise<HydratedEntry[]> => {
|
|
119
|
-
const ownerHandleCache = new Map<Id<'users'>, Promise<string | null>>()
|
|
120
|
-
|
|
121
|
-
const getOwnerHandle = (ownerUserId: Id<'users'>) => {
|
|
122
|
-
const cached = ownerHandleCache.get(ownerUserId)
|
|
123
|
-
if (cached) return cached
|
|
124
|
-
const handlePromise = ctx.db
|
|
125
|
-
.get(ownerUserId)
|
|
126
|
-
.then((owner) => owner?.handle ?? owner?._id ?? null)
|
|
127
|
-
ownerHandleCache.set(ownerUserId, handlePromise)
|
|
128
|
-
return handlePromise
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const entries = await Promise.all(
|
|
132
|
-
args.embeddingIds.map(async (embeddingId) => {
|
|
133
|
-
const embedding = await ctx.db.get(embeddingId)
|
|
134
|
-
if (!embedding) return null
|
|
135
|
-
const skill = await ctx.db.get(embedding.skillId)
|
|
136
|
-
if (!skill || skill.softDeletedAt) return null
|
|
137
|
-
const [version, ownerHandle] = await Promise.all([
|
|
138
|
-
ctx.db.get(embedding.versionId),
|
|
139
|
-
getOwnerHandle(skill.ownerUserId),
|
|
140
|
-
])
|
|
141
|
-
const publicSkill = toPublicSkill(skill)
|
|
142
|
-
if (!publicSkill) return null
|
|
143
|
-
return { embeddingId, skill: publicSkill, version, ownerHandle }
|
|
144
|
-
}),
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
return entries.filter((entry): entry is HydratedEntry => entry !== null)
|
|
148
|
-
},
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
type HydratedSoulEntry = {
|
|
152
|
-
embeddingId: Id<'soulEmbeddings'>
|
|
153
|
-
soul: NonNullable<ReturnType<typeof toPublicSoul>>
|
|
154
|
-
version: Doc<'soulVersions'> | null
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
type SoulSearchResult = HydratedSoulEntry & { score: number }
|
|
158
|
-
|
|
159
|
-
export const searchSouls: ReturnType<typeof action> = action({
|
|
160
|
-
args: {
|
|
161
|
-
query: v.string(),
|
|
162
|
-
limit: v.optional(v.number()),
|
|
163
|
-
},
|
|
164
|
-
handler: async (ctx, args): Promise<SoulSearchResult[]> => {
|
|
165
|
-
const query = args.query.trim()
|
|
166
|
-
if (!query) return []
|
|
167
|
-
const queryTokens = tokenize(query)
|
|
168
|
-
if (queryTokens.length === 0) return []
|
|
169
|
-
let vector: number[]
|
|
170
|
-
try {
|
|
171
|
-
vector = await generateEmbedding(query)
|
|
172
|
-
} catch (error) {
|
|
173
|
-
console.warn('Search embedding generation failed', error)
|
|
174
|
-
return []
|
|
175
|
-
}
|
|
176
|
-
const limit = args.limit ?? 10
|
|
177
|
-
// Convex vectorSearch max limit is 256; clamp candidate sizes accordingly.
|
|
178
|
-
const maxCandidate = Math.min(Math.max(limit * 10, 200), 256)
|
|
179
|
-
let candidateLimit = Math.min(Math.max(limit * 3, 50), 256)
|
|
180
|
-
let hydrated: HydratedSoulEntry[] = []
|
|
181
|
-
let scoreById = new Map<Id<'soulEmbeddings'>, number>()
|
|
182
|
-
let exactMatches: HydratedSoulEntry[] = []
|
|
183
|
-
|
|
184
|
-
while (candidateLimit <= maxCandidate) {
|
|
185
|
-
const results = await ctx.vectorSearch('soulEmbeddings', 'by_embedding', {
|
|
186
|
-
vector,
|
|
187
|
-
limit: candidateLimit,
|
|
188
|
-
filter: (q) => q.or(q.eq('visibility', 'latest'), q.eq('visibility', 'latest-approved')),
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
hydrated = (await ctx.runQuery(internal.search.hydrateSoulResults, {
|
|
192
|
-
embeddingIds: results.map((result) => result._id),
|
|
193
|
-
})) as HydratedSoulEntry[]
|
|
194
|
-
|
|
195
|
-
scoreById = new Map<Id<'soulEmbeddings'>, number>(
|
|
196
|
-
results.map((result) => [result._id, result._score]),
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
exactMatches = hydrated.filter((entry) =>
|
|
200
|
-
matchesExactTokens(queryTokens, [
|
|
201
|
-
entry.soul.displayName,
|
|
202
|
-
entry.soul.slug,
|
|
203
|
-
entry.soul.summary,
|
|
204
|
-
]),
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
if (exactMatches.length >= limit || results.length < candidateLimit) {
|
|
208
|
-
break
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const nextLimit = getNextCandidateLimit(candidateLimit, maxCandidate)
|
|
212
|
-
if (!nextLimit) break
|
|
213
|
-
candidateLimit = nextLimit
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return exactMatches
|
|
217
|
-
.map((entry) => ({
|
|
218
|
-
...entry,
|
|
219
|
-
score: scoreById.get(entry.embeddingId) ?? 0,
|
|
220
|
-
}))
|
|
221
|
-
.filter((entry) => entry.soul)
|
|
222
|
-
.slice(0, limit)
|
|
223
|
-
},
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
export const hydrateSoulResults = internalQuery({
|
|
227
|
-
args: { embeddingIds: v.array(v.id('soulEmbeddings')) },
|
|
228
|
-
handler: async (ctx, args): Promise<HydratedSoulEntry[]> => {
|
|
229
|
-
const entries: HydratedSoulEntry[] = []
|
|
230
|
-
|
|
231
|
-
for (const embeddingId of args.embeddingIds) {
|
|
232
|
-
const embedding = await ctx.db.get(embeddingId)
|
|
233
|
-
if (!embedding) continue
|
|
234
|
-
const soul = await ctx.db.get(embedding.soulId)
|
|
235
|
-
if (soul?.softDeletedAt) continue
|
|
236
|
-
const version = await ctx.db.get(embedding.versionId)
|
|
237
|
-
const publicSoul = toPublicSoul(soul)
|
|
238
|
-
if (!publicSoul) continue
|
|
239
|
-
entries.push({ embeddingId, soul: publicSoul, version })
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return entries
|
|
243
|
-
},
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
export const getSkillBadgeMapsInternal = internalQuery({
|
|
247
|
-
args: { skillIds: v.array(v.id('skills')) },
|
|
248
|
-
handler: async (ctx, args) => {
|
|
249
|
-
const badgeMap = await getSkillBadgeMaps(ctx, args.skillIds)
|
|
250
|
-
return Array.from(badgeMap.entries())
|
|
251
|
-
},
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
export const __test = { getNextCandidateLimit }
|
package/convex/seed.test.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import type { Doc } from './_generated/dataModel'
|
|
3
|
-
import { decideSeedStart } from './seed'
|
|
4
|
-
|
|
5
|
-
function seedState(cursor: string, updatedAt: number) {
|
|
6
|
-
return { cursor, updatedAt } as unknown as Doc<'githubBackupSyncState'>
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
describe('decideSeedStart', () => {
|
|
10
|
-
it('returns done when done', () => {
|
|
11
|
-
expect(decideSeedStart(seedState('done', Date.now()), Date.now())).toEqual({
|
|
12
|
-
started: false,
|
|
13
|
-
reason: 'done',
|
|
14
|
-
})
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it('returns running when lock fresh', () => {
|
|
18
|
-
const now = Date.now()
|
|
19
|
-
expect(decideSeedStart(seedState('running', now), now + 1000)).toEqual({
|
|
20
|
-
started: false,
|
|
21
|
-
reason: 'running',
|
|
22
|
-
})
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('starts when lock stale', () => {
|
|
26
|
-
const now = Date.now()
|
|
27
|
-
const stale = now - 10 * 60 * 1000 - 1
|
|
28
|
-
expect(decideSeedStart(seedState('running', stale), now)).toEqual({
|
|
29
|
-
started: true,
|
|
30
|
-
reason: 'patched',
|
|
31
|
-
})
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('starts when missing', () => {
|
|
35
|
-
expect(decideSeedStart(null, Date.now())).toEqual({ started: true, reason: 'inserted' })
|
|
36
|
-
})
|
|
37
|
-
})
|
package/convex/seed.ts
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import { v } from 'convex/values'
|
|
2
|
-
import { internal } from './_generated/api'
|
|
3
|
-
import type { Doc, Id } from './_generated/dataModel'
|
|
4
|
-
import type { ActionCtx, DatabaseReader, DatabaseWriter } from './_generated/server'
|
|
5
|
-
import { action, internalMutation, internalQuery } from './_generated/server'
|
|
6
|
-
import { publishSoulVersionForUser } from './lib/soulPublish'
|
|
7
|
-
import { SOUL_SEED_DISPLAY_NAME, SOUL_SEED_HANDLE, SOUL_SEED_KEY, SOUL_SEEDS } from './seedSouls'
|
|
8
|
-
|
|
9
|
-
const SEED_LOCK_STALE_MS = 10 * 60 * 1000
|
|
10
|
-
|
|
11
|
-
type SeedStateDoc = Doc<'githubBackupSyncState'>
|
|
12
|
-
|
|
13
|
-
type SeedStartDecision = {
|
|
14
|
-
started: boolean
|
|
15
|
-
reason: 'done' | 'running' | 'patched' | 'inserted'
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async function getSeedState(ctx: { db: DatabaseReader }): Promise<SeedStateDoc | null> {
|
|
19
|
-
const entries = (await ctx.db
|
|
20
|
-
.query('githubBackupSyncState')
|
|
21
|
-
.withIndex('by_key', (q) => q.eq('key', SOUL_SEED_KEY))
|
|
22
|
-
.order('desc')
|
|
23
|
-
.take(2)) as SeedStateDoc[]
|
|
24
|
-
return entries[0] ?? null
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function cleanupSeedState(ctx: { db: DatabaseWriter }, keepId: Id<'githubBackupSyncState'>) {
|
|
28
|
-
const entries = (await ctx.db
|
|
29
|
-
.query('githubBackupSyncState')
|
|
30
|
-
.withIndex('by_key', (q) => q.eq('key', SOUL_SEED_KEY))
|
|
31
|
-
.order('desc')
|
|
32
|
-
.take(50)) as SeedStateDoc[]
|
|
33
|
-
|
|
34
|
-
for (const entry of entries) {
|
|
35
|
-
if (entry._id === keepId) continue
|
|
36
|
-
await ctx.db.delete(entry._id)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function decideSeedStart(existing: SeedStateDoc | null, now: number): SeedStartDecision {
|
|
41
|
-
const cursor = existing?.cursor ?? null
|
|
42
|
-
if (cursor === 'done') return { started: false, reason: 'done' }
|
|
43
|
-
if (cursor === 'running' && existing && now - existing.updatedAt < SEED_LOCK_STALE_MS) {
|
|
44
|
-
return { started: false, reason: 'running' }
|
|
45
|
-
}
|
|
46
|
-
return existing ? { started: true, reason: 'patched' } : { started: true, reason: 'inserted' }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export const getSoulSeedStateInternal = internalQuery({
|
|
50
|
-
args: {},
|
|
51
|
-
handler: async (ctx) => getSeedState(ctx),
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
export const setSoulSeedStateInternal = internalMutation({
|
|
55
|
-
args: { status: v.string() },
|
|
56
|
-
handler: async (ctx, args) => {
|
|
57
|
-
const existing = await getSeedState(ctx)
|
|
58
|
-
const now = Date.now()
|
|
59
|
-
if (existing) {
|
|
60
|
-
await ctx.db.patch(existing._id, { cursor: args.status, updatedAt: now })
|
|
61
|
-
await cleanupSeedState(ctx, existing._id)
|
|
62
|
-
return existing._id
|
|
63
|
-
}
|
|
64
|
-
const id = await ctx.db.insert('githubBackupSyncState', {
|
|
65
|
-
key: SOUL_SEED_KEY,
|
|
66
|
-
cursor: args.status,
|
|
67
|
-
updatedAt: now,
|
|
68
|
-
})
|
|
69
|
-
await cleanupSeedState(ctx, id)
|
|
70
|
-
return id
|
|
71
|
-
},
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
export const tryStartSoulSeedInternal = internalMutation({
|
|
75
|
-
args: {},
|
|
76
|
-
handler: async (ctx) => {
|
|
77
|
-
const now = Date.now()
|
|
78
|
-
const existing = await getSeedState(ctx)
|
|
79
|
-
const decision = decideSeedStart(existing, now)
|
|
80
|
-
|
|
81
|
-
if (!decision.started) return decision
|
|
82
|
-
|
|
83
|
-
if (existing) {
|
|
84
|
-
await ctx.db.patch(existing._id, { cursor: 'running', updatedAt: now })
|
|
85
|
-
await cleanupSeedState(ctx, existing._id)
|
|
86
|
-
return { started: true, reason: 'patched' as const }
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const id = await ctx.db.insert('githubBackupSyncState', {
|
|
90
|
-
key: SOUL_SEED_KEY,
|
|
91
|
-
cursor: 'running',
|
|
92
|
-
updatedAt: now,
|
|
93
|
-
})
|
|
94
|
-
await cleanupSeedState(ctx, id)
|
|
95
|
-
return { started: true, reason: 'inserted' as const }
|
|
96
|
-
},
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
export const hasAnySoulsInternal = internalQuery({
|
|
100
|
-
args: {},
|
|
101
|
-
handler: async (ctx) => {
|
|
102
|
-
const entry = await ctx.db.query('souls').take(1)
|
|
103
|
-
return entry.length > 0
|
|
104
|
-
},
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
export const ensureSoulSeeds = action({
|
|
108
|
-
args: {},
|
|
109
|
-
handler: async (ctx) => {
|
|
110
|
-
const started = (await ctx.runMutation(internal.seed.tryStartSoulSeedInternal, {})) as {
|
|
111
|
-
started: boolean
|
|
112
|
-
reason: 'done' | 'running' | 'patched' | 'inserted'
|
|
113
|
-
}
|
|
114
|
-
if (!started.started) {
|
|
115
|
-
if (started.reason === 'done') return { seeded: false, reason: 'already-seeded' as const }
|
|
116
|
-
return { seeded: false, reason: 'in-progress' as const }
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const hasSouls = (await ctx.runQuery(internal.seed.hasAnySoulsInternal, {})) as boolean
|
|
120
|
-
if (hasSouls) {
|
|
121
|
-
await ctx.runMutation(internal.seed.setSoulSeedStateInternal, { status: 'done' })
|
|
122
|
-
return { seeded: false, reason: 'souls-exist' as const }
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
try {
|
|
126
|
-
const result = await runSeed(ctx)
|
|
127
|
-
await ctx.runMutation(internal.seed.setSoulSeedStateInternal, { status: 'done' })
|
|
128
|
-
return { seeded: true, reason: 'seeded' as const, ...result }
|
|
129
|
-
} catch (error) {
|
|
130
|
-
await ctx.runMutation(internal.seed.setSoulSeedStateInternal, { status: 'error' })
|
|
131
|
-
throw error
|
|
132
|
-
}
|
|
133
|
-
},
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
export const seed = action({
|
|
137
|
-
args: {},
|
|
138
|
-
handler: async (ctx) => runSeed(ctx),
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
async function runSeed(ctx: ActionCtx) {
|
|
142
|
-
const userId = (await ctx.runMutation(internal.seed.ensureSeedUserInternal, {
|
|
143
|
-
handle: SOUL_SEED_HANDLE,
|
|
144
|
-
displayName: SOUL_SEED_DISPLAY_NAME,
|
|
145
|
-
})) as Id<'users'>
|
|
146
|
-
|
|
147
|
-
const created: string[] = []
|
|
148
|
-
const skipped: string[] = []
|
|
149
|
-
|
|
150
|
-
for (const seedEntry of SOUL_SEEDS) {
|
|
151
|
-
const existing = (await ctx.runQuery(internal.souls.getSoulBySlugInternal, {
|
|
152
|
-
slug: seedEntry.slug,
|
|
153
|
-
})) as Doc<'souls'> | null
|
|
154
|
-
if (existing) {
|
|
155
|
-
if (existing.softDeletedAt && existing.ownerUserId === userId) {
|
|
156
|
-
await ctx.runMutation(internal.souls.setSoulSoftDeletedInternal, {
|
|
157
|
-
userId,
|
|
158
|
-
slug: seedEntry.slug,
|
|
159
|
-
deleted: false,
|
|
160
|
-
})
|
|
161
|
-
}
|
|
162
|
-
skipped.push(seedEntry.slug)
|
|
163
|
-
continue
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const body = seedEntry.readme
|
|
167
|
-
if (!body) {
|
|
168
|
-
skipped.push(seedEntry.slug)
|
|
169
|
-
continue
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const bytes = new TextEncoder().encode(body)
|
|
173
|
-
const sha256 = await sha256Hex(bytes)
|
|
174
|
-
const storageId = await ctx.storage.store(new Blob([bytes], { type: 'text/markdown' }))
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
await publishSoulVersionForUser(ctx, userId, {
|
|
178
|
-
slug: seedEntry.slug,
|
|
179
|
-
displayName: seedEntry.displayName,
|
|
180
|
-
version: seedEntry.version,
|
|
181
|
-
changelog: '',
|
|
182
|
-
tags: seedEntry.tags,
|
|
183
|
-
files: [
|
|
184
|
-
{
|
|
185
|
-
path: 'SOUL.md',
|
|
186
|
-
size: bytes.byteLength,
|
|
187
|
-
storageId,
|
|
188
|
-
sha256,
|
|
189
|
-
contentType: 'text/markdown',
|
|
190
|
-
},
|
|
191
|
-
],
|
|
192
|
-
})
|
|
193
|
-
created.push(seedEntry.slug)
|
|
194
|
-
} catch (error) {
|
|
195
|
-
if (!isExpectedSeedSkipError(error)) throw error
|
|
196
|
-
skipped.push(seedEntry.slug)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return { created, skipped }
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function isExpectedSeedSkipError(error: unknown) {
|
|
204
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
205
|
-
return (
|
|
206
|
-
message.includes('Version already exists') || message.includes('Only the owner can publish')
|
|
207
|
-
)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export const ensureSeedUserInternal = internalMutation({
|
|
211
|
-
args: {
|
|
212
|
-
handle: v.string(),
|
|
213
|
-
displayName: v.string(),
|
|
214
|
-
},
|
|
215
|
-
handler: async (ctx, args) => {
|
|
216
|
-
const baseHandle = args.handle.trim()
|
|
217
|
-
const displayName = args.displayName.trim()
|
|
218
|
-
const candidates = [baseHandle, `${baseHandle}-bot`]
|
|
219
|
-
for (let i = 2; i <= 6; i += 1) candidates.push(`${baseHandle}-bot-${i}`)
|
|
220
|
-
|
|
221
|
-
for (const candidate of candidates) {
|
|
222
|
-
const existing = await ctx.db
|
|
223
|
-
.query('users')
|
|
224
|
-
.withIndex('handle', (q) => q.eq('handle', candidate))
|
|
225
|
-
.take(2)
|
|
226
|
-
const user = (existing[0] ?? null) as Doc<'users'> | null
|
|
227
|
-
if (user) {
|
|
228
|
-
if ((user.displayName ?? user.name) === displayName) return user._id
|
|
229
|
-
continue
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return ctx.db.insert('users', {
|
|
233
|
-
handle: candidate,
|
|
234
|
-
displayName,
|
|
235
|
-
createdAt: Date.now(),
|
|
236
|
-
updatedAt: Date.now(),
|
|
237
|
-
})
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
throw new Error('Unable to allocate seed user handle')
|
|
241
|
-
},
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
async function sha256Hex(bytes: Uint8Array) {
|
|
245
|
-
const data = new Uint8Array(bytes)
|
|
246
|
-
const digest = await crypto.subtle.digest('SHA-256', data)
|
|
247
|
-
return toHex(new Uint8Array(digest))
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function toHex(bytes: Uint8Array) {
|
|
251
|
-
let out = ''
|
|
252
|
-
for (const byte of bytes) out += byte.toString(16).padStart(2, '0')
|
|
253
|
-
return out
|
|
254
|
-
}
|