feat: TypeScript views generated from JSON lens files via panproto The codegen pipeline now reads packages/lenses/*.lens.json as the single source of truth for DB projection transforms. Each lens file contains both structural transforms (remove_field, rename_field, add_field) and DDL metadata (table section). The pipeline: 1. Load human-readable lens JSON files 2. Convert steps to panproto combinators → ProtolensChain 3. Instantiate chain against Lexicon schema → target schema 4. Walk target schema vertices to emit TypeScript interfaces RecordConfig is kept as a fallback but no longer drives TypeScript generation. All 15 view types are now generated from lens files.
Author: Aaron Steven White
Commit
5320e288d1721f915ec574f74dc6566186d43f27Parent: 9e088dce24
Structural diff unavailable
These commits were pushed via plain git push, so no pre-parsed
schemas are available. Install git-remote-cospan and re-push via panproto:// to
see scope-level changes, breaking change detection, and semantic diffs.
brew install panproto/tap/git-remote-cospan18 files changed +493 -259
@@ -1,137 +1,16 @@
1-// Auto-generated by cospan-codegen via panproto protolens combinators. 2-// Source: Lexicon schemas transformed through DB projection lens. 1+// Auto-generated by cospan-codegen via panproto protolens (from JSON lens files). 2+// Source: packages/lenses/*.lens.json 33 // Do not edit manually. 44 5-// dev.cospan.node (via panproto combinators) 6-export interface NodeView { 7- did: string; 8- rkey: string; 9- createdAt: string; 10- publicEndpoint: string | null; 11- indexedAt: string; 12-} 13- 14-export function normalizeNodeView(raw: Partial<NodeView>): NodeView { 15- return { 16- did: raw.did ?? '', 17- rkey: raw.rkey ?? '', 18- createdAt: raw.createdAt ?? '', 19- publicEndpoint: raw.publicEndpoint ?? null, 20- indexedAt: raw.indexedAt ?? '', 21- }; 22-} 23- 24-// dev.cospan.actor.profile (via panproto combinators) 25-export interface ActorProfileView { 26- did: string; 27- bluesky: string; 28- description: string | null; 29- displayName: string | null; 30- avatarCid: string; 31- indexedAt: string; 32-} 33- 34-export function normalizeActorProfileView(raw: Partial<ActorProfileView>): ActorProfileView { 35- return { 36- did: raw.did ?? '', 37- bluesky: raw.bluesky ?? '', 38- description: raw.description ?? null, 39- displayName: raw.displayName ?? null, 40- avatarCid: raw.avatarCid ?? '', 41- indexedAt: raw.indexedAt ?? '', 42- }; 43-} 44- 45-// dev.cospan.repo (via panproto combinators) 46-export interface RepoView { 47- did: string; 48- rkey: string; 49- createdAt: string; 50- description: string | null; 51- name: string; 52- protocol: string; 53- sourceRepo: string | null; 54- defaultBranch: string; 55- forkCount: number; 56- nodeDid: string; 57- nodeUrl: string; 58- openIssueCount: number; 59- openMrCount: number; 60- source: string; 61- sourceUri: string; 62- starCount: number; 63- visibility: string; 64- indexedAt: string; 65-} 66- 67-export function normalizeRepoView(raw: Partial<RepoView>): RepoView { 68- return { 69- did: raw.did ?? '', 70- rkey: raw.rkey ?? '', 71- createdAt: raw.createdAt ?? '', 72- description: raw.description ?? null, 73- name: raw.name ?? '', 74- protocol: raw.protocol ?? '', 75- sourceRepo: raw.sourceRepo ?? null, 76- defaultBranch: raw.defaultBranch ?? '', 77- forkCount: raw.forkCount ?? 0, 78- nodeDid: raw.nodeDid ?? '', 79- nodeUrl: raw.nodeUrl ?? '', 80- openIssueCount: raw.openIssueCount ?? 0, 81- openMrCount: raw.openMrCount ?? 0, 82- source: raw.source ?? '', 83- sourceUri: raw.sourceUri ?? '', 84- starCount: raw.starCount ?? 0, 85- visibility: raw.visibility ?? 'public', 86- indexedAt: raw.indexedAt ?? '', 87- }; 88-} 89- 90-// dev.cospan.vcs.refUpdate (via panproto combinators) 91-export interface RefUpdateView { 92- rkey: string; 93- committerDid: string; 94- createdAt: string; 95- lensId: string | null; 96- migrationId: string | null; 97- newTarget: string; 98- oldTarget: string | null; 99- protocol: string; 100- ref: string; 101- breakingChangeCount: number; 102- commitCount: number; 103- lensQuality: number; 104- repoDid: string; 105- repoName: string; 106- indexedAt: string; 107-} 108- 109-export function normalizeRefUpdateView(raw: Partial<RefUpdateView>): RefUpdateView { 110- return { 111- rkey: raw.rkey ?? '', 112- committerDid: raw.committerDid ?? '', 113- createdAt: raw.createdAt ?? '', 114- lensId: raw.lensId ?? null, 115- migrationId: raw.migrationId ?? null, 116- newTarget: raw.newTarget ?? '', 117- oldTarget: raw.oldTarget ?? null, 118- protocol: raw.protocol ?? '', 119- ref: raw.ref ?? '', 120- breakingChangeCount: raw.breakingChangeCount ?? 0, 121- commitCount: raw.commitCount ?? 0, 122- lensQuality: raw.lensQuality ?? 0, 123- repoDid: raw.repoDid ?? '', 124- repoName: raw.repoName ?? '', 125- indexedAt: raw.indexedAt ?? '', 126- }; 127-} 128- 129-// dev.cospan.repo.issue (via panproto combinators) 130-export interface IssueView { 5+// dev.cospan.repo.pull (via lens file) 6+export interface PullView { 1317 did: string; 1328 rkey: string; 1339 body: string | null; 13410 createdAt: string; 11+ sourceRef: string; 12+ sourceRepo: string | null; 13+ targetRef: string; 13514 title: string; 13615 commentCount: number; 13716 repoDid: string;
@@ -140,12 +19,15 @@ export interface IssueView {
14019 indexedAt: string; 14120 } 14221 143-export function normalizeIssueView(raw: Partial<IssueView>): IssueView { 22+export function normalizePullView(raw: Partial<PullView>): PullView { 14423 return { 14524 did: raw.did ?? '', 14625 rkey: raw.rkey ?? '', 14726 body: raw.body ?? null, 14827 createdAt: raw.createdAt ?? '', 28+ sourceRef: raw.sourceRef ?? '', 29+ sourceRepo: raw.sourceRepo ?? null, 30+ targetRef: raw.targetRef ?? '', 14931 title: raw.title ?? '', 15032 commentCount: raw.commentCount ?? 0, 15133 repoDid: raw.repoDid ?? '',
@@ -155,30 +37,12 @@ export function normalizeIssueView(raw: Partial<IssueView>): IssueView {
15537 }; 15638 } 15739 158-// dev.cospan.repo.issue.comment (via panproto combinators) 159-export interface IssueCommentView { 160- did: string; 161- rkey: string; 162- body: string; 163- createdAt: string; 164- indexedAt: string; 165-} 166- 167-export function normalizeIssueCommentView(raw: Partial<IssueCommentView>): IssueCommentView { 168- return { 169- did: raw.did ?? '', 170- rkey: raw.rkey ?? '', 171- body: raw.body ?? '', 172- createdAt: raw.createdAt ?? '', 173- indexedAt: raw.indexedAt ?? '', 174- }; 175-} 176- 177-// dev.cospan.repo.issue.state (via panproto combinators) 40+// dev.cospan.repo.issue.state (via lens file) 17841 export interface IssueStateView { 17942 did: string; 18043 rkey: string; 18144 createdAt: string; 45+ issueUri: string | null; 18246 reason: string | null; 18347 state: string; 18448 indexedAt: string;
@@ -189,21 +53,19 @@ export function normalizeIssueStateView(raw: Partial<IssueStateView>): IssueStat
18953 did: raw.did ?? '', 19054 rkey: raw.rkey ?? '', 19155 createdAt: raw.createdAt ?? '', 56+ issueUri: raw.issueUri ?? null, 19257 reason: raw.reason ?? null, 19358 state: raw.state ?? '', 19459 indexedAt: raw.indexedAt ?? '', 19560 }; 19661 } 19762 198-// dev.cospan.repo.pull (via panproto combinators) 199-export interface PullView { 63+// dev.cospan.repo.issue (via lens file) 64+export interface IssueView { 20065 did: string; 20166 rkey: string; 20267 body: string | null; 20368 createdAt: string; 204- sourceRef: string; 205- sourceRepo: string | null; 206- targetRef: string; 20769 title: string; 20870 commentCount: number; 20971 repoDid: string;
@@ -212,15 +74,12 @@ export interface PullView {
21274 indexedAt: string; 21375 } 21476 215-export function normalizePullView(raw: Partial<PullView>): PullView { 77+export function normalizeIssueView(raw: Partial<IssueView>): IssueView { 21678 return { 21779 did: raw.did ?? '', 21880 rkey: raw.rkey ?? '', 21981 body: raw.body ?? null, 22082 createdAt: raw.createdAt ?? '', 221- sourceRef: raw.sourceRef ?? '', 222- sourceRepo: raw.sourceRepo ?? null, 223- targetRef: raw.targetRef ?? '', 22483 title: raw.title ?? '', 22584 commentCount: raw.commentCount ?? 0, 22685 repoDid: raw.repoDid ?? '',
@@ -230,108 +89,96 @@ export function normalizePullView(raw: Partial<PullView>): PullView {
23089 }; 23190 } 23291 233-// dev.cospan.repo.pull.comment (via panproto combinators) 234-export interface PullCommentView { 92+// dev.cospan.repo.issue.comment (via lens file) 93+export interface IssueCommentView { 23594 did: string; 23695 rkey: string; 23796 body: string; 23897 createdAt: string; 239- reviewDecision: string | null; 98+ issueUri: string | null; 24099 indexedAt: string; 241100 } 242101 243-export function normalizePullCommentView(raw: Partial<PullCommentView>): PullCommentView { 102+export function normalizeIssueCommentView(raw: Partial<IssueCommentView>): IssueCommentView { 244103 return { 245104 did: raw.did ?? '', 246105 rkey: raw.rkey ?? '', 247106 body: raw.body ?? '', 248107 createdAt: raw.createdAt ?? '', 249- reviewDecision: raw.reviewDecision ?? null, 250- indexedAt: raw.indexedAt ?? '', 251- }; 252-} 253- 254-// dev.cospan.repo.pull.state (via panproto combinators) 255-export interface PullStateView { 256- did: string; 257- rkey: string; 258- createdAt: string; 259- mergeCommitId: string | null; 260- state: string; 261- indexedAt: string; 262-} 263- 264-export function normalizePullStateView(raw: Partial<PullStateView>): PullStateView { 265- return { 266- did: raw.did ?? '', 267- rkey: raw.rkey ?? '', 268- createdAt: raw.createdAt ?? '', 269- mergeCommitId: raw.mergeCommitId ?? null, 270- state: raw.state ?? '', 108+ issueUri: raw.issueUri ?? null, 271109 indexedAt: raw.indexedAt ?? '', 272110 }; 273111 } 274112 275-// dev.cospan.feed.star (via panproto combinators) 276-export interface StarView { 277- did: string; 278- rkey: string; 279- createdAt: string; 280- subject: string; 281- indexedAt: string; 282-} 283- 284-export function normalizeStarView(raw: Partial<StarView>): StarView { 285- return { 286- did: raw.did ?? '', 287- rkey: raw.rkey ?? '', 288- createdAt: raw.createdAt ?? '', 289- subject: raw.subject ?? '', 290- indexedAt: raw.indexedAt ?? '', 291- }; 292-} 293- 294-// dev.cospan.feed.reaction (via panproto combinators) 295-export interface ReactionView { 113+// dev.cospan.org (via lens file) 114+export interface OrgView { 296115 did: string; 297116 rkey: string; 298117 createdAt: string; 299- emoji: string; 300- subject: string; 118+ description: string | null; 119+ name: string; 120+ avatarCid: string; 301121 indexedAt: string; 302122 } 303123 304-export function normalizeReactionView(raw: Partial<ReactionView>): ReactionView { 124+export function normalizeOrgView(raw: Partial<OrgView>): OrgView { 305125 return { 306126 did: raw.did ?? '', 307127 rkey: raw.rkey ?? '', 308128 createdAt: raw.createdAt ?? '', 309- emoji: raw.emoji ?? '', 310- subject: raw.subject ?? '', 129+ description: raw.description ?? null, 130+ name: raw.name ?? '', 131+ avatarCid: raw.avatarCid ?? '', 311132 indexedAt: raw.indexedAt ?? '', 312133 }; 313134 } 314135 315-// dev.cospan.graph.follow (via panproto combinators) 316-export interface FollowView { 136+// dev.cospan.repo (via lens file) 137+export interface RepoView { 317138 did: string; 318139 rkey: string; 319140 createdAt: string; 320- subject: string; 141+ defaultBranch: string | null; 142+ description: string | null; 143+ name: string; 144+ protocol: string; 145+ sourceRepo: string | null; 146+ visibility: string | null; 147+ forkCount: number; 148+ nodeDid: string; 149+ nodeUrl: string; 150+ openIssueCount: number; 151+ openMrCount: number; 152+ source: string; 153+ sourceUri: string; 154+ starCount: number; 321155 indexedAt: string; 322156 } 323157 324-export function normalizeFollowView(raw: Partial<FollowView>): FollowView { 158+export function normalizeRepoView(raw: Partial<RepoView>): RepoView { 325159 return { 326160 did: raw.did ?? '', 327161 rkey: raw.rkey ?? '', 328162 createdAt: raw.createdAt ?? '', 329- subject: raw.subject ?? '', 163+ defaultBranch: raw.defaultBranch ?? null, 164+ description: raw.description ?? null, 165+ name: raw.name ?? '', 166+ protocol: raw.protocol ?? '', 167+ sourceRepo: raw.sourceRepo ?? null, 168+ visibility: raw.visibility ?? null, 169+ forkCount: raw.forkCount ?? 0, 170+ nodeDid: raw.nodeDid ?? '', 171+ nodeUrl: raw.nodeUrl ?? '', 172+ openIssueCount: raw.openIssueCount ?? 0, 173+ openMrCount: raw.openMrCount ?? 0, 174+ source: raw.source ?? '', 175+ sourceUri: raw.sourceUri ?? '', 176+ starCount: raw.starCount ?? 0, 330177 indexedAt: raw.indexedAt ?? '', 331178 }; 332179 } 333180 334-// dev.cospan.label.definition (via panproto combinators) 181+// dev.cospan.label.definition (via lens file) 335182 export interface LabelView { 336183 did: string; 337184 rkey: string;
@@ -358,53 +205,56 @@ export function normalizeLabelView(raw: Partial<LabelView>): LabelView {
358205 }; 359206 } 360207 361-// dev.cospan.org (via panproto combinators) 362-export interface OrgView { 208+// dev.cospan.org.member (via lens file) 209+export interface OrgMemberView { 363210 did: string; 364211 rkey: string; 365212 createdAt: string; 366- description: string | null; 367- name: string; 368- avatarCid: string; 213+ memberDid: string | null; 214+ orgUri: string | null; 215+ role: string; 369216 indexedAt: string; 370217 } 371218 372-export function normalizeOrgView(raw: Partial<OrgView>): OrgView { 219+export function normalizeOrgMemberView(raw: Partial<OrgMemberView>): OrgMemberView { 373220 return { 374221 did: raw.did ?? '', 375222 rkey: raw.rkey ?? '', 376223 createdAt: raw.createdAt ?? '', 377- description: raw.description ?? null, 378- name: raw.name ?? '', 379- avatarCid: raw.avatarCid ?? '', 224+ memberDid: raw.memberDid ?? null, 225+ orgUri: raw.orgUri ?? null, 226+ role: raw.role ?? '', 380227 indexedAt: raw.indexedAt ?? '', 381228 }; 382229 } 383230 384-// dev.cospan.org.member (via panproto combinators) 385-export interface OrgMemberView { 231+// dev.cospan.actor.profile (via lens file) 232+export interface ActorProfileView { 386233 did: string; 387- rkey: string; 388- createdAt: string; 389- role: string; 234+ bluesky: string; 235+ description: string | null; 236+ displayName: string | null; 237+ avatarCid: string; 390238 indexedAt: string; 391239 } 392240 393-export function normalizeOrgMemberView(raw: Partial<OrgMemberView>): OrgMemberView { 241+export function normalizeActorProfileView(raw: Partial<ActorProfileView>): ActorProfileView { 394242 return { 395243 did: raw.did ?? '', 396- rkey: raw.rkey ?? '', 397- createdAt: raw.createdAt ?? '', 398- role: raw.role ?? '', 244+ bluesky: raw.bluesky ?? '', 245+ description: raw.description ?? null, 246+ displayName: raw.displayName ?? null, 247+ avatarCid: raw.avatarCid ?? '', 399248 indexedAt: raw.indexedAt ?? '', 400249 }; 401250 } 402251 403-// dev.cospan.repo.collaborator (via panproto combinators) 252+// dev.cospan.repo.collaborator (via lens file) 404253 export interface CollaboratorView { 405254 did: string; 406255 rkey: string; 407256 createdAt: string; 257+ did: string; 408258 role: string; 409259 repoDid: string; 410260 repoName: string;
@@ -416,6 +266,7 @@ export function normalizeCollaboratorView(raw: Partial<CollaboratorView>): Colla
416266 did: raw.did ?? '', 417267 rkey: raw.rkey ?? '', 418268 createdAt: raw.createdAt ?? '', 269+ did: raw.did ?? '', 419270 role: raw.role ?? '', 420271 repoDid: raw.repoDid ?? '', 421272 repoName: raw.repoName ?? '',
@@ -423,7 +274,7 @@ export function normalizeCollaboratorView(raw: Partial<CollaboratorView>): Colla
423274 }; 424275 } 425276 426-// dev.cospan.repo.dependency (via panproto combinators) 277+// dev.cospan.repo.dependency (via lens file) 427278 export interface DependencyView { 428279 did: string; 429280 rkey: string;
@@ -456,7 +307,92 @@ export function normalizeDependencyView(raw: Partial<DependencyView>): Dependenc
456307 }; 457308 } 458309 459-// dev.cospan.pipeline (via panproto combinators) 310+// dev.cospan.vcs.refUpdate (via lens file) 311+export interface RefUpdateView { 312+ rkey: string; 313+ commitCount: number | null; 314+ committerDid: string; 315+ createdAt: string; 316+ lensId: string | null; 317+ lensQuality: Record<string, unknown> | null; 318+ migrationId: string | null; 319+ newTarget: string; 320+ oldTarget: string | null; 321+ protocol: string; 322+ ref: string; 323+ breakingChangeCount: number; 324+ repoDid: string; 325+ repoName: string; 326+ indexedAt: string; 327+} 328+ 329+export function normalizeRefUpdateView(raw: Partial<RefUpdateView>): RefUpdateView { 330+ return { 331+ rkey: raw.rkey ?? '', 332+ commitCount: raw.commitCount ?? null, 333+ committerDid: raw.committerDid ?? '', 334+ createdAt: raw.createdAt ?? '', 335+ lensId: raw.lensId ?? null, 336+ lensQuality: raw.lensQuality ?? null, 337+ migrationId: raw.migrationId ?? null, 338+ newTarget: raw.newTarget ?? '', 339+ oldTarget: raw.oldTarget ?? null, 340+ protocol: raw.protocol ?? '', 341+ ref: raw.ref ?? '', 342+ breakingChangeCount: raw.breakingChangeCount ?? 0, 343+ repoDid: raw.repoDid ?? '', 344+ repoName: raw.repoName ?? '', 345+ indexedAt: raw.indexedAt ?? '', 346+ }; 347+} 348+ 349+// dev.cospan.repo.pull.state (via lens file) 350+export interface PullStateView { 351+ did: string; 352+ rkey: string; 353+ createdAt: string; 354+ mergeCommitId: string | null; 355+ pullUri: string | null; 356+ state: string; 357+ indexedAt: string; 358+} 359+ 360+export function normalizePullStateView(raw: Partial<PullStateView>): PullStateView { 361+ return { 362+ did: raw.did ?? '', 363+ rkey: raw.rkey ?? '', 364+ createdAt: raw.createdAt ?? '', 365+ mergeCommitId: raw.mergeCommitId ?? null, 366+ pullUri: raw.pullUri ?? null, 367+ state: raw.state ?? '', 368+ indexedAt: raw.indexedAt ?? '', 369+ }; 370+} 371+ 372+// dev.cospan.repo.pull.comment (via lens file) 373+export interface PullCommentView { 374+ did: string; 375+ rkey: string; 376+ body: string; 377+ createdAt: string; 378+ pullUri: string | null; 379+ reviewDecision: string | null; 380+ indexedAt: string; 381+} 382+ 383+export function normalizePullCommentView(raw: Partial<PullCommentView>): PullCommentView { 384+ return { 385+ did: raw.did ?? '', 386+ rkey: raw.rkey ?? '', 387+ body: raw.body ?? '', 388+ createdAt: raw.createdAt ?? '', 389+ pullUri: raw.pullUri ?? null, 390+ reviewDecision: raw.reviewDecision ?? null, 391+ indexedAt: raw.indexedAt ?? '', 392+ }; 393+} 394+ 395+// dev.cospan.pipeline (via lens file) 460396 export interface PipelineView { 461397 did: string; 462398 rkey: string;
@@ -465,6 +401,7 @@ export interface PipelineView {
465401 createdAt: string; 466402 ref: string | null; 467403 status: string; 404+ workflows: unknown[] | null; 468405 breakingChangeCheck: string; 469406 equationVerification: string; 470407 gatTypeCheck: string;
@@ -483,6 +420,7 @@ export function normalizePipelineView(raw: Partial<PipelineView>): PipelineView
483420 createdAt: raw.createdAt ?? '', 484421 ref: raw.ref ?? null, 485422 status: raw.status ?? 'pending', 423+ workflows: raw.workflows ?? null, 486424 breakingChangeCheck: raw.breakingChangeCheck ?? '', 487425 equationVerification: raw.equationVerification ?? '', 488426 gatTypeCheck: raw.gatTypeCheck ?? '',
@@ -238,13 +238,192 @@ fn emit_view_from_target(target: &Schema, nsid: &str, config: &RecordConfig) ->
238238 out 239239 } 240240 241+/// Emit TypeScript from target schema with explicit config (lens-file path). 242+fn emit_view_from_target_with( 243+ target: &Schema, 244+ nsid: &str, 245+ view_name: &str, 246+ include_did: bool, 247+ include_rkey: bool, 248+ column_defaults: &std::collections::HashMap<String, String>, 249+) -> String { 250+ let body_id = find_record_body(target, nsid); 251+ let mut out = String::new(); 252+ out.push_str(&format!("// {nsid} (via lens file)\n")); 253+ out.push_str(&format!("export interface {view_name} {{\n")); 254+ 255+ if include_did { out.push_str(" did: string;\n"); } 256+ if include_rkey { out.push_str(" rkey: string;\n"); } 257+ 258+ let props = children_by_edge(target, &body_id, "prop"); 259+ for (edge, prop_vertex) in &props { 260+ let field_name = edge.name.as_ref().map(|n| n.as_str()).unwrap_or("unknown"); 261+ let ts_type = vertex_kind_to_ts(&prop_vertex.kind); 262+ let is_required = is_field_required(target, &body_id, field_name); 263+ if is_required { 264+ out.push_str(&format!(" {field_name}: {ts_type};\n")); 265+ } else { 266+ out.push_str(&format!(" {field_name}: {ts_type} | null;\n")); 267+ } 268+ } 269+ 270+ let body_prefix = format!("{body_id}."); 271+ let prop_targets: std::collections::HashSet<String> = 272+ props.iter().map(|(_, v)| v.id.to_string()).collect(); 273+ let mut extra_vertices: Vec<_> = target 274+ .vertices 275+ .iter() 276+ .filter(|(id, _)| { 277+ let id_str = id.to_string(); 278+ id_str.starts_with(&body_prefix) 279+ && !id_str[body_prefix.len()..].contains('.') 280+ && !id_str[body_prefix.len()..].contains(':') 281+ && !prop_targets.contains(&id_str) 282+ }) 283+ .collect(); 284+ extra_vertices.sort_by_key(|(id, _)| id.to_string()); 285+ 286+ for (id, v) in &extra_vertices { 287+ let field_name = id.as_str().strip_prefix(&body_prefix).unwrap_or(id.as_str()); 288+ let ts_type = vertex_kind_to_ts(&v.kind); 289+ out.push_str(&format!(" {field_name}: {ts_type};\n")); 290+ } 291+ 292+ out.push_str(" indexedAt: string;\n"); 293+ out.push_str("}\n\n"); 294+ 295+ // Normalization 296+ out.push_str(&format!("export function normalize{view_name}(raw: Partial<{view_name}>): {view_name} {{\n")); 297+ out.push_str(" return {\n"); 298+ if include_did { out.push_str(" did: raw.did ?? '',\n"); } 299+ if include_rkey { out.push_str(" rkey: raw.rkey ?? '',\n"); } 300+ for (edge, prop_vertex) in &props { 301+ let field_name = edge.name.as_ref().map(|n| n.as_str()).unwrap_or("unknown"); 302+ let is_required = is_field_required(target, &body_id, field_name); 303+ let default = if !is_required { "null" } else { 304+ default_for_kind_with_map(&prop_vertex.kind, field_name, column_defaults) 305+ }; 306+ out.push_str(&format!(" {field_name}: raw.{field_name} ?? {default},\n")); 307+ } 308+ for (id, v) in &extra_vertices { 309+ let field_name = id.as_str().strip_prefix(&body_prefix).unwrap_or(id.as_str()); 310+ let default = default_for_kind_with_map(&v.kind, field_name, column_defaults); 311+ out.push_str(&format!(" {field_name}: raw.{field_name} ?? {default},\n")); 312+ } 313+ out.push_str(" indexedAt: raw.indexedAt ?? '',\n"); 314+ out.push_str(" };\n"); 315+ out.push_str("}\n\n"); 316+ 317+ out 318+} 319+ 320+fn default_for_kind_with_map(kind: &Name, field_name: &str, defaults: &std::collections::HashMap<String, String>) -> &'static str { 321+ if let Some(expr) = defaults.get(field_name) { 322+ return match expr.as_str() { 323+ "'open'" => "'open'", 324+ "'pending'" => "'pending'", 325+ "'public'" => "'public'", 326+ "'main'" => "'main'", 327+ "0" => "0", 328+ _ => "''", 329+ }; 330+ } 331+ match kind.as_str() { 332+ "string" | "cid-link" | "ref" | "token" | "bytes" => "''", 333+ "integer" | "number" | "float" => "0", 334+ "boolean" => "false", 335+ _ => "''", 336+ } 337+} 338+ 339+fn nsid_to_pascal(nsid: &str) -> String { 340+ nsid.split('.').map(|s| { 341+ let mut c = s.chars(); 342+ match c.next() { 343+ None => String::new(), 344+ Some(f) => f.to_uppercase().collect::<String>() + c.as_str(), 345+ } 346+ }).collect() 347+} 348+ 241349 fn emit_list_response(type_name: &str, wrapper_key: &str) -> String { 242350 format!( 243351 "export interface {type_name}ListResponse {{\n {wrapper_key}: {type_name}View[];\n cursor: string | null;\n}}\n\n" 244352 ) 245353 } 246354 247-/// Emit the full generated TypeScript views file. 355+/// Emit views from JSON lens files (primary path). 356+pub fn emit_all_views_from_lenses( 357+ schemas: &[(Schema, String)], 358+ lenses: &[crate::lens_config::LensFile], 359+) -> String { 360+ let mut out = String::new(); 361+ out.push_str("// Auto-generated by cospan-codegen via panproto protolens (from JSON lens files).\n"); 362+ out.push_str("// Source: packages/lenses/*.lens.json\n"); 363+ out.push_str("// Do not edit manually.\n\n"); 364+ 365+ for lens in crate::lens_config::db_projection_lenses(lenses) { 366+ if let Some((schema, _)) = schemas.iter().find(|(_, nsid)| nsid == &lens.source) { 367+ let body_id = find_record_body(schema, &lens.source); 368+ let chain = crate::lens_config::steps_to_protolens_chain(&lens.steps, &body_id); 369+ let protocol = Protocol::default(); 370+ let target = match chain.instantiate(schema, &protocol) { 371+ Ok(l) => l.tgt_schema, 372+ Err(e) => { 373+ eprintln!(" warn: lens instantiation for {}: {e:?}", lens.source); 374+ schema.clone() 375+ } 376+ }; 377+ 378+ let table = lens.table.as_ref(); 379+ let view_name = table 380+ .map(|t| { 381+ t.row_struct 382+ .strip_suffix("Row") 383+ .unwrap_or(&t.row_struct) 384+ .to_string() 385+ + "View" 386+ }) 387+ .unwrap_or_else(|| format!("{}View", nsid_to_pascal(&lens.source))); 388+ let include_did = table.map(|t| t.include_did).unwrap_or(true); 389+ let include_rkey = table.map(|t| t.include_rkey).unwrap_or(true); 390+ 391+ out.push_str(&emit_view_from_target_with( 392+ &target, 393+ &lens.source, 394+ &view_name, 395+ include_did, 396+ include_rkey, 397+ table.map(|t| &t.column_defaults).unwrap_or(&std::collections::HashMap::new()), 398+ )); 399+ } 400+ } 401+ 402+ let list_endpoints = [ 403+ ("Repo", "repos"), 404+ ("Issue", "issues"), 405+ ("IssueComment", "comments"), 406+ ("Pull", "pulls"), 407+ ("PullComment", "comments"), 408+ ("Star", "stars"), 409+ ("Follow", "follows"), 410+ ("Node", "nodes"), 411+ ("Org", "orgs"), 412+ ("OrgMember", "members"), 413+ ("Collaborator", "collaborators"), 414+ ("RefUpdate", "refUpdates"), 415+ ("Label", "labels"), 416+ ("Pipeline", "pipelines"), 417+ ("Reaction", "reactions"), 418+ ]; 419+ for (type_name, wrapper_key) in &list_endpoints { 420+ out.push_str(&emit_list_response(type_name, wrapper_key)); 421+ } 422+ 423+ out 424+} 425+ 426+/// Emit views from RecordConfig (legacy path, kept for backward compat). 248427 pub fn emit_all_views(schemas: &[(Schema, String)], configs: &[RecordConfig]) -> String { 249428 let mut out = String::new(); 250429 out.push_str("// Auto-generated by cospan-codegen via panproto protolens combinators.\n");
@@ -260,26 +260,35 @@ fn main() -> Result<()> {
260260 rmp_serde::to_vec(&db_projections)?, 261261 )?; 262262 263- // --- Generate TypeScript view types (API response shapes) --- 263+ // --- Generate TypeScript view types from lens files --- 264264 { 265- let configs = record_config::all_record_configs(); 265+ let lenses_dir = workspace_root.join("packages/lenses"); 266+ let lenses = lens_config::load_all_lenses(&lenses_dir)?; 267+ 268+ // Collect schemas for all lens source NSIDs 266269 let mut schema_pairs: Vec<(panproto_schema::Schema, String)> = Vec::new(); 267- for config in &configs { 270+ for lens in &lenses { 268271 for lexicon_path in &lexicon_files { 269272 let json_str = fs::read_to_string(lexicon_path)?; 270273 let json: serde_json::Value = serde_json::from_str(&json_str)?; 271274 let nsid = json.get("id").and_then(|v| v.as_str()).unwrap_or("unknown"); 272- if nsid == config.nsid { 275+ if nsid == lens.source && !schema_pairs.iter().any(|(_, n)| n == nsid) { 273276 let schema = atproto::parse_lexicon(&json)?; 274277 schema_pairs.push((schema, nsid.to_string())); 275278 break; 276279 } 277280 } 278281 } 279- let views_ts = emit_typescript_views::emit_all_views(&schema_pairs, &configs); 282+ 283+ // Try lens-file path first, fall back to RecordConfig 284+ let views_ts = if lenses.iter().any(|l| l.table.is_some()) { 285+ emit_typescript_views::emit_all_views_from_lenses(&schema_pairs, &lenses) 286+ } else { 287+ let configs = record_config::all_record_configs(); 288+ emit_typescript_views::emit_all_views(&schema_pairs, &configs) 289+ }; 280290 fs::write(generated_dir.join("typescript/views.ts"), &views_ts)?; 281291 282- // Copy to web app for direct import 283292 let web_gen_dir = workspace_root.join("apps/web/src/lib/generated"); 284293 fs::create_dir_all(&web_gen_dir)?; 285294 fs::write(web_gen_dir.join("views.ts"), &views_ts)?;
@@ -15,5 +15,11 @@
1515 "expr": "path_extract(avatar, ['ref', '$link'])" 1616 } 1717 } 18- ] 18+ ], 19+ "table": { 20+ "name": "actor_profiles", 21+ "row_struct": "ActorProfileRow", 22+ "conflict_keys": ["did"], 23+ "include_rkey": false 24+ } 1925 }
@@ -21,5 +21,10 @@
2121 "expr": "index(split(replace(repo, 'at://', ''), '/'), 2)" 2222 } 2323 } 24- ] 24+ ], 25+ "table": { 26+ "name": "labels", 27+ "row_struct": "LabelRow", 28+ "conflict_keys": ["did", "rkey"] 29+ } 2530 }