@atproto/pds 0.3.0-beta.2 → 0.3.0-beta.3

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.
@@ -57,6 +57,7 @@ import * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos';
57
57
  import * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate';
58
58
  import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl';
59
59
  import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos';
60
+ import * as ComAtprotoTempUpgradeRepoVersion from './types/com/atproto/temp/upgradeRepoVersion';
60
61
  import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences';
61
62
  import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile';
62
63
  import * as AppBskyActorGetProfiles from './types/app/bsky/actor/getProfiles';
@@ -137,6 +138,7 @@ export declare class AtprotoNS {
137
138
  repo: RepoNS;
138
139
  server: ServerNS;
139
140
  sync: SyncNS;
141
+ temp: TempNS;
140
142
  constructor(server: Server);
141
143
  }
142
144
  export declare class AdminNS {
@@ -225,6 +227,11 @@ export declare class SyncNS {
225
227
  requestCrawl<AV extends AuthVerifier>(cfg: ConfigOf<AV, ComAtprotoSyncRequestCrawl.Handler<ExtractAuth<AV>>, ComAtprotoSyncRequestCrawl.HandlerReqCtx<ExtractAuth<AV>>>): void;
226
228
  subscribeRepos<AV extends StreamAuthVerifier>(cfg: ConfigOf<AV, ComAtprotoSyncSubscribeRepos.Handler<ExtractAuth<AV>>, ComAtprotoSyncSubscribeRepos.HandlerReqCtx<ExtractAuth<AV>>>): void;
227
229
  }
230
+ export declare class TempNS {
231
+ _server: Server;
232
+ constructor(server: Server);
233
+ upgradeRepoVersion<AV extends AuthVerifier>(cfg: ConfigOf<AV, ComAtprotoTempUpgradeRepoVersion.Handler<ExtractAuth<AV>>, ComAtprotoTempUpgradeRepoVersion.HandlerReqCtx<ExtractAuth<AV>>>): void;
234
+ }
228
235
  export declare class AppNS {
229
236
  _server: Server;
230
237
  bsky: BskyNS;
@@ -3251,6 +3251,32 @@ export declare const schemaDict: {
3251
3251
  };
3252
3252
  };
3253
3253
  };
