···1+# Please use your handle cause it uses it in the example
2+HANDLE=baileytownsend.dev
3+#I'm not adding oauth for an example
4+APP_PASSWORD=555-555-5555
5+PDS_HOST=https://yourpds.com
···1+# ATProto Upserts
2+3+Example repo showing you how to upsert data to the PDS. This will have an attached leaflet I'll add later.
4+5+Setup
6+Make a copy of [.env.template](.env.template) and rename it to .env. Fill it out. Make sure to use your handle.
7+8+```bash
9+(p)npm install
10+# Runs the tid example
11+(p)npm run eample:tid
12+# Runs the upsert example
13+(p)npm run example:upsert
14+```
15+16+[./tid.js](./tid.js)
17+Shows how you can create a TID from a datetime and convert it back
18+```shell
19+Its 1/15/2026, 9:57:18 PM or 1768535838046
20+TID: 3mcj7faghtk2r
21+Converted back: 1/15/2026, 9:57:18 PM or 1768535838046
22+```
23+24+[./upsert.js](./upsert.js)
25+Shows how you can upsert data to the PDS using the TID from a known date to upsert.
26+27+```shell
28+You just finished a run. Uploading it to the PDS.
29+Uploaded activity with rkey: 3mcj75qpg7c2r
30+The PDS shows you went on a run at 2026-01-16T03:53:06.681Z.
31+You just finished a walk. Uploading it to the PDS.
32+Uploaded activity with rkey: 3mcj75qpg7c2r
33+Uploaded activity with rkey: 3mcj75qspoc2r
34+Since you did an upsert you should only have 2 records even tho you uploaded 3.
35+You have 2 activities
36+The PDS shows you went on a walk at 2026-01-16T03:53:06.789Z.
37+The PDS shows you went on a run at 2026-01-16T03:53:06.681Z.
38+```
···1+import 'dotenv/config';
2+import { Client, CredentialManager, ok } from '@atcute/client';
3+import * as TID from '@atcute/tid';
4+const CLOCK_ID = 23;
5+6+const logErrorAndThrow = (err) => {
7+ console.error(err);
8+ throw err;
9+};
10+11+12+//Loading in env variables can ignore
13+const handle = process.env.HANDLE;
14+if (!handle) logErrorAndThrow('HANDLE is not set');
15+const appPassword = process.env.APP_PASSWORD;
16+if (!appPassword) logErrorAndThrow('APP_PASSWORD is not set');
17+const pdsHost = process.env.PDS_HOST;
18+if (!pdsHost) logErrorAndThrow('PDS_HOST is not set');
19+20+21+//Sets up the atcute client
22+const manager = new CredentialManager({ service: pdsHost });
23+const rpc = new Client({ handler: manager });
24+25+// sign in with handle/email and password (or app password (please be an app password))
26+await manager.login({ identifier: handle, password: appPassword});
27+28+29+30+// The actual example starts here, everything else before is boilerplate
31+32+33+//reversing your handle to make a fun example lexicon
34+const reversedHandle = handle.split('.').reverse().map(word => word).join('.');
35+//An activity here is like a workout. running, walking, lifting weights, etc.
36+let collection = `${reversedHandle}.feed.activity`
37+38+//A list of activities that may be gotten from your phone or wherever, but you get the whole list everytime
39+let activities = []
40+41+// Helper function to upload activities
42+const uploadActivities = async (activities, handle, collection, rpc) => {
43+44+ for (const activity of activities) {
45+46+ //Creates that unique key from the startTime of the activity so we don't have duplicates
47+ let rKey = TID.create(activity.startTime.getTime() * 1000, CLOCK_ID);
48+49+ await ok(rpc.post('com.atproto.repo.putRecord', {
50+ input: {
51+ repo: handle,
52+ collection,
53+ rkey: rKey,
54+ record: activity,
55+56+ }
57+ }));
58+ console.log(`Uploaded activity with rkey: ${rKey}`);
59+ }
60+61+}
62+63+64+//I just finished a run, it's saved to my phone, now uploading it to the PDS
65+activities.push({
66+ $type: collection,
67+ type: 'run',
68+ startTime: new Date()
69+})
70+71+console.log('You just finished a run. Uploading it to the PDS.');
72+73+//Upload my activities
74+await uploadActivities(activities, handle, collection, rpc);
75+76+//Going to generate the key here to show you can find the record from it easily if you have a date
77+const rkey = TID.create(activities[0].startTime.getTime() * 1000, CLOCK_ID);
78+79+const activityFromPDS = await ok(rpc.get('com.atproto.repo.getRecord', {
80+ params: {
81+ repo: handle,
82+ collection,
83+ rkey,
84+ }
85+}));
86+87+console.log(`The PDS shows you went on a ${activityFromPDS.value.type} at ${activityFromPDS.value.startTime.toLocaleString()}.`);
88+89+90+//I decide I want to go on a walk later, so I add it to the list
91+activities.push({
92+ $type: collection,
93+ type: 'walk',
94+ startTime: new Date()
95+})
96+97+console.log('You just finished a walk. Uploading it to the PDS.');
98+99+//upload the new activities so the newest one can sync
100+await uploadActivities(activities, handle, collection, rpc);
101+102+const listResult = await ok(rpc.get('com.atproto.repo.listRecords', {
103+ params: {
104+ repo: handle,
105+ collection,
106+ limit: 10,
107+ }
108+}));
109+110+111+console.log("Since you did an upsert you should only have 2 records even tho you uploaded 3.")
112+console.log(`You have ${listResult.records.length} activities saved in the PDS.`);
113+for (const recordIndex in listResult.records){
114+ const record = listResult.records[recordIndex]
115+ console.log(`The PDS shows you went on a ${record.value.type} at ${record.value.startTime.toLocaleString()}.`);
116+}