···11+# Please use your handle cause it uses it in the example
22+HANDLE=baileytownsend.dev
33+#I'm not adding oauth for an example
44+APP_PASSWORD=555-555-5555
55+PDS_HOST=https://yourpds.com
···11+# ATProto Upserts
22+33+Example repo showing you how to upsert data to the PDS. This will have an attached leaflet I'll add later.
44+55+Setup
66+Make a copy of [.env.template](.env.template) and rename it to .env. Fill it out. Make sure to use your handle.
77+88+```bash
99+(p)npm install
1010+# Runs the tid example
1111+(p)npm run eample:tid
1212+# Runs the upsert example
1313+(p)npm run example:upsert
1414+```
1515+1616+[./tid.js](./tid.js)
1717+Shows how you can create a TID from a datetime and convert it back
1818+```shell
1919+Its 1/15/2026, 9:57:18 PM or 1768535838046
2020+TID: 3mcj7faghtk2r
2121+Converted back: 1/15/2026, 9:57:18 PM or 1768535838046
2222+```
2323+2424+[./upsert.js](./upsert.js)
2525+Shows how you can upsert data to the PDS using the TID from a known date to upsert.
2626+2727+```shell
2828+You just finished a run. Uploading it to the PDS.
2929+Uploaded activity with rkey: 3mcj75qpg7c2r
3030+The PDS shows you went on a run at 2026-01-16T03:53:06.681Z.
3131+You just finished a walk. Uploading it to the PDS.
3232+Uploaded activity with rkey: 3mcj75qpg7c2r
3333+Uploaded activity with rkey: 3mcj75qspoc2r
3434+Since you did an upsert you should only have 2 records even tho you uploaded 3.
3535+You have 2 activities
3636+The PDS shows you went on a walk at 2026-01-16T03:53:06.789Z.
3737+The PDS shows you went on a run at 2026-01-16T03:53:06.681Z.
3838+```
···11+import 'dotenv/config';
22+import { Client, CredentialManager, ok } from '@atcute/client';
33+import * as TID from '@atcute/tid';
44+const CLOCK_ID = 23;
55+66+const logErrorAndThrow = (err) => {
77+ console.error(err);
88+ throw err;
99+};
1010+1111+1212+//Loading in env variables can ignore
1313+const handle = process.env.HANDLE;
1414+if (!handle) logErrorAndThrow('HANDLE is not set');
1515+const appPassword = process.env.APP_PASSWORD;
1616+if (!appPassword) logErrorAndThrow('APP_PASSWORD is not set');
1717+const pdsHost = process.env.PDS_HOST;
1818+if (!pdsHost) logErrorAndThrow('PDS_HOST is not set');
1919+2020+2121+//Sets up the atcute client
2222+const manager = new CredentialManager({ service: pdsHost });
2323+const rpc = new Client({ handler: manager });
2424+2525+// sign in with handle/email and password (or app password (please be an app password))
2626+await manager.login({ identifier: handle, password: appPassword});
2727+2828+2929+3030+// The actual example starts here, everything else before is boilerplate
3131+3232+3333+//reversing your handle to make a fun example lexicon
3434+const reversedHandle = handle.split('.').reverse().map(word => word).join('.');
3535+//An activity here is like a workout. running, walking, lifting weights, etc.
3636+let collection = `${reversedHandle}.feed.activity`
3737+3838+//A list of activities that may be gotten from your phone or wherever, but you get the whole list everytime
3939+let activities = []
4040+4141+// Helper function to upload activities
4242+const uploadActivities = async (activities, handle, collection, rpc) => {
4343+4444+ for (const activity of activities) {
4545+4646+ //Creates that unique key from the startTime of the activity so we don't have duplicates
4747+ let rKey = TID.create(activity.startTime.getTime() * 1000, CLOCK_ID);
4848+4949+ await ok(rpc.post('com.atproto.repo.putRecord', {
5050+ input: {
5151+ repo: handle,
5252+ collection,
5353+ rkey: rKey,
5454+ record: activity,
5555+5656+ }
5757+ }));
5858+ console.log(`Uploaded activity with rkey: ${rKey}`);
5959+ }
6060+6161+}
6262+6363+6464+//I just finished a run, it's saved to my phone, now uploading it to the PDS
6565+activities.push({
6666+ $type: collection,
6767+ type: 'run',
6868+ startTime: new Date()
6969+})
7070+7171+console.log('You just finished a run. Uploading it to the PDS.');
7272+7373+//Upload my activities
7474+await uploadActivities(activities, handle, collection, rpc);
7575+7676+//Going to generate the key here to show you can find the record from it easily if you have a date
7777+const rkey = TID.create(activities[0].startTime.getTime() * 1000, CLOCK_ID);
7878+7979+const activityFromPDS = await ok(rpc.get('com.atproto.repo.getRecord', {
8080+ params: {
8181+ repo: handle,
8282+ collection,
8383+ rkey,
8484+ }
8585+}));
8686+8787+console.log(`The PDS shows you went on a ${activityFromPDS.value.type} at ${activityFromPDS.value.startTime.toLocaleString()}.`);
8888+8989+9090+//I decide I want to go on a walk later, so I add it to the list
9191+activities.push({
9292+ $type: collection,
9393+ type: 'walk',
9494+ startTime: new Date()
9595+})
9696+9797+console.log('You just finished a walk. Uploading it to the PDS.');
9898+9999+//upload the new activities so the newest one can sync
100100+await uploadActivities(activities, handle, collection, rpc);
101101+102102+const listResult = await ok(rpc.get('com.atproto.repo.listRecords', {
103103+ params: {
104104+ repo: handle,
105105+ collection,
106106+ limit: 10,
107107+ }
108108+}));
109109+110110+111111+console.log("Since you did an upsert you should only have 2 records even tho you uploaded 3.")
112112+console.log(`You have ${listResult.records.length} activities saved in the PDS.`);
113113+for (const recordIndex in listResult.records){
114114+ const record = listResult.records[recordIndex]
115115+ console.log(`The PDS shows you went on a ${record.value.type} at ${record.value.startTime.toLocaleString()}.`);
116116+}