Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ could then read the DSN file back to Circuit JSON.
This module has the zod definitions and conversion functions for using circuit json,
and is the primary way that Circuit JSON is defined and maintained.

> **Zod v4 migration:** `circuit-json` schemas now require `zod@^4`. Because
> Zod v3 and v4 schemas cannot be composed, downstream packages that pass these
> schemas between package boundaries must upgrade together. Consumers that inspect
> raw Zod errors should migrate to Zod v4's error APIs, and should account for Zod
> v4 rejecting non-finite numbers and unsafe integers where earlier versions
> accepted them.

https://github.com/user-attachments/assets/2f28b7ba-689e-4d80-85b2-5bdef84b41f8

> To quickly generate Circuit JSON with tscircuit, use [tscircuit/eval](https://github.com/tscircuit/eval)
Expand Down
5 changes: 3 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
"ts-node": "^10.9.2",
"tsup": "^8.3.0",
"typescript": "^5.7.2",
"zod": "3"
"zod": "^4.0.0"
},
"peerDependencies": {
"format-si-unit": "*"
"format-si-unit": "*",
"zod": "^4.0.0"
},
"description": "Definitions for the tscircuit intermediary JSON format",
"files": [
Expand Down
74 changes: 35 additions & 39 deletions scripts/check-snake-case.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { z } from "zod"
import { z } from "zod"
import { any_circuit_element } from "../src/any_circuit_element"

const seen = new Set<z.ZodTypeAny>()
const seen = new Set<z.core.$ZodType>()
const errors: string[] = []

const SNAKE_CASE_IGNORED_PATH_FRAGMENTS = [
Expand All @@ -24,52 +24,48 @@ function checkSnakeCase(name: string, path: string) {
}
}

function traverse(schema: z.ZodTypeAny, path: string) {
function traverse(schema: z.core.$ZodType, path: string) {
if (seen.has(schema)) return
seen.add(schema)

const def = schema._def
const typeName = def.typeName

if (typeName === "ZodObject") {
const shape = def.shape()
for (const key in shape) {
if (schema instanceof z.ZodObject) {
for (const key in schema.shape) {
checkSnakeCase(key, `${path}.${key}`)
traverse(shape[key], `${path}.${key}`)
traverse(schema.shape[key], `${path}.${key}`)
}
} else if (typeName === "ZodUnion" || typeName === "ZodDiscriminatedUnion") {
const options =
def.options || (def.optionsMap ? Array.from(def.optionsMap.values()) : [])
options.forEach((opt: any, i: number) => {
traverse(opt, `${path}[union:${i}]`)
} else if (
schema instanceof z.ZodUnion ||
schema instanceof z.ZodDiscriminatedUnion
) {
schema.options.forEach((option, index) => {
traverse(option, `${path}[union:${index}]`)
})
} else if (typeName === "ZodArray") {
traverse(def.type, `${path}[]`)
} else if (schema instanceof z.ZodArray) {
traverse(schema.element, `${path}[]`)
} else if (
typeName === "ZodOptional" ||
typeName === "ZodNullable" ||
typeName === "ZodBranded"
schema instanceof z.ZodOptional ||
schema instanceof z.ZodNullable ||
schema instanceof z.ZodDefault ||
schema instanceof z.ZodPrefault
) {
traverse(def.innerType, path)
} else if (typeName === "ZodEnum") {
for (const v of def.values as string[]) {
checkSnakeCase(v, `${path} (enum value)`)
traverse(schema.unwrap(), path)
} else if (schema instanceof z.ZodEnum) {
for (const value of schema.options) {
if (typeof value === "string") {
checkSnakeCase(value, `${path} (enum value)`)
}
}
} else if (typeName === "ZodRecord") {
traverse(def.keyType, `${path}[key]`)
traverse(def.valueType, `${path}[value]`)
} else if (typeName === "ZodLazy") {
traverse(def.getter(), path)
} else if (typeName === "ZodEffects") {
traverse(def.schema, path)
} else if (typeName === "ZodPipeline") {
traverse(def.in, path)
traverse(def.out, path)
} else if (typeName === "ZodIntersection") {
traverse(def.left, path)
traverse(def.right, path)
} else if (typeName === "ZodDefault") {
traverse(def.innerType, path)
} else if (schema instanceof z.ZodRecord) {
traverse(schema.keyType, `${path}[key]`)
traverse(schema.valueType, `${path}[value]`)
} else if (schema instanceof z.ZodLazy) {
traverse(schema.unwrap(), path)
} else if (schema instanceof z.ZodPipe) {
traverse(schema.in, path)
traverse(schema.out, path)
} else if (schema instanceof z.ZodIntersection) {
traverse(schema.def.left, path)
traverse(schema.def.right, path)
}
}

Expand Down
12 changes: 3 additions & 9 deletions src/pcb/pcb_copper_text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,21 @@ export const pcb_copper_text = z
pcb_group_id: z.string().optional(),
subcircuit_id: z.string().optional(),
font: z.literal("tscircuit2024").default("tscircuit2024"),
font_size: distance.default("0.2mm"),
font_size: distance.prefault("0.2mm"),
pcb_component_id: z.string(),
text: z.string(),
is_knockout: z.boolean().default(false).optional(),
is_knockout: z.boolean().optional(),
knockout_padding: z
.object({
left: length,
top: length,
bottom: length,
right: length,
})
.default({
left: "0.2mm",
top: "0.2mm",
bottom: "0.2mm",
right: "0.2mm",
})
.optional(),
ccw_rotation: z.number().optional(),
layer: layer_ref,
is_mirrored: z.boolean().default(false).optional(),
is_mirrored: z.boolean().optional(),
anchor_position: point.default({ x: 0, y: 0 }),
anchor_alignment: ninePointAnchor.default("center"),
})
Expand Down
4 changes: 2 additions & 2 deletions src/pcb/pcb_fabrication_note_dimension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ export const pcb_fabrication_note_dimension = z
})
.optional(),
font: z.literal("tscircuit2024").default("tscircuit2024"),
font_size: length.default("1mm"),
font_size: length.prefault("1mm"),
color: z.string().optional(),
arrow_size: length.default("1mm"),
arrow_size: length.prefault("1mm"),
})
.describe("Defines a measurement annotation within PCB fabrication notes")

Expand Down
2 changes: 1 addition & 1 deletion src/pcb/pcb_fabrication_note_rect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const pcb_fabrication_note_rect = z
width: length,
height: length,
layer: visible_layer,
stroke_width: length.default("0.1mm"),
stroke_width: length.prefault("0.1mm"),
corner_radius: length.optional(),
is_filled: z.boolean().optional(),
has_stroke: z.boolean().optional(),
Expand Down
2 changes: 1 addition & 1 deletion src/pcb/pcb_fabrication_note_text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const pcb_fabrication_note_text = z
subcircuit_id: z.string().optional(),
pcb_group_id: z.string().optional(),
font: z.literal("tscircuit2024").default("tscircuit2024"),
font_size: distance.default("1mm"),
font_size: distance.prefault("1mm"),
pcb_component_id: z.string(),
text: z.string(),
ccw_rotation: z.number().optional(),
Expand Down
4 changes: 2 additions & 2 deletions src/pcb/pcb_note_dimension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ export const pcb_note_dimension = z
})
.optional(),
font: z.literal("tscircuit2024").default("tscircuit2024"),
font_size: length.default("1mm"),
font_size: length.prefault("1mm"),
layer: visible_layer.default("top"),
color: z.string().optional(),
arrow_size: length.default("1mm"),
arrow_size: length.prefault("1mm"),
})
.describe("Defines a measurement annotation within PCB documentation notes")

