@atproto/pds 0.4.56 → 0.4.57

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/api/com/atproto/repo/applyWrites.d.ts.map +1 -1
  3. package/dist/api/com/atproto/repo/applyWrites.js +43 -7
  4. package/dist/api/com/atproto/repo/applyWrites.js.map +1 -1
  5. package/dist/api/com/atproto/repo/createRecord.d.ts.map +1 -1
  6. package/dist/api/com/atproto/repo/createRecord.js +10 -2
  7. package/dist/api/com/atproto/repo/createRecord.js.map +1 -1
  8. package/dist/api/com/atproto/repo/deleteRecord.d.ts.map +1 -1
  9. package/dist/api/com/atproto/repo/deleteRecord.js +11 -0
  10. package/dist/api/com/atproto/repo/deleteRecord.js.map +1 -1
  11. package/dist/api/com/atproto/repo/putRecord.d.ts.map +1 -1
  12. package/dist/api/com/atproto/repo/putRecord.js +7 -0
  13. package/dist/api/com/atproto/repo/putRecord.js.map +1 -1
  14. package/dist/lexicon/lexicons.d.ts +131 -3
  15. package/dist/lexicon/lexicons.d.ts.map +1 -1
  16. package/dist/lexicon/lexicons.js +143 -6
  17. package/dist/lexicon/lexicons.js.map +1 -1
  18. package/dist/lexicon/types/com/atproto/repo/applyWrites.d.ts +38 -4
  19. package/dist/lexicon/types/com/atproto/repo/applyWrites.d.ts.map +1 -1
  20. package/dist/lexicon/types/com/atproto/repo/applyWrites.js +31 -1
  21. package/dist/lexicon/types/com/atproto/repo/applyWrites.js.map +1 -1
  22. package/dist/lexicon/types/com/atproto/repo/createRecord.d.ts +5 -2
  23. package/dist/lexicon/types/com/atproto/repo/createRecord.d.ts.map +1 -1
  24. package/dist/lexicon/types/com/atproto/repo/defs.d.ts +12 -0
  25. package/dist/lexicon/types/com/atproto/repo/defs.d.ts.map +1 -0
  26. package/dist/lexicon/types/com/atproto/repo/defs.js +16 -0
  27. package/dist/lexicon/types/com/atproto/repo/defs.js.map +1 -0
  28. package/dist/lexicon/types/com/atproto/repo/deleteRecord.d.ts +14 -2
  29. package/dist/lexicon/types/com/atproto/repo/deleteRecord.d.ts.map +1 -1
  30. package/dist/lexicon/types/com/atproto/repo/putRecord.d.ts +5 -2
  31. package/dist/lexicon/types/com/atproto/repo/putRecord.d.ts.map +1 -1
  32. package/dist/lexicon/types/tools/ozone/communication/createTemplate.d.ts +3 -0
  33. package/dist/lexicon/types/tools/ozone/communication/createTemplate.d.ts.map +1 -1
  34. package/dist/lexicon/types/tools/ozone/communication/defs.d.ts +2 -0
  35. package/dist/lexicon/types/tools/ozone/communication/defs.d.ts.map +1 -1
  36. package/dist/lexicon/types/tools/ozone/communication/defs.js.map +1 -1
  37. package/dist/lexicon/types/tools/ozone/communication/updateTemplate.d.ts +3 -0
  38. package/dist/lexicon/types/tools/ozone/communication/updateTemplate.d.ts.map +1 -1
  39. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts +1 -1
  40. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts.map +1 -1
  41. package/dist/repo/prepare.d.ts +4 -2
  42. package/dist/repo/prepare.d.ts.map +1 -1
  43. package/dist/repo/prepare.js +30 -14
  44. package/dist/repo/prepare.js.map +1 -1
  45. package/dist/repo/types.d.ts +3 -0
  46. package/dist/repo/types.d.ts.map +1 -1
  47. package/dist/repo/types.js.map +1 -1
  48. package/dist/sequencer/events.d.ts +2 -2
  49. package/package.json +4 -4
  50. package/src/api/com/atproto/repo/applyWrites.ts +41 -0
  51. package/src/api/com/atproto/repo/createRecord.ts +16 -7
  52. package/src/api/com/atproto/repo/deleteRecord.ts +11 -0
  53. package/src/api/com/atproto/repo/putRecord.ts +7 -0
  54. package/src/lexicon/lexicons.ts +143 -6
  55. package/src/lexicon/types/com/atproto/repo/applyWrites.ts +70 -3
  56. package/src/lexicon/types/com/atproto/repo/createRecord.ts +5 -2
  57. package/src/lexicon/types/com/atproto/repo/defs.ts +25 -0
  58. package/src/lexicon/types/com/atproto/repo/deleteRecord.ts +13 -1
  59. package/src/lexicon/types/com/atproto/repo/putRecord.ts +5 -2
  60. package/src/lexicon/types/tools/ozone/communication/createTemplate.ts +3 -0
  61. package/src/lexicon/types/tools/ozone/communication/defs.ts +2 -0
  62. package/src/lexicon/types/tools/ozone/communication/updateTemplate.ts +3 -0
  63. package/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +1 -0
  64. package/src/repo/prepare.ts +31 -13
  65. package/src/repo/types.ts +4 -0
  66. package/tests/crud.test.ts +131 -14
