From 6681b5b5af940aa3170b2d2ee00ee90311ba2501 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 2 May 2025 09:45:54 +0200 Subject: [PATCH 01/13] refactor: make SanityClient and ObservableSanityClient consistent --- src/SanityClient.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SanityClient.ts b/src/SanityClient.ts index 7b2c28680..23578948e 100644 --- a/src/SanityClient.ts +++ b/src/SanityClient.ts @@ -176,7 +176,7 @@ export class ObservableSanityClient { Q extends QueryWithoutParams | QueryParams = QueryParams, const G extends string = string, >( - query: string, + query: G, params: Q extends QueryWithoutParams ? QueryWithoutParams : Q, options: UnfilteredResponseQueryOptions, ): Observable>> @@ -1355,8 +1355,8 @@ export class SanityClient { * @param operations - Optional object of patch operations to initialize the patch instance with * @returns Patch instance - call `.commit()` to perform the operations defined */ - patch(documentId: PatchSelection, operations?: PatchOperations): Patch { - return new Patch(documentId, operations, this) + patch(selection: PatchSelection, operations?: PatchOperations): Patch { + return new Patch(selection, operations, this) } /** From 2d10256fcec3dc2eeb0ccc25075a092f8c626226 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 2 May 2025 10:14:45 +0200 Subject: [PATCH 02/13] chore: add observable tests --- test/client.test-d.ts | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/client.test-d.ts b/test/client.test-d.ts index 51ed0c189..109d65e6f 100644 --- a/test/client.test-d.ts +++ b/test/client.test-d.ts @@ -6,6 +6,7 @@ import { type QueryWithoutParams, type RawQueryResponse, } from '@sanity/client' +import {lastValueFrom} from 'rxjs' import {describe, expectTypeOf, test} from 'vitest' describe('client.fetch', () => { @@ -25,26 +26,55 @@ describe('client.fetch', () => { }) test('simple query', async () => { expectTypeOf(await client.fetch('*')).toMatchTypeOf() + expectTypeOf(await lastValueFrom(client.observable.fetch('*'))).toMatchTypeOf() expectTypeOf(await client.fetch('*', undefined)).toMatchTypeOf() + expectTypeOf(await lastValueFrom(client.observable.fetch('*', undefined))).toMatchTypeOf() expectTypeOf(await client.fetch('*', {})).toMatchTypeOf() + expectTypeOf(await lastValueFrom(client.observable.fetch('*', {}))).toMatchTypeOf() expectTypeOf(await client.fetch('*[_type == $type]', {type: 'post'})).toMatchTypeOf() + expectTypeOf( + await lastValueFrom(client.observable.fetch('*[_type == $type]', {type: 'post'})), + ).toMatchTypeOf() expectTypeOf(await client.fetch('*', undefined, {filterResponse: false})).toMatchTypeOf< RawQueryResponse >() + expectTypeOf( + await lastValueFrom(client.observable.fetch('*', undefined, {filterResponse: false})), + ).toMatchTypeOf>() expectTypeOf( await client.fetch('*', {} satisfies QueryParams, { filterResponse: false, }), ).toMatchTypeOf>() + expectTypeOf( + await lastValueFrom( + client.observable.fetch('*', {} satisfies QueryParams, { + filterResponse: false, + }), + ), + ).toMatchTypeOf>() expectTypeOf( await client.fetch('*[_type == $type]', {type: 'post'}, {filterResponse: false}), ).toMatchTypeOf>() + expectTypeOf( + await lastValueFrom( + client.observable.fetch('*[_type == $type]', {type: 'post'}, {filterResponse: false}), + ), + ).toMatchTypeOf>() }) test('generics', async () => { expectTypeOf(await client.fetch('count(*)')).toMatchTypeOf() + expectTypeOf( + await lastValueFrom(client.observable.fetch('count(*)')), + ).toMatchTypeOf() expectTypeOf( await client.fetch('count(*[_type == $type])', {type: 'post'}), ).toMatchTypeOf() + expectTypeOf( + await lastValueFrom( + client.observable.fetch('count(*[_type == $type])', {type: 'post'}), + ), + ).toMatchTypeOf() expectTypeOf( await client.fetch('count(*[_type == $type])', { // @ts-expect-error -- should fail @@ -184,6 +214,9 @@ describe('client.fetch', () => { expectTypeOf( await client.fetch('count(*)', {}, {filterResponse: true}), ).toMatchTypeOf() + expectTypeOf( + await lastValueFrom(client.observable.fetch('count(*)', {}, {filterResponse: true})), + ).toMatchTypeOf() expectTypeOf( await client.fetch('count(*)', {}, {filterResponse: false}), ).toMatchTypeOf<{ @@ -192,6 +225,14 @@ describe('client.fetch', () => { query: string resultSourceMap?: ContentSourceMap }>() + expectTypeOf( + await lastValueFrom(client.observable.fetch('count(*)', {}, {filterResponse: false})), + ).toMatchTypeOf<{ + result: number + ms: number + query: string + resultSourceMap?: ContentSourceMap + }>() expectTypeOf( await client.fetch( 'count(*[_type == $type])', @@ -214,6 +255,9 @@ describe('client.fetch', () => { }) test('stega: false', async () => { expectTypeOf(await client.fetch('*', {}, {stega: false})).toMatchTypeOf() + expectTypeOf( + await lastValueFrom(client.observable.fetch('*', {}, {stega: false})), + ).toMatchTypeOf() }) test('params can use properties that conflict with Next.js-defined properties', async () => { // `client.fetch` has type checking to prevent the common mistake of passing `cache` and `next` options as params (2nd parameter) in Next.js projects, where they should be passed as options (the 3rd parameter) From 5128a44598b7bb5fd65e1972211ffacfa55cf1e5 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 2 May 2025 10:57:10 +0200 Subject: [PATCH 03/13] chore: claude generated tests --- test/client.test-d.ts | 521 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 521 insertions(+) diff --git a/test/client.test-d.ts b/test/client.test-d.ts index 109d65e6f..462e5b534 100644 --- a/test/client.test-d.ts +++ b/test/client.test-d.ts @@ -5,6 +5,19 @@ import { type QueryParams, type QueryWithoutParams, type RawQueryResponse, + type ClientConfig, + type InitializedClientConfig, + type SanityDocument, + type SingleMutationResult, + type MultipleMutationResult, + type MutationSelection, + type Mutation, + type Action, + type Patch, + type Transaction, + type ObservablePatch, + type ObservableTransaction, + type BaseActionOptions, } from '@sanity/client' import {lastValueFrom} from 'rxjs' import {describe, expectTypeOf, test} from 'vitest' @@ -394,3 +407,511 @@ describe('client.fetch', () => { ).toMatchTypeOf() }) }) + +// Tests for client configuration methods +describe('client.config and client.withConfig', () => { + test('client.config', () => { + const client = createClient({}) + + // Test config getter + expectTypeOf(client.config()).toMatchTypeOf() + expectTypeOf(client.observable.config()).toMatchTypeOf() + + // Test config setter + const newConfig: Partial = {apiVersion: '2023-05-03'} + expectTypeOf(client.config(newConfig)).toEqualTypeOf(client) + expectTypeOf(client.observable.config(newConfig)).toEqualTypeOf(client.observable) + + // Test withConfig + expectTypeOf(client.withConfig(newConfig)).toMatchTypeOf(client) + expectTypeOf(client.observable.withConfig(newConfig)).toMatchTypeOf(client.observable) + }) +}) + +// Tests for URL helper methods +describe('client URL methods', () => { + test('client.getUrl', () => { + const client = createClient({}) + + // Test getUrl with default canUseCdn + expectTypeOf(client.getUrl('/path/to/resource')).toMatchTypeOf() + expectTypeOf(client.observable.getUrl('/path/to/resource')).toMatchTypeOf() + + // Test getUrl with explicit canUseCdn + expectTypeOf(client.getUrl('/path/to/resource', true)).toMatchTypeOf() + expectTypeOf(client.observable.getUrl('/path/to/resource', true)).toMatchTypeOf() + + expectTypeOf(client.getUrl('/path/to/resource', false)).toMatchTypeOf() + expectTypeOf(client.observable.getUrl('/path/to/resource', false)).toMatchTypeOf() + }) + + test('client.getDataUrl', () => { + const client = createClient({}) + + // Test getDataUrl with only operation + expectTypeOf(client.getDataUrl('query')).toMatchTypeOf() + expectTypeOf(client.observable.getDataUrl('query')).toMatchTypeOf() + + // Test getDataUrl with operation and path + expectTypeOf(client.getDataUrl('query', 'production')).toMatchTypeOf() + expectTypeOf(client.observable.getDataUrl('query', 'production')).toMatchTypeOf() + }) +}) + +// Tests for document methods +describe('client document methods', () => { + const client = createClient({}) + + test('client.getDocument', async () => { + // Basic usage + expectTypeOf(await client.getDocument('doc123')).toMatchTypeOf< + SanityDocument | undefined + >() + expectTypeOf(await lastValueFrom(client.observable.getDocument('doc123'))).toMatchTypeOf< + SanityDocument | undefined + >() + + // With generic type + expectTypeOf(await client.getDocument<{title: string}>('doc123')).toMatchTypeOf< + SanityDocument<{title: string}> | undefined + >() + expectTypeOf( + await lastValueFrom(client.observable.getDocument<{title: string}>('doc123')), + ).toMatchTypeOf | undefined>() + + // With options + expectTypeOf(await client.getDocument('doc123', {tag: 'tag1'})).toMatchTypeOf< + SanityDocument | undefined + >() + expectTypeOf( + await lastValueFrom(client.observable.getDocument('doc123', {tag: 'tag1'})), + ).toMatchTypeOf | undefined>() + }) + + test('client.getDocuments', async () => { + // Basic usage + expectTypeOf(await client.getDocuments(['doc123', 'doc456'])).toMatchTypeOf< + (SanityDocument | null)[] + >() + expectTypeOf( + await lastValueFrom(client.observable.getDocuments(['doc123', 'doc456'])), + ).toMatchTypeOf<(SanityDocument | null)[]>() + + // With generic type + expectTypeOf(await client.getDocuments<{title: string}>(['doc123', 'doc456'])).toMatchTypeOf< + (SanityDocument<{title: string}> | null)[] + >() + expectTypeOf( + await lastValueFrom(client.observable.getDocuments<{title: string}>(['doc123', 'doc456'])), + ).toMatchTypeOf<(SanityDocument<{title: string}> | null)[]>() + + // With options + expectTypeOf(await client.getDocuments(['doc123', 'doc456'], {tag: 'tag1'})).toMatchTypeOf< + (SanityDocument | null)[] + >() + expectTypeOf( + await lastValueFrom(client.observable.getDocuments(['doc123', 'doc456'], {tag: 'tag1'})), + ).toMatchTypeOf<(SanityDocument | null)[]>() + }) + + test('client.create', async () => { + const doc = {_type: 'post', title: 'Hello World'} + + // Basic usage + expectTypeOf(await client.create(doc)).toMatchTypeOf>() + expectTypeOf(await lastValueFrom(client.observable.create(doc))).toMatchTypeOf< + SanityDocument + >() + + // With generic type + expectTypeOf(await client.create<{title: string}>(doc)).toMatchTypeOf< + SanityDocument<{title: string}> + >() + expectTypeOf(await lastValueFrom(client.observable.create<{title: string}>(doc))).toMatchTypeOf< + SanityDocument<{title: string}> + >() + + // Return first document with options + expectTypeOf(await client.create(doc, {returnFirst: true})).toMatchTypeOf>() + expectTypeOf( + await lastValueFrom(client.observable.create(doc, {returnFirst: true})), + ).toMatchTypeOf>() + + // Return documents array with options + const docsResult = await client.create(doc, {returnFirst: false, returnDocuments: true}) + expectTypeOf(docsResult).toBeArray() + + const obsDocsResult = await lastValueFrom( + client.observable.create(doc, {returnFirst: false, returnDocuments: true}), + ) + expectTypeOf(obsDocsResult).toBeArray() + + // Return mutation result + expectTypeOf( + await client.create(doc, {returnDocuments: false}), + ).toMatchTypeOf() + expectTypeOf( + await lastValueFrom(client.observable.create(doc, {returnDocuments: false})), + ).toMatchTypeOf() + }) + + test('client.createIfNotExists', async () => { + const docWithId = {_id: 'unique123', _type: 'post', title: 'Hello World'} + + // Basic usage + expectTypeOf(await client.createIfNotExists(docWithId)).toMatchTypeOf>() + expectTypeOf(await lastValueFrom(client.observable.createIfNotExists(docWithId))).toMatchTypeOf< + SanityDocument + >() + + // With generic type + expectTypeOf(await client.createIfNotExists<{title: string}>(docWithId)).toMatchTypeOf< + SanityDocument<{title: string}> + >() + expectTypeOf( + await lastValueFrom(client.observable.createIfNotExists<{title: string}>(docWithId)), + ).toMatchTypeOf>() + + // Return first document with options + expectTypeOf(await client.createIfNotExists(docWithId, {returnFirst: true})).toMatchTypeOf< + SanityDocument + >() + expectTypeOf( + await lastValueFrom(client.observable.createIfNotExists(docWithId, {returnFirst: true})), + ).toMatchTypeOf>() + + // Return documents array with options + const docsResult = await client.createIfNotExists(docWithId, { + returnFirst: false, + returnDocuments: true, + }) + expectTypeOf(docsResult).toBeArray() + + const obsDocsResult = await lastValueFrom( + client.observable.createIfNotExists(docWithId, {returnFirst: false, returnDocuments: true}), + ) + expectTypeOf(obsDocsResult).toBeArray() + + // Return mutation result + expectTypeOf( + await client.createIfNotExists(docWithId, {returnDocuments: false}), + ).toMatchTypeOf() + expectTypeOf( + await lastValueFrom(client.observable.createIfNotExists(docWithId, {returnDocuments: false})), + ).toMatchTypeOf() + }) + + test('client.createOrReplace', async () => { + const docWithId = {_id: 'unique123', _type: 'post', title: 'Hello World'} + + // Basic usage + expectTypeOf(await client.createOrReplace(docWithId)).toMatchTypeOf>() + expectTypeOf(await lastValueFrom(client.observable.createOrReplace(docWithId))).toMatchTypeOf< + SanityDocument + >() + + // With generic type + expectTypeOf(await client.createOrReplace<{title: string}>(docWithId)).toMatchTypeOf< + SanityDocument<{title: string}> + >() + expectTypeOf( + await lastValueFrom(client.observable.createOrReplace<{title: string}>(docWithId)), + ).toMatchTypeOf>() + + // Return first document with options + expectTypeOf(await client.createOrReplace(docWithId, {returnFirst: true})).toMatchTypeOf< + SanityDocument + >() + expectTypeOf( + await lastValueFrom(client.observable.createOrReplace(docWithId, {returnFirst: true})), + ).toMatchTypeOf>() + + // Return documents array with options + const docsResult = await client.createOrReplace(docWithId, { + returnFirst: false, + returnDocuments: true, + }) + expectTypeOf(docsResult).toBeArray() + + const obsDocsResult = await lastValueFrom( + client.observable.createOrReplace(docWithId, {returnFirst: false, returnDocuments: true}), + ) + expectTypeOf(obsDocsResult).toBeArray() + + // Return mutation result + expectTypeOf( + await client.createOrReplace(docWithId, {returnDocuments: false}), + ).toMatchTypeOf() + expectTypeOf( + await lastValueFrom(client.observable.createOrReplace(docWithId, {returnDocuments: false})), + ).toMatchTypeOf() + }) + + test('client.delete', async () => { + // With document ID + expectTypeOf(await client.delete('doc123')).toMatchTypeOf>() + expectTypeOf(await lastValueFrom(client.observable.delete('doc123'))).toMatchTypeOf< + SanityDocument + >() + + // With generic type + expectTypeOf(await client.delete<{title: string}>('doc123')).toMatchTypeOf< + SanityDocument<{title: string}> + >() + expectTypeOf( + await lastValueFrom(client.observable.delete<{title: string}>('doc123')), + ).toMatchTypeOf>() + + // With selection object + const selection: MutationSelection = {query: '*[_type == "post"]'} + expectTypeOf(await client.delete(selection)).toMatchTypeOf>() + expectTypeOf(await lastValueFrom(client.observable.delete(selection))).toMatchTypeOf< + SanityDocument + >() + + // Return first document with options + expectTypeOf(await client.delete('doc123', {returnFirst: true})).toMatchTypeOf< + SanityDocument + >() + expectTypeOf( + await lastValueFrom(client.observable.delete('doc123', {returnFirst: true})), + ).toMatchTypeOf>() + + // Return documents array with options + const docsResult = await client.delete('doc123', {returnFirst: false, returnDocuments: true}) + expectTypeOf(docsResult).toBeArray() + + const obsDocsResult = await lastValueFrom( + client.observable.delete('doc123', {returnFirst: false, returnDocuments: true}), + ) + expectTypeOf(obsDocsResult).toBeArray() + + // Return mutation result + expectTypeOf( + await client.delete('doc123', {returnDocuments: false}), + ).toMatchTypeOf() + expectTypeOf( + await lastValueFrom(client.observable.delete('doc123', {returnDocuments: false})), + ).toMatchTypeOf() + }) + + test('client.mutate', async () => { + const mutations: Mutation[] = [{create: {_type: 'post', title: 'Hello World'}}] + + // Basic usage + expectTypeOf(await client.mutate(mutations)).toMatchTypeOf>() + expectTypeOf(await lastValueFrom(client.observable.mutate(mutations))).toMatchTypeOf< + SanityDocument + >() + + // Return first document with options + expectTypeOf(await client.mutate(mutations, {returnFirst: true})).toMatchTypeOf< + SanityDocument + >() + expectTypeOf( + await lastValueFrom(client.observable.mutate(mutations, {returnFirst: true})), + ).toMatchTypeOf>() + + // Return documents array with options + const docsResult = await client.mutate(mutations, {returnFirst: false, returnDocuments: true}) + expectTypeOf(docsResult).toBeArray() + + const obsDocsResult = await lastValueFrom( + client.observable.mutate(mutations, {returnFirst: false, returnDocuments: true}), + ) + expectTypeOf(obsDocsResult).toBeArray() + + // Return mutation result + expectTypeOf( + await client.mutate(mutations, {returnDocuments: false}), + ).toMatchTypeOf() + expectTypeOf( + await lastValueFrom(client.observable.mutate(mutations, {returnDocuments: false})), + ).toMatchTypeOf() + + // With transaction + const transaction = client.transaction().create({_type: 'post', title: 'Hello World'}) + expectTypeOf(await client.mutate(transaction)).toMatchTypeOf>() + + // With observable transaction + const obsTransaction = client.observable + .transaction() + .create({_type: 'post', title: 'Hello World'}) + expectTypeOf(await lastValueFrom(client.observable.mutate(obsTransaction))).toMatchTypeOf< + SanityDocument + >() + }) + + test('client.patch', async () => { + // Create patch with ID + const patch = client.patch('doc123') + expectTypeOf(patch).toMatchTypeOf() + + // Create observable patch with ID + const obsPatch = client.observable.patch('doc123') + expectTypeOf(obsPatch).toMatchTypeOf() + + // Create patch with array of IDs + const patchWithIds = client.patch(['doc123', 'doc456']) + expectTypeOf(patchWithIds).toMatchTypeOf() + + // Create patch with query selection + const patchWithQuery = client.patch({query: '*[_type == "post"]'}) + expectTypeOf(patchWithQuery).toMatchTypeOf() + + // Create patch with operations + const patchWithOps = client.patch('doc123', {set: {title: 'Updated Title'}}) + expectTypeOf(patchWithOps).toMatchTypeOf() + + // Patch operations + const patchWithChain = client + .patch('doc123') + .set({title: 'Updated Title'}) + .inc({count: 1}) + .dec({visits: 1}) + .unset(['oldField']) + + expectTypeOf(patchWithChain).toMatchTypeOf() + + // Commit operations + const commitResult = await patchWithChain.commit() + expectTypeOf(commitResult).toMatchTypeOf>() + + const obsCommitResult = await lastValueFrom(obsPatch.set({title: 'Updated Title'}).commit()) + expectTypeOf(obsCommitResult).toMatchTypeOf>() + + // Commit with options + const commitOptions = await patchWithChain.commit({returnDocuments: false}) + expectTypeOf(commitOptions).toMatchTypeOf() + + const obsCommitOptions = await lastValueFrom(obsPatch.commit({returnDocuments: false})) + expectTypeOf(obsCommitOptions).toMatchTypeOf() + }) + + test('client.transaction', async () => { + // Create empty transaction + const transaction = client.transaction() + expectTypeOf(transaction).toMatchTypeOf() + + // Create observable transaction + const obsTransaction = client.observable.transaction() + expectTypeOf(obsTransaction).toMatchTypeOf() + + // Create transaction with operations + const transactionWithOps = client.transaction([{create: {_type: 'post', title: 'Hello World'}}]) + expectTypeOf(transactionWithOps).toMatchTypeOf() + + // Transaction operations + const transactionChain = client + .transaction() + .create({_type: 'post', title: 'Hello World'}) + .createIfNotExists({_id: 'unique123', _type: 'post', title: 'Hello Again'}) + .delete('doc123') + + expectTypeOf(transactionChain).toMatchTypeOf() + + // Commit operations + const commitResult = await transactionChain.commit() + expectTypeOf(commitResult).toMatchTypeOf() + + const obsCommitResult = await lastValueFrom( + obsTransaction.create({_type: 'post', title: 'Hello'}).commit(), + ) + expectTypeOf(obsCommitResult).toMatchTypeOf() + + // Commit with options + const commitOptions = await transactionChain.commit({returnFirst: true}) + expectTypeOf(commitOptions).toMatchTypeOf() + + const obsCommitOptions = await lastValueFrom( + obsTransaction.commit({returnFirst: true, returnDocuments: false}), + ) + expectTypeOf(obsCommitOptions).toMatchTypeOf() + }) + + test('client.action', async () => { + // Single action + const action = { + actionType: 'sanity.action.document.publish', + draftId: 'draft.bike-123', + publishedId: 'bike-123', + } satisfies Action + + const actionResult = await client.action(action) + expectTypeOf(actionResult).toBeObject() + + const obsActionResult = await lastValueFrom(client.observable.action(action)) + expectTypeOf(obsActionResult).toBeObject() + + // Action array + const actions = [ + { + actionType: 'sanity.action.document.publish', + draftId: 'draft.bike-123', + publishedId: 'bike-123', + }, + { + actionType: 'sanity.action.document.unpublish', + publishedId: 'bike-456', + draftId: 'draft.bike-456', + }, + ] satisfies Action[] + + const actionsResult = await client.action(actions) + expectTypeOf(actionsResult).toBeObject() + + const obsActionsResult = await lastValueFrom(client.observable.action(actions)) + expectTypeOf(obsActionsResult).toBeObject() + + // With options + const actionOptions = { + dryRun: true, + transactionId: 'my-transaction-id', + } satisfies BaseActionOptions + + const actionWithOptions = await client.action(action, actionOptions) + expectTypeOf(actionWithOptions).toBeObject() + + const obsActionWithOptions = await lastValueFrom( + client.observable.action(action, actionOptions), + ) + expectTypeOf(obsActionWithOptions).toBeObject() + }) + + test('client.request', async () => { + // Basic request + const requestResult = await client.request({ + method: 'GET', + uri: '/path/to/endpoint', + }) + expectTypeOf(requestResult).toBeAny() + + const obsRequestResult = await lastValueFrom( + client.observable.request({ + method: 'GET', + uri: '/path/to/endpoint', + }), + ) + expectTypeOf(obsRequestResult).toBeAny() + + // With generic type + interface CustomResponse { + result: string + timestamp: number + } + + const typedRequest = await client.request({ + method: 'GET', + uri: '/path/to/endpoint', + }) + expectTypeOf(typedRequest).toMatchTypeOf() + + const obsTypedRequest = await lastValueFrom( + client.observable.request({ + method: 'GET', + uri: '/path/to/endpoint', + }), + ) + expectTypeOf(obsTypedRequest).toMatchTypeOf() + }) +}) From fca611e34d99d72db10230819f5b62101fcbe2c7 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 2 May 2025 11:43:20 +0200 Subject: [PATCH 04/13] chore: add base types --- src/SanityClient.ts | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/SanityClient.ts b/src/SanityClient.ts index 23578948e..5e4627e01 100644 --- a/src/SanityClient.ts +++ b/src/SanityClient.ts @@ -59,7 +59,7 @@ export type { } /** @public */ -export class ObservableSanityClient { +export class ObservableSanityClient implements ObservableSanityClientType { assets: ObservableAssetsClient datasets: ObservableDatasetsClient live: LiveClient @@ -727,7 +727,7 @@ export class ObservableSanityClient { } /** @public */ -export class SanityClient { +export class SanityClient implements SanityClientType { assets: AssetsClient datasets: DatasetsClient live: LiveClient @@ -1429,3 +1429,27 @@ export class SanityClient { return dataMethods._getDataUrl(this, operation, path) } } + +/** + * Shared base type for the `SanityClient` and `ObservableSanityClient` classes. + * TODO: refactor the Promise and Observable differences to use generics so we no longer suffer from all this duplication in TS docs + */ +interface SanityClientBase {} + +/** + * The interface implemented by the `SanityClient` class. + * When writing code that wants to take an instance of `SanityClient` as input it's better to use this type, + * as the `SanityClient` class has private properties and thus TypeScrict will consider the type incompatible + * in cases where you might have multiple `@sanity/client` instances in your node_modules. + * @public + */ +export interface SanityClientType extends SanityClientBase {} + +/** + * The interface implemented by the `ObservableSanityClient` class. + * When writing code that wants to take an instance of `ObservableSanityClient` as input it's better to use this type, + * as the `ObservableSanityClient` class has private properties and thus TypeScrict will consider the type incompatible + * in cases where you might have multiple `@sanity/client` instances in your node_modules. + * @public + */ +export interface ObservableSanityClientType extends SanityClientBase {} From 32238fd393d1f456b848104c68330f337e364d7f Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 2 May 2025 12:05:29 +0200 Subject: [PATCH 05/13] feat: add `SanityClientType` interfaces --- src/SanityClient.ts | 1060 ++++++++++++++++++++++++++++++++++++++++- test/client.test-d.ts | 56 +++ 2 files changed, 1106 insertions(+), 10 deletions(-) diff --git a/src/SanityClient.ts b/src/SanityClient.ts index 5e4627e01..937ef0444 100644 --- a/src/SanityClient.ts +++ b/src/SanityClient.ts @@ -1434,16 +1434,35 @@ export class SanityClient implements SanityClientType { * Shared base type for the `SanityClient` and `ObservableSanityClient` classes. * TODO: refactor the Promise and Observable differences to use generics so we no longer suffer from all this duplication in TS docs */ -interface SanityClientBase {} +interface SanityClientBase { + live: LiveClient + listen: typeof _listen -/** - * The interface implemented by the `SanityClient` class. - * When writing code that wants to take an instance of `SanityClient` as input it's better to use this type, - * as the `SanityClient` class has private properties and thus TypeScrict will consider the type incompatible - * in cases where you might have multiple `@sanity/client` instances in your node_modules. - * @public - */ -export interface SanityClientType extends SanityClientBase {} + /** + * Returns the current client configuration + */ + config(): InitializedClientConfig + /** + * Reconfigure the client. Note that this _mutates_ the current client. + */ + config(newConfig?: Partial): this + + /** + * Get a Sanity API URL for the URI provided + * + * @param uri - URI/path to build URL for + * @param canUseCdn - Whether or not to allow using the API CDN for this route + */ + getUrl(uri: string, canUseCdn?: boolean): string + + /** + * Get a Sanity API URL for the data operation and path provided + * + * @param operation - Data operation (eg `query`, `mutate`, `listen` or similar) + * @param path - Path to append after the operation + */ + getDataUrl(operation: string, path?: string): string +} /** * The interface implemented by the `ObservableSanityClient` class. @@ -1452,4 +1471,1025 @@ export interface SanityClientType extends SanityClientBase {} * in cases where you might have multiple `@sanity/client` instances in your node_modules. * @public */ -export interface ObservableSanityClientType extends SanityClientBase {} +export interface ObservableSanityClientType extends SanityClientBase { + assets: ObservableAssetsClient + datasets: ObservableDatasetsClient + projects: ObservableProjectsClient + users: ObservableUsersClient + + /** + * Clone the client - returns a new instance + */ + clone(): ObservableSanityClient + + /** + * Clone the client with a new (partial) configuration. + * + * @param newConfig - New client configuration properties, shallowly merged with existing configuration + */ + withConfig(newConfig?: Partial): ObservableSanityClient + + /** + * Perform a GROQ-query against the configured dataset. + * + * @param query - GROQ-query to perform + */ + fetch< + R = Any, + Q extends QueryWithoutParams = QueryWithoutParams, + const G extends string = string, + >( + query: G, + params?: Q | QueryWithoutParams, + ): Observable> + /** + * Perform a GROQ-query against the configured dataset. + * + * @param query - GROQ-query to perform + * @param params - Optional query parameters + * @param options - Optional request options + */ + fetch< + R = Any, + Q extends QueryWithoutParams | QueryParams = QueryParams, + const G extends string = string, + >( + query: G, + params: Q extends QueryWithoutParams ? QueryWithoutParams : Q, + options?: FilteredResponseQueryOptions, + ): Observable> + /** + * Perform a GROQ-query against the configured dataset. + * + * @param query - GROQ-query to perform + * @param params - Optional query parameters + * @param options - Request options + */ + fetch< + R = Any, + Q extends QueryWithoutParams | QueryParams = QueryParams, + const G extends string = string, + >( + query: G, + params: Q extends QueryWithoutParams ? QueryWithoutParams : Q, + options: UnfilteredResponseQueryOptions, + ): Observable>> + /** + * Perform a GROQ-query against the configured dataset. + * + * @param query - GROQ-query to perform + * @param params - Optional query parameters + * @param options - Request options + */ + fetch< + R = Any, + Q extends QueryWithoutParams | QueryParams = QueryParams, + const G extends string = string, + >( + query: G, + params: Q extends QueryWithoutParams ? QueryWithoutParams : Q, + options: UnfilteredResponseWithoutQuery, + ): Observable>> + + /** + * Fetch a single document with the given ID. + * + * @param id - Document ID to fetch + * @param options - Request options + */ + getDocument = Record>( + id: string, + options?: {tag?: string}, + ): Observable | undefined> + + /** + * Fetch multiple documents in one request. + * Should be used sparingly - performing a query is usually a better option. + * The order/position of documents is preserved based on the original array of IDs. + * If any of the documents are missing, they will be replaced by a `null` entry in the returned array + * + * @param ids - Document IDs to fetch + * @param options - Request options + */ + getDocuments = Record>( + ids: string[], + options?: {tag?: string}, + ): Observable<(SanityDocument | null)[]> + + /** + * Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database. + * Returns an observable that resolves to the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + create = Record>( + document: SanityDocumentStub, + options: FirstDocumentMutationOptions, + ): Observable> + /** + * Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database. + * Returns an observable that resolves to an array containing the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + create = Record>( + document: SanityDocumentStub, + options: AllDocumentsMutationOptions, + ): Observable[]> + /** + * Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database. + * Returns an observable that resolves to a mutation result object containing the ID of the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + create = Record>( + document: SanityDocumentStub, + options: FirstDocumentIdMutationOptions, + ): Observable + /** + * Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database. + * Returns an observable that resolves to a mutation result object containing the ID of the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + create = Record>( + document: SanityDocumentStub, + options: AllDocumentIdsMutationOptions, + ): Observable + /** + * Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database. + * Returns an observable that resolves to the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + create = Record>( + document: SanityDocumentStub, + options?: BaseMutationOptions, + ): Observable> + + /** + * Create a document if no document with the same ID already exists. + * Returns an observable that resolves to the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + createIfNotExists = Record>( + document: IdentifiedSanityDocumentStub, + options: FirstDocumentMutationOptions, + ): Observable> + /** + * Create a document if no document with the same ID already exists. + * Returns an observable that resolves to an array containing the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + createIfNotExists = Record>( + document: IdentifiedSanityDocumentStub, + options: AllDocumentsMutationOptions, + ): Observable[]> + /** + * Create a document if no document with the same ID already exists. + * Returns an observable that resolves to a mutation result object containing the ID of the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + createIfNotExists = Record>( + document: IdentifiedSanityDocumentStub, + options: FirstDocumentIdMutationOptions, + ): Observable + /** + * Create a document if no document with the same ID already exists. + * Returns an observable that resolves to a mutation result object containing the ID of the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + createIfNotExists = Record>( + document: IdentifiedSanityDocumentStub, + options: AllDocumentIdsMutationOptions, + ): Observable + /** + * Create a document if no document with the same ID already exists. + * Returns an observable that resolves to the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + createIfNotExists = Record>( + document: IdentifiedSanityDocumentStub, + options?: BaseMutationOptions, + ): Observable> + + /** + * Create a document if it does not exist, or replace a document with the same document ID + * Returns an observable that resolves to the created document. + * + * @param document - Document to either create or replace + * @param options - Mutation options + */ + createOrReplace = Record>( + document: IdentifiedSanityDocumentStub, + options: FirstDocumentMutationOptions, + ): Observable> + /** + * Create a document if it does not exist, or replace a document with the same document ID + * Returns an observable that resolves to an array containing the created document. + * + * @param document - Document to either create or replace + * @param options - Mutation options + */ + createOrReplace = Record>( + document: IdentifiedSanityDocumentStub, + options: AllDocumentsMutationOptions, + ): Observable[]> + /** + * Create a document if it does not exist, or replace a document with the same document ID + * Returns an observable that resolves to a mutation result object containing the ID of the created document. + * + * @param document - Document to either create or replace + * @param options - Mutation options + */ + createOrReplace = Record>( + document: IdentifiedSanityDocumentStub, + options: FirstDocumentIdMutationOptions, + ): Observable + /** + * Create a document if it does not exist, or replace a document with the same document ID + * Returns an observable that resolves to a mutation result object containing the created document ID. + * + * @param document - Document to either create or replace + * @param options - Mutation options + */ + createOrReplace = Record>( + document: IdentifiedSanityDocumentStub, + options: AllDocumentIdsMutationOptions, + ): Observable + /** + * Create a document if it does not exist, or replace a document with the same document ID + * Returns an observable that resolves to the created document. + * + * @param document - Document to either create or replace + * @param options - Mutation options + */ + createOrReplace = Record>( + document: IdentifiedSanityDocumentStub, + options?: BaseMutationOptions, + ): Observable> + + /** + * Deletes a document with the given document ID. + * Returns an observable that resolves to the deleted document. + * + * @param id - Document ID to delete + * @param options - Options for the mutation + */ + delete = Record>( + id: string, + options: FirstDocumentMutationOptions, + ): Observable> + /** + * Deletes a document with the given document ID. + * Returns an observable that resolves to an array containing the deleted document. + * + * @param id - Document ID to delete + * @param options - Options for the mutation + */ + delete = Record>( + id: string, + options: AllDocumentsMutationOptions, + ): Observable[]> + /** + * Deletes a document with the given document ID. + * Returns an observable that resolves to a mutation result object containing the deleted document ID. + * + * @param id - Document ID to delete + * @param options - Options for the mutation + */ + delete(id: string, options: FirstDocumentIdMutationOptions): Observable + /** + * Deletes a document with the given document ID. + * Returns an observable that resolves to a mutation result object containing the deleted document ID. + * + * @param id - Document ID to delete + * @param options - Options for the mutation + */ + delete(id: string, options: AllDocumentIdsMutationOptions): Observable + /** + * Deletes a document with the given document ID. + * Returns an observable that resolves to the deleted document. + * + * @param id - Document ID to delete + * @param options - Options for the mutation + */ + delete = Record>( + id: string, + options?: BaseMutationOptions, + ): Observable> + /** + * Deletes one or more documents matching the given query or document ID. + * Returns an observable that resolves to first deleted document. + * + * @param selection - An object with either an `id` or `query` key defining what to delete + * @param options - Options for the mutation + */ + delete = Record>( + selection: MutationSelection, + options: FirstDocumentMutationOptions, + ): Observable> + /** + * Deletes one or more documents matching the given query or document ID. + * Returns an observable that resolves to an array containing the deleted documents. + * + * @param selection - An object with either an `id` or `query` key defining what to delete + * @param options - Options for the mutation + */ + delete = Record>( + selection: MutationSelection, + options: AllDocumentsMutationOptions, + ): Observable[]> + /** + * Deletes one or more documents matching the given query or document ID. + * Returns an observable that resolves to a mutation result object containing the ID of the first deleted document. + * + * @param selection - An object with either an `id` or `query` key defining what to delete + * @param options - Options for the mutation + */ + delete( + selection: MutationSelection, + options: FirstDocumentIdMutationOptions, + ): Observable + /** + * Deletes one or more documents matching the given query or document ID. + * Returns an observable that resolves to a mutation result object containing the document IDs that were deleted. + * + * @param selection - An object with either an `id` or `query` key defining what to delete + * @param options - Options for the mutation + */ + delete( + selection: MutationSelection, + options: AllDocumentIdsMutationOptions, + ): Observable + /** + * Deletes one or more documents matching the given query or document ID. + * Returns an observable that resolves to first deleted document. + * + * @param selection - An object with either an `id` or `query` key defining what to delete + * @param options - Options for the mutation + */ + delete = Record>( + selection: MutationSelection, + options?: BaseMutationOptions, + ): Observable> + + /** + * Perform mutation operations against the configured dataset + * Returns an observable that resolves to the first mutated document. + * + * @param operations - Mutation operations to execute + * @param options - Mutation options + */ + mutate = Record>( + operations: Mutation[] | ObservablePatch | ObservableTransaction, + options: FirstDocumentMutationOptions, + ): Observable> + /** + * Perform mutation operations against the configured dataset. + * Returns an observable that resolves to an array of the mutated documents. + * + * @param operations - Mutation operations to execute + * @param options - Mutation options + */ + mutate = Record>( + operations: Mutation[] | ObservablePatch | ObservableTransaction, + options: AllDocumentsMutationOptions, + ): Observable[]> + /** + * Perform mutation operations against the configured dataset + * Returns an observable that resolves to a mutation result object containing the document ID of the first mutated document. + * + * @param operations - Mutation operations to execute + * @param options - Mutation options + */ + mutate = Record>( + operations: Mutation[] | ObservablePatch | ObservableTransaction, + options: FirstDocumentIdMutationOptions, + ): Observable + /** + * Perform mutation operations against the configured dataset + * Returns an observable that resolves to a mutation result object containing the mutated document IDs. + * + * @param operations - Mutation operations to execute + * @param options - Mutation options + */ + mutate = Record>( + operations: Mutation[] | ObservablePatch | ObservableTransaction, + options: AllDocumentIdsMutationOptions, + ): Observable + /** + * Perform mutation operations against the configured dataset + * Returns an observable that resolves to the first mutated document. + * + * @param operations - Mutation operations to execute + * @param options - Mutation options + */ + mutate = Record>( + operations: Mutation[] | ObservablePatch | ObservableTransaction, + options?: BaseMutationOptions, + ): Observable> + + /** + * Create a new buildable patch of operations to perform + * + * @param documentId - Document ID to patch + * @param operations - Optional object of patch operations to initialize the patch instance with + * @returns Patch instance - call `.commit()` to perform the operations defined + */ + patch(documentId: string, operations?: PatchOperations): ObservablePatch + + /** + * Create a new buildable patch of operations to perform + * + * @param documentIds - Array of document IDs to patch + * @param operations - Optional object of patch operations to initialize the patch instance with + * @returns Patch instance - call `.commit()` to perform the operations defined + */ + patch(documentIds: string[], operations?: PatchOperations): ObservablePatch + + /** + * Create a new buildable patch of operations to perform + * + * @param selection - An object with `query` and optional `params`, defining which document(s) to patch + * @param operations - Optional object of patch operations to initialize the patch instance with + * @returns Patch instance - call `.commit()` to perform the operations defined + */ + patch(selection: MutationSelection, operations?: PatchOperations): ObservablePatch + + /** + * Create a new buildable patch of operations to perform + * + * @param selection - Document ID, an array of document IDs, or an object with `query` and optional `params`, defining which document(s) to patch + * @param operations - Optional object of patch operations to initialize the patch instance with + * @returns Patch instance - call `.commit()` to perform the operations defined + */ + patch(selection: PatchSelection, operations?: PatchOperations): ObservablePatch + + /** + * Create a new transaction of mutations + * + * @param operations - Optional array of mutation operations to initialize the transaction instance with + */ + transaction = Record>( + operations?: Mutation[], + ): ObservableTransaction + + /** + * Perform action operations against the configured dataset + * + * @param operations - Action operation(s) to execute + * @param options - Action options + */ + action( + operations: Action | Action[], + options?: BaseActionOptions, + ): Observable + + /** + * Perform an HTTP request against the Sanity API + * + * @param options - Request options + */ + request(options: RawRequestOptions): Observable +} + +/** + * The interface implemented by the `SanityClient` class. + * When writing code that wants to take an instance of `SanityClient` as input it's better to use this type, + * as the `SanityClient` class has private properties and thus TypeScrict will consider the type incompatible + * in cases where you might have multiple `@sanity/client` instances in your node_modules. + * @public + */ +export interface SanityClientType extends SanityClientBase { + assets: AssetsClient + datasets: DatasetsClient + projects: ProjectsClient + users: UsersClient + + /** + * Observable version of the Sanity client, with the same configuration as the promise-based one + */ + observable: ObservableSanityClient + + /** + * Clone the client - returns a new instance + */ + clone(): SanityClient + + /** + * Clone the client with a new (partial) configuration. + * + * @param newConfig - New client configuration properties, shallowly merged with existing configuration + */ + withConfig(newConfig?: Partial): SanityClient + + /** + * Perform a GROQ-query against the configured dataset. + * + * @param query - GROQ-query to perform + */ + fetch< + R = Any, + Q extends QueryWithoutParams = QueryWithoutParams, + const G extends string = string, + >( + query: G, + params?: Q | QueryWithoutParams, + ): Promise> + /** + * Perform a GROQ-query against the configured dataset. + * + * @param query - GROQ-query to perform + * @param params - Optional query parameters + * @param options - Optional request options + */ + fetch< + R = Any, + Q extends QueryWithoutParams | QueryParams = QueryParams, + const G extends string = string, + >( + query: G, + params: Q extends QueryWithoutParams ? QueryWithoutParams : Q, + options?: FilteredResponseQueryOptions, + ): Promise> + /** + * Perform a GROQ-query against the configured dataset. + * + * @param query - GROQ-query to perform + * @param params - Optional query parameters + * @param options - Request options + */ + fetch< + R = Any, + Q extends QueryWithoutParams | QueryParams = QueryParams, + const G extends string = string, + >( + query: G, + params: Q extends QueryWithoutParams ? QueryWithoutParams : Q, + options: UnfilteredResponseQueryOptions, + ): Promise>> + /** + * Perform a GROQ-query against the configured dataset. + * + * @param query - GROQ-query to perform + * @param params - Optional query parameters + * @param options - Request options + */ + fetch< + R = Any, + Q extends QueryWithoutParams | QueryParams = QueryParams, + const G extends string = string, + >( + query: G, + params: Q extends QueryWithoutParams ? QueryWithoutParams : Q, + options: UnfilteredResponseWithoutQuery, + ): Promise>> + + /** + * Fetch a single document with the given ID. + * + * @param id - Document ID to fetch + * @param options - Request options + */ + getDocument = Record>( + id: string, + options?: {signal?: AbortSignal; tag?: string}, + ): Promise | undefined> + + /** + * Fetch multiple documents in one request. + * Should be used sparingly - performing a query is usually a better option. + * The order/position of documents is preserved based on the original array of IDs. + * If any of the documents are missing, they will be replaced by a `null` entry in the returned array + * + * @param ids - Document IDs to fetch + * @param options - Request options + */ + getDocuments = Record>( + ids: string[], + options?: {signal?: AbortSignal; tag?: string}, + ): Promise<(SanityDocument | null)[]> + + /** + * Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database. + * Returns a promise that resolves to the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + create = Record>( + document: SanityDocumentStub, + options: FirstDocumentMutationOptions, + ): Promise> + /** + * Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database. + * Returns a promise that resolves to an array containing the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + create = Record>( + document: SanityDocumentStub, + options: AllDocumentsMutationOptions, + ): Promise[]> + /** + * Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database. + * Returns a promise that resolves to a mutation result object containing the ID of the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + create = Record>( + document: SanityDocumentStub, + options: FirstDocumentIdMutationOptions, + ): Promise + /** + * Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database. + * Returns a promise that resolves to a mutation result object containing the ID of the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + create = Record>( + document: SanityDocumentStub, + options: AllDocumentIdsMutationOptions, + ): Promise + /** + * Create a document. Requires a `_type` property. If no `_id` is provided, it will be generated by the database. + * Returns a promise that resolves to the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + create = Record>( + document: SanityDocumentStub, + options?: BaseMutationOptions, + ): Promise> + + /** + * Create a document if no document with the same ID already exists. + * Returns a promise that resolves to the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + createIfNotExists = Record>( + document: IdentifiedSanityDocumentStub, + options: FirstDocumentMutationOptions, + ): Promise> + /** + * Create a document if no document with the same ID already exists. + * Returns a promise that resolves to an array containing the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + createIfNotExists = Record>( + document: IdentifiedSanityDocumentStub, + options: AllDocumentsMutationOptions, + ): Promise[]> + /** + * Create a document if no document with the same ID already exists. + * Returns a promise that resolves to a mutation result object containing the ID of the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + createIfNotExists = Record>( + document: IdentifiedSanityDocumentStub, + options: FirstDocumentIdMutationOptions, + ): Promise + /** + * Create a document if no document with the same ID already exists. + * Returns a promise that resolves to a mutation result object containing the ID of the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + createIfNotExists = Record>( + document: IdentifiedSanityDocumentStub, + options: AllDocumentIdsMutationOptions, + ): Promise + /** + * Create a document if no document with the same ID already exists. + * Returns a promise that resolves to the created document. + * + * @param document - Document to create + * @param options - Mutation options + */ + createIfNotExists = Record>( + document: IdentifiedSanityDocumentStub, + options?: BaseMutationOptions, + ): Promise> + + /** + * Create a document if it does not exist, or replace a document with the same document ID + * Returns a promise that resolves to the created document. + * + * @param document - Document to either create or replace + * @param options - Mutation options + */ + createOrReplace = Record>( + document: IdentifiedSanityDocumentStub, + options: FirstDocumentMutationOptions, + ): Promise> + /** + * Create a document if it does not exist, or replace a document with the same document ID + * Returns a promise that resolves to an array containing the created document. + * + * @param document - Document to either create or replace + * @param options - Mutation options + */ + createOrReplace = Record>( + document: IdentifiedSanityDocumentStub, + options: AllDocumentsMutationOptions, + ): Promise[]> + /** + * Create a document if it does not exist, or replace a document with the same document ID + * Returns a promise that resolves to a mutation result object containing the ID of the created document. + * + * @param document - Document to either create or replace + * @param options - Mutation options + */ + createOrReplace = Record>( + document: IdentifiedSanityDocumentStub, + options: FirstDocumentIdMutationOptions, + ): Promise + /** + * Create a document if it does not exist, or replace a document with the same document ID + * Returns a promise that resolves to a mutation result object containing the created document ID. + * + * @param document - Document to either create or replace + * @param options - Mutation options + */ + createOrReplace = Record>( + document: IdentifiedSanityDocumentStub, + options: AllDocumentIdsMutationOptions, + ): Promise + /** + * Create a document if it does not exist, or replace a document with the same document ID + * Returns a promise that resolves to the created document. + * + * @param document - Document to either create or replace + * @param options - Mutation options + */ + createOrReplace = Record>( + document: IdentifiedSanityDocumentStub, + options?: BaseMutationOptions, + ): Promise> + + /** + * Deletes a document with the given document ID. + * Returns a promise that resolves to the deleted document. + * + * @param id - Document ID to delete + * @param options - Options for the mutation + */ + delete = Record>( + id: string, + options: FirstDocumentMutationOptions, + ): Promise> + /** + * Deletes a document with the given document ID. + * Returns a promise that resolves to an array containing the deleted document. + * + * @param id - Document ID to delete + * @param options - Options for the mutation + */ + delete = Record>( + id: string, + options: AllDocumentsMutationOptions, + ): Promise[]> + /** + * Deletes a document with the given document ID. + * Returns a promise that resolves to a mutation result object containing the deleted document ID. + * + * @param id - Document ID to delete + * @param options - Options for the mutation + */ + delete(id: string, options: FirstDocumentIdMutationOptions): Promise + /** + * Deletes a document with the given document ID. + * Returns a promise that resolves to a mutation result object containing the deleted document ID. + * + * @param id - Document ID to delete + * @param options - Options for the mutation + */ + delete(id: string, options: AllDocumentIdsMutationOptions): Promise + /** + * Deletes a document with the given document ID. + * Returns a promise that resolves to the deleted document. + * + * @param id - Document ID to delete + * @param options - Options for the mutation + */ + delete = Record>( + id: string, + options?: BaseMutationOptions, + ): Promise> + /** + * Deletes one or more documents matching the given query or document ID. + * Returns a promise that resolves to first deleted document. + * + * @param selection - An object with either an `id` or `query` key defining what to delete + * @param options - Options for the mutation + */ + delete = Record>( + selection: MutationSelection, + options: FirstDocumentMutationOptions, + ): Promise> + /** + * Deletes one or more documents matching the given query or document ID. + * Returns a promise that resolves to an array containing the deleted documents. + * + * @param selection - An object with either an `id` or `query` key defining what to delete + * @param options - Options for the mutation + */ + delete = Record>( + selection: MutationSelection, + options: AllDocumentsMutationOptions, + ): Promise[]> + /** + * Deletes one or more documents matching the given query or document ID. + * Returns a promise that resolves to a mutation result object containing the ID of the first deleted document. + * + * @param selection - An object with either an `id` or `query` key defining what to delete + * @param options - Options for the mutation + */ + delete( + selection: MutationSelection, + options: FirstDocumentIdMutationOptions, + ): Promise + /** + * Deletes one or more documents matching the given query or document ID. + * Returns a promise that resolves to a mutation result object containing the document IDs that were deleted. + * + * @param selection - An object with either an `id` or `query` key defining what to delete + * @param options - Options for the mutation + */ + delete( + selection: MutationSelection, + options: AllDocumentIdsMutationOptions, + ): Promise + /** + * Deletes one or more documents matching the given query or document ID. + * Returns a promise that resolves to first deleted document. + * + * @param selection - An object with either an `id` or `query` key defining what to delete + * @param options - Options for the mutation + */ + delete = Record>( + selection: MutationSelection, + options?: BaseMutationOptions, + ): Promise> + + /** + * Perform mutation operations against the configured dataset + * Returns a promise that resolves to the first mutated document. + * + * @param operations - Mutation operations to execute + * @param options - Mutation options + */ + mutate = Record>( + operations: Mutation[] | Patch | Transaction, + options: FirstDocumentMutationOptions, + ): Promise> + /** + * Perform mutation operations against the configured dataset. + * Returns a promise that resolves to an array of the mutated documents. + * + * @param operations - Mutation operations to execute + * @param options - Mutation options + */ + mutate = Record>( + operations: Mutation[] | Patch | Transaction, + options: AllDocumentsMutationOptions, + ): Promise[]> + /** + * Perform mutation operations against the configured dataset + * Returns a promise that resolves to a mutation result object containing the document ID of the first mutated document. + * + * @param operations - Mutation operations to execute + * @param options - Mutation options + */ + mutate = Record>( + operations: Mutation[] | Patch | Transaction, + options: FirstDocumentIdMutationOptions, + ): Promise + /** + * Perform mutation operations against the configured dataset + * Returns a promise that resolves to a mutation result object containing the mutated document IDs. + * + * @param operations - Mutation operations to execute + * @param options - Mutation options + */ + mutate>( + operations: Mutation[] | Patch | Transaction, + options: AllDocumentIdsMutationOptions, + ): Promise + /** + * Perform mutation operations against the configured dataset + * Returns a promise that resolves to the first mutated document. + * + * @param operations - Mutation operations to execute + * @param options - Mutation options + */ + mutate = Record>( + operations: Mutation[] | Patch | Transaction, + options?: BaseMutationOptions, + ): Promise> + + /** + * Create a new buildable patch of operations to perform + * + * @param documentId - Document ID to patch + * @param operations - Optional object of patch operations to initialize the patch instance with + * @returns Patch instance - call `.commit()` to perform the operations defined + */ + patch(documentId: string, operations?: PatchOperations): Patch + + /** + * Create a new buildable patch of operations to perform + * + * @param documentIds - Array of document IDs to patch + * @param operations - Optional object of patch operations to initialize the patch instance with + * @returns Patch instance - call `.commit()` to perform the operations defined + */ + patch(documentIds: string[], operations?: PatchOperations): Patch + + /** + * Create a new buildable patch of operations to perform + * + * @param selection - An object with `query` and optional `params`, defining which document(s) to patch + * @param operations - Optional object of patch operations to initialize the patch instance with + * @returns Patch instance - call `.commit()` to perform the operations defined + */ + patch(selection: MutationSelection, operations?: PatchOperations): Patch + + /** + * Create a new buildable patch of operations to perform + * + * @param selection - Document ID, an array of document IDs, or an object with `query` and optional `params`, defining which document(s) to patch + * @param operations - Optional object of patch operations to initialize the patch instance with + * @returns Patch instance - call `.commit()` to perform the operations defined + */ + patch(selection: PatchSelection, operations?: PatchOperations): Patch + + /** + * Create a new transaction of mutations + * + * @param operations - Optional array of mutation operations to initialize the transaction instance with + */ + transaction = Record>( + operations?: Mutation[], + ): Transaction + + /** + * Perform action operations against the configured dataset + * Returns a promise that resolves to the transaction result + * + * @param operations - Action operation(s) to execute + * @param options - Action options + */ + action( + operations: Action | Action[], + options?: BaseActionOptions, + ): Promise + + /** + * Perform a request against the Sanity API + * NOTE: Only use this for Sanity API endpoints, not for your own APIs! + * + * @param options - Request options + * @returns Promise resolving to the response body + */ + request(options: RawRequestOptions): Promise + + /** + * Perform an HTTP request a `/data` sub-endpoint + * NOTE: Considered internal, thus marked as deprecated. Use `request` instead. + * + * @deprecated - Use `request()` or your own HTTP library instead + * @param endpoint - Endpoint to hit (mutate, query etc) + * @param body - Request body + * @param options - Request options + * @internal + */ + dataRequest(endpoint: string, body: unknown, options?: BaseMutationOptions): Promise +} diff --git a/test/client.test-d.ts b/test/client.test-d.ts index 462e5b534..f009f04c9 100644 --- a/test/client.test-d.ts +++ b/test/client.test-d.ts @@ -18,6 +18,7 @@ import { type ObservablePatch, type ObservableTransaction, type BaseActionOptions, + type SanityClientType, } from '@sanity/client' import {lastValueFrom} from 'rxjs' import {describe, expectTypeOf, test} from 'vitest' @@ -915,3 +916,58 @@ describe('client document methods', () => { expectTypeOf(obsTypedRequest).toMatchTypeOf() }) }) + +describe('client interfaces for library users', () => { + test('SanityClientType', () => { + async function defineLive(client: SanityClientType) { + expectTypeOf(await client.fetch('*')).toMatchTypeOf() + expectTypeOf(await lastValueFrom(client.observable.fetch('*'))).toMatchTypeOf() + expectTypeOf(await client.fetch('*', undefined)).toMatchTypeOf() + expectTypeOf( + await lastValueFrom(client.observable.fetch('*', undefined)), + ).toMatchTypeOf() + expectTypeOf(await client.fetch('*', {})).toMatchTypeOf() + expectTypeOf(await lastValueFrom(client.observable.fetch('*', {}))).toMatchTypeOf() + expectTypeOf(await client.fetch('*[_type == $type]', {type: 'post'})).toMatchTypeOf() + expectTypeOf( + await lastValueFrom(client.observable.fetch('*[_type == $type]', {type: 'post'})), + ).toMatchTypeOf() + expectTypeOf(await client.fetch('*', undefined, {filterResponse: false})).toMatchTypeOf< + RawQueryResponse + >() + expectTypeOf( + await lastValueFrom(client.observable.fetch('*', undefined, {filterResponse: false})), + ).toMatchTypeOf>() + expectTypeOf( + await client.fetch('*', {} satisfies QueryParams, { + filterResponse: false, + }), + ).toMatchTypeOf>() + expectTypeOf( + await lastValueFrom( + client.observable.fetch( + '*', + {} satisfies QueryParams, + { + filterResponse: false, + }, + ), + ), + ).toMatchTypeOf>() + expectTypeOf( + await client.fetch('*[_type == $type]', {type: 'post'}, {filterResponse: false}), + ).toMatchTypeOf>() + expectTypeOf( + await lastValueFrom( + client.observable.fetch('*[_type == $type]', {type: 'post'}, {filterResponse: false}), + ), + ).toMatchTypeOf>() + } + const client = createClient({ + projectId: 'my-project-id', + dataset: 'my-dataset', + apiVersion: '2021-03-25', + }) + defineLive(client) + }) +}) From 93343311878008b56180349b68418fe1a05f3f58 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 2 May 2025 12:13:03 +0200 Subject: [PATCH 06/13] chore: fix linter --- test/client.test-d.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/client.test-d.ts b/test/client.test-d.ts index f009f04c9..cd110e9f4 100644 --- a/test/client.test-d.ts +++ b/test/client.test-d.ts @@ -1,24 +1,24 @@ import { + type Action, + type BaseActionOptions, + type ClientConfig, type ContentSourceMap, createClient, + type InitializedClientConfig, + type MultipleMutationResult, + type Mutation, + type MutationSelection, + type ObservablePatch, + type ObservableTransaction, + type Patch, type QueryOptions, type QueryParams, type QueryWithoutParams, type RawQueryResponse, - type ClientConfig, - type InitializedClientConfig, + type SanityClientType, type SanityDocument, type SingleMutationResult, - type MultipleMutationResult, - type MutationSelection, - type Mutation, - type Action, - type Patch, type Transaction, - type ObservablePatch, - type ObservableTransaction, - type BaseActionOptions, - type SanityClientType, } from '@sanity/client' import {lastValueFrom} from 'rxjs' import {describe, expectTypeOf, test} from 'vitest' From 7e6dded13e621857088db6d6c05107645eead410 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 2 May 2025 12:31:26 +0200 Subject: [PATCH 07/13] chore: cleanup comments that aren't docs --- src/SanityClient.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/SanityClient.ts b/src/SanityClient.ts index 937ef0444..262ba73d6 100644 --- a/src/SanityClient.ts +++ b/src/SanityClient.ts @@ -66,15 +66,9 @@ export class ObservableSanityClient implements ObservableSanityClientType { projects: ObservableProjectsClient users: ObservableUsersClient - /** - * Private properties - */ #clientConfig: InitializedClientConfig #httpRequest: HttpRequest - /** - * Instance properties - */ listen = _listen constructor(httpRequest: HttpRequest, config: ClientConfig = defaultConfig) { @@ -739,15 +733,9 @@ export class SanityClient implements SanityClientType { */ observable: ObservableSanityClient - /** - * Private properties - */ #clientConfig: InitializedClientConfig #httpRequest: HttpRequest - /** - * Instance properties - */ listen = _listen constructor(httpRequest: HttpRequest, config: ClientConfig = defaultConfig) { From a2d9ea56cb406e3039a410c29c090dfff0ba186e Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 2 May 2025 13:19:30 +0200 Subject: [PATCH 08/13] chore: widen types --- src/SanityClient.ts | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/SanityClient.ts b/src/SanityClient.ts index 262ba73d6..177604a4f 100644 --- a/src/SanityClient.ts +++ b/src/SanityClient.ts @@ -1426,15 +1426,6 @@ interface SanityClientBase { live: LiveClient listen: typeof _listen - /** - * Returns the current client configuration - */ - config(): InitializedClientConfig - /** - * Reconfigure the client. Note that this _mutates_ the current client. - */ - config(newConfig?: Partial): this - /** * Get a Sanity API URL for the URI provided * @@ -1468,14 +1459,23 @@ export interface ObservableSanityClientType extends SanityClientBase { /** * Clone the client - returns a new instance */ - clone(): ObservableSanityClient + clone(): ObservableSanityClientType + + /** + * Returns the current client configuration + */ + config(): InitializedClientConfig + /** + * Reconfigure the client. Note that this _mutates_ the current client. + */ + config(newConfig?: Partial): ObservableSanityClientType /** * Clone the client with a new (partial) configuration. * * @param newConfig - New client configuration properties, shallowly merged with existing configuration */ - withConfig(newConfig?: Partial): ObservableSanityClient + withConfig(newConfig?: Partial): ObservableSanityClientType /** * Perform a GROQ-query against the configured dataset. @@ -1973,19 +1973,28 @@ export interface SanityClientType extends SanityClientBase { /** * Observable version of the Sanity client, with the same configuration as the promise-based one */ - observable: ObservableSanityClient + observable: ObservableSanityClientType /** * Clone the client - returns a new instance */ - clone(): SanityClient + clone(): SanityClientType + + /** + * Returns the current client configuration + */ + config(): InitializedClientConfig + /** + * Reconfigure the client. Note that this _mutates_ the current client. + */ + config(newConfig?: Partial): SanityClientType /** * Clone the client with a new (partial) configuration. * * @param newConfig - New client configuration properties, shallowly merged with existing configuration */ - withConfig(newConfig?: Partial): SanityClient + withConfig(newConfig?: Partial): SanityClientType /** * Perform a GROQ-query against the configured dataset. From 2296f6b0f0af64f8f394b35c5dddb806544566d9 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 2 May 2025 13:28:44 +0200 Subject: [PATCH 09/13] chore: move AssetsClient to interfaces --- src/SanityClient.ts | 44 +++++++++++++------ src/assets/AssetsClient.ts | 87 +++++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 14 deletions(-) diff --git a/src/SanityClient.ts b/src/SanityClient.ts index 177604a4f..db80fa816 100644 --- a/src/SanityClient.ts +++ b/src/SanityClient.ts @@ -1,14 +1,29 @@ import {lastValueFrom, Observable} from 'rxjs' -import {AssetsClient, ObservableAssetsClient} from './assets/AssetsClient' +import { + AssetsClient, + type AssetsClientType, + ObservableAssetsClient, + type ObservableAssetsClientType, +} from './assets/AssetsClient' import {defaultConfig, initConfig} from './config' import * as dataMethods from './data/dataMethods' import {_listen} from './data/listen' import {LiveClient} from './data/live' import {ObservablePatch, Patch} from './data/patch' import {ObservableTransaction, Transaction} from './data/transaction' -import {DatasetsClient, ObservableDatasetsClient} from './datasets/DatasetsClient' -import {ObservableProjectsClient, ProjectsClient} from './projects/ProjectsClient' +import { + DatasetsClient, + DatasetsClient as DatasetsClientType, + ObservableDatasetsClient, + ObservableDatasetsClient as ObservableDatasetsClientType, +} from './datasets/DatasetsClient' +import { + ObservableProjectsClient, + ObservableProjectsClient as ObservableProjectsClientType, + ProjectsClient, + ProjectsClient as ProjectsClientType, +} from './projects/ProjectsClient' import type { Action, AllDocumentIdsMutationOptions, @@ -43,7 +58,12 @@ import type { UnfilteredResponseQueryOptions, UnfilteredResponseWithoutQuery, } from './types' -import {ObservableUsersClient, UsersClient} from './users/UsersClient' +import { + ObservableUsersClient, + ObservableUsersClient as ObservableUsersClientType, + UsersClient, + UsersClient as UsersClientType, +} from './users/UsersClient' export type { _listen, @@ -1451,10 +1471,10 @@ interface SanityClientBase { * @public */ export interface ObservableSanityClientType extends SanityClientBase { - assets: ObservableAssetsClient - datasets: ObservableDatasetsClient - projects: ObservableProjectsClient - users: ObservableUsersClient + assets: ObservableAssetsClientType + datasets: ObservableDatasetsClientType + projects: ObservableProjectsClientType + users: ObservableUsersClientType /** * Clone the client - returns a new instance @@ -1965,10 +1985,10 @@ export interface ObservableSanityClientType extends SanityClientBase { * @public */ export interface SanityClientType extends SanityClientBase { - assets: AssetsClient - datasets: DatasetsClient - projects: ProjectsClient - users: UsersClient + assets: AssetsClientType + datasets: DatasetsClientType + projects: ProjectsClientType + users: UsersClientType /** * Observable version of the Sanity client, with the same configuration as the promise-based one diff --git a/src/assets/AssetsClient.ts b/src/assets/AssetsClient.ts index 32a56883e..b75c06219 100644 --- a/src/assets/AssetsClient.ts +++ b/src/assets/AssetsClient.ts @@ -17,7 +17,90 @@ import type { import * as validators from '../validators' /** @internal */ -export class ObservableAssetsClient { +export interface ObservableAssetsClientType { + /** + * Uploads a file asset to the configured dataset + * + * @param assetType - Asset type (file) + * @param body - Asset content - can be a browser File instance, a Blob, a Node.js Buffer instance or a Node.js ReadableStream. + * @param options - Options to use for the upload + */ + upload( + assetType: 'file', + body: UploadBody, + options?: UploadClientConfig, + ): Observable> + + /** + * Uploads an image asset to the configured dataset + * + * @param assetType - Asset type (image) + * @param body - Asset content - can be a browser File instance, a Blob, a Node.js Buffer instance or a Node.js ReadableStream. + * @param options - Options to use for the upload + */ + upload( + assetType: 'image', + body: UploadBody, + options?: UploadClientConfig, + ): Observable> + + /** + * Uploads a file or an image asset to the configured dataset + * + * @param assetType - Asset type (file/image) + * @param body - Asset content - can be a browser File instance, a Blob, a Node.js Buffer instance or a Node.js ReadableStream. + * @param options - Options to use for the upload + */ + upload( + assetType: 'file' | 'image', + body: UploadBody, + options?: UploadClientConfig, + ): Observable> +} + +/** @internal */ +export interface AssetsClientType { + /** + * Uploads a file asset to the configured dataset + * + * @param assetType - Asset type (file) + * @param body - Asset content - can be a browser File instance, a Blob, a Node.js Buffer instance or a Node.js ReadableStream. + * @param options - Options to use for the upload + */ + upload( + assetType: 'file', + body: UploadBody, + options?: UploadClientConfig, + ): Promise + + /** + * Uploads an image asset to the configured dataset + * + * @param assetType - Asset type (image) + * @param body - Asset content - can be a browser File instance, a Blob, a Node.js Buffer instance or a Node.js ReadableStream. + * @param options - Options to use for the upload + */ + upload( + assetType: 'image', + body: UploadBody, + options?: UploadClientConfig, + ): Promise + + /** + * Uploads a file or an image asset to the configured dataset + * + * @param assetType - Asset type (file/image) + * @param body - Asset content - can be a browser File instance, a Blob, a Node.js Buffer instance or a Node.js ReadableStream. + * @param options - Options to use for the upload + */ + upload( + assetType: 'file' | 'image', + body: UploadBody, + options?: UploadClientConfig, + ): Promise +} + +export class ObservableAssetsClient implements ObservableAssetsClientType { #client: ObservableSanityClient #httpRequest: HttpRequest constructor(client: ObservableSanityClient, httpRequest: HttpRequest) { @@ -72,7 +155,7 @@ export class ObservableAssetsClient { } /** @internal */ -export class AssetsClient { +export class AssetsClient implements AssetsClientType { #client: SanityClient #httpRequest: HttpRequest constructor(client: SanityClient, httpRequest: HttpRequest) { From 3de6558a88fc7eb88be53956ab082e313a3bfec2 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 2 May 2025 13:33:14 +0200 Subject: [PATCH 10/13] refactor: handle the other private classes --- src/SanityClient.ts | 12 +++---- src/datasets/DatasetsClient.ts | 66 ++++++++++++++++++++++++++++++++-- src/projects/ProjectsClient.ts | 46 ++++++++++++++++++++++-- src/users/UsersClient.ts | 26 ++++++++++++-- 4 files changed, 138 insertions(+), 12 deletions(-) diff --git a/src/SanityClient.ts b/src/SanityClient.ts index db80fa816..ac619c3ac 100644 --- a/src/SanityClient.ts +++ b/src/SanityClient.ts @@ -14,15 +14,15 @@ import {ObservablePatch, Patch} from './data/patch' import {ObservableTransaction, Transaction} from './data/transaction' import { DatasetsClient, - DatasetsClient as DatasetsClientType, + type DatasetsClientType, ObservableDatasetsClient, - ObservableDatasetsClient as ObservableDatasetsClientType, + type ObservableDatasetsClientType, } from './datasets/DatasetsClient' import { ObservableProjectsClient, - ObservableProjectsClient as ObservableProjectsClientType, + type ObservableProjectsClientType, ProjectsClient, - ProjectsClient as ProjectsClientType, + type ProjectsClientType, } from './projects/ProjectsClient' import type { Action, @@ -60,9 +60,9 @@ import type { } from './types' import { ObservableUsersClient, - ObservableUsersClient as ObservableUsersClientType, + type ObservableUsersClientType, UsersClient, - UsersClient as UsersClientType, + type UsersClientType, } from './users/UsersClient' export type { diff --git a/src/datasets/DatasetsClient.ts b/src/datasets/DatasetsClient.ts index f9ddc9b96..4313d3fea 100644 --- a/src/datasets/DatasetsClient.ts +++ b/src/datasets/DatasetsClient.ts @@ -6,7 +6,69 @@ import type {DatasetAclMode, DatasetResponse, DatasetsResponse, HttpRequest} fro import * as validate from '../validators' /** @internal */ -export class ObservableDatasetsClient { +export interface ObservableDatasetsClientType { + /** + * Create a new dataset with the given name + * + * @param name - Name of the dataset to create + * @param options - Options for the dataset + */ + create(name: string, options?: {aclMode?: DatasetAclMode}): Observable + + /** + * Edit a dataset with the given name + * + * @param name - Name of the dataset to edit + * @param options - New options for the dataset + */ + edit(name: string, options?: {aclMode?: DatasetAclMode}): Observable + + /** + * Delete a dataset with the given name + * + * @param name - Name of the dataset to delete + */ + delete(name: string): Observable<{deleted: true}> + + /** + * Fetch a list of datasets for the configured project + */ + list(): Observable +} + +/** @internal */ +export interface DatasetsClientType { + /** + * Create a new dataset with the given name + * + * @param name - Name of the dataset to create + * @param options - Options for the dataset + */ + create(name: string, options?: {aclMode?: DatasetAclMode}): Promise + + /** + * Edit a dataset with the given name + * + * @param name - Name of the dataset to edit + * @param options - New options for the dataset + */ + edit(name: string, options?: {aclMode?: DatasetAclMode}): Promise + + /** + * Delete a dataset with the given name + * + * @param name - Name of the dataset to delete + */ + delete(name: string): Promise<{deleted: true}> + + /** + * Fetch a list of datasets for the configured project + */ + list(): Promise +} + +/** @internal */ +export class ObservableDatasetsClient implements ObservableDatasetsClientType { #client: ObservableSanityClient #httpRequest: HttpRequest constructor(client: ObservableSanityClient, httpRequest: HttpRequest) { @@ -55,7 +117,7 @@ export class ObservableDatasetsClient { } /** @internal */ -export class DatasetsClient { +export class DatasetsClient implements DatasetsClientType { #client: SanityClient #httpRequest: HttpRequest constructor(client: SanityClient, httpRequest: HttpRequest) { diff --git a/src/projects/ProjectsClient.ts b/src/projects/ProjectsClient.ts index 094e215ed..ee40106d6 100644 --- a/src/projects/ProjectsClient.ts +++ b/src/projects/ProjectsClient.ts @@ -6,7 +6,49 @@ import type {HttpRequest, SanityProject} from '../types' import * as validate from '../validators' /** @internal */ -export class ObservableProjectsClient { +export interface ObservableProjectsClientType { + /** + * Fetch a list of projects the authenticated user has access to. + * + * @param options - Options for the list request + * @param options.includeMembers - Whether to include members in the response (default: true) + */ + list(options?: {includeMembers?: true}): Observable + list(options?: {includeMembers?: false}): Observable[]> + list(options?: { + includeMembers?: boolean + }): Observable[]> + + /** + * Fetch a project by project ID + * + * @param projectId - ID of the project to fetch + */ + getById(projectId: string): Observable +} + +/** @internal */ +export interface ProjectsClientType { + /** + * Fetch a list of projects the authenticated user has access to. + * + * @param options - Options for the list request + * @param options.includeMembers - Whether to include members in the response (default: true) + */ + list(options?: {includeMembers?: true}): Promise + list(options?: {includeMembers?: false}): Promise[]> + list(options?: {includeMembers?: boolean}): Promise + + /** + * Fetch a project by project ID + * + * @param projectId - ID of the project to fetch + */ + getById(projectId: string): Promise +} + +/** @internal */ +export class ObservableProjectsClient implements ObservableProjectsClientType { #client: ObservableSanityClient #httpRequest: HttpRequest constructor(client: ObservableSanityClient, httpRequest: HttpRequest) { @@ -42,7 +84,7 @@ export class ObservableProjectsClient { } /** @internal */ -export class ProjectsClient { +export class ProjectsClient implements ProjectsClientType { #client: SanityClient #httpRequest: HttpRequest constructor(client: SanityClient, httpRequest: HttpRequest) { diff --git a/src/users/UsersClient.ts b/src/users/UsersClient.ts index 7bd1eb5fe..357ccb968 100644 --- a/src/users/UsersClient.ts +++ b/src/users/UsersClient.ts @@ -4,8 +4,30 @@ import {_request} from '../data/dataMethods' import type {ObservableSanityClient, SanityClient} from '../SanityClient' import type {CurrentSanityUser, HttpRequest, SanityUser} from '../types' +/** @internal */ +export interface ObservableUsersClientType { + /** + * Fetch a user by user ID + * + * @param id - User ID of the user to fetch. If `me` is provided, a minimal response including the users role is returned. + */ + getById( + id: T, + ): Observable +} + +/** @internal */ +export interface UsersClientType { + /** + * Fetch a user by user ID + * + * @param id - User ID of the user to fetch. If `me` is provided, a minimal response including the users role is returned. + */ + getById(id: T): Promise +} + /** @public */ -export class ObservableUsersClient { +export class ObservableUsersClient implements ObservableUsersClientType { #client: ObservableSanityClient #httpRequest: HttpRequest constructor(client: ObservableSanityClient, httpRequest: HttpRequest) { @@ -30,7 +52,7 @@ export class ObservableUsersClient { } /** @public */ -export class UsersClient { +export class UsersClient implements UsersClientType { #client: SanityClient #httpRequest: HttpRequest constructor(client: SanityClient, httpRequest: HttpRequest) { From 382ae2c6c5676328119cc724434e33f5b1084c7d Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 2 May 2025 13:34:58 +0200 Subject: [PATCH 11/13] chore: refactor --- src/assets/AssetsClient.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/assets/AssetsClient.ts b/src/assets/AssetsClient.ts index b75c06219..57e73c771 100644 --- a/src/assets/AssetsClient.ts +++ b/src/assets/AssetsClient.ts @@ -100,6 +100,7 @@ export interface AssetsClientType { ): Promise } +/** @internal */ export class ObservableAssetsClient implements ObservableAssetsClientType { #client: ObservableSanityClient #httpRequest: HttpRequest From 7771e2a51a34e7c43ab431b843191f6849310748 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 2 May 2025 13:39:27 +0200 Subject: [PATCH 12/13] chore: test with latest client --- test/client.test-d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/client.test-d.ts b/test/client.test-d.ts index cd110e9f4..51554031b 100644 --- a/test/client.test-d.ts +++ b/test/client.test-d.ts @@ -22,6 +22,7 @@ import { } from '@sanity/client' import {lastValueFrom} from 'rxjs' import {describe, expectTypeOf, test} from 'vitest' +import {createClient as createLatestClient} from '@sanity/client-latest' describe('client.fetch', () => { const client = createClient({}) @@ -963,7 +964,7 @@ describe('client interfaces for library users', () => { ), ).toMatchTypeOf>() } - const client = createClient({ + const client = createLatestClient({ projectId: 'my-project-id', dataset: 'my-dataset', apiVersion: '2021-03-25', From 9be0a0ee197c7acdc27a1d30744ee77992ba2b34 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 2 May 2025 13:52:37 +0200 Subject: [PATCH 13/13] chore: hmmmm --- src/SanityClient.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SanityClient.ts b/src/SanityClient.ts index ac619c3ac..fa1c91e5c 100644 --- a/src/SanityClient.ts +++ b/src/SanityClient.ts @@ -1488,14 +1488,14 @@ export interface ObservableSanityClientType extends SanityClientBase { /** * Reconfigure the client. Note that this _mutates_ the current client. */ - config(newConfig?: Partial): ObservableSanityClientType + config(newConfig?: Partial): this /** * Clone the client with a new (partial) configuration. * * @param newConfig - New client configuration properties, shallowly merged with existing configuration */ - withConfig(newConfig?: Partial): ObservableSanityClientType + withConfig(newConfig?: Partial): this /** * Perform a GROQ-query against the configured dataset. @@ -2007,14 +2007,14 @@ export interface SanityClientType extends SanityClientBase { /** * Reconfigure the client. Note that this _mutates_ the current client. */ - config(newConfig?: Partial): SanityClientType + config(newConfig?: Partial): this /** * Clone the client with a new (partial) configuration. * * @param newConfig - New client configuration properties, shallowly merged with existing configuration */ - withConfig(newConfig?: Partial): SanityClientType + withConfig(newConfig?: Partial): this /** * Perform a GROQ-query against the configured dataset.