handy online tools for AT Protocol boat.kelinci.net
atproto bluesky atcute typescript solidjs

refactor: switch to valita

comfy...

mary.my.id fd899d06 e3e4c761

verified
+147 -211
+2 -2
package.json
··· 13 "@atcute/cid": "^1.0.1", 14 "@atcute/client": "^2.0.3", 15 "@atproto/crypto": "^0.4.2", 16 "@mary/events": "npm:@jsr/mary__events@^0.1.0", 17 "@mary/solid-freeze": "npm:@externdefs/solid-freeze@^0.1.1", 18 "@mary/tar": "npm:@jsr/mary__tar@^0.2.4", 19 "nanoid": "^5.0.8", 20 "native-file-system-adapter": "^3.0.1", 21 "solid-js": "^1.9.2", 22 - "uint8arrays": "^5.1.0", 23 - "valibot": "1.0.0-beta.2" 24 }, 25 "devDependencies": { 26 "@tailwindcss/forms": "^0.5.9",
··· 13 "@atcute/cid": "^1.0.1", 14 "@atcute/client": "^2.0.3", 15 "@atproto/crypto": "^0.4.2", 16 + "@badrap/valita": "^0.4.2", 17 "@mary/events": "npm:@jsr/mary__events@^0.1.0", 18 "@mary/solid-freeze": "npm:@externdefs/solid-freeze@^0.1.1", 19 "@mary/tar": "npm:@jsr/mary__tar@^0.2.4", 20 "nanoid": "^5.0.8", 21 "native-file-system-adapter": "^3.0.1", 22 "solid-js": "^1.9.2", 23 + "uint8arrays": "^5.1.0" 24 }, 25 "devDependencies": { 26 "@tailwindcss/forms": "^0.5.9",
+9 -15
pnpm-lock.yaml
··· 26 '@atproto/crypto': 27 specifier: ^0.4.2 28 version: 0.4.2 29 '@mary/events': 30 specifier: npm:@jsr/mary__events@^0.1.0 31 version: '@jsr/mary__events@0.1.0' ··· 47 uint8arrays: 48 specifier: ^5.1.0 49 version: 5.1.0 50 - valibot: 51 - specifier: 1.0.0-beta.2 52 - version: 1.0.0-beta.2(typescript@5.7.0-beta) 53 devDependencies: 54 '@tailwindcss/forms': 55 specifier: ^0.5.9 ··· 197 '@babel/types@7.26.0': 198 resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} 199 engines: {node: '>=6.9.0'} 200 201 '@cloudflare/kv-asset-handler@0.3.4': 202 resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==} ··· 1457 util-deprecate@1.0.2: 1458 resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1459 1460 - valibot@1.0.0-beta.2: 1461 - resolution: {integrity: sha512-XwAXmUPdB0Hikm6M18dD/a+j+57KA0dFlna5Yh77LBeeItSIQMwyZOjf8E2nWkhDtQ+Ie4GiaZPkLmaszH/4+w==} 1462 - peerDependencies: 1463 - typescript: '>=5' 1464 - peerDependenciesMeta: 1465 - typescript: 1466 - optional: true 1467 - 1468 validate-html-nesting@1.2.2: 1469 resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==} 1470 ··· 1727 dependencies: 1728 '@babel/helper-string-parser': 7.25.9 1729 '@babel/helper-validator-identifier': 7.25.9 1730 1731 '@cloudflare/kv-asset-handler@0.3.4': 1732 dependencies: ··· 2770 picocolors: 1.1.1 2771 2772 util-deprecate@1.0.2: {} 2773 - 2774 - valibot@1.0.0-beta.2(typescript@5.7.0-beta): 2775 - optionalDependencies: 2776 - typescript: 5.7.0-beta 2777 2778 validate-html-nesting@1.2.2: {} 2779
··· 26 '@atproto/crypto': 27 specifier: ^0.4.2 28 version: 0.4.2 29 + '@badrap/valita': 30 + specifier: ^0.4.2 31 + version: 0.4.2 32 '@mary/events': 33 specifier: npm:@jsr/mary__events@^0.1.0 34 version: '@jsr/mary__events@0.1.0' ··· 50 uint8arrays: 51 specifier: ^5.1.0 52 version: 5.1.0 53 devDependencies: 54 '@tailwindcss/forms': 55 specifier: ^0.5.9 ··· 197 '@babel/types@7.26.0': 198 resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} 199 engines: {node: '>=6.9.0'} 200 + 201 + '@badrap/valita@0.4.2': 202 + resolution: {integrity: sha512-Mwmr7k2iK0Yy0POLnAFUgab2mxKYeIsYXHY7sg3jo8XFsFHbG0SBmTcktXD0uW8N4WZePKf8s68QV7QDTGSdHA==} 203 + engines: {node: '>= 18'} 204 205 '@cloudflare/kv-asset-handler@0.3.4': 206 resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==} ··· 1461 util-deprecate@1.0.2: 1462 resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1463 1464 validate-html-nesting@1.2.2: 1465 resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==} 1466 ··· 1723 dependencies: 1724 '@babel/helper-string-parser': 7.25.9 1725 '@babel/helper-validator-identifier': 7.25.9 1726 + 1727 + '@badrap/valita@0.4.2': {} 1728 1729 '@cloudflare/kv-asset-handler@0.3.4': 1730 dependencies: ··· 2768 picocolors: 1.1.1 2769 2770 util-deprecate@1.0.2: {} 2771 2772 validate-html-nesting@1.2.2: {} 2773
+1 -3
src/api/queries/did-doc.ts
··· 1 - import * as v from 'valibot'; 2 - 3 import { At } from '@atcute/client/lexicons'; 4 5 import { didDocument, DidDocument } from '../types/did-doc'; ··· 50 throw new Error(`unsupported did method`); 51 } 52 53 - return v.parse(didDocument, rawDoc); 54 };
··· 1 import { At } from '@atcute/client/lexicons'; 2 3 import { didDocument, DidDocument } from '../types/did-doc'; ··· 48 throw new Error(`unsupported did method`); 49 } 50 51 + return didDocument.parse(rawDoc, { mode: 'passthrough' }); 52 };
+1 -3
src/api/queries/plc.ts
··· 1 - import * as v from 'valibot'; 2 - 3 import { At } from '@atcute/client/lexicons'; 4 5 import { plcLogEntries } from '../types/plc'; ··· 12 } 13 14 const json = await response.json(); 15 - return v.parse(plcLogEntries, json); 16 };
··· 1 import { At } from '@atcute/client/lexicons'; 2 3 import { plcLogEntries } from '../types/plc'; ··· 10 } 11 12 const json = await response.json(); 13 + return plcLogEntries.parse(json); 14 };
+44 -66
src/api/types/did-doc.ts
··· 1 - import * as v from 'valibot'; 2 3 import { didString, serviceUrlString, urlString } from './strings'; 4 5 const verificationMethod = v.object({ 6 id: v.string(), 7 type: v.string(), 8 controller: didString, 9 - publicKeyMultibase: v.optional( 10 - v.pipe( 11 - v.string(), 12 - v.regex(/^z[a-km-zA-HJ-NP-Z1-9]+$|^u[a-zA-Z0-9-_]+$/, 'must be a valid multibase value'), 13 - ), 14 - ), 15 }); 16 17 - const service = v.pipe( 18 - v.object({ 19 id: v.string(), 20 type: v.string(), 21 - serviceEndpoint: v.union([urlString, v.record(v.string(), urlString), v.array(urlString)]), 22 - }), 23 - v.forward( 24 - v.check((input) => { 25 - switch (input.type) { 26 - case 'AtprotoPersonalDataServer': 27 - case 'AtprotoLabeler': 28 - case 'BskyFeedGenerator': 29 - case 'BskyNotificationService': 30 - return v.is(serviceUrlString, input.serviceEndpoint); 31 } 32 33 - return true; 34 - }, 'must be a valid atproto service endpoint'), 35 - ['serviceEndpoint'], 36 - ), 37 - ); 38 39 export const didDocument = v.object({ 40 '@context': v.array(urlString), 41 id: didString, 42 - alsoKnownAs: v.optional(v.array(urlString), []), 43 - verificationMethod: v.optional(v.array(verificationMethod), []), 44 - service: v.optional( 45 - v.pipe( 46 - v.array(service), 47 - v.rawCheck(({ dataset, addIssue }) => { 48 - if (dataset.typed) { 49 - const set = new Set<string>(); 50 - const services = dataset.value; 51 52 - for (let idx = 0, len = services.length; idx < len; idx++) { 53 - const service = services[idx]; 54 - const id = service.id; 55 56 - if (!set.has(id)) { 57 - set.add(id); 58 - } else { 59 - addIssue({ 60 - message: `duplicate service id`, 61 - path: [ 62 - { 63 - type: 'array', 64 - origin: 'value', 65 - input: services, 66 - key: idx, 67 - value: service, 68 - }, 69 - { 70 - type: 'object', 71 - origin: 'value', 72 - input: service, 73 - key: 'id', 74 - value: id, 75 - }, 76 - ], 77 - }); 78 - } 79 - } 80 - } 81 - }), 82 - ), 83 - [], 84 - ), 85 }); 86 87 - export type DidDocument = v.InferOutput<typeof didDocument>; 88 89 export const getPdsEndpoint = (doc: DidDocument): string | undefined => { 90 return getServiceEndpoint(doc, '#atproto_pds', 'AtprotoPersonalDataServer');
··· 1 + import * as v from '@badrap/valita'; 2 3 import { didString, serviceUrlString, urlString } from './strings'; 4 + 5 + const PUBLIC_KEY_MULTIBASE_RE = /^z[a-km-zA-HJ-NP-Z1-9]+$/; 6 7 const verificationMethod = v.object({ 8 id: v.string(), 9 type: v.string(), 10 controller: didString, 11 + publicKeyMultibase: v 12 + .string() 13 + .assert((input) => PUBLIC_KEY_MULTIBASE_RE.test(input), `must be a valid base58btc multibase key`), 14 }); 15 16 + const service = v 17 + .object({ 18 id: v.string(), 19 type: v.string(), 20 + serviceEndpoint: v.union(urlString, v.record(urlString), v.array(urlString)), 21 + }) 22 + .chain((input) => { 23 + switch (input.type) { 24 + case 'AtprotoPersonalDataServer': 25 + case 'AtprotoLabeler': 26 + case 'BskyFeedGenerator': 27 + case 'BskyNotificationService': { 28 + const result = serviceUrlString.try(input.serviceEndpoint); 29 + if (!result.ok) { 30 + return v.err({ 31 + message: `must be a valid atproto service url`, 32 + path: ['serviceEndpoint'], 33 + }); 34 + } 35 } 36 + } 37 38 + return v.ok(input); 39 + }); 40 41 export const didDocument = v.object({ 42 '@context': v.array(urlString), 43 id: didString, 44 + alsoKnownAs: v.array(urlString).optional(() => []), 45 + verificationMethod: v.array(verificationMethod).optional(() => []), 46 + service: v.array(service).chain((input) => { 47 + for (let i = 0, len = input.length; i < len; i++) { 48 + const service = input[i]; 49 + const id = service.id; 50 51 + for (let j = 0; j < i; j++) { 52 + if (input[j].id === id) { 53 + return v.err({ 54 + message: `duplicate service id`, 55 + path: [i, 'id'], 56 + }); 57 + } 58 + } 59 + } 60 61 + return v.ok(input); 62 + }), 63 }); 64 65 + export type DidDocument = v.Infer<typeof didDocument>; 66 67 export const getPdsEndpoint = (doc: DidDocument): string | undefined => { 68 return getServiceEndpoint(doc, '#atproto_pds', 'AtprotoPersonalDataServer');
+56 -61
src/api/types/plc.ts
··· 1 - import * as v from 'valibot'; 2 3 import { didKeyString, didString, handleString, serviceUrlString, urlString } from './strings'; 4 5 - export const legacyGenesisOp = v.object({ 6 type: v.literal('create'), 7 signingKey: didKeyString, 8 recoveryKey: didKeyString, ··· 11 prev: v.null(), 12 sig: v.string(), 13 }); 14 - export type PlcLegacyGenesisOp = v.InferOutput<typeof legacyGenesisOp>; 15 16 - export const tombstoneOp = v.object({ 17 type: v.literal('plc_tombstone'), 18 prev: v.string(), 19 sig: v.string(), 20 }); 21 - export type PlcTombstoneOp = v.InferOutput<typeof tombstoneOp>; 22 23 - export const service = v.object({ 24 type: v.string(), 25 endpoint: urlString, 26 }); 27 - export type Service = v.InferOutput<typeof service>; 28 29 const updateOp = v.object({ 30 type: v.literal('plc_operation'), 31 - prev: v.nullable(v.string()), 32 sig: v.string(), 33 - rotationKeys: v.pipe( 34 - v.array(didKeyString), 35 - v.minLength(1), 36 - v.check((v) => new Set(v).size === v.length, `must contain unique keys`), 37 - ), 38 - verificationMethods: v.record(v.string(), didKeyString), 39 alsoKnownAs: v.array(urlString), 40 - services: v.record(v.string(), service), 41 }); 42 - export type PlcUpdateOp = v.InferOutput<typeof updateOp>; 43 44 - export const plcOperation = v.union([legacyGenesisOp, tombstoneOp, updateOp]); 45 - export type PlcOperation = v.InferOutput<typeof plcOperation>; 46 47 export const plcLogEntry = v.object({ 48 did: didString, 49 cid: v.string(), 50 operation: plcOperation, 51 nullified: v.boolean(), 52 - createdAt: v.pipe( 53 - v.string(), 54 - v.check((dateString) => { 55 - const date = new Date(dateString); 56 - return !Number.isNaN(date.getTime()); 57 - }), 58 - ), 59 }); 60 - export type PlcLogEntry = v.InferOutput<typeof plcLogEntry>; 61 62 export const plcLogEntries = v.array(plcLogEntry); 63 64 - export const updatePayload = v.object({ 65 - ...v.omit(updateOp, ['type', 'prev', 'sig', 'services']).entries, 66 services: v.record( 67 - v.string(), 68 - v.pipe( 69 - v.object({ 70 - type: v.string(), 71 - endpoint: urlString, 72 - }), 73 - v.rawTransform(({ dataset, addIssue, NEVER }) => { 74 - const input = dataset.value; 75 76 - switch (input.type) { 77 - case 'AtprotoPersonalDataServer': 78 - case 'AtprotoLabeler': 79 - case 'BskyFeedGenerator': 80 - case 'BskyNotificationService': { 81 - if (!v.is(serviceUrlString, input.endpoint)) { 82 - addIssue({ 83 - message: `must be a valid atproto service endpoint`, 84 - path: [ 85 - { 86 - type: 'object', 87 - origin: 'value', 88 - input: input, 89 - key: 'endpoint', 90 - value: input.endpoint, 91 - }, 92 - ], 93 - }); 94 95 - return NEVER; 96 - } 97 98 - return { ...input, endpoint: input.endpoint.replace(/\/$/, '') }; 99 } 100 } 101 102 - return input; 103 - }), 104 - ), 105 ), 106 }); 107 - export type PlcUpdatePayload = v.InferOutput<typeof updatePayload>;
··· 1 + import * as v from '@badrap/valita'; 2 3 import { didKeyString, didString, handleString, serviceUrlString, urlString } from './strings'; 4 5 + const legacyGenesisOp = v.object({ 6 type: v.literal('create'), 7 signingKey: didKeyString, 8 recoveryKey: didKeyString, ··· 11 prev: v.null(), 12 sig: v.string(), 13 }); 14 15 + const tombstoneOp = v.object({ 16 type: v.literal('plc_tombstone'), 17 prev: v.string(), 18 sig: v.string(), 19 }); 20 21 + const service = v.object({ 22 type: v.string(), 23 endpoint: urlString, 24 }); 25 + export type Service = v.Infer<typeof service>; 26 27 const updateOp = v.object({ 28 type: v.literal('plc_operation'), 29 + prev: v.string().nullable(), 30 sig: v.string(), 31 + rotationKeys: v.array(didKeyString).chain((input) => { 32 + if (input.length === 0) { 33 + return v.err({ message: `missing rotation keys` }); 34 + } 35 + 36 + for (let i = 0, len = input.length; i < len; i++) { 37 + const key = input[i]; 38 + 39 + for (let j = 0; j < i; j++) { 40 + if (input[j] === key) { 41 + return v.err({ 42 + message: `duplicate rotation key`, 43 + path: [i], 44 + }); 45 + } 46 + } 47 + } 48 + 49 + return v.ok(input); 50 + }), 51 + verificationMethods: v.record(didKeyString), 52 alsoKnownAs: v.array(urlString), 53 + services: v.record(service), 54 }); 55 + export type PlcUpdateOp = v.Infer<typeof updateOp>; 56 57 + const plcOperation = v.union(legacyGenesisOp, tombstoneOp, updateOp); 58 59 export const plcLogEntry = v.object({ 60 did: didString, 61 cid: v.string(), 62 operation: plcOperation, 63 nullified: v.boolean(), 64 + createdAt: v 65 + .string() 66 + .assert((input) => !Number.isNaN(new Date(input).getTime()), `must be a valid datetime string`), 67 }); 68 + export type PlcLogEntry = v.Infer<typeof plcLogEntry>; 69 70 export const plcLogEntries = v.array(plcLogEntry); 71 72 + export const updatePayload = updateOp.omit('type', 'prev', 'sig').extend({ 73 services: v.record( 74 + service.chain((input) => { 75 + switch (input.type) { 76 + case 'AtprotoPersonalDataServer': 77 + case 'AtprotoLabeler': 78 + case 'BskyFeedGenerator': 79 + case 'BskyNotificationService': { 80 + const endpoint = input.endpoint; 81 + const result = serviceUrlString.try(endpoint); 82 83 + if (!result.ok) { 84 + return v.err({ 85 + message: `must be a valid atproto service url`, 86 + path: ['endpoint'], 87 + }); 88 + } 89 90 + const trimmed = endpoint.replace(/\/$/, ''); 91 92 + if (endpoint !== trimmed) { 93 + return v.ok({ ...input, endpoint: trimmed }); 94 } 95 } 96 + } 97 98 + return v.ok(input); 99 + }), 100 ), 101 }); 102 + export type PlcUpdatePayload = v.Infer<typeof updatePayload>;
+22 -22
src/api/types/strings.ts
··· 1 - import * as v from 'valibot'; 2 3 - import { DID_RE, HANDLE_RE } from '../utils/strings'; 4 5 - export const didString = v.pipe(v.string(), v.regex(DID_RE, 'must be a valid did')); 6 - export const handleString = v.pipe(v.string(), v.regex(HANDLE_RE, 'must be a valid handle')); 7 8 - export const urlString = v.pipe(v.string(), v.url()); 9 10 - export const serviceUrlString = v.pipe( 11 - v.string(), 12 - v.check((urlString) => { 13 - const url = URL.parse(urlString); 14 15 - return ( 16 - url !== null && 17 - (url.protocol === 'https:' || url.protocol === 'http:') && 18 - url.pathname === '/' && 19 - url.search === '' && 20 - url.hash === '' 21 - ); 22 - }, 'must be a valid atproto service url'), 23 - ); 24 25 - export const didKeyString = v.pipe( 26 - v.string(), 27 - v.regex(/^did:key:z[a-km-zA-HJ-NP-Z1-9]+$/, 'must be a valid did:key'), 28 - );
··· 1 + import * as v from '@badrap/valita'; 2 3 + import { DID_KEY_RE, DID_RE, HANDLE_RE } from '../utils/strings'; 4 5 + export const didString = v.string().assert((input) => DID_RE.test(input), `must be a valid did`); 6 7 + export const didKeyString = v.string().assert((input) => DID_KEY_RE.test(input), `must be a valid did:key`); 8 9 + export const handleString = v.string().assert((input) => HANDLE_RE.test(input), `must be a valid handle`); 10 11 + export const urlString = v.string().assert((input) => URL.canParse(input), `must be a valid url`); 12 13 + export const serviceUrlString = v.string().assert((input) => { 14 + const url = URL.parse(input); 15 + 16 + return ( 17 + url !== null && 18 + (url.protocol === 'https:' || url.protocol === 'http:') && 19 + url.pathname === '/' && 20 + url.search === '' && 21 + url.hash === '' 22 + ); 23 + }, `must be a valid atproto service url`); 24 + 25 + export const isServiceUrlString = (str: string) => { 26 + const result = serviceUrlString.try(str); 27 + return result.ok; 28 + };
+2
src/api/utils/strings.ts
··· 9 10 export const DID_WEB_RE = /^([a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*(?:\.[a-zA-Z]{2,}))$/; 11 12 export const HANDLE_RE = /^[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(?:\.[a-zA-Z]{2,})$/; 13 14 export const DID_OR_HANDLE_RE =
··· 9 10 export const DID_WEB_RE = /^([a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*(?:\.[a-zA-Z]{2,}))$/; 11 12 + export const DID_KEY_RE = /^did:key:z[a-km-zA-HJ-NP-Z1-9]+$/; 13 + 14 export const HANDLE_RE = /^[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(?:\.[a-zA-Z]{2,})$/; 15 16 export const DID_OR_HANDLE_RE =
-21
src/globals/multiagent.ts
··· 1 - import * as v from 'valibot'; 2 - 3 - import { didString, serviceUrlString } from '~/api/types/strings'; 4 - 5 - const hexColorString = v.pipe(v.string(), v.regex(v.HEX_COLOR_REGEX)); 6 - 7 - const multiagentAccountData = v.object({ 8 - did: v.pipe(didString, v.readonly()), 9 - service: serviceUrlString, 10 - session: v.unknown(), 11 - scope: v.union([v.literal('full'), v.literal('privileged'), v.literal('limited')]), 12 - name: v.string(), 13 - color: hexColorString, 14 - }); 15 - 16 - const multiagentStorage = v.object({ 17 - active: v.optional(didString), 18 - accounts: v.array(multiagentAccountData), 19 - }); 20 - 21 - console.log(multiagentStorage);
···
+2 -3
src/views/blob/blob-export.tsx
··· 1 import { FileSystemWritableFileStream, showSaveFilePicker } from 'native-file-system-adapter'; 2 import { createSignal } from 'solid-js'; 3 - import * as v from 'valibot'; 4 5 import { simpleFetchHandler, XRPC, XRPCError } from '@atcute/client'; 6 import { At } from '@atcute/client/lexicons'; ··· 9 import { getDidDocument } from '~/api/queries/did-doc'; 10 import { resolveHandleViaAppView, resolveHandleViaPds } from '~/api/queries/handle'; 11 import { getPdsEndpoint } from '~/api/types/did-doc'; 12 - import { serviceUrlString } from '~/api/types/strings'; 13 import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings'; 14 15 import { makeAbortable } from '~/lib/utils/abortable'; ··· 276 const input = ev.currentTarget; 277 const value = input.value; 278 279 - if (value !== '' && !v.is(serviceUrlString, value)) { 280 input.setCustomValidity('Must be a valid service URL'); 281 } else { 282 input.setCustomValidity('');
··· 1 import { FileSystemWritableFileStream, showSaveFilePicker } from 'native-file-system-adapter'; 2 import { createSignal } from 'solid-js'; 3 4 import { simpleFetchHandler, XRPC, XRPCError } from '@atcute/client'; 5 import { At } from '@atcute/client/lexicons'; ··· 8 import { getDidDocument } from '~/api/queries/did-doc'; 9 import { resolveHandleViaAppView, resolveHandleViaPds } from '~/api/queries/handle'; 10 import { getPdsEndpoint } from '~/api/types/did-doc'; 11 + import { isServiceUrlString } from '~/api/types/strings'; 12 import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings'; 13 14 import { makeAbortable } from '~/lib/utils/abortable'; ··· 275 const input = ev.currentTarget; 276 const value = input.value; 277 278 + if (value !== '' && isServiceUrlString(value)) { 279 input.setCustomValidity('Must be a valid service URL'); 280 } else { 281 input.setCustomValidity('');
+2 -3
src/views/identity/did-lookup.tsx
··· 1 import { Match, Switch } from 'solid-js'; 2 - import * as v from 'valibot'; 3 4 import { At } from '@atcute/client/lexicons'; 5 6 import { getDidDocument } from '~/api/queries/did-doc'; 7 import { resolveHandleViaAppView } from '~/api/queries/handle'; 8 - import { serviceUrlString } from '~/api/types/strings'; 9 import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings'; 10 11 import { useTitle } from '~/lib/navigation/router'; ··· 124 {doc.service.map(({ id, type, serviceEndpoint }, idx) => { 125 const isString = typeof serviceEndpoint === 'string'; 126 const isURL = isString && URL.canParse('' + serviceEndpoint); 127 - const isServiceUrl = isString && v.is(serviceUrlString, serviceEndpoint); 128 129 const isPDS = type === 'AtprotoPersonalDataServer'; 130 const isLabeler = type === 'AtprotoLabeler';
··· 1 import { Match, Switch } from 'solid-js'; 2 3 import { At } from '@atcute/client/lexicons'; 4 5 import { getDidDocument } from '~/api/queries/did-doc'; 6 import { resolveHandleViaAppView } from '~/api/queries/handle'; 7 + import { isServiceUrlString } from '~/api/types/strings'; 8 import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings'; 9 10 import { useTitle } from '~/lib/navigation/router'; ··· 123 {doc.service.map(({ id, type, serviceEndpoint }, idx) => { 124 const isString = typeof serviceEndpoint === 'string'; 125 const isURL = isString && URL.canParse('' + serviceEndpoint); 126 + const isServiceUrl = isString && isServiceUrlString(serviceEndpoint); 127 128 const isPDS = type === 'AtprotoPersonalDataServer'; 129 const isLabeler = type === 'AtprotoLabeler';
+4 -9
src/views/identity/plc-applicator.tsx
··· 8 import { P256Keypair, Secp256k1Keypair, verifySignature } from '@atproto/crypto'; 9 import * as uint8arrays from 'uint8arrays'; 10 11 - import * as v from 'valibot'; 12 - 13 import { getDidDocument } from '~/api/queries/did-doc'; 14 import { resolveHandleViaAppView } from '~/api/queries/handle'; 15 import { getPlcAuditLogs } from '~/api/queries/plc'; ··· 608 return; 609 } 610 611 - const result = v.safeParse(updatePayload, json); 612 - if (!result.success) { 613 - const issue = result.issues[0]; 614 - const path = v.getDotPath(issue); 615 - 616 - setError({ step: 4, message: `Error at '.${path}'\n${issue.message}` }); 617 return; 618 } 619 620 - states.payload = result.output; 621 622 setStep(5); 623 }}
··· 8 import { P256Keypair, Secp256k1Keypair, verifySignature } from '@atproto/crypto'; 9 import * as uint8arrays from 'uint8arrays'; 10 11 import { getDidDocument } from '~/api/queries/did-doc'; 12 import { resolveHandleViaAppView } from '~/api/queries/handle'; 13 import { getPlcAuditLogs } from '~/api/queries/plc'; ··· 606 return; 607 } 608 609 + const result = updatePayload.try(json); 610 + if (!result.ok) { 611 + setError({ step: 4, message: result.message }); 612 return; 613 } 614 615 + states.payload = result.value; 616 617 setStep(5); 618 }}
+2 -3
src/views/repository/repo-export.tsx
··· 1 import { type FileSystemFileHandle, showSaveFilePicker } from 'native-file-system-adapter'; 2 import { createSignal } from 'solid-js'; 3 - import * as v from 'valibot'; 4 5 import { At } from '@atcute/client/lexicons'; 6 7 import { getDidDocument } from '~/api/queries/did-doc'; 8 import { resolveHandleViaAppView, resolveHandleViaPds } from '~/api/queries/handle'; 9 import { getPdsEndpoint } from '~/api/types/did-doc'; 10 - import { serviceUrlString } from '~/api/types/strings'; 11 import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings'; 12 13 import { useTitle } from '~/lib/navigation/router'; ··· 202 const input = ev.currentTarget; 203 const value = input.value; 204 205 - if (value !== '' && !v.is(serviceUrlString, value)) { 206 input.setCustomValidity('Must be a valid service URL'); 207 } else { 208 input.setCustomValidity('');
··· 1 import { type FileSystemFileHandle, showSaveFilePicker } from 'native-file-system-adapter'; 2 import { createSignal } from 'solid-js'; 3 4 import { At } from '@atcute/client/lexicons'; 5 6 import { getDidDocument } from '~/api/queries/did-doc'; 7 import { resolveHandleViaAppView, resolveHandleViaPds } from '~/api/queries/handle'; 8 import { getPdsEndpoint } from '~/api/types/did-doc'; 9 + import { isServiceUrlString } from '~/api/types/strings'; 10 import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings'; 11 12 import { useTitle } from '~/lib/navigation/router'; ··· 201 const input = ev.currentTarget; 202 const value = input.value; 203 204 + if (value !== '' && isServiceUrlString(value)) { 205 input.setCustomValidity('Must be a valid service URL'); 206 } else { 207 input.setCustomValidity('');