@fishertsau/moonlight
Version: 
Various helpers or lib for javascript projects
783 lines (624 loc) • 17.9 kB
Markdown
# moonlight
- 提供JavaScript常用的功能,類似helper或是utility,讓javascript專案使用
- 可用於Server-side專案,如node.js,或是Client-side專案,如vue,react中
- 發佈於npm中,使用時當作一個package使用
- 可用於es6與commonjs中
## Main functions
1. Validator
2. time
3. array
4. hostname
5. Checker
6. Utils
7. MoonUse
8. Model
### Input Validator
- check the input validity with specified rules
- only the validated attribute(s) are returned
- convert data type on validating, e.g '100' to 100 for 'type:number'
- rules
    - required
    - type:[string|date|datetime|isoDatetime|number|email|bool|boolean|null|nonEmptyString|object]
    - same:attributeName
- validator:: obj -> obj -> obj
- examples
  ```javascript
     const rules = {
          foo: 'required|type:null', // null
          boo: 'type:bool',  // true, false, 'true', 'false'
          boo: 'type:boolean',  // true, false, 'true', 'false'
 
         // string 
          moo: 'type:string', // string
          moo: 'required|type:emptyString',  // ''
          moo: 'required|type:nonEmptyString',  // non empty string 
          moo: 'required|type:lowerCaseOrNumberString',  // lower case or number string (e.g. 'abc123')
          moo: 'required|type:isNumberString',  // number string  (e.g. '123')
   
          // object 
          boo: 'type:object',  // object
          boo: 'type:emptyObject',  // {}
 
          // same content 
          bar: 'required|same:foo',
  
          // valid values
          loo: 'validValues:[v1,v2,v3]',
  
          aoo: 'type:array', // array
          aoo: 'type:emptyArray', // []
          aoo: 'type:arrayOfString', // ['a','b']
          aoo: 'type:arrayOfInt', // [1,2]
          aoo: 'type:arrayOfNull', // [null, null]
 
          // multiple types 
          soo: 'type:[number,null,nonEmptyString]', // number, null, non empty string
 
         // number & integer 
          foo: 'required|type:number', // number (e.g. 1.2)
          aoo: 'type:int', // integer (e.g. 1)
          aoo: 'type:positiveInt', // > 0
          aoo: 'type:positiveNum', // > 0
          aoo: 'type:positiveNumWithZero', // >= 0
  
          // date & datetime 
          too: 'type:datetime',  // yyyy-mm-dd hh:mm:ss (e.g. '2000-01-01 12:00:00')
          too: 'type:isoDatetime',  // yyyy-mm-ddThh:mm:ss+08:00 (e.g. '2000-01-01T12:00:00+08:00')
          too: 'type:date', // yyyy-mm-dd (e.g. '2000-01-01')
          too: 'type:yearMonthDay', // {year:1, month: 1, day: 1}
  
            // application type
          foo:'type:twInvoiceDonationOrgCode', //  愛心碼 Taiwan invoice donation code 3 至 7 位數字  (e.g. '12345')
  
          // credit card
          foo: 'type:creditCardNum', // credit card number (e.g. '1234567890123456') 
          foo: 'type:creditCardCvvCvc', // credit card cvv/cvc (e.g. '123')
          foo: 'type:creditCardExpDate', // credit card expiration date (e.g. '01/23')
          // length: same length
          foo: 'sameLengthArr:bar', // same length as bar (e.g. '123' and 'abc')
          foo: 'minLen:3', // min length  (e.g. '123')
          foo: 'maxLen:5', // max length  (e.g. '12345')
 
          // range
          foo: 'range:1,10', // 1 <= foo <= 10 (for int only)
  
          // requiredWhenExists
          foo: 'requiredWhenExists:boo', // required when boo is not empty  (e.g. 'foo' is required when 'boo' is not empty)
          foo: 'requiredIfValueIs:bar:1', // required when bar is 1  (e.g. 'foo' is required when 'bar' is 1)
  
          // requiredIfValueIs
          foo: 'requiredIfValueIs:bar:true', // required when bar is true  (e.g. 'foo' is required when 'bar' is 1)
          foo: 'requiredIfValueIs:bar:10', // required when bar is 10  (e.g. 'foo' is required when 'bar' is 10)
  
         // regex
         foo: 'regex:/^\\d{3}-\\d{3}-\\d{4}$/', // regex (e.g. '123-456-7890')
         foo: 'regex:/^\\d+$/', // numbers only (e.g. '1234567890')
   
          // app types 
          koo: 'type:email', // email
          koo: 'type:hostname', // hostname (e.g. 'www.google.com')
          koo: 'type:hostnameWithPath', // hostname with path (e.g. 'www.google.com/path')
          koo: 'type:lineId', // line id (e.g. 'U1234567890abcdef1234567890abcdef')
          koo: 'type:twTaxId', // Taiwan tax id  (8 digits) (e.g. '12345678')
          koo: 'type:twMobileNo', // Taiwan mobile number (10 digits) (e.g. '0912345678')
          koo: 'type:twLandPhoneNo', // Taiwan land phone number (9 digits) (e.g. '022345678')
          koo: 'type:addressWithDefault', // address with default value (e.g. {city:1, area:1, street:'abc'})
     };
    const validatedData = { foo: 123, bar: 123, koo: '2000-01-01', loo: 'invalidValue', moo: '', aoo:'notArray'};
    let result = validator(rules)(validatedData);
    // => { validated: true ,  values:{foo:123, bar:123,koo:'2000-01-01'}}
    // => { validated: false ,
    //      errors: {
    //         foo: ['foo should be a number.'], 
    //         koo: ['koo should be a string.']
    //         loo: ['loo should be in one of the values: v1,v2,v3.']
    //         moo: ['moo should be an non empty string.']
    //         aoo: ['aoo should be in array format.']
    //      }
    //    }
  ```
