Fix set/get parameters to use Record<string,*>. (#1094)

* Add TypeScript test file & tsd as devDep to run it

This helps those who want to contribute to the TypeScript definitions
by adding:
 * a tool called `tsd,`
 * its corresponding configuration in package.json
 * a teste file index.test-d.ts

Also add a extra line between type definitions to improve readability.

* Fix set/get parameters to use Record<string,*>.

The previous implementation did not allow users to
retrieve items that were inserted using "set," and
it incorrectly used ArrayOf to extract record types.

The previous implementation also prevented users
from ever inserting an array. Although it the database
does not handle arrays elegantly, it allows the user
to insert them. Typing should not prohibit use of
arrays.
This commit is contained in:
Bernie Telles 2021-07-30 00:20:32 -07:00 committed by GitHub
parent 70eb769209
commit 5d5432a9b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 94 additions and 65 deletions

View File

@ -1,6 +1,17 @@
{
"compilerOptions": {
"emitDeclarationOnly": true,
"declaration": true
}
"declaration": true,
},
"include": [
// only compile the type definitions by default.
// That is, ignore the files in 'examples/**'
"types/**.d.ts"
],
"exclude": [
// Among the type definitions, don't try to compile
// the test file which implements bad types.
// It implements bad types for testing purposes.
"types/index.test-d.ts"
]
}

93
types/chain.d.ts vendored
View File

