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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/ObservableObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,11 @@ const proxyHandler: ProxyHandler<any> = {
}
}

if (p === 'constructor') {
const ctor = peekInternal(node)?.constructor;
return typeof ctor === 'function' ? ctor : Object;
}

let value = peekInternal(node, /*activateRecursive*/ p === 'get' || p === 'peek');

// Trying to get an iterator if the raw value is a primitive should return undefined.
Expand Down Expand Up @@ -639,7 +644,20 @@ const proxyHandler: ProxyHandler<any> = {
},
has(node: NodeInfo, prop: string) {
const value = getNodeValue(node);
return Reflect.has(value, prop);

// Short-circuit for the inputs that make Reflect.has error.
if (value === undefined || value === null) {
return false;
}

// Functions behave like objects here, so let them flow through.
if (typeof value === 'object' || typeof value === 'function') {
return Reflect.has(value, prop);
}

// For primitives (number, string, boolean, bigint, symbol) report “no key”.
// That keeps inspection code happy without pretending properties exist.
return false;
},
apply(target, thisArg, argArray) {
// If it's a function call it as a function
Expand Down
3 changes: 2 additions & 1 deletion src/observableTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export type RemoveObservables<T> =
: T;

interface ObservableArray<T, U>
extends ObservablePrimitive<T>,
extends
ObservablePrimitive<T>,
Pick<Array<Observable<U>>, ArrayOverrideFnNames>,
Omit<RemoveIndex<Array<U>>, ArrayOverrideFnNames> {}

Expand Down
6 changes: 4 additions & 2 deletions src/sync-plugins/crud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ export interface CrudErrorParams extends Omit<SyncedErrorParams, 'source'> {

export type CrudOnErrorFn = (error: Error, params: CrudErrorParams) => void;

export interface SyncedCrudPropsBase<TRemote extends object, TLocal = TRemote>
extends Omit<SyncedOptions<TRemote, TLocal>, 'get' | 'set' | 'initial' | 'subscribe' | 'waitForSet' | 'onError'> {
export interface SyncedCrudPropsBase<TRemote extends object, TLocal = TRemote> extends Omit<
SyncedOptions<TRemote, TLocal>,
'get' | 'set' | 'initial' | 'subscribe' | 'waitForSet' | 'onError'
> {
create?(input: TRemote, params: SyncedSetParams<TRemote>): Promise<CrudResult<TRemote> | null | undefined | void>;
update?(
input: Partial<TRemote>,
Expand Down
6 changes: 4 additions & 2 deletions src/sync-plugins/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ export interface SyncedFetchOnSavedParams<TRemote, TLocal = TRemote> {
props: SyncedFetchProps<TRemote, TLocal>;
}

export interface SyncedFetchProps<TRemote, TLocal = TRemote>
extends Omit<SyncedOptions<TRemote, TLocal>, 'get' | 'set'> {
export interface SyncedFetchProps<TRemote, TLocal = TRemote> extends Omit<
SyncedOptions<TRemote, TLocal>,
'get' | 'set'
> {
get: Selector<string>;
set?: Selector<string>;
getInit?: RequestInit;
Expand Down
3 changes: 2 additions & 1 deletion src/sync-plugins/firebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ import { clone } from '../globals';
// Should it have mode merge by default?

export interface SyncedFirebaseProps<TRemote extends object, TLocal, TAs extends CrudAsOption = 'value'>
extends Omit<SyncedCrudPropsMany<TRemote, TLocal, TAs>, 'list' | 'retry'>,
extends
Omit<SyncedCrudPropsMany<TRemote, TLocal, TAs>, 'list' | 'retry'>,
Omit<SyncedCrudPropsBase<TRemote, TLocal>, 'onError'> {
refPath: (uid: string | undefined) => string;
query?: (ref: DatabaseReference) => DatabaseReference | Query;
Expand Down
28 changes: 17 additions & 11 deletions src/sync-plugins/keel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,10 @@ interface PageInfo {
totalCount: number;
}

interface SyncedKeelPropsManyBase<TRemote extends { id: string }, TLocal, AOption extends CrudAsOption>
extends Omit<SyncedCrudPropsMany<TRemote, TLocal, AOption>, 'list'> {
interface SyncedKeelPropsManyBase<TRemote extends { id: string }, TLocal, AOption extends CrudAsOption> extends Omit<
SyncedCrudPropsMany<TRemote, TLocal, AOption>,
'list'
> {
first?: number;
get?: never;
}
Expand All @@ -103,8 +105,11 @@ interface SyncedKeelPropsManyWhere<
>;
where?: Where | (() => Where);
}
interface SyncedKeelPropsManyNoWhere<TRemote extends { id: string }, TLocal, AOption extends CrudAsOption>
extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
interface SyncedKeelPropsManyNoWhere<
TRemote extends { id: string },
TLocal,
AOption extends CrudAsOption,
> extends SyncedKeelPropsManyBase<TRemote, TLocal, AOption> {
list?: (params: KeelListParams<{}>) => Promise<
CrudResult<
APIResult<{
Expand All @@ -127,8 +132,10 @@ type SyncedKeelPropsMany<
? SyncedKeelPropsManyWhere<TRemote, TLocal, AOption, Where>
: SyncedKeelPropsManyNoWhere<TRemote, TLocal, AOption>;

interface SyncedKeelPropsSingle<TRemote extends { id: string }, TLocal>
extends Omit<SyncedCrudPropsSingle<TRemote, TLocal>, 'get'> {
interface SyncedKeelPropsSingle<TRemote extends { id: string }, TLocal> extends Omit<
SyncedCrudPropsSingle<TRemote, TLocal>,
'get'
> {
get?: (params: KeelGetParams) => Promise<APIResult<TRemote>>;

first?: never;
Expand All @@ -141,11 +148,10 @@ export interface KeelErrorParams extends CrudErrorParams {
action: string;
}

export interface SyncedKeelPropsBase<TRemote extends { id: string }, TLocal = TRemote>
extends Omit<
SyncedCrudPropsBase<TRemote, TLocal>,
'create' | 'update' | 'delete' | 'updatePartial' | 'fieldUpdatedAt' | 'fieldCreatedAt' | 'onError'
> {
export interface SyncedKeelPropsBase<TRemote extends { id: string }, TLocal = TRemote> extends Omit<
SyncedCrudPropsBase<TRemote, TLocal>,
'create' | 'update' | 'delete' | 'updatePartial' | 'fieldUpdatedAt' | 'fieldCreatedAt' | 'onError'
> {
client?: KeelClient;
create?: (i: NoInfer<Partial<TRemote>>) => Promise<APIResult<NoInfer<TRemote>>>;
update?: (params: { where: any; values?: Partial<NoInfer<TRemote>> }) => Promise<APIResult<TRemote>>;
Expand Down
13 changes: 6 additions & 7 deletions src/sync-plugins/supabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,10 @@ export type SyncedSupabaseConfig<TRemote extends { id: string | number }, TLocal
'create' | 'update' | 'delete'
>;

export interface SyncedSupabaseConfiguration
extends Omit<
SyncedSupabaseConfig<{ id: string | number }, { id: string | number }>,
'persist' | keyof SyncedOptions
> {
export interface SyncedSupabaseConfiguration extends Omit<
SyncedSupabaseConfig<{ id: string | number }, { id: string | number }>,
'persist' | keyof SyncedOptions
> {
persist?: SyncedOptionsGlobal;
enabled?: Observable<boolean>;
as?: Exclude<CrudAsOption, 'value'>;
Expand All @@ -87,8 +86,8 @@ interface SyncedSupabaseProps<
TOption extends CrudAsOption = 'object',
TRemote extends SupabaseRowOf<Client, Collection, SchemaName> = SupabaseRowOf<Client, Collection, SchemaName>,
TLocal = TRemote,
> extends SyncedSupabaseConfig<TRemote, TLocal>,
Omit<SyncedCrudPropsMany<TRemote, TRemote, TOption>, 'list'> {
>
extends SyncedSupabaseConfig<TRemote, TLocal>, Omit<SyncedCrudPropsMany<TRemote, TRemote, TOption>, 'list'> {
supabase?: Client;
collection: Collection;
schema?: SchemaName;
Expand Down
12 changes: 8 additions & 4 deletions src/sync-plugins/tanstack-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ import {

let nextMutationKey = 0;

export interface ObservableQueryOptions<TQueryFnData, TError, TData, TQueryKey extends QueryKey>
extends Omit<QueryObserverOptions<TQueryFnData, TError, TData, TData, TQueryKey>, 'queryKey'> {
export interface ObservableQueryOptions<TQueryFnData, TError, TData, TQueryKey extends QueryKey> extends Omit<
QueryObserverOptions<TQueryFnData, TError, TData, TData, TQueryKey>,
'queryKey'
> {
queryKey?: TQueryKey | (() => TQueryKey);
}

export interface SyncedQueryParams<TQueryFnData, TError, TData, TQueryKey extends QueryKey>
extends Omit<SyncedOptions<TData>, 'get' | 'set' | 'retry'> {
export interface SyncedQueryParams<TQueryFnData, TError, TData, TQueryKey extends QueryKey> extends Omit<
SyncedOptions<TData>,
'get' | 'set' | 'retry'
> {
queryClient: QueryClient;
query: ObservableQueryOptions<TQueryFnData, TError, TData, TQueryKey>;
mutation?: MutationObserverOptions<TQueryFnData, TError, TData>;
Expand Down
9 changes: 4 additions & 5 deletions src/sync/syncTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,10 @@ export interface SyncedOptions<TRemote = any, TLocal = TRemote> extends Omit<Lin
// log?: (message?: any, ...optionalParams: any[]) => void;
}

export interface SyncedOptionsGlobal<T = any>
extends Omit<
SyncedOptions<T>,
'get' | 'set' | 'persist' | 'initial' | 'waitForSet' | 'waitFor' | 'transform' | 'subscribe'
> {
export interface SyncedOptionsGlobal<T = any> extends Omit<
SyncedOptions<T>,
'get' | 'set' | 'persist' | 'initial' | 'waitForSet' | 'waitFor' | 'transform' | 'subscribe'
> {
persist?: ObservablePersistPluginOptions & Omit<PersistOptions, 'name' | 'transform' | 'options'>;
}

Expand Down
12 changes: 12 additions & 0 deletions tests/tests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3881,4 +3881,16 @@ describe('Misc', () => {
expect(descriptProxy).toEqual(undefined);
expect(descriptValue).toEqual(undefined);
});
test('Observable is compatible with vitest equality checks and error printing', () => {
const obs = observable({ foo: 'bar' });
const obj = obs.foo;
const ctor = obj.constructor;

// https://github.com/vitest-dev/vitest/blob/v3.2.4/packages/expect/src/jest-utils.ts#L42-L49
expect(!!obj && typeof obj === 'object' && 'asymmetricMatch' in obj).toBe(false);

// https://github.com/vitest-dev/vitest/blob/v3.2.4/packages/pretty-format/src/index.ts#L288
expect(typeof ctor).toBe('function');
expect(obj.constructor).toBe(ctor);
});
});