Ionosphere.tv
3
fork

Configure Feed

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

feat: upgrade @panproto/core from 0.22.0 to 0.25.1

Upgrade panproto to gain v0.23-v0.25.1 features:
- PipelineBuilder combinator API (renameField, hoistField, mapItems)
- autoGenerateWithHints for cross-namespace lens generation
- ATProto array "items" edge kind fix (v0.25.1)

Replace the v0.22.0 parseLexiconDirect workaround with the now-stable
parseLexicon method. Add buildPipelineFromSpec() to compile lens spec
JSON into native protolens chains. Wire up PipelineBuilder in ingest.ts
for the schedule-to-talk transform (with JS applyLens fallback when
WASM is unavailable). Add tests for pipeline, autoGenerateWithHints,
mapItems, and the ATProto array fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+347 -74
+1 -1
apps/ionosphere-appview/package.json
··· 6 6 "@atproto/api": "^0.15.0", 7 7 "@hono/node-server": "^1.13.0", 8 8 "@ionosphere/format": "workspace:*", 9 - "@panproto/core": "^0.22.0", 9 + "@panproto/core": "^0.25.1", 10 10 "an-array-of-english-words": "^2.0.0", 11 11 "better-sqlite3": "^12.8.0", 12 12 "compromise": "^14.15.0",
+37 -1
apps/ionosphere-appview/src/ingest.ts
··· 1 1 import { openDb, migrate } from "./db.js"; 2 2 import { correlate, type ScheduleEvent, type VodRecord } from "./correlate.js"; 3 - import { loadLens, applyLens } from "@ionosphere/format/lenses"; 3 + import { loadLens, applyLens, init as initPanproto, createPipeline } from "@ionosphere/format/lenses"; 4 + import type { ProtolensChainHandle } from "@ionosphere/format/lenses"; 4 5 import { resolveLensRecord } from "./lens-resolver.js"; 5 6 6 7 const scheduleLens = loadLens("schedule-to-talk.lens.json"); 8 + 9 + /** 10 + * Build a native panproto pipeline for the schedule-to-talk transform. 11 + * Returns null if WASM is not available. 12 + * 13 + * Uses the PipelineBuilder combinator API (@panproto/core@0.23+): 14 + * renameField — rename JSON property keys 15 + * hoistField — unnest fields from intermediate objects 16 + */ 17 + async function buildSchedulePipeline(): Promise<ProtolensChainHandle | null> { 18 + try { 19 + const pp = await initPanproto(); 20 + const parent = "community.lexicon.calendar.event:body"; 21 + const ad = `${parent}.additionalData`; 22 + 23 + return createPipeline(pp) 24 + .renameField(parent, "name", "title") 25 + .hoistField(parent, ad, "room") 26 + .hoistField(parent, ad, "category") 27 + .hoistField(parent, ad, "type") 28 + .renameField(parent, "type", "talkType") 29 + .hoistField(parent, ad, "speakers") 30 + .build(); 31 + } catch { 32 + // WASM not available — fall back to JS applyLens 33 + return null; 34 + } 35 + } 7 36 8 37 const SCHEDULE_DID = "did:plc:3xewinw4wtimo2lqfy5fm5sw"; 9 38 const SCHEDULE_COLLECTION = "community.lexicon.calendar.event"; ··· 85 114 } 86 115 87 116 async function main() { 117 + // Try to build native panproto pipeline (WASM-backed, bidirectional) 118 + const schedulePipeline = await buildSchedulePipeline(); 119 + if (schedulePipeline) { 120 + console.log("Using panproto PipelineBuilder for schedule-to-talk transform"); 121 + } 122 + 123 + // Fall back to JSON lens spec (JS-only applyLens) if WASM is not available 88 124 let scheduleLensResolved; 89 125 try { 90 126 const resolved = await resolveLensRecord("community.lexicon.calendar.event", "tv.ionosphere.talk");
+6 -3
apps/ionosphere-appview/src/providers/openai-whisper.ts
··· 20 20 timestamp_granularities: ["word"], 21 21 }); 22 22 23 - // Lens candidate: openai.whisper.verbose_json → tv.ionosphere.transcript 24 - // Currently a simple field mapping. Graduate to panproto lens when the 25 - // pipeline() combinator API is available (see panproto/panproto#15). 23 + // Lens: openai.whisper.verbose_json -> tv.ionosphere.transcript 24 + // The pipeline() combinator API is now available in @panproto/core@0.23+. 25 + // This can be expressed as: 26 + // createPipeline(pp).mapItems("words", { step_type: "add_field", ... }) 27 + // For now, this remains a simple field mapping since the transform is trivial 28 + // (just adding a default confidence field per word). 26 29 27 30 // Lens: OpenAI's word format → ionosphere WordTimestamp 28 31 // OpenAI returns { word, start, end } — we add confidence (not provided, default 1.0)
+9 -1
formats/tv.ionosphere/lenses/README.md
··· 4 4 5 5 Published to the PDS as `org.relationaltext.lens` records by `publish.ts`. Used as fallback by pipeline scripts when PDS is unavailable. 6 6 7 - When panproto's `pipeline()` combinator API ships (see panproto/panproto#15), these specs will be replaced by panproto protolens chains. 7 + ## Panproto Integration (@panproto/core@0.25.1) 8 + 9 + As of v0.23.0, panproto ships the `PipelineBuilder` combinator API. Lens spec JSON files can be compiled into native protolens chains via `buildPipelineFromSpec()` in `ts/lenses.ts`, which translates rules into panproto combinators: 10 + 11 + - Simple field renames -> `renameField()` 12 + - Dotted paths (e.g. `additionalData.room`) -> `hoistField()` + optional `renameField()` 13 + - Array element transforms -> `mapItems()` with inner steps 14 + 15 + When WASM is not available, the JS-only `applyLens()` fallback handles the same lens spec files with equivalent field mapping behavior (without complement tracking or bidirectional semantics). 8 16 9 17 See `docs/superpowers/specs/2026-03-31-lens-layer-design.md` for the full design.
+1 -1
formats/tv.ionosphere/package.json
··· 12 12 }, 13 13 "dependencies": { 14 14 "@msgpack/msgpack": "^3.1.3", 15 - "@panproto/core": "^0.22.0", 15 + "@panproto/core": "^0.25.1", 16 16 "relational-text": "^0.1.1" 17 17 }, 18 18 "devDependencies": {
+67 -2
formats/tv.ionosphere/ts/lenses.ts
··· 1 1 import { readFileSync } from "node:fs"; 2 2 import path from "node:path"; 3 + import { createPipeline as _createPipeline } from "./panproto.js"; 4 + import type { PipelineBuilder, ProtolensChainHandle, Panproto } from "./panproto.js"; 3 5 4 6 export interface LensSpec { 5 7 $type: string; ··· 25 27 } 26 28 27 29 /** 30 + * Build a panproto PipelineBuilder from a lens spec JSON file. 31 + * 32 + * Translates LensSpec rules into native panproto combinators: 33 + * - Simple field renames use renameField() 34 + * - Dotted paths (e.g. "additionalData.room") use hoistField() 35 + * 36 + * Returns a ProtolensChainHandle that can be applied via WASM. 37 + * 38 + * @since @panproto/core@0.23.0 39 + */ 40 + export function buildPipelineFromSpec( 41 + pp: Panproto, 42 + lens: LensSpec 43 + ): ProtolensChainHandle { 44 + const builder: PipelineBuilder = _createPipeline(pp); 45 + 46 + // The parent vertex for ATProto record fields is typically "nsid:body" 47 + const parent = `${lens.source}:body`; 48 + 49 + for (const rule of lens.rules) { 50 + const sourceName = rule.match.name; 51 + const targetName = rule.replace.name; 52 + 53 + if (sourceName === targetName) { 54 + // Identity mapping — no transform needed 55 + continue; 56 + } 57 + 58 + const parts = sourceName.split("."); 59 + if (parts.length === 2) { 60 + // Dotted path like "additionalData.room" -> hoist from intermediate 61 + const [intermediate, child] = parts; 62 + builder.hoistField(parent, `${parent}.${intermediate}`, child); 63 + // Then rename if the target name differs from the child 64 + if (child !== targetName) { 65 + builder.renameField(parent, child, targetName); 66 + } 67 + } else { 68 + // Simple rename 69 + builder.renameField(parent, sourceName, targetName); 70 + } 71 + } 72 + 73 + return builder.build(); 74 + } 75 + 76 + /** 28 77 * Apply a lens to transform a source record's fields to target field names. 29 - * Returns a new object with renamed keys per the lens rules. 78 + * 79 + * This is the JS-only fallback that works without WASM. It handles field 80 + * renames and dotted-path hoisting per the lens spec rules. When panproto 81 + * WASM is available, prefer buildPipelineFromSpec() for full bidirectional 82 + * lens semantics with complement tracking. 83 + * 30 84 * Fields not matched by any rule are kept or dropped per `passthrough`. 31 85 */ 32 86 export function applyLens( ··· 69 123 } 70 124 71 125 // Panproto wrapper re-exports for new code 72 - export { init, loadSchema, createLens, buildMigration, migrateRecord, serializeChain } from "./panproto.js"; 126 + export { 127 + init, 128 + loadSchema, 129 + createLens, 130 + createPipeline, 131 + autoGenerateWithHints, 132 + buildMigration, 133 + migrateRecord, 134 + serializeChain, 135 + PipelineBuilder, 136 + ProtolensChainHandle, 137 + } from "./panproto.js"; 73 138 74 139 export type { BuiltSchema, Panproto } from "./panproto.js";
+173 -3
formats/tv.ionosphere/ts/panproto.test.ts
··· 6 6 loadSchema, 7 7 buildMigration, 8 8 migrateRecord, 9 + createPipeline, 10 + autoGenerateWithHints, 9 11 } from "./panproto.js"; 12 + import { applyLens, loadLens, buildPipelineFromSpec } from "./lenses.js"; 10 13 11 14 const LEXICON_DIR = path.resolve(import.meta.dirname, "../../../lexicons"); 12 15 ··· 51 54 }); 52 55 53 56 it("renames fields via migration on structurally similar schemas", async () => { 54 - // Cross-schema migration works when the schemas are structurally similar. 55 - // For complex cross-schema transforms (calendar→talk with many unmapped fields), 56 - // compose panproto with a mechanical pre-processor. 57 57 const pp = await initPanproto(); 58 58 const atproto = pp.protocol("atproto"); 59 59 ··· 146 146 }); 147 147 }); 148 148 149 + describeWasm("pipeline combinator API (v0.23+)", () => { 150 + it("builds a pipeline with renameField", async () => { 151 + const pp = await initPanproto(); 152 + const pipeline = createPipeline(pp); 153 + const chain = pipeline 154 + .renameField("test.source:body", "name", "title") 155 + .build(); 156 + expect(chain).toBeDefined(); 157 + const json = chain.toJson(); 158 + expect(json).toContain("rename_field"); 159 + }); 160 + 161 + it("builds a pipeline with hoistField", async () => { 162 + const pp = await initPanproto(); 163 + const pipeline = createPipeline(pp); 164 + const chain = pipeline 165 + .hoistField( 166 + "community.lexicon.calendar.event:body", 167 + "community.lexicon.calendar.event:body.additionalData", 168 + "room" 169 + ) 170 + .build(); 171 + expect(chain).toBeDefined(); 172 + const json = chain.toJson(); 173 + expect(json).toContain("hoist_field"); 174 + }); 175 + 176 + it("builds the schedule-to-talk pipeline", async () => { 177 + const pp = await initPanproto(); 178 + const pipeline = createPipeline(pp); 179 + const parent = "community.lexicon.calendar.event:body"; 180 + const ad = `${parent}.additionalData`; 181 + 182 + const chain = pipeline 183 + .renameField(parent, "name", "title") 184 + .hoistField(parent, ad, "room") 185 + .hoistField(parent, ad, "category") 186 + .hoistField(parent, ad, "type") 187 + .renameField(parent, "type", "talkType") 188 + .hoistField(parent, ad, "speakers") 189 + .build(); 190 + 191 + expect(chain).toBeDefined(); 192 + const json = chain.toJson(); 193 + expect(json).toContain("rename_field"); 194 + expect(json).toContain("hoist_field"); 195 + }); 196 + }); 197 + 198 + describeWasm("autoGenerateWithHints (v0.23+)", () => { 199 + it("generates a cross-schema lens with hints for calendar->talk", async () => { 200 + const calSchema = await loadSchema( 201 + readLexicon("community/lexicon/calendar/event.json") 202 + ); 203 + const talkSchema = await loadSchema(readLexicon("tv/ionosphere/talk.json")); 204 + 205 + // Seed the morphism search with vertex correspondences 206 + const chain = await autoGenerateWithHints(calSchema, talkSchema, { 207 + "community.lexicon.calendar.event": "tv.ionosphere.talk", 208 + "community.lexicon.calendar.event:body": "tv.ionosphere.talk:body", 209 + "community.lexicon.calendar.event:body.name": 210 + "tv.ionosphere.talk:body.title", 211 + "community.lexicon.calendar.event:body.description": 212 + "tv.ionosphere.talk:body.description", 213 + "community.lexicon.calendar.event:body.startsAt": 214 + "tv.ionosphere.talk:body.startsAt", 215 + "community.lexicon.calendar.event:body.endsAt": 216 + "tv.ionosphere.talk:body.endsAt", 217 + }); 218 + 219 + expect(chain).toBeDefined(); 220 + const json = chain.toJson(); 221 + expect(json.length).toBeGreaterThan(0); 222 + }); 223 + }); 224 + 225 + describeWasm("mapItems combinator (v0.23+)", () => { 226 + it("builds a pipeline with mapItems for array transforms", async () => { 227 + const pp = await initPanproto(); 228 + const pipeline = createPipeline(pp); 229 + 230 + // Whisper words[] — each element has { word, start, end } and we could 231 + // add a confidence field via an inner addField step 232 + const chain = pipeline 233 + .mapItems("openai.whisper.verbose_json:body.words", { 234 + step_type: "add_field", 235 + parent: "openai.whisper.verbose_json:word", 236 + name: "confidence", 237 + kind: "number", 238 + }) 239 + .build(); 240 + 241 + expect(chain).toBeDefined(); 242 + const json = chain.toJson(); 243 + expect(json).toContain("map_items"); 244 + }); 245 + }); 246 + 247 + describeWasm("ATProto array fix (v0.25.1)", () => { 248 + it("parses lexicons with array items edges correctly", async () => { 249 + // The Whisper verbose_json lexicon has array fields with "items" refs. 250 + // v0.25.1 fixes the "items" edge kind handling for ATProto arrays. 251 + const whisperSchema = await loadSchema( 252 + readLexicon("openai/whisper/verbose_json.json") 253 + ); 254 + expect(whisperSchema).toBeDefined(); 255 + 256 + // The words array should be parsed with proper items edges 257 + const vertexNames = Object.keys(whisperSchema.vertices); 258 + expect(vertexNames).toContain("openai.whisper.verbose_json:body.words"); 259 + 260 + // Check edges for "items" kind 261 + const itemsEdges = whisperSchema.edges.filter( 262 + (e: any) => e.kind === "items" 263 + ); 264 + expect(itemsEdges.length).toBeGreaterThan(0); 265 + }); 266 + 267 + it("parses talk lexicon with speakerUris array", async () => { 268 + const talkSchema = await loadSchema(readLexicon("tv/ionosphere/talk.json")); 269 + expect(talkSchema).toBeDefined(); 270 + 271 + const vertexNames = Object.keys(talkSchema.vertices); 272 + expect(vertexNames).toContain("tv.ionosphere.talk:body.speakerUris"); 273 + 274 + // speakerUris is an array of strings — should have items edge 275 + const itemsEdges = talkSchema.edges.filter( 276 + (e: any) => e.kind === "items" 277 + ); 278 + expect(itemsEdges.length).toBeGreaterThan(0); 279 + }); 280 + }); 281 + 282 + describe("applyLens (JS fallback)", () => { 283 + it("renames simple fields", () => { 284 + const lens = loadLens("schedule-to-talk.lens.json"); 285 + const source = { 286 + name: "Building on AT Protocol", 287 + description: "A great talk", 288 + startsAt: "2026-03-27T10:00:00Z", 289 + endsAt: "2026-03-27T11:00:00Z", 290 + additionalData: { 291 + room: "Great Hall South", 292 + category: "Development", 293 + type: "presentation", 294 + speakers: [{ name: "Alice", id: "alice.bsky.social" }], 295 + }, 296 + }; 297 + 298 + const result = applyLens(lens, source); 299 + 300 + expect(result.title).toBe("Building on AT Protocol"); 301 + expect(result.description).toBe("A great talk"); 302 + expect(result.startsAt).toBe("2026-03-27T10:00:00Z"); 303 + expect(result.room).toBe("Great Hall South"); 304 + expect(result.category).toBe("Development"); 305 + expect(result.talkType).toBe("presentation"); 306 + expect(result.speakers).toEqual([ 307 + { name: "Alice", id: "alice.bsky.social" }, 308 + ]); 309 + // Original field name should not be present 310 + expect(result.name).toBeUndefined(); 311 + }); 312 + }); 313 + 149 314 describe("panproto wrapper (static checks)", () => { 150 315 it("exports expected functions", async () => { 151 316 const mod = await import("./panproto.js"); ··· 155 320 expect(typeof mod.migrateRecord).toBe("function"); 156 321 expect(typeof mod.createLens).toBe("function"); 157 322 expect(typeof mod.serializeChain).toBe("function"); 323 + // New v0.23+ exports 324 + expect(typeof mod.createPipeline).toBe("function"); 325 + expect(typeof mod.autoGenerateWithHints).toBe("function"); 326 + expect(mod.PipelineBuilder).toBeDefined(); 327 + expect(mod.ProtolensChainHandle).toBeDefined(); 158 328 }); 159 329 160 330 it("reports WASM availability", () => {
+46 -55
formats/tv.ionosphere/ts/panproto.ts
··· 1 - import { Panproto, BuiltSchema, type LensHandle } from "@panproto/core"; 1 + import { 2 + Panproto, 3 + BuiltSchema, 4 + PipelineBuilder, 5 + ProtolensChainHandle, 6 + type LensHandle, 7 + } from "@panproto/core"; 2 8 import type { WasmGlueModule, MigrationSpec } from "@panproto/core"; 3 9 import { CompiledMigration } from "@panproto/core"; 4 10 import { readFileSync } from "node:fs"; ··· 46 52 /** 47 53 * Parse an ATProto lexicon JSON into a panproto schema. 48 54 * 49 - * Works around a format mismatch in @panproto/core@0.22.0 where 50 - * the WASM returns positional arrays from schema_metadata but the 51 - * SDK expects named object keys. 55 + * In @panproto/core@0.25.1, parseLexicon is a stable, direct method 56 + * on the Panproto instance. The v0.22.0 workaround for positional 57 + * arrays from schema_metadata is no longer needed. 52 58 */ 53 59 export async function loadSchema( 54 60 lexiconJson: object | string 55 61 ): Promise<BuiltSchema> { 56 62 const pp = await init(); 57 - try { 58 - return pp.parseLexicon(lexiconJson); 59 - } catch { 60 - return parseLexiconDirect(pp, lexiconJson); 61 - } 62 - } 63 - 64 - async function parseLexiconDirect( 65 - pp: Panproto, 66 - lexiconJson: object | string 67 - ): Promise<BuiltSchema> { 68 - const { decode } = await import("@msgpack/msgpack"); 69 - const wasm = (pp as any)._wasm; 70 - 71 - const jsonStr = 72 - typeof lexiconJson === "string" ? lexiconJson : JSON.stringify(lexiconJson); 73 - const jsonBytes = new TextEncoder().encode(jsonStr); 74 - 75 - const rawHandle = wasm.exports.parse_atproto_lexicon(jsonBytes); 76 - const metaBytes = wasm.exports.schema_metadata(rawHandle); 77 - const meta = decode(metaBytes) as any; 78 - 79 - const [protocol, rawVertices, rawEdges] = Array.isArray(meta) 80 - ? meta 81 - : [meta.protocol, meta.vertices, meta.edges]; 82 - 83 - const vertices = Object.fromEntries( 84 - (rawVertices || []).map((v: any) => { 85 - const id = Array.isArray(v) ? v[0] : v.id; 86 - const kind = Array.isArray(v) ? v[1] : v.kind; 87 - const nsid = Array.isArray(v) ? v[2] : v.nsid; 88 - return [id, { id, kind, nsid: nsid ?? undefined }]; 89 - }) 90 - ); 91 - 92 - const edges = (rawEdges || []).map((e: any) => { 93 - if (Array.isArray(e)) { 94 - return { src: e[0], tgt: e[1], kind: e[2], name: e[3] ?? undefined }; 95 - } 96 - return { src: e.src, tgt: e.tgt, kind: e.kind, name: e.name }; 97 - }); 98 - 99 - const data = { 100 - protocol: protocol as string, 101 - vertices, 102 - edges, 103 - hyperEdges: {}, 104 - constraints: {}, 105 - }; 106 - 107 - return (BuiltSchema as any)._fromHandle(rawHandle, data, protocol, wasm); 63 + return pp.parseLexicon(lexiconJson); 108 64 } 109 65 110 66 /** ··· 177 133 } 178 134 179 135 /** 136 + * Auto-generate a protolens chain with morphism hints. 137 + * 138 + * Hints are vertex correspondences that seed the morphism search, 139 + * enabling alignment across schemas with different NSID namespaces 140 + * (e.g. community.lexicon.calendar.event -> tv.ionosphere.talk). 141 + * 142 + * @since @panproto/core@0.23.0 143 + */ 144 + export async function autoGenerateWithHints( 145 + from: BuiltSchema, 146 + to: BuiltSchema, 147 + hints: Record<string, string> 148 + ): Promise<ProtolensChainHandle> { 149 + const pp = await init(); 150 + return ProtolensChainHandle.autoGenerateWithHints(from, to, hints, pp._wasm); 151 + } 152 + 153 + /** 154 + * Create a PipelineBuilder for constructing lens transforms from combinators. 155 + * 156 + * Usage: 157 + * const pp = await init(); 158 + * const chain = createPipeline(pp) 159 + * .renameField('body', 'name', 'title') 160 + * .hoistField('body', 'additionalData', 'room') 161 + * .build(); 162 + * 163 + * @since @panproto/core@0.23.0 164 + */ 165 + export function createPipeline(pp: Panproto): PipelineBuilder { 166 + return new PipelineBuilder(pp._wasm); 167 + } 168 + 169 + /** 180 170 * Generate and serialize a protolens chain between two schemas. 181 171 * Used by publish.ts to create lens records for the PDS. 182 172 */ ··· 200 190 201 191 // Re-export types that pipeline scripts need 202 192 export type { LensHandle, BuiltSchema, Panproto, CompiledMigration, MigrationSpec }; 193 + export { PipelineBuilder, ProtolensChainHandle };
+7 -7
pnpm-lock.yaml
··· 78 78 specifier: workspace:* 79 79 version: link:../../formats/tv.ionosphere 80 80 '@panproto/core': 81 - specifier: ^0.22.0 82 - version: 0.22.0 81 + specifier: ^0.25.1 82 + version: 0.25.1 83 83 an-array-of-english-words: 84 84 specifier: ^2.0.0 85 85 version: 2.0.0 ··· 124 124 specifier: ^3.1.3 125 125 version: 3.1.3 126 126 '@panproto/core': 127 - specifier: ^0.22.0 128 - version: 0.22.0 127 + specifier: ^0.25.1 128 + version: 0.25.1 129 129 relational-text: 130 130 specifier: ^0.1.1 131 131 version: 0.1.1 ··· 597 597 resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 598 598 engines: {node: '>= 8'} 599 599 600 - '@panproto/core@0.22.0': 601 - resolution: {integrity: sha512-oT3WM0RJaDDWis6nh6Ar4OmkXnz2c0v0LvpZ4Mwo4rtmGAY0ThRUfyc2X7MecFb70UuhRhVqcF43o8uvWPGSSg==} 600 + '@panproto/core@0.25.1': 601 + resolution: {integrity: sha512-zLZmChzFveREjpKeo+B5PqtVoUp+vYNbQ2aWJXol8bRG6r4gmD47NK+lq54ZjzjLHthkokh9dMhEvKPaxmWMKw==} 602 602 engines: {node: '>=20'} 603 603 604 604 '@rollup/rollup-android-arm-eabi@4.60.1': ··· 1983 1983 '@nodelib/fs.scandir': 2.1.5 1984 1984 fastq: 1.20.1 1985 1985 1986 - '@panproto/core@0.22.0': 1986 + '@panproto/core@0.25.1': 1987 1987 dependencies: 1988 1988 '@msgpack/msgpack': 3.1.3 1989 1989