### time
- now :: null -> DateTime
  ```javascript
      now();
      //=> current time
  ```
- isoStrToUtcStr:: isoDatetimeString -> utcDatetimeString
  ```javascript
      isoStrToUtcStr('1997-07-16T19:20:35+03:00');
      //=> '1997-07-16 16:20:35'
  ```
- isoStrToTaipeiStr :: isoDatetimeString -> taipeiDatetimeString
  ```javascript
     isoStrToTaipeiStr('1997-07-16T19:20:35+03:00');  // utc: 1997-07-16T16:20:35
     //=> '1997-07-17 00:20:35'
  ```
```js
{
  // base
  Dayjs,
    now,
    // string to object
    toTimeObj,
    fromIsoTimeStrToObj,
    fromTimeStrAtTZ,
    fromTpeTimeStr,
    // datetime string
    utcDatetimeStrNow, // 2000-01-01 12:00:00
    isoStrNow,
    // string to string
    isoStrToUtcStr,    // 2000-01-01T12:00:00.000Z ->  2000-01-01 12:00:00
    utcStrToIsoStr,
    isoStrToTaipeiStr, // 2000-01-01T12:00:00.000Z ->  2000-01-01 20:00:00
    datetimeStrToDateStr, // 2000-01-01 12:00:00 -> 2000-01-01
    isoStrToTpeDateStr, // 2000-01-01T12:00:00.000Z -> 2000-01-01
    // date|datetime string
    dateAtTZ,
    todayStartInTpeStr,
    todayEndInTpeStr,
    todayEndInIsoStr,
    todayInTpeStrDateOnly,
    // time obj
    todayStartInTpe,
    todayEndInTpe,
    // type check
    isIsoFormat,
    // time zone
    TZ,
    TZ_TYPE,
    // comparison
    isBefore,
    isAfter,
    isBetween,
    isSame,
    // duration
    getDuration,
    // status
    getStatusByTimeFrame,
    STATUS_BY_TIME_FRAME,
    REF_TIME_STATUS_TYPE,
    // time manipulation
    minAgoFromNow
}
```
### array
- shuffle :: array -> array
    - re-order the array items randomly
### hostname
- isValidHostname :: String -> Boolean
    - To validate a give hostname
