Skip to content

Commit 711e271

Browse files
committed
work done I think
1 parent 64a3cf2 commit 711e271

7 files changed

Lines changed: 90 additions & 30 deletions

File tree

app/components/global/BlogPostWrapper.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import type { BlogPostFrontmatter } from '#shared/schemas/blog'
3+
import { generateBlogTID } from '#shared/utils/atproto'
34
45
const props = defineProps<{
56
frontmatter: BlogPostFrontmatter
@@ -14,6 +15,15 @@ useSeoMeta({
1415
...(props.frontmatter.draft ? { robots: 'noindex, nofollow' } : {}),
1516
})
1617
18+
useHead({
19+
link: [
20+
{
21+
rel: 'site.standard.document',
22+
href: `at://${NPMX_DEV_DID}/site.standard.document/${generateBlogTID(props.frontmatter.date, props.frontmatter.slug)}`,
23+
},
24+
],
25+
})
26+
1727
defineOgImageComponent('BlogPost', {
1828
title: props.frontmatter.title,
1929
authors: props.frontmatter.authors,

modules/standard-site-sync.ts

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import process from 'node:process'
22
import { createHash } from 'node:crypto'
33
import { defineNuxtModule, useNuxt, createResolver } from 'nuxt/kit'
44
import { safeParse } from 'valibot'
5-
import { BlogPostSchema, type BlogPostFrontmatter } from '../shared/schemas/blog'
6-
import { NPMX_SITE } from '../shared/utils/constants'
5+
import { BlogPostSchema, type BlogPostFrontmatter } from '#shared/schemas/blog'
6+
import { NPMX_DEV_DID, NPMX_SITE } from '#shared/utils/constants'
77
import { read } from 'gray-matter'
8-
import { TID } from '@atproto/common'
98
import { PasswordSession } from '@atproto/lex-password-session'
109
import {
1110
Client,
@@ -15,11 +14,9 @@ import {
1514
} from '@atproto/lex'
1615
import * as com from '../shared/types/lexicons/com'
1716
import * as site from '../shared/types/lexicons/site'
17+
import { generateBlogTID, npmxPublicationRkey } from '#shared/utils/atproto'
1818

1919
const syncedDocuments = new Map<string, string>()
20-
const CLOCK_ID_THREE = 3
21-
const MS_TO_MICROSECONDS = 1000
22-
const ONE_DAY_MILLISECONDS = 86400000
2320

2421
type BlogPostDocument = Pick<
2522
BlogPostFrontmatter,
@@ -153,23 +150,6 @@ async function syncsiteStandardDocuments(client: Client, documentsToSync: Docume
153150
console.log('[standard-site-sync] synced all new publications')
154151
}
155152

156-
// Parse date from frontmatter, add file-path entropy for same-date collision resolution
157-
function generateTID(dateString: string, filePath: string): string {
158-
let timestamp = new Date(dateString).getTime()
159-
160-
// If date has no time component (exact midnight), add file-based entropy
161-
// This ensures unique TIDs when multiple posts share the same date
162-
if (timestamp % ONE_DAY_MILLISECONDS === 0) {
163-
// Hash the file path to generate deterministic microseconds offset
164-
const pathHash = createHash('md5').update(filePath).digest('hex')
165-
const offset = parseInt(pathHash.slice(0, 8), 16) % 1000000 // 0-999999 microseconds
166-
timestamp += offset
167-
}
168-
169-
// Clock id(3) needs to be the same everytime to get the same TID from a timestamp
170-
return TID.fromTime(timestamp * MS_TO_MICROSECONDS, CLOCK_ID_THREE).str
171-
}
172-
173153
// Schema expects 'path' & frontmatter provides 'slug'
174154
function normalizeBlogFrontmatter(frontmatter: Record<string, unknown>): Record<string, unknown> {
175155
return {
@@ -187,7 +167,8 @@ function createContentHash(data: unknown): string {
187167

188168
function buildATProtoDocument(siteUrl: string, data: BlogPostDocument) {
189169
return site.standard.document.$build({
190-
site: siteUrl as `${string}:${string}`,
170+
// site: siteUrl as `${string}:${string}`,
171+
site: `at://${NPMX_DEV_DID}/site.standard.publication/${npmxPublicationRkey()}`,
191172
path: data.path,
192173
title: data.title,
193174
description: data.description ?? data.excerpt,
@@ -242,7 +223,7 @@ const syncFile = async (
242223
return
243224
}
244225

245-
const tid = generateTID(data.date, filePath)
226+
const tid = generateBlogTID(data.date, data.slug)
246227

247228
let checkForBlogResult = await pdsPublicClient.xrpcSafe(com.atproto.repo.getRecord, {
248229
params: {
@@ -275,11 +256,7 @@ const syncFile = async (
275256
* @returns
276257
*/
277258
const checkPublication = async (identifier: AtIdentifierString, pdsPublicClient: Client) => {
278-
// Using our release date as the tid for the publication
279-
const publicationTid = TID.fromTime(
280-
new Date('2026-03-03').getTime() * MS_TO_MICROSECONDS,
281-
CLOCK_ID_THREE,
282-
).str
259+
const publicationTid = npmxPublicationRkey()
283260

284261
//Check to see if we have a publication yet
285262
const publicationCheck = await pdsPublicClient.xrpcSafe(com.atproto.repo.getRecord, {
@@ -304,6 +281,9 @@ const checkPublication = async (identifier: AtIdentifierString, pdsPublicClient:
304281
name: 'npmx.dev',
305282
url: 'https://npmx.dev/blog',
306283
description: 'a fast, modern browser for the npm registry',
284+
preferences: {
285+
showInDiscover: true,
286+
},
307287
}),
308288
}
309289
}

nuxt.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export default defineNuxtConfig({
133133
'/opensearch.xml': { isr: true },
134134
'/oauth-client-metadata.json': { prerender: true },
135135
'/.well-known/jwks.json': { prerender: true },
136+
'/.well-known/site.standard.publication': { prerender: true },
136137
// never cache
137138
'/api/auth/**': { isr: false, cache: false },
138139
'/api/social/**': { isr: false, cache: false },

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
},
5555
"dependencies": {
5656
"@atcute/bluesky-richtext-segmenter": "3.0.0",
57+
"@atcute/tid": "1.1.2",
5758
"@atproto/api": "^0.19.0",
5859
"@atproto/common": "0.5.13",
5960
"@atproto/lex": "0.0.19",

pnpm-lock.yaml

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { npmxPublicationRkey } from '#shared/utils/atproto'
2+
3+
export default defineEventHandler(async _ => {
4+
return `at://${NPMX_DEV_DID}/site.standard.publication/${npmxPublicationRkey()}`
5+
})

shared/utils/atproto.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { TID_CLOCK_ID } from './constants'
2+
import * as TID from '@atcute/tid'
3+
4+
const ONE_DAY_MILLISECONDS = 86400000
5+
const MS_TO_MICROSECONDS = 1000
6+
7+
// A very simple hasher to get an offset for blog posts on the same day
8+
const simpleHash = (str: string): number => {
9+
let h = 0
10+
for (let i = 0; i < str.length; i++) {
11+
h = ((h << 5) - h + str.charCodeAt(i)) >>> 0
12+
}
13+
return h
14+
}
15+
16+
// Parse date from frontmatter, add slug-path entropy for same-date collision resolution
17+
export const generateBlogTID = (dateString: string, slug: string): string => {
18+
let timestamp = new Date(dateString).getTime()
19+
20+
if (timestamp % ONE_DAY_MILLISECONDS === 0) {
21+
const offset = simpleHash(slug) % 1000000
22+
timestamp += offset
23+
}
24+
25+
// Clock id(3) needs to be the same everytime to get the same TID from a timestamp
26+
return TID.create(timestamp * MS_TO_MICROSECONDS, TID_CLOCK_ID)
27+
}
28+
29+
// Using our release date as the tid for the publication
30+
export const npmxPublicationRkey = () =>
31+
TID.create(new Date('2026-03-03 06:00:00').getTime() * MS_TO_MICROSECONDS, TID_CLOCK_ID)

0 commit comments

Comments
 (0)