@atproto/lex-document 0.0.3 → 0.0.4
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.
- package/CHANGELOG.md +48 -0
- package/dist/lexicon-document.d.ts +3453 -21513
- package/dist/lexicon-document.d.ts.map +1 -1
- package/dist/lexicon-document.js +103 -135
- package/dist/lexicon-document.js.map +1 -1
- package/dist/lexicon-schema-builder.d.ts +11 -51
- package/dist/lexicon-schema-builder.d.ts.map +1 -1
- package/dist/lexicon-schema-builder.js +68 -15
- package/dist/lexicon-schema-builder.js.map +1 -1
- package/package.json +9 -4
- package/src/lexicon-document.test.ts +92 -2
- package/src/lexicon-document.ts +198 -276
- package/src/lexicon-schema-builder.test.ts +97 -7
- package/src/lexicon-schema-builder.ts +73 -19
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +7 -0
- package/tsconfig.tests.json +9 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseCid } from '@atproto/lex-data'
|
|
2
|
+
import { l } from '@atproto/lex-schema'
|
|
2
3
|
import { LexiconDocument, lexiconDocumentSchema } from './lexicon-document.js'
|
|
3
4
|
import { LexiconIterableIndexer } from './lexicon-iterable-indexer.js'
|
|
4
5
|
import { LexiconSchemaBuilder } from './lexicon-schema-builder.js'
|
|
@@ -59,20 +60,26 @@ describe('LexiconSchemaBuilder', () => {
|
|
|
59
60
|
type: 'object',
|
|
60
61
|
required: ['object', 'array', 'boolean', 'integer', 'string'],
|
|
61
62
|
properties: {
|
|
62
|
-
object: { type: 'ref', ref: '#
|
|
63
|
+
object: { type: 'ref', ref: '#subObject' },
|
|
63
64
|
array: { type: 'array', items: { type: 'string' } },
|
|
64
65
|
boolean: { type: 'boolean' },
|
|
65
66
|
integer: { type: 'integer' },
|
|
66
67
|
string: { type: 'string' },
|
|
68
|
+
refToEnumWithDefault: { type: 'ref', ref: '#enumWithDefault' },
|
|
67
69
|
},
|
|
68
70
|
},
|
|
69
|
-
|
|
71
|
+
subObject: {
|
|
70
72
|
type: 'object',
|
|
71
73
|
required: ['boolean'],
|
|
72
74
|
properties: {
|
|
73
75
|
boolean: { type: 'boolean' },
|
|
74
76
|
},
|
|
75
77
|
},
|
|
78
|
+
enumWithDefault: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
default: 'option3',
|
|
81
|
+
enum: ['option1', 'option2', 'option3'],
|
|
82
|
+
},
|
|
76
83
|
},
|
|
77
84
|
}),
|
|
78
85
|
])
|
|
@@ -90,6 +97,7 @@ describe('LexiconSchemaBuilder', () => {
|
|
|
90
97
|
boolean: true,
|
|
91
98
|
integer: 123,
|
|
92
99
|
string: 'string',
|
|
100
|
+
refToEnumWithDefault: 'option3',
|
|
93
101
|
},
|
|
94
102
|
array: ['one', 'two'],
|
|
95
103
|
boolean: true,
|
|
@@ -100,12 +108,12 @@ describe('LexiconSchemaBuilder', () => {
|
|
|
100
108
|
did: 'did:web:example.com',
|
|
101
109
|
cid: 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
102
110
|
bytes: new Uint8Array([0, 1, 2, 3]),
|
|
103
|
-
cidLink:
|
|
111
|
+
cidLink: parseCid(
|
|
104
112
|
'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
105
113
|
),
|
|
106
114
|
}
|
|
107
115
|
|
|
108
|
-
expect(schema.
|
|
116
|
+
expect(schema.safeParse(value)).toStrictEqual({ success: true, value })
|
|
109
117
|
})
|
|
110
118
|
|
|
111
119
|
it('Validates objects correctly', () => {
|
|
@@ -122,7 +130,89 @@ describe('LexiconSchemaBuilder', () => {
|
|
|
122
130
|
string: 'string',
|
|
123
131
|
}
|
|
124
132
|
|
|
125
|
-
expect(schema.
|
|
133
|
+
expect(schema.safeParse(value)).toStrictEqual({
|
|
134
|
+
success: true,
|
|
135
|
+
value: {
|
|
136
|
+
object: { boolean: true },
|
|
137
|
+
array: ['one', 'two'],
|
|
138
|
+
boolean: true,
|
|
139
|
+
integer: 123,
|
|
140
|
+
string: 'string',
|
|
141
|
+
refToEnumWithDefault: 'option3',
|
|
142
|
+
},
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('rejects invalid enum values', () => {
|
|
147
|
+
const schema = getSchema(
|
|
148
|
+
'com.example.kitchenSink#object',
|
|
149
|
+
l.TypedObjectSchema,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const value = {
|
|
153
|
+
object: { boolean: true },
|
|
154
|
+
array: ['one', 'two'],
|
|
155
|
+
boolean: true,
|
|
156
|
+
integer: 123,
|
|
157
|
+
string: 'string',
|
|
158
|
+
refToEnumWithDefault: 'invalidOption',
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
expect(schema.safeParse(value)).toMatchObject({
|
|
162
|
+
success: false,
|
|
163
|
+
error: {
|
|
164
|
+
issues: [
|
|
165
|
+
{
|
|
166
|
+
code: 'invalid_value',
|
|
167
|
+
input: 'invalidOption',
|
|
168
|
+
values: ['option1', 'option2', 'option3'],
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('does not apply defaults when allowTransform is false', () => {
|
|
176
|
+
const schema = getSchema(
|
|
177
|
+
'com.example.kitchenSink#object',
|
|
178
|
+
l.TypedObjectSchema,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
const value = {
|
|
182
|
+
object: { boolean: true },
|
|
183
|
+
array: ['one', 'two'],
|
|
184
|
+
boolean: true,
|
|
185
|
+
integer: 123,
|
|
186
|
+
string: 'string',
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
expect(schema.safeParse(value, { allowTransform: false })).toStrictEqual({
|
|
190
|
+
success: true,
|
|
191
|
+
value: {
|
|
192
|
+
object: { boolean: true },
|
|
193
|
+
array: ['one', 'two'],
|
|
194
|
+
boolean: true,
|
|
195
|
+
integer: 123,
|
|
196
|
+
string: 'string',
|
|
197
|
+
},
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('allows missing optional record fields', () => {
|
|
202
|
+
const schema = getSchema(
|
|
203
|
+
'com.example.kitchenSink#object',
|
|
204
|
+
l.TypedObjectSchema,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
expect(
|
|
208
|
+
schema.matches({
|
|
209
|
+
object: { boolean: true },
|
|
210
|
+
array: ['one', 'two'],
|
|
211
|
+
boolean: true,
|
|
212
|
+
integer: 123,
|
|
213
|
+
string: 'string',
|
|
214
|
+
}),
|
|
215
|
+
).toBe(true)
|
|
126
216
|
})
|
|
127
217
|
|
|
128
218
|
it('Rejects missing required record fields', () => {
|
|
@@ -139,7 +229,7 @@ describe('LexiconSchemaBuilder', () => {
|
|
|
139
229
|
string: 'string',
|
|
140
230
|
}
|
|
141
231
|
|
|
142
|
-
expect(schema.
|
|
232
|
+
expect(schema.safeParse(value)).toMatchObject({
|
|
143
233
|
success: false,
|
|
144
234
|
error: { issues: [{ code: 'required_key', key: 'array' }] },
|
|
145
235
|
})
|
|
@@ -33,8 +33,8 @@ export class LexiconSchemaBuilder {
|
|
|
33
33
|
const ctx = new LexiconSchemaBuilder(indexer)
|
|
34
34
|
try {
|
|
35
35
|
const result = await ctx.buildFullRef(fullRef)
|
|
36
|
-
if (!(result instanceof l.
|
|
37
|
-
throw new Error(`Ref ${fullRef} is not a
|
|
36
|
+
if (!(result instanceof l.Schema)) {
|
|
37
|
+
throw new Error(`Ref ${fullRef} is not a schema type`)
|
|
38
38
|
}
|
|
39
39
|
return result
|
|
40
40
|
} finally {
|
|
@@ -90,8 +90,8 @@ export class LexiconSchemaBuilder {
|
|
|
90
90
|
|
|
91
91
|
this.#asyncTasks.add(
|
|
92
92
|
this.buildFullRef(fullRef).then((v) => {
|
|
93
|
-
if (!(v instanceof l.
|
|
94
|
-
throw new Error(`Only refs to
|
|
93
|
+
if (!(v instanceof l.Schema)) {
|
|
94
|
+
throw new Error(`Only refs to schema types are allowed`)
|
|
95
95
|
}
|
|
96
96
|
validator = v
|
|
97
97
|
}),
|
|
@@ -167,11 +167,7 @@ export class LexiconSchemaBuilder {
|
|
|
167
167
|
case 'token':
|
|
168
168
|
return l.token(doc.id, hash)
|
|
169
169
|
case 'record':
|
|
170
|
-
return l.record(
|
|
171
|
-
def.key ? l.asRecordKey(def.key) : 'any',
|
|
172
|
-
doc.id,
|
|
173
|
-
this.compileObject(doc, def.record),
|
|
174
|
-
)
|
|
170
|
+
return l.record(def.key, doc.id, this.compileObject(doc, def.record))
|
|
175
171
|
case 'object':
|
|
176
172
|
return l.typedObject(doc.id, hash, this.compileObject(doc, def))
|
|
177
173
|
default:
|
|
@@ -183,13 +179,48 @@ export class LexiconSchemaBuilder {
|
|
|
183
179
|
doc: LexiconDocument,
|
|
184
180
|
def: LexiconArray | LexiconArrayItems,
|
|
185
181
|
): l.Validator<unknown> {
|
|
182
|
+
if (
|
|
183
|
+
'const' in def &&
|
|
184
|
+
'enum' in def &&
|
|
185
|
+
def.enum != null &&
|
|
186
|
+
def.const !== undefined &&
|
|
187
|
+
!(def.enum as readonly unknown[]).includes(def.const)
|
|
188
|
+
) {
|
|
189
|
+
return l.never()
|
|
190
|
+
}
|
|
191
|
+
|
|
186
192
|
switch (def.type) {
|
|
187
|
-
case 'string':
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
+
case 'string': {
|
|
194
|
+
const schema: l.StringSchema = l.string(def)
|
|
195
|
+
if (def.const != null) {
|
|
196
|
+
schema.assert(def.const)
|
|
197
|
+
return l.literal(def.const, def)
|
|
198
|
+
} else if (def.enum != null) {
|
|
199
|
+
for (const v of def.enum) schema.assert(v)
|
|
200
|
+
return l.enum(def.enum, def)
|
|
201
|
+
} else {
|
|
202
|
+
return schema
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
case 'integer': {
|
|
206
|
+
const schema: l.IntegerSchema = l.integer(def)
|
|
207
|
+
if (def.const != null) {
|
|
208
|
+
schema.assert(def.const)
|
|
209
|
+
return l.literal(def.const, def)
|
|
210
|
+
} else if (def.enum != null) {
|
|
211
|
+
for (const v of def.enum) schema.assert(v)
|
|
212
|
+
return l.enum(def.enum, def)
|
|
213
|
+
} else {
|
|
214
|
+
return schema
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
case 'boolean': {
|
|
218
|
+
if (def.const != null) {
|
|
219
|
+
return l.literal(def.const, def)
|
|
220
|
+
} else {
|
|
221
|
+
return l.boolean(def)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
193
224
|
case 'blob':
|
|
194
225
|
return l.blob(def)
|
|
195
226
|
case 'cid-link':
|
|
@@ -232,9 +263,23 @@ export class LexiconSchemaBuilder {
|
|
|
232
263
|
const props: Record<string, l.Validator> = {}
|
|
233
264
|
for (const [key, propDef] of Object.entries(def.properties)) {
|
|
234
265
|
if (propDef === undefined) continue
|
|
235
|
-
|
|
266
|
+
|
|
267
|
+
const isNullable = def.nullable?.includes(key)
|
|
268
|
+
const isRequired = def.required?.includes(key)
|
|
269
|
+
|
|
270
|
+
let schema = this.compileLeaf(doc, propDef)
|
|
271
|
+
|
|
272
|
+
if (isNullable) {
|
|
273
|
+
schema = l.nullable(schema)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!isRequired) {
|
|
277
|
+
schema = l.optional(schema)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
props[key] = schema
|
|
236
281
|
}
|
|
237
|
-
return l.object(props
|
|
282
|
+
return l.object(props)
|
|
238
283
|
}
|
|
239
284
|
|
|
240
285
|
protected compilePayload(
|
|
@@ -273,9 +318,18 @@ export class LexiconSchemaBuilder {
|
|
|
273
318
|
const props: Record<string, l.Validator> = {}
|
|
274
319
|
for (const [key, propDef] of Object.entries(def.properties)) {
|
|
275
320
|
if (propDef === undefined) continue
|
|
276
|
-
|
|
321
|
+
|
|
322
|
+
const isRequired = def.required?.includes(key)
|
|
323
|
+
|
|
324
|
+
let schema = this.compileLeaf(doc, propDef)
|
|
325
|
+
|
|
326
|
+
if (!isRequired) {
|
|
327
|
+
schema = l.optional(schema)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
props[key] = schema
|
|
277
331
|
}
|
|
278
|
-
return l.params(props
|
|
332
|
+
return l.params(props)
|
|
279
333
|
}
|
|
280
334
|
}
|
|
281
335
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": ["../../../tsconfig/isomorphic.json"],
|
|
3
|
+
"include": ["./src"],
|
|
4
|
+
"exclude": ["**/*.test.ts"],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"noImplicitAny": true,
|
|
7
|
+
"importHelpers": true,
|
|
8
|
+
"target": "ES2023",
|
|
9
|
+
"rootDir": "./src",
|
|
10
|
+
"outDir": "./dist"
|
|
11
|
+
}
|
|
12
|
+
}
|
package/tsconfig.json
ADDED