mushroomjs
Install
npm install mushroomjs
or
yarn add mushroomjs
Import
Simple
import mushroom from "mushroomjs";
Builder
import { Filter, Sort, Project } from 'mushroomjs';
Full
import mushroom, {
defineAsyncResource,
defineAsyncView,
fireEvent,
createRestfulAsyncFunction,
IdType,
IMushroom,
Mushroom,
MushroomRequest,
MushroomRequestSetting,
MushroomRestfulRequest,
MushroomResponse,
MushroomResourceBase,
MushroomListResource,
MushroomFindByIdResource,
MushroomCreateResource,
MushroomBatchCreateResource,
MushroomUpdateResource,
MushroomBatchUpdateResource,
MushroomPartialUpdateResource,
MushroomDeleteResource,
MushroomBatchDeleteResource,
MushroomExtensibleResource,
MushroomError
} from 'mushroomjs';
Define resource
Defination
Create a typescript defination script. Ex: api.ts
import mushroom, {
defineAsyncResource,
IMushroom,
MushroomRequest,
MushroomRequestSetting,
MushroomResourceBase,
MushroomListResource,
MushroomCountResource,
MushroomFindByIdResource,
MushroomCreateResource,
MushroomBatchCreateResource,
MushroomUpdateResource,
MushroomBatchUpdateResource,
MushroomPartialUpdateResource,
MushroomDeleteResource,
MushroomBatchDeleteResource,
MushroomListResponse,
MushroomCountResponse
} from "mushroomjs";
interface Foo {
id?: string,
x?: number,
y?: boolean
}
interface FooCustomFunctionRequest extends MushroomRequest {
params: {
f1: boolean,
f2?: string
},
body: {
a: string,
b?: number
}
}
interface FooCustomFunctionResult {
u: string,
v: number
}
interface FooCustomFunction {
customFunctionAsync(request: FooCustomFunctionRequest): Promise<MushroomResponse<FooCustomFunctionResult>>
}
interface FooViewSampleParameters {
param1: string,
param2?: number;
}
interface FooViewSampleItemResult {
v1: IdType,
v2: boolean,
v3?: number
}
interface FooViewSample {
views: {
sampleAsync(viewParams: FooViewSampleParameters, settings?: MushroomRequestSetting): Promise<MushroomResponse<FooViewSampleItemResult[]>>
}
}
defineAsyncResource<Foo>({
name: "foo",
actions: {
findMany: {
clientCache: false, // false means disabled cache
paging: "limitOffset",
includeTotal: true
},
findById: {
clientCache: true // false means disabled cache
},
createOne: {},
createMany: {},
updateOne: {},
updateMany: {},
updatePartially: {},
deleteOne: {},
deleteMany: {},
_raw_http_method_customFunction: {}
},
views: {
sample: { clientCache: true }
}
});
mushroom.$using("your absolute root API URL");
interface MushroomApi extends IMushroom {
foo: MushroomResourceBase & MushroomListResource<Foo> & MushroomCountResource, & MushroomFindByIdResource<Foo> & MushroomCreateResource<Foo> & MushroomBatchCreateResource<Foo> & MushroomUpdateResource<Foo> & MushroomBatchUpdateResource<Foo> & MushroomPartialUpdateResource<Foo> & MushroomDeleteResource & MushroomBatchDeleteResource & FooCustomFunction & FooViewSample
}
export default mushroom as MushroomApi;
Usage
import mushroom from "./api"
async function example() {
await mushroom.foo.listAsync();
await mushroom.foo.getAllAsync();
await mushroom.foo.countAsync();
await mushroom.foo.findByIdAsync({id: yourId});
let newId = await mushroom.foo.createAsync(fooInstance);
let newIds = await mushroom.foo.batchCreateAsync(fooInstances);
await mushroom.foo.updateAsync(fooInstance);
await mushroom.foo.batchUpdateAsync(fooInstances);
await mushroom.foo.partialUpdateAsync(fooInstance);
await mushroom.foo.deleteAsync(id);
await mushroom.foo.batchDeleteAsync(ids);
await muuhroom.foo.customFunction({ params: {f1: true}, body: {a: 10} });
let result = await mushroom.foo.views.sample({param1: "abc", param2: 1});
}
Customize headers or/and params of common action
mushroom.foo.createAsync(fooInstance, {
extra: {
headers: {
// your custom headers
},
params: {
// your custom params
}
}
});
[Advanced] Call as prefer role
To specify the role of user, use field preferRole of second parameter. Eg:
The current user has 2 roles Admin and User, api foo.list supports both roles and Admin is in higher priority, to switch to User role, please follow this example:
mushroom.foo.listAsync(arg, {
preferRole: 'User'
});
[Advanced] Raw request body for customized API
In some special APIs, request data is required in other than JSON format. In this case, use isRawData to tell mushroomjs. Eg:
// Request body in BINARY format
mushroom.foo.specialApiAsync({
body: file, // File
settings: {
isRawData: true,
extra: {
headers: {
'Content-Type': 'image/jpeg' // change to the MIME type of your file or use octet/stream for general case
}
}
}
});
// Request body in FORMDATA format
mushroom.foo.specialApiAsync({
body: formdata, // FormData
settings: {
isRawData: true
}
});
[Advanced] Text response and Blob response
In some special APIs, response data is not in JSON format. In this case, please do not use response.result, use response.resultAsText(): string or response.resultAsBlob(): Blob instead. Ex:
const response = await mushroom.foo.specialApiAsync(/*parameters here if any*/);
const text = response.resultAsText();
Define global view
Global view defination
Create a typescript defination script. Ex: api.ts
import mushroom, {
defineAsyncView,
IMushroom,
MushroomRequestSetting
} from "mushroomjs";
interface SampleViewParameters {
param1: string,
param2?: number;
}
interface SampleViewItemResult {
v1: IdType,
v2: boolean,
v3?: number
}
interface SampleView {
sampleAsync(viewParams: SampleViewParameters, settings?: MushroomRequestSetting): Promise<MushroomResponse<SampleViewItemResult[]>>
}
defineAsyncView("sample", { clientCache: true });
mushroom.$using("your absolute root API URL");
interface MushroomApi extends IMushroom {
$view: SampleView
}
export default mushroom as MushroomApi;
Global view usage
import mushroom from "./api"
async function example() {
let result = await mushroom.$views.sample({
param1: "abc",
param2: 1
});
}
Event
Register event handler
import mushroom, { MushroomRestfulRequest, MushroomRequest, MushroomError } from "mushroomjs";
mushroom._on("eventName", fnEventHandler);
Remove event handler
mushroom._unbindEvent("eventName"); // remove all event handlers of 'eventName' event
mushroom._unbindEvent("eventName", fnEventHandler); // remove specific event handler of 'eventName' event
Reflection
mushroom._hasEvent("eventName"); // return true if eventName has handler(s)
Built-in events
On request begining
mushroom._on("beginRequest", (args: BeginMushroomRequestHandlerArguments) => {});
Affter request ended
mushroom._on("endRequest", (args: EndMushroomRequestHandlerArguments) => {});
Before sending request
mushroom._on("beforeSend", (request: MushroomRestfulRequest, rawRequest: MushroomRequest) => {});
Switch to online state
mushroom._on("online", () => {});
Fall to offline state
mushroom._on("offline", () => {});
API URL
mushroom.$using(rootApiUrl); // set root API URL
let url = mushroom.$using(); // get current root API URL
[Advanced] Settings for each request
preferRole
override cache settings
To override global cache age, see Request level at Cache age
override global request timeout
To override global request timeout
mushroom.foo.listAsync(arg, {
timeout: 3000, // timeout after 3000 miliseconds or 3 seconds
});
inject events for each request
mushroom.foo.listAsync(arg, {
beforeSend: (request, rawRequest) => {}
});
abort request
To abort a request, use AbortController in setting. Ex:
const abortController = new AbortController();
setTimeout(() => abortController.abort(), 500);
try {
const res = await mushroom.foo.listAsync({}, {
abortController: abortController
});
console.log(res);
}
catch (e) {
console.error(e);
}
Custom methods:
const abortController = new AbortController();
setTimeout(() => abortController.abort(), 500);
try {
const res = await mushroom.foo.barAsync({
settings: {
abortController: abortController
}
});
console.log(res);
}
catch (e) {
console.error(e);
}
Caching
By default, mushroom js driver supports client cache for 2 methods listAsync and findByIdAsync if actions.findMany.clientCache == true, actions.findById.clientCache == true
Please see Defination at Define resource.
Mushroom js driver only supports client cache for request which is GET method.
Invalid cache
Clear resource cache
mushroom.foo.invalidCache();
Clear cache by url
mushroom.$cache.invalid(url) // url: string - a RESTful url will be invalid cache
Clear cache by pattern
mushroom.$cache.invalid(pattern) // pattern: RegEx - a regular expression of RESTful urls will be invalid cache
Clear all cache
mushroom.$cache.invalid()
Cache age
Global level
mushroom.$setting.set("request.cache.age", ms); // set global cache age value (in milisecond)
Request level
let result1 = await mushroom.foo.listAsync({ }, {
cacheAge: 5000 // cache in 5 seconds
});
let result2 = await mushroom.foo.findByIdAsync({
id: "your id"
}, {
cacheAge: 10000 // cache in 10 seconds
});
let result = await mushroom.foo.customMethodAsync({ }, {
cacheAge: 15000 // cache in 15 seconds
});
Settings
Setting global cache age
mushroom.$setting.set("request.cache.age", 300000); // default: 5 minutes
Setting global request timeout
mushroom.$setting.set("request.timeout", timeout_in_ms); // default: undefined (mean: depending on each browser/system)
Setting common HTTP methods
mushroom.$setting.set("request.common_methods", ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]);
Setting flags
Log generic information
mushroom.$setting.set("diagnostic.log_info", true); // default: false
Warning on slow connection
mushroom.$setting.set("diagnostic.warning_slow_connection", true); // default: false
Slow connection info will be outputed at console.
mushroom.$setting.set("diagnostic.slow_connection_milliseconds", 2000); // default: 2 seconds
Slow connection if an API was requested that took more than diagnostic.slow_connection_milliseconds
Log request
mushroom.$setting.set("diagnostic.log_request", true); // default: false
Log response
mushroom.$setting.set("diagnostic.log_response", true); // default: false
Log cache hit
mushroom.$setting.set("diagnostic.log_cache_hit", true); // default: false
For NodeJS
Required addition dependency: node-fetch
Custom extension
Custom extension defination
mushroom.$ext = mushroom.$ext || {};
mushroom.$ext.method1Async = ... // see Defination of Custom method above
Custom extension usage
await mushroom.$ext.method1Async(1, 2, 3);
Builders
Filter builder
Usage
import { Filter } from 'mushroomjs';
let filter : IBuilder;
// create filter here
mushroom.foo.listAsync({
filters: filter.build()
})
eq
filter = Filter.eq("x", 10); // x=10
ne
filter = Filter.ne("x", 10); // x!=10
lt
filter = Filter.lt("x", 10); // x<10
lte
filter = Filter.lte("x", 10); // x<=10
gt
filter = Filter.gt("x", 10); // x>10
gte
filter = Filter.gte("x", 10); // x>=10
min
Alias of gte
filter = Filter.min("x", 10); // x>=10
max
Alias of lte
filter = Filter.max("x", 10); // x<=10
in
filter = Filter.in("x", [1, 2, 3]); // x:in:1,2,3
nin
filter = Filter.nin("x", [1, 2, 3]); // x:nin:1,2,3
all
filter = Filter.all("x", [1, 2, 3]); // x:all:1,2,3
like
filter = Filter.like("x", "%abc%"); // x:like:%abc% (% will be url-encoded to %25)
regex
Without options
filter = Filter.regex("x", /ab[cd]/); // x:regex:ab[cd]
filter = Filter.regex("x", "ab[cd]"); // x:regex:ab[cd]
With options
filter = Filter.regex("x", /ab[cd]/i); // x:regex_i:ab[cd]
filter = Filter.regex("x", "ab[cd]", "i"); // x:regex_i:ab[cd]
filter = Filter.regex("x", /ab[cd]/m); // x:regex_m:ab[cd]
filter = Filter.regex("x", "ab[cd]", "m"); // x:regex_m:ab[cd]
filter = Filter.regex("x", /ab[cd]/im); // x:regex_im:ab[cd]
filter = Filter.regex("x", "ab[cd]", "im"); // x:regex_im:ab[cd]
elementMatch
filter = Filter.elementMatch("x", [Filter.eq("y", 10), Filter.gt("z", 3)]); // x:elemMatch:y=10,z>3
nelementMatch
filter = Filter.nelementMatch("x", [Filter.eq("y", 10), Filter.gt("z", 3)]); // x:nelemMatch:y=10,z>3
size
filter = Filter.size("x", 4); // x:size:4
isNull
filter = Filter.isNull("x"); // x:is_null:true
filter = Filter.isNull("x", false); // x:is_null:false
and
filter = Filter.and([Filter.eq("y", 10), Filter.gt("z", 3)]); // y=10&z>3
filter = Filter.and(Filter.eq("y", 10), Filter.gt("z", 3)); // y=10&z>3
or
filter = Filter.or([Filter.eq("y", 10), Filter.gt("z", 3)]); // y=10|z>3
filter = Filter.or(Filter.eq("y", 10), Filter.gt("z", 3)); // y=10|z>3
empty
filter = Filter.empty(); // empty string
filter chain
filter = Filter.eq("a", 1).lt("b", 2); // a=1&b<2
filter = Filter.eq("a", 1).lt("b", 2).or(Filter.eq("c", 3).gte("d", 4)); // :and:a=1,b=2|:and:c=3,d=4
filter = Filter.eq("a", 1).lt("b", 2).and(Filter.or(Filter.eq("c", 3), Filter.gte("d", 4))); // a=1&b=2&c=3|d=4
filter = Filter.empty().eq("a", 1); // a=1
toArray
filters = Filter.eq("a", 1).lt("b", 2).toArray() // [Filter.eq("a", 1), Filter.lt("b", 2)]
filters = Filter.eq("a", 1).or("b", 2).toArray() // [Filter.eq("a", 1), Filter.lt("b", 2)]
filters = Filter.elementMatch("x", [Filter.eq("a", 1), Filter.lt("b", 2)]).toArray() // [Filter.eq("a", 1), Filter.lt("b", 2)]
filters = Filter.eq("a", 1).toArray() // [Filter.eq("a", 1), Filter.lt("b", 2)]
asArray
filters = Filter.eq("a", 1).lt("b", 2).asArray() // [Filter.eq("a", 1), Filter.lt("b", 2)]
filters = Filter.eq("a", 1).or("b", 2).asArray() // [Filter.eq("a", 1), Filter.lt("b", 2)]
filters = Filter.elementMatch("x", [Filter.eq("a", 1), Filter.lt("b", 2)]).asArray() // [Filter.eq("a", 1), Filter.lt("b", 2)]
filters = Filter.eq("a", 1).asArray() // Error
variables
filters = Filter.eq("d", Filter.variables.now()) // d=$now
filters = Filter.eq("d", Filter.variables.today()) // d=$today
filters = Filter.eq("d", Filter.variables.userId()) // d=$user_id
filters = Filter.eq("ip", Filter.variables.userIp()) // ip=$user_ip
filters = Filter.eq("d", Filter.variables.nowAdd(5, 'hour')) // d=$now+5hours
filters = Filter.eq("d", Filter.variables.todayAdd(2, 'week')) // d=$today+2weeks
filters = Filter.in("company_id", Filter.variables.fromName<string>('company_id')) // company_id=$company_id
filters = Filter.in("number", Filter.variables.fromName<number[]>('my_numbers')) // number:in:$my_numbers
Sort builder
Usage
import { Sort } from 'mushroomjs';
let sort : IBuilder;
// create sort here
mushroom.foo.listAsync({
sort: sort.build()
})
ascending
sort = Sort.ascending("x"); // x
descending
sort = Sort.descending("x"); // -x
thenByAscending
sort = Sort.ascending("x").thenByAscending("y"); // x,y
thenByDescending
sort = Sort.ascending("x").thenByDescending("y"); // x,-y
Project builder
Usage
import { Project } from 'mushroomjs';
let project : IBuilder;
// create project here
mushroom.foo.listAsync({
fields: project.build()
})
include
project = Project.include("x"); // x
project = Project.include("x", "y"); // x,y
project = Project.include("x").include("y"); // x,y
Release notes
1.2.0
New features:
- Add new setting to specify the common HTTP methods (not need to use header X-HTTP-Method-Override):
request.common_methods - Add new rest object field in
MushroomRestfulRequestto customizefetch - Allow to customize request headers and params of common actions (such as findById, list, create...)
- Support raw request data
- Support variables in filter builder
1.1.1
Fix bug:
- Get wrong result when using nested
andfilter in FilterBuilder
1.1.0
New features:
- Support Typescript
- Support NodeJS
- Support Abort request
- Support softDelete
- Support
preferRolefor request - Support
channelforvn_text - Add builders (Filter, Sort, Project)
- Add new settings for log actions, such as
diagnostic.log_info,diagnostic.log_cache_hit - Add new methods
countAsync,getAllAsyncfor resource - Add new methods
resultAsBlob,resultAsTextfor reponse.
Fix bugs:
- iOS: missing escape url for filter
- Missing headers which were passed in each api call time
- Wrong in some cases when call api
deleteAsyncandbatchDeleteAsync