Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,10 @@
"packageManager": "pnpm@10.4.0",
"engines": {
"node": ">=20.19.0"
},
"pnpm": {
"overrides": {
"vite": "npm:rolldown-vite@7.0.6"
}
}
}
3 changes: 2 additions & 1 deletion packages/prototypey/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"main": "lib/index.js",
"exports": {
".": "./lib/index.js",
"./infer": "./lib/infer.js"
"./infer": "./lib/infer.js",
"./lib/lib.d.ts": "./lib/lib.d.ts"
},
"files": [
"lib/",
Expand Down
19 changes: 10 additions & 9 deletions packages/prototypey/src/infer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,13 @@ type ReplaceRefsInType<T, Defs, Visited = never> =
* Infers the TypeScript type for a lexicon namespace, returning only the 'main' definition
* with all local refs (#user, #post, etc.) resolved to their actual types.
*/
export type Infer<T extends { id: string; defs: Record<string, unknown> }> =
Prettify<
"main" extends keyof T["defs"]
? { $type: T["id"] } & ReplaceRefsInType<
InferType<T["defs"]["main"]>,
{ [K in keyof T["defs"]]: InferType<T["defs"][K]> }
>
: never
>;
export type Infer<
T extends { json: { id: string; defs: Record<string, unknown> } },
> = Prettify<
"main" extends keyof T["json"]["defs"]
? { $type: T["json"]["id"] } & ReplaceRefsInType<
InferType<T["json"]["defs"]["main"]>,
{ [K in keyof T["json"]["defs"]]: InferType<T["json"]["defs"][K]> }
>
: never
>;
2 changes: 1 addition & 1 deletion packages/prototypey/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ interface SubscriptionOptions {

class Namespace<T extends LexiconNamespace> {
public json: T;
public infer: Infer<T> = null as unknown as Infer<T>;
public infer: Infer<{ json: T }> = null as unknown as Infer<{ json: T }>;

constructor(json: T) {
this.json = json;
Expand Down
1 change: 1 addition & 0 deletions packages/site/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
12 changes: 12 additions & 0 deletions packages/site/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>prototypey - Type-safe lexicon inference for ATProto</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
31 changes: 31 additions & 0 deletions packages/site/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@prototypey/site",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest"
},
"dependencies": {
"@monaco-editor/react": "^4.6.0",
"monaco-editor": "^0.52.2",
"prototypey": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"jsdom": "^25.0.1",
"typescript": "5.8.3",
"vite": "^6.0.5",
"vitest": "^3.2.4"
}
}
2 changes: 2 additions & 0 deletions packages/site/public/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { GetNullable, GetRequired, Infer } from "./infer";
export { lx } from "./lib";
190 changes: 190 additions & 0 deletions packages/site/public/types/infer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { Prettify } from "./type-utils.js";

//#region src/infer.d.ts
type InferType<T> = T extends {
type: "record";
}
? InferRecord<T>
: T extends {
type: "object";
}
? InferObject<T>
: T extends {
type: "array";
}
? InferArray<T>
: T extends {
type: "params";
}
? InferParams<T>
: T extends {
type: "union";
}
? InferUnion<T>
: T extends {
type: "token";
}
? InferToken<T>
: T extends {
type: "ref";
}
? InferRef<T>
: T extends {
type: "unknown";
}
? unknown
: T extends {
type: "null";
}
? null
: T extends {
type: "boolean";
}
? boolean
: T extends {
type: "integer";
}
? number
: T extends {
type: "string";
}
? string
: T extends {
type: "bytes";
}
? Uint8Array
: T extends {
type: "cid-link";
}
? string
: T extends {
type: "blob";
}
? Blob
: never;
type InferToken<T> = T extends {
enum: readonly (infer U)[];
}
? U
: string;
type GetRequired<T> = T extends {
required: readonly (infer R)[];
}
? R
: never;
type GetNullable<T> = T extends {
nullable: readonly (infer N)[];
}
? N
: never;
type InferObject<
T,
Nullable extends string = GetNullable<T> & string,
Required extends string = GetRequired<T> & string,
NullableAndRequired extends string = Required & Nullable & string,
Normal extends string = "properties" extends keyof T
? Exclude<keyof T["properties"], Required | Nullable> & string
: never,
> = Prettify<
T extends {
properties: infer P;
}
? { -readonly [K in Normal]?: InferType<P[K & keyof P]> } & {
-readonly [K in Exclude<Required, NullableAndRequired>]-?: InferType<
P[K & keyof P]
>;
} & {
-readonly [K in Exclude<Nullable, NullableAndRequired>]?: InferType<
P[K & keyof P]
> | null;
} & {
-readonly [K in NullableAndRequired]: InferType<P[K & keyof P]> | null;
}
: {}
>;
type InferArray<T> = T extends {
items: infer Items;
}
? InferType<Items>[]
: never[];
type InferUnion<T> = T extends {
refs: readonly (infer R)[];
}
? R extends string
? {
$type: R;
[key: string]: unknown;
}
: never
: never;
type InferRef<T> = T extends {
ref: infer R;
}
? R extends string
? {
$type: R;
[key: string]: unknown;
}
: unknown
: unknown;
type InferParams<T> = InferObject<T>;
type InferRecord<T> = T extends {
record: infer R;
}
? R extends {
type: "object";
}
? InferObject<R>
: R extends {
type: "union";
}
? InferUnion<R>
: unknown
: unknown;
/**
* Recursively replaces stub references in a type with their actual definitions.
* Detects circular references and missing references, returning string literal error messages.
*/
type ReplaceRefsInType<T, Defs, Visited = never> = T extends {
$type: `#${infer DefName}`;
}
? DefName extends keyof Defs
? DefName extends Visited
? `[Circular reference detected: #${DefName}]`
: Prettify<
ReplaceRefsInType<Defs[DefName], Defs, Visited | DefName> & {
$type: T["$type"];
}
>
: `[Reference not found: #${DefName}]`
: T extends Uint8Array | Blob
? T
: T extends readonly (infer Item)[]
? ReplaceRefsInType<Item, Defs, Visited>[]
: T extends object
? T extends (...args: unknown[]) => unknown
? T
: { [K in keyof T]: ReplaceRefsInType<T[K], Defs, Visited> }
: T;
/**
* Infers the TypeScript type for a lexicon namespace, returning only the 'main' definition
* with all local refs (#user, #post, etc.) resolved to their actual types.
*/
type Infer<
T extends {
id: string;
defs: Record<string, unknown>;
},
> = Prettify<
"main" extends keyof T["defs"]
? {
$type: T["id"];
} & ReplaceRefsInType<
InferType<T["defs"]["main"]>,
{ [K in keyof T["defs"]]: InferType<T["defs"][K]> }
>
: never
>;
//#endregion
export { GetNullable, GetRequired, Infer };
//# sourceMappingURL=infer.d.ts.map
Loading