a collection of lightweight TypeScript packages for AT Protocol, the protocol powering Bluesky
atproto bluesky typescript npm
101
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(bluesky-richtext-segmenter)!: make `segmentize` generic

Mary 507aba8c 2787d43b

+54 -17
+8
.changeset/every-ways-fry.md
··· 1 + --- 2 + '@atcute/bluesky-richtext-segmenter': major 3 + --- 4 + 5 + make `segmentize` generic 6 + 7 + I've marked this as a major change but it shouldn't pose any issues for most cases. this change 8 + should allow this segmenter to be used anywhere and not just on Bluesky's rich text interfaces.
+36 -2
packages/bluesky/richtext-segmenter/lib/index.test.ts
··· 1 - import { expect, it } from 'vitest'; 2 - import { segmentize } from './index.js'; 1 + import { expect, expectTypeOf, it } from 'vitest'; 2 + import type { AppBskyRichtextFacet } from '@atcute/bluesky'; 3 + import { segmentize, type Facet, type RichtextSegment } from './index.js'; 3 4 4 5 it('does utf8 slicing', () => { 5 6 expect( ··· 60 61 }, 61 62 ]); 62 63 }); 64 + 65 + type BlueskyFeature = AppBskyRichtextFacet.Main['features'][number]; 66 + 67 + it('infers feature type from facets', () => { 68 + const facets: AppBskyRichtextFacet.Main[] = []; 69 + const result = segmentize('hello', facets); 70 + 71 + expectTypeOf(result).toEqualTypeOf<RichtextSegment<BlueskyFeature>[]>(); 72 + }); 73 + 74 + it('works with custom feature types', () => { 75 + interface CustomFeature { 76 + $type: 'custom#feature'; 77 + value: number; 78 + } 79 + 80 + const facets: Facet<CustomFeature>[] = [ 81 + { index: { byteStart: 0, byteEnd: 5 }, features: [{ $type: 'custom#feature', value: 42 }] }, 82 + ]; 83 + const result = segmentize('hello', facets); 84 + 85 + expectTypeOf(result).toEqualTypeOf<RichtextSegment<CustomFeature>[]>(); 86 + }); 87 + 88 + it('returns unknown feature type when facets is undefined', () => { 89 + const result = segmentize('hello', undefined); 90 + 91 + expectTypeOf(result).toEqualTypeOf<RichtextSegment<unknown>[]>(); 92 + }); 93 + 94 + it('accepts bluesky facets as compatible with Facet interface', () => { 95 + expectTypeOf<AppBskyRichtextFacet.Main>().toExtend<Facet<BlueskyFeature>>(); 96 + });
+9 -11
packages/bluesky/richtext-segmenter/lib/index.ts
··· 1 - import type { AppBskyRichtextFacet } from '@atcute/bluesky'; 2 - 3 - type UnwrapArray<T> = T extends (infer V)[] ? V : never; 4 - 5 - export type Facet = AppBskyRichtextFacet.Main; 6 - export type FacetFeature = UnwrapArray<Facet['features']>; 1 + export interface Facet<F = unknown> { 2 + index: { byteStart: number; byteEnd: number }; 3 + features: F[]; 4 + } 7 5 8 - export interface RichtextSegment { 6 + export interface RichtextSegment<F = unknown> { 9 7 text: string; 10 - features: FacetFeature[] | undefined; 8 + features: F[] | undefined; 11 9 } 12 10 13 - const segment = (text: string, features: FacetFeature[] | undefined): RichtextSegment => { 11 + const segment = <F>(text: string, features: F[] | undefined): RichtextSegment<F> => { 14 12 return { text, features: text.length > 0 ? features : undefined }; 15 13 }; 16 14 17 - export const segmentize = (text: string, facets: Facet[] | undefined): RichtextSegment[] => { 15 + export const segmentize = <F>(text: string, facets: Facet<F>[] | undefined): RichtextSegment<F>[] => { 18 16 if (facets === undefined || facets.length === 0) { 19 17 return [segment(text, undefined)]; 20 18 } 21 19 22 - const segments: RichtextSegment[] = []; 20 + const segments: RichtextSegment<F>[] = []; 23 21 const utf16Length = text.length; 24 22 let utf16Cursor = 0; 25 23 let utf8Cursor = 0;
+1 -4
packages/bluesky/richtext-segmenter/package.json
··· 26 26 "test": "vitest", 27 27 "prepublish": "rm -rf dist; pnpm run build" 28 28 }, 29 - "dependencies": { 29 + "devDependencies": { 30 30 "@atcute/bluesky": "workspace:^", 31 - "@atcute/lexicons": "workspace:^" 32 - }, 33 - "devDependencies": { 34 31 "@vitest/coverage-v8": "^4.0.16", 35 32 "vitest": "^4.0.16" 36 33 }