[DEPRECATED] Go implementation of plcbundle

op server endpoint

Changed files
+160 -15
cmd
plcbundle
commands
server
+1 -15
cmd/plcbundle/commands/did.go
··· 99 99 100 100 // Resolve handle to DID with timing 101 101 ctx := context.Background() 102 - did, handleResolveTime, err := mgr.ResolveHandleOrDID(ctx, input) 102 + did, _, err := mgr.ResolveHandleOrDID(ctx, input) 103 103 if err != nil { 104 104 return err 105 - } 106 - 107 - // Show what we resolved to (if it was a handle) 108 - if input != did && !showJSON { 109 - fmt.Fprintf(os.Stderr, "Resolved handle '%s' → %s (in %s)\n\n", 110 - input, did, handleResolveTime) 111 105 } 112 106 113 107 stats := mgr.GetDIDIndexStats() ··· 1283 1277 } 1284 1278 } 1285 1279 1286 - fmt.Printf("═══════════════════════════════════════════════════════════════\n") 1287 1280 fmt.Printf("✓ Lookup complete in %s\n", totalElapsed) 1288 - if stats["exists"].(bool) { 1289 - fmt.Printf(" Method: DID index (fast)\n") 1290 - } else { 1291 - fmt.Printf(" Method: Full scan (slow)\n") 1292 - } 1293 - fmt.Printf("═══════════════════════════════════════════════════════════════\n") 1294 - 1295 1281 return nil 1296 1282 } 1297 1283
+158
server/handlers.go
··· 183 183 sb.WriteString(" GET /bundle/:number Bundle metadata (JSON)\n") 184 184 sb.WriteString(" GET /data/:number Raw bundle (zstd compressed)\n") 185 185 sb.WriteString(" GET /jsonl/:number Decompressed JSONL stream\n") 186 + sb.WriteString(" GET /op/:pointer Get single operation\n") 186 187 sb.WriteString(" GET /status Server status\n") 187 188 sb.WriteString(" GET /mempool Mempool operations (JSONL)\n") 188 189 ··· 232 233 sb.WriteString(fmt.Sprintf(" curl %s/bundle/1\n", baseURL)) 233 234 sb.WriteString(fmt.Sprintf(" curl %s/data/42 -o 000042.jsonl.zst\n", baseURL)) 234 235 sb.WriteString(fmt.Sprintf(" curl %s/jsonl/1\n", baseURL)) 236 + sb.WriteString(fmt.Sprintf(" curl %s/op/0\n", baseURL)) 235 237 236 238 if s.config.EnableWebSocket { 237 239 sb.WriteString(fmt.Sprintf(" websocat %s/ws\n", wsURL)) ··· 601 603 sendJSON(w, 200, auditLog) 602 604 } 603 605 } 606 + 607 + // handleOperation gets a single operation with detailed timing headers 608 + func (s *Server) handleOperation() http.HandlerFunc { 609 + return func(w http.ResponseWriter, r *http.Request) { 610 + pointer := r.PathValue("pointer") 611 + 612 + // Parse pointer format: "bundle:position" or global position 613 + bundleNum, position, err := parseOperationPointer(pointer) 614 + if err != nil { 615 + sendJSON(w, 400, map[string]string{"error": err.Error()}) 616 + return 617 + } 618 + 619 + // Validate position range 620 + if position < 0 || position >= types.BUNDLE_SIZE { 621 + sendJSON(w, 400, map[string]string{ 622 + "error": fmt.Sprintf("Position must be 0-%d", types.BUNDLE_SIZE-1), 623 + }) 624 + return 625 + } 626 + 627 + // Time the entire request 628 + totalStart := time.Now() 629 + 630 + // Time the operation load 631 + loadStart := time.Now() 632 + op, err := s.manager.LoadOperation(r.Context(), bundleNum, position) 633 + loadDuration := time.Since(loadStart) 634 + 635 + if err != nil { 636 + if strings.Contains(err.Error(), "not in index") || 637 + strings.Contains(err.Error(), "not found") { 638 + sendJSON(w, 404, map[string]string{"error": "Operation not found"}) 639 + } else { 640 + sendJSON(w, 500, map[string]string{"error": err.Error()}) 641 + } 642 + return 643 + } 644 + 645 + totalDuration := time.Since(totalStart) 646 + 647 + // Calculate global position 648 + globalPos := (bundleNum * types.BUNDLE_SIZE) + position 649 + 650 + // Calculate operation age 651 + opAge := time.Since(op.CreatedAt) 652 + 653 + // Set response headers with useful metadata 654 + setOperationHeaders(w, op, bundleNum, position, globalPos, loadDuration, totalDuration, opAge) 655 + 656 + // Send raw JSON if available (faster, preserves exact format) 657 + if len(op.RawJSON) > 0 { 658 + w.Header().Set("Content-Type", "application/json") 659 + w.Write(op.RawJSON) 660 + } else { 661 + sendJSON(w, 200, op) 662 + } 663 + } 664 + } 665 + 666 + // parseOperationPointer parses pointer in format "bundle:position" or global position 667 + func parseOperationPointer(pointer string) (bundleNum, position int, err error) { 668 + // Check if it's the "bundle:position" format 669 + if strings.Contains(pointer, ":") { 670 + parts := strings.Split(pointer, ":") 671 + if len(parts) != 2 { 672 + return 0, 0, fmt.Errorf("invalid pointer format: use 'bundle:position' or global position") 673 + } 674 + 675 + bundleNum, err = strconv.Atoi(parts[0]) 676 + if err != nil { 677 + return 0, 0, fmt.Errorf("invalid bundle number: %w", err) 678 + } 679 + 680 + position, err = strconv.Atoi(parts[1]) 681 + if err != nil { 682 + return 0, 0, fmt.Errorf("invalid position: %w", err) 683 + } 684 + 685 + if bundleNum < 1 { 686 + return 0, 0, fmt.Errorf("bundle number must be >= 1") 687 + } 688 + 689 + return bundleNum, position, nil 690 + } 691 + 692 + // Parse as global position 693 + globalPos, err := strconv.Atoi(pointer) 694 + if err != nil { 695 + return 0, 0, fmt.Errorf("invalid position: must be number or 'bundle:position' format") 696 + } 697 + 698 + if globalPos < 0 { 699 + return 0, 0, fmt.Errorf("global position must be >= 0") 700 + } 701 + 702 + // Handle small numbers as shorthand for bundle 1 703 + if globalPos < types.BUNDLE_SIZE { 704 + return 1, globalPos, nil 705 + } 706 + 707 + // Convert global position to bundle + position 708 + bundleNum = globalPos / types.BUNDLE_SIZE 709 + position = globalPos % types.BUNDLE_SIZE 710 + 711 + // Minimum bundle number is 1 712 + if bundleNum < 1 { 713 + bundleNum = 1 714 + } 715 + 716 + return bundleNum, position, nil 717 + } 718 + 719 + // setOperationHeaders sets useful response headers 720 + func setOperationHeaders( 721 + w http.ResponseWriter, 722 + op *plcclient.PLCOperation, 723 + bundleNum, position, globalPos int, 724 + loadDuration, totalDuration, opAge time.Duration, 725 + ) { 726 + // === Location Information === 727 + w.Header().Set("X-Bundle-Number", fmt.Sprintf("%d", bundleNum)) 728 + w.Header().Set("X-Position", fmt.Sprintf("%d", position)) 729 + w.Header().Set("X-Global-Position", fmt.Sprintf("%d", globalPos)) 730 + w.Header().Set("X-Pointer", fmt.Sprintf("%d:%d", bundleNum, position)) 731 + 732 + // === Operation Metadata === 733 + w.Header().Set("X-Operation-DID", op.DID) 734 + w.Header().Set("X-Operation-CID", op.CID) 735 + w.Header().Set("X-Operation-Created", op.CreatedAt.Format(time.RFC3339)) 736 + w.Header().Set("X-Operation-Age-Seconds", fmt.Sprintf("%d", int(opAge.Seconds()))) 737 + 738 + // Nullification status 739 + if op.IsNullified() { 740 + w.Header().Set("X-Operation-Nullified", "true") 741 + if nullCID := op.GetNullifyingCID(); nullCID != "" { 742 + w.Header().Set("X-Operation-Nullified-By", nullCID) 743 + } 744 + } else { 745 + w.Header().Set("X-Operation-Nullified", "false") 746 + } 747 + 748 + // === Size Information === 749 + if len(op.RawJSON) > 0 { 750 + w.Header().Set("X-Operation-Size", fmt.Sprintf("%d", len(op.RawJSON))) 751 + } 752 + 753 + // === Performance Metrics (in milliseconds with precision) === 754 + w.Header().Set("X-Load-Time-Ms", fmt.Sprintf("%.3f", float64(loadDuration.Microseconds())/1000.0)) 755 + w.Header().Set("X-Total-Time-Ms", fmt.Sprintf("%.3f", float64(totalDuration.Microseconds())/1000.0)) 756 + 757 + // === Caching Hints === 758 + // Set cache control (operations are immutable once bundled) 759 + w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") 760 + w.Header().Set("ETag", op.CID) // CID is perfect for ETag 761 + }
+1
server/server.go
··· 69 69 mux.HandleFunc("GET /bundle/{number}", s.handleBundle()) 70 70 mux.HandleFunc("GET /data/{number}", s.handleBundleData()) 71 71 mux.HandleFunc("GET /jsonl/{number}", s.handleBundleJSONL()) 72 + mux.HandleFunc("GET /op/{pointer}", s.handleOperation()) 72 73 mux.HandleFunc("GET /status", s.handleStatus()) 73 74 mux.HandleFunc("GET /debug/memory", s.handleDebugMemory()) 74 75