@@ -15,6 +15,8 @@ export interface TemplateView {
15
15
  /** Subject of the message, used in emails. */
16
16
  contentMarkdown: string
17
17
  disabled: boolean
18
+ /** Message language. */
19
+ lang?: string
18
20
  /** DID of the user who last updated the template. */
19
21
  lastUpdatedBy: string
20
22
  createdAt: string
@@ -16,6 +16,8 @@ export interface InputSchema {
16
16
  id: string
17
17
  /** Name of the template. */
18
18
  name?: string
19
+ /** Message language. */
20
+ lang?: string
19
21
  /** Content of the template, markdown supported, can contain variable placeholders. */
20
22
  contentMarkdown?: string
21
23
  /** Subject of the message, used in emails. */
@@ -42,6 +44,7 @@ export interface HandlerSuccess {
42
44
  export interface HandlerError {
43
45
  status: number
44
46
  message?: string
47
+ error?: 'DuplicateTemplateName'
45
48
  }
46
49
 
47
50
  export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
@@ -26,6 +26,7 @@ export interface InputSchema {
26
26
  | ToolsOzoneModerationDefs.ModEventMuteReporter
27
27
  | ToolsOzoneModerationDefs.ModEventUnmuteReporter
28
28
  | ToolsOzoneModerationDefs.ModEventReverseTakedown
29
+ | ToolsOzoneModerationDefs.ModEventResolveAppeal
29
30
  | ToolsOzoneModerationDefs.ModEventEmail
30
31
  | ToolsOzoneModerationDefs.ModEventTag
31
32
  | { $type: string; [k: string]: unknown }
@@ -29,6 +29,7 @@ import {
29
29
  InvalidRecordError,
30
30
  PreparedWrite,
31
31
  PreparedBlobRef,
32
+ ValidationStatus,
32
33
  } from './types'
33
34
  import * as lex from '../lexicon/lexicons'
34
35
  import { isRecord as isFeedGenerator } from '../lexicon/types/app/bsky/feed/generator'
@@ -39,7 +40,10 @@ import { isRecord as isList } from '../lexicon/types/app/bsky/graph/list'
39
40
  import { isRecord as isProfile } from '../lexicon/types/app/bsky/actor/profile'
40
41
  import { hasExplicitSlur } from '../handle/explicit-slurs'
41
42
 
42
- export const assertValidRecord = (record: Record<string, unknown>) => {
43
+ export const assertValidRecordWithStatus = (
44
+ record: Record<string, unknown>,
45
+ opts: { requireLexicon: boolean },
46
+ ): ValidationStatus => {
43
47
  if (typeof record.$type !== 'string') {
44
48
  throw new InvalidRecordError('No $type provided')
45
49
  }
@@ -48,7 +52,11 @@ export const assertValidRecord = (record: Record<string, unknown>) => {
48
52
  assertValidCreatedAt(record)
49
53
  } catch (e) {
50
54
  if (e instanceof LexiconDefNotFoundError) {
51
- throw new InvalidRecordError(e.message)
55
+ if (opts.requireLexicon) {
56
+ throw new InvalidRecordError(e.message)
57
+ } else {
58
+ return 'unknown'
59
+ }
52
60
  }
53
61
  throw new InvalidRecordError(
54
62
  `Invalid ${record.$type} record: ${
@@ -56,6 +64,7 @@ export const assertValidRecord = (record: Record<string, unknown>) => {
56
64
  }`,
57
65
  )
58
66
  }
67
+ return 'valid'
59
68
  }
60
69
 
61
70
  // additional more rigorous check on datetimes
@@ -98,12 +107,15 @@ export const prepareCreate = async (opts: {
98
107
  record: RepoRecord
99
108
  validate?: boolean
100
109
  }): Promise<PreparedCreate> => {
101
- const { did, collection, swapCid, validate = true } = opts
102
- const record = setCollectionName(collection, opts.record, validate)
103
- if (validate) {
104
- assertValidRecord(record)
110
+ const { did, collection, swapCid, validate } = opts
111
+ const maybeValidate = validate !== false
112
+ const record = setCollectionName(collection, opts.record, maybeValidate)
113
+ let validationStatus: ValidationStatus
114
+ if (maybeValidate) {
115
+ validationStatus = assertValidRecordWithStatus(record, {
116
+ requireLexicon: validate === true,
117
+ })
105
118
  }
106
-
107
119
  const nextRkey = TID.next()
108
120
  const rkey = opts.rkey || nextRkey.toString()
109
121
  // @TODO: validate against Lexicon record 'key' type, not just overall recordkey syntax
@@ -115,7 +127,8 @@ export const prepareCreate = async (opts: {
115
127
  cid: await cidForSafeRecord(record),
116
128
  swapCid,
117
129
  record,
118
- blobs: blobsForWrite(record, validate),
130
+ blobs: blobsForWrite(record, maybeValidate),
131
+ validationStatus,
119
132
  }
120
133
  }
121
134
 
@@ -127,10 +140,14 @@ export const prepareUpdate = async (opts: {
127
140
  record: RepoRecord
128
141
  validate?: boolean
129
142
  }): Promise<PreparedUpdate> => {
130
- const { did, collection, rkey, swapCid, validate = true } = opts
131
- const record = setCollectionName(collection, opts.record, validate)
132
- if (validate) {
133
- assertValidRecord(record)
143
+ const { did, collection, rkey, swapCid, validate } = opts
144
+ const maybeValidate = validate !== false
145
+ const record = setCollectionName(collection, opts.record, maybeValidate)
146
+ let validationStatus: ValidationStatus
147
+ if (maybeValidate) {
148
+ validationStatus = assertValidRecordWithStatus(record, {
149
+ requireLexicon: validate === true,
150
+ })
134
151
  }
135
152
  assertNoExplicitSlurs(rkey, record)
136
153
  return {
@@ -139,7 +156,8 @@ export const prepareUpdate = async (opts: {
139
156
  cid: await cidForSafeRecord(record),
140
157
  swapCid,
141
158
  record,
142
- blobs: blobsForWrite(record, validate),
159
+ blobs: blobsForWrite(record, maybeValidate),
160
+ validationStatus,
143
161
  }
144
162
  }
145
163
 
package/src/repo/types.ts CHANGED
@@ -3,6 +3,8 @@ import { AtUri } from '@atproto/syntax'
3
3
  import { WriteOpAction } from '@atproto/repo'
4
4
  import { RepoRecord } from '@atproto/lexicon'
5
5
 
6
+ export type ValidationStatus = 'valid' | 'unknown' | undefined
7
+
6
8
  export type BlobConstraint = {
7
9
  accept?: string[]
8
10
  maxSize?: number
@@ -21,6 +23,7 @@ export type PreparedCreate = {
21
23
  swapCid?: CID | null
22
24
  record: RepoRecord
23
25
  blobs: PreparedBlobRef[]
26
+ validationStatus: ValidationStatus
24
27
  }
25
28
 
26
29
  export type PreparedUpdate = {
@@ -30,6 +33,7 @@ export type PreparedUpdate = {
30
33
  swapCid?: CID | null
31
34
  record: RepoRecord
32
35
  blobs: PreparedBlobRef[]
36
+ validationStatus: ValidationStatus
33
37
  }
34
38
 
35
39
  export type PreparedDelete = {
@@ -6,7 +6,7 @@ import { TestNetworkNoAppView } from '@atproto/dev-env'
6
6
  import { cidForCbor, TID, ui8ToArrayBuffer } from '@atproto/common'
7
7
  import { BlobNotFoundError } from '@atproto/repo'
8
8
  import * as Post from '../src/lexicon/types/app/bsky/feed/post'
9
- import { paginateAll } from './_util'
9
+ import { forSnapshot, paginateAll } from './_util'
10
10
  import AppContext from '../src/context'
11
11
  import { ids, lexicons } from '../src/lexicon/lexicons'
12
12
 
@@ -565,8 +565,9 @@ describe('crud operations', () => {
565
565
  expect(got.data.value['$type']).toBe(uri.collection)
566
566
  })
567
567
 
568
- it('requires the schema to be known if validating', async () => {
568
+ it('requires the schema to be known if explicitly validating', async () => {
569
569
  const prom = aliceAgent.api.com.atproto.repo.createRecord({
570
+ validate: true,
570
571
  repo: aliceAgent.accountDid,
571
572
  collection: 'com.example.foobar',
572
573
  record: { $type: 'com.example.foobar' },
@@ -576,6 +577,16 @@ describe('crud operations', () => {
576
577
  )
577
578
  })
578
579
 
580
+ it('does not require the schema to be known if not explicitly validating', async () => {
581
+ const prom = await aliceAgent.api.com.atproto.repo.createRecord({
582
+ // validate not set
583
+ repo: aliceAgent.accountDid,
584
+ collection: 'com.example.foobar',
585
+ record: { $type: 'com.example.foobar' },
586
+ })
587
+ expect(prom.data.validationStatus).toBe('unknown')
588
+ })
589
+
579
590
  it('requires the $type to match the schema', async () => {
580
591
  await expect(
581
592
  aliceAgent.api.com.atproto.repo.createRecord({
@@ -637,6 +648,7 @@ describe('crud operations', () => {
637
648
  describe('unvalidated writes', () => {
638
649
  it('disallows creation of unknown lexicons when validate is set to true', async () => {
639
650
  const attempt = aliceAgent.api.com.atproto.repo.createRecord({
651
+ validate: true,
640
652
  repo: aliceAgent.accountDid,
641
653
  collection: 'com.example.record',
642
654
  record: {
@@ -648,52 +660,157 @@ describe('crud operations', () => {
648
660
  )
649
661
  })
650
662
 
651
- it('allows creation of unknown lexicons when validate is set to false', async () => {
652
- const res = await aliceAgent.api.com.atproto.repo.createRecord({
663
+ it('allows creation of unknown lexicons when validate is not set to true', async () => {
664
+ // validate: default
665
+ const res1 = await aliceAgent.api.com.atproto.repo.createRecord({
653
666
  repo: aliceAgent.accountDid,
654
667
  collection: 'com.example.record',
655
668
  record: {
656
- blah: 'thing',
669
+ blah: 'thing1',
657
670
  },
671
+ })
672
+ expect(res1.data.validationStatus).toBe('unknown')
673
+ const record1 = await ctx.actorStore.read(
674
+ aliceAgent.accountDid,
675
+ (store) =>
676
+ store.record.getRecord(new AtUri(res1.data.uri), res1.data.cid),
677
+ )
678
+ expect(record1?.value).toEqual({
679
+ $type: 'com.example.record',
680
+ blah: 'thing1',
681
+ })
682
+ // validate: false
683
+ const res2 = await aliceAgent.api.com.atproto.repo.createRecord({
658
684
  validate: false,
685
+ repo: aliceAgent.accountDid,
686
+ collection: 'com.example.record',
687
+ record: {
688
+ blah: 'thing2',
689
+ },
659
690
  })
660
- const record = await ctx.actorStore.read(aliceAgent.accountDid, (store) =>
661
- store.record.getRecord(new AtUri(res.data.uri), res.data.cid),
691
+ expect(res2.data.validationStatus).toBeUndefined()
692
+ const record2 = await ctx.actorStore.read(
693
+ aliceAgent.accountDid,
694
+ (store) =>
695
+ store.record.getRecord(new AtUri(res2.data.uri), res2.data.cid),
662
696
  )
663
- expect(record?.value).toEqual({
697
+ expect(record2?.value).toEqual({
664
698
  $type: 'com.example.record',
665
- blah: 'thing',
699
+ blah: 'thing2',
666
700
  })
667
701
  })
668
702
 
669
703
  it('allows update of unknown lexicons when validate is set to false', async () => {
670
704
  const createRes = await aliceAgent.api.com.atproto.repo.createRecord({
705
+ validate: false,
671
706
  repo: aliceAgent.accountDid,
672
707
  collection: 'com.example.record',
673
708
  record: {
674
709
  blah: 'thing',
675
710
  },
676
- validate: false,
677
711
  })
678
712
  const uri = new AtUri(createRes.data.uri)
679
- const updateRes = await aliceAgent.api.com.atproto.repo.putRecord({
713
+ // validate: default
714
+ const updateRes1 = await aliceAgent.api.com.atproto.repo.putRecord({
680
715
  repo: aliceAgent.accountDid,
681
716
  collection: 'com.example.record',
682
717
  rkey: uri.rkey,
683
718
  record: {
684
719
  blah: 'something else',
685
720
  },
721
+ })
722
+ const record1 = await ctx.actorStore.read(
723
+ aliceAgent.accountDid,
724
+ (store) => store.record.getRecord(uri, updateRes1.data.cid),
725
+ )
726
+ expect(record1?.value).toEqual({
727
+ $type: 'com.example.record',
728
+ blah: 'something else',
729
+ })
730
+ // validate: false
731
+ const updateRes2 = await aliceAgent.api.com.atproto.repo.putRecord({
686
732
  validate: false,
733
+ repo: aliceAgent.accountDid,
734
+ collection: 'com.example.record',
735
+ rkey: uri.rkey,
736
+ record: {
737
+ blah: 'something else',
738
+ },
687
739
  })
688
- const record = await ctx.actorStore.read(aliceAgent.accountDid, (store) =>
689
- store.record.getRecord(uri, updateRes.data.cid),
740
+ const record2 = await ctx.actorStore.read(
741
+ aliceAgent.accountDid,
742
+ (store) => store.record.getRecord(uri, updateRes2.data.cid),
690
743
  )
691
- expect(record?.value).toEqual({
744
+ expect(record2?.value).toEqual({
692
745
  $type: 'com.example.record',
693
746
  blah: 'something else',
694
747
  })
695
748
  })
696
749
 
750
+ it('applyWrites returns results with validation status', async () => {
751
+ const existing1 = await aliceAgent.api.com.atproto.repo.createRecord({
752
+ validate: false,
753
+ repo: aliceAgent.accountDid,
754
+ collection: 'com.example.record',
755
+ record: {
756
+ blah: 'thing1',
757
+ },
758
+ })
759
+ const existing2 = await aliceAgent.api.com.atproto.repo.createRecord({
760
+ validate: false,
761
+ repo: aliceAgent.accountDid,
762
+ collection: 'com.example.record',
763
+ record: {
764
+ blah: 'thing2',
765
+ },
766
+ })
767
+ const {
768
+ data: { results },
769
+ } = await aliceAgent.com.atproto.repo.applyWrites({
770
+ repo: aliceAgent.accountDid,
771
+ writes: [
772
+ {
773
+ $type: `${ids.ComAtprotoRepoApplyWrites}#create`,
774
+ action: 'create',
775
+ collection: ids.AppBskyFeedPost,
776
+ value: {
777
+ $type: ids.AppBskyFeedPost,
778
+ text: '👋',
779
+ createdAt: new Date().toISOString(),
780
+ },
781
+ },
782
+ {
783
+ $type: `${ids.ComAtprotoRepoApplyWrites}#update`,
784
+ action: 'update',
785
+ collection: 'com.example.record',
786
+ rkey: new AtUri(existing1.data.uri).rkey,
787
+ value: {},
788
+ },
789
+ {
790
+ $type: `${ids.ComAtprotoRepoApplyWrites}#delete`,
791
+ action: 'delete',
792
+ collection: 'com.example.record',
793
+ rkey: new AtUri(existing2.data.uri).rkey,
794
+ },
795
+ ],
796
+ })
797
+ expect(forSnapshot(results)).toEqual([
798
+ {
799
+ $type: `${ids.ComAtprotoRepoApplyWrites}#createResult`,
800
+ cid: 'cids(0)',
801
+ uri: 'record(0)',
802
+ validationStatus: 'valid',
803
+ },
804
+ {
805
+ $type: `${ids.ComAtprotoRepoApplyWrites}#updateResult`,
806
+ cid: 'cids(1)',
807
+ uri: 'record(1)',
808
+ validationStatus: 'unknown',
809
+ },
810
+ { $type: `${ids.ComAtprotoRepoApplyWrites}#deleteResult` },
811
+ ])
812
+ })
813
+
697
814
  it('correctly associates images with unknown record types', async () => {
698
815
  const file = await fs.readFile(
699
816
  '../dev-env/src/seed/img/key-portrait-small.jpg',