@ -1,4 +1,4 @@
import { AlwaysDisallowedType, DisallowPrimitives, DisallowArray, AckCallback, ArrayOf, ArrayAsRecord, Saveable, IGunCryptoKeyPair } from './types';
import { AlwaysDisallowedType, DisallowPrimitives, AckCallback, Saveable, IGunCryptoKeyPair } from './types';
import { IGunConstructorOptions } from './options';
declare type ITSResolvable<R> = R | PromiseLike<R>;
@ -19,33 +19,47 @@ export interface IGunChainReference<DataType = Record<string, any>, ReferenceKey
* @param callback invoked on each acknowledgment
* @param options additional options (used for specifying certs)
*/
put(data: Partial<AlwaysDisallowedType<DisallowPrimitives<IsTop, DisallowArray<DataType>>>>, callback?: AckCallback | null, options?: { opt?: { cert?: string } }): IGunChainReference<DataType, ReferenceKey, IsTop>;
put(data: Partial<AlwaysDisallowedType<DisallowPrimitives<IsTop, DataType>>>, callback?: AckCallback | null, options?: { opt?: { cert?: string } }): IGunChainReference<DataType, ReferenceKey, IsTop>;
/**
* Where to read data from.
* @param key The key is the ID or property name of the data that you saved from earlier
* (or that will be saved later).
* * Note that if you use .put at any depth after a get it first reads the data and then writes, merging the data as a partial update.
* @param callback You will usually be using gun.on or gun.once to actually retrieve your data,
* not this callback (it is intended for more low level control, for module and extensions).
* **.set does not means 'set data', it means a Mathematical Set**
*
* **Avoid use callback. The type in the document may be wrong.**
* Add a unique item to an unordered list.
* `gun.set` works like a mathematical set, where each item in the list is unique.
* If the item is added twice, it will be merged.
*
* **Here the type of callback respect to the actual behavior**
* **This means only objects, for now, are supported.**
* @param data the object to add to the set
* @param callback optional function to invoke when the operation is complete
* @param options additional options (used for specifying certs)
*/
get<K extends keyof DataType>(key: ArrayOf<DataType> extends never ? K : ArrayOf<DataType>, callback?: (
set<K extends keyof DataType>(data: Partial<AlwaysDisallowedType<DisallowPrimitives<IsTop, DataType[K]>>>, callback?: AckCallback | null, options?: { opt?: { cert?: string } }): IGunChainReference<DataType, ReferenceKey, IsTop>;
/**
* Where to read data from.
* @param key The key is the ID or property name of the data that you saved from earlier
* (or that will be saved later).
* * Note that if you use .put at any depth after a get it first reads the data and then writes, merging the data as a partial update.
* @param callback You will usually be using gun.on or gun.once to actually retrieve your data,
* not this callback (it is intended for more low level control, for module and extensions).
*
* **Avoid use callback. The type in the document may be wrong.**
*
* **Here the type of callback respect to the actual behavior**
*/
get<K extends keyof DataType>(key: Exclude<K, Array<any>>, callback?: (
/** the raw data. Internal node of gun. Will not typed here. */
paramA: Record<'gun' | '$' | 'root' | 'id' | 'back' | 'on' | 'tag' | 'get' | 'soul' | 'ack' | 'put', any>,
/** the key, ID, or property name of the data. */
paramB: Record<'off' | 'to' | 'next' | 'the' | 'on' | 'as' | 'back' | 'rid' | 'id', any>) => void): IGunChainReference<DataType[K], K, IsTop extends 'pre_root' ? 'root' : false>;
/**
* Change the configuration of the gun database instance.
* @param options The options argument is the same object you pass to the constructor.
*
* The options's properties replace those in the instance's configuration but options.peers are **added** to peers known to the gun instance.
* @returns No mention in the document, behavior as `ChainReference<DataType, ReferenceKey>`
*/
* Change the configuration of the gun database instance.
* @param options The options argument is the same object you pass to the constructor.
*
* The options's properties replace those in the instance's configuration but options.peers are **added** to peers known to the gun instance.
* @returns No mention in the document, behavior as `ChainReference<DataType, ReferenceKey>`
*/
opt(options: IGunConstructorOptions): IGunChainReference<DataType, ReferenceKey>;
/**
@ -69,7 +83,7 @@ export interface IGunChainReference<DataType = Record<string, any>, ReferenceKey
* Since gun streams data, the callback will probably be called multiple times as new chunk comes in.
* To remove a listener call .off() on the same property or node.
*/
on(callback: (data: DisallowPrimitives<IsTop, AlwaysDisallowedType<ArrayAsRecord<DataType>>>, key: ReferenceKey) => void, option?: {
on(callback: (data: DisallowPrimitives<IsTop, AlwaysDisallowedType<DataType>>, key: ReferenceKey) => void, option?: {
change: boolean;
} | boolean): IGunChainReference<DataType, ReferenceKey>;
@ -77,33 +91,16 @@ export interface IGunChainReference<DataType = Record<string, any>, ReferenceKey
* Get the current data without subscribing to updates. Or `undefined` if it cannot be found.
* @returns In the document, it said the return value may change in the future. Don't rely on it.
*/
once(callback?: (data: (DisallowPrimitives<IsTop, AlwaysDisallowedType<ArrayAsRecord<DataType>>>) | undefined, key: ReferenceKey) => void, option?: {
once(callback?: (data: (DisallowPrimitives<IsTop, AlwaysDisallowedType<Record<string, DataType>>>) | undefined, key: ReferenceKey) => void, option?: {
wait: number;
}): IGunChainReference<DataType, ReferenceKey>;
/**
* **.set does not means 'set data', it means a Mathematical Set**
*
* Add a unique item to an unordered list.
* `gun.set` works like a mathematical set, where each item in the list is unique.
* If the item is added twice, it will be merged.
*
* **This means only objects, for now, are supported.**
* @param data the object to add to the set
* @param callback optional function to invoke when the operation is complete
* @param options additional options (used for specifying certs)
*/
set(data: AlwaysDisallowedType<DataType extends Array<infer U> ? U extends {
[key: string]: any;
[key: number]: any;
} ? ArrayOf<DataType> : never : never>, callback?: AckCallback | null, options?: { opt?: { cert?: string } }): IGunChainReference<ArrayOf<DataType>>;
/**
* Map iterates over each property and item on a node, passing it down the chain,
* behaving like a forEach on your data.
* It also subscribes to every item as well and listens for newly inserted items.
*/
map(callback?: (value: ArrayOf<DataType>, key: DataType) => ArrayOf<DataType> | undefined): IGunChainReference<ArrayOf<DataType>, ReferenceKey>;
map(callback?: (value: DataType, key: string) => DataType | undefined): IGunChainReference<DataType, ReferenceKey>;
/**
* Undocumented, but extremely useful and mentioned in the document
@ -139,7 +136,7 @@ export interface IGunChainReference<DataType = Record<string, any>, ReferenceKey
* **Warning**: Not included by default! You must include it yourself via `require('gun/lib/open.js')` or
* `<script src="https://cdn.jsdelivr.net/npm/gun/lib/open.js"></script>`!
*/
open?(callback: (data: ArrayAsRecord<DataType>) => void): IGunChainReference<DataType, ReferenceKey>;
open?(callback: (data: DataType) => void): IGunChainReference<DataType, ReferenceKey>;
/**
* Loads the full object once. It is the same as `open` but with the behavior of `once`.
@ -147,7 +144,7 @@ export interface IGunChainReference<DataType = Record<string, any>, ReferenceKey
* **Warning**: Not included by default! You must include it yourself via `require('gun/lib/load.js')` or
* `<script src="https://cdn.jsdelivr.net/npm/gun/lib/load.js"></script>`!
*/
load?(callback: (data: ArrayAsRecord<DataType>) => void): IGunChainReference<DataType, ReferenceKey>;
load?(callback: (data: DataType) => void): IGunChainReference<DataType, ReferenceKey>;
/**
* Returns a promise for you to use.
@ -155,8 +152,8 @@ export interface IGunChainReference<DataType = Record<string, any>, ReferenceKey
* **Warning**: Not included by default! You must include it yourself via `require('gun/lib/then.js')` or
* `<script src="https://cdn.jsdelivr.net/npm/gun/lib/then.js"></script>`!
*/
then?<R, TResult1 = ArrayAsRecord<DataType>>(onfulfilled: (value: TResult1) => ITSResolvable<R>): Promise<R>;
then?<TResult1 = ArrayAsRecord<DataType>>(): Promise<TResult1>;
// then?<R, TResult1 = DataType>(onfulfilled: (value: TResult1) => ITSResolvable<R>): Promise<R>;
// then?<TResult1 = DataType>(): Promise<TResult1>;
/**
* Returns a promise for you to use.
@ -165,12 +162,12 @@ export interface IGunChainReference<DataType = Record<string, any>, ReferenceKey
* `<script src="https://cdn.jsdelivr.net/npm/gun/lib/then.js"></script>`!
*/
promise?<R, TResult1 = {
put: ArrayAsRecord<DataType>;
put: Record<string, DataType>;
key: ReferenceKey;
gun: IGunChainReference<DataType, ReferenceKey>;
}>(onfulfilled: (value: TResult1) => ITSResolvable<R>): Promise<R>;
promise?<TResult1 = {
put: ArrayAsRecord<DataType>;
put: Record<string, DataType>;
key: ReferenceKey;
gun: IGunChainReference<DataType, ReferenceKey>;
}>(): Promise<TResult1>;
@ -184,7 +181,7 @@ export interface IGunChainReference<DataType = Record<string, any>, ReferenceKey
* `<script src="https://cdn.jsdelivr.net/npm/gun/lib/bye.js"></script>`!
*/
bye?(): {
put(data: DisallowArray<Saveable<DataType>>): void;
put(data: Saveable<DataType>): void;
};
/**
@ -194,7 +191,7 @@ export interface IGunChainReference<DataType = Record<string, any>, ReferenceKey
* **Warning**: Not included by default! You must include it yourself via `require('gun/lib/later.js')` or
* `<script src="https://cdn.jsdelivr.net/npm/gun/lib/later.js"></script>`!
*/
later?(callback: (this: IGunChainReference<DataType, ReferenceKey>, data: ArrayAsRecord<DataType>, key: ReferenceKey) => void, seconds: number): IGunChainReference<DataType, ReferenceKey>;
later?(callback: (this: IGunChainReference<DataType, ReferenceKey>, data: Record<string, DataType>, key: ReferenceKey) => void, seconds: number): IGunChainReference<DataType, ReferenceKey>;
/**
* After you save some data in an unordered list, you may need to remove it.
@ -202,17 +199,17 @@ export interface IGunChainReference<DataType = Record<string, any>, ReferenceKey
* **Warning**: Not included by default! You must include it yourself via `require('gun/lib/unset.js')` or
* `<script src="https://cdn.jsdelivr.net/npm/gun/lib/unset.js"></script>`!
*/
unset?(data: ArrayOf<DataType>): IGunChainReference<DataType, ReferenceKey>;
unset?<K extends keyof DataType>(data: K): IGunChainReference<DataType, ReferenceKey>;
/**
* Subscribes to all future events that occur on the Timegraph and retrieve a specified number of old events
*
* **Warning**: The Timegraph extension isn't required by default, you would need to include at "gun/lib/time.js"
*/
time?(callback: (data: ArrayOf<DataType>, key: ReferenceKey, time: number) => void, alsoReceiveNOldEvents?: number): IGunChainReference<DataType, ReferenceKey>;
time?<K extends keyof DataType>(callback: (data: DataType[K], key: ReferenceKey, time: number) => void, alsoReceiveNOldEvents?: number): IGunChainReference<DataType, ReferenceKey>;
/** Pushes data to a Timegraph with it's time set to Gun.state()'s time */
time?(data: ArrayOf<DataType>): void;
time?<K extends keyof DataType>(data: DataType[K]): void;
/**
* Creates a new user and calls callback upon completion.

View File

@ -23,27 +23,33 @@ interface AppState {
bool: boolean;
specstr: 'a' | 'b';
obj: {
arr2: Array<{ foo: number; bar: string }>;
arr2: Record<string, { foo: number; bar: string }>;
};
};
chatRoom: Array<{ by: string; message: string }>;
chatRoom: Record<string, { by: string; message: string }>;
}
const app = new Gun<AppState>();
// Put and get something that was previously put
app.get('object')
.get('bool')
.put(true);
app.get('object')
.get('num')
.put(1);
app.get('object')
.get('obj')
.get('arr2')
.set({ foo: 1, bar: '2' });
app.get('object').put({
bool: true
});
// Set and get something that was inserted using `set`.
const appSet = app.get('object')
.get('obj')
.get('arr2');
appSet.set({ foo: 1, bar: '2' });
// getting an auto-generated key may return an undefined value.
appSet.get('stringIdentifier').once(a => a?.foo);
expectError(
app.get('object')
.get('bool')

33
types/types.d.ts vendored
View File

@ -1,19 +1,29 @@
import { IGunChainReference } from './chain';
export declare type ArrayOf<T> = T extends Array<infer U> ? U : never;
/** Gun does not accept Array value, so we need extract to make types correct */
export declare type AllowArray<T> = ArrayOf<T> extends never ? T : ArrayOf<T>;
export declare type DisallowArray<T> = ArrayOf<T> extends never ? T : never;
/** These types cannot be stored on Gun */
export declare type AlwaysDisallowedType<T> = T extends (...args: any[]) => void ? never : T extends {
new (...args: any[]): any;
} ? never : AccessObject<T>;
export declare type DisallowArray<T> = Exclude<T, Array<any>>;
/** These types cannot be stored on Gun (functions and classes) */
export declare type AlwaysDisallowedType<T> = T extends (...args: any[]) => void ? never
: T extends { new(...args: any[]): any; } ? never
: AccessObject<T>;
export declare type AccessObject<T> = T extends object ? {
[key in keyof T]: (AlwaysDisallowedType<T[key]> extends never ? never : AccessObject<T[key]>);
} : T;
/** These types cannot be stored on Gun's root level */
export declare type DisallowPrimitives<Open, T> = Open extends false ? T : T extends string ? never : T extends number ? never : T extends boolean ? never : T extends null ? never : T extends undefined ? never : T;
export declare type ArrayAsRecord<DataType> = ArrayOf<DataType> extends never ? DataType : Record<string, any>;
export declare type DisallowPrimitives<Open, T> = Open extends false ? T
: T extends string ? never
: T extends number ? never
: T extends boolean ? never
: T extends null ? never
: T extends undefined ? never
: T;
export declare type Saveable<DataType> = Partial<DataType> | string | number | boolean | null | IGunChainReference<DataType>;
export declare type AckCallback = (ack: {
err: Error;
ok: any;
@ -21,16 +31,21 @@ export declare type AckCallback = (ack: {
err: undefined;
ok: string;
}) => void;
export declare type IGunCryptoKeyPair = Record<'pub' | 'priv' | 'epub' | 'epriv', string>;
export interface IGunRecordNodeRawBase {
'#': string;
}
export interface IGunRecordNodeRawExtra<DataType> extends IGunRecordNodeRawBase {
'>': Record<keyof DataType, number>;
}
export interface IGunRecordNodeRaw<DataType> {
'_': IGunRecordNodeRawExtra<DataType>;
}
export declare type IGunRecordNode<DataType> = {
[K in keyof DataType]: IGunRecordNodeRawBase;
} & IGunRecordNodeRaw<DataType>;