3254
+ ComAtprotoTempUpgradeRepoVersion: {
3255
+ lexicon: number;
3256
+ id: string;
3257
+ defs: {
3258
+ main: {
3259
+ type: string;
3260
+ description: string;
3261
+ input: {
3262
+ encoding: string;
3263
+ schema: {
3264
+ type: string;
3265
+ required: string[];
3266
+ properties: {
3267
+ did: {
3268
+ type: string;
3269
+ format: string;
3270
+ };
3271
+ force: {
3272
+ type: string;
3273
+ };
3274
+ };
3275
+ };
3276
+ };
3277
+ };
3278
+ };
3279
+ };
3254
3280
  AppBskyActorDefs: {
3255
3281
  lexicon: number;
3256
3282
  id: string;
@@ -6465,6 +6491,7 @@ export declare const ids: {
6465
6491
  ComAtprotoSyncNotifyOfUpdate: string;
6466
6492
  ComAtprotoSyncRequestCrawl: string;
6467
6493
  ComAtprotoSyncSubscribeRepos: string;
6494
+ ComAtprotoTempUpgradeRepoVersion: string;
6468
6495
  AppBskyActorDefs: string;
6469
6496
  AppBskyActorGetPreferences: string;
6470
6497
  AppBskyActorGetProfile: string;
@@ -13,6 +13,7 @@ import { LocalService } from './local';
13
13
  export declare function createServices(resources: {
14
14
  repoSigningKey: crypto.Keypair;
15
15
  blobstore: BlobStore;
16
+ pdsHostname: string;
16
17
  appViewAgent?: AtpAgent;
17
18
  appViewDid?: string;
18
19
  appViewCdnUrlPattern?: string;
@@ -15,11 +15,12 @@ declare type CommonSignedUris = 'avatar' | 'banner' | 'feed_thumbnail' | 'feed_f
15
15
  export declare class LocalService {
16
16
  db: Database;
17
17
  signingKey: Keypair;
18
+ pdsHostname: string;
18
19
  appviewAgent?: AtpAgent | undefined;
19
20
  appviewDid?: string | undefined;
20
21
  appviewCdnUrlPattern?: string | undefined;
21
- constructor(db: Database, signingKey: Keypair, appviewAgent?: AtpAgent | undefined, appviewDid?: string | undefined, appviewCdnUrlPattern?: string | undefined);
22
- static creator(signingKey: Keypair, appviewAgent?: AtpAgent, appviewDid?: string, appviewCdnUrlPattern?: string): (db: Database) => LocalService;
22
+ constructor(db: Database, signingKey: Keypair, pdsHostname: string, appviewAgent?: AtpAgent | undefined, appviewDid?: string | undefined, appviewCdnUrlPattern?: string | undefined);
23
+ static creator(signingKey: Keypair, pdsHostname: string, appviewAgent?: AtpAgent, appviewDid?: string, appviewCdnUrlPattern?: string): (db: Database) => LocalService;
23
24
  getImageUrl(pattern: CommonSignedUris, did: string, cid: string): string;
24
25
  serviceAuthHeaders(did: string): Promise<{
25
26
  headers: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/pds",
3
- "version": "0.3.0-beta.2",
3
+ "version": "0.3.0-beta.3",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,15 +39,15 @@
39
39
  "typed-emitter": "^2.1.0",
40
40
  "uint8arrays": "3.0.0",
41
41
  "zod": "^3.21.4",
42
- "@atproto/api": "^0.6.12",
43
- "@atproto/common": "^0.3.0",
44
42
  "@atproto/aws": "^0.1.0",
45
- "@atproto/crypto": "^0.2.2",
43
+ "@atproto/api": "^0.6.12",
46
44
  "@atproto/syntax": "^0.1.0",
45
+ "@atproto/common": "^0.3.0",
47
46
  "@atproto/identity": "^0.2.0",
47
+ "@atproto/crypto": "^0.2.2",
48
48
  "@atproto/repo": "^0.3.0",
49
- "@atproto/lexicon": "^0.2.0",
50
49
  "@atproto/xrpc-server": "^0.3.0",
50
+ "@atproto/lexicon": "^0.2.0",
51
51
  "@atproto/xrpc": "^0.3.0"
52
52
  },
53
53
  "devDependencies": {
@@ -61,10 +61,10 @@
61
61
  "@types/qs": "^6.9.7",
62
62
  "@types/sharp": "^0.31.0",
63
63
  "axios": "^0.27.2",
64
- "@atproto/bsky": "^0.0.3",
65
- "@atproto/api": "^0.6.12",
66
64
  "@atproto/dev-env": "^0.2.3",
67
- "@atproto/lex-cli": "^0.2.0"
65
+ "@atproto/api": "^0.6.12",
66
+ "@atproto/lex-cli": "^0.2.0",
67
+ "@atproto/bsky": "^0.0.3"
68
68
  },
69
69
  "scripts": {
70
70
  "codegen": "lex gen-server ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/*",
@@ -6,6 +6,7 @@ import moderation from './moderation'
6
6
  import repo from './repo'
7
7
  import serverMethods from './server'
8
8
  import sync from './sync'
9
+ import upgradeRepoVersion from './upgradeRepoVersion'
9
10
 
10
11
  export default function (server: Server, ctx: AppContext) {
11
12
  admin(server, ctx)
@@ -14,4 +15,5 @@ export default function (server: Server, ctx: AppContext) {
14
15
  repo(server, ctx)
15
16
  serverMethods(server, ctx)
16
17
  sync(server, ctx)
18
+ upgradeRepoVersion(server, ctx)
17
19
  }
@@ -0,0 +1,140 @@
1
+ import { InvalidRequestError } from '@atproto/xrpc-server'
2
+ import { TID, chunkArray, wait } from '@atproto/common'
3
+ import { Server } from '../../../lexicon'
4
+ import SqlRepoStorage from '../../../sql-repo-storage'
5
+ import AppContext from '../../../context'
6
+ import {
7
+ BlockMap,
8
+ CidSet,
9
+ DataDiff,
10
+ MST,
11
+ MemoryBlockstore,
12
+ def,
13
+ signCommit,
14
+ } from '@atproto/repo'
15
+ import { CID } from 'multiformats/cid'
16
+ import { formatSeqCommit, sequenceEvt } from '../../../sequencer'
17
+ import { httpLogger as log } from '../../../logger'
18
+
19
+ export default function (server: Server, ctx: AppContext) {
20
+ server.com.atproto.temp.upgradeRepoVersion({
21
+ auth: ctx.roleVerifier,
22
+ handler: async ({ input, auth }) => {
23
+ if (!auth.credentials.admin) {
24
+ throw new InvalidRequestError('must be admin')
25
+ }
26
+ const { did, force } = input.body
27
+
28
+ await ctx.db.transaction(async (dbTxn) => {
29
+ const storage = new SqlRepoStorage(dbTxn, did)
30
+ await obtainLock(storage)
31
+ const prevCid = await storage.getRoot()
32
+ if (!prevCid) {
33
+ throw new InvalidRequestError('Could not find repo')
34
+ }
35
+ const prev = await storage.readObj(prevCid, def.versionedCommit)
36
+ const records = await dbTxn.db
37
+ .selectFrom('record')
38
+ .select(['collection', 'rkey', 'cid'])
39
+ .where('did', '=', did)
40
+ .execute()
41
+ const memoryStore = new MemoryBlockstore()
42
+ let data = await MST.create(memoryStore)
43
+ for (const record of records) {
44
+ const dataKey = record.collection + '/' + record.rkey
45
+ const cid = CID.parse(record.cid)
46
+ data = await data.add(dataKey, cid)
47
+ }
48
+ const dataCid = await data.getPointer()
49
+ if (!force && !dataCid.equals(prev.data)) {
50
+ throw new InvalidRequestError('Data cid did not match')
51
+ }
52
+ const recordCids = records.map((r) => r.cid)
53
+ const diff = await DataDiff.of(data, null)
54
+ const cidsToKeep = [...recordCids, ...diff.newMstBlocks.cids()]
55
+ const rev = TID.nextStr(prev.rev)
56
+ if (force) {
57
+ const got = await storage.getBlocks(diff.newMstBlocks.cids())
58
+ const toAdd = diff.newMstBlocks.getMany(got.missing)
59
+ log.info(
60
+ { missing: got.missing.length },
61
+ 'force added missing blocks',
62
+ )
63
+ // puts any new blocks & no-ops for already existing
64
+ await storage.putMany(toAdd.blocks, rev)
65
+ }
66
+ for (const chunk of chunkArray(cidsToKeep, 500)) {
67
+ const cidStrs = chunk.map((c) => c.toString())
68
+ await dbTxn.db
69
+ .updateTable('ipld_block')
70
+ .set({ repoRev: rev })
71
+ .where('creator', '=', did)
72
+ .where('cid', 'in', cidStrs)
73
+ .execute()
74
+ }
75
+ await dbTxn.db
76
+ .deleteFrom('ipld_block')
77
+ .where('creator', '=', did)
78
+ .where((qb) =>
79
+ qb.where('repoRev', 'is', null).orWhere('repoRev', '!=', rev),
80
+ )
81
+ .execute()
82
+ await dbTxn.db
83
+ .updateTable('repo_blob')
84
+ .set({ repoRev: rev })
85
+ .where('did', '=', did)
86
+ .execute()
87
+ await dbTxn.db
88
+ .updateTable('record')
89
+ .set({ repoRev: rev })
90
+ .where('did', '=', did)
91
+ .execute()
92
+ const commit = await signCommit(
93
+ {
94
+ did,
95
+ version: 3,
96
+ rev: TID.nextStr(),
97
+ prev: prevCid,
98
+ data: dataCid,
99
+ },
100
+ ctx.repoSigningKey,
101
+ )
102
+ const newBlocks = new BlockMap()
103
+ const commitCid = await newBlocks.add(commit)
104
+ await storage.putMany(newBlocks, rev)
105
+ await dbTxn.db
106
+ .updateTable('repo_root')
107
+ .set({
108
+ root: commitCid.toString(),
109
+ rev,
110
+ indexedAt: storage.getTimestamp(),
111
+ })
112
+ .where('did', '=', did)
113
+ .execute()
114
+
115
+ const commitData = {
116
+ cid: commitCid,
117
+ rev,
118
+ prev: prevCid,
119
+ since: null,
120
+ newBlocks,
121
+ removedCids: new CidSet(),
122
+ }
123
+ const seqEvt = await formatSeqCommit(did, commitData, [])
124
+ await sequenceEvt(dbTxn, seqEvt)
125
+ })
126
+ },
127
+ })
128
+ }
129
+
130
+ const obtainLock = async (storage: SqlRepoStorage, tries = 20) => {
131
+ const obtained = await storage.lockRepo()
132
+ if (obtained) {
133
+ return
134
+ }
135
+ if (tries < 1) {
136
+ throw new InvalidRequestError('could not obtain lock')
137
+ }
138
+ await wait(50)
139
+ return obtainLock(storage, tries - 1)
140
+ }
package/src/context.ts CHANGED
@@ -180,6 +180,7 @@ export class AppContext {
180
180
  repoSigningKey,
181
181
  blobstore,
182
182
  appViewAgent,
183
+ pdsHostname: cfg.service.hostname,
183
184
  appViewDid: cfg.bskyAppView.did,
184
185
  appViewCdnUrlPattern: cfg.bskyAppView.cdnUrlPattern,
185
186
  backgroundQueue,
@@ -67,6 +67,7 @@ import * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos'
67
67
  import * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate'
68
68
  import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl'
69
69
  import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos'
70
+ import * as ComAtprotoTempUpgradeRepoVersion from './types/com/atproto/temp/upgradeRepoVersion'
70
71
  import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences'
71
72
  import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile'
72
73
  import * as AppBskyActorGetProfiles from './types/app/bsky/actor/getProfiles'
@@ -163,6 +164,7 @@ export class AtprotoNS {
163
164
  repo: RepoNS
164
165
  server: ServerNS
165
166
  sync: SyncNS
167
+ temp: TempNS
166
168
 
167
169
  constructor(server: Server) {
168
170
  this._server = server
@@ -173,6 +175,7 @@ export class AtprotoNS {
173
175
  this.repo = new RepoNS(server)
174
176
  this.server = new ServerNS(server)
175
177
  this.sync = new SyncNS(server)
178
+ this.temp = new TempNS(server)
176
179
  }
177
180
  }
178
181
 
@@ -870,6 +873,25 @@ export class SyncNS {
870
873
  }
871
874
  }
872
875
 
876
+ export class TempNS {
877
+ _server: Server
878
+
879
+ constructor(server: Server) {
880
+ this._server = server
881
+ }
882
+
883
+ upgradeRepoVersion<AV extends AuthVerifier>(
884
+ cfg: ConfigOf<
885
+ AV,
886
+ ComAtprotoTempUpgradeRepoVersion.Handler<ExtractAuth<AV>>,
887
+ ComAtprotoTempUpgradeRepoVersion.HandlerReqCtx<ExtractAuth<AV>>
888
+ >,
889
+ ) {
890
+ const nsid = 'com.atproto.temp.upgradeRepoVersion' // @ts-ignore
891
+ return this._server.xrpc.method(nsid, cfg)
892
+ }
893
+ }
894
+
873
895
  export class AppNS {
874
896
  _server: Server
875
897
  bsky: BskyNS
@@ -3517,6 +3517,32 @@ export const schemaDict = {
3517
3517
  },
3518
3518
  },
3519
3519
  },
3520
+ ComAtprotoTempUpgradeRepoVersion: {
3521
+ lexicon: 1,
3522
+ id: 'com.atproto.temp.upgradeRepoVersion',
3523
+ defs: {
3524
+ main: {
3525
+ type: 'procedure',
3526
+ description: 'Upgrade a repo to v3',
3527
+ input: {
3528
+ encoding: 'application/json',
3529
+ schema: {
3530
+ type: 'object',
3531
+ required: ['did'],
3532
+ properties: {
3533
+ did: {
3534
+ type: 'string',
3535
+ format: 'did',
3536
+ },
3537
+ force: {
3538
+ type: 'boolean',
3539
+ },
3540
+ },
3541
+ },
3542
+ },
3543
+ },
3544
+ },
3545
+ },
3520
3546
  AppBskyActorDefs: {
3521
3547
  lexicon: 1,
3522
3548
  id: 'app.bsky.actor.defs',
@@ -6838,6 +6864,7 @@ export const ids = {
6838
6864
  ComAtprotoSyncNotifyOfUpdate: 'com.atproto.sync.notifyOfUpdate',
6839
6865
  ComAtprotoSyncRequestCrawl: 'com.atproto.sync.requestCrawl',
6840
6866
  ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos',
6867
+ ComAtprotoTempUpgradeRepoVersion: 'com.atproto.temp.upgradeRepoVersion',
6841
6868
  AppBskyActorDefs: 'app.bsky.actor.defs',
6842
6869
  AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences',
6843
6870
  AppBskyActorGetProfile: 'app.bsky.actor.getProfile',
@@ -0,0 +1,39 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import express from 'express'
5
+ import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { lexicons } from '../../../../lexicons'
7
+ import { isObj, hasProp } from '../../../../util'
8
+ import { CID } from 'multiformats/cid'
9
+ import { HandlerAuth } from '@atproto/xrpc-server'
10
+
11
+ export interface QueryParams {}
12
+
13
+ export interface InputSchema {
14
+ did: string
15
+ force?: boolean
16
+ [k: string]: unknown
17
+ }
18
+
19
+ export interface HandlerInput {
20
+ encoding: 'application/json'
21
+ body: InputSchema
22
+ }
23
+
24
+ export interface HandlerError {
25
+ status: number
26
+ message?: string
27
+ }
28
+
29
+ export type HandlerOutput = HandlerError | void
30
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
31
+ auth: HA
32
+ params: QueryParams
33
+ input: HandlerInput
34
+ req: express.Request
35
+ res: express.Response
36
+ }
37
+ export type Handler<HA extends HandlerAuth = never> = (
38
+ ctx: HandlerReqCtx<HA>,
39
+ ) => Promise<HandlerOutput> | HandlerOutput
@@ -14,6 +14,7 @@ import { LocalService } from './local'
14
14
  export function createServices(resources: {
15
15
  repoSigningKey: crypto.Keypair
16
16
  blobstore: BlobStore
17
+ pdsHostname: string
17
18
  appViewAgent?: AtpAgent
18
19
  appViewDid?: string
19
20
  appViewCdnUrlPattern?: string
@@ -23,6 +24,7 @@ export function createServices(resources: {
23
24
  const {
24
25
  repoSigningKey,
25
26
  blobstore,
27
+ pdsHostname,
26
28
  appViewAgent,
27
29
  appViewDid,
28
30
  appViewCdnUrlPattern,
@@ -41,6 +43,7 @@ export function createServices(resources: {
41
43
  ),
42
44
  local: LocalService.creator(
43
45
  repoSigningKey,
46
+ pdsHostname,
44
47
  appViewAgent,
45
48
  appViewDid,
46
49
  appViewCdnUrlPattern,
@@ -39,6 +39,7 @@ export class LocalService {
39
39
  constructor(
40
40
  public db: Database,
41
41
  public signingKey: Keypair,
42
+ public pdsHostname: string,
42
43
  public appviewAgent?: AtpAgent,
43
44
  public appviewDid?: string,
44
45
  public appviewCdnUrlPattern?: string,
@@ -46,6 +47,7 @@ export class LocalService {
46
47
 
47
48
  static creator(
48
49
  signingKey: Keypair,
50
+ pdsHostname: string,
49
51
  appviewAgent?: AtpAgent,
50
52
  appviewDid?: string,
51
53
  appviewCdnUrlPattern?: string,
@@ -54,6 +56,7 @@ export class LocalService {
54
56
  new LocalService(
55
57
  db,
56
58
  signingKey,
59
+ pdsHostname,
57
60
  appviewAgent,
58
61
  appviewDid,
59
62
  appviewCdnUrlPattern,
@@ -62,7 +65,7 @@ export class LocalService {
62
65
 
63
66
  getImageUrl(pattern: CommonSignedUris, did: string, cid: string) {
64
67
  if (!this.appviewCdnUrlPattern) {
65
- return ''
68
+ return `https://${this.pdsHostname}/xrpc/${ids.ComAtprotoSyncGetBlob}?did=${did}&cid=${cid}`
66
69
  }
67
70
  return util.format(this.appviewCdnUrlPattern, pattern, did, cid)
68
71
  }
package/test.log CHANGED
Binary file