From 8927865b9425e39f93218ee5e0587030106176e9 Mon Sep 17 00:00:00 2001 From: Saiichi Hashimoto Date: Tue, 18 Jul 2023 17:29:54 -0500 Subject: [PATCH 1/3] fix(aliased): allow inferring non objects BREAKING CHANGE: InferSchemaValues now returns an object keyed by type rather than a union. Fixes #121 --- packages/types/src/index.test.ts | 407 +++++++++++++++++++------------ packages/types/src/index.ts | 47 ++-- 2 files changed, 282 insertions(+), 172 deletions(-) diff --git a/packages/types/src/index.test.ts b/packages/types/src/index.test.ts index 9e51a8a8..69d20c86 100644 --- a/packages/types/src/index.test.ts +++ b/packages/types/src/index.test.ts @@ -2659,20 +2659,22 @@ describe("defineConfig", () => { }, }); - expectType>().toStrictEqual< - | { - _createdAt: string; - _id: string; - _rev: string; - _type: "foo"; - _updatedAt: string; - bar?: boolean; - } - | { - _type: "baz"; - qux?: boolean; - } - >(); + type Values = InferSchemaValues; + + expectType().toStrictEqual<{ + baz: { + _type: "baz"; + qux?: boolean; + }; + foo: { + _createdAt: string; + _id: string; + _rev: string; + _type: "foo"; + _updatedAt: string; + bar?: boolean; + }; + }>(); }); it("infers aliased type value", () => { @@ -2705,24 +2707,65 @@ describe("defineConfig", () => { }, }); - expectType>().toStrictEqual< - | { - _createdAt: string; - _id: string; - _rev: string; - _type: "foo"; - _updatedAt: string; - bar?: { - _type: "bar"; - } & { - baz?: boolean; - }; - } - | { + type Values = InferSchemaValues; + + expectType().toStrictEqual<{ + bar: { + _type: "bar"; + baz?: boolean; + }; + foo: { + _createdAt: string; + _id: string; + _rev: string; + _type: "foo"; + _updatedAt: string; + bar?: { _type: "bar"; + } & { baz?: boolean; - } - >(); + }; + }; + }>(); + }); + + it("infers non-object aliased type value", () => { + const config = defineConfig({ + dataset: "dataset", + projectId: "projectId", + schema: { + types: [ + defineType({ + name: "foo", + type: "document", + fields: [ + defineField({ + name: "bar", + type: "bar", + }), + ], + }), + defineType({ + name: "bar", + type: "string", + }), + ], + }, + }); + + type Values = InferSchemaValues; + + expectType().toStrictEqual<{ + bar: string; + foo: { + _createdAt: string; + _id: string; + _rev: string; + _type: "foo"; + _updatedAt: string; + bar?: string; + }; + }>(); }); it('adds "_type" to inferred named alias values in arrays', () => { @@ -2761,25 +2804,27 @@ describe("defineConfig", () => { }, }); - expectType>().toStrictEqual< - | { - _createdAt: string; - _id: string; - _rev: string; - _type: "foo"; - _updatedAt: string; - array?: ({ - _key: string; - _type: "aliasedMemberName"; - } & { - baz?: boolean; - })[]; - } - | { - _type: "bar"; + type Values = InferSchemaValues; + + expectType().toStrictEqual<{ + bar: { + _type: "bar"; + baz?: boolean; + }; + foo: { + _createdAt: string; + _id: string; + _rev: string; + _type: "foo"; + _updatedAt: string; + array?: ({ + _key: string; + _type: "aliasedMemberName"; + } & { baz?: boolean; - } - >(); + })[]; + }; + }>(); }); it("infers cyclical type value", () => { @@ -2803,9 +2848,11 @@ describe("defineConfig", () => { }, }); + type Values = InferSchemaValues; + // It really is cyclical! - expectType["foo"]["foo"]>().toStrictEqual< - InferSchemaValues["foo"] + expectType().toStrictEqual< + Values["foo"]["foo"] >(); }); @@ -2852,19 +2899,12 @@ describe("defineConfig", () => { }, }); + type Values = InferSchemaValues; + // It really is cyclical! expectType< - Extract< - InferSchemaValues, - // Gets us the Foo object - { _type: "foo" } - >["bar"]["baz"]["foo"]["bar"]["baz"]["foo"] - >().toStrictEqual< - Extract< - InferSchemaValues, - { _type: "foo" } - >["bar"]["baz"]["foo"] - >(); + Values["foo"]["bar"]["baz"]["foo"]["bar"]["baz"]["foo"] + >().toStrictEqual(); }); it("infers plugin type value", () => { @@ -2906,16 +2946,20 @@ describe("defineConfig", () => { ], }); - expectType>().toStrictEqual<{ - _createdAt: string; - _id: string; - _rev: string; - _type: "foo"; - _updatedAt: string; - pluginValue?: { - _type: "pluginValue"; - } & { - baz?: boolean; + type Values = InferSchemaValues; + + expectType().toStrictEqual<{ + foo: { + _createdAt: string; + _id: string; + _rev: string; + _type: "foo"; + _updatedAt: string; + pluginValue?: { + _type: "pluginValue"; + } & { + baz?: boolean; + }; }; }>(); }); @@ -2959,13 +3003,17 @@ describe("defineConfig", () => { ], }); - expectType>().toStrictEqual<{ - _createdAt: string; - _id: string; - _rev: string; - _type: "foo"; - _updatedAt: string; - pluginValue?: unknown; + type Values = InferSchemaValues; + + expectType().toStrictEqual<{ + foo: { + _createdAt: string; + _id: string; + _rev: string; + _type: "foo"; + _updatedAt: string; + pluginValue?: unknown; + }; }>(); }); }); @@ -3046,20 +3094,22 @@ describe("definePlugin", () => { }, })(); - expectType>().toStrictEqual< - | { - _createdAt: string; - _id: string; - _rev: string; - _type: "foo"; - _updatedAt: string; - bar?: boolean; - } - | { - _type: "baz"; - qux?: boolean; - } - >(); + type Values = InferSchemaValues; + + expectType().toStrictEqual<{ + baz: { + _type: "baz"; + qux?: boolean; + }; + foo: { + _createdAt: string; + _id: string; + _rev: string; + _type: "foo"; + _updatedAt: string; + bar?: boolean; + }; + }>(); }); it("infers aliased type value", () => { @@ -3091,24 +3141,64 @@ describe("definePlugin", () => { }, })(); - expectType>().toStrictEqual< - | { - _createdAt: string; - _id: string; - _rev: string; - _type: "foo"; - _updatedAt: string; - bar?: { - _type: "bar"; - } & { - baz?: boolean; - }; - } - | { + type Values = InferSchemaValues; + + expectType().toStrictEqual<{ + bar: { + _type: "bar"; + baz?: boolean; + }; + foo: { + _createdAt: string; + _id: string; + _rev: string; + _type: "foo"; + _updatedAt: string; + bar?: { _type: "bar"; + } & { baz?: boolean; - } - >(); + }; + }; + }>(); + }); + + it("infers non-object aliased type value", () => { + const plugin = definePlugin({ + name: "plugin", + schema: { + types: [ + defineType({ + name: "foo", + type: "document", + fields: [ + defineField({ + name: "bar", + type: "bar", + }), + ], + }), + defineType({ + name: "bar", + type: "string", + }), + ], + }, + })(); + + type Values = InferSchemaValues; + + expectType().toStrictEqual<{ + bar: string; + foo: { + _createdAt: string; + _id: string; + _rev: string; + _type: "foo"; + _updatedAt: string; + bar?: string; + }; + }>(); }); it('adds "_type" to inferred named alias values in arrays', () => { @@ -3146,25 +3236,27 @@ describe("definePlugin", () => { }, })(); - expectType>().toStrictEqual< - | { - _createdAt: string; - _id: string; - _rev: string; - _type: "foo"; - _updatedAt: string; - array?: ({ - _key: string; - _type: "aliasedMemberName"; - } & { - baz?: boolean; - })[]; - } - | { - _type: "bar"; + type Values = InferSchemaValues; + + expectType().toStrictEqual<{ + bar: { + _type: "bar"; + baz?: boolean; + }; + foo: { + _createdAt: string; + _id: string; + _rev: string; + _type: "foo"; + _updatedAt: string; + array?: ({ + _key: string; + _type: "aliasedMemberName"; + } & { baz?: boolean; - } - >(); + })[]; + }; + }>(); }); it("infers cyclical type value", () => { @@ -3187,9 +3279,11 @@ describe("definePlugin", () => { }, })(); + type Values = InferSchemaValues; + // It really is cyclical! - expectType["foo"]["foo"]>().toStrictEqual< - InferSchemaValues["foo"] + expectType().toStrictEqual< + Values["foo"]["foo"] >(); }); @@ -3235,19 +3329,12 @@ describe("definePlugin", () => { }, })(); + type Values = InferSchemaValues; + // It really is cyclical! expectType< - Extract< - InferSchemaValues, - // Gets us the Foo object - { _type: "foo" } - >["bar"]["baz"]["foo"]["bar"]["baz"]["foo"] - >().toStrictEqual< - Extract< - InferSchemaValues, - { _type: "foo" } - >["bar"]["baz"]["foo"] - >(); + Values["foo"]["bar"]["baz"]["foo"]["bar"]["baz"]["foo"] + >().toStrictEqual(); }); it("infers plugin type value", () => { @@ -3288,16 +3375,20 @@ describe("definePlugin", () => { ], })(); - expectType>().toStrictEqual<{ - _createdAt: string; - _id: string; - _rev: string; - _type: "foo"; - _updatedAt: string; - pluginValue?: { - _type: "pluginValue"; - } & { - baz?: boolean; + type Values = InferSchemaValues; + + expectType().toStrictEqual<{ + foo: { + _createdAt: string; + _id: string; + _rev: string; + _type: "foo"; + _updatedAt: string; + pluginValue?: { + _type: "pluginValue"; + } & { + baz?: boolean; + }; }; }>(); }); @@ -3340,13 +3431,17 @@ describe("definePlugin", () => { ], })(); - expectType>().toStrictEqual<{ - _createdAt: string; - _id: string; - _rev: string; - _type: "foo"; - _updatedAt: string; - pluginValue?: unknown; + type Values = InferSchemaValues; + + expectType().toStrictEqual<{ + foo: { + _createdAt: string; + _id: string; + _rev: string; + _type: "foo"; + _updatedAt: string; + pluginValue?: unknown; + }; }>(); }); }); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 27501543..37f1bdf5 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -102,6 +102,11 @@ type DefinitionBase< validation?: ValidationBuilder; }; +/** + * Infers the Value of a Definition, without aliased types. + * + * @private + */ export type _InferValue> = Def extends DefinitionBase ? Value : never; @@ -644,21 +649,27 @@ export const defineConfig = < type ExpandAliasValues< Value, - TAliasedDefinition extends Type<"object", any, any, any, any, any> + TAliasedDefinition extends Type > = Value extends AliasValue ? Extract< TAliasedDefinition, - Type<"object", TType, any, any, any, any> + Type > extends never ? unknown : ExpandAliasValues< _InferValue< - Extract> + Extract> >, TAliasedDefinition - > & { - _type: TType; - } + > & + (Extract< + TAliasedDefinition, + Type<"object", TType, any, any, any, any> + > extends never + ? unknown + : { + _type: TType; + }) : Value extends (infer Item)[] ? (Item extends ObjectArrayMemberValue ? Item extends { [key: string]: any } @@ -679,25 +690,29 @@ export type InferSchemaValues< > = TConfig extends MaybeArray< ConfigBase > - ? ExpandAliasValues< - TTypeDefinition extends Type< - "object", + ? { + [TName in TTypeDefinition extends Type< + any, infer TName extends string, any, any, any, any > - ? _InferValue & { _type: TName } - : _InferValue, - Extract< + ? TName + : never]: ExpandAliasValues< + TTypeDefinition extends Type<"object", TName, any, any, any, any> + ? _InferValue & { _type: TName } + : TTypeDefinition extends Type + ? _InferValue + : never, + // TPluginTypeDefinition | TTypeDefinition | (Type extends TPluginTypeDefinition ? never : TPluginTypeDefinition) | (Type extends TTypeDefinition ? never - : TTypeDefinition), - Type<"object", any, any, any, any, any> - > - > + : TTypeDefinition) + >; + } : never; From 5de2bd92e35595df20019473e7ba84349f0e5593 Mon Sep 17 00:00:00 2001 From: Saiichi Hashimoto Date: Tue, 18 Jul 2023 17:32:50 -0500 Subject: [PATCH 2/3] docs(types): adding InferSchemaValues migration docs between v2 and v3 Fixes #129 --- packages/types/README.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/types/README.md b/packages/types/README.md index c7c89129..93f38c6a 100644 --- a/packages/types/README.md +++ b/packages/types/README.md @@ -78,7 +78,7 @@ export default config; type Values = InferSchemaValues; // Import Product type into your application! -export type Product = Extract; +export type Product = Values["product"]; /** * Product === { * _createdAt: string; @@ -135,7 +135,7 @@ export default config; type Values = InferSchemaValues; -export type Foo = Extract; +export type Foo = Values["foo"]; /** * Foo === { * _createdAt: string; @@ -203,7 +203,7 @@ export default config; type Values = InferSchemaValues; -export type Foo = Extract; +export type Foo = Values["foo"]; /** * Foo === { * _createdAt: string; @@ -227,3 +227,20 @@ Typescript was an after-the-fact concern with sanity, since the rise of typescri The long term goal is to deprecate the monorepo altogether. Building this seperately was to move quickly and these features should be in sanity directly (and is likely one of their internal goals). The idea is to introduce these changes iteratively into sanity itself while removing them from this library, until it's reduced to simply passing through the `define*` methods directly, and will then be deprecated. This shouldn't deter you from using it! Under the hood, it's passing all the inputs to sanity's native `define*` methods, so you shouldn't have any runtime differences. With all the typings being attempting to make their way into sanity, you should keep all the benefits of just importing the `define*` methods and noticing no differences. + +## Migrations + +### Migrating from 2.x to 3.x + +#### InferSchemaValues + +`InferSchemaValues` used to return a union of all types but now returns an object keyed off by type. This is because using `Extract` to retrieve specific type was difficult. Object types would have a `_type` for easy extraction, but all the other types were less reliable (i.e. arrays and primitives). + +```diff +export default config; + +type Values = InferSchemaValues; + +- export type Product = Extract ++ export type Product = Values["product"]; +``` From fabd2af9341ccc447699d223b058ff6d07146ede Mon Sep 17 00:00:00 2001 From: Saiichi Hashimoto Date: Tue, 18 Jul 2023 17:38:53 -0500 Subject: [PATCH 3/3] fix(types): fully deprecate InferValue BREAKING CHANGE: Removed InferValue from exports completely. Replace with InferSchemaValues. --- packages/types/README.md | 49 +++++++++++++++++++++++++++++++++++++ packages/types/src/index.ts | 7 ------ 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/packages/types/README.md b/packages/types/README.md index 93f38c6a..f3850d5c 100644 --- a/packages/types/README.md +++ b/packages/types/README.md @@ -244,3 +244,52 @@ type Values = InferSchemaValues; - export type Product = Extract + export type Product = Values["product"]; ``` + +#### InferValue + +Types used to be inferred using `InferValue` for easy exporting. Now, `InferSchemaValues` needs to be used, and individual types keyed off of it. The reason for this is that only the config has context about aliased types, so `InferValue` was always going to be missing those values. + +```diff +const product = defineType({ + name: "product", + type: "document", + title: "Product", + fields: [ + // ... + ], +}); + +- export type Product = InferValue; + +const config = defineConfig({ + // ... + schema: { + types: [ + product, + // ... + ], + }, +}); + +export default config; + +type Values = InferSchemaValues; + ++ export type Product = Values["product"]; +``` + +You can still use `_InferValue` but this is discouraged, because it will be missing the context from the config: + +```diff +const product = defineType({ + name: "product", + type: "document", + title: "Product", + fields: [ + // ... + ], +}); + +- export type Product = InferValue; ++ export type Product = _InferValue; +``` diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 37f1bdf5..dfc95d41 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -110,13 +110,6 @@ type DefinitionBase< export type _InferValue> = Def extends DefinitionBase ? Value : never; -/** - * @deprecated Use {@link InferSchemaValues} instead. Otherwise, you won't get any aliased types (e.g. named object types, plugin types). - */ -export type InferValue = Def extends DefinitionBase - ? _InferValue - : never; - type RewriteValue> = Merge< { [key in keyof Rule]: Rule[key] extends (...args: infer Args) => Rule