### Checker
```
  isTrue,
  isEmpty,
  isValidBool,
  isFunction,
  isUndefined,
  // object
  isObject,
  isEmptyObject,
  // string
  isString,
  isNonEmptyString,
  isNumberString,
  isEmptyString,
  isLowerCaseOrNumberString,
  isNumberOrString,
  isNumberOrNumberString,
  // number
  isIntBetween,
  isNumber,
  isValidInteger,
  isPositiveInteger,
  isUnsignedInteger,
  isSameNumOrNumStr,
  // number format
  ifOnlyNumberAndLetter,
  ifHexNumberStr,
  ifDecimalNumberStr,
  // time
  isYMDStr,
  isDatetimeStr,
  isIsoFormat,
  isIsoFormatWithZeroOffset,
  // array
  isArray,
  isEmptyArray,
  isNonEmptyArray,
  isArrayOfString,
  isArrayOfInt,
  isArrayOfArray, // [[], []]
  isArrayOfNumber,
  isArrayOfNull,
  // application
  isValidLineId,
  isValidEmail,
  isValidTwTaxId,
  isValidTwMobileNo,
  isValidTwLandPhoneNo,
  isValidTwNationalId,
  isValidTwInvoiceDonationCode,
  isValidTwMobileInvoiceBarcode,
  isValidCreditCardNum,
  isValidCreditCardCvvCvc,
  isValidCreditCardExpDate,
  isValidHostname,
  isValidHostnameWithPath,
```
- isTrue :: a -> boolean
    - To check if a given value is true
- isEmpty :: a -> boolean
    - To check is a given value is empty
- isIntBetween:: int a -> int b -> int c -> boolean
    - To check is a given int or value is between the specified range
- isNumber:: a -> boolean
    - To check is given value is a number
- isString:: a -> boolean
    - To check is given value is a string
- isObject:: a -> boolean
    - To check is given value is an object
    - null and array are excluded
- isValidEmail:: a -> boolean
    - To check is given value is a valid email
- isValidBool:: a -> boolean
    - To check is given value is a valid boolean value
- isFunction:: a -> boolean
    - To check is given value a Function
- isValidHostname:: a -> boolean
    - To check if a given value a valid hostname
- isYMDStr:: str a -> boolean
    - To check is given string is in date format
    - valid date format: yyyy-mm-dd (time is not included)
- isDateTime:: str a -> boolean
    - To check is given string is in datetime format
    - valid datetime format includes: unix time (integer), date+time, UTC
- isIsoFormat:: a -> boolean
    - To check is given value is in ISO 8601 format
- isIsoFormatWithZeroOffset:: a -> boolean
    - To check is given value is in ISO 8601 format with zero offset
### Utils
- clearSpace:: string -> string
    - To clear or remove space in a string
- trim
    - To trim the string(s) in object properties and values
    - To trim string(s) in list
    - Can trim string(s) in nested objects or nested array
    ```js
       trim({
          'p1  ': 'foo ',
          p2: ['  abc', ' def  '],
          p3: { p3_1: ['p31 '] },
          p4: { p4_1: ['p41 ', ' p42'], 'p4_2': {} },
          p5: 100,
         });
       //=> 
        {
          p1: 'foo',
          p2: ['abc', 'def'],
          p3: { p3_1: ['p31'] },
          p4: { p4_1: ['p41', 'p42'], 'p4_2' : {} },
          p5: 100,
        }
    ```
- extractByPath
    - To extract a value from a structured collection object
    - Object structure
      ```javascript
         {
            k1: {
              k1a: {}
              k1b: {}
            },
            k2: {
              k2a: {}
              k2b: {}
            }
         }
      ```
    - example:
  ```javascript
    extractByPath(['info', 'age'])({'person1':{info:{age:10}}, 'person2':{info:{age:20}}});
    //=>  {'person1':10, 'person2':20}
  ```
- getDirty
    - To get the values in new object which differ from that in the original object
    - The function is auto-curry
    - example:
  ```javascript
   oriObj = {a:1, b:3}
   newObj = {a:1, b:5, c:7}
   getDirty(oriObj)(newObj);
    //=> {b:5, c:7}
  ```
- renameKey
    - To change a key name
    - example:
  ```javascript
   oriObj = {foo:1}
   renameKey('foo','bar')(oriObj);
    //=> {bar:1}
  ```
- createEventEmitter
    - To create an event emitter
    - example:
  ```javascript
  const em = createEventEmitter(); 
  // register event handler
  em.on('someEvent', someHandler); 
  
  // remove event handler 
  // A. remove specific handler
  em.remove('someEvent', someHandler); 
  // B. remove all handlers
  em.remove('someEvent'); 
  
  // send out event
  em.emit('someEvent', payload); 
  ```