Expand Down
2 changes: 1 addition & 1 deletion src/pcb/pcb_note_line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const pcb_note_line = z
x2: distance,
y2: distance,
layer: visible_layer.default("top"),
stroke_width: distance.default("0.1mm"),
stroke_width: distance.prefault("0.1mm"),
color: z.string().optional(),
is_dashed: z.boolean().optional(),
})
Expand Down
2 changes: 1 addition & 1 deletion src/pcb/pcb_note_path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const pcb_note_path = z
text: z.string().optional(),
route: z.array(point),
layer: visible_layer.default("top"),
stroke_width: length.default("0.1mm"),
stroke_width: length.prefault("0.1mm"),
color: z.string().optional(),
})
.describe("Defines a polyline documentation note on the PCB")
Expand Down
2 changes: 1 addition & 1 deletion src/pcb/pcb_note_rect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const pcb_note_rect = z
width: length,
height: length,
layer: visible_layer.default("top"),
stroke_width: length.default("0.1mm"),
stroke_width: length.prefault("0.1mm"),
corner_radius: length.optional(),
is_filled: z.boolean().optional(),
has_stroke: z.boolean().optional(),
Expand Down
2 changes: 1 addition & 1 deletion src/pcb/pcb_note_text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const pcb_note_text = z
subcircuit_id: z.string().optional(),
name: z.string().optional(),
font: z.literal("tscircuit2024").default("tscircuit2024"),
font_size: distance.default("1mm"),
font_size: distance.prefault("1mm"),
text: z.string().optional(),
anchor_position: point.default({ x: 0, y: 0 }),
anchor_alignment: z
Expand Down
2 changes: 1 addition & 1 deletion src/pcb/pcb_silkscreen_circle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const pcb_silkscreen_circle = z
center: point,
radius: length,
layer: visible_layer,
stroke_width: length.default("1mm"),
stroke_width: length.prefault("1mm"),
is_filled: z.boolean().optional(),
})
.describe("Defines a silkscreen circle on the PCB")
Expand Down
2 changes: 1 addition & 1 deletion src/pcb/pcb_silkscreen_line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const pcb_silkscreen_line = z
pcb_component_id: z.string(),
pcb_group_id: z.string().optional(),
subcircuit_id: z.string().optional(),
stroke_width: distance.default("0.1mm"),
stroke_width: distance.prefault("0.1mm"),
x1: distance,
y1: distance,
x2: distance,
Expand Down
4 changes: 2 additions & 2 deletions src/pcb/pcb_silkscreen_rect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export const pcb_silkscreen_rect = z
width: length,
height: length,
layer: layer_ref,
stroke_width: length.default("1mm"),
stroke_width: length.prefault("1mm"),
corner_radius: length.optional(),
is_filled: z.boolean().default(true).optional(),
is_filled: z.boolean().optional(),
has_stroke: z.boolean().optional(),
is_stroke_dashed: z.boolean().optional(),
ccw_rotation: z.number().optional(),
Expand Down
12 changes: 3 additions & 9 deletions src/pcb/pcb_silkscreen_text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,21 @@ export const pcb_silkscreen_text = z
pcb_group_id: z.string().optional(),
subcircuit_id: z.string().optional(),
font: z.literal("tscircuit2024").default("tscircuit2024"),
font_size: distance.default("0.2mm"),
font_size: distance.prefault("0.2mm"),
pcb_component_id: z.string(),
text: z.string(),
is_knockout: z.boolean().default(false).optional(),
is_knockout: z.boolean().optional(),
knockout_padding: z
.object({
left: length,
top: length,
bottom: length,
right: length,
})
.default({
left: "0.2mm",
top: "0.2mm",
bottom: "0.2mm",
right: "0.2mm",
})
.optional(),
ccw_rotation: z.number().optional(),
layer: layer_ref,
is_mirrored: z.boolean().default(false).optional(),
is_mirrored: z.boolean().optional(),
anchor_position: point.default({ x: 0, y: 0 }),
anchor_alignment: ninePointAnchor.default("center"),
})
Expand Down
5 changes: 1 addition & 4 deletions src/pcb/pcb_trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,7 @@ export const pcb_trace = z
pcb_trace_id: getZodPrefixedIdWithDefault("pcb_trace"),
pcb_group_id: z.string().optional(),
subcircuit_id: z.string().optional(),
route_thickness_mode: z
.enum(["constant", "interpolated"])
.default("constant")
.optional(),
route_thickness_mode: z.enum(["constant", "interpolated"]).optional(),
route_order_index: z.number().optional(),
should_round_corners: z.boolean().optional(),
trace_length: z.number().optional(),
Expand Down
4 changes: 2 additions & 2 deletions src/pcb/pcb_via.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export const pcb_via = z
subcircuit_connectivity_map_key: z.string().optional(),
x: distance,
y: distance,
outer_diameter: distance.default("0.6mm"),
hole_diameter: distance.default("0.25mm"),
outer_diameter: distance.prefault("0.6mm"),
hole_diameter: distance.prefault("0.25mm"),
/** @deprecated */
from_layer: layer_ref.optional(),
/** @deprecated */
Expand Down
3 changes: 2 additions & 1 deletion src/schematic/schematic_component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { length } from "../units"
import { expectTypesMatch } from "src/utils/expect-types-match"

export const schematic_pin_styles = z.record(
z.string(),
z.object({
left_margin: length.optional(),
right_margin: length.optional(),
Expand Down Expand Up @@ -130,7 +131,7 @@ export const schematic_component = z.object({
box_width: length.optional(),
symbol_name: z.string().optional(),
port_arrangement: port_arrangement.optional(),
port_labels: z.record(z.string()).optional(),
port_labels: z.record(z.string(), z.string()).optional(),
symbol_display_value: z.string().optional(),
subcircuit_id: z.string().optional(),
schematic_group_id: z.string().optional(),
Expand Down
2 changes: 1 addition & 1 deletion src/source/base/source_component_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const source_component_base = z.object({
name: z.string(),
manufacturer_part_number: z.string().optional(),
supplier_part_numbers: z
.record(supplier_name, z.array(z.string()))
.partialRecord(supplier_name, z.array(z.string()))
.optional(),
display_value: z.string().optional(),
display_name: z.string().optional(),
Expand Down
Loading
Loading