- pluckObjProp
    - To pluck a nested obj prop
    - example:
  ```javascript
    // Functor f => k -> {k:v} -> {k:v}
    const obj = {
                  prop1: { foo: 123, bar: 'abc' },
                  prop2: { foo: 999, bar: 'abc' }
          }
    pluckObjProp('foo')(obj)
    //=> {prop1: 123, prop2: 999}
  ```
- clean
    - To remove obj props if value is undefined/null/emptyString
    - Applicable to nested object
  ```javascript
     clean({
        foo: 123,
        bar: undefined,
        koo: {
          k1: 'abc',
          k2: undefined,
        },
        poo: null,
        moo: '',
    })
     //=> { foo: 123, koo: { k1:'abc'} }
  ```
- cleanNilUndefined
    - To remove obj props if value is undefined/null
    - Applicable to nested object
  ```javascript
     clean({
        foo: 123,
        bar: undefined,
        koo: {
          k1: 'abc',
          k2: undefined,
        },
        poo: null,
        moo: '',
    })
     //=> { foo: 123, koo: { k1:'abc'} moo: '' }
  ```
- hasValue
    - To check if an object has specified value
    - Applicable to simple object
  ```javascript
     hasValue(123, { foo: 123 })
     //=> true
  
     hasValue('abc', { foo: 123 })
     //=> false 
  ```
- rmArrSquareInReqParamsKey
  ```javascript
     harmArrSquareInReqParamsKeys(
        {
          from: 0,
          'ids[]': [ 'abc1705548556993', 'abc1705558802725']
        });
     //=> 
        {
           from: 0,
           'ids': [ 'abc1705548556993' , 'abc1705558802725']
         };
  ```
- getObjectDifference
 ```js
     const obj1 = {
  a: 1,
  b: {
    c: 2,
    d: {
      e: 3,
    },
  },
  f: {
    g: 5,
  },
};
const obj2 = {
  a: 1,
  b: {
    c: 3,
    d: {
      e: 4,
    },
  },
};
getObjectDifference(obj1, obj2);
//=> 
{
  {
    'b.c'
  :
    [2, 3], 'b.d.e';
  :
    [3, 4], f;
  :
    [{ g: 5 }, undefined];
  }
 ```
- parseObj :: obj -> QueryString
    - convert an object to query string
    - example:
      ```javascript
        parsrStr({a:'foo',b:'bar'})
        //=> a=foo&b=bar
      ```
- parseStr :: QueryString -> obj
    - convert a query string to an object
    - example:
      ```javascript
        parsrObj('a=foo&b=bar')
        //=> {a:'foo',b:'bar'}
      ``` 
- convertKeyValue :: [a] -> obj (a: 'str=str')
    - convert a key-value array to an object
  ```javascript
     convertKeyValue(['foo=bar', 'fiz=fuz']);
     //=> {foo:'bar', fiz:'fuz'}
  ```
- convertObjToArr :: obj -> [a] (a: 'str=str')
    - convert an object to a key-value array
  ```javascript
     convertKeyValue({ foo: 'bar', fiz: 'fuz' });
     //=> ['foo=bar', 'fiz=fuz']
  ```
- snakeToCamel :: str -> str
    - convert a snake-string to camel-case string
  ```javascript
     snakeToCamel('abc_def_ghi')
     //=> 'abcDefGhi'
  ```
- objKeyToCamel :: obj -> obj
    - convert all keys in an object from snake to camel string
    - nested object is supported
  ```javascript
     objKeyToCamel({this_is_key: val})
     //=> {thisIsKey: val}
  ```
- camelToSnake :: str -> str
    - convert a camel-case string to snake-case string
  ```javascript
     camelToSnake('abcDefGhi')
     //=> 'abc_def_ghi'
  ```
- objKeyToSnake :: obj -> obj
    - convert all keys in an object from camel to snake-case string
    - nested object is supported
  ```javascript
     objKeyToSnake({ thisIsKey: 'someVal' })
     //=> { this_is_key: 'someVal' }
  ```
- parseCookie :: string -> obj
    - parse a cookie string to an object
  ```javascript
     parseCookie('foo=bar ; equation=E%3Dmc%5E2;   asd=');
     //=> { foo: 'bar', equation: 'E=mc^2', asd: '' }
  ```
- serializeCookie :: obj -> string
    - serialize an object to cookie string
  ```javascript
     serializeCookie({ foo: 'bar', equation: 'E=mc^2', asd: '' });
     //=> 'foo=bar;equation=E%3Dmc%5E2;asd='
  ```
### MoonUse
- useRetry
- useRetryAsync
```javascript
  useRetryAsync({
  operation: async () => someAsyncOperation(),
  maxRetries: 3,
  backoffStrategy: (attempt) => 500 * attempt,
  shouldRetry: (error) => error.message === 'some error',
});
```
- useLock
- useWait
    - To make an action execution wait for a given time before execution
  ```javascript
    useWait(1000, action);
    //=> wait for 1000ms, and then run action 
  - ```
- useRedisCache
    - To get value from redis cache, otherwise get the value and cache it
  ```javascript
    const foo = await useCache('someCacheKey', getter, {keyLife:100});
    //=> keyLife: key有效時間 (in second)
  - ```
### Model
- basicTypes
```
  YES_NO_TYPE,
  NULLABLE_YES_NO_TYPE,
  STRING_TYPE,
  NON_EMPTY_STRING_TYPE,
  NUMBER_OR_STRING_TYPE,
  NULLABLE_STRING_TYPE,
  INTEGER_TYPE,
  UNSIGNED_INTEGER_TYPE,
  NULLABLE_INTEGER_TYPE,
  POSITIVE_INTEGER_TYPE,
  NULLABLE_UNSIGNED_INTEGER_TYPE,
  BIG_INTEGER_TYPE,
  NULLABLE_BIG_INTEGER_TYPE,
  UNSIGNED_TINY_INTEGER_TYPE,
  NUMBER_TYPE,
  ARRAY_TYPE,
  ARRAY_OF_INT_TYPE,
  NULLABLE_ARRAY_TYPE,
  OBJECT_TYPE,
  NULLABLE_OBJECT_TYPE,
  FUNCTION_TYPE,
  NULLABLE_FUNCTION_TYPE,
```
    - example
    ```
         Model.basicTypes.STRING_TYPE;
    ```
- datetimeTypes
```
  DATE_TYPE,
  NULLABLE_DATE_TYPE,
  DATETIME_TYPE,
  NULLABLE_DATETIME_TYPE,
  ISO_DATETIME_TYPE,
  NULLABLE_ISO_DATETIME_TYPE,
```
- appTypes
```
  EMAIL_TYPE,
  NULLABLE_EMAIL_TYPE,
  EMPTY_STRING_OR_EMAIL_TYPE,
  LINE_ID_TYPE,
  EMPTY_STRING_OR_LINE_ID_TYPE,
  CITY_TYPE,
  CITY_AREA_TYPE,
  MEDIA_TYPE,
  TW_TAX_ID_TYPE,
  NULLABLE_TW_TAX_ID_TYPE,
  TW_MOBILE_NO_TYPE,
  HOSTNAME_TYPE,
  NULLABLE_HOSTNAME_TYPE,
  EMPTY_STRING_OR_HOSTNAME_TYPE,
```
- appTypes.CITY_TYPE (台灣城市)
    - 結構: 整數 (城市編號)
    - properties:
        - validator
        - cityList (台灣城市清單)
    - 會檢查縣市是否為合法值
- appTypes.CITY_AREA_TYPE (台灣城市與區域)
    - 結構: ``` {cityId: 1, areaId: 1} ```
    - properties:
        - validator
        - cityList (台灣城市清單)
        - areaList (台灣區域清單,含城市編號與區域號碼)
    - 會檢查縣市與區域的對應,是否為合法值
    - 城市與區域未定: {cityId:0, areaId:0}
    - 使用範例
    ```
     const someModelDef = {
       location: {
         type: CITY_AREA_TYPE,
       }
     }
    ```
- cityList內容
```
  const cityList = [
   { id: 0, name: '未定義' },
   { id: 1, name: '台北市' },
   ...
  ];
```
- areaList內容
```
  const areaList = [
    { id: 0, cityId: 0, name: '未定義', zip: 0 },
    { id: 1, cityId: 1, name: '中正區', zip: 100 },
    ...